PluginProbe ʕ •ᴥ•ʔ
Photo Gallery by FooGallery : Responsive Image Gallery, Masonry Gallery & Carousel / trunk
Photo Gallery by FooGallery : Responsive Image Gallery, Masonry Gallery & Carousel vtrunk
trunk 1.10.3 2.0.24 2.1.34 2.2.44 2.3.3 2.4.32 3.0.6 3.1.11 3.1.12 3.1.13 3.1.20 3.1.25 3.1.26 3.1.26.1 3.1.26.2
foogallery / freemius / includes / class-fs-logger.php
foogallery / freemius / includes Last commit date
customizer 7 months ago debug 2 years ago entities 7 months ago managers 7 months ago sdk 2 years ago supplements 3 years ago .DS_Store 7 months ago class-freemius-abstract.php 3 years ago class-freemius.php 7 months ago class-fs-admin-notices.php 3 years ago class-fs-api.php 7 months ago class-fs-garbage-collector.php 7 months ago class-fs-hook-snapshot.php 7 months ago class-fs-lock.php 3 years ago class-fs-logger.php 7 months ago class-fs-options.php 8 years ago class-fs-plugin-updater.php 7 months ago class-fs-security.php 7 months ago class-fs-storage.php 7 months ago class-fs-user-lock.php 3 years ago fs-core-functions.php 7 months ago fs-essential-functions.php 2 years ago fs-html-escaping-functions.php 7 months ago fs-plugin-info-dialog.php 7 months ago index.php 3 years ago l10n.php 3 years ago
class-fs-logger.php
729 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 /**
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 if ( empty( $filename ) ) {
640 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
641 }
642
643 $upload_dir = wp_upload_dir();
644 $filepath = rtrim( $upload_dir['path'], '/' ) . "/{$filename}";
645
646 WP_Filesystem();
647 if ( ! $GLOBALS['wp_filesystem']->is_writable( dirname( $filepath ) ) ) {
648 return false;
649 }
650
651 $query = self::build_db_logs_query(
652 $filters,
653 $limit,
654 $offset,
655 $order,
656 true
657 );
658
659 $columns = '';
660 for ( $i = 0, $len = count( self::$_log_columns ); $i < $len; $i ++ ) {
661 if ( $i > 0 ) {
662 $columns .= ', ';
663 }
664
665 $columns .= "'" . self::$_log_columns[ $i ] . "'";
666 }
667
668 $query = "SELECT {$columns} UNION ALL " . $query;
669
670 $result = $GLOBALS['wpdb']->get_results( $query );
671
672 if ( false === $result ) {
673 return false;
674 }
675
676 if ( ! self::write_csv_to_filesystem( $filepath, $result ) ) {
677 return false;
678 }
679
680 return rtrim( $upload_dir['url'], '/' ) . '/' . $filename;
681 }
682
683 /**
684 * @author Vova Feldman (@svovaf)
685 * @since 1.2.1.6
686 *
687 * @param string $filename
688 *
689 * @return string
690 */
691 public static function get_logs_download_url( $filename = '' ) {
692 $upload_dir = wp_upload_dir();
693 if ( empty( $filename ) ) {
694 $filename = 'fs-logs-' . date( 'Y-m-d_H-i-s', WP_FS__SCRIPT_START_TIME ) . '.csv';
695 }
696
697 return rtrim( $upload_dir['url'], '/' ) . $filename;
698 }
699
700 /**
701 * @param string $file_path
702 * @param array $query_results
703 *
704 * @return bool
705 */
706 private static function write_csv_to_filesystem( $file_path, $query_results ) {
707 if ( empty( $query_results ) ) {
708 return false;
709 }
710
711 $content = '';
712
713 foreach ( $query_results as $row ) {
714 $row_data = array_map( function ( $value ) {
715 return str_replace( "\n", ' ', $value );
716 }, (array) $row );
717 $content .= implode( "\t", $row_data ) . "\n";
718 }
719
720 if ( ! $GLOBALS['wp_filesystem']->put_contents( $file_path, $content, FS_CHMOD_FILE ) ) {
721 return false;
722 }
723
724 return true;
725 }
726
727 #endregion
728 }
729