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