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

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