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

939 lines 34.1 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 $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 '^\'
334
335 $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 '^"'
336
337 $log = str_replace( "[internal function]", "^internal function^", $log );
338
339 // We are splitting the log file not using PHP_EOL to preserve the stack traces for PHP Fatal Errors among other things
340 $lines = explode("[", $log);
341 $prepended_lines = array();
342
343 // Pluck out the last 100k entries, the newest entry is last
344 // To pluck out the first 100000 entries, use array_slice( $lines, 0, 100000 )
345 $lines = array_slice( $lines, -100000 );
346
347 foreach ( $lines as $line ) {
348 if ( !empty($line) ) {
349 $line = str_replace( "UTC]", "UTC]@@@", $line ); // add '@@@' as marker/separator after time stamp
350 $line = str_replace( "Stack trace:", "<hr />Stack trace:", $line ); // add line break for stack trace section
351 if ( strpos( $line, 'PHP Fatal' ) !== false ) {
352 $line = str_replace( "#", "<hr />#", $line ); // add line break on PHP Fatal error's stack trace lines
353 }
354 $line = str_replace( "Argument <hr />#", "Argument #", $line ); // remove hr on certain error message
355 $line = str_replace( "parameter <hr />#", "parameter #", $line ); // remove hr on certain error message
356 $line = str_replace( "the <hr />#", "the #", $line ); // remove hr on certain error message
357 $line = str_replace( "^\\", "[\\", $line ); // reverse the temporary replacement of '[\' with '^\'
358 $line = str_replace( "^\"", "[\"", $line ); // reverse the temporary replacement of '["' with '^"'
359 $line = str_replace( "^internal function^", "[internal function]", $line );
360 $prepended_line = '[' . $line; // Put back the missing '[' after explode operation
361 $prepended_lines[] = $prepended_line;
362 }
363 }
364
365 // Reverse the order of the entries, so the newest entry is first
366 $latest_lines = array_reverse( $prepended_lines );
367
368 // Will hold error details types
369 $errors_master_list = array();
370
371 foreach( $latest_lines as $line ) {
372
373 $line = explode("@@@ ", trim( $line ) ); // split the line using the '@@@' marker/separator defined earlier. '@@@' will be deleted by explode().
374
375 $timestamp = str_replace( [ "[", "]" ], "", $line[0] );
376
377 // Initialize error-related variables
378 $error = '';
379 $error_source = '';
380 $error_file = '';
381 $error_file_path = '';
382 $error_file_line = '';
383
384 if ( array_key_exists('1', $line) ) {
385 $error = $line[1];
386
387 // Check if there is a file path to pluck out of the error line
388 if ( false !== strpos( $error, ABSPATH ) ) {
389
390 // Separata file path and error line from error message
391
392 // Handling for PHP Fatal errors with stack trace included
393 if ( false !== strpos( $error, 'Stack trace:' ) ) {
394 $error_parts = explode( 'Stack trace:', $error );
395 $error_message = str_replace( '<hr />', '', $error_parts[0] );
396 if ( isset( $error_parts[1] ) ) {
397 $error_stack_trace = ' ' . $error_parts[1];
398 }
399
400 $error_message_parts = explode( ' in /', $error_message );
401
402 // Reconstruct the error details without error file path and line info
403 $error = $error_message_parts[0] . '<hr />Stack trace:' . $error_stack_trace;
404 // Shorten the file path in the error details
405 $error = str_replace( ABSPATH, '/', $error );
406 if ( isset( $error_message_parts[1] ) ) {
407 $error_file = '/' . $error_message_parts[1];
408 $error_file_info = explode ( ':', $error_file );
409 $error_file_path = $error_file_info[0];
410 if ( array_key_exists('1', $error_file_info) ) {
411 $error_file_line = $error_file_info[1];
412 }
413 }
414 } else {
415 $error_message_parts = explode( ' in /', $error );
416
417 $error = $error_message_parts[0];
418 if ( isset( $error_message_parts[1] ) ) {
419 $error_file = '/' . $error_message_parts[1];
420
421 $error_file_info = explode ( ' on line ', $error_file );
422 $error_file_path = $error_file_info[0];
423 if ( array_key_exists('1', $error_file_info) ) {
424 $error_file_line = $error_file_info[1];
425 }
426 }
427 }
428
429 // Shorten the file path where the error occurred
430 $error_file_path = str_replace( ABSPATH, '/', $error_file_path );
431
432 // Define whether source of error is WP Core, Theme, Plugin or Other
433
434 if ( ( false !== strpos( $error_file, '/wp-admin/' ) ) ||
435 ( false !== strpos( $error_file, '/wp-includes/' ) ) ) {
436 $error_source = __( 'WordPress core', 'debug-log-manager' );
437 } elseif ( ( false !== strpos( $error_file, '/wp-content/themes/' ) ) ) {
438 $error_source = __( 'Theme', 'debug-log-manager' );
439 } elseif ( ( false !== strpos( $error_file, '/wp-content/plugins/' ) ) ) {
440 $error_source = __( 'Plugin', 'debug-log-manager' );
441 } else {
442 $error_source = '';
443 }
444
445 // Get plugin/theme directory name of error file when error source is plugin or theme
446
447 if ( ( 'Plugin' == $error_source ) || ( 'Theme' == $error_source ) ) {
448 $error_file_path_parts = explode( '/', $error_file_path );
449 $error_file_directory = $error_file_path_parts[3];
450 }
451
452 // Get plugin name
453
454 $plugins = get_plugins();
455
456 if ( 'Plugin' == $error_source ) {
457 foreach ( $plugins as $plugin_path_file => $plugin_info ) {
458 if ( false !== strpos( $plugin_path_file, $error_file_directory ) ) {
459 $error_source_plugin_path_file = $plugin_path_file;
460 $error_source_plugin_name = $plugin_info['Name'];
461 $error_source_plugin_uri = $plugin_info['PluginURI'];
462 // $error_source_plugin_version = $plugin_info['Version'];
463 }
464 }
465 }
466
467 // Get theme name
468
469 if ( 'Theme' == $error_source ) {
470 $theme = wp_get_theme( $error_file_directory );
471 if ( $theme->exists() ) {
472 $error_source_theme_dir = $error_file_directory;
473 $error_source_theme_name = $theme->get( 'Name' );
474 $error_source_theme_uri = $theme->get( 'ThemeURI' );
475 // $error_source_theme_version = $theme->get( 'Version' );
476 } else {
477 $error_source_theme_name = $error_file_directory;
478 }
479 }
480
481 }
482
483 } else {
484
485 $error = __( 'No error message specified...', 'debug-log-manager' );
486
487 }
488
489 if ( ( false !== strpos( $error, 'PHP Fatal' )) || ( false !== strpos( $error, 'FATAL' ) ) || ( false !== strpos( $error, 'E_ERROR' ) ) ) {
490 $error_type = __( 'PHP Fatal', 'debug-log-manager' );
491 $error_details = str_replace( "PHP Fatal error: ", "", $error );
492 $error_details = str_replace( "PHP Fatal: ", "", $error_details );
493 $error_details = str_replace( "FATAL ", "", $error_details );
494 $error_details = str_replace( "E_ERROR: ", "", $error_details );
495 } elseif ( ( false !== strpos( $error, 'PHP Warning' ) ) || ( false !== strpos( $error, 'E_WARNING' ) ) ) {
496 $error_type = __( 'PHP Warning', 'debug-log-manager' );
497 $error_details = str_replace( "PHP Warning: ", "", $error );
498 $error_details = str_replace( "E_WARNING: ", "", $error_details );
499 } elseif ( ( false !== strpos( $error, 'PHP Notice' ) ) || ( false !== strpos( $error, 'E_NOTICE' ) ) ) {
500 $error_type = __( 'PHP Notice', 'debug-log-manager' );
501 $error_details = str_replace( "PHP Notice: ", "", $error );
502 $error_details = str_replace( "E_NOTICE: ", "", $error_details );
503 } elseif ( false !== strpos( $error, 'PHP Deprecated' ) ) {
504 $error_type = __( 'PHP Deprecated', 'debug-log-manager' );
505 $error_details = str_replace( "PHP Deprecated: ", "", $error );
506 } elseif ( ( false !== strpos( $error, 'PHP Parse' ) ) || ( false !== strpos( $error, 'E_PARSE' ) ) ) {
507 $error_type = __( 'PHP Parse', 'debug-log-manager' );
508 $error_details = str_replace( "PHP Parse error: ", "", $error );
509 $error_details = str_replace( "E_PARSE: ", "", $error_details );
510 } elseif ( false !== strpos( $error, 'EXCEPTION:' ) ) {
511 $error_type = __( 'PHP Exception', 'debug-log-manager' );
512 $error_details = str_replace( "EXCEPTION: ", "", $error );
513 } elseif ( false !== strpos( $error, 'WordPress database error' ) ) {
514 $error_type = __( 'Database', 'debug-log-manager' );
515 $error_details = str_replace( "WordPress database error ", "", $error );
516 } elseif ( false !== strpos( $error, 'JavaScript Error' ) ) {
517 $error_type = __( 'JavaScript', 'debug-log-manager' );
518 $error_details = str_replace( "JavaScript Error: ", "", $error );
519 } else {
520 $error_type = __( 'Other', 'debug-log-manager' );
521 $error_details = $error;
522 if ( $this->is_json( $error_details ) ) {
523 // For JSON string in error message, originally added via error_log( json_encode( $variable ) )
524 // This will output said JSON string as well-formated array in the log entries table
525 $error_details = '<pre>' . print_r( json_decode( $error_details, true ), true ) . '</pre>';
526 }
527 }
528
529 // 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.
530
531 if ( ! empty( $error_source ) ) {
532 if ( 'WordPress core' == $error_source ) {
533 $wp_version = get_bloginfo( 'version' );
534 $file_viewer_url = 'https://github.com/WordPress/wordpress-develop/blob/' . $wp_version . '/src' . $error_file_path;
535 $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;
536 } elseif ( 'Theme' == $error_source ) {
537 if ( ! defined( 'DISALLOW_FILE_EDIT' ) || ( false === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
538 $file_viewer_url = get_admin_url() . 'theme-editor.php?file=' . urlencode( str_replace( '/wp-content/themes/', '', $error_file_path ) ) . '&theme=' . $error_source_theme_dir;
539 $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;
540 }
541 if ( defined( 'DISALLOW_FILE_EDIT' ) && ( true === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
542 $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;
543 }
544 } elseif ( 'Plugin' == $error_source ) {
545 if ( ! defined( 'DISALLOW_FILE_EDIT' ) || ( false === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
546 $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 );
547 $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;
548 }
549 if ( defined( 'DISALLOW_FILE_EDIT' ) && ( true === constant( 'DISALLOW_FILE_EDIT' ) ) ) {
550 $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;
551 }
552 }
553 }
554
555 // https://www.php.net/manual/en/function.array-search.php#120784
556 if ( array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ) === false ) {
557
558 $errors_master_list[] = array(
559 'type' => $error_type,
560 'details' => trim( $error_details ),
561 'occurrences' => array( $timestamp ),
562 );
563
564 } else {
565
566 $error_position = array_search( trim( $error_details ), array_column( $errors_master_list, 'details' ) ); // integer
567
568 array_push( $errors_master_list[$error_position]['occurrences'], $timestamp );
569
570 }
571
572 }
573
574 return json_encode( $errors_master_list );
575
576 }
577
578 /**
579 * Get the latest entries for auto-refresh feature
580 *
581 * @since 1.0.0
582 */
583 public function get_latest_entries() {
584
585 $errors_master_list = json_decode( $this->get_processed_entries(), true );
586
587 $n = 1;
588 $entries = array();
589
590 foreach ( $errors_master_list as $error ) {
591
592 if ( function_exists( 'wp_date' ) ) {
593 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
594 } else {
595 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
596 }
597
598 $occurrence_count = count( $error['occurrences'] );
599
600 $entry = array(
601 $n,
602 $error['type'],
603 $error['details'],
604 $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>',
605 );
606
607 $entries[] = $entry;
608
609 $n++;
610
611 }
612
613 $data = array(
614 'entries' => $entries,
615 );
616
617 echo json_encode( $data );
618
619 }
620
621 /**
622 * Get debug log in data table format
623 *
624 * @since 1.0.0
625 */
626 public function get_entries_datatable() {
627
628 ?>
629 <div>
630 <select id="errorTypeFilter" class="dlm-error-type-filter">
631 <option value=""><?php esc_html_e( 'All Error Types', 'debug-log-manager' ); ?></option>
632 <option value="<?php esc_attr_e( 'PHP Fatal', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Fatal', 'debug-log-manager' ); ?></option>
633 <option value="<?php esc_attr_e( 'PHP Warning', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Warning', 'debug-log-manager' ); ?></option>
634 <option value="<?php esc_attr_e( 'PHP Notice', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Notice', 'debug-log-manager' ); ?></option>
635 <option value="<?php esc_attr_e( 'PHP Deprecated', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Deprecated', 'debug-log-manager' ); ?></option>
636 <option value="<?php esc_attr_e( 'PHP Parse', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Parse', 'debug-log-manager' ); ?></option>
637 <option value="<?php esc_attr_e( 'PHP Exception', 'debug-log-manager' ); ?>"><?php esc_html_e( 'PHP Exception', 'debug-log-manager' ); ?></option>
638 <option value="<?php esc_attr_e( 'Database', 'debug-log-manager' ); ?>"><?php esc_html_e( 'Database', 'debug-log-manager' ); ?></option>
639 <option value="<?php esc_attr_e( 'JavaScript', 'debug-log-manager' ); ?>"><?php esc_html_e( 'JavaScript', 'debug-log-manager' ); ?></option>
640 <option value="<?php esc_attr_e( 'Other', 'debug-log-manager' ); ?>"><?php esc_html_e( 'Other', 'debug-log-manager' ); ?></option>
641 </select>
642 </div>
643 <table id="debug-log" class="wp-list-table widefat striped">
644 <thead>
645 <tr>
646 <th class="dlm-entry-no">#</th>
647 <th class="dlm-entry-type"><?php esc_html_e( 'Error Type', 'debug-log-manager' ); ?></th>
648 <th class="dlm-entry-details"><?php esc_html_e( 'Details', 'debug-log-manager' ); ?></th>
649 <th class="dlm-entry-datetime"><?php esc_html_e( 'Last Occurrence', 'debug-log-manager' ); ?></th>
650 </tr>
651 </thead>
652 <tbody>
653 <?php
654
655 $errors_master_list = json_decode( $this->get_processed_entries(), true );
656
657 $n = 1;
658
659 foreach ( $errors_master_list as $error ) {
660
661 if ( function_exists( 'wp_date' ) ) {
662 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
663 } else {
664 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
665 }
666
667 $occurrence_count = count( $error['occurrences'] );
668 ?>
669
670 <tr>
671 <td class="dlm-entry-no"><?php echo esc_html( $n ); ?></td>
672 <td class="dlm-entry-type"><?php echo esc_html( $error['type'] ); ?></td>
673 <td class="dlm-entry-details"><?php echo wp_kses( $error['details'], 'post' ); ?></td>
674 <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>
675 </tr>
676
677 <?php
678 $n++;
679
680 }
681
682 ?>
683 </tbody>
684 </table>
685 <?php
686
687 }
688
689 /**
690 * Get entries for dashboard widget
691 *
692 * @since 1.8.0
693 */
694 public function get_dashboard_widget_entries() {
695
696 $errors_master_list = json_decode( $this->get_processed_entries(), true );
697
698 $n = 1;
699 $entries_to_show = 5;
700
701 ?>
702 <style>
703
704 #debug_log_manager_widget.postbox .inside {
705 margin: 0;
706 padding: 0;
707 }
708
709 .dlm-dashboard-widget-entry {
710 padding: 12px;
711 border-bottom: 1px solid #e6e7e7;
712 word-wrap: break-word; /* All browsers since IE 5.5+ */
713 overflow-wrap: break-word; /* Renamed property in CSS3 draft spec */
714 }
715
716 .dlm-dashboard-widget-entry:nth-child(odd) {
717 background-color: #f6f7f7;
718 }
719
720 .dlm-dashboard-widget-entry-message a.error-source-link {
721 color: #50575e;
722 text-decoration: none;
723 }
724
725 .dlm-dashboard-widget-entry-message a.error-source-link span {
726 position: relative;
727 margin-left: 2px;
728 color: #777;
729 font-size: 18px;
730 width: 18px;
731 height: 18px;
732 transition: .25s;
733 text-decoration: none;
734 }
735
736 .dlm-dashboard-widget-entry-message a.error-source-link span.offset-up {
737 top: -1px;
738 }
739
740 .dlm-dashboard-widget-entry-message a.error-source-link span.offset-down {
741 top: 1px;
742 }
743
744 .dlm-dashboard-widget-entry-message a.error-source-link:hover {
745 color: #2271b1;
746 text-decoration: underline;
747 }
748
749 #debug-log .dlm-entry-details a.error-source-link:hover span {
750 color: #2271b1;
751 text-decoration: none;
752 }
753
754 .dlm-dashboard-widget-entry-meta {
755 display: flex;
756 }
757
758 .dlm-dashboard-widget-entry-type {
759 margin-right: 4px;
760 font-weight: 600;
761 }
762
763 .dlm-dashboard-widget-footer {
764 display: flex;
765 justify-content: space-between;
766 align-items: center;
767 width: 100%;
768 box-sizing: border-box;
769 padding: 12px;
770 background-color: #f6f7f7;
771 }
772
773 </style>
774 <div class="dlm-dashboard-widget-entries">
775 <?php
776
777 foreach ( $errors_master_list as $error ) {
778
779 if ( $n <= $entries_to_show ) {
780
781 if ( function_exists( 'wp_date' ) ) {
782 $localized_timestamp = wp_date( 'M j, Y, H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
783 } else {
784 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
785 }
786
787 $occurrence_count = count( $error['occurrences'] );
788
789 ?>
790 <div class="dlm-dashboard-widget-entry">
791 <div class="dlm-dashboard-widget-entry-meta">
792 <div class="dlm-dashboard-widget-entry-type">
793 <?php echo esc_html( $error['type'] ); ?>
794 </div>
795 <div class="dlm-dashboard-widget-entry-datetime">
796 | <?php echo esc_html( $localized_timestamp ); ?>
797 </div>
798 </div>
799 <div class="dlm-dashboard-widget-entry-message">
800 <?php echo wp_kses( $error['details'], 'post' ); ?>
801 </div>
802 </div>
803 <?php
804
805 }
806
807 $n++;
808
809 }
810
811 ?>
812 </div>
813 <div class="dlm-dashboard-widget-footer">
814 <div class="dlm-dashboard-widget-logging-status">
815 <?php echo $this->get_status(); ?>
816 </div>
817 <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>
818 </div>
819 <?php
820
821 }
822
823 /**
824 * Clear log file
825 *
826 * @since 1.0.0
827 */
828 public function clear_log() {
829
830 if ( isset( $_REQUEST ) && current_user_can( 'manage_options' ) ) {
831
832 if ( wp_verify_nonce( sanitize_text_field( $_REQUEST['nonce'] ), 'dlm-app' . get_current_user_id() ) ) {
833
834 $debug_log_file_path = get_option( 'debug_log_manager_file_path' );
835
836 file_put_contents( $debug_log_file_path, '' );
837
838 echo true;
839
840 }
841
842 }
843
844 }
845
846 /**
847 * Disable WP core's plugin/theme editor
848 *
849 * @since 2.0.0
850 */
851 public function disable_wp_file_editor() {
852
853 $options = array(
854 'add' => true, // Add the config if missing.
855 'raw' => true, // Display value in raw format without quotes.
856 'normalize' => false, // Normalize config output using WP Coding Standards.
857 );
858
859 $this->wp_config->update( 'constant', 'DISALLOW_FILE_EDIT', 'true', $options );
860
861 // Prepare entries from the debug log for the data table
862
863 $errors_master_list = json_decode( $this->get_processed_entries(), true );
864
865 $n = 1;
866 $entries = array();
867
868 foreach ( $errors_master_list as $error ) {
869
870 if ( function_exists( 'wp_date' ) ) {
871 $localized_timestamp = wp_date( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) ); // last occurrence
872 } else {
873 $localized_timestamp = date_i18n( 'M j, Y - H:i:s', strtotime( $error['occurrences'][0] ) );
874 }
875
876 $occurrence_count = count( $error['occurrences'] );
877
878 $entry = array(
879 $n,
880 $error['type'],
881 $error['details'],
882 $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>',
883 );
884
885 $entries[] = $entry;
886
887 $n++;
888
889 }
890
891 // Assemble data to return
892
893 $data = array(
894 'status' => 'disabled', // Plugin/theme editor
895 'entries' => $entries, // To parse data table with unlinked plugin/theme file paths
896 );
897
898 echo json_encode( $data );
899
900 }
901
902 /**
903 * Log javascript errors
904 *
905 * @since 1.4.0
906 */
907 public function log_js_errors() {
908
909 // Since we are using XHR for the js error logging, JSON data comes in via php://input
910 $request = json_decode(urldecode(file_get_contents('php://input')), true); // an array
911
912 // Verify error content and nonce and then log the JS error
913 // Source: https://plugins.svn.wordpress.org/lh-javascript-error-log/trunk/lh-javascript-error-log.php
914 if ( isset( $request['message'] ) && isset( $request['script'] ) && isset( $request['lineNo'] ) && isset( $request['columnNo'] ) && ! empty( $request['nonce'] ) && wp_verify_nonce( $request['nonce'], DLM_SLUG ) ) {
915
916 error_log( 'JavaScript Error: ' . $request['message'] . ' in ' . $request['script'] . ' on line ' . $request['lineNo'] . ' column ' . $request['columnNo'] . ' at ' . get_site_url() . $request['pageUrl'] );
917
918 } else {
919
920 wp_die();
921
922 }
923
924 }
925
926 /**
927 * Check if a string is valid JSON
928 *
929 * @link https://stackoverflow.com/a/6041773
930 * @since 2.1.0
931 */
932 public function is_json( $string ) {
933
934 json_decode( $string );
935 return json_last_error() === JSON_ERROR_NONE;
936
937 }
938
939 }