PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.4.3
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.4.3
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
code-profiler / lib / helper.php

helper.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy 1.4.3, at lib/helper.php

533 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 +=====================================================================+
4 | ____ _ ____ __ _ _ |
5 | / ___|___ __| | ___ | _ \ _ __ ___ / _(_) | ___ _ __ |
6 | | | / _ \ / _` |/ _ \ | |_) | '__/ _ \| |_| | |/ _ \ '__| |
7 | | |__| (_) | (_| | __/ | __/| | | (_) | _| | | __/ | |
8 | \____\___/ \__,_|\___| |_| |_| \___/|_| |_|_|\___|_| |
9 | |
10 | (c) Jerome Bruandet ~ https://code-profiler.com/ |
11 +=====================================================================+
12 */
13
14 if (! defined( 'ABSPATH' ) ) { die( 'Forbidden' ); }
15
16 // =====================================================================
17 // Various constants
18
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 ] );
55 }
56 // =====================================================================
57 // Update version in the DB and check if options must be updated.
58
59 function code_profiler_init_update() {
60
61 $cp_options = get_option( 'code-profiler' );
62 if ( empty( $cp_options['version'] ) ||
63 version_compare( $cp_options['version'], CODE_PROFILER_VERSION, '<' ) ) {
64
65 $cp_options['version'] = CODE_PROFILER_VERSION;
66
67 // Version 1.1
68 if (! isset( $cp_options['enable_wpcli'] ) ) {
69 $cp_options['enable_wpcli'] = 1;
70 }
71
72 // Version 1.2
73 if (! isset( $cp_options['disable_wpcron'] ) ) {
74 $cp_options['disable_wpcron'] = 1;
75 }
76
77 // Version 1.3.1
78 if (! isset( $cp_options['http_response'] ) ) {
79 $cp_options['http_response'] = '^(?:3|4|5)\d{2}$';
80 }
81
82 // Update version in the DB
83 update_option( 'code-profiler', $cp_options );
84 }
85
86 }
87 add_action('admin_init', 'code_profiler_init_update' );
88
89 // =====================================================================
90 // Verify or create our storage folder in the uploads directory.
91 // Call during activation and access to the plugin.
92
93 function code_profiler_check_uploadsdir() {
94
95 if (! file_exists( CODE_PROFILER_UPLOAD_DIR ) ) {
96 mkdir( CODE_PROFILER_UPLOAD_DIR, 0755 );
97 }
98 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/index.html' ) ) {
99 touch( CODE_PROFILER_UPLOAD_DIR .'/index.html' );
100 }
101 // For Apache & Litespeed
102 if (! file_exists( CODE_PROFILER_UPLOAD_DIR .'/.htaccess' ) ) {
103 file_put_contents(
104 CODE_PROFILER_UPLOAD_DIR .'/.htaccess',
105 'Require all denied'
106 );
107 }
108 // Make sure there's a MU plugin directory
109 if (! is_dir( WPMU_PLUGIN_DIR ) ) {
110 wp_mkdir_p( WPMU_PLUGIN_DIR );
111 }
112
113
114 if (! file_exists( WPMU_PLUGIN_DIR .'/0----code-profiler.php' ) ) {
115
116 if (! file_exists( __DIR__ .'/mu.plugin' ) ) {
117 code_profiler_log_error( sprintf(
118 esc_html__('Cannot find the MU plugin: %s', 'code-profiler'),
119 __DIR__ .'/mu.plugin'
120 ));
121 } else {
122 $res = copy( __DIR__ .'/mu.plugin', WPMU_PLUGIN_DIR .'/0----code-profiler.php' );
123 if ( $res === false ) {
124 code_profiler_log_error( sprintf(
125 esc_html__('Cannot copy the MU plugin to %s', 'code-profiler'),
126 WPMU_PLUGIN_DIR
127 ));
128 }
129 }
130 } else {
131 // 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' );
134 }
135 }
136
137 // Log file
138 if (! file_exists( CODE_PROFILER_LOG ) ) {
139 file_put_contents( CODE_PROFILER_LOG, "<?php exit; ?>\n" );
140 }
141
142 }
143
144 // =====================================================================
145 // Create the default options.
146
147 function code_profiler_default_options() {
148
149 $cp_options = [
150 'show_paths' => 'relative',
151 'display_name' => 'full',
152 'truncate_name' => 30,
153 'chart_type' => 'x',
154 'chart_max_plugins' => 25,
155 'hide_empty_value' => 1,
156 'table_max_rows' => 30,
157 'warn_composer' => 1,
158 'enable_wpcli' => 1,
159 'disable_wpcron' => 1,
160 'http_response' => '^(?:3|4|5)\d{2}$'
161 ];
162
163 update_option( 'code-profiler', $cp_options );
164
165 }
166
167 // =====================================================================
168 // Create a profile name.
169
170 function code_profiler_profile_name() {
171
172 return date('Y-m-d_') . substr( time(), 4 );
173
174 }
175
176 // =====================================================================
177 // Disable opcode cache.
178
179 function code_profiler_disable_opcode() {
180
181 try {
182 if (extension_loaded('Zend OPcache')) {
183 ini_set('opcache.enable', 0);
184 } elseif ( extension_loaded('wincache') ) {
185 ini_set('wincache.fcenabled', 0);
186 }
187 set_time_limit(0);
188
189 } catch ( Exception $e ) { }
190
191 $cp_options = get_option( 'code-profiler' );
192 if (! empty( $cp_options['disable_wpcron'] ) ) {
193 if (! defined( 'DISABLE_WP_CRON' ) ) {
194 define('DISABLE_WP_CRON', true );
195 }
196 }
197
198 }
199 // =====================================================================
200 // Verify the security key when running the profile.
201
202 function code_profiler_verify_key() {
203
204 $response = [
205 'status' => 'error',
206 'message' => __('Security keys do not match. Reload the page and try again (%s)', 'code-profiler' )
207 ];
208
209 $cp_options = get_option( 'code-profiler' );
210
211 if ( empty( $cp_options['hash'] ) ) {
212 $response['message'] = sprintf( $response['message'], '#1' );
213
214 } else {
215 if ( empty( $_REQUEST['profiler_key'] ) ) {
216 $response['message'] = sprintf( $response['message'], '#2' );
217
218 } else {
219 if ( $cp_options['hash'] == sha1( $_REQUEST['profiler_key'] ) ) {
220 return;
221 } else {
222 $response['message'] = sprintf( $response['message'], '#3' );
223 }
224 }
225 }
226 wp_send_json( $response );
227
228 }
229
230 // =====================================================================
231 // Write message to the log.
232 // Log level can be a combination of INFO (1), WARN (2) and ERROR (4).
233
234 function code_profiler_write2log( $message, $level = 1 ) {
235
236 file_put_contents( CODE_PROFILER_LOG, time() ."~~$level~~$message\n", FILE_APPEND );
237 }
238
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 ); }
242
243 // =====================================================================
244 // Verify if a profile exists and return its full path.
245
246 function code_profiler_get_profile_path( $id, $type = 'slugs' ) {
247
248 $return = false;
249
250 if (! empty( $id ) && preg_match('/^\d{10}\.\d+$/', $id ) ) {
251 $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$id.*.$type.profile" );
252 if ( is_array( $glob ) && ! empty( $glob[0] ) ) {
253 if ( preg_match( "`/$id\.(.+?).$type.profile$`", $glob[0], $match ) ) {
254
255 return CODE_PROFILER_UPLOAD_DIR. "/$id.{$match[1]}";
256 }
257 }
258 }
259 return false;
260 }
261
262 // =====================================================================
263 // Open, parse and return the content of a profile file.
264
265 function code_profiler_get_profile_data( $file, $type = 'slugs' ) {
266
267 $buffer = [];
268
269 $fh = fopen( "$file.$type.profile", 'rb' );
270
271 if ( $fh === false ) {
272 $err = sprintf(
273 CODE_PROFILER_ERROR_NOTICE,
274 sprintf(
275 esc_html__('Unable to open profile file: %s.', 'code-profiler' ),
276 esc_html( "$file.$type.profile" )
277 )
278 );
279 $buffer['error'] = $err;
280 return $buffer;
281 }
282
283 while (! feof( $fh ) ) {
284 $buffer[] = fgetcsv( $fh, 1000, "\t" );
285 }
286
287 fclose( $fh );
288
289 if ( empty( $buffer ) ) {
290 $err = sprintf(
291 CODE_PROFILER_ERROR_NOTICE,
292 sprintf(
293 esc_html__('Profile file is empty or corrupted: %s.', 'code-profiler' ),
294 esc_html( "$file.$type.profile" )
295 )
296 );
297 $buffer['error'] = $err;
298 return $buffer;
299 }
300
301 // Get rid of the empty field created by fgetcsv
302 return array_filter( $buffer );
303 }
304
305 // =====================================================================
306 // Exit with a json-encoded error message except if we're running
307 // from WP CLI
308
309 function code_profiler_wp_send_json( $response ) {
310
311 if ( defined( 'WP_CLI' ) && WP_CLI ) {
312 WP_CLI::error( $response['message'] );
313 }
314 wp_send_json( $response );
315 }
316
317 // =====================================================================
318 // Retrieve the summary stats for a given profile.
319
320 function code_profiler_getsummarystats( $profile_path, $type = 'html' ) {
321
322 if ( $type == 'html' ) {
323 $open = '<ul><li>&#10148; ';
324 $div = '</li><li>&#10148; ';
325 $close = '</li></ul>';
326 } else {
327 // WP-CLI
328 $open = " \u{27A4} ";
329 $div = "\n \u{27A4} ";
330 $close = "\n\n";
331 }
332
333 if (! file_exists( "$profile_path.summary.profile" ) ) {
334 return false;
335 }
336 $decode = json_decode( file_get_contents( "$profile_path.summary.profile" ) );
337
338 $string = $open;
339
340 if ( empty( $decode->items ) ) {
341 return false;
342 }
343 $decode->items--;
344 $string .= sprintf(
345 __('%s plugins and 1 theme', 'code-profiler'),
346 (int) $decode->items
347 );
348
349 $string .= $div;
350
351 if ( empty( $decode->time ) ) {
352 return false;
353 }
354 $string .= sprintf(
355 __('Execution time: %ss', 'code-profiler'),
356 number_format( $decode->time, 4 )
357 );
358
359 $string .= $div;
360
361 if ( empty( $decode->memory ) ) {
362 return false;
363 }
364 $string .= sprintf(
365 __('Peak memory: %s MB', 'code-profiler'),
366 number_format( $decode->memory, 2)
367 );
368
369 $string .= $div;
370
371 if ( empty( $decode->io ) ) {
372 return false;
373 }
374 $string .= sprintf(
375 __('File I/O operations: %s', 'code-profiler'),
376 number_format( $decode->io )
377 );
378
379 $string .= $div;
380
381 if ( empty( $decode->queries ) ) {
382 return false;
383 }
384 $string .= sprintf(
385 __('SQL queries: %s', 'code-profiler'),
386 number_format( $decode->queries )
387 );
388
389 $string .= $close;
390
391 return $string;
392 }
393
394 // =====================================================================
395 // Remove *tmp files left after an HTTP error (4xx or 5xx).
396
397 function code_profiler_cleantmpfiles() {
398
399 $glob = glob( CODE_PROFILER_UPLOAD_DIR .'/*.tmp' );
400 if ( is_array( $glob ) ) {
401 $count = 0;
402 foreach( $glob as $file ) {
403 $count++;
404 unlink( $file );
405 }
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 }
413 }
414
415 // =====================================================================
416 // Check and inform if there's an update.
417
418 function code_profiler_check_update( $transient ) {
419
420 if ( empty( $transient->checked['code-profiler/index.php'] ) ) {
421 $version = CODE_PROFILER_VERSION;
422 } else {
423 $version = $transient->checked['code-profiler/index.php'];
424 }
425
426 $args = [
427 'slug' => 'code-profiler',
428 'version' => $version
429 ];
430
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,
438 ),
439 'user-agent' => "WordPress/$wp_version;$domain; ".
440 CODE_PROFILER_VERSION .'; version'
441 );
442
443 $res = wp_remote_post( CODE_PROFILER_UPDATE_URI, $request_string);
444
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;
450 } 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;
468 }
469
470 return $transient;
471 }
472
473 add_filter('pre_set_site_transient_update_plugins', 'code_profiler_check_update');
474
475 // =====================================================================
476 // We return the new version's changelog.
477
478 function code_profiler_check_plugin_info( $def, $action, $args ) {
479
480 if (! isset($args->slug) || $args->slug != 'code-profiler' ||
481 $action != 'plugin_information' ) {
482
483 return $def;
484 }
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;
490 }
491 $args->version = $current_version;
492
493 $domain = strtolower( parse_url( network_home_url(), PHP_URL_HOST ) );
494 global $wp_version;
495
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 );
505
506 $res = wp_remote_post( CODE_PROFILER_UPDATE_URI, $request_string );
507
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'] );
512 }
513 }
514 return false;
515 }
516
517 add_filter('plugins_api', 'code_profiler_check_plugin_info', 10, 3);
518
519 // =====================================================================
520 // We don't want to be bothered by other themes/plugins' admin notices.
521
522 add_action('admin_head', 'code_profiler_hide_admin_notices');
523
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');
528 }
529 }
530
531 // =====================================================================
532 // EOF
533