PluginProbe
Debug Log Manager – Conveniently Monitor and Inspect Errors / 2.1.3
Debug Log Manager – Conveniently Monitor and Inspect Errors v2.1.3
2.5.2 2.5.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.8.0 1.8.2 1.8.3 1.8.4 All 49 releases
debug-log-manager / classes / class-debug-log.php

class-debug-log.php in Debug Log Manager – Conveniently Monitor and Inspect Errors 2.1.3, at classes/class-debug-log.php

925 lines 33.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace DLM\Classes;
4
5 /**
6 * Class related to the debug log file and entries
7 *
8 * @since 1.0.0
9 */
10 class Debug_Log {
11
12 // The wp_config object
13 private $wp_config;
14
15 /**
16 * Class constructor
17 */
18 public function __construct() {
19
20 $this->wp_config = new WP_Config_Transformer; // already in the DLM\Classes namespace, so, no need to repeat
21
22 }
23
24 /**
25 * Get status of WP_DEBUG
26 *
27 * @since 1.0.0
28 */
29 public function get_status() {
30
31 $value = get_option( 'debug_log_manager' );
32 $status = $value['status'];
33
34 if ( function_exists( 'wp_date' ) ) {
35 $date_time = wp_date( 'M j, Y - H:i:s', strtotime( $value['on'] ) );
36 } else {
37 $date_time = date_i18n( 'M j, Y - H:i:s', strtotime( $value['on'] ) );
38 }
39
40 if ( 'enabled' == $status ) {
41
42 return '<div id="debug-log-status" class="dlm-log-status"><strong>' . esc_html__( 'Error Logging', 'debug-log-manager' ) . '</strong>: ' . ucfirst( esc_html__( 'enabled', 'debug-log-manager' ) ) . ' ' . esc_html__( 'on', 'debug-log-manager' ) . ' ' . esc_html( $date_time ) . '</div>';
43
44 } elseif ( 'disabled' == $status ) {
45
46 return '<div id="debug-log-status" class="dlm-log-status"><strong>' . esc_html__( 'Error Logging', 'debug-log-manager' ) . '</strong>: ' . ucfirst( esc_html__( 'disabled', 'debug-log-manager' ) ) . ' ' . esc_html__( 'on', 'debug-log-manager' ) . ' ' . esc_html( $date_time ) . '</div>';
47
48 } else {}
49
50 }
51
52 /**
53 * Get log auto-refresh status
54 *
55 * @since 1.3.0
56 */
57 public function get_autorefresh_status() {
58
59 if ( false !== get_option( 'debug_log_manager_autorefresh' ) ) {
60
61 $autorefresh_status = get_option( 'debug_log_manager_autorefresh' );
62
63 } else {
64
65 $autorefresh_status = 'disabled';
66 update_option( 'debug_log_manager_autorefresh', $autorefresh_status, false );
67
68 }
69
70 if ( 'enabled' == $autorefresh_status ) {
71
72 return '<div id="debug-autorefresh-status" class="dlm-autorefresh-status"><strong>Auto-Refresh</strong>: ' . ucfirst( esc_html__( 'enabled', 'debug-log-manager' ) ) . '</div>';
73
74 } elseif ( 'disabled' == $autorefresh_status ) {
75
76 return '<div id="debug-autorefresh-status" class="dlm-autorefresh-status"><strong>Auto-Refresh</strong>: ' . ucfirst( esc_html__( 'disabled', 'debug-log-manager' ) ) . '</div>';
77
78 }
79
80 }
81
82 /**
83 * Enable / disable WP_DEBUG
84 *
85 * @since 1.0.0
86 */
87 public function toggle_debugging() {
88
89 if ( isset( $_REQUEST ) ) {
90
91 $log_info = get_option( 'debug_log_manager' );
92 $dlm_debug_log_file_path = get_option( 'debug_log_manager_file_path' );
93
94 if ( function_exists( 'wp_date' ) ) {
95 $date_time = wp_date( 'M j, Y - H:i:s' ); // Localized according to WP timezone settings
96 } else {
97 $date_time = date_i18n( 'M j, Y - H:i:s' );
98
99 }
100 $date_time_for_option = date( 'M j, Y H:i:s' ); // in UTC
101
102 if ( 'disabled' == $log_info['status'] ) {
103
104 $option_value = array(
105 'status' => 'enabled',
106 'on' => $date_time_for_option,
107 );
108
109 update_option( 'debug_log_manager', $option_value, false );
110
111 // If WP_DEBUG_LOG is defined, copy content of existing debug.log file into the log file created by this plugin
112
113 if ( $this->wp_config->exists( 'constant', 'WP_DEBUG_LOG' ) ) {
114
115 $wp_debug_log_const = $this->wp_config->get_value( 'constant', 'WP_DEBUG_LOG' );
116
117 if ( in_array( $wp_debug_log_const, array( 'true', 'false' ), true ) ) {
118 $wp_debug_log_const = (bool) $wp_debug_log_const;
119 }
120
121 if ( is_bool( $wp_debug_log_const ) ) {
122 // WP_DEBUG_LOG is true or false. Log file is in default location of /wp-content/debug.log.
123
124 if ( is_file( WP_CONTENT_DIR . '/debug.log' ) ) {
125 // Copy existing debug log content to this plugin's debug log.
126 $default_debug_log_content = file_get_contents( WP_CONTENT_DIR . '/debug.log' );
127 file_put_contents( $dlm_debug_log_file_path, $default_debug_log_content );
128 unlink( realpath( WP_CONTENT_DIR . '/debug.log' ) ); // delete existing debug log
129 }
130
131 } elseif ( is_string( $wp_debug_log_const ) ) {
132 // WP_DEBUG_LOG is custom path to log file. Copy existing debug log content to this plugin's debug log.
133
134 if ( is_file( $wp_debug_log_const ) && ( $wp_debug_log_const != $dlm_debug_log_file_path ) ) {
135 $custom_debug_log_content = file_get_contents( $wp_debug_log_const );
136 file_put_contents( $dlm_debug_log_file_path, $custom_debug_log_content );
137 unlink( $wp_debug_log_const ); // delete existing debug log
138 }
139
140 }
141
142 $copy = true; // existing debug.log file's entries are copied to this plugin's debug.log file
143
144 } else {
145
146 $copy = false; // existing debug.log file's entries are NOT copied to this plugin's debug.log file
147
148 }
149
150 // Define Debug constants in wp-config.php
151
152 $options = array(
153 'add' => true, // Add the config if missing.
154 'raw' => true, // Display value in raw format without quotes.
155 'normalize' => false, // Normalize config output using WP Coding Standards.
156 );
157
158 $this->wp_config->update( 'constant', 'WP_DEBUG', 'true', $options );
159
160 $options = array(
161 'add' => true, // Add the config if missing.
162 'raw' => true, // Display value in raw format without quotes.
163 'normalize' => false, // Normalize config output using WP Coding Standards.
164 );
165
166 $this->wp_config->update( 'constant', 'SCRIPT_DEBUG', 'true', $options );
167
168 $options = array(
169 'add' => true, // Add the config if missing.
170 'raw' => false, // Display value in raw format without quotes.
171 'normalize' => false, // Normalize config output using WP Coding Standards.
172 );
173
174 $this->wp_config->update( 'constant', 'WP_DEBUG_LOG', get_option( 'debug_log_manager_file_path' ), $options );
175
176 $options = array(
177 'add' => true, // Add the config if missing.
178 'raw' => true, // Display value in raw format without quotes.
179 'normalize' => false, // Normalize config output using WP Coding Standards.
180 );
181
182 $this->wp_config->update( 'constant', 'WP_DEBUG_DISPLAY', 'false', $options );
183
184 $options = array(
185 'add' => true, // Add the config if missing.
186 'raw' => true, // Display value in raw format without quotes.
187 'normalize' => false, // Normalize config output using WP Coding Standards.
188 );
189
190 $this->wp_config->update( 'constant', 'DISALLOW_FILE_EDIT', 'false', $options );
191
192 // Get the debug.log file size
193
194 $log_file_path = get_option( 'debug_log_manager_file_path' );
195 $log_file_shortpath = str_replace( sanitize_text_field( $_SERVER['DOCUMENT_ROOT'] ), "", $log_file_path );
196 $file_size = size_format( (int) filesize( $log_file_path ) );
197
198 // Prepare entries from the debug log for the data table
199
200 $errors_master_list = json_decode( $this->get_processed_entries(), true );
201
202 $n = 1;
203 $entries = array();
204
205 foreach ( $errors_master_list as $error ) {
206
207 if ( function_exists( 'wp_date' ) ) {
208 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
209 } else {
210 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
211 }
212
213 $occurrence_count = count( $error['occurrences'] );
214
215 $entry = array(
216 $n,
217 $error['type'],
218 $error['details'],
219 $localized_timestamp . '<br /><span class="dlm-faint">(' . sprintf( _n( '%s occurrence logged', '%s occurrences logged', $occurrence_count, 'debug-log-manager' ), number_format_i18n( $occurrence_count ) ) . ')<span>',
220 );
221
222 $entries[] = $entry;
223
224 $n++;
225
226 }
227
228 // Assemble data to return
229
230 $data = array(
231 'status' => 'enabled',
232 'copy' => $copy,
233 'message' => '<strong>' . esc_html__( 'Error Logging', 'debug-log-manager' ) . '</strong>: ' . esc_html__( 'Enabled on', 'debug-log-manager' ) . ' ' . esc_html( $date_time ),
234 'entries' => $entries,
235 'size' => $file_size,
236 );
237
238 echo json_encode( $data );
239
240 } elseif ( 'enabled' == $log_info['status'] ) {
241
242 $option_value = array(
243 'status' => 'disabled',
244 'on' => $date_time_for_option,
245 );
246
247 update_option( 'debug_log_manager', $option_value, false );
248
249 // Remove Debug constants in wp-config.php
250
251 $this->wp_config->remove( 'constant', 'WP_DEBUG' );
252 $this->wp_config->remove( 'constant', 'SCRIPT_DEBUG' );
253 $this->wp_config->remove( 'constant', 'WP_DEBUG_LOG' );
254 $this->wp_config->remove( 'constant', 'WP_DEBUG_DISPLAY' );
255 $this->wp_config->remove( 'constant', 'DISALLOW_FILE_EDIT' );
256
257 // Assemble data to return
258
259 $data = array(
260 'status' => 'disabled',
261 'copy' => false,
262 'message' => '<strong>' . esc_html__( 'Error Logging', 'debug-log-manager' ) . '</strong>: ' . esc_html__( 'Disabled on', 'debug-log-manager' ) . ' ' . esc_html( $date_time ),
263 'entries' => '',
264 'size' => '',
265 );
266
267 echo json_encode( $data );
268
269 } else {}
270
271 }
272
273 }
274
275 /**
276 * Toggle auto-refresh of entries table
277 *
278 * @since 1.3.0
279 */
280 public function toggle_autorefresh() {
281
282 if ( isset( $_REQUEST ) ) {
283
284 $autorefresh_status = get_option( 'debug_log_manager_autorefresh' );
285
286 if ( $autorefresh_status == 'disabled' ) {
287
288 update_option( 'debug_log_manager_autorefresh', 'enabled', false );
289
290 $data = array(
291 'status' => 'enabled',
292 'message' => '<strong>' . esc_html__( 'Auto-Refresh', 'debug-log-manager' ) . '</strong>: ' . esc_html__( 'Enabled', 'debug-log-manager' ),
293 );
294
295 echo json_encode( $data );
296
297 } elseif ( $autorefresh_status == 'enabled' ) {
298
299 update_option( 'debug_log_manager_autorefresh', 'disabled', false );
300
301 $data = array(
302 'status' => 'disabled',
303 'message' => '<strong>' . esc_html__( 'Auto-Refresh', 'debug-log-manager' ) . '</strong>: ' . esc_html__( 'Disabled', 'debug-log-manager' ),
304 );
305
306 echo json_encode( $data );
307
308 } else {}
309
310 }
311
312 }
313
314 /**
315 * Get the processed debug log data
316 *
317 * @return string $errors_master_list The processed error log entries
318 * @since 1.2.0
319 */
320 public function get_processed_entries() {
321
322 $debug_log_file_path = get_option( 'debug_log_manager_file_path' );
323
324 // Read the errors log file
325 $log = file_get_contents( $debug_log_file_path );
326
327 $log = str_replace( "[\\", "^\\", $log ); // certain error message contains the '[\' string, which will make the following split via explode() to split lines at places in the message it's not supposed to. So, we temporarily replace those with '^\'
328
329 $log = str_replace( "[\"", "^\"", $log ); // certain error message contains the '["' string, which will make the following split via explode() to split lines at places in the message it's not supposed to. So, we temporarily replace those with '^"'
330
331 $log = str_replace( "[internal function]", "^internal function^", $log );
332
333 // We are splitting the log file not using PHP_EOL to preserve the stack traces for PHP Fatal Errors among other things
334 $lines = explode("[", $log);
335 $prepended_lines = array();
336
337 // Pluck out the last 100k entries, the newest entry is last
338 // To pluck out the first 100000 entries, use array_slice( $lines, 0, 100000 )
339 $lines = array_slice( $lines, -100000 );
340
341 foreach ( $lines as $line ) {
342 if ( !empty($line) ) {
343 $line = str_replace( "UTC]", "UTC]@@@", $line ); // add '@@@' as marker/separator after time stamp
344 $line = str_replace( "Stack trace:", "<hr />Stack trace:", $line ); // add line break for stack trace section
345 if ( strpos( $line, 'PHP Fatal' ) !== false ) {
346 $line = str_replace( "#", "<hr />#", $line ); // add line break on PHP Fatal error's stack trace lines
347 }
348 $line = str_replace( "Argument <hr />#", "Argument #", $line ); // remove hr on certain error message
349 $line = str_replace( "parameter <hr />#", "parameter #", $line ); // remove hr on certain error message
350 $line = str_replace( "the <hr />#", "the #", $line ); // remove hr on certain error message
351 $line = str_replace( "^\\", "[\\", $line ); // reverse the temporary replacement of '[\' with '^\'
352 $line = str_replace( "^\"", "[\"", $line ); // reverse the temporary replacement of '["' with '^"'
353 $line = str_replace( "^internal function^", "[internal function]", $line );
354 $prepended_line = '[' . $line; // Put back the missing '[' after explode operation
355 $prepended_lines[] = $prepended_line;
356 }
357 }
358
359 // Reverse the order of the entries, so the newest entry is first
360 $latest_lines = array_reverse( $prepended_lines );
361
362 // Will hold error details types
363 $errors_master_list = array();
364
365 foreach( $latest_lines as $line ) {
366
367 $line = explode("@@@ ", trim( $line ) ); // split the line using the '@@@' marker/separator defined earlier. '@@@' will be deleted by explode().
368
369 $timestamp = str_replace( [ "[", "]" ], "", $line[0] );
370
371 // Initialize error-related variables
372 $error = '';
373 $error_source = '';
374 $error_file = '';
375 $error_file_path = '';
376 $error_file_line = '';
377
378 if ( array_key_exists('1', $line) ) {
379 $error = $line[1];
380
381 // Check if there is a file path to pluck out of the error line
382 if ( false !== strpos( $error, ABSPATH ) ) {
383
384 // Separata file path and error line from error message
385
386 // Handling for PHP Fatal errors with stack trace included
387 if ( false !== strpos( $error, 'Stack trace:' ) ) {
388 $error_parts = explode( 'Stack trace:', $error );
389 $error_message = str_replace( '<hr />', '', $error_parts[0] );
390 if ( isset( $error_parts[1] ) ) {
391 $error_stack_trace = ' ' . $error_parts[1];
392 }
393
394 $error_message_parts = explode( ' in /', $error_message );
395
396 // Reconstruct the error details without error file path and line info
397 $error = $error_message_parts[0] . '<hr />Stack trace:' . $error_stack_trace;
398 // Shorten the file path in the error details
399 $error = str_replace( ABSPATH, '/', $error );
400 if ( isset( $error_message_parts[1] ) ) {
401 $error_file = '/' . $error_message_parts[1];
402 $error_file_info = explode ( ':', $error_file );
403 $error_file_path = $error_file_info[0];
404 if ( array_key_exists('1', $error_file_info) ) {
405 $error_file_line = $error_file_info[1];
406 }
407 }
408 } else {
409 $error_message_parts = explode( ' in /', $error );
410
411 $error = $error_message_parts[0];
412 if ( isset( $error_message_parts[1] ) ) {
413 $error_file = '/' . $error_message_parts[1];
414
415 $error_file_info = explode ( ' on line ', $error_file );
416 $error_file_path = $error_file_info[0];
417 if ( array_key_exists('1', $error_file_info) ) {
418 $error_file_line = $error_file_info[1];
419 }
420 }
421 }
422
423 // Shorten the file path where the error occurred
424 $error_file_path = str_replace( ABSPATH, '/', $error_file_path );
425
426 // Define whether source of error is WP Core, Theme, Plugin or Other
427
428 if ( ( false !== strpos( $error_file, '/wp-admin/' ) ) ||
429 ( false !== strpos( $error_file, '/wp-includes/' ) ) ) {
430 $error_source = __( 'WordPress core', 'debug-log-manager' );
431 } elseif ( ( false !== strpos( $error_file, '/wp-content/themes/' ) ) ) {
432 $error_source = __( 'Theme', 'debug-log-manager' );
433 } elseif ( ( false !== strpos( $error_file, '/wp-content/plugins/' ) ) ) {
434 $error_source = __( 'Plugin', 'debug-log-manager' );
435 } else {
436 $error_source = '';
437 }
438
439 // Get plugin/theme directory name of error file when error source is plugin or theme
440
441 if ( ( 'Plugin' == $error_source ) || ( 'Theme' == $error_source ) ) {
442 $error_file_path_parts = explode( '/', $error_file_path );
443 $error_file_directory = $error_file_path_parts[3];
444 }
445
446 // Get plugin name
447
448 $plugins = get_plugins();
449
450 if ( 'Plugin' == $error_source ) {
451 foreach ( $plugins as $plugin_path_file => $plugin_info ) {
452 if ( false !== strpos( $plugin_path_file, $error_file_directory ) ) {
453 $error_source_plugin_path_file = $plugin_path_file;
454 $error_source_plugin_name = $plugin_info['Name'];
455 $error_source_plugin_uri = $plugin_info['PluginURI'];
456 // $error_source_plugin_version = $plugin_info['Version'];
457 }
458 }
459 }
460
461 // Get theme name
462
463 if ( 'Theme' == $error_source ) {
464 $theme = wp_get_theme( $error_file_directory );
465 if ( $theme->exists() ) {
466 $error_source_theme_dir = $error_file_directory;
467 $error_source_theme_name = $theme->get( 'Name' );
468 $error_source_theme_uri = $theme->get( 'ThemeURI' );
469 // $error_source_theme_version = $theme->get( 'Version' );
470 } else {
471 $error_source_theme_name = $error_file_directory;
472 }
473 }
474
475 }
476
477 } else {
478
479 $error = __( 'No error message specified...', 'debug-log-manager' );
480
481 }
482
483 if ( ( false !== strpos( $error, 'PHP Fatal' )) || ( false !== strpos( $error, 'FATAL' ) ) || ( false !== strpos( $error, 'E_ERROR' ) ) ) {
484 $error_type = __( 'PHP Fatal', 'debug-log-manager' );
485 $error_details = str_replace( "PHP Fatal error: ", "", $error );
486 $error_details = str_replace( "PHP Fatal: ", "", $error_details );
487 $error_details = str_replace( "FATAL ", "", $error_details );
488 $error_details = str_replace( "E_ERROR: ", "", $error_details );
489 } elseif ( ( false !== strpos( $error, 'PHP Warning' ) ) || ( false !== strpos( $error, 'E_WARNING' ) ) ) {
490 $error_type = __( 'PHP Warning', 'debug-log-manager' );
491 $error_details = str_replace( "PHP Warning: ", "", $error );
492 $error_details = str_replace( "E_WARNING: ", "", $error_details );
493 } elseif ( ( false !== strpos( $error, 'PHP Notice' ) ) || ( false !== strpos( $error, 'E_NOTICE' ) ) ) {
494 $error_type = __( 'PHP Notice', 'debug-log-manager' );
495 $error_details = str_replace( "PHP Notice: ", "", $error );
496 $error_details = str_replace( "E_NOTICE: ", "", $error_details );
497 } elseif ( false !== strpos( $error, 'PHP Deprecated' ) ) {
498 $error_type = __( 'PHP Deprecated', 'debug-log-manager' );
499 $error_details = str_replace( "PHP Deprecated: ", "", $error );
500 } elseif ( ( false !== strpos( $error, 'PHP Parse' ) ) || ( false !== strpos( $error, 'E_PARSE' ) ) ) {
501 $error_type = __( 'PHP Parse', 'debug-log-manager' );
502 $error_details = str_replace( "PHP Parse error: ", "", $error );
503 $error_details = str_replace( "E_PARSE: ", "", $error_details );
504 } elseif ( false !== strpos( $error, 'EXCEPTION:' ) ) {
505 $error_type = __( 'PHP Exception', 'debug-log-manager' );
506 $error_details = str_replace( "EXCEPTION: ", "", $error );
507 } elseif ( false !== strpos( $error, 'WordPress database error' ) ) {
508 $error_type = __( 'Database', 'debug-log-manager' );
509 $error_details = str_replace( "WordPress database error ", "", $error );
510 } elseif ( false !== strpos( $error, 'JavaScript Error' ) ) {
511 $error_type = __( 'JavaScript', 'debug-log-manager' );
512 $error_details = str_replace( "JavaScript Error: ", "", $error );
513 } else {
514 $error_type = __( 'Other', 'debug-log-manager' );
515 $error_details = $error;
516 if ( $this->is_json( $error_details ) ) {
517 // For JSON string in error message, originally added via error_log( json_encode( $variable ) )
518 // This will output said JSON string as well-formated array in the log entries table
519 $error_details = '<pre>' . print_r( json_decode( $error_details, true ), true ) . '</pre>';
520 }
521 }
522
523 // Append error source, file path and line number info to error details. If core plugin/theme editor is not disabled, link file path to the editor view.
524
525 if ( ! empty( $error_source ) ) {
526 if ( 'WordPress core' == $error_source ) {
527 $wp_version = get_bloginfo( 'version' );
528 $file_viewer_url = 'https://github.com/WordPress/wordpress-develop/blob/' . $wp_version . '/src' . $error_file_path;
529 $error_details = '<span class="error-details">' . $error_details . '</span><hr />' . $error_source . '<br />' . __( 'File', 'debug-log-manager' ) . ': <a href="' . $file_viewer_url . '" target="_blank" class="error-source-link">' . $error_file_path . '<span class="dashicons dashicons-visibility offset-down"></span></a><br />' . __( 'Line', 'debug-log-manager' ) . ': ' . $error_file_line;
530 } elseif ( 'Theme' == $error_source ) {
531 if ( ! defined( 'DISALLOW_FILE_EDIT' ) || ( false === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
532 $file_viewer_url = get_admin_url() . 'theme-editor.php?file=' . urlencode( str_replace( '/wp-content/themes/', '', $error_file_path ) ) . '&theme=' . $error_source_theme_dir;
533 $error_details = '<span class="error-details">' . $error_details . '</span><hr />' . $error_source . ': <a href="' . $error_source_theme_uri . '" target="_blank" class="error-source-link">' . $error_source_theme_name . '<span class="dashicons dashicons-external offset-up"></span></a><br />' . __( 'File', 'debug-log-manager' ) . ': <a href="' . $file_viewer_url . '" target="_blank" class="error-source-link">' . $error_file_path . '<span class="dashicons dashicons-visibility offset-down"></span></a><br />' . __( 'Line', 'debug-log-manager' ) . ': ' . $error_file_line;
534 }
535 if ( defined( 'DISALLOW_FILE_EDIT' ) && ( true === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
536 $error_details = '<span class="error-details">' . $error_details . '</span><hr />' . $error_source . ': <a href="' . $error_source_theme_uri . '" target="_blank" class="error-source-link">' . $error_source_theme_name . '<span class="dashicons dashicons-external offset-up"></span></a><br />' . __( 'File', 'debug-log-manager' ) . ': ' . $error_file_path . '<br />' . __( 'Line', 'debug-log-manager' ) . ': ' . $error_file_line;
537 }
538 } elseif ( 'Plugin' == $error_source ) {
539 if ( ! defined( 'DISALLOW_FILE_EDIT' ) || ( false === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
540 $file_viewer_url = get_admin_url() . 'plugin-editor.php?file=' . urlencode( str_replace( '/wp-content/plugins/', '', $error_file_path ) ) . '&plugin=' . urlencode( $error_source_plugin_path_file );
541 $error_details = '<span class="error-details">' . $error_details . '</span><hr />' . $error_source . ': <a href="' . $error_source_plugin_uri . '" target="_blank" class="error-source-link">' . $error_source_plugin_name . '<span class="dashicons dashicons-external offset-up"></span></a><br />' . __( 'File', 'debug-log-manager' ) . ': <a href="' . $file_viewer_url . '" target="_blank" class="error-source-link">' . $error_file_path . '<span class="dashicons dashicons-visibility offset-down"></span></a><br />' . __( 'Line', 'debug-log-manager' ) . ': ' . $error_file_line;
542 }
543 if ( defined( 'DISALLOW_FILE_EDIT' ) && ( true === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
544 $error_details = '<span class="error-details">' . $error_details . '</span><hr />' . $error_source . ': <a href="' . $error_source_plugin_uri . '" target="_blank" class="error-source-link">' . $error_source_plugin_name . '<span class="dashicons dashicons-external offset-up"></span></a><br />' . __( 'File', 'debug-log-manager' ) . ': ' . $error_file_path . '<br />' . __( 'Line', 'debug-log-manager' ) . ': ' . $error_file_line;
545 }
546 }
547 }
548
549 // https://www.php.net/manual/en/function.array-search.php#120784
550 if ( array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ) === false ) {
551
552 $errors_master_list[] = array(
553 'type' => $error_type,
554 'details' => trim( $error_details ),
555 'occurrences' => array( $timestamp ),
556 );
557
558 } else {
559
560 $error_position = array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ); // integer
561
562 array_push( $errors_master_list[$error_position]['occurrences'], $timestamp );
563
564 }
565
566 }
567
568 return json_encode( $errors_master_list );
569
570 }
571
572 /**
573 * Get the latest entries for auto-refresh feature
574 *
575 * @since 1.0.0
576 */
577 public function get_latest_entries() {
578
579 $errors_master_list = json_decode( $this->get_processed_entries(), true );
580
581 $n = 1;
582 $entries = array();
583
584 foreach ( $errors_master_list as $error ) {
585
586 if ( function_exists( 'wp_date' ) ) {
587 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
588 } else {
589 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
590 }
591
592 $occurrence_count = count( $error['occurrences'] );
593
594 $entry = array(
595 $n,
596 $error['type'],
597 $error['details'],
598 $localized_timestamp . '<br /><span class="dlm-faint">(' . sprintf( _n( '%s occurrence logged', '%s occurrences logged', $occurrence_count, 'debug-log-manager' ), number_format_i18n( $occurrence_count ) ) . ')<span>',
599 );
600
601 $entries[] = $entry;
602
603 $n++;
604
605 }
606
607 $data = array(
608 'entries' => $entries,
609 );
610
611 echo json_encode( $data );
612
613 }
614
615 /**
616 * Get debug log in data table format
617 *
618 * @since 1.0.0
619 */
620 public function get_entries_datatable() {
621
622 ?>
623 <div>
624 <select id="errorTypeFilter" class="dlm-error-type-filter">
625 <option value=""><?php esc_html_e( 'All Error Types', 'debug-log-manager' ); ?></option>
626 <option value="<?php esc_attr_e( 'PHP Fatal', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Fatal', 'debug-log-manager' ); ?></option>
627 <option value="<?php esc_attr_e( 'PHP Warning', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Warning', 'debug-log-manager' ); ?></option>
628 <option value="<?php esc_attr_e( 'PHP Notice', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Notice', 'debug-log-manager' ); ?></option>
629 <option value="<?php esc_attr_e( 'PHP Deprecated', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Deprecated', 'debug-log-manager' ); ?></option>
630 <option value="<?php esc_attr_e( 'PHP Parse', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Parse', 'debug-log-manager' ); ?></option>
631 <option value="<?php esc_attr_e( 'PHP Exception', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Exception', 'debug-log-manager' ); ?></option>
632 <option value="<?php esc_attr_e( 'Database', 'debug-log-manager' ); ?>"><?php esc_html_e( 'Database', 'debug-log-manager' ); ?></option>
633 <option value="<?php esc_attr_e( 'JavaScript', 'debug-log-manager' ); ?>"><?php esc_html_e( 'JavaScript', 'debug-log-manager' ); ?></option>
634 <option value="<?php esc_attr_e( 'Other', 'debug-log-manager' ); ?>"><?php esc_html_e( 'Other', 'debug-log-manager' ); ?></option>
635 </select>
636 </div>
637 <table id="debug-log" class="wp-list-table widefat striped">
638 <thead>
639 <tr>
640 <th class="dlm-entry-no">#</th>
641 <th class="dlm-entry-type"><?php esc_html_e( 'Error Type', 'debug-log-manager' ); ?></th>
642 <th class="dlm-entry-details"><?php esc_html_e( 'Details', 'debug-log-manager' ); ?></th>
643 <th class="dlm-entry-datetime"><?php esc_html_e( 'Last Occurrence', 'debug-log-manager' ); ?></th>
644 </tr>
645 </thead>
646 <tbody>
647 <?php
648
649 $errors_master_list = json_decode( $this->get_processed_entries(), true );
650
651 $n = 1;
652
653 foreach ( $errors_master_list as $error ) {
654
655 if ( function_exists( 'wp_date' ) ) {
656 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
657 } else {
658 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
659 }
660
661 $occurrence_count = count( $error['occurrences'] );
662 ?>
663
664 <tr>
665 <td class="dlm-entry-no"><?php echo esc_html( $n ); ?></td>
666 <td class="dlm-entry-type"><?php echo esc_html( $error['type'] ); ?></td>
667 <td class="dlm-entry-details"><?php echo wp_kses( $error['details'], 'post' ); ?></td>
668 <td class="dlm-entry-datetime"><?php echo esc_html( $localized_timestamp ); ?><br /><span class="dlm-faint">(<?php printf( _n( '%s occurrence logged', '%s occurrences logged', $occurrence_count, 'debug-log-manager' ), number_format_i18n( $occurrence_count ) ); ?>)<span></td>
669 </tr>
670
671 <?php
672 $n++;
673
674 }
675
676 ?>
677 </tbody>
678 </table>
679 <?php
680
681 }
682
683 /**
684 * Get entries for dashboard widget
685 *
686 * @since 1.8.0
687 */
688 public function get_dashboard_widget_entries() {
689
690 $errors_master_list = json_decode( $this->get_processed_entries(), true );
691
692 $n = 1;
693 $entries_to_show = 5;
694
695 ?>
696 <style>
697
698 #debug_log_manager_widget.postbox .inside {
699 margin: 0;
700 padding: 0;
701 }
702
703 .dlm-dashboard-widget-entry {
704 padding: 12px;
705 border-bottom: 1px solid #e6e7e7;
706 word-wrap: break-word; /* All browsers since IE 5.5+ */
707 overflow-wrap: break-word; /* Renamed property in CSS3 draft spec */
708 }
709
710 .dlm-dashboard-widget-entry:nth-child(odd) {
711 background-color: #f6f7f7;
712 }
713
714 .dlm-dashboard-widget-entry-message a.error-source-link {
715 color: #50575e;
716 text-decoration: none;
717 }
718
719 .dlm-dashboard-widget-entry-message a.error-source-link span {
720 position: relative;
721 margin-left: 2px;
722 color: #777;
723 font-size: 18px;
724 width: 18px;
725 height: 18px;
726 transition: .25s;
727 text-decoration: none;
728 }
729
730 .dlm-dashboard-widget-entry-message a.error-source-link span.offset-up {
731 top: -1px;
732 }
733
734 .dlm-dashboard-widget-entry-message a.error-source-link span.offset-down {
735 top: 1px;
736 }
737
738 .dlm-dashboard-widget-entry-message a.error-source-link:hover {
739 color: #2271b1;
740 text-decoration: underline;
741 }
742
743 #debug-log .dlm-entry-details a.error-source-link:hover span {
744 color: #2271b1;
745 text-decoration: none;
746 }
747
748 .dlm-dashboard-widget-entry-meta {
749 display: flex;
750 }
751
752 .dlm-dashboard-widget-entry-type {
753 margin-right: 4px;
754 font-weight: 600;
755 }
756
757 .dlm-dashboard-widget-footer {
758 display: flex;
759 justify-content: space-between;
760 align-items: center;
761 width: 100%;
762 box-sizing: border-box;
763 padding: 12px;
764 background-color: #f6f7f7;
765 }
766
767 </style>
768 <div class="dlm-dashboard-widget-entries">
769 <?php
770
771 foreach ( $errors_master_list as $error ) {
772
773 if ( $n <= $entries_to_show ) {
774
775 if ( function_exists( 'wp_date' ) ) {
776 $localized_timestamp = wp_date( 'M j, Y, H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
777 } else {
778 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
779 }
780
781 $occurrence_count = count( $error['occurrences'] );
782
783 ?>
784 <div class="dlm-dashboard-widget-entry">
785 <div class="dlm-dashboard-widget-entry-meta">
786 <div class="dlm-dashboard-widget-entry-type">
787 <?php echo esc_html( $error['type'] ); ?>
788 </div>
789 <div class="dlm-dashboard-widget-entry-datetime">
790 | <?php echo esc_html( $localized_timestamp ); ?>
791 </div>
792 </div>
793 <div class="dlm-dashboard-widget-entry-message">
794 <?php echo wp_kses( $error['details'], 'post' ); ?>
795 </div>
796 </div>
797 <?php
798
799 }
800
801 $n++;
802
803 }
804
805 ?>
806 </div>
807 <div class="dlm-dashboard-widget-footer">
808 <div class="dlm-dashboard-widget-logging-status">
809 <?php echo $this->get_status(); ?>
810 </div>
811 <a href="<?php echo get_dashboard_url(); ?>tools.php?page=debug-log-manager" class="button"><?php esc_html_e( 'Go to Debug Log Manager', 'debug-log-manager' ); ?></a>
812 </div>
813 <?php
814
815 }
816
817 /**
818 * Clear log file
819 *
820 * @since 1.0.0
821 */
822 public function clear_log() {
823
824 $debug_log_file_path = get_option( 'debug_log_manager_file_path' );
825
826 file_put_contents( $debug_log_file_path, '' );
827
828 echo true;
829
830 }
831
832 /**
833 * Disable WP core's plugin/theme editor
834 *
835 * @since 2.0.0
836 */
837 public function disable_wp_file_editor() {
838
839 $options = array(
840 'add' => true, // Add the config if missing.
841 'raw' => true, // Display value in raw format without quotes.
842 'normalize' => false, // Normalize config output using WP Coding Standards.
843 );
844
845 $this->wp_config->update( 'constant', 'DISALLOW_FILE_EDIT', 'true', $options );
846
847 // Prepare entries from the debug log for the data table
848
849 $errors_master_list = json_decode( $this->get_processed_entries(), true );
850
851 $n = 1;
852 $entries = array();
853
854 foreach ( $errors_master_list as $error ) {
855
856 if ( function_exists( 'wp_date' ) ) {
857 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
858 } else {
859 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
860 }
861
862 $occurrence_count = count( $error['occurrences'] );
863
864 $entry = array(
865 $n,
866 $error['type'],
867 $error['details'],
868 $localized_timestamp . '<br /><span class="dlm-faint">(' . sprintf( _n( '%s occurrence logged', '%s occurrences logged', $occurrence_count, 'debug-log-manager' ), number_format_i18n( $occurrence_count ) ) . ')<span>',
869 );
870
871 $entries[] = $entry;
872
873 $n++;
874
875 }
876
877 // Assemble data to return
878
879 $data = array(
880 'status' => 'disabled', // Plugin/theme editor
881 'entries' => $entries, // To parse data table with unlinked plugin/theme file paths
882 );
883
884 echo json_encode( $data );
885
886 }
887
888 /**
889 * Log javascript errors
890 *
891 * @since 1.4.0
892 */
893 public function log_js_errors() {
894
895 // Since we are using XHR for the js error logging, JSON data comes in via php://input
896 $request = json_decode(urldecode(file_get_contents('php://input')), true); // an array
897
898 // Verify error content and nonce and then log the JS error
899 // Source: https://plugins.svn.wordpress.org/lh-javascript-error-log/trunk/lh-javascript-error-log.php
900 if ( isset( $request['message'] ) && isset( $request['script'] ) && isset( $request['lineNo'] ) && isset( $request['columnNo'] ) && ! empty( $request['nonce'] ) && wp_verify_nonce( $request['nonce'], DLM_SLUG ) ) {
901
902 error_log( 'JavaScript Error: ' . $request['message'] . ' in ' . $request['script'] . ' on line ' . $request['lineNo'] . ' column ' . $request['columnNo'] . ' at ' . get_site_url() . $request['pageUrl'] );
903
904 } else {
905
906 wp_die();
907
908 }
909
910 }
911
912 /**
913 * Check if a string is valid JSON
914 *
915 * @link https://stackoverflow.com/a/6041773
916 * @since 2.1.0
917 */
918 public function is_json( $string ) {
919
920 json_decode( $string );
921 return json_last_error() === JSON_ERROR_NONE;
922
923 }
924
925 }