PluginProbe
Debug Log Manager – Conveniently Monitor and Inspect Errors / 2.3.1
Debug Log Manager – Conveniently Monitor and Inspect Errors v2.3.1
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.3.1, at classes/class-debug-log.php

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