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 +566 -202 1.4.41.9.6 View file →
@@ -1,6 +1,6 @@
1 1 <?php
2 -/*
2 +/**
3 3 +=====================================================================+
4 4 | ____ _ ____ __ _ _ |
5 5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
@@ -6,89 +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 -define('CODE_PROFILER_UPDATE_URI', 'https://code-profiler.com/update/' );
29 -global $wp_version;
30 -if (! defined('CODE_PROFILER_UA') ) { // UA signatures can be user-defined in the wp-config.php
31 - define ('CODE_PROFILER_UA', [
32 - esc_html__('Desktop', 'code-profiler') => [
33 - 'Firefox' => 'Mozilla/5.0 (Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0',
34 - 'Chrome' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)'.
35 - ' Chrome/102.0.5005.63 Safari/537.36',
36 - 'Edge' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,'.
37 - ' like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/100.0.1185.39'
38 - ],
39 - esc_html__('Mobile', 'code-profiler') => [
40 - 'Android Phone' => 'Mozilla/5.0 (Android 12; Mobile; rv:68.0) Gecko/68.0 Firefox/100.0',
41 - 'Android Tablet' => 'Mozilla/5.0 (Linux; Android 12.0; SAMSUNG-SM-T377A Build/NMF26X)'.
42 - ' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.58 Mobile Safari/537.36',
43 - 'iPhone' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_5 like Mac OS X) AppleWebKit/605.1.15'.
44 - ' (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
45 - 'iPad' => 'Mozilla/5.0 (iPad; CPU OS 15_5 like Mac OS X) AppleWebKit/605.1.15'.
46 - ' (KHTML, like Gecko) GSA/213.0.449417121 Mobile/15E148 Safari/604.1'
47 - ],
48 - esc_html__('Bot', 'code-profiler') => [
49 - 'Google Bot' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
50 - 'WordPress' => 'Mozilla/5.0 (compatible; CodeProfiler for WordPress/'. $wp_version .
51 - '; https://code-profiler.com/)'
52 - ]
53 -
54 - ] );
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');
55 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>');
56 43 if (! defined('CODE_PROFILER_MUPLUGIN') ) {
44 + // MU plugin's name can be defined in the wp-config.php
57 45 define('CODE_PROFILER_MUPLUGIN', '0----code-profiler.php');
58 46 }
47 +
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 +
59 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 +// =====================================================================
60 129 // Update version in the DB and check if options must be updated.
61 130
62 131 function code_profiler_init_update() {
63 132
64 - $cp_options = get_option( 'code-profiler' );
65 - if ( empty( $cp_options['version'] ) ||
66 - 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 + }
67 139
140 + if ( empty( $cp_options['version'] ) ) {
68 141 $cp_options['version'] = CODE_PROFILER_VERSION;
142 + update_option('code-profiler', $cp_options );
69 143
144 + } elseif ( version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<') ) {
145 +
70 146 // Version 1.1
71 - if (! isset( $cp_options['enable_wpcli'] ) ) {
72 - $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 + }
73 151 }
74 152
75 153 // Version 1.2
76 - if (! isset( $cp_options['disable_wpcron'] ) ) {
77 - $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 + }
78 158 }
79 159
80 160 // Version 1.3.1
81 - if (! isset( $cp_options['http_response'] ) ) {
82 - $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 + }
83 165 }
84 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 +
85 248 // Update version in the DB
86 - update_option( 'code-profiler', $cp_options );
249 + update_option('code-profiler', $cp_options );
87 250 }
88 251
89 252 }
90 -add_action('admin_init', 'code_profiler_init_update' );
253 +add_action('admin_init', 'code_profiler_init_update');
91 254
92 255 // =====================================================================
93 256 // Verify or create our storage folder in the uploads directory.
94 257 // Call during activation and access to the plugin.
@@ -95,15 +258,23 @@
95 258
96 259 function code_profiler_check_uploadsdir() {
97 260
98 261 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
99 - mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
262 + wp_mkdir_p( CODE_PROFILER_UPLOAD_DIR );
100 263 }
101 - if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html' ) ) {
102 - 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 + );
103 271 }
272 + if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html') ) {
273 + touch( CODE_PROFILER_UPLOAD_DIR .'/index.html');
274 + }
104 275 // For Apache & Litespeed
105 - if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess' ) ) {
276 + if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess') ) {
106 277 file_put_contents(
107 278 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
108 279 'Require all denied'
109 280 );
@@ -115,9 +286,18 @@
115 286
116 287
117 288 if (! file_exists( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) ) {
118 289
119 - 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') ) {
120 300 code_profiler_log_error( sprintf(
121 301 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
122 302 __DIR__ .'/mu.plugin'
123 303 ));
@@ -131,9 +311,9 @@
131 311 }
132 312 }
133 313 } else {
134 314 // Update MU plugin it if needed
135 - if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin' ) ) {
315 + if ( md5_file( WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN ) !== md5_file( __DIR__ .'/mu.plugin') ) {
136 316 copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/'. CODE_PROFILER_MUPLUGIN );
137 317 }
138 318 }
139 319
@@ -144,12 +324,59 @@
144 324
145 325 }
146 326
147 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 +// =====================================================================
148 373 // Create the default options.
149 374
150 375 function code_profiler_default_options() {
151 376
377 + $buffer = code_profiler_suggested_memory();
378 +
152 379 $cp_options = [
153 380 'show_paths' => 'relative',
154 381 'display_name' => 'full',
155 382 'truncate_name' => 30,
@@ -156,25 +383,34 @@
156 383 'chart_type' => 'x',
157 384 'chart_max_plugins' => 25,
158 385 'hide_empty_value' => 1,
159 386 'table_max_rows' => 30,
160 - 'warn_composer' => 1,
161 387 'enable_wpcli' => 1,
162 388 'disable_wpcron' => 1,
163 - 'http_response' => '^(?:3|4|5)\d{2}$'
389 + 'http_response' => '^(?:3|4|5)\d{2}$',
390 + 'accuracy' => 1,
391 + 'buffer' => (int) $buffer
164 392 ];
165 393
166 - update_option( 'code-profiler', $cp_options );
394 + update_option('code-profiler', $cp_options );
167 395
168 396 }
169 397
170 -// =====================================================================
398 +// ===================================================================== 2023-06-14
171 399 // Create a profile name.
172 400
173 401 function code_profiler_profile_name() {
174 402
175 403 return date('Y-m-d_') . substr( time(), 4 );
404 +}
176 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 );
177 413 }
178 414
179 415 // =====================================================================
180 416 // Disable opcode cache.
@@ -181,80 +417,141 @@
181 417
182 418 function code_profiler_disable_opcode() {
183 419
184 420 try {
185 - if (extension_loaded('Zend OPcache')) {
421 + if ( extension_loaded('Zend OPcache') ) {
186 422 ini_set('opcache.enable', 0);
187 423 } elseif ( extension_loaded('wincache') ) {
188 424 ini_set('wincache.fcenabled', 0);
189 425 }
190 426 set_time_limit(0);
427 + ini_set('memory_limit', -1);
191 428
192 429 } catch ( Exception $e ) { }
193 430
194 - $cp_options = get_option( 'code-profiler' );
431 + $cp_options = get_option('code-profiler');
432 + /**
433 + * Disable WP-CRON.
434 + */
195 435 if (! empty( $cp_options['disable_wpcron'] ) ) {
196 - if (! defined( 'DISABLE_WP_CRON' ) ) {
436 + if (! defined('DISABLE_WP_CRON') ) {
197 437 define('DISABLE_WP_CRON', true );
198 438 }
199 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 +}
200 453
201 -}
202 454 // =====================================================================
203 -// Verify the security key when running the profile.
455 +// Verify the security key when running the profiler.
204 456
205 457 function code_profiler_verify_key() {
206 458
207 459 $response = [
208 460 'status' => 'error',
209 - '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)'
210 463 ];
211 464
212 - $cp_options = get_option( 'code-profiler' );
465 + if ( empty( $_REQUEST['profiler_key'] ) ) {
466 + $response['message'] = sprintf( $response['message'], '001');
213 467
214 - if ( empty( $cp_options['hash'] ) ) {
215 - $response['message'] = sprintf( $response['message'], '#1' );
216 -
217 468 } else {
218 - if ( empty( $_REQUEST['profiler_key'] ) ) {
219 - $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 + }
220 480
221 - } else {
222 - if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
223 - return;
224 - } else {
225 - $response['message'] = sprintf( $response['message'], '#3' );
226 - }
481 + // Delete it and accept the request
482 + unlink( $file );
483 + return;
227 484 }
485 + $response['message'] = sprintf( $response['message'], '002');
228 486 }
229 487 wp_send_json( $response );
230 -
231 488 }
232 489
233 490 // =====================================================================
234 491 // Write message to the log.
235 -// 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).
236 494
237 -function code_profiler_write2log( $message, $level = 1 ) {
495 +function code_profiler_write2log( $message, $level, $create ) {
238 496
239 - 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 + }
240 509 }
241 510
242 -function code_profiler_log_info( $string ) { code_profiler_write2log( $string, 1 ); }
243 -function code_profiler_log_warn( $string ) { code_profiler_write2log( $string, 2 ); }
244 -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 +}
245 523
246 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 +// =====================================================================
247 544 // Verify if a profile exists and return its full path.
248 545
249 -function code_profiler_get_profile_path( $id, $type = 'slugs' ) {
546 +function code_profiler_get_profile_path( $id, $type = 'slugs') {
250 547
251 - $return = false;
548 + if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
252 549
253 - if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
254 - $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$id.*.$type.profile" );
550 + $glob = code_profiler_glob(CODE_PROFILER_UPLOAD_DIR, "$id\..+\.$type\.profile$", true);
551 +
255 552 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
256 - if ( preg_match( "`/$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
553 + if ( preg_match( "`$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
257 554
258 555 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
259 556 }
260 557 }
@@ -264,19 +561,19 @@
264 561
265 562 // =====================================================================
266 563 // Open, parse and return the content of a profile file.
267 564
268 -function code_profiler_get_profile_data( $file, $type = 'slugs' ) {
565 +function code_profiler_get_profile_data( $file, $type = 'slugs') {
269 566
270 567 $buffer = [];
271 568
272 - $fh = fopen( "$file.$type.profile", 'rb' );
569 + $fh = fopen( "$file.$type.profile", 'rb');
273 570
274 571 if ( $fh === false ) {
275 572 $err = sprintf(
276 573 CODE_PROFILER_ERROR_NOTICE,
277 574 sprintf(
278 - esc_html__('Unable to open profile file: %s.', 'code-profiler' ),
575 + esc_html__('Unable to open profile file: %s.', 'code-profiler'),
279 576 esc_html( "$file.$type.profile" )
280 577 )
281 578 );
282 579 $buffer['error'] = $err;
@@ -282,10 +579,17 @@
282 579 $buffer['error'] = $err;
283 580 return $buffer;
284 581 }
285 582
583 + /**
584 + * We don't use fgetcsv() as it requires the $escape parameter since PHP 8.4
585 + */
286 586 while (! feof( $fh ) ) {
287 - $buffer[] = fgetcsv( $fh, 1000, "\t" );
587 + $tmp = trim( fgets( $fh, 1000 ) );
588 + if (! $tmp ) {
589 + continue;
590 + }
591 + $buffer[] = explode( "\t", $tmp );
288 592 }
289 593
290 594 fclose( $fh );
291 595
@@ -292,9 +596,9 @@
292 596 if ( empty( $buffer ) ) {
293 597 $err = sprintf(
294 598 CODE_PROFILER_ERROR_NOTICE,
295 599 sprintf(
296 - esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler' ),
600 + esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler'),
297 601 esc_html( "$file.$type.profile" )
298 602 )
299 603 );
300 604 $buffer['error'] = $err;
@@ -310,9 +614,9 @@
310 614 // from WP CLI
311 615
312 616 function code_profiler_wp_send_json( $response ) {
313 617
314 - if ( defined( 'WP_CLI' ) && WP_CLI ) {
618 + if ( defined('WP_CLI') && WP_CLI ) {
315 619 WP_CLI::error( $response['message'] );
316 620 }
317 621 wp_send_json( $response );
318 622 }
@@ -319,11 +623,11 @@
319 623
320 624 // =====================================================================
321 625 // Retrieve the summary stats for a given profile.
322 626
323 -function code_profiler_getsummarystats( $profile_path, $type = 'html' ) {
627 +function code_profiler_getsummarystats( $profile_path, $type = 'html') {
324 628
325 - if ( $type == 'html' ) {
629 + if ( $type == 'html') {
326 630 $open = '<ul><li>&#10148; ';
327 631 $div = '</li><li>&#10148; ';
328 632 $close = '</li></ul>';
329 633 } else {
@@ -340,56 +644,77 @@
340 644
341 645 $string = $open;
342 646
343 647 if ( empty( $decode->items ) ) {
344 - return false;
648 + $tmp = __('N/A', 'code-profiler');
649 + } else {
650 + $tmp = (int) --$decode->items;
345 651 }
346 - $decode->items--;
347 652 $string .= sprintf(
348 653 __('%s plugins and 1 theme', 'code-profiler'),
349 - (int) $decode->items
654 + $tmp
350 655 );
351 656
352 657 $string .= $div;
353 658
354 659 if ( empty( $decode->time ) ) {
355 - return false;
660 + $tmp = __('N/A', 'code-profiler');
661 + } else {
662 + $tmp = number_format( $decode->time, 4 );
356 663 }
357 664 $string .= sprintf(
358 665 __('Execution time: %ss', 'code-profiler'),
359 - number_format( $decode->time, 4 )
666 + $tmp
360 667 );
361 668
362 669 $string .= $div;
363 670
364 671 if ( empty( $decode->memory ) ) {
365 - return false;
672 + $tmp = __('N/A', 'code-profiler');
673 + } else {
674 + $tmp = number_format( $decode->memory, 2);
366 675 }
367 676 $string .= sprintf(
368 677 __('Peak memory: %s MB', 'code-profiler'),
369 - number_format( $decode->memory, 2)
678 + $tmp
370 679 );
371 680
372 681 $string .= $div;
373 682
374 683 if ( empty( $decode->io ) ) {
375 - return false;
684 + $tmp = __('N/A', 'code-profiler');
685 + } else {
686 + $tmp = number_format( (int) $decode->io );
376 687 }
377 688 $string .= sprintf(
378 689 __('File I/O operations: %s', 'code-profiler'),
379 - number_format( $decode->io )
690 + $tmp
380 691 );
381 692
382 693 $string .= $div;
383 694
384 695 if ( empty( $decode->queries ) ) {
385 - return false;
696 + $tmp = __('N/A', 'code-profiler');
697 + } else {
698 + $tmp = number_format( (int) $decode->queries );
386 699 }
387 700 $string .= sprintf(
388 701 __('SQL queries: %s', 'code-profiler'),
389 - number_format( $decode->queries )
702 + $tmp
390 703 );
391 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 +
392 717 $string .= $close;
393 718
394 719 return $string;
395 720 }
@@ -394,141 +719,180 @@
394 719 return $string;
395 720 }
396 721
397 722 // =====================================================================
398 -// Remove *tmp files left after an HTTP error (4xx or 5xx).
723 +// Remove *tmp files left in the profiles folder.
399 724
400 725 function code_profiler_cleantmpfiles() {
401 726
402 - $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.tmp' );
727 + $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, '\.tmp$', true );
728 +
403 729 if ( is_array( $glob ) ) {
404 - $count = 0;
730 +
731 + $now = time();
405 732 foreach( $glob as $file ) {
406 - $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 + */
407 750 unlink( $file );
408 751 }
409 - if ( $count ) {
410 - code_profiler_log_info( sprintf(
411 - __('Deleting %s temporary files found in the profiles folder', 'code-profiler' ),
412 - (int) $count
413 - ) );
414 - }
415 752 }
416 753 }
417 754
418 755 // =====================================================================
419 -// Check and inform if there's an update.
756 +// Remove non-ASCII characters from a string.
420 757
421 -function code_profiler_check_update( $transient ) {
758 +function code_profiler_ASCII_filter( $string ) {
422 759
423 - if ( empty( $transient->checked['code-profiler/index.php'] ) ) {
424 - $version = CODE_PROFILER_VERSION;
425 - } else {
426 - $version = $transient->checked['code-profiler/index.php'];
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" );
427 770 }
771 +}
428 772
429 - $args = [
430 - 'slug' => 'code-profiler',
431 - 'version' => $version
432 - ];
773 +// ===================================================================== 2023-06-15
774 +// We don't want to be bothered by other themes/plugins' admin notices.
433 775
434 - $domain = strtolower( parse_url( network_home_url(), PHP_URL_HOST ) );
435 - global $wp_version;
436 - $request_string = array(
437 - 'body' => array(
438 - 'action' => 'version',
439 - 'request' => serialize( $args ),
440 - 'version' => CODE_PROFILER_VERSION,
776 +add_action('admin_head', 'code_profiler_hide_admin_notices');
777 +
778 +function code_profiler_hide_admin_notices() {
779 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler') {
780 + remove_all_actions('admin_notices');
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'
441 793 ),
442 - 'user-agent' => "WordPress/$wp_version;$domain; ".
443 - CODE_PROFILER_VERSION .'; version'
444 - );
794 + '<a href="https://nintechnet.com/codeprofiler/" target="_blank">',
795 + '</a>'
796 + ).
797 + '</span>';
798 +}
445 799
446 - $res = wp_remote_post( CODE_PROFILER_UPDATE_URI, $request_string);
447 800
448 - if (! is_wp_error( $res ) && $res['response']['code'] == 200 ) {
449 - $response = unserialize( $res['body'] );
450 - }
451 - if ( ! empty( $response ) && is_object( $response ) ) {
452 - $transient->response['code-profiler/index.php'] = $response;
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'";
453 808 } else {
454 - // No update
455 - $item = (object) array(
456 - 'id' => 'code-profiler/index.php',
457 - 'slug' => 'code-profiler',
458 - 'plugin' => 'code-profiler/index.php',
459 - 'new_version' => CODE_PROFILER_VERSION,
460 - 'url' => '',
461 - 'package' => '',
462 - 'icons' => array(),
463 - 'banners' => array(),
464 - 'banners_rtl' => array(),
465 - 'tested' => '',
466 - 'requires_php' => '',
467 - 'compatibility' => new stdClass(),
468 - );
469 - // Needed for the enable/disable auto-updates link.
470 - $transient->no_update['code-profiler/index.php'] = $item;
809 + echo " style='display:none'";
471 810 }
472 -
473 - return $transient;
474 811 }
475 812
476 -add_filter('pre_set_site_transient_update_plugins', 'code_profiler_check_update');
477 -
478 813 // =====================================================================
479 -// We return the new version's changelog.
814 +// Retrieve all themes.
480 815
481 -function code_profiler_check_plugin_info( $def, $action, $args ) {
816 +function code_profiler_get_themes() {
482 817
483 - if (! isset($args->slug) || $args->slug != 'code-profiler' ||
484 - $action != 'plugin_information' ) {
818 + $list = [];
485 819
486 - return $def;
820 + // Make sure the function is loaded
821 + if (! function_exists('wp_get_themes') ) {
822 + require_once ABSPATH . 'wp-includes/theme.php';
487 823 }
488 - $plugin_info = get_site_transient('update_plugins');
489 - if ( isset( $plugin_info->checked['code-profiler/index.php'] ) ) {
490 - $current_version = $plugin_info->checked['code-profiler/index.php'];
491 - } else {
492 - $current_version = CODE_PROFILER_VERSION;
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 + }
493 838 }
494 - $args->version = $current_version;
839 + return $list;
840 +}
495 841
496 - $domain = strtolower( parse_url( network_home_url(), PHP_URL_HOST ) );
497 - global $wp_version;
842 +// =====================================================================
843 +// Retrieve all available cron events.
498 844
499 - $request_string = array(
500 - 'body' => array(
501 - 'action' => 'plugin_information',
502 - 'request' => serialize( $args ),
503 - 'version' => CODE_PROFILER_VERSION
504 - ),
505 - 'user-agent' => "WordPress/$wp_version;$domain; ".
506 - CODE_PROFILER_VERSION .'; plugin_information'
507 - );
845 +function code_profiler_get_crons() {
508 846
509 - $res = wp_remote_post( CODE_PROFILER_UPDATE_URI, $request_string );
847 + $list = [];
510 848
511 - if (! is_wp_error( $res ) && $res['response']['code'] == 200 ) {
512 - // Server can return either serialized data or a json-encoded error
513 - if ( is_serialized( $res['body'] ) ) {
514 - return unserialize( $res['body'] );
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;
515 858 }
516 859 }
517 - return false;
860 + sort( $list );
861 + return $list;
518 862 }
519 863
520 -add_filter('plugins_api', 'code_profiler_check_plugin_info', 10, 3);
864 +// =====================================================================
865 +// Create a file with the ABSPATH.
521 866
522 -// =====================================================================
523 -// We don't want to be bothered by other themes/plugins' admin notices.
867 +function code_profiler_create_tmpfile() {
524 868
525 -add_action('admin_head', 'code_profiler_hide_admin_notices');
869 + $tmp_folder = dirname( __DIR__ ) .'/tmp';
870 + $tmp_file = "$tmp_folder/profiler.inc.php";
526 871
527 -function code_profiler_hide_admin_notices() {
528 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'code-profiler' ) {
529 - remove_all_actions('admin_notices');
530 - remove_all_actions('all_admin_notices');
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 + );
531 895 }
532 896 }
533 897
534 898 // =====================================================================