PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / trunk
Code Profiler – WordPress Performance Profiling and Debugging Made Easy vtrunk
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 / class-ajax.php

class-ajax.php in Code Profiler – WordPress Performance Profiling and Debugging Made Easy trunk, at lib/class-ajax.php

922 lines 27.0 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://nintechnet.com/codeprofiler/ |
11 +=====================================================================+
12 */
13
14 if (! defined('ABSPATH') ) {
15 die('Forbidden');
16 }
17
18 // =====================================================================
19
20 class CodeProfiler_helpers {
21
22 /**
23 * Hooks.
24 */
25 public static function init() {
26 /**
27 * Start the profiler.
28 */
29 add_action('wp_ajax_codeprofiler_start_profiler', [ __CLASS__,'codeprofiler_start_profiler'] );
30 /**
31 * Profiler's report.
32 */
33 add_action('wp_ajax_codeprofiler_prepare_report', [ __CLASS__,'codeprofiler_prepare_report'] );
34 /**
35 * Rename a profile.
36 */
37 add_action('wp_ajax_codeprofiler_rename', [ __CLASS__,'codeprofiler_rename'] );
38 }
39
40
41 /**
42 * Start the profiler.
43 */
44 public static function codeprofiler_start_profiler() {
45
46 $response = ['status' => 'error'];
47
48 code_profiler_hide_errors();
49
50 $cp_options = get_option('code-profiler');
51 if (! empty( $cp_options['mem'] ) ) {
52 $mem = $cp_options['mem'];
53 } else {
54 $mem = [];
55 }
56
57 code_profiler_log_debug(
58 esc_html__('Entering AJAX endpoint (profiler initialization)', 'code-profiler')
59 );
60
61 // If this is an AJAX call, make sure it comes from an admin/superadmin.
62 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_start_profiler') {
63 // Admin/Superadmin only
64 if (! is_super_admin() ) {
65 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
66 $response['message'] = $msg;
67 code_profiler_log_error( $msg );
68 code_profiler_wp_send_json( $response );
69 }
70 }
71
72 code_profiler_log_debug(
73 esc_html__('Verifying security nonce', 'code-profiler')
74 );
75
76 // Verify the security nonce
77 if ( empty( $_POST['cp_nonce'] ) ||
78 ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
79
80 $msg = esc_html__(
81 'Missing or wrong security nonce. Reload the page and try again',
82 'code-profiler'
83 );
84 $response['message'] = $msg;
85 code_profiler_log_error( $msg );
86 code_profiler_wp_send_json( $response );
87 }
88
89 code_profiler_log_debug(
90 esc_html__('Checking MU plugin availability', 'code-profiler')
91 );
92
93 // Verify the MU plugin is loaded
94 if (! defined('CODE_PROFILER_MU_ON') ) {
95 $msg = esc_html__('The MU plugin is not loaded, please check the log', 'code-profiler');
96 $response['message'] = $msg;
97 code_profiler_log_error( $msg );
98 code_profiler_wp_send_json( $response );
99 }
100
101 code_profiler_log_debug(
102 esc_html__('Retrieving parameters #1', 'code-profiler')
103 );
104
105 // Frontend or backend
106 if ( empty( $_POST['x_end'] ) ||
107 ! in_array( $_POST['x_end'], ['frontend', 'backend', 'custom', 'wpcron'] ) ) {
108
109 $msg = sprintf(
110 esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'x_end'
111 );
112 $response['message'] = $msg;
113 code_profiler_log_error( $msg );
114 code_profiler_wp_send_json( $response );
115 }
116 $mem['x_end'] = $_POST['x_end'];
117
118 code_profiler_log_debug(
119 esc_html__('Retrieving parameters #2', 'code-profiler')
120 );
121
122 if ( empty( $_POST['post'] ) ) {
123 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'post');
124 $response['message'] = $msg;
125 code_profiler_log_error( $msg );
126 code_profiler_wp_send_json( $response );
127 }
128 $mem['post'] = $_POST['post'];
129
130 // Make sure we have no more that 4 decimals, because when returning
131 // it via AJAX, it will display more decimals than that
132 $microtime = number_format( microtime( true ), 4, '.', '');
133
134 code_profiler_log_debug(
135 esc_html__('Retrieving parameters #3', 'code-profiler')
136 );
137
138 // Authentication
139 if ( empty( $_POST['x_auth'] ) || $_POST['x_auth'] != 'authenticated' ) {
140 $_POST['x_auth'] = 'unauthenticated';
141 }
142 $mem['x_auth'] = $_POST['x_auth'];
143
144 code_profiler_log_debug(
145 esc_html__('Retrieving parameters #4', 'code-profiler')
146 );
147
148 if ( empty( $_POST['profile'] ) || strlen( $_POST['profile'] ) > 100 ) {
149 $profile = code_profiler_profile_name();
150 } else {
151 $profile = sanitize_file_name( $_POST['profile'] );
152 }
153
154 /**
155 * WP cron events use POST with an empty payload.
156 */
157 if ( $_POST['x_end'] == 'wpcron') {
158
159 $doing_wp_cron = sprintf( '%.22F', microtime( true ) );
160 set_transient('doing_cron', $doing_wp_cron );
161
162 code_profiler_log_debug(
163 esc_html__('Cron event detected: setting method to POST with an empty payload.', 'code-profiler')
164 );
165 $_POST['method'] = 'post';
166 $_POST['content_type'] = 1;
167 $_POST['payload'] = '';
168 $_POST['post'] = esc_url(
169 plugins_url('wp-cron.php?doing_wp_cron=' .
170 $doing_wp_cron,
171 dirname( __FILE__ ) )
172 ) ."&wpcron={$_POST['post']}" ;
173 }
174
175 // URI to profile
176 $url = esc_url_raw( $_POST['post'] );
177 $siteurl = esc_html( site_url() );
178 code_profiler_log_info( sprintf(
179 /* Translators: version, site url, profile name, profile url */
180 esc_html__('Initializing Code Profiler v%s on %s. Profile: %s - %s', 'code-profiler'),
181 CODE_PROFILER_VERSION,
182 $siteurl,
183 $profile,
184 $url
185 ) );
186
187 code_profiler_log_debug(
188 esc_html__('Retrieving parameters #5', 'code-profiler')
189 );
190
191 // User-agent
192 if ( empty( $_POST['user_agent'] ) ) {
193 $user_agent = 'Firefox';
194 } else {
195 $user_agent = sanitize_text_field( $_POST['user_agent'] );
196 }
197 foreach( CODE_PROFILER_UA as $types => $types_array ) {
198 foreach( $types_array as $name => $value ) {
199 if ( $user_agent == $name ) {
200 $ua_signature = $value;
201 break;
202 }
203 }
204 }
205 if ( empty( $ua_signature ) ) {
206 $ua_signature = CODE_PROFILER_UA['Desktop']['Firefox'];
207 }
208 $mem['user_agent'] = $user_agent;
209
210 // Theme
211 $themes = code_profiler_get_themes();
212 if ( empty( $_POST['theme'] ) || empty( $themes[ $_POST['theme'] ] ) ) {
213 $theme = '';
214 unset( $mem['theme'] );
215 } else {
216 code_profiler_log_debug(
217 esc_html__('Retrieving parameters #6', 'code-profiler')
218 );
219 $theme = $_POST['theme'];
220 $mem['theme'] = $theme;
221 // Append the template to the stylesheet
222 if (! empty( $themes[ $theme ]['t'] ) ) {
223 $theme .= "::{$themes[ $theme ]['t']}";
224 } else {
225 $theme .= "::$theme";
226 }
227 }
228
229 code_profiler_log_debug(
230 esc_html__('Creating security key', 'code-profiler')
231 );
232
233 // Create security key
234 $profiler_key = bin2hex( random_bytes( 16 ) );
235 touch( CODE_PROFILER_UPLOAD_DIR .'/key_'. sha1( $profiler_key ) .'.tmp');
236
237 code_profiler_log_debug(
238 esc_html__('Building HTTP query', 'code-profiler')
239 );
240
241 /**
242 * Build the query.
243 */
244 $raw_url = $url;
245 $url = add_query_arg( [
246 'CODE_PROFILER_ON' => $microtime,
247 'profiler_key' => $profiler_key
248 ], $url );
249
250 global $wp_version;
251 $headers = [
252 'Cache-Control' => 'no-cache, no-store, must-revalidate',
253 'Pragma' => 'no-cache',
254 'Expires' => '0',
255 'httpversion' => '1.1',
256 // Devs must be allowed to use it on localhost over TLS too
257 'sslverify' => apply_filters('https_local_ssl_verify', false ),
258 'timeout' => 300, // 300-second timeout instead of the default 5s
259 'redirection' => 0, // We don't want to be redirected
260 'headers' => [
261 // Lowercase header name
262 'code-profiler-key' => $profiler_key,
263 'accept-language' => 'en-US,en;q=0.5',
264 'user-agent' => $ua_signature,
265 'theme' => $theme
266 ]
267 ];
268
269 // Custom HTTP headers
270 if (! empty( $_POST['custom_headers'] ) ) {
271 $custom_headers = explode( PHP_EOL, trim( stripslashes( $_POST['custom_headers'] ) ) );
272 if (! empty( $custom_headers[0] ) ) {
273 code_profiler_log_debug(
274 esc_html__('Building custom HTTP headers', 'code-profiler')
275 );
276 $is_custom_headers = '';
277 foreach( $custom_headers as $custom_header ) {
278 if ( strpos( $custom_header, ':') === false ) {
279 continue;
280 }
281 list( $key, $value ) = explode(':', $custom_header, 2 );
282 // Lowercase header name
283 $key = trim( strtolower( $key ) );
284 $value = trim( $value );
285 // We want printable ASCII characters only
286 $value = code_profiler_ASCII_filter( $value );
287 if (! empty( $key ) && ! empty( $value ) ) {
288 $headers['headers'][ $key ] = $value;
289 $is_custom_headers .= "$key: $value\n";
290 }
291 }
292 }
293 }
294 if (! empty( $is_custom_headers ) ) {
295 $mem['custom_headers'] = json_encode( $is_custom_headers );
296 } else {
297 unset( $mem['custom_headers'] );
298 }
299
300 code_profiler_log_debug(
301 esc_html__('Checking HTTP options', 'code-profiler')
302 );
303
304 // Forward basic authentication if any (not available from WP CLI)
305 if ( function_exists('apache_request_headers') ) {
306 $apache_headers = apache_request_headers();
307 if ( isset( $apache_headers['Authorization'] ) ) {
308 $headers['headers']['Authorization'] = $apache_headers['Authorization'];
309 }
310 // WP-CLI ($ wp code-profiler run --u=FOO --p=BAR)
311 } elseif ( defined('WP_CLI') && ! empty( $_POST['Authorization'] ) ) {
312 $headers['headers']['Authorization'] = $_POST['Authorization'];
313 }
314
315 if ( $_POST['x_auth'] == 'authenticated') {
316
317 code_profiler_log_debug(
318 esc_html__('Creating authentication cookies', 'code-profiler')
319 );
320
321 // Used for authentication
322 if ( is_ssl() ) {
323 $cookie_auth = SECURE_AUTH_COOKIE;
324 $scheme = 'secure_auth';
325 } else {
326 $cookie_auth = AUTH_COOKIE;
327 $scheme = 'auth';
328 }
329
330 // Retrieve the user name (since 1.4.3)
331 if (! defined('WP_CLI') ) {
332 if ( empty( $_POST['username'] ) ) {
333 $msg = esc_html__('Missing authenticated username', 'code-profiler');
334 $response['message'] = $msg;
335 code_profiler_log_error( $msg );
336 code_profiler_wp_send_json( $response );
337 }
338 $username = sanitize_user( $_POST['username'] );
339 $user_object = get_user_by('login', $username );
340 if ( $user_object === false ) {
341 $msg = sprintf( esc_html__('User %s does not exist.', 'code-profiler'), $username);
342 $response['message'] = $msg;
343 code_profiler_log_error( $msg );
344 code_profiler_wp_send_json( $response );
345 }
346
347 /**
348 * We add an edit_user capability check here, so that an admin can give access
349 * to the profiler to a dev with custom capabilities but who won't be able
350 * to run it/authenticate as another user.
351 */
352 if (! user_can( get_current_user_id(), 'edit_user', $user_object->ID ) ) {
353 $msg = sprintf(
354 esc_html__('Sorry, you do not have the edit_user capability to run the profiler as user %s', 'code-profiler-pro'),
355 esc_html( $username )
356 );
357 code_profiler_log_error( $msg );
358 $response['message'] = $msg;
359 code_profiler_wp_send_json( $response );
360 }
361
362 $mem['username'] = strtolower( $username );
363 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie(
364 $user_object->ID,
365 time() + 180,
366 $scheme
367 );
368 if ( empty( $headers['cookies'][ $cookie_auth ] ) ) {
369 $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler');
370 code_profiler_log_error( $msg );
371 $response['message'] = $msg;
372 code_profiler_wp_send_json( $response );
373 }
374 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie(
375 $user_object->ID,
376 time() + 180,
377 'logged_in'
378 );
379 if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) {
380 $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler');
381 code_profiler_log_error( $msg );
382 $response['message'] = $msg;
383 code_profiler_wp_send_json( $response );
384 }
385 // WP CLI
386 } else {
387 $id = get_current_user_id();
388 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie(
389 $id,
390 time() + 180,
391 $scheme
392 );
393 if ( empty( $headers['cookies'][ $cookie_auth ] ) ) {
394 $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler');
395 code_profiler_log_error( $msg );
396 $response['message'] = $msg;
397 code_profiler_wp_send_json( $response );
398 }
399 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie(
400 $id,
401 time() + 180,
402 'logged_in'
403 );
404 if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) {
405 $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler');
406 code_profiler_log_error( $msg );
407 $response['message'] = $msg;
408 code_profiler_wp_send_json( $response );
409 }
410 }
411 $session_id = session_id();
412 if ( $session_id !== false ) {
413 $session_name = session_name();
414 $headers['cookies'][ $session_name ] = $session_id;
415 }
416 }
417
418 if ( function_exists('opcache_reset') ) {
419 code_profiler_log_debug(
420 esc_html__('Clearing opcode cache', 'code-profiler')
421 );
422 opcache_reset();
423 }
424
425 // GET or POST method
426 if (! empty( $_POST['method'] ) && $_POST['method'] == 'post') {
427 $safe_method = 'wp_safe_remote_post';
428 $mem['method'] = 'post';
429
430 // Content-type
431 $content_type = [
432 1 => 'application/x-www-form-urlencoded', // Formatted
433 3 => 'application/x-www-form-urlencoded', // Raw
434 2 => 'application/json'
435 ];
436 if ( empty( $_POST['content_type'] ) ||
437 ! in_array( $_POST['content_type'], [ 1, 2, 3 ] ) ) {
438
439 $mem['content_type'] = 1;
440 } else {
441 $mem['content_type'] = (int) $_POST['content_type'];
442 }
443 $headers['headers']['content-type'] = $content_type[ $mem['content_type'] ];
444
445 // Optional POST payload
446 if (! empty( $_POST['payload'] ) ) {
447 $_payload = trim( stripslashes( $_POST['payload'] ) );
448
449 code_profiler_log_debug(
450 esc_html__('Building POST payload', 'code-profiler')
451 );
452 /**
453 * application/x-www-form-urlencoded (formatted)
454 */
455 if ( $mem['content_type'] == 1 ) {
456 $payload_array = explode( PHP_EOL, $_payload );
457 foreach( $payload_array as $item ) {
458 $payload = explode('=', trim( $item ), 2 );
459 if ( isset( $payload[1] ) ) {
460 $payload[0] = trim( $payload[0] );
461 $payload[1] = trim( $payload[1] );
462 $headers['body'][ $payload[0] ] = $payload[1];
463 }
464 }
465 /**
466 * application/x-www-form-urlencoded (raw)
467 */
468 } elseif ( $mem['content_type'] == 3 ) {
469 parse_str( $_payload , $payload_array );
470 foreach( $payload_array as $key => $item ) {
471 $headers['body'][ $key ] = $item;
472 }
473 /**
474 * application/json
475 */
476 } else {
477 $headers['body'] = $_payload;
478 }
479 $mem['payload'] = json_encode( $_payload );
480
481 } else {
482 // POST request without a payload
483 unset( $mem['payload'] );
484 }
485
486 } else {
487 $safe_method = 'wp_safe_remote_get';
488 $mem['method'] = 'get';
489 }
490
491 // Optional user-defined cookies
492 if (! empty( $_POST['cookies'] ) ) {
493
494 code_profiler_log_debug(
495 esc_html__('Building HTTP Cookies', 'code-profiler')
496 );
497
498 $cookies_array = explode( PHP_EOL, trim( stripslashes( $_POST['cookies'] ) ) );
499 foreach( $cookies_array as $item ) {
500 $cookie = explode('=', trim( $item ), 2 );
501 if ( isset( $cookie[1] ) ) {
502 $cookie[0] = trim( $cookie[0] );
503 $cookie[1] = trim( $cookie[1] );
504 $headers['cookies'][ $cookie[0] ] = $cookie[1];
505 }
506 }
507 $mem['cookies'] = json_encode( $_POST['cookies'] );
508 } else {
509 unset( $mem['cookies'] );
510 }
511
512 /**
513 * Optional file and folder exclusions.
514 */
515 $tmp_exclusions = [];
516 if (! empty( $_POST['exclusions'] ) ) {
517 $tmp_array = explode( PHP_EOL, trim( stripslashes( $_POST['exclusions'] ) ) );
518 foreach( $tmp_array as $item ) {
519 $item = trim( code_profiler_ASCII_filter( $item ) );
520 if ( $item ) {
521 $tmp_exclusions[] = $item;
522 }
523 }
524 }
525 /**
526 * Remove duplicates.
527 */
528 $exclusions = array_unique( $tmp_exclusions );
529
530 if ( $exclusions) {
531 $mem['exclusions'] = json_encode( $exclusions );
532 } else {
533 unset( $mem['exclusions'] );
534 }
535
536 $cp_options['mem'] = $mem;
537 update_option('code-profiler', $cp_options );
538
539 /**
540 * Save the profile configuration into a temporary file (used by the re-run feature).
541 */
542 file_put_contents(
543 CODE_PROFILER_UPLOAD_DIR ."/$microtime.". CODE_PROFILER_TMP_RERUN_LOG,
544 json_encode( $mem )
545 );
546
547 code_profiler_log_debug(
548 esc_html__('Sending HTTP request', 'code-profiler')
549 );
550
551 // On a multisite network, the superadmin must log in to the site they want to profile,
552 // i.e., they can't profile all sites from the main one, otherwise the profiler would
553 // not be able to profile it and wouldn't throw an error.
554 $allowed_hosts = [
555 wp_parse_url( home_url(), PHP_URL_HOST ),
556 wp_parse_url( site_url(), PHP_URL_HOST ),
557 ];
558 $target = wp_parse_url( $url );
559 if (! in_array( $target['host'], $allowed_hosts, true ) ) {
560 $msg = sprintf(
561 esc_html__('The site to profile seems wrong: %s', 'code-profiler'),
562 esc_html( $target['host'] )
563 );
564 $response['message'] = $msg;
565 code_profiler_log_error( $msg );
566 code_profiler_wp_send_json( $response );
567 }
568 // We allow ports 80, 443, 8080 and 8443
569 $allowed_ports = [ 80, 443, 8080, 8443 ];
570 if (! empty( $target['port'] ) && ! in_array( $target['port'], $allowed_ports ) ) {
571 $msg = sprintf(
572 esc_html__('The profiler can only access ports 80, 443, 8080 and 8443, not port %s',
573 'code-profiler'),
574 esc_html( $target['port'] )
575 );
576 $response['message'] = $msg;
577 code_profiler_log_error( $msg );
578 code_profiler_wp_send_json( $response );
579 }
580
581 // We must allow developers to run the profiler on an IP address from a LAN network etc.
582 add_filter('http_request_host_is_external', '__return_true');
583 $res = $safe_method( $url, $headers );
584 remove_filter('http_request_host_is_external', '__return_true');
585
586 // Connection error
587 if ( is_wp_error( $res ) ) {
588 $msg = esc_html__('Cannot connect to the requested page: %s', 'code-profiler');
589 $response['message'] = sprintf(
590 $msg,
591 esc_html( $res->get_error_message() )
592 );
593 code_profiler_log_error( sprintf( $msg, $res->get_error_message() ) );
594 code_profiler_wp_send_json( $response );
595 }
596
597 code_profiler_log_debug(
598 esc_html__('Fetching HTTP response', 'code-profiler')
599 );
600
601 /**
602 * Always log last HTTP response headers and body,
603 * except sensitive data (cookies & PHP session ID).
604 */
605 if ( isset( $res['headers'] ) && isset( $res['body'] ) ) {
606
607 require __DIR__.'/class-logs.php';
608 /**
609 * Parse headers.
610 */
611 $response_headers = "HTTP {$res['response']['code']} {$res['response']['message']}\n";
612
613 foreach( $res['headers'] as $key => $value ) {
614 /**
615 * Remove cookies.
616 */
617 if ( $key == 'set-cookie') {
618 $response_headers .= ucfirst( $key ) .': *** '. __('Removed', 'code-profiler') ." ***\n";
619 } else {
620 /**
621 * HTTP headers can contain arrays.
622 */
623 if ( is_array( $value ) ) {
624 foreach( $value as $k => $v ) {
625 $response_headers .= ucfirst( $key ) .": $v\n";
626 }
627 } else {
628 $response_headers .= ucfirst( $key ) .": $value\n";
629 }
630 }
631 }
632 /**
633 * Save to the log.
634 */
635 if ( empty( $headers['body'] ) ) {
636 $headers['body'] = 'x';
637 }
638 if ( empty( $res['body'] ) ) {
639 $res['body'] = '<'. __('empty', 'code-profiler') . '>';
640 }
641 CodeProfiler_Logs::save_HTTP_log(
642 $raw_url,
643 $headers['body'],
644 $response_headers,
645 $res['body']
646 );
647 }
648
649 // HTTP status code
650 if (! empty( $cp_options['http_response'] ) ) {
651 if ( preg_match( "/{$cp_options['http_response']}/", $res['response']['code'] ) ) {
652
653 $msg = '';
654
655 $log = esc_html__(
656 /* Translators: HTTP response code and message */
657 'The website returned the following HTTP status code: %s %s.', 'code-profiler'
658 );
659
660 if ( $res['response']['code'] < 500 ) {
661 $log .= ' '. esc_html__('By default, the profiler will always abort and throw an error if the server did not return a 200 HTTP status code. You can change that behaviour in the "Settings" section if the page you are profiling needs to return a different code (3xx, 4xx or 5xx).', 'code-profiler');
662 }
663
664 $msg .= $log .' '. esc_html__('You may find more details about this error in your PHP error log and/or in the "Logs" section.', 'code-profiler');
665
666 $response['message'] = sprintf(
667 $msg,
668 (int) $res['response']['code'],
669 $res['response']['message']
670 );
671 code_profiler_log_error(
672 sprintf( $log, $res['response']['code'], $res['response']['message'] )
673 );
674
675 // If it is a 301/302 redirection, we write the new URL to the log
676 if ( in_array( $res['response']['code'], [301, 302] ) &&
677 isset( $res['headers']['location'] ) ) {
678
679 code_profiler_log_error(
680 sprintf(
681 /* Translators: URL */
682 esc_html__('The URL redirects to: %s', 'code-profiler'),
683 $res['headers']['location']
684 )
685 );
686 }
687 code_profiler_wp_send_json( $response );
688 }
689 }
690
691 code_profiler_log_debug(
692 esc_html__('Decoding body', 'code-profiler')
693 );
694
695 // Check response
696 $message = json_decode( $res['body'], true );
697 if ( isset( $message['status'] ) && isset( $message['message'] ) ) {
698 $response['status'] = $message['status'];
699 $response['message'] = $message['message'];
700 code_profiler_wp_send_json( $response );
701 }
702 code_profiler_log_info(
703 esc_html__('Collecting data to analyze', 'code-profiler')
704 );
705 // Return success
706 $response = ['status' => 'success'];
707 $response['message'] = 'success';
708 $response['microtime'] = $microtime;
709
710 code_profiler_log_debug(
711 esc_html__('Leaving AJAX endpoint', 'code-profiler')
712 );
713
714 // AJAX action?
715 if ( defined('DOING_AJAX') && DOING_AJAX ) {
716 code_profiler_wp_send_json( $response );
717 }
718
719 return json_encode( $response );
720
721 }
722
723
724 /**
725 * Profiler's report.
726 */
727 public static function codeprofiler_prepare_report() {
728
729 $response = ['status' => 'error'];
730
731 // If this is an AJAX call, make sure it comes from an admin/superadmin.
732 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_prepare_report') {
733 // Admin/Superadmin only
734 if (! is_super_admin() ) {
735 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
736 $response['message'] = $msg;
737 code_profiler_log_error( $msg );
738 code_profiler_wp_send_json( $response );
739 }
740 }
741
742 code_profiler_log_debug(
743 esc_html__('Entering AJAX endpoint (report preparation)', 'code-profiler')
744 );
745
746 code_profiler_hide_errors();
747
748 code_profiler_log_debug(
749 esc_html__('Verifying security nonce', 'code-profiler')
750 );
751
752 // Verify the security nonce
753 if ( empty( $_POST['cp_nonce'] ) ||
754 ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
755
756 $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again',
757 'code-profiler');
758 $response['message'] = $msg;
759 code_profiler_log_error( $msg );
760 code_profiler_wp_send_json( $response );
761 }
762
763 code_profiler_log_debug(
764 esc_html__('Retrieving profile ID', 'code-profiler')
765 );
766
767 if ( empty( $_POST['microtime'] ) || ! preg_match('/^\d{10}\.\d+$/', $_POST['microtime'] ) ) {
768 $msg = esc_html__('Missing parameter (microtime).', 'code-profiler');
769 $response['message'] = $msg;
770 code_profiler_log_error( $msg );
771 code_profiler_wp_send_json( $response );
772 }
773 $microtime = sanitize_text_field( $_POST['microtime'] );
774
775 code_profiler_log_debug(
776 esc_html__('Retrieving profile name', 'code-profiler')
777 );
778
779 $profile = sanitize_file_name( $_POST['profile'] );
780 if ( empty( $profile ) ) {
781 $msg = esc_html__('Missing profile name.', 'code-profiler');
782 $response['message'] = $msg;
783 code_profiler_log_error( $msg );
784 code_profiler_wp_send_json( $response );
785 }
786
787 code_profiler_log_info(
788 esc_html__('Preparing the report', 'code-profiler')
789 );
790 require 'class-report.php';
791 $report = new CodeProfiler_Report( $profile, $microtime );
792 $report->prepare_report();
793
794 // Take a 1s break so that we can spot any potential error
795 // in the backend before AJAX refresh the page
796 usleep( 1000000 );
797
798 code_profiler_log_info(
799 esc_html__('All done, exiting profiler', 'code-profiler')
800 );
801 $response['cp_profile'] = $microtime;
802 $response['status'] = 'success';
803 $response['message'] = 'success';
804
805 code_profiler_log_debug(
806 esc_html__('Leaving AJAX endpoint', 'code-profiler')
807 );
808
809 // AJAX action?
810 if ( defined('DOING_AJAX') && DOING_AJAX ) {
811 code_profiler_wp_send_json( $response );
812 }
813
814 return json_encode( $response );
815
816 }
817
818
819 /**
820 * Rename a profile.
821 */
822 public static function codeprofiler_rename() {
823
824 $response = ['status' => 'error'];
825
826 code_profiler_hide_errors();
827
828 // Admin/Superadmin only
829 if (! is_super_admin() ) {
830 $response['message'] = esc_html__(
831 'You are not allowed to performed this action.', 'code-profiler'
832 );
833 wp_send_json( $response );
834 }
835
836 // Verify the security nonce
837 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'rename-profile') ) {
838 $response['message'] = esc_html__(
839 'Missing or wrong security nonce. Reload the page and try again.', 'code-profiler'
840 );
841 wp_send_json( $response );
842 }
843
844 if ( empty( $_POST['new_name'] ) ) {
845 $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler');
846 wp_send_json( $response );
847 }
848 $new_name = sanitize_file_name( $_POST['new_name'] );
849 if ( strlen( $new_name ) > 100 ) {
850 $new_name = substr( $new_name, 0, 100 );
851 }
852 if ( empty( $new_name ) ) {
853 $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler');
854 wp_send_json( $response );
855 }
856
857 if ( empty( $_POST['profile'] ) || ! preg_match('/^\d{10}\.\d{4}$/', $_POST['profile'] ) ) {
858 $response['message'] = esc_html__('Missing profile identifier.', 'code-profiler');
859 wp_send_json( $response );
860 }
861 $profile = $_POST['profile'];
862
863 $glob = code_profiler_glob( CODE_PROFILER_UPLOAD_DIR, "^$profile", true );
864
865 $res = false;
866
867 if ( is_array( $glob ) ) {
868 foreach( $glob as $path ) {
869 // preg_quote is needed for Windows servers because ABSPATH will contain backslashes
870 if ( preg_match('`^'. preg_quote( CODE_PROFILER_UPLOAD_DIR . DIRECTORY_SEPARATOR ) .
871 '(\d{10}\.\d{4})\..+?\.([a-z]+?\.profile)$`', $path, $match ) ) {
872
873 $res = rename( $path, CODE_PROFILER_UPLOAD_DIR . "/{$match[1]}.$new_name.{$match[2]}" );
874 if ( $res === false ) {
875 $response['message'] = esc_html__(
876 'The operation failed.', 'code-profiler'
877 );
878 wp_send_json( $response );
879 }
880 }
881 }
882 }
883
884 if ( $res === false ) {
885 $response['message'] = esc_html__(
886 'The operation failed.', 'code-profiler'
887 );
888 wp_send_json( $response );
889 }
890
891 /**
892 * Rename the profile in the summary file,
893 * so that it can be used by the re-run feature.
894 */
895 if (! empty( $match[1] ) &&
896 is_file( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.$new_name.summary.profile" ) ) {
897
898 $data = json_decode(
899 file_get_contents( CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.$new_name.summary.profile" ),
900 true
901 );
902 if (! empty( $data['rerun']['profile'] ) ) {
903 $data['rerun']['profile'] = $new_name;
904 file_put_contents(
905 CODE_PROFILER_UPLOAD_DIR ."/{$match[1]}.$new_name.summary.profile", json_encode( $data )
906 );
907 }
908 }
909
910 $response['status'] = 'success';
911 $response['newname'] = $new_name;
912 wp_send_json( $response );
913
914 }
915
916 }
917
918 CodeProfiler_helpers::init();
919
920 // =====================================================================
921 // EOF
922