PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.6.10
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.6.10
1.9.5 1.9.4 1.9.3 trunk 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6 1.6.1 1.6.10 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 All 40 releases
code-profiler / lib / helper.php

helper.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy 1.6.10, at lib/helper.php

670 lines 17.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 +=====================================================================+
4 | ____ _ ____ __ _ _ |
5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
7 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
8 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
9 | |
10 | (c) Jerome Bruandet ~ https://code-profiler.com/ |
11 +=====================================================================+
12 */
13
14 if (! defined('ABSPATH') ) {
15 die('Forbidden');
16 }
17
18 // =====================================================================
19 // Various constants
20
21 // The profiles directory can be defined in the wp-config.php script
22 if (! defined('CODE_PROFILER_UPLOAD_DIR') ) {
23 $upload_dir = wp_upload_dir();
24 define('CODE_PROFILER_UPLOAD_DIR', $upload_dir['basedir'] .'/code-profiler');
25 }
26 define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php');
27 define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp');
28 define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp');
29 define('CODE_PROFILER_TMP_CALLS_LOG', 'calls.tmp');
30 define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp');
31 define('CODE_PROFILER_TMP_CONNECTIONS_LOG', 'connections.tmp');
32 define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>');
33 define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>');
34 global $wp_version;
35 if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
36 define ('CODE_PROFILER_UA', [
37 esc_html__('Desktop', 'code-profiler') => [
38 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0',
39 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,'.
40 ' like Gecko) Chrome/125.0.0.0 Safari/537.36',
41 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
42 ' like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.2535.67'
43 ],
44 esc_html__('Mobile', 'code-profiler') => [
45 'Android Phone' => 'Mozilla/5.0 (Android 14; Mobile; rv:68.0) Gecko/68.0 Firefox/126.0',
46 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 14.0; SAMSUNG-SM-T377A Build/NMF26X)'.
47 ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36',
48 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15'.
49 ' (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1',
50 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15'.
51 ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/605.1.15'
52 ],
53 esc_html__('Bot', 'code-profiler') => [
54 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
55 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
56 '; https://code-profiler.com/)'
57 ]
58
59 ] );
60 }
61
62 if (! defined('CODE_PROFILER_MUPLUGIN') ) {
63 // MU plugin's name can be defined in the wp-config.php
64 define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
65 }
66 define('CODE_PROFILER_ACCURACY', [
67 1 => __('Highest', 'code-profiler'),
68 5 => __('High', 'code-profiler'),
69 10 => __('Moderate', 'code-profiler'),
70 15 => __('Low', 'code-profiler'),
71 20 => __('Lowest', 'code-profiler')
72 ]);
73 // =====================================================================
74 // Prevent cURL timeout if a plugin changes its timeout options.
75
76 function code_profiler_curl_timeout( $handle, $parsed_args, $url ) {
77
78 if ( isset( $_REQUEST['action'] ) &&
79 $_REQUEST['action'] == 'codeprofiler_start_profiler') {
80
81 curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 300 );
82 curl_setopt( $handle, CURLOPT_TIMEOUT, 300 );
83 }
84 }
85 add_action('http_api_curl', 'code_profiler_curl_timeout', 1000, 3 );
86
87 // =====================================================================
88 // Update version in the DB and check if options must be updated.
89
90 function code_profiler_init_update() {
91
92 $cp_options = get_option('code-profiler');
93 if ( empty( $cp_options['version'] ) ||
94 version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
95
96 $cp_options['version'] = CODE_PROFILER_VERSION;
97
98 // Version 1.1
99 if (! isset( $cp_options['enable_wpcli'] ) ) {
100 $cp_options['enable_wpcli'] = 1;
101 }
102
103 // Version 1.2
104 if (! isset( $cp_options['disable_wpcron'] ) ) {
105 $cp_options['disable_wpcron'] = 1;
106 }
107
108 // Version 1.3.1
109 if (! isset( $cp_options['http_response'] ) ) {
110 $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
111 }
112
113 // Version 1.5
114 if (! isset( $cp_options['accuracy'] ) ) {
115 $cp_options['accuracy'] = 1;
116 }
117
118 // Update version in the DB
119 update_option('code-profiler', $cp_options );
120 }
121
122 }
123 add_action('admin_init', 'code_profiler_init_update');
124
125 // =====================================================================
126 // Verify or create our storage folder in the uploads directory.
127 // Call during activation and access to the plugin.
128
129 function code_profiler_check_uploadsdir() {
130
131 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
132 mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
133 }
134 if (! is_writable( CODE_PROFILER_UPLOAD_DIR ) ) {
135 // PHP running as an Apache module?
136 chmod( CODE_PROFILER_UPLOAD_DIR, 0777 );
137 }
138 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
139 touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
140 }
141 // For Apache & Litespeed
142 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess') ) {
143 file_put_contents(
144 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
145 'Require all denied'
146 );
147 }
148 // Make sure there's a MU plugin directory
149 if (! is_dir( WPMU_PLUGIN_DIR ) ) {
150 wp_mkdir_p( WPMU_PLUGIN_DIR );
151 }
152
153
154 if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
155
156 if (! is_writable( WPMU_PLUGIN_DIR ) ) {
157 wp_die(
158 sprintf(
159 __('Error: The MU folder is read only, please make it writable: %s', 'code-profiler'),
160 esc_html( WPMU_PLUGIN_DIR ) .'/'
161 )
162 );
163 }
164
165 if (! file_exists( __DIR__ .'/mu.plugin') ) {
166 code_profiler_log_error( sprintf(
167 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
168 __DIR__ .'/mu.plugin'
169 ));
170 } else {
171 $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
172 if ( $res === false ) {
173 code_profiler_log_error( sprintf(
174 esc_html__('Cannot copy the MU plugin to %s', 'code-profiler'),
175 WPMU_PLUGIN_DIR
176 ));
177 }
178 }
179 } else {
180 // Update MU plugin it if needed
181 if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin') ) {
182 copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
183 }
184 }
185
186 // Log file
187 if (! file_exists( CODE_PROFILER_LOG ) ) {
188 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
189 }
190
191 }
192
193 // =====================================================================
194 // Check the PHP memory limit and return a suggested size
195 // for the profiler's buffer.
196
197 function code_profiler_suggested_memory() {
198
199 $memory_limit = ini_get('memory_limit');
200
201 if ('-1' == $memory_limit ) {
202 // Return max size
203 return 10;
204 }
205
206 if ( preg_match('/^(\d+)([PTGMK])$/i', $memory_limit, $match ) ) {
207 $bytes = (int) $match[1];
208 switch ( strtoupper( $match[2] ) ) {
209 case 'P':
210 $bytes *= 1024;
211 case 'T':
212 $bytes *= 1024;
213 case 'G':
214 $bytes *= 1024;
215 case 'M':
216 $bytes *= 1024;
217 case 'K':
218 $bytes *= 1024;
219 }
220 // 256 MB
221 if ( $bytes >= 268435456 ) {
222 return 10;
223 // 128 MB
224 } elseif ( $bytes >= 134217728 ) {
225 return 7;
226 // 64 MB
227 } elseif ( $bytes >= 67108864 ) {
228 return 4;
229 // <64 MB
230 } else {
231 return 1;
232 }
233 }
234 // Don't know :/
235 return 5;
236 }
237
238 // =====================================================================
239 // Create the default options.
240
241 function code_profiler_default_options() {
242
243 $buffer = code_profiler_suggested_memory();
244
245 $cp_options = [
246 'show_paths' => 'relative',
247 'display_name' => 'full',
248 'truncate_name' => 30,
249 'chart_type' => 'x',
250 'chart_max_plugins' => 25,
251 'hide_empty_value' => 1,
252 'table_max_rows' => 30,
253 'warn_composer' => 1,
254 'enable_wpcli' => 1,
255 'disable_wpcron' => 1,
256 'http_response' => '^(?:3|4|5)\d{2}$',
257 'accuracy' => 1,
258 'buffer' => (int) $buffer
259 ];
260
261 update_option('code-profiler', $cp_options );
262
263 }
264
265 // ===================================================================== 2023-06-14
266 // Create a profile name.
267
268 function code_profiler_profile_name() {
269
270 return date('Y-m-d_') . substr( time(), 4 );
271 }
272
273 // ===================================================================== 2023-06-14
274 // Disable PHP display_errors so that notice, warning and error messages
275 // don't show up in the AJAX response.
276
277 function code_profiler_hide_errors() {
278
279 ini_set('display_errors', 0 );
280 }
281
282 // =====================================================================
283 // Disable opcode cache.
284
285 function code_profiler_disable_opcode() {
286
287 try {
288 if ( extension_loaded('Zend OPcache') ) {
289 ini_set('opcache.enable', 0);
290 } elseif ( extension_loaded('wincache') ) {
291 ini_set('wincache.fcenabled', 0);
292 }
293 set_time_limit(0);
294 ini_set('memory_limit', -1);
295
296 } catch ( Exception $e ) { }
297
298 $cp_options = get_option('code-profiler');
299 if (! empty( $cp_options['disable_wpcron'] ) ) {
300 if (! defined('DISABLE_WP_CRON') ) {
301 define('DISABLE_WP_CRON', true );
302 }
303 }
304
305 }
306 // =====================================================================
307 // Verify the security key when running the profile.
308
309 function code_profiler_verify_key() {
310
311 $response = [
312 'status' => 'error',
313 'message' => __('Security keys do not match. Reload the page and try again (%s)', 'code-profiler')
314 ];
315
316 $cp_options = get_option('code-profiler');
317
318 if ( empty( $cp_options['hash'] ) ) {
319 $response['message'] = sprintf( $response['message'], '#1');
320
321 } else {
322 if ( empty( $_REQUEST['profiler_key'] ) ) {
323 $response['message'] = sprintf( $response['message'], '#2');
324
325 } else {
326 if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
327 return;
328 } else {
329 $response['message'] = sprintf( $response['message'], '#3');
330 }
331 }
332 }
333 wp_send_json( $response );
334
335 }
336
337 // =====================================================================
338 // Write message to the log.
339 // Log level can be a combination of INFO (1), WARN (2), ERROR (4)
340 // and DEBUG (8).
341
342 function code_profiler_write2log( $message, $level, $create ) {
343
344 if ( empty( $create ) ) {
345 file_put_contents(
346 CODE_PROFILER_LOG,
347 time() ."~~$level~~$message\n",
348 FILE_APPEND
349 );
350 } else {
351 file_put_contents(
352 CODE_PROFILER_LOG,
353 time() ."~~$level~~$message\n"
354 );
355 }
356 }
357
358 function code_profiler_log_info( $string, $create = 0 ) {
359 code_profiler_write2log( $string, 1, $create );
360 }
361 function code_profiler_log_warn( $string, $create = 0 ) {
362 code_profiler_write2log( $string, 2, $create );
363 }
364 function code_profiler_log_error( $string, $create = 0 ) {
365 code_profiler_write2log( $string, 4, $create );
366 }
367 function code_profiler_log_debug( $string, $create = 0 ) {
368 code_profiler_write2log( $string, 8, $create );
369 }
370
371 // =====================================================================
372 // Retrieve and return matching files from a directory.
373
374 function code_profiler_glob( $directory, $regex, $pathname = false ) {
375
376 $list = [];
377
378 foreach ( new DirectoryIterator( $directory ) as $finfo ) {
379 if (! $finfo->isDot() && preg_match("`$regex`", $finfo->getFilename() ) ) {
380 if ( $pathname ) {
381 $list[] = $finfo->getPathname();
382 } else {
383 $list[] = $finfo->getFilename();
384 }
385 }
386 }
387 return $list;
388 }
389
390 // =====================================================================
391 // Verify if a profile exists and return its full path.
392
393 function code_profiler_get_profile_path( $id, $type = 'slugs') {
394
395 if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
396
397 $glob = code_profiler_glob(CODE_PROFILER_UPLOAD_DIR, "$id\..+\.$type\.profile$", true);
398
399 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
400 if ( preg_match( "`$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
401
402 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
403 }
404 }
405 }
406 return false;
407 }
408
409 // =====================================================================
410 // Open, parse and return the content of a profile file.
411
412 function code_profiler_get_profile_data( $file, $type = 'slugs') {
413
414 $buffer = [];
415
416 $fh = fopen( "$file.$type.profile", 'rb');
417
418 if ( $fh === false ) {
419 $err = sprintf(
420 CODE_PROFILER_ERROR_NOTICE,
421 sprintf(
422 esc_html__('Unable to open profile file: %s.', 'code-profiler'),
423 esc_html( "$file.$type.profile" )
424 )
425 );
426 $buffer['error'] = $err;
427 return $buffer;
428 }
429
430 while (! feof( $fh ) ) {
431 $buffer[] = fgetcsv( $fh, 1000, "\t" );
432 }
433
434 fclose( $fh );
435
436 if ( empty( $buffer ) ) {
437 $err = sprintf(
438 CODE_PROFILER_ERROR_NOTICE,
439 sprintf(
440 esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler'),
441 esc_html( "$file.$type.profile" )
442 )
443 );
444 $buffer['error'] = $err;
445 return $buffer;
446 }
447
448 // Get rid of the empty field created by fgetcsv
449 return array_filter( $buffer );
450 }
451
452 // =====================================================================
453 // Exit with a json-encoded error message except if we're running
454 // from WP CLI
455
456 function code_profiler_wp_send_json( $response ) {
457
458 if ( defined('WP_CLI') && WP_CLI ) {
459 WP_CLI::error( $response['message'] );
460 }
461 wp_send_json( $response );
462 }
463
464 // =====================================================================
465 // Retrieve the summary stats for a given profile.
466
467 function code_profiler_getsummarystats( $profile_path, $type = 'html') {
468
469 if ( $type == 'html') {
470 $open = '<ul><li>&#10148; ';
471 $div = '</li><li>&#10148; ';
472 $close = '</li></ul>';
473 } else {
474 // WP-CLI
475 $open = " \u{27A4} ";
476 $div = "\n \u{27A4} ";
477 $close = "\n\n";
478 }
479
480 if (! file_exists( "$profile_path.summary.profile" ) ) {
481 return false;
482 }
483 $decode = json_decode( file_get_contents( "$profile_path.summary.profile" ) );
484
485 $string = $open;
486
487 if ( empty( $decode->items ) ) {
488 $tmp = __('N/A', 'code-profiler');
489 } else {
490 $tmp = (int) --$decode->items;
491 }
492 $string .= sprintf(
493 __('%s plugins and 1 theme', 'code-profiler'),
494 $tmp
495 );
496
497 $string .= $div;
498
499 if ( empty( $decode->time ) ) {
500 $tmp = __('N/A', 'code-profiler');
501 } else {
502 $tmp = number_format( $decode->time, 4 );
503 }
504 $string .= sprintf(
505 __('Execution time: %ss', 'code-profiler'),
506 $tmp
507 );
508
509 $string .= $div;
510
511 if ( empty( $decode->memory ) ) {
512 $tmp = __('N/A', 'code-profiler');
513 } else {
514 $tmp = number_format( $decode->memory, 2);
515 }
516 $string .= sprintf(
517 __('Peak memory: %s MB', 'code-profiler'),
518 $tmp
519 );
520
521 $string .= $div;
522
523 if ( empty( $decode->io ) ) {
524 $tmp = __('N/A', 'code-profiler');
525 } else {
526 $tmp = number_format( (int) $decode->io );
527 }
528 $string .= sprintf(
529 __('File I/O operations: %s', 'code-profiler'),
530 $tmp
531 );
532
533 $string .= $div;
534
535 if ( empty( $decode->queries ) ) {
536 $tmp = __('N/A', 'code-profiler');
537 } else {
538 $tmp = number_format( (int) $decode->queries );
539 }
540 $string .= sprintf(
541 __('SQL queries: %s', 'code-profiler'),
542 $tmp
543 );
544
545 $string .= $div;
546
547 if ( empty( CODE_PROFILER_ACCURACY[ $decode->precision ] ) ) {
548 $tmp = __('N/A', 'code-profiler');
549 } else {
550 $tmp = CODE_PROFILER_ACCURACY[ $decode->precision ];
551 }
552 $string .= sprintf(
553 __('Accuracy: %s', 'code-profiler'),
554 $tmp
555 );
556
557 $string .= $close;
558
559 return $string;
560 }
561
562 // =====================================================================
563 // Remove *tmp files left after an HTTP error (4xx or 5xx).
564
565 function code_profiler_cleantmpfiles() {
566
567 $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.tmp$', true );
568
569 if ( is_array( $glob ) ) {
570 $count = 0;
571 foreach( $glob as $file ) {
572 $count++;
573 unlink( $file );
574 }
575 if ( $count ) {
576 code_profiler_log_info( sprintf(
577 __('Deleting %s temporary files found in the profiles folder', 'code-profiler'),
578 (int) $count
579 ) );
580 }
581 }
582 }
583
584 // =====================================================================
585 // Remove non-ASCII characters from a string.
586
587 function code_profiler_ASCII_filter( $string ) {
588
589 return preg_replace('/[\x00-\x1f\x7f-\xff]/', '', $string );
590 }
591
592 // =====================================================================
593 // Clear the log if it's bigger than 100KB.
594
595 function code_profiler_clearlog() {
596
597 if ( file_exists( CODE_PROFILER_LOG ) && filesize( CODE_PROFILER_LOG ) > 100000 ) {
598 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
599 }
600 }
601
602 // ===================================================================== 2023-06-15
603 // We don't want to be bothered by other themes/plugins' admin notices.
604
605 add_action('admin_head', 'code_profiler_hide_admin_notices');
606
607 function code_profiler_hide_admin_notices() {
608 if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
609 remove_all_actions('admin_notices');
610 remove_all_actions('all_admin_notices');
611 add_filter('admin_footer_text', 'code_profiler_admin_footer');
612 }
613 }
614
615 function code_profiler_admin_footer () {
616 echo '<span id="footer-thankyou">'.
617 sprintf(
618 /* Translators: %s are the '<a href="">' and '</a>' anchors */
619 esc_html(
620 'Thank you for using %sCode Profiler%s.',
621 'code-profiler'
622 ),
623 '<a href="https://code-profiler.com" target="_blank">',
624 '</a>'
625 ).
626 '</span>';
627 }
628
629 // =====================================================================
630 // Display/hide an element depending on some value
631
632 function code_profiler_hide( $var, $val ) {
633
634 if ( $var == $val ) {
635 echo " style='display:none'";
636 }
637 }
638
639 // =====================================================================
640 // Retrieve all themes.
641
642 function code_profiler_get_themes() {
643
644 $list = [];
645
646 // Make sure the function is loaded
647 if (! function_exists('wp_get_themes') ) {
648 require_once ABSPATH . 'wp-includes/theme.php';
649 }
650 $themes = wp_get_themes();
651 foreach( $themes as $k => $v ) {
652 if ( $v->Name ) {
653 $list[ $k ] = [
654 'n' => $v->Name,
655
656 't' => $v->Template
657 ];
658 } else {
659 $list[ $k ] = [
660 'n' => $k,
661 't' => $v->Template
662 ];
663 }
664 }
665 return $list;
666 }
667
668 // =====================================================================
669 // EOF
670