PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.9.6
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.9.6
1.9.6 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 All 41 releases
← All changes | lib/helper.php +559 -105 1.5.11.9.6 View file →
@@ -1,6 +1,6 @@
1 1 <?php
2 -/*
2 +/**
3 3 +=====================================================================+
4 4 | ____ _ ____ __ _ _ |
5 5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
@@ -6,96 +6,246 @@
6 6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
7 7 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
8 8 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
9 9 | |
10 - | (c) Jerome Bruandet ~ https://code-profiler.com/ |
10 + | (c) Jerome Bruandet ~ https://nintechnet.com/codeprofiler/ |
11 11 +=====================================================================+
12 12 */
13 13
14 -if (! defined('ABSPATH') ) { die('Forbidden'); }
14 +if (! defined('ABSPATH') ) {
15 + die('Forbidden');
16 +}
15 17
16 18 // =====================================================================
17 19 // Various constants
18 20
19 -$upload_dir = wp_upload_dir();
20 -define('CODE_PROFILER_UPLOAD_DIR', $upload_dir['basedir'] .'/code-profiler');
21 +// The profiles directory can be defined in the wp-config.php script
22 +if (! defined('CODE_PROFILER_UPLOAD_DIR') ) {
23 + // When running Code Profiler via WP CLI on a child site of a multisite installation,
24 + // we need to get the main site upload folder otherwise wp_upload_dir will return
25 + // the child site's
26 + if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined('MULTISITE') ) ) {
27 + $upload_dir = code_profiler_upload_dir();
28 + } else {
29 + $upload_dir = wp_upload_dir();
30 + }
31 + define('CODE_PROFILER_UPLOAD_DIR', $upload_dir['basedir'] .'/code-profiler');
32 +}
21 33 define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php');
22 34 define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp');
23 35 define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp');
24 -define('CODE_PROFILER_TMP_TICKS_LOG', 'ticks.tmp');
36 +define('CODE_PROFILER_TMP_RERUN_LOG', 'rerun.tmp');
37 +define('CODE_PROFILER_TMP_CALLS_LOG', 'calls.tmp');
25 38 define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp');
39 +define('CODE_PROFILER_TMP_CONNECTIONS_LOG', 'connections.tmp');
26 40 define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>');
27 41 define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>');
28 -global $wp_version;
29 -if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
30 - define ('CODE_PROFILER_UA', [
31 - esc_html__('Desktop', 'code-profiler') => [
32 - 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0',
33 - 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)'.
34 - ' Chrome/105.0.0.0 Safari/537.36',
35 - 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
36 - ' like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27'
37 - ],
38 - esc_html__('Mobile', 'code-profiler') => [
39 - 'Android Phone' => 'Mozilla/5.0 (Android 12; Mobile; rv:68.0) Gecko/68.0 Firefox/104.0',
40 - 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 12.0; SAMSUNG-SM-T377A Build/NMF26X)'.
41 - ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36',
42 - 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15'.
43 - ' (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
44 - 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 15_5 like Mac OS X) AppleWebKit/605.1.15'.
45 - ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/604.1'
46 - ],
47 - esc_html__('Bot', 'code-profiler') => [
48 - 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
49 - 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
50 - '; https://code-profiler.com/)'
51 - ]
42 +define('CODE_PROFILER_WARNING_NOTICE', '<div class="notice notice-warning"><p>%s</p></div>');
43 +if (! defined('CODE_PROFILER_MUPLUGIN') ) {
44 + // MU plugin's name can be defined in the wp-config.php
45 + define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
46 +}
52 47
53 - ] );
48 +/**
49 + * Since WP 6.7, translation loading must not be triggered too early.
50 + */
51 +add_action('init', 'code_profiler_i18n_constants');
52 +function code_profiler_i18n_constants() {
53 +
54 + global $wp_version;
55 + if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
56 + define ('CODE_PROFILER_UA', [
57 + esc_html__('Desktop', 'code-profiler') => [
58 + 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0',
59 + 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,'.
60 + ' like Gecko) Chrome/147.0.0.0 Safari/537.36',
61 + 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
62 + ' like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/Chrome/147.0.7727.50'
63 + ],
64 + esc_html__('Mobile', 'code-profiler') => [
65 + 'Android Phone' => 'Mozilla/5.0 (Android 16; Mobile; rv:68.0) Gecko/68.0 Firefox/149.0',
66 + 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 16.0; SAMSUNG-SM-T377A Build/NMF26X)'.
67 + ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.7727.50 Mobile Safari/537.36',
68 + 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7_7 like Mac OS X) AppleWebKit/605.1.15'.
69 + ' (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1',
70 + 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 18_7_7 like Mac OS X) AppleWebKit/605.1.15'.
71 + ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/605.1.15'
72 + ],
73 + esc_html__('Bot', 'code-profiler') => [
74 + 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
75 + 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
76 + '; https://nintechnet.com/codeprofiler/)'
77 + ]
78 +
79 + ] );
80 + }
81 + define('CODE_PROFILER_ACCURACY', [
82 + 1 => __('Highest', 'code-profiler'),
83 + 5 => __('High', 'code-profiler'),
84 + 10 => __('Moderate', 'code-profiler'),
85 + 15 => __('Low', 'code-profiler'),
86 + 20 => __('Lowest', 'code-profiler')
87 + ]);
54 88 }
55 89
56 -if (! defined('CODE_PROFILER_MUPLUGIN') ) {
57 - // MU plugin's name can be defined in the wp-config.php
58 - define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
90 +// =====================================================================
91 +// Find the main site upload dir on a multisite installation.
92 +// Used when running the profiler via WP CLI.
93 +// This code is based on the WP private _wp_upload_dir function.
94 +
95 +function code_profiler_upload_dir() {
96 +
97 + $upload_path = trim( get_option('upload_path') );
98 + if ( empty( $upload_path ) || 'wp-content/uploads' === $upload_path ) {
99 + $basedir = WP_CONTENT_DIR . '/uploads';
100 + } elseif ( strpos( $upload_path, ABSPATH ) !== 0 ) {
101 + // $basedir is absolute, $upload_path is (maybe) relative to ABSPATH.
102 + $basedir = path_join( ABSPATH, $upload_path );
103 + } else {
104 + $basedir = $upload_path;
105 + }
106 +
107 + if ( defined('UPLOADS') && ! ( is_multisite() && get_site_option('ms_files_rewriting') ) ) {
108 + $basedir = ABSPATH . UPLOADS;
109 + }
110 +
111 + return ['basedir' => $basedir];
59 112 }
60 -define('CODE_PROFILER_ACCURACY', [
61 - 1 => __('Highest (default)', 'code-profiler'),
62 - 5 => __('High', 'code-profiler'),
63 - 10 => __('Moderate', 'code-profiler'),
64 - 15 => __('Low', 'code-profiler'),
65 - 20 => __('Lowest', 'code-profiler')
66 -]);
113 +
67 114 // =====================================================================
115 +// Prevent cURL timeout if a plugin changes its timeout options.
116 +
117 +function code_profiler_curl_timeout( $handle, $parsed_args, $url ) {
118 +
119 + if ( isset( $_REQUEST['action'] ) &&
120 + $_REQUEST['action'] == 'codeprofiler_start_profiler') {
121 +
122 + curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, 300 );
123 + curl_setopt( $handle, CURLOPT_TIMEOUT, 300 );
124 + }
125 +}
126 +add_action('http_api_curl', 'code_profiler_curl_timeout', 1000, 3 );
127 +
128 +// =====================================================================
68 129 // Update version in the DB and check if options must be updated.
69 130
70 131 function code_profiler_init_update() {
71 132
72 - $cp_options = get_option('code-profiler');
73 - if ( empty( $cp_options['version'] ) ||
74 - version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
133 + if ( ( $cp_options = get_option('code-profiler') ) == false ) {
134 + /**
135 + * "Automatic conversion of false to array is deprecated" since PHP 8.1
136 + */
137 + $cp_options = [];
138 + }
75 139
140 + if ( empty( $cp_options['version'] ) ) {
76 141 $cp_options['version'] = CODE_PROFILER_VERSION;
142 + update_option('code-profiler', $cp_options );
77 143
144 + } elseif ( version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
145 +
78 146 // Version 1.1
79 - if (! isset( $cp_options['enable_wpcli'] ) ) {
80 - $cp_options['enable_wpcli'] = 1;
147 + if ( version_compare( $cp_options['version'], '1.1', '<' ) ) {
148 + if (! isset( $cp_options['enable_wpcli'] ) ) {
149 + $cp_options['enable_wpcli'] = 1;
150 + }
81 151 }
82 152
83 153 // Version 1.2
84 - if (! isset( $cp_options['disable_wpcron'] ) ) {
85 - $cp_options['disable_wpcron'] = 1;
154 + if ( version_compare( $cp_options['version'], '1.2', '<' ) ) {
155 + if (! isset( $cp_options['disable_wpcron'] ) ) {
156 + $cp_options['disable_wpcron'] = 1;
157 + }
86 158 }
87 159
88 160 // Version 1.3.1
89 - if (! isset( $cp_options['http_response'] ) ) {
90 - $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
161 + if ( version_compare( $cp_options['version'], '1.3.1', '<' ) ) {
162 + if (! isset( $cp_options['http_response'] ) ) {
163 + $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
164 + }
91 165 }
92 166
93 167 // Version 1.5
94 - if (! isset( $cp_options['accuracy'] ) ) {
95 - $cp_options['accuracy'] = 1;
168 + if ( version_compare( $cp_options['version'], '1.5', '<' ) ) {
169 + if (! isset( $cp_options['accuracy'] ) ) {
170 + $cp_options['accuracy'] = 1;
171 + }
96 172 }
97 173
174 + // Version 1.7.5
175 + if ( version_compare( $cp_options['version'], '1.7.5', '<' ) ) {
176 + if (! isset( $cp_options['php_error'] ) ) {
177 + $cp_options['php_error'] = 1;
178 + }
179 + }
180 +
181 + // Version 1.8
182 + if ( version_compare( $cp_options['version'], '1.8', '<' ) ) {
183 + if ( isset( $cp_options['mem_where'] ) ) {
184 + $cp_options['mem']['x_end'] = $cp_options['mem_where'];
185 + unset( $cp_options['mem_where'] );
186 + }
187 + if ( isset( $cp_options['mem_post'] ) ) {
188 + $cp_options['mem']['post'] = $cp_options['mem_post'];
189 + unset( $cp_options['mem_post'] );
190 + }
191 + if ( isset( $cp_options['mem_user'] ) ) {
192 + $cp_options['mem']['x_auth'] = $cp_options['mem_user'];
193 + unset( $cp_options['mem_user'] );
194 + }
195 + if ( isset( $cp_options['mem_username'] ) ) {
196 + $cp_options['mem']['username'] = $cp_options['mem_username'];
197 + unset( $cp_options['mem_username'] );
198 + }
199 + if ( isset( $cp_options['mem_method'] ) ) {
200 + $cp_options['mem']['method'] = $cp_options['mem_method'];
201 + unset( $cp_options['mem_method'] );
202 + }
203 + if ( isset( $cp_options['mem_theme'] ) ) {
204 + $cp_options['mem']['theme'] = $cp_options['mem_theme'];
205 + unset( $cp_options['mem_theme'] );
206 + }
207 + if ( isset( $cp_options['ua'] ) ) {
208 + $cp_options['mem']['user_agent'] = $cp_options['ua'];
209 + unset( $cp_options['ua'] );
210 + }
211 + if ( isset( $cp_options['cookies'] ) ) {
212 + $cp_options['mem']['cookies'] = $cp_options['cookies'];
213 + unset( $cp_options['cookies'] );
214 + }
215 + if ( isset( $cp_options['mem_content_type'] ) ) {
216 + $cp_options['mem']['content_type'] = $cp_options['mem_content_type'];
217 + unset( $cp_options['mem_content_type'] );
218 + }
219 + if ( isset( $cp_options['payload'] ) ) {
220 + $cp_options['mem']['payload'] = $cp_options['payload'];
221 + unset( $cp_options['payload'] );
222 + }
223 + if ( isset( $cp_options['custom_headers'] ) ) {
224 + $cp_options['mem']['custom_headers'] = $cp_options['custom_headers'];
225 + unset( $cp_options['custom_headers'] );
226 + }
227 + if ( isset( $cp_options['exclusions'] ) ) {
228 + $cp_options['mem']['exclusions'] = $cp_options['exclusions'];
229 + unset( $cp_options['exclusions'] );
230 + }
231 + }
232 +
233 + // Version 1.8.1
234 + if ( version_compare( $cp_options['version'], '1.8.1', '<' ) ) {
235 + CodeProfiler_WPCron::install();
236 + }
237 +
238 + /**
239 + * Version 1.9.2
240 + */
241 + if ( version_compare( $cp_options['version'], '1.9.2', '<' ) ) {
242 + unset( $cp_options['warn_composer'] );
243 + }
244 +
245 + // Adjust current version
246 + $cp_options['version'] = CODE_PROFILER_VERSION;
247 +
98 248 // Update version in the DB
99 249 update_option('code-profiler', $cp_options );
100 250 }
101 251
@@ -108,10 +258,18 @@
108 258
109 259 function code_profiler_check_uploadsdir() {
110 260
111 261 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
112 - mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
262 + wp_mkdir_p( CODE_PROFILER_UPLOAD_DIR );
113 263 }
264 + if (! is_writable( CODE_PROFILER_UPLOAD_DIR ) || ! is_dir( CODE_PROFILER_UPLOAD_DIR ) ) {
265 + wp_die(
266 + sprintf(
267 + __('Error: The %s folder cannot be created or is read only', 'code-profiler'),
268 + esc_html( CODE_PROFILER_UPLOAD_DIR ) .'/'
269 + )
270 + );
271 + }
114 272 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
115 273 touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
116 274 }
117 275 // For Apache & Litespeed
@@ -128,8 +286,17 @@
128 286
129 287
130 288 if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
131 289
290 + if (! is_writable( WPMU_PLUGIN_DIR ) ) {
291 + wp_die(
292 + sprintf(
293 + __('Error: The MU folder is read only, please make it writable: %s', 'code-profiler'),
294 + esc_html( WPMU_PLUGIN_DIR ) .'/'
295 + )
296 + );
297 + }
298 +
132 299 if (! file_exists( __DIR__ .'/mu.plugin') ) {
133 300 code_profiler_log_error( sprintf(
134 301 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
135 302 __DIR__ .'/mu.plugin'
@@ -157,12 +324,59 @@
157 324
158 325 }
159 326
160 327 // =====================================================================
328 +// Check the PHP memory limit and return a suggested size
329 +// for the profiler's buffer.
330 +
331 +function code_profiler_suggested_memory() {
332 +
333 + $memory_limit = ini_get('memory_limit');
334 +
335 + if ('-1' == $memory_limit ) {
336 + // Return max size
337 + return 10;
338 + }
339 +
340 + if ( preg_match('/^(\d+)([PTGMK])$/i', $memory_limit, $match ) ) {
341 + $bytes = (int) $match[1];
342 + switch ( strtoupper( $match[2] ) ) {
343 + case 'P':
344 + $bytes *= 1024;
345 + case 'T':
346 + $bytes *= 1024;
347 + case 'G':
348 + $bytes *= 1024;
349 + case 'M':
350 + $bytes *= 1024;
351 + case 'K':
352 + $bytes *= 1024;
353 + }
354 + // 256 MB
355 + if ( $bytes >= 268435456 ) {
356 + return 10;
357 + // 128 MB
358 + } elseif ( $bytes >= 134217728 ) {
359 + return 7;
360 + // 64 MB
361 + } elseif ( $bytes >= 67108864 ) {
362 + return 4;
363 + // <64 MB
364 + } else {
365 + return 1;
366 + }
367 + }
368 + // Don't know :/
369 + return 5;
370 +}
371 +
372 +// =====================================================================
161 373 // Create the default options.
162 374
163 375 function code_profiler_default_options() {
164 376
377 + $buffer = code_profiler_suggested_memory();
378 +
165 379 $cp_options = [
166 380 'show_paths' => 'relative',
167 381 'display_name' => 'full',
168 382 'truncate_name' => 30,
@@ -169,13 +383,13 @@
169 383 'chart_type' => 'x',
170 384 'chart_max_plugins' => 25,
171 385 'hide_empty_value' => 1,
172 386 'table_max_rows' => 30,
173 - 'warn_composer' => 1,
174 387 'enable_wpcli' => 1,
175 388 'disable_wpcron' => 1,
176 389 'http_response' => '^(?:3|4|5)\d{2}$',
177 - 'accuracy' => 1
390 + 'accuracy' => 1,
391 + 'buffer' => (int) $buffer
178 392 ];
179 393
180 394 update_option('code-profiler', $cp_options );
181 395
@@ -180,15 +394,23 @@
180 394 update_option('code-profiler', $cp_options );
181 395
182 396 }
183 397
184 -// =====================================================================
398 +// ===================================================================== 2023-06-14
185 399 // Create a profile name.
186 400
187 401 function code_profiler_profile_name() {
188 402
189 403 return date('Y-m-d_') . substr( time(), 4 );
404 +}
190 405
406 +// ===================================================================== 2023-06-14
407 +// Disable PHP display_errors so that notice, warning and error messages
408 +// don't show up in the AJAX response.
409 +
410 +function code_profiler_hide_errors() {
411 +
412 + ini_set('display_errors', 0 );
191 413 }
192 414
193 415 // =====================================================================
194 416 // Disable opcode cache.
@@ -195,80 +417,141 @@
195 417
196 418 function code_profiler_disable_opcode() {
197 419
198 420 try {
199 - if (extension_loaded('Zend OPcache')) {
421 + if ( extension_loaded('Zend OPcache') ) {
200 422 ini_set('opcache.enable', 0);
201 423 } elseif ( extension_loaded('wincache') ) {
202 424 ini_set('wincache.fcenabled', 0);
203 425 }
204 426 set_time_limit(0);
427 + ini_set('memory_limit', -1);
205 428
206 429 } catch ( Exception $e ) { }
207 430
208 431 $cp_options = get_option('code-profiler');
432 + /**
433 + * Disable WP-CRON.
434 + */
209 435 if (! empty( $cp_options['disable_wpcron'] ) ) {
210 436 if (! defined('DISABLE_WP_CRON') ) {
211 437 define('DISABLE_WP_CRON', true );
212 438 }
213 439 }
440 + /**
441 + * Enable PHP error logging.
442 + */
443 + if (! empty( $cp_options['php_error'] ) ) {
444 + ini_set('log_errors', 1 );
445 + $phplog = ini_get('error_log');
446 + if ( empty( $phplog ) || $phplog === false ) {
447 + ini_set('error_log', WP_CONTENT_DIR .'/debug.log');
448 + } else {
449 + ini_set('error_log', $phplog );
450 + }
451 + }
452 +}
214 453
215 -}
216 454 // =====================================================================
217 -// Verify the security key when running the profile.
455 +// Verify the security key when running the profiler.
218 456
219 457 function code_profiler_verify_key() {
220 458
221 459 $response = [
222 460 'status' => 'error',
223 - 'message' => __('Security keys do not match. Reload the page and try again (%s)', 'code-profiler')
461 + // We cannot load translation here.
462 + 'message' => 'Security keys do not match. Reload the page and try again (%s)'
224 463 ];
225 464
226 - $cp_options = get_option('code-profiler');
465 + if ( empty( $_REQUEST['profiler_key'] ) ) {
466 + $response['message'] = sprintf( $response['message'], '001');
227 467
228 - if ( empty( $cp_options['hash'] ) ) {
229 - $response['message'] = sprintf( $response['message'], '#1');
230 -
231 468 } else {
232 - if ( empty( $_REQUEST['profiler_key'] ) ) {
233 - $response['message'] = sprintf( $response['message'], '#2');
469 + $file = CODE_PROFILER_UPLOAD_DIR .'/key_'. sha1( $_REQUEST['profiler_key'] ) .'.tmp';
470 + if ( file_exists( $file ) ) {
471 + /**
472 + * We limit the TTL to max $expire seconds to match code_profiler_cleantmpfiles().
473 + */
474 + $now = time();
475 + $expire = 20; // seconds
476 + if ( filemtime( $file ) + $expire < $now ) {
477 + $response['message'] = sprintf( $response['message'], '003');
478 + wp_send_json( $response );
479 + }
234 480
235 - } else {
236 - if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
237 - return;
238 - } else {
239 - $response['message'] = sprintf( $response['message'], '#3');
240 - }
481 + // Delete it and accept the request
482 + unlink( $file );
483 + return;
241 484 }
485 + $response['message'] = sprintf( $response['message'], '002');
242 486 }
243 487 wp_send_json( $response );
244 -
245 488 }
246 489
247 490 // =====================================================================
248 491 // Write message to the log.
249 -// Log level can be a combination of INFO (1), WARN (2) and ERROR (4).
492 +// Log level can be a combination of INFO (1), WARN (2), ERROR (4)
493 +// and DEBUG (8).
250 494
251 -function code_profiler_write2log( $message, $level = 1 ) {
495 +function code_profiler_write2log( $message, $level, $create ) {
252 496
253 - file_put_contents( CODE_PROFILER_LOG, time() ."~~$level~~$message\n", FILE_APPEND );
497 + if ( empty( $create ) ) {
498 + file_put_contents(
499 + CODE_PROFILER_LOG,
500 + time() ."~~$level~~$message\n",
501 + FILE_APPEND
502 + );
503 + } else {
504 + file_put_contents(
505 + CODE_PROFILER_LOG,
506 + time() ."~~$level~~$message\n"
507 + );
508 + }
254 509 }
255 510
256 -function code_profiler_log_info( $string ) { code_profiler_write2log( $string, 1 ); }
257 -function code_profiler_log_warn( $string ) { code_profiler_write2log( $string, 2 ); }
258 -function code_profiler_log_error( $string ) { code_profiler_write2log( $string, 4 ); }
511 +function code_profiler_log_info( $string, $create = 0 ) {
512 + code_profiler_write2log( $string, 1, $create );
513 +}
514 +function code_profiler_log_warn( $string, $create = 0 ) {
515 + code_profiler_write2log( $string, 2, $create );
516 +}
517 +function code_profiler_log_error( $string, $create = 0 ) {
518 + code_profiler_write2log( $string, 4, $create );
519 +}
520 +function code_profiler_log_debug( $string, $create = 0 ) {
521 + code_profiler_write2log( $string, 8, $create );
522 +}
259 523
260 524 // =====================================================================
525 +// Retrieve and return matching files from a directory.
526 +
527 +function code_profiler_glob( $directory, $regex, $pathname = false ) {
528 +
529 + $list = [];
530 +
531 + foreach ( new DirectoryIterator( $directory ) as $finfo ) {
532 + if (! $finfo->isDot() && preg_match("`$regex`", $finfo->getFilename() ) ) {
533 + if ( $pathname ) {
534 + $list[] = $finfo->getPathname();
535 + } else {
536 + $list[] = $finfo->getFilename();
537 + }
538 + }
539 + }
540 + return $list;
541 +}
542 +
543 +// =====================================================================
261 544 // Verify if a profile exists and return its full path.
262 545
263 546 function code_profiler_get_profile_path( $id, $type = 'slugs') {
264 547
265 - $return = false;
548 + if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
266 549
267 - if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
268 - $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$id.*.$type.profile" );
550 + $glob = code_profiler_glob(CODE_PROFILER_UPLOAD_DIR, "$id\..+\.$type\.profile$", true);
551 +
269 552 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
270 - if ( preg_match( "`/$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
553 + if ( preg_match( "`$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
271 554
272 555 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
273 556 }
274 557 }
@@ -296,10 +579,17 @@
296 579 $buffer['error'] = $err;
297 580 return $buffer;
298 581 }
299 582
583 + /**
584 + * We don't use fgetcsv() as it requires the $escape parameter since PHP 8.4
585 + */
300 586 while (! feof( $fh ) ) {
301 - $buffer[] = fgetcsv( $fh, 1000, "\t" );
587 + $tmp = trim( fgets( $fh, 1000 ) );
588 + if (! $tmp ) {
589 + continue;
590 + }
591 + $buffer[] = explode( "\t", $tmp );
302 592 }
303 593
304 594 fclose( $fh );
305 595
@@ -354,56 +644,77 @@
354 644
355 645 $string = $open;
356 646
357 647 if ( empty( $decode->items ) ) {
358 - return false;
648 + $tmp = __('N/A', 'code-profiler');
649 + } else {
650 + $tmp = (int) --$decode->items;
359 651 }
360 - $decode->items--;
361 652 $string .= sprintf(
362 653 __('%s plugins and 1 theme', 'code-profiler'),
363 - (int) $decode->items
654 + $tmp
364 655 );
365 656
366 657 $string .= $div;
367 658
368 659 if ( empty( $decode->time ) ) {
369 - return false;
660 + $tmp = __('N/A', 'code-profiler');
661 + } else {
662 + $tmp = number_format( $decode->time, 4 );
370 663 }
371 664 $string .= sprintf(
372 665 __('Execution time: %ss', 'code-profiler'),
373 - number_format( $decode->time, 4 )
666 + $tmp
374 667 );
375 668
376 669 $string .= $div;
377 670
378 671 if ( empty( $decode->memory ) ) {
379 - return false;
672 + $tmp = __('N/A', 'code-profiler');
673 + } else {
674 + $tmp = number_format( $decode->memory, 2);
380 675 }
381 676 $string .= sprintf(
382 677 __('Peak memory: %s MB', 'code-profiler'),
383 - number_format( $decode->memory, 2)
678 + $tmp
384 679 );
385 680
386 681 $string .= $div;
387 682
388 683 if ( empty( $decode->io ) ) {
389 - return false;
684 + $tmp = __('N/A', 'code-profiler');
685 + } else {
686 + $tmp = number_format( (int) $decode->io );
390 687 }
391 688 $string .= sprintf(
392 689 __('File I/O operations: %s', 'code-profiler'),
393 - number_format( $decode->io )
690 + $tmp
394 691 );
395 692
396 693 $string .= $div;
397 694
398 695 if ( empty( $decode->queries ) ) {
399 - return false;
696 + $tmp = __('N/A', 'code-profiler');
697 + } else {
698 + $tmp = number_format( (int) $decode->queries );
400 699 }
401 700 $string .= sprintf(
402 701 __('SQL queries: %s', 'code-profiler'),
403 - number_format( $decode->queries )
702 + $tmp
404 703 );
405 704
705 + $string .= $div;
706 +
707 + if ( empty( CODE_PROFILER_ACCURACY[ $decode->precision ] ) ) {
708 + $tmp = __('N/A', 'code-profiler');
709 + } else {
710 + $tmp = CODE_PROFILER_ACCURACY[ $decode->precision ];
711 + }
712 + $string .= sprintf(
713 + __('Accuracy: %s', 'code-profiler'),
714 + $tmp
715 + );
716 +
406 717 $string .= $close;
407 718
408 719 return $string;
409 720 }
@@ -408,29 +719,59 @@
408 719 return $string;
409 720 }
410 721
411 722 // =====================================================================
412 -// Remove *tmp files left after an HTTP error (4xx or 5xx).
723 +// Remove *tmp files left in the profiles folder.
413 724
414 725 function code_profiler_cleantmpfiles() {
415 726
416 - $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.tmp');
727 + $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.tmp$', true );
728 +
417 729 if ( is_array( $glob ) ) {
418 - $count = 0;
730 +
731 + $now = time();
419 732 foreach( $glob as $file ) {
420 - $count++;
733 + /**
734 + * Temporary files deletion:
735 + * - +20 seconds for temp sessions (to match the TTL checked at runtime).
736 + * - +10 minutes for unmatched temp profiles.
737 + */
738 + if ( strpos( $file, CODE_PROFILER_UPLOAD_DIR .'/key_') === 0 ) {
739 + if ( filemtime( $file ) + 20 > $now ) {
740 + continue;
741 + }
742 + } else {
743 + if ( filemtime( $file ) + 60 * 10 > $now ) {
744 + continue;
745 + }
746 + }
747 + /**
748 + * Delete all temp files.
749 + */
421 750 unlink( $file );
422 751 }
423 - if ( $count ) {
424 - code_profiler_log_info( sprintf(
425 - __('Deleting %s temporary files found in the profiles folder', 'code-profiler'),
426 - (int) $count
427 - ) );
428 - }
429 752 }
430 753 }
431 754
432 755 // =====================================================================
756 +// Remove non-ASCII characters from a string.
757 +
758 +function code_profiler_ASCII_filter( $string ) {
759 +
760 + return preg_replace('/[\x00-\x1f\x7f-\xff]/', '', $string );
761 +}
762 +
763 +// =====================================================================
764 +// Clear the log if it's bigger than 100KB.
765 +
766 +function code_profiler_clearlog() {
767 +
768 + if ( file_exists( CODE_PROFILER_LOG ) && filesize( CODE_PROFILER_LOG ) > 100000 ) {
769 + file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
770 + }
771 +}
772 +
773 +// ===================================================================== 2023-06-15
433 774 // We don't want to be bothered by other themes/plugins' admin notices.
434 775
435 776 add_action('admin_head', 'code_profiler_hide_admin_notices');
436 777
@@ -437,8 +778,121 @@
437 778 function code_profiler_hide_admin_notices() {
438 779 if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
439 780 remove_all_actions('admin_notices');
440 781 remove_all_actions('all_admin_notices');
782 + add_filter('admin_footer_text', 'code_profiler_admin_footer');
783 + }
784 +}
785 +
786 +function code_profiler_admin_footer () {
787 + echo '<span id="footer-thankyou">'.
788 + sprintf(
789 + /* Translators: %s are the '<a href="">' and '</a>' anchors */
790 + esc_html(
791 + 'Thank you for using %sCode Profiler%s.',
792 + 'code-profiler'
793 + ),
794 + '<a href="https://nintechnet.com/codeprofiler/" target="_blank">',
795 + '</a>'
796 + ).
797 + '</span>';
798 +}
799 +
800 +
801 +// =====================================================================
802 +// Hide/display an element depending on some value.
803 +
804 +function code_profiler_showhide( $var, $val ) {
805 +
806 + if ( $var == $val ) {
807 + echo " style='display:block'";
808 + } else {
809 + echo " style='display:none'";
810 + }
811 +}
812 +
813 +// =====================================================================
814 +// Retrieve all themes.
815 +
816 +function code_profiler_get_themes() {
817 +
818 + $list = [];
819 +
820 + // Make sure the function is loaded
821 + if (! function_exists('wp_get_themes') ) {
822 + require_once ABSPATH . 'wp-includes/theme.php';
823 + }
824 + $themes = wp_get_themes();
825 + foreach( $themes as $k => $v ) {
826 + if ( $v->Name ) {
827 + $list[ $k ] = [
828 + 'n' => $v->Name,
829 +
830 + 't' => $v->Template
831 + ];
832 + } else {
833 + $list[ $k ] = [
834 + 'n' => $k,
835 + 't' => $v->Template
836 + ];
837 + }
838 + }
839 + return $list;
840 +}
841 +
842 +// =====================================================================
843 +// Retrieve all available cron events.
844 +
845 +function code_profiler_get_crons() {
846 +
847 + $list = [];
848 +
849 + $wp_crons = _get_cron_array();
850 +
851 + if ( empty( $wp_crons ) ) {
852 + return $list;
853 + }
854 +
855 + foreach ( $wp_crons as $timestamp => $cronhooks ) {
856 + foreach ( $cronhooks as $hook => $keys ) {
857 + $list[] = $hook;
858 + }
859 + }
860 + sort( $list );
861 + return $list;
862 +}
863 +
864 +// =====================================================================
865 +// Create a file with the ABSPATH.
866 +
867 +function code_profiler_create_tmpfile() {
868 +
869 + $tmp_folder = dirname( __DIR__ ) .'/tmp';
870 + $tmp_file = "$tmp_folder/profiler.inc.php";
871 +
872 + if (! is_dir( $tmp_folder ) ) {
873 + $res = @ mkdir( $tmp_folder );
874 + if ( false === $res ) {
875 + return sprintf(
876 + __('Cannot create temporary folder: [%s]. Any attempt to profile a cron event will likely fail.', 'code-profiler'),
877 + $tmp_folder
878 + );
879 + }
880 + }
881 +
882 + if (! is_file( "$tmp_folder/index.html" ) ) {
883 + touch( "$tmp_folder/index.html" );
884 + }
885 +
886 + $res = @ file_put_contents(
887 + $tmp_file,
888 + "<?php\nconst ABSPATH = '". ABSPATH ."';\n"
889 + );
890 + if ( false === $res ) {
891 + return sprintf(
892 + __('Cannot create temporary file: [%s]. Any attempt to profile a cron event will likely fail.', 'code-profiler'),
893 + $tmp_file
894 + );
441 895 }
442 896 }
443 897
444 898 // =====================================================================