PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 3.6.0
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v3.6.0
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
← All changes | freemius/includes/class-fs-logger.php +695 -688 1.43.6.0 View file →
@@ -1,688 +1,695 @@
1 -<?php
2 - /**
3 - * @package Freemius
4 - * @copyright Copyright (c) 2015, Freemius, Inc.
5 - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
6 - * @since 1.0.3
7 - */
8 -
9 - if ( ! defined( 'ABSPATH' ) ) {
10 - exit;
11 - }
12 -
13 - class FS_Logger {
14 - private $_id;
15 - private $_on = false;
16 - private $_echo = false;
17 - private $_file_start = 0;
18 - /**
19 - * @var int PHP Process ID.
20 - */
21 - private static $_processID;
22 - /**
23 - * @var string PHP Script user name.
24 - */
25 - private static $_ownerName;
26 - /**
27 - * @var bool Is storage logging turned on.
28 - */
29 - private static $_isStorageLoggingOn;
30 - /**
31 - * @var int ABSPATH length.
32 - */
33 - private static $_abspathLength;
34 -
35 - private static $LOGGERS = array();
36 - private static $LOG = array();
37 - private static $CNT = 0;
38 - private static $_HOOKED_FOOTER = false;
39 -
40 - private function __construct( $id, $on = false, $echo = false ) {
41 - $this->_id = $id;
42 -
43 - $bt = debug_backtrace();
44 - $caller = $bt[2];
45 -
46 - if ( false !== strpos( $caller['file'], 'plugins' ) ) {
47 - $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' );
48 - } else {
49 - $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' );
50 - }
51 -
52 - if ( $on ) {
53 - $this->on();
54 - }
55 - if ( $echo ) {
56 - $this->echo_on();
57 - }
58 - }
59 -
60 - /**
61 - * @param string $id
62 - * @param bool $on
63 - * @param bool $echo
64 - *
65 - * @return FS_Logger
66 - */
67 - public static function get_logger( $id, $on = false, $echo = false ) {
68 - $id = strtolower( $id );
69 -
70 - if ( ! isset( self::$_processID ) ) {
71 - self::init();
72 - }
73 -
74 - if ( ! isset( self::$LOGGERS[ $id ] ) ) {
75 - self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo );
76 - }
77 -
78 - return self::$LOGGERS[ $id ];
79 - }
80 -
81 - /**
82 - * Initialize logging global info.
83 - *
84 - * @author Vova Feldman (@svovaf)
85 - * @since 1.2.1.6
86 - */
87 - private static function init() {
88 - self::$_ownerName = function_exists( 'get_current_user' ) ?
89 - get_current_user() :
90 - 'unknown';
91 - self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
92 - self::$_abspathLength = strlen( ABSPATH );
93 - self::$_processID = mt_rand( 0, 32000 );
94 -
95 - // Process ID may be `false` on errors.
96 - if ( ! is_numeric( self::$_processID ) ) {
97 - self::$_processID = 0;
98 - }
99 - }
100 -
101 - private static function hook_footer() {
102 - if ( self::$_HOOKED_FOOTER ) {
103 - return;
104 - }
105 -
106 - if ( is_admin() ) {
107 - add_action( 'admin_footer', 'FS_Logger::dump', 100 );
108 - } else {
109 - add_action( 'wp_footer', 'FS_Logger::dump', 100 );
110 - }
111 - }
112 -
113 - function is_on() {
114 - return $this->_on;
115 - }
116 -
117 - function on() {
118 - $this->_on = true;
119 -
120 - if ( ! function_exists( 'dbDelta' ) ) {
121 - require_once ABSPATH . 'wp-admin/includes/upgrade.php';
122 - }
123 -
124 - self::hook_footer();
125 - }
126 -
127 - function echo_on() {
128 - $this->on();
129 -
130 - $this->_echo = true;
131 - }
132 -
133 - function is_echo_on() {
134 - return $this->_echo;
135 - }
136 -
137 - function get_id() {
138 - return $this->_id;
139 - }
140 -
141 - function get_file() {
142 - return $this->_file_start;
143 - }
144 -
145 - private function _log( &$message, $type = 'log', $wrapper ) {
146 - if ( ! $this->is_on() ) {
147 - return;
148 - }
149 -
150 - $bt = debug_backtrace();
151 - $depth = $wrapper ? 3 : 2;
152 - while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) {
153 - $depth ++;
154 - }
155 -
156 - $caller = $bt[ $depth ];
157 -
158 - /**
159 - * Retrieve the correct call file & line number from backtrace
160 - * when logging from a wrapper method.
161 - *
162 - * @author Vova Feldman
163 - * @since 1.2.1.6
164 - */
165 - if ( empty( $caller['line'] ) ) {
166 - $depth --;
167 -
168 - while ( $depth >= 0 ) {
169 - if ( ! empty( $bt[ $depth ]['line'] ) ) {
170 - $caller['line'] = $bt[ $depth ]['line'];
171 - $caller['file'] = $bt[ $depth ]['file'];
172 - break;
173 - }
174 - }
175 - }
176 -
177 - $log = array_merge( $caller, array(
178 - 'cnt' => self::$CNT ++,
179 - 'logger' => $this,
180 - 'timestamp' => microtime( true ),
181 - 'log_type' => $type,
182 - 'msg' => $message,
183 - ) );
184 -
185 - if ( self::$_isStorageLoggingOn ) {
186 - $this->db_log( $type, $message, self::$CNT, $caller );
187 - }
188 -
189 - self::$LOG[] = $log;
190 -
191 - if ( $this->is_echo_on() && ! Freemius::is_ajax() ) {
192 - echo self::format_html( $log ) . "\n";
193 - }
194 - }
195 -
196 - function log( $message, $wrapper = false ) {
197 - $this->_log( $message, 'log', $wrapper );
198 - }
199 -
200 - function info( $message, $wrapper = false ) {
201 - $this->_log( $message, 'info', $wrapper );
202 - }
203 -
204 - function warn( $message, $wrapper = false ) {
205 - $this->_log( $message, 'warn', $wrapper );
206 - }
207 -
208 - function error( $message, $wrapper = false ) {
209 - $this->_log( $message, 'error', $wrapper );
210 - }
211 -
212 - /**
213 - * Log API error.
214 - *
215 - * @author Vova Feldman (@svovaf)
216 - * @since 1.2.1.5
217 - *
218 - * @param mixed $api_result
219 - * @param bool $wrapper
220 - */
221 - function api_error( $api_result, $wrapper = false ) {
222 - $message = '';
223 - if ( is_object( $api_result ) && isset( $api_result->error ) ) {
224 - $message = $api_result->error->message;
225 - } else if ( is_object( $api_result ) ) {
226 - $message = var_export( $api_result, true );
227 - } else if ( is_string( $api_result ) ) {
228 - $message = $api_result;
229 - } else if ( empty( $api_result ) ) {
230 - $message = 'Empty API result.';
231 - }
232 -
233 - $message = 'API Error: ' . $message;
234 -
235 - $this->_log( $message, 'error', $wrapper );
236 - }
237 -
238 - function entrance( $message = '', $wrapper = false ) {
239 - $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message;
240 -
241 - $this->_log( $msg, 'log', $wrapper );
242 - }
243 -
244 - function departure( $message = '', $wrapper = false ) {
245 - $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message;
246 -
247 - $this->_log( $msg, 'log', $wrapper );
248 - }
249 -
250 - #--------------------------------------------------------------------------------
251 - #region Log Formatting
252 - #--------------------------------------------------------------------------------
253 -
254 - private static function format( $log, $show_type = true ) {
255 - return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']';
256 - }
257 -
258 - private static function format_html( $log ) {
259 - return '<div style="font-size: 13px; font-family: monospace; color: #7da767; padding: 8px 3px; background: #000; border-bottom: 1px solid #555;">[' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] <b><code style="color: #c4b1e0;">' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . '</code> >> <b style="color: #f59330;">' . esc_html( $log['msg'] ) . '</b></b>' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']</div>';
260 - }
261 -
262 - #endregion
263 -
264 - static function dump() {
265 - ?>
266 - <!-- BEGIN: Freemius PHP Console Log -->
267 - <script type="text/javascript">
268 - <?php
269 - foreach ( self::$LOG as $log ) {
270 - echo 'console.' . $log['log_type'] . '(' . json_encode( self::format( $log, false ) ) . ')' . "\n";
271 - }
272 - ?>
273 - </script>
274 - <!-- END: Freemius PHP Console Log -->
275 - <?php
276 - }
277 -
278 - static function get_log() {
279 - return self::$LOG;
280 - }
281 -
282 - #--------------------------------------------------------------------------------
283 - #region Database Logging
284 - #--------------------------------------------------------------------------------
285 -
286 - /**
287 - * @author Vova Feldman (@svovaf)
288 - * @since 1.2.1.6
289 - *
290 - * @return bool
291 - */
292 - public static function is_storage_logging_on() {
293 - if ( ! isset( self::$_isStorageLoggingOn ) ) {
294 - self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
295 - }
296 -
297 - return self::$_isStorageLoggingOn;
298 - }
299 -
300 - /**
301 - * Turns on/off database persistent debugging to capture
302 - * multi-session logs to debug complex flows like
303 - * plugin auto-deactivate on premium version activation.
304 - *
305 - * @todo Check if Theme Check has issues with DB tables for themes.
306 - *
307 - * @author Vova Feldman (@svovaf)
308 - * @since 1.2.1.6
309 - *
310 - * @param bool $is_on
311 - *
312 - * @return bool
313 - */
314 - public static function _set_storage_logging( $is_on = true ) {
315 - global $wpdb;
316 -
317 - $table = "{$wpdb->prefix}fs_logger";
318 -
319 - if ( $is_on ) {
320 - /**
321 - * Create logging table.
322 - *
323 - * NOTE:
324 - * dbDelta must use KEY and not INDEX for indexes.
325 - *
326 - * @link https://core.trac.wordpress.org/ticket/2695
327 - */
328 - $result = $wpdb->query( "CREATE TABLE {$table} (
329 -`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
330 -`process_id` INT UNSIGNED NOT NULL,
331 -`user_name` VARCHAR(64) NOT NULL,
332 -`logger` VARCHAR(128) NOT NULL,
333 -`log_order` INT UNSIGNED NOT NULL,
334 -`type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log',
335 -`message` TEXT NOT NULL,
336 -`file` VARCHAR(256) NOT NULL,
337 -`line` INT UNSIGNED NOT NULL,
338 -`function` VARCHAR(256) NOT NULL,
339 -`request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call',
340 -`request_url` VARCHAR(1024) NOT NULL,
341 -`created` DECIMAL(16, 6) NOT NULL,
342 -PRIMARY KEY (`id`),
343 -KEY `process_id` (`process_id` ASC),
344 -KEY `process_logger` (`process_id` ASC, `logger` ASC),
345 -KEY `function` (`function` ASC),
346 -KEY `type` (`type` ASC))" );
347 - } else {
348 - /**
349 - * Drop logging table.
350 - */
351 - $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" );
352 - }
353 -
354 - if ( false !== $result ) {
355 - update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) );
356 - }
357 -
358 - return ( false !== $result );
359 - }
360 -
361 - /**
362 - * @author Vova Feldman (@svovaf)
363 - * @since 1.2.1.6
364 - *
365 - * @param string $type
366 - * @param string $message
367 - * @param int $log_order
368 - * @param array $caller
369 - *
370 - * @return false|int
371 - */
372 - private function db_log(
373 - &$type,
374 - &$message,
375 - &$log_order,
376 - &$caller
377 - ) {
378 - global $wpdb;
379 -
380 - $request_type = 'call';
381 - if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
382 - $request_type = 'cron';
383 - } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
384 - $request_type = 'ajax';
385 - }
386 -
387 - $request_url = WP_FS__IS_HTTP_REQUEST ?
388 - $_SERVER['REQUEST_URI'] :
389 - '';
390 -
391 - return $wpdb->insert(
392 - "{$wpdb->prefix}fs_logger",
393 - array(
394 - 'process_id' => self::$_processID,
395 - 'user_name' => self::$_ownerName,
396 - 'logger' => $this->_id,
397 - 'log_order' => $log_order,
398 - 'type' => $type,
399 - 'request_type' => $request_type,
400 - 'request_url' => $request_url,
401 - 'message' => $message,
402 - 'file' => isset( $caller['file'] ) ?
403 - substr( $caller['file'], self::$_abspathLength ) :
404 - '',
405 - 'line' => $caller['line'],
406 - 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'],
407 - 'created' => microtime( true ),
408 - )
409 - );
410 - }
411 -
412 - /**
413 - * Persistent DB logger columns.
414 - *
415 - * @var array
416 - */
417 - private static $_log_columns = array(
418 - 'id',
419 - 'process_id',
420 - 'user_name',
421 - 'logger',
422 - 'log_order',
423 - 'type',
424 - 'message',
425 - 'file',
426 - 'line',
427 - 'function',
428 - 'request_type',
429 - 'request_url',
430 - 'created',
431 - );
432 -
433 - /**
434 - * Create DB logs query.
435 - *
436 - * @author Vova Feldman (@svovaf)
437 - * @since 1.2.1.6
438 - *
439 - * @param bool $filters
440 - * @param int $limit
441 - * @param int $offset
442 - * @param bool $order
443 - * @param bool $escape_eol
444 - *
445 - * @return string
446 - */
447 - private static function build_db_logs_query(
448 - $filters = false,
449 - $limit = 200,
450 - $offset = 0,
451 - $order = false,
452 - $escape_eol = false
453 - ) {
454 - global $wpdb;
455 -
456 - $select = '*';
457 -
458 - if ( $escape_eol ) {
459 - $select = '';
460 - for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
461 - if ( $i > 0 ) {
462 - $select .= ', ';
463 - }
464 -
465 - if ( 'message' !== self::$_log_columns[ $i ] ) {
466 - $select .= self::$_log_columns[ $i ];
467 - } else {
468 - $select .= 'REPLACE(message , \'\n\', \' \') AS message';
469 - }
470 - }
471 - }
472 -
473 - $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger";
474 - if ( is_array( $filters ) ) {
475 - $criteria = array();
476 -
477 - if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) {
478 - $filters['type'] = strtolower( $filters['type'] );
479 -
480 - switch ( $filters['type'] ) {
481 - case 'warn_error':
482 - $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) );
483 - break;
484 - case 'error':
485 - case 'warn':
486 - $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] );
487 - break;
488 - case 'info':
489 - default:
490 - $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) );
491 - break;
492 - }
493 - }
494 -
495 - if ( ! empty( $filters['request_type'] ) ) {
496 - $filters['request_type'] = strtolower( $filters['request_type'] );
497 -
498 - if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) {
499 - $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] );
500 - }
501 - }
502 -
503 - if ( ! empty( $filters['file'] ) ) {
504 - $criteria[] = array(
505 - 'col' => 'file',
506 - 'op' => 'LIKE',
507 - 'val' => '%' . esc_sql( $filters['file'] ),
508 - );
509 - }
510 -
511 - if ( ! empty( $filters['function'] ) ) {
512 - $criteria[] = array(
513 - 'col' => 'function',
514 - 'op' => 'LIKE',
515 - 'val' => '%' . esc_sql( $filters['function'] ),
516 - );
517 - }
518 -
519 - if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) {
520 - $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] );
521 - }
522 -
523 - if ( ! empty( $filters['logger'] ) ) {
524 - $criteria[] = array(
525 - 'col' => 'logger',
526 - 'op' => 'LIKE',
527 - 'val' => '%' . esc_sql( $filters['logger'] ) . '%',
528 - );
529 - }
530 -
531 - if ( ! empty( $filters['message'] ) ) {
532 - $criteria[] = array(
533 - 'col' => 'message',
534 - 'op' => 'LIKE',
535 - 'val' => '%' . esc_sql( $filters['message'] ) . '%',
536 - );
537 - }
538 -
539 - if ( 0 < count( $criteria ) ) {
540 - $query .= "\nWHERE\n";
541 -
542 - $first = true;
543 - foreach ( $criteria as $c ) {
544 - if ( ! $first ) {
545 - $query .= "AND\n";
546 - }
547 -
548 - if ( is_array( $c['val'] ) ) {
549 - $operator = 'IN';
550 -
551 - for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) {
552 - $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'";
553 - }
554 -
555 - $val = '(' . implode( ',', $c['val'] ) . ')';
556 - } else {
557 - $operator = ! empty( $c['op'] ) ? $c['op'] : '=';
558 - $val = "'" . esc_sql( $c['val'] ) . "'";
559 - }
560 -
561 - $query .= "`{$c['col']}` {$operator} {$val}\n";
562 -
563 - $first = false;
564 - }
565 - }
566 - }
567 -
568 - if ( ! is_array( $order ) ) {
569 - $order = array(
570 - 'col' => 'id',
571 - 'order' => 'desc'
572 - );
573 - }
574 -
575 - $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}";
576 -
577 - return $query;
578 - }
579 -
580 - /**
581 - * Load logs from DB.
582 - *
583 - * @author Vova Feldman (@svovaf)
584 - * @since 1.2.1.6
585 - *
586 - * @param bool $filters
587 - * @param int $limit
588 - * @param int $offset
589 - * @param bool $order
590 - *
591 - * @return object[]|null
592 - */
593 - public static function load_db_logs(
594 - $filters = false,
595 - $limit = 200,
596 - $offset = 0,
597 - $order = false
598 - ) {
599 - global $wpdb;
600 -
601 - $query = self::build_db_logs_query(
602 - $filters,
603 - $limit,
604 - $offset,
605 - $order
606 - );
607 -
608 - return $wpdb->get_results( $query );
609 - }
610 -
611 - /**
612 - * Load logs from DB.
613 - *
614 - * @author Vova Feldman (@svovaf)
615 - * @since 1.2.1.6
616 - *
617 - * @param bool $filters
618 - * @param string $filename
619 - * @param int $limit
620 - * @param int $offset
621 - * @param bool $order
622 - *
623 - * @return false|string File download URL or false on failure.
624 - */
625 - public static function download_db_logs(
626 - $filters = false,
627 - $filename = '',
628 - $limit = 10000,
629 - $offset = 0,
630 - $order = false
631 - ) {
632 - global $wpdb;
633 -
634 - $query = self::build_db_logs_query(
635 - $filters,
636 - $limit,
637 - $offset,
638 - $order,
639 - true
640 - );
641 -
642 - $upload_dir = wp_upload_dir();
643 - if ( empty( $filename ) ) {
644 - $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
645 - }
646 - $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
647 -
648 - $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'";
649 -
650 - $columns = '';
651 - for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
652 - if ( $i > 0 ) {
653 - $columns .= ', ';
654 - }
655 -
656 - $columns .= "'" . self::$_log_columns[ $i ] . "'";
657 - }
658 -
659 - $query = "SELECT {$columns} UNION ALL " . $query;
660 -
661 - $result = $wpdb->query( $query );
662 -
663 - if ( false === $result ) {
664 - return false;
665 - }
666 -
667 - return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
668 - }
669 -
670 - /**
671 - * @author Vova Feldman (@svovaf)
672 - * @since 1.2.1.6
673 - *
674 - * @param string $filename
675 - *
676 - * @return string
677 - */
678 - public static function get_logs_download_url( $filename = '' ) {
679 - $upload_dir = wp_upload_dir();
680 - if ( empty( $filename ) ) {
681 - $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
682 - }
683 -
684 - return rtrim( $upload_dir['url'], '/' ) . $filename;
685 - }
686 -
687 - #endregion
688 - }
1 +<?php
2 + /**
3 + * @package Freemius
4 + * @copyright Copyright (c) 2015, Freemius, Inc.
5 + * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 + * @since 1.0.3
7 + */
8 +
9 + if ( ! defined( 'ABSPATH' ) ) {
10 + exit;
11 + }
12 +
13 + class FS_Logger {
14 + private $_id;
15 + private $_on = false;
16 + private $_echo = false;
17 + private $_file_start = 0;
18 + /**
19 + * @var int PHP Process ID.
20 + */
21 + private static $_processID;
22 + /**
23 + * @var string PHP Script user name.
24 + */
25 + private static $_ownerName;
26 + /**
27 + * @var bool Is storage logging turned on.
28 + */
29 + private static $_isStorageLoggingOn;
30 + /**
31 + * @var int ABSPATH length.
32 + */
33 + private static $_abspathLength;
34 +
35 + /**
36 + * @var FS_Logger[] $LOGGERS
37 + */
38 + private static $LOGGERS = array();
39 + private static $LOG = array();
40 + private static $CNT = 0;
41 + private static $_HOOKED_FOOTER = false;
42 +
43 + private function __construct( $id, $on = false, $echo = false ) {
44 + $bt = debug_backtrace();
45 +
46 + $this->_id = $id;
47 +
48 + $caller = $bt[2];
49 +
50 + if ( false !== strpos( $caller['file'], 'plugins' ) ) {
51 + $this->_file_start = strpos( $caller['file'], 'plugins' ) + strlen( 'plugins/' );
52 + } else {
53 + $this->_file_start = strpos( $caller['file'], 'themes' ) + strlen( 'themes/' );
54 + }
55 +
56 + if ( $on ) {
57 + $this->on();
58 + }
59 + if ( $echo ) {
60 + $this->echo_on();
61 + }
62 + }
63 +
64 + /**
65 + * @param string $id
66 + * @param bool $on
67 + * @param bool $echo
68 + *
69 + * @return FS_Logger
70 + */
71 + public static function get_logger( $id, $on = false, $echo = false ) {
72 + $id = strtolower( $id );
73 +
74 + if ( ! isset( self::$_processID ) ) {
75 + self::init();
76 + }
77 +
78 + if ( ! isset( self::$LOGGERS[ $id ] ) ) {
79 + self::$LOGGERS[ $id ] = new FS_Logger( $id, $on, $echo );
80 + }
81 +
82 + return self::$LOGGERS[ $id ];
83 + }
84 +
85 + /**
86 + * Initialize logging global info.
87 + *
88 + * @author Vova Feldman (@svovaf)
89 + * @since 1.2.1.6
90 + */
91 + private static function init() {
92 + self::$_ownerName = function_exists( 'get_current_user' ) ?
93 + get_current_user() :
94 + 'unknown';
95 + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
96 + self::$_abspathLength = strlen( ABSPATH );
97 + self::$_processID = mt_rand( 0, 32000 );
98 +
99 + // Process ID may be `false` on errors.
100 + if ( ! is_numeric( self::$_processID ) ) {
101 + self::$_processID = 0;
102 + }
103 + }
104 +
105 + private static function hook_footer() {
106 + if ( self::$_HOOKED_FOOTER ) {
107 + return;
108 + }
109 +
110 + if ( is_admin() ) {
111 + add_action( 'admin_footer', 'FS_Logger::dump', 100 );
112 + } else {
113 + add_action( 'wp_footer', 'FS_Logger::dump', 100 );
114 + }
115 + }
116 +
117 + function is_on() {
118 + return $this->_on;
119 + }
120 +
121 + function on() {
122 + $this->_on = true;
123 +
124 + if ( ! function_exists( 'dbDelta' ) ) {
125 + require_once ABSPATH . 'wp-admin/includes/upgrade.php';
126 + }
127 +
128 + self::hook_footer();
129 + }
130 + function echo_on() {
131 + $this->on();
132 +
133 + $this->_echo = true;
134 + }
135 +
136 + function is_echo_on() {
137 + return $this->_echo;
138 + }
139 +
140 + function get_id() {
141 + return $this->_id;
142 + }
143 +
144 + function get_file() {
145 + return $this->_file_start;
146 + }
147 +
148 + private function _log( &$message, $type, $wrapper = false ) {
149 + if ( ! $this->is_on() ) {
150 + return;
151 + }
152 +
153 + $bt = debug_backtrace();
154 + $depth = $wrapper ? 3 : 2;
155 + while ( $depth < count( $bt ) - 1 && 'eval' === $bt[ $depth ]['function'] ) {
156 + $depth ++;
157 + }
158 +
159 + $caller = $bt[ $depth ];
160 +
161 + /**
162 + * Retrieve the correct call file & line number from backtrace
163 + * when logging from a wrapper method.
164 + *
165 + * @author Vova Feldman
166 + * @since 1.2.1.6
167 + */
168 + if ( empty( $caller['line'] ) ) {
169 + $depth --;
170 +
171 + while ( $depth >= 0 ) {
172 + if ( ! empty( $bt[ $depth ]['line'] ) ) {
173 + $caller['line'] = $bt[ $depth ]['line'];
174 + $caller['file'] = $bt[ $depth ]['file'];
175 + break;
176 + }
177 + }
178 + }
179 +
180 + $log = array_merge( $caller, array(
181 + 'cnt' => self::$CNT ++,
182 + 'logger' => $this,
183 + 'timestamp' => microtime( true ),
184 + 'log_type' => $type,
185 + 'msg' => $message,
186 + ) );
187 +
188 + if ( self::$_isStorageLoggingOn ) {
189 + $this->db_log( $type, $message, self::$CNT, $caller );
190 + }
191 +
192 + self::$LOG[] = $log;
193 +
194 + if ( $this->is_echo_on() && ! Freemius::is_ajax() ) {
195 + echo self::format_html( $log ) . "\n";
196 + }
197 + }
198 +
199 + function log( $message, $wrapper = false ) {
200 + $this->_log( $message, 'log', $wrapper );
201 + }
202 +
203 + function info( $message, $wrapper = false ) {
204 + $this->_log( $message, 'info', $wrapper );
205 + }
206 +
207 + function warn( $message, $wrapper = false ) {
208 + $this->_log( $message, 'warn', $wrapper );
209 + }
210 +
211 + function error( $message, $wrapper = false ) {
212 + $this->_log( $message, 'error', $wrapper );
213 + }
214 +
215 + /**
216 + * Log API error.
217 + *
218 + * @author Vova Feldman (@svovaf)
219 + * @since 1.2.1.5
220 + *
221 + * @param mixed $api_result
222 + * @param bool $wrapper
223 + */
224 + function api_error( $api_result, $wrapper = false ) {
225 + $message = '';
226 + if ( is_object( $api_result ) &&
227 + ! empty( $api_result->error ) &&
228 + ! empty( $api_result->error->message )
229 + ) {
230 + $message = $api_result->error->message;
231 + } else if ( is_object( $api_result ) ) {
232 + $message = var_export( $api_result, true );
233 + } else if ( is_string( $api_result ) ) {
234 + $message = $api_result;
235 + } else if ( empty( $api_result ) ) {
236 + $message = 'Empty API result.';
237 + }
238 +
239 + $message = 'API Error: ' . $message;
240 +
241 + $this->_log( $message, 'error', $wrapper );
242 + }
243 +
244 + function entrance( $message = '', $wrapper = false ) {
245 + $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message;
246 +
247 + $this->_log( $msg, 'log', $wrapper );
248 + }
249 +
250 + function departure( $message = '', $wrapper = false ) {
251 + $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message;
252 +
253 + $this->_log( $msg, 'log', $wrapper );
254 + }
255 +
256 + #--------------------------------------------------------------------------------
257 + #region Log Formatting
258 + #--------------------------------------------------------------------------------
259 +
260 + private static function format( $log, $show_type = true ) {
261 + return '[' . str_pad( $log['cnt'], strlen( self::$CNT ), '0', STR_PAD_LEFT ) . '] [' . $log['logger']->_id . '] ' . ( $show_type ? '[' . $log['log_type'] . ']' : '' ) . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . ' >> ' . $log['msg'] . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ') ' : '' ) . ' [' . $log['timestamp'] . ']';
262 + }
263 +
264 + private static function format_html( $log ) {
265 + return '<div style="font-size: 13px; font-family: monospace; color: #7da767; padding: 8px 3px; background: #000; border-bottom: 1px solid #555;">[' . $log['cnt'] . '] [' . $log['logger']->_id . '] [' . $log['log_type'] . '] <b><code style="color: #c4b1e0;">' . ( ! empty( $log['class'] ) ? $log['class'] . $log['type'] : '' ) . $log['function'] . '</code> >> <b style="color: #f59330;">' . esc_html( $log['msg'] ) . '</b></b>' . ( isset( $log['file'] ) ? ' (' . substr( $log['file'], $log['logger']->_file_start ) . ' ' . $log['line'] . ')' : '' ) . ' [' . $log['timestamp'] . ']</div>';
266 + }
267 +
268 + #endregion
269 +
270 + static function dump() {
271 + ?>
272 + <!-- BEGIN: Freemius PHP Console Log -->
273 + <script type="text/javascript">
274 + <?php
275 + foreach ( self::$LOG as $log ) {
276 + echo 'console.' . $log['log_type'] . '(' . json_encode( self::format( $log, false ) ) . ')' . "\n";
277 + }
278 + ?>
279 + </script>
280 + <!-- END: Freemius PHP Console Log -->
281 + <?php
282 + }
283 +
284 + static function get_log() {
285 + return self::$LOG;
286 + }
287 +
288 + #--------------------------------------------------------------------------------
289 + #region Database Logging
290 + #--------------------------------------------------------------------------------
291 +
292 + /**
293 + * @author Vova Feldman (@svovaf)
294 + * @since 1.2.1.6
295 + *
296 + * @return bool
297 + */
298 + public static function is_storage_logging_on() {
299 + if ( ! isset( self::$_isStorageLoggingOn ) ) {
300 + self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
301 + }
302 +
303 + return self::$_isStorageLoggingOn;
304 + }
305 +
306 + /**
307 + * Turns on/off database persistent debugging to capture
308 + * multi-session logs to debug complex flows like
309 + * plugin auto-deactivate on premium version activation.
310 + *
311 + * @todo Check if Theme Check has issues with DB tables for themes.
312 + *
313 + * @author Vova Feldman (@svovaf)
314 + * @since 1.2.1.6
315 + *
316 + * @param bool $is_on
317 + *
318 + * @return bool
319 + */
320 + public static function _set_storage_logging( $is_on = true ) {
321 + global $wpdb;
322 +
323 + $table = "{$wpdb->prefix}fs_logger";
324 +
325 + /**
326 + * Drop logging table in any case.
327 + */
328 + $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" );
329 +
330 + if ( $is_on ) {
331 + /**
332 + * Create logging table.
333 + *
334 + * NOTE:
335 + * dbDelta must use KEY and not INDEX for indexes.
336 + *
337 + * @link https://core.trac.wordpress.org/ticket/2695
338 + */
339 + $result = $wpdb->query( "CREATE TABLE IF NOT EXISTS {$table} (
340 +`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
341 +`process_id` INT UNSIGNED NOT NULL,
342 +`user_name` VARCHAR(64) NOT NULL,
343 +`logger` VARCHAR(128) NOT NULL,
344 +`log_order` INT UNSIGNED NOT NULL,
345 +`type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log',
346 +`message` TEXT NOT NULL,
347 +`file` VARCHAR(256) NOT NULL,
348 +`line` INT UNSIGNED NOT NULL,
349 +`function` VARCHAR(256) NOT NULL,
350 +`request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call',
351 +`request_url` VARCHAR(1024) NOT NULL,
352 +`created` DECIMAL(16, 6) NOT NULL,
353 +PRIMARY KEY (`id`),
354 +KEY `process_id` (`process_id` ASC),
355 +KEY `process_logger` (`process_id` ASC, `logger` ASC),
356 +KEY `function` (`function` ASC),
357 +KEY `type` (`type` ASC))" );
358 + }
359 +
360 + if ( false !== $result ) {
361 + update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) );
362 + self::$_isStorageLoggingOn = $is_on;
363 + }
364 +
365 + return ( false !== $result );
366 + }
367 +
368 + /**
369 + * @author Vova Feldman (@svovaf)
370 + * @since 1.2.1.6
371 + *
372 + * @param string $type
373 + * @param string $message
374 + * @param int $log_order
375 + * @param array $caller
376 + *
377 + * @return false|int
378 + */
379 + private function db_log(
380 + &$type,
381 + &$message,
382 + &$log_order,
383 + &$caller
384 + ) {
385 + global $wpdb;
386 +
387 + $request_type = 'call';
388 + if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
389 + $request_type = 'cron';
390 + } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
391 + $request_type = 'ajax';
392 + }
393 +
394 + $request_url = WP_FS__IS_HTTP_REQUEST ?
395 + $_SERVER['REQUEST_URI'] :
396 + '';
397 +
398 + return $wpdb->insert(
399 + "{$wpdb->prefix}fs_logger",
400 + array(
401 + 'process_id' => self::$_processID,
402 + 'user_name' => self::$_ownerName,
403 + 'logger' => $this->_id,
404 + 'log_order' => $log_order,
405 + 'type' => $type,
406 + 'request_type' => $request_type,
407 + 'request_url' => $request_url,
408 + 'message' => $message,
409 + 'file' => isset( $caller['file'] ) ?
410 + substr( $caller['file'], self::$_abspathLength ) :
411 + '',
412 + 'line' => $caller['line'],
413 + 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'],
414 + 'created' => microtime( true ),
415 + )
416 + );
417 + }
418 +
419 + /**
420 + * Persistent DB logger columns.
421 + *
422 + * @var array
423 + */
424 + private static $_log_columns = array(
425 + 'id',
426 + 'process_id',
427 + 'user_name',
428 + 'logger',
429 + 'log_order',
430 + 'type',
431 + 'message',
432 + 'file',
433 + 'line',
434 + 'function',
435 + 'request_type',
436 + 'request_url',
437 + 'created',
438 + );
439 +
440 + /**
441 + * Create DB logs query.
442 + *
443 + * @author Vova Feldman (@svovaf)
444 + * @since 1.2.1.6
445 + *
446 + * @param bool $filters
447 + * @param int $limit
448 + * @param int $offset
449 + * @param bool $order
450 + * @param bool $escape_eol
451 + *
452 + * @return string
453 + */
454 + private static function build_db_logs_query(
455 + $filters = false,
456 + $limit = 200,
457 + $offset = 0,
458 + $order = false,
459 + $escape_eol = false
460 + ) {
461 + global $wpdb;
462 +
463 + $select = '*';
464 +
465 + if ( $escape_eol ) {
466 + $select = '';
467 + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
468 + if ( $i > 0 ) {
469 + $select .= ', ';
470 + }
471 +
472 + if ( 'message' !== self::$_log_columns[ $i ] ) {
473 + $select .= self::$_log_columns[ $i ];
474 + } else {
475 + $select .= 'REPLACE(message , \'\n\', \' \') AS message';
476 + }
477 + }
478 + }
479 +
480 + $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger";
481 + if ( is_array( $filters ) ) {
482 + $criteria = array();
483 +
484 + if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) {
485 + $filters['type'] = strtolower( $filters['type'] );
486 +
487 + switch ( $filters['type'] ) {
488 + case 'warn_error':
489 + $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) );
490 + break;
491 + case 'error':
492 + case 'warn':
493 + $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] );
494 + break;
495 + case 'info':
496 + default:
497 + $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) );
498 + break;
499 + }
500 + }
501 +
502 + if ( ! empty( $filters['request_type'] ) ) {
503 + $filters['request_type'] = strtolower( $filters['request_type'] );
504 +
505 + if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) {
506 + $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] );
507 + }
508 + }
509 +
510 + if ( ! empty( $filters['file'] ) ) {
511 + $criteria[] = array(
512 + 'col' => 'file',
513 + 'op' => 'LIKE',
514 + 'val' => '%' . esc_sql( $filters['file'] ),
515 + );
516 + }
517 +
518 + if ( ! empty( $filters['function'] ) ) {
519 + $criteria[] = array(
520 + 'col' => 'function',
521 + 'op' => 'LIKE',
522 + 'val' => '%' . esc_sql( $filters['function'] ),
523 + );
524 + }
525 +
526 + if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) {
527 + $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] );
528 + }
529 +
530 + if ( ! empty( $filters['logger'] ) ) {
531 + $criteria[] = array(
532 + 'col' => 'logger',
533 + 'op' => 'LIKE',
534 + 'val' => '%' . esc_sql( $filters['logger'] ) . '%',
535 + );
536 + }
537 +
538 + if ( ! empty( $filters['message'] ) ) {
539 + $criteria[] = array(
540 + 'col' => 'message',
541 + 'op' => 'LIKE',
542 + 'val' => '%' . esc_sql( $filters['message'] ) . '%',
543 + );
544 + }
545 +
546 + if ( 0 < count( $criteria ) ) {
547 + $query .= "\nWHERE\n";
548 +
549 + $first = true;
550 + foreach ( $criteria as $c ) {
551 + if ( ! $first ) {
552 + $query .= "AND\n";
553 + }
554 +
555 + if ( is_array( $c['val'] ) ) {
556 + $operator = 'IN';
557 +
558 + for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) {
559 + $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'";
560 + }
561 +
562 + $val = '(' . implode( ',', $c['val'] ) . ')';
563 + } else {
564 + $operator = ! empty( $c['op'] ) ? $c['op'] : '=';
565 + $val = "'" . esc_sql( $c['val'] ) . "'";
566 + }
567 +
568 + $query .= "`{$c['col']}` {$operator} {$val}\n";
569 +
570 + $first = false;
571 + }
572 + }
573 + }
574 +
575 + if ( ! is_array( $order ) ) {
576 + $order = array(
577 + 'col' => 'id',
578 + 'order' => 'desc'
579 + );
580 + }
581 +
582 + $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}";
583 +
584 + return $query;
585 + }
586 +
587 + /**
588 + * Load logs from DB.
589 + *
590 + * @author Vova Feldman (@svovaf)
591 + * @since 1.2.1.6
592 + *
593 + * @param bool $filters
594 + * @param int $limit
595 + * @param int $offset
596 + * @param bool $order
597 + *
598 + * @return object[]|null
599 + */
600 + public static function load_db_logs(
601 + $filters = false,
602 + $limit = 200,
603 + $offset = 0,
604 + $order = false
605 + ) {
606 + global $wpdb;
607 +
608 + $query = self::build_db_logs_query(
609 + $filters,
610 + $limit,
611 + $offset,
612 + $order
613 + );
614 +
615 + return $wpdb->get_results( $query );
616 + }
617 +
618 + /**
619 + * Load logs from DB.
620 + *
621 + * @author Vova Feldman (@svovaf)
622 + * @since 1.2.1.6
623 + *
624 + * @param bool $filters
625 + * @param string $filename
626 + * @param int $limit
627 + * @param int $offset
628 + * @param bool $order
629 + *
630 + * @return false|string File download URL or false on failure.
631 + */
632 + public static function download_db_logs(
633 + $filters = false,
634 + $filename = '',
635 + $limit = 10000,
636 + $offset = 0,
637 + $order = false
638 + ) {
639 + global $wpdb;
640 +
641 + $query = self::build_db_logs_query(
642 + $filters,
643 + $limit,
644 + $offset,
645 + $order,
646 + true
647 + );
648 +
649 + $upload_dir = wp_upload_dir();
650 + if ( empty( $filename ) ) {
651 + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
652 + }
653 + $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
654 +
655 + $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'";
656 +
657 + $columns = '';
658 + for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
659 + if ( $i > 0 ) {
660 + $columns .= ', ';
661 + }
662 +
663 + $columns .= "'" . self::$_log_columns[ $i ] . "'";
664 + }
665 +
666 + $query = "SELECT {$columns} UNION ALL " . $query;
667 +
668 + $result = $wpdb->query( $query );
669 +
670 + if ( false === $result ) {
671 + return false;
672 + }
673 +
674 + return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
675 + }
676 +
677 + /**
678 + * @author Vova Feldman (@svovaf)
679 + * @since 1.2.1.6
680 + *
681 + * @param string $filename
682 + *
683 + * @return string
684 + */
685 + public static function get_logs_download_url( $filename = '' ) {
686 + $upload_dir = wp_upload_dir();
687 + if ( empty( $filename ) ) {
688 + $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
689 + }
690 +
691 + return rtrim( $upload_dir['url'], '/' ) . $filename;
692 + }
693 +
694 + #endregion
695 + }