PluginProbe ʕ •ᴥ•ʔ
Code Manager / 1.0.0
Code Manager v1.0.0
1.0.48 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 5 years ago debug 5 years ago entities 5 years ago managers 5 years ago sdk 5 years ago supplements 5 years ago class-freemius-abstract.php 5 years ago class-freemius.php 5 years ago class-fs-admin-notices.php 5 years ago class-fs-api.php 5 years ago class-fs-logger.php 5 years ago class-fs-options.php 5 years ago class-fs-plugin-updater.php 5 years ago class-fs-security.php 5 years ago class-fs-storage.php 5 years ago class-fs-user-lock.php 5 years ago fs-core-functions.php 5 years ago fs-essential-functions.php 5 years ago fs-plugin-info-dialog.php 5 years ago i18n.php 5 years ago index.php 5 years ago l10n.php 5 years ago
class-fs-logger.php
691 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 $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 ) &&
224 ! empty( $api_result->error ) &&
225 ! empty( $api_result->error->message )
226 ) {
227 $message = $api_result->error->message;
228 } else if ( is_object( $api_result ) ) {
229 $message = var_export( $api_result, true );
230 } else if ( is_string( $api_result ) ) {
231 $message = $api_result;
232 } else if ( empty( $api_result ) ) {
233 $message = 'Empty API result.';
234 }
235
236 $message = 'API Error: ' . $message;
237
238 $this->_log( $message, 'error', $wrapper );
239 }
240
241 function entrance( $message = '', $wrapper = false ) {
242 $msg = 'Entrance' . ( empty( $message ) ? '' : ' > ' ) . $message;
243
244 $this->_log( $msg, 'log', $wrapper );
245 }
246
247 function departure( $message = '', $wrapper = false ) {
248 $msg = 'Departure' . ( empty( $message ) ? '' : ' > ' ) . $message;
249
250 $this->_log( $msg, 'log', $wrapper );
251 }
252
253 #--------------------------------------------------------------------------------
254 #region Log Formatting
255 #--------------------------------------------------------------------------------
256
257 private static function format( $log, $show_type = true ) {
258 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'] . ']';
259 }
260
261 private static function format_html( $log ) {
262 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>';
263 }
264
265 #endregion
266
267 static function dump() {
268 ?>
269 <!-- BEGIN: Freemius PHP Console Log -->
270 <script type="text/javascript">
271 <?php
272 foreach ( self::$LOG as $log ) {
273 echo 'console.' . $log['log_type'] . '(' . json_encode( self::format( $log, false ) ) . ')' . "\n";
274 }
275 ?>
276 </script>
277 <!-- END: Freemius PHP Console Log -->
278 <?php
279 }
280
281 static function get_log() {
282 return self::$LOG;
283 }
284
285 #--------------------------------------------------------------------------------
286 #region Database Logging
287 #--------------------------------------------------------------------------------
288
289 /**
290 * @author Vova Feldman (@svovaf)
291 * @since 1.2.1.6
292 *
293 * @return bool
294 */
295 public static function is_storage_logging_on() {
296 if ( ! isset( self::$_isStorageLoggingOn ) ) {
297 self::$_isStorageLoggingOn = ( 1 == get_option( 'fs_storage_logger', 0 ) );
298 }
299
300 return self::$_isStorageLoggingOn;
301 }
302
303 /**
304 * Turns on/off database persistent debugging to capture
305 * multi-session logs to debug complex flows like
306 * plugin auto-deactivate on premium version activation.
307 *
308 * @todo Check if Theme Check has issues with DB tables for themes.
309 *
310 * @author Vova Feldman (@svovaf)
311 * @since 1.2.1.6
312 *
313 * @param bool $is_on
314 *
315 * @return bool
316 */
317 public static function _set_storage_logging( $is_on = true ) {
318 global $wpdb;
319
320 $table = "{$wpdb->prefix}fs_logger";
321
322 if ( $is_on ) {
323 /**
324 * Create logging table.
325 *
326 * NOTE:
327 * dbDelta must use KEY and not INDEX for indexes.
328 *
329 * @link https://core.trac.wordpress.org/ticket/2695
330 */
331 $result = $wpdb->query( "CREATE TABLE {$table} (
332 `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
333 `process_id` INT UNSIGNED NOT NULL,
334 `user_name` VARCHAR(64) NOT NULL,
335 `logger` VARCHAR(128) NOT NULL,
336 `log_order` INT UNSIGNED NOT NULL,
337 `type` ENUM('log','info','warn','error') NOT NULL DEFAULT 'log',
338 `message` TEXT NOT NULL,
339 `file` VARCHAR(256) NOT NULL,
340 `line` INT UNSIGNED NOT NULL,
341 `function` VARCHAR(256) NOT NULL,
342 `request_type` ENUM('call','ajax','cron') NOT NULL DEFAULT 'call',
343 `request_url` VARCHAR(1024) NOT NULL,
344 `created` DECIMAL(16, 6) NOT NULL,
345 PRIMARY KEY (`id`),
346 KEY `process_id` (`process_id` ASC),
347 KEY `process_logger` (`process_id` ASC, `logger` ASC),
348 KEY `function` (`function` ASC),
349 KEY `type` (`type` ASC))" );
350 } else {
351 /**
352 * Drop logging table.
353 */
354 $result = $wpdb->query( "DROP TABLE IF EXISTS $table;" );
355 }
356
357 if ( false !== $result ) {
358 update_option( 'fs_storage_logger', ( $is_on ? 1 : 0 ) );
359 }
360
361 return ( false !== $result );
362 }
363
364 /**
365 * @author Vova Feldman (@svovaf)
366 * @since 1.2.1.6
367 *
368 * @param string $type
369 * @param string $message
370 * @param int $log_order
371 * @param array $caller
372 *
373 * @return false|int
374 */
375 private function db_log(
376 &$type,
377 &$message,
378 &$log_order,
379 &$caller
380 ) {
381 global $wpdb;
382
383 $request_type = 'call';
384 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
385 $request_type = 'cron';
386 } else if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
387 $request_type = 'ajax';
388 }
389
390 $request_url = WP_FS__IS_HTTP_REQUEST ?
391 $_SERVER['REQUEST_URI'] :
392 '';
393
394 return $wpdb->insert(
395 "{$wpdb->prefix}fs_logger",
396 array(
397 'process_id' => self::$_processID,
398 'user_name' => self::$_ownerName,
399 'logger' => $this->_id,
400 'log_order' => $log_order,
401 'type' => $type,
402 'request_type' => $request_type,
403 'request_url' => $request_url,
404 'message' => $message,
405 'file' => isset( $caller['file'] ) ?
406 substr( $caller['file'], self::$_abspathLength ) :
407 '',
408 'line' => $caller['line'],
409 'function' => ( ! empty( $caller['class'] ) ? $caller['class'] . $caller['type'] : '' ) . $caller['function'],
410 'created' => microtime( true ),
411 )
412 );
413 }
414
415 /**
416 * Persistent DB logger columns.
417 *
418 * @var array
419 */
420 private static $_log_columns = array(
421 'id',
422 'process_id',
423 'user_name',
424 'logger',
425 'log_order',
426 'type',
427 'message',
428 'file',
429 'line',
430 'function',
431 'request_type',
432 'request_url',
433 'created',
434 );
435
436 /**
437 * Create DB logs query.
438 *
439 * @author Vova Feldman (@svovaf)
440 * @since 1.2.1.6
441 *
442 * @param bool $filters
443 * @param int $limit
444 * @param int $offset
445 * @param bool $order
446 * @param bool $escape_eol
447 *
448 * @return string
449 */
450 private static function build_db_logs_query(
451 $filters = false,
452 $limit = 200,
453 $offset = 0,
454 $order = false,
455 $escape_eol = false
456 ) {
457 global $wpdb;
458
459 $select = '*';
460
461 if ( $escape_eol ) {
462 $select = '';
463 for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
464 if ( $i > 0 ) {
465 $select .= ', ';
466 }
467
468 if ( 'message' !== self::$_log_columns[ $i ] ) {
469 $select .= self::$_log_columns[ $i ];
470 } else {
471 $select .= 'REPLACE(message , \'\n\', \' \') AS message';
472 }
473 }
474 }
475
476 $query = "SELECT {$select} FROM {$wpdb->prefix}fs_logger";
477 if ( is_array( $filters ) ) {
478 $criteria = array();
479
480 if ( ! empty( $filters['type'] ) && 'all' !== $filters['type'] ) {
481 $filters['type'] = strtolower( $filters['type'] );
482
483 switch ( $filters['type'] ) {
484 case 'warn_error':
485 $criteria[] = array( 'col' => 'type', 'val' => array( 'warn', 'error' ) );
486 break;
487 case 'error':
488 case 'warn':
489 $criteria[] = array( 'col' => 'type', 'val' => $filters['type'] );
490 break;
491 case 'info':
492 default:
493 $criteria[] = array( 'col' => 'type', 'val' => array( 'info', 'log' ) );
494 break;
495 }
496 }
497
498 if ( ! empty( $filters['request_type'] ) ) {
499 $filters['request_type'] = strtolower( $filters['request_type'] );
500
501 if ( in_array( $filters['request_type'], array( 'call', 'ajax', 'cron' ) ) ) {
502 $criteria[] = array( 'col' => 'request_type', 'val' => $filters['request_type'] );
503 }
504 }
505
506 if ( ! empty( $filters['file'] ) ) {
507 $criteria[] = array(
508 'col' => 'file',
509 'op' => 'LIKE',
510 'val' => '%' . esc_sql( $filters['file'] ),
511 );
512 }
513
514 if ( ! empty( $filters['function'] ) ) {
515 $criteria[] = array(
516 'col' => 'function',
517 'op' => 'LIKE',
518 'val' => '%' . esc_sql( $filters['function'] ),
519 );
520 }
521
522 if ( ! empty( $filters['process_id'] ) && is_numeric( $filters['process_id'] ) ) {
523 $criteria[] = array( 'col' => 'process_id', 'val' => $filters['process_id'] );
524 }
525
526 if ( ! empty( $filters['logger'] ) ) {
527 $criteria[] = array(
528 'col' => 'logger',
529 'op' => 'LIKE',
530 'val' => '%' . esc_sql( $filters['logger'] ) . '%',
531 );
532 }
533
534 if ( ! empty( $filters['message'] ) ) {
535 $criteria[] = array(
536 'col' => 'message',
537 'op' => 'LIKE',
538 'val' => '%' . esc_sql( $filters['message'] ) . '%',
539 );
540 }
541
542 if ( 0 < count( $criteria ) ) {
543 $query .= "\nWHERE\n";
544
545 $first = true;
546 foreach ( $criteria as $c ) {
547 if ( ! $first ) {
548 $query .= "AND\n";
549 }
550
551 if ( is_array( $c['val'] ) ) {
552 $operator = 'IN';
553
554 for ( $i = 0, $len = count( $c['val'] ); $i < $len; $i ++ ) {
555 $c['val'][ $i ] = "'" . esc_sql( $c['val'][ $i ] ) . "'";
556 }
557
558 $val = '(' . implode( ',', $c['val'] ) . ')';
559 } else {
560 $operator = ! empty( $c['op'] ) ? $c['op'] : '=';
561 $val = "'" . esc_sql( $c['val'] ) . "'";
562 }
563
564 $query .= "`{$c['col']}` {$operator} {$val}\n";
565
566 $first = false;
567 }
568 }
569 }
570
571 if ( ! is_array( $order ) ) {
572 $order = array(
573 'col' => 'id',
574 'order' => 'desc'
575 );
576 }
577
578 $query .= " ORDER BY {$order['col']} {$order['order']} LIMIT {$offset},{$limit}";
579
580 return $query;
581 }
582
583 /**
584 * Load logs from DB.
585 *
586 * @author Vova Feldman (@svovaf)
587 * @since 1.2.1.6
588 *
589 * @param bool $filters
590 * @param int $limit
591 * @param int $offset
592 * @param bool $order
593 *
594 * @return object[]|null
595 */
596 public static function load_db_logs(
597 $filters = false,
598 $limit = 200,
599 $offset = 0,
600 $order = false
601 ) {
602 global $wpdb;
603
604 $query = self::build_db_logs_query(
605 $filters,
606 $limit,
607 $offset,
608 $order
609 );
610
611 return $wpdb->get_results( $query );
612 }
613
614 /**
615 * Load logs from DB.
616 *
617 * @author Vova Feldman (@svovaf)
618 * @since 1.2.1.6
619 *
620 * @param bool $filters
621 * @param string $filename
622 * @param int $limit
623 * @param int $offset
624 * @param bool $order
625 *
626 * @return false|string File download URL or false on failure.
627 */
628 public static function download_db_logs(
629 $filters = false,
630 $filename = '',
631 $limit = 10000,
632 $offset = 0,
633 $order = false
634 ) {
635 global $wpdb;
636
637 $query = self::build_db_logs_query(
638 $filters,
639 $limit,
640 $offset,
641 $order,
642 true
643 );
644
645 $upload_dir = wp_upload_dir();
646 if ( empty( $filename ) ) {
647 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
648 }
649 $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
650
651 $query .= " INTO OUTFILE '{$filepath}' FIELDS TERMINATED BY '\t' ESCAPED BY '\\\\' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\\n'";
652
653 $columns = '';
654 for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
655 if ( $i > 0 ) {
656 $columns .= ', ';
657 }
658
659 $columns .= "'" . self::$_log_columns[ $i ] . "'";
660 }
661
662 $query = "SELECT {$columns} UNION ALL " . $query;
663
664 $result = $wpdb->query( $query );
665
666 if ( false === $result ) {
667 return false;
668 }
669
670 return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
671 }
672
673 /**
674 * @author Vova Feldman (@svovaf)
675 * @since 1.2.1.6
676 *
677 * @param string $filename
678 *
679 * @return string
680 */
681 public static function get_logs_download_url( $filename = '' ) {
682 $upload_dir = wp_upload_dir();
683 if ( empty( $filename ) ) {
684 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
685 }
686
687 return rtrim( $upload_dir['url'], '/' ) . $filename;
688 }
689
690 #endregion
691 }