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