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 +594 -118 1.41.9.6 View file →
@@ -1,6 +1,6 @@
1 1 <?php
2 -/*
2 +/**
3 3 +=====================================================================+
4 4 | ____ _ ____ __ _ _ |
5 5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
@@ -6,81 +6,252 @@
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 -define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php' );
22 -define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp' );
23 -define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp' );
24 -define('CODE_PROFILER_TMP_TICKS_LOG', 'ticks.tmp' );
25 -define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp' );
26 -define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>' );
27 -define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>' );
28 -global $wp_version;
29 -define ( 'CODE_PROFILER_UA', [
30 - esc_html__('Desktop', 'code-profiler') => [
31 - 'FireFox' => 'Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0',
32 - 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36'
33 - ],
34 - esc_html__('Mobile', 'code-profiler') => [
35 - 'Android Phone' => 'Mozilla/5.0 (Android 12.0; Mobile; rv:97.0) Gecko/97.0'.
36 - ' Firefox/97.0',
37 - 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 12.0; SAMSUNG-SM-T377A Build/NMF26X)'.
38 - ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.3538.102 Mobile Safari/537.36',
39 - 'iPhone' => 'Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1'.
40 - '.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15',
41 - 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15'.
42 - ' (KHTML, like Gecko) Version/11.1.1 Mobile/15E148 Safari/605.1.15'
43 - ],
44 - esc_html__('Bot', 'code-profiler') => [
45 - 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
46 - 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
47 - '; https://code-profiler.com/)'
48 - ]
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 +}
33 +define('CODE_PROFILER_LOG', CODE_PROFILER_UPLOAD_DIR .'/log.php');
34 +define('CODE_PROFILER_TMP_IOSTATS_LOG', 'iostats.tmp');
35 +define('CODE_PROFILER_TMP_SUMMARY_LOG', 'summary.tmp');
36 +define('CODE_PROFILER_TMP_RERUN_LOG', 'rerun.tmp');
37 +define('CODE_PROFILER_TMP_CALLS_LOG', 'calls.tmp');
38 +define('CODE_PROFILER_TMP_DISKIO_LOG', 'diskio.tmp');
39 +define('CODE_PROFILER_TMP_CONNECTIONS_LOG', 'connections.tmp');
40 +define('CODE_PROFILER_UPDATE_NOTICE', '<div class="updated notice is-dismissible"><p>%s</p></div>');
41 +define('CODE_PROFILER_ERROR_NOTICE', '<div class="error notice is-dismissible"><p>%s</p></div>');
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 +}
49 47
50 -] );
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 + ]);
88 +}
89 +
51 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];
112 +}
113 +
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 +// =====================================================================
52 129 // Update version in the DB and check if options must be updated.
53 130
54 131 function code_profiler_init_update() {
55 132
56 - $cp_options = get_option( 'code-profiler' );
57 - if ( empty( $cp_options['version'] ) ||
58 - 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 + }
59 139
140 + if ( empty( $cp_options['version'] ) ) {
60 141 $cp_options['version'] = CODE_PROFILER_VERSION;
142 + update_option('code-profiler', $cp_options );
61 143
144 + } elseif ( version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
145 +
62 146 // Version 1.1
63 - if (! isset( $cp_options['enable_wpcli'] ) ) {
64 - $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 + }
65 151 }
66 152
67 153 // Version 1.2
68 - if (! isset( $cp_options['disable_wpcron'] ) ) {
69 - $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 + }
70 158 }
71 159
72 160 // Version 1.3.1
73 - if (! isset( $cp_options['http_response'] ) ) {
74 - $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 + }
75 165 }
76 166
167 + // Version 1.5
168 + if ( version_compare( $cp_options['version'], '1.5', '<' ) ) {
169 + if (! isset( $cp_options['accuracy'] ) ) {
170 + $cp_options['accuracy'] = 1;
171 + }
172 + }
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 +
77 248 // Update version in the DB
78 - update_option( 'code-profiler', $cp_options );
249 + update_option('code-profiler', $cp_options );
79 250 }
80 251
81 252 }
82 -add_action('admin_init', 'code_profiler_init_update' );
253 +add_action('admin_init', 'code_profiler_init_update');
83 254
84 255 // =====================================================================
85 256 // Verify or create our storage folder in the uploads directory.
86 257 // Call during activation and access to the plugin.
@@ -87,15 +258,23 @@
87 258
88 259 function code_profiler_check_uploadsdir() {
89 260
90 261 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
91 - mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
262 + wp_mkdir_p( CODE_PROFILER_UPLOAD_DIR );
92 263 }
93 - if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html' ) ) {
94 - touch( CODE_PROFILER_UPLOAD_DIR .'/index.html' );
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 + );
95 271 }
272 + if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
273 + touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
274 + }
96 275 // For Apache & Litespeed
97 - if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess' ) ) {
276 + if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess') ) {
98 277 file_put_contents(
99 278 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
100 279 'Require all denied'
101 280 );
@@ -105,17 +284,26 @@
105 284 wp_mkdir_p( WPMU_PLUGIN_DIR );
106 285 }
107 286
108 287
109 - if (! file_exists( WPMU_PLUGIN_DIR .'/0----code-profiler.php' ) ) {
288 + if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
110 289
111 - if (! file_exists( __DIR__ .'/mu.plugin' ) ) {
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 +
299 + if (! file_exists( __DIR__ .'/mu.plugin') ) {
112 300 code_profiler_log_error( sprintf(
113 301 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
114 302 __DIR__ .'/mu.plugin'
115 303 ));
116 304 } else {
117 - $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/0----code-profiler.php' );
305 + $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
118 306 if ( $res === false ) {
119 307 code_profiler_log_error( sprintf(
120 308 esc_html__('Cannot copy the MU plugin to %s', 'code-profiler'),
121 309 WPMU_PLUGIN_DIR
@@ -123,10 +311,10 @@
123 311 }
124 312 }
125 313 } else {
126 314 // Update MU plugin it if needed
127 - if ( md5_file( WPMU_PLUGIN_DIR .'/0----code-profiler.php' ) !== md5_file( __DIR__ .'/mu.plugin' ) ) {
128 - copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/0----code-profiler.php' );
315 + if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin') ) {
316 + copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
129 317 }
130 318 }
131 319
132 320 // Log file
@@ -136,12 +324,59 @@
136 324
137 325 }
138 326
139 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 +// =====================================================================
140 373 // Create the default options.
141 374
142 375 function code_profiler_default_options() {
143 376
377 + $buffer = code_profiler_suggested_memory();
378 +
144 379 $cp_options = [
145 380 'show_paths' => 'relative',
146 381 'display_name' => 'full',
147 382 'truncate_name' => 30,
@@ -148,25 +383,34 @@
148 383 'chart_type' => 'x',
149 384 'chart_max_plugins' => 25,
150 385 'hide_empty_value' => 1,
151 386 'table_max_rows' => 30,
152 - 'warn_composer' => 1,
153 387 'enable_wpcli' => 1,
154 388 'disable_wpcron' => 1,
155 - 'http_response' => '^(?:3|4|5)\d{2}$'
389 + 'http_response' => '^(?:3|4|5)\d{2}$',
390 + 'accuracy' => 1,
391 + 'buffer' => (int) $buffer
156 392 ];
157 393
158 - update_option( 'code-profiler', $cp_options );
394 + update_option('code-profiler', $cp_options );
159 395
160 396 }
161 397
162 -// =====================================================================
398 +// ===================================================================== 2023-06-14
163 399 // Create a profile name.
164 400
165 401 function code_profiler_profile_name() {
166 402
167 403 return date('Y-m-d_') . substr( time(), 4 );
404 +}
168 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 );
169 413 }
170 414
171 415 // =====================================================================
172 416 // Disable opcode cache.
@@ -173,80 +417,141 @@
173 417
174 418 function code_profiler_disable_opcode() {
175 419
176 420 try {
177 - if (extension_loaded('Zend OPcache')) {
421 + if ( extension_loaded('Zend OPcache') ) {
178 422 ini_set('opcache.enable', 0);
179 423 } elseif ( extension_loaded('wincache') ) {
180 424 ini_set('wincache.fcenabled', 0);
181 425 }
182 426 set_time_limit(0);
427 + ini_set('memory_limit', -1);
183 428
184 429 } catch ( Exception $e ) { }
185 430
186 - $cp_options = get_option( 'code-profiler' );
431 + $cp_options = get_option('code-profiler');
432 + /**
433 + * Disable WP-CRON.
434 + */
187 435 if (! empty( $cp_options['disable_wpcron'] ) ) {
188 - if (! defined( 'DISABLE_WP_CRON' ) ) {
436 + if (! defined('DISABLE_WP_CRON') ) {
189 437 define('DISABLE_WP_CRON', true );
190 438 }
191 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 +}
192 453
193 -}
194 454 // =====================================================================
195 -// Verify the security key when running the profile.
455 +// Verify the security key when running the profiler.
196 456
197 457 function code_profiler_verify_key() {
198 458
199 459 $response = [
200 460 'status' => 'error',
201 - '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)'
202 463 ];
203 464
204 - $cp_options = get_option( 'code-profiler' );
465 + if ( empty( $_REQUEST['profiler_key'] ) ) {
466 + $response['message'] = sprintf( $response['message'], '001');
205 467
206 - if ( empty( $cp_options['hash'] ) ) {
207 - $response['message'] = sprintf( $response['message'], '#1' );
208 -
209 468 } else {
210 - if ( empty( $_REQUEST['profiler_key'] ) ) {
211 - $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 + }
212 480
213 - } else {
214 - if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
215 - return;
216 - } else {
217 - $response['message'] = sprintf( $response['message'], '#3' );
218 - }
481 + // Delete it and accept the request
482 + unlink( $file );
483 + return;
219 484 }
485 + $response['message'] = sprintf( $response['message'], '002');
220 486 }
221 487 wp_send_json( $response );
222 -
223 488 }
224 489
225 490 // =====================================================================
226 491 // Write message to the log.
227 -// 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).
228 494
229 -function code_profiler_write2log( $message, $level = 1 ) {
495 +function code_profiler_write2log( $message, $level, $create ) {
230 496
231 - 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 + }
232 509 }
233 510
234 -function code_profiler_log_info( $string ) { code_profiler_write2log( $string, 1 ); }
235 -function code_profiler_log_warn( $string ) { code_profiler_write2log( $string, 2 ); }
236 -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 +}
237 523
238 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 +// =====================================================================
239 544 // Verify if a profile exists and return its full path.
240 545
241 -function code_profiler_get_profile_path( $id, $type = 'slugs' ) {
546 +function code_profiler_get_profile_path( $id, $type = 'slugs') {
242 547
243 - $return = false;
548 + if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
244 549
245 - if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
246 - $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$id.*.$type.profile" );
550 + $glob = code_profiler_glob(CODE_PROFILER_UPLOAD_DIR, "$id\..+\.$type\.profile$", true);
551 +
247 552 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
248 - if ( preg_match( "`/$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
553 + if ( preg_match( "`$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
249 554
250 555 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
251 556 }
252 557 }
@@ -256,19 +561,19 @@
256 561
257 562 // =====================================================================
258 563 // Open, parse and return the content of a profile file.
259 564
260 -function code_profiler_get_profile_data( $file, $type = 'slugs' ) {
565 +function code_profiler_get_profile_data( $file, $type = 'slugs') {
261 566
262 567 $buffer = [];
263 568
264 - $fh = fopen( "$file.$type.profile", 'rb' );
569 + $fh = fopen( "$file.$type.profile", 'rb');
265 570
266 571 if ( $fh === false ) {
267 572 $err = sprintf(
268 573 CODE_PROFILER_ERROR_NOTICE,
269 574 sprintf(
270 - esc_html__('Unable to open profile file: %s.', 'code-profiler' ),
575 + esc_html__('Unable to open profile file: %s.', 'code-profiler'),
271 576 esc_html( "$file.$type.profile" )
272 577 )
273 578 );
274 579 $buffer['error'] = $err;
@@ -274,10 +579,17 @@
274 579 $buffer['error'] = $err;
275 580 return $buffer;
276 581 }
277 582
583 + /**
584 + * We don't use fgetcsv() as it requires the $escape parameter since PHP 8.4
585 + */
278 586 while (! feof( $fh ) ) {
279 - $buffer[] = fgetcsv( $fh, 1000, "\t" );
587 + $tmp = trim( fgets( $fh, 1000 ) );
588 + if (! $tmp ) {
589 + continue;
590 + }
591 + $buffer[] = explode( "\t", $tmp );
280 592 }
281 593
282 594 fclose( $fh );
283 595
@@ -284,9 +596,9 @@
284 596 if ( empty( $buffer ) ) {
285 597 $err = sprintf(
286 598 CODE_PROFILER_ERROR_NOTICE,
287 599 sprintf(
288 - esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler' ),
600 + esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler'),
289 601 esc_html( "$file.$type.profile" )
290 602 )
291 603 );
292 604 $buffer['error'] = $err;
@@ -302,9 +614,9 @@
302 614 // from WP CLI
303 615
304 616 function code_profiler_wp_send_json( $response ) {
305 617
306 - if ( defined( 'WP_CLI' ) && WP_CLI ) {
618 + if ( defined('WP_CLI') && WP_CLI ) {
307 619 WP_CLI::error( $response['message'] );
308 620 }
309 621 wp_send_json( $response );
310 622 }
@@ -311,11 +623,11 @@
311 623
312 624 // =====================================================================
313 625 // Retrieve the summary stats for a given profile.
314 626
315 -function code_profiler_getsummarystats( $profile_path, $type = 'html' ) {
627 +function code_profiler_getsummarystats( $profile_path, $type = 'html') {
316 628
317 - if ( $type == 'html' ) {
629 + if ( $type == 'html') {
318 630 $open = '<ul><li>&#10148; ';
319 631 $div = '</li><li>&#10148; ';
320 632 $close = '</li></ul>';
321 633 } else {
@@ -332,56 +644,77 @@
332 644
333 645 $string = $open;
334 646
335 647 if ( empty( $decode->items ) ) {
336 - return false;
648 + $tmp = __('N/A', 'code-profiler');
649 + } else {
650 + $tmp = (int) --$decode->items;
337 651 }
338 - $decode->items--;
339 652 $string .= sprintf(
340 653 __('%s plugins and 1 theme', 'code-profiler'),
341 - (int) $decode->items
654 + $tmp
342 655 );
343 656
344 657 $string .= $div;
345 658
346 659 if ( empty( $decode->time ) ) {
347 - return false;
660 + $tmp = __('N/A', 'code-profiler');
661 + } else {
662 + $tmp = number_format( $decode->time, 4 );
348 663 }
349 664 $string .= sprintf(
350 665 __('Execution time: %ss', 'code-profiler'),
351 - number_format( $decode->time, 4 )
666 + $tmp
352 667 );
353 668
354 669 $string .= $div;
355 670
356 671 if ( empty( $decode->memory ) ) {
357 - return false;
672 + $tmp = __('N/A', 'code-profiler');
673 + } else {
674 + $tmp = number_format( $decode->memory, 2);
358 675 }
359 676 $string .= sprintf(
360 677 __('Peak memory: %s MB', 'code-profiler'),
361 - number_format( $decode->memory, 2)
678 + $tmp
362 679 );
363 680
364 681 $string .= $div;
365 682
366 683 if ( empty( $decode->io ) ) {
367 - return false;
684 + $tmp = __('N/A', 'code-profiler');
685 + } else {
686 + $tmp = number_format( (int) $decode->io );
368 687 }
369 688 $string .= sprintf(
370 689 __('File I/O operations: %s', 'code-profiler'),
371 - number_format( $decode->io )
690 + $tmp
372 691 );
373 692
374 693 $string .= $div;
375 694
376 695 if ( empty( $decode->queries ) ) {
377 - return false;
696 + $tmp = __('N/A', 'code-profiler');
697 + } else {
698 + $tmp = number_format( (int) $decode->queries );
378 699 }
379 700 $string .= sprintf(
380 701 __('SQL queries: %s', 'code-profiler'),
381 - number_format( $decode->queries )
702 + $tmp
382 703 );
383 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 +
384 717 $string .= $close;
385 718
386 719 return $string;
387 720 }
@@ -386,37 +719,180 @@
386 719 return $string;
387 720 }
388 721
389 722 // =====================================================================
390 -// Remove *tmp files left after an HTTP error (4xx or 5xx).
723 +// Remove *tmp files left in the profiles folder.
391 724
392 725 function code_profiler_cleantmpfiles() {
393 726
394 - $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.tmp' );
727 + $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.tmp$', true );
728 +
395 729 if ( is_array( $glob ) ) {
396 - $count = 0;
730 +
731 + $now = time();
397 732 foreach( $glob as $file ) {
398 - $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 + */
399 750 unlink( $file );
400 751 }
401 - if ( $count ) {
402 - code_profiler_log_info( sprintf(
403 - __('Deleting %s temporary files found in the profiles folder', 'code-profiler' ),
404 - (int) $count
405 - ) );
406 - }
407 752 }
408 753 }
409 754
410 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
411 774 // We don't want to be bothered by other themes/plugins' admin notices.
412 775
413 776 add_action('admin_head', 'code_profiler_hide_admin_notices');
414 777
415 778 function code_profiler_hide_admin_notices() {
416 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler' ) {
779 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
417 780 remove_all_actions('admin_notices');
418 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 + );
419 895 }
420 896 }
421 897
422 898 // =====================================================================