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