PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.6
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.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 1.6.8 All 40 releases
code-profiler / lib / ajax.php

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

566 lines 18.1 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 // Start the profiler.
18
19 add_action('wp_ajax_codeprofiler_start_profiler', 'codeprofiler_start_profiler');
20
21 function codeprofiler_start_profiler() {
22
23 $response = ['status' => 'error'];
24
25 $cp_options = get_option('code-profiler');
26
27 code_profiler_log_debug(
28 esc_html__('Entering AJAX endpoint (profiler initialization)', 'code-profiler')
29 );
30
31 // If this is an AJAX call, make sure it comes from an admin/superadmin.
32 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_start_profiler') {
33 // Admin/Superadmin only
34 if (! is_super_admin() ) {
35 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
36 $response['message'] = $msg;
37 code_profiler_log_error( $msg );
38 code_profiler_wp_send_json( $response );
39 }
40 }
41
42 code_profiler_log_debug(
43 esc_html__('Verifying security nonce', 'code-profiler')
44 );
45
46 // Verify the security nonce
47 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
48 $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler');
49 $response['message'] = $msg;
50 code_profiler_log_error( $msg );
51 code_profiler_wp_send_json( $response );
52 }
53
54 code_profiler_log_debug(
55 esc_html__('Checking MU plugin availability', 'code-profiler')
56 );
57
58 // Verify the MU plugin is loaded
59 if (! defined('CODE_PROFILER_MU_ON') ) {
60 $msg = esc_html__('The MU plugin is not loaded, please check the log', 'code-profiler');
61 $response['message'] = $msg;
62 code_profiler_log_error( $msg );
63 code_profiler_wp_send_json( $response );
64 }
65
66 code_profiler_log_debug(
67 esc_html__('Cleaning up the temporary folder', 'code-profiler')
68 );
69
70 // Clean-up temp files left in the profiles folder
71 code_profiler_cleantmpfiles();
72
73 code_profiler_log_debug(
74 esc_html__('Retrieving parameters #1', 'code-profiler')
75 );
76
77 // Frontend or backend
78 if ( empty( $_POST['where'] ) || ! in_array( $_POST['where'], ['frontend', 'backend', 'custom'] ) ) {
79 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'where');
80 $response['message'] = $msg;
81 code_profiler_log_error( $msg );
82 code_profiler_wp_send_json( $response );
83 }
84 $cp_options['mem_where'] = $_POST['where'];
85
86 code_profiler_log_debug(
87 esc_html__('Retrieving parameters #2', 'code-profiler')
88 );
89
90 if ( empty( $_POST['post'] ) ) {
91 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'post');
92 $response['message'] = $msg;
93 code_profiler_log_error( $msg );
94 code_profiler_wp_send_json( $response );
95 }
96 $cp_options['mem_post'] = $_POST['post'];
97
98 // Make sure we have no more that 4 decimals, because when returning
99 // it via AJAX, it will display more decimals than that
100 $microtime = number_format( microtime( true ), 4, '.', '');
101
102 code_profiler_log_debug(
103 esc_html__('Retrieving parameters #3', 'code-profiler')
104 );
105
106 // Authentication
107 if ( empty( $_POST['user'] ) || ! in_array( $_POST['user'], ['authenticated', 'unauthenticated'] ) ) {
108 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'user');
109 $response['message'] = $msg;
110 code_profiler_log_error( $msg );
111 code_profiler_wp_send_json( $response );
112 }
113 $cp_options['mem_user'] = $_POST['user'];
114
115 code_profiler_log_debug(
116 esc_html__('Retrieving parameters #4', 'code-profiler')
117 );
118
119 if ( empty( $_POST['profile'] ) || strlen( $_POST['profile'] ) > 100 ) {
120 $profile = code_profiler_profile_name();
121 } else {
122 $profile = sanitize_file_name( $_POST['profile'] );
123 }
124
125 // URI to profile
126 $url = esc_url_raw( $_POST['post'] );
127 code_profiler_log_info( sprintf(
128 esc_html__('Initializing Code Profiler v%s for %s (profile: %s)', 'code-profiler'),
129 CODE_PROFILER_VERSION,
130 $url,
131 $profile
132 ) );
133
134 code_profiler_log_debug(
135 esc_html__('Retrieving parameters #5', 'code-profiler')
136 );
137
138 // User-agent
139 if ( empty( $_POST['ua'] ) ) {
140 $ua = 'Firefox';
141 } else {
142 $ua = sanitize_text_field( $_POST['ua'] );
143 }
144 foreach( CODE_PROFILER_UA as $types => $types_array ) {
145 foreach( $types_array as $name => $value ) {
146 if ( $ua == $name ) {
147 $ua_signature = $value;
148 break;
149 }
150 }
151 }
152 if ( empty( $ua_signature ) ) {
153 $ua_signature = CODE_PROFILER_UA['Desktop']['Firefox'];
154 }
155 $cp_options['ua'] = $ua;
156
157 code_profiler_log_debug(
158 esc_html__('Creating security key', 'code-profiler')
159 );
160
161 // Create security key
162 $profiler_key = bin2hex( random_bytes( 16 ) );
163 $cp_options['hash'] = sha1( $profiler_key );
164
165 code_profiler_log_debug(
166 esc_html__('Building HTTP query', 'code-profiler')
167 );
168
169 // Build query
170 $url = add_query_arg( [
171 'CODE_PROFILER_ON' => $microtime,
172 'profiler_key' => $profiler_key
173 ], $url );
174
175 global $wp_version;
176 $headers = [
177 'Cache-Control' => 'no-cache, no-store, must-revalidate',
178 'Pragma' => 'no-cache',
179 'Expires' => '0',
180 'httpversion' => '1.1',
181 // Devs must be allowed to use it on localhost over TLS too
182 'sslverify' => apply_filters('https_local_ssl_verify', false ),
183 'timeout' => 300, // 300-second timeout instead of the default 5s
184 'redirection' => 0, // We don't want to be redirected
185 'headers' => [
186 'code-profiler-key' => $profiler_key,
187 'Accept-Language' => 'en-US,en;q=0.5',
188 'User-Agent' => $ua_signature
189 ]
190 ];
191
192 code_profiler_log_debug(
193 esc_html__('Checking HTTP options', 'code-profiler')
194 );
195
196 // Forward basic authentication if any (not available from WP CLI)
197 if ( function_exists('apache_request_headers') ) {
198 $apache_headers = apache_request_headers();
199 if ( isset( $apache_headers['Authorization'] ) ) {
200 $headers['headers']['Authorization'] = $apache_headers['Authorization'];
201 }
202 // WP-CLI ($ wp code-profiler run --u=FOO --p=BAR)
203 } elseif ( defined('WP_CLI') && ! empty( $_POST['Authorization'] ) ) {
204 $headers['headers']['Authorization'] = $_POST['Authorization'];
205 }
206
207 if ( $_POST['user'] == 'authenticated') {
208
209 code_profiler_log_debug(
210 esc_html__('Creating authentication cookies', 'code-profiler')
211 );
212
213 // Used for authentication
214 if ( is_ssl() ) {
215 $cookie_auth = SECURE_AUTH_COOKIE;
216 $scheme = 'secure_auth';
217 } else {
218 $cookie_auth = AUTH_COOKIE;
219 $scheme = 'auth';
220 }
221
222 // Retrieve the user name (since 1.4.3)
223 if (! defined('WP_CLI') ) {
224 if ( empty( $_POST['username'] ) ) {
225 $msg = esc_html__('Missing authenticated username', 'code-profiler');
226 $response['message'] = $msg;
227 code_profiler_log_error( $msg );
228 code_profiler_wp_send_json( $response );
229 }
230 $username = sanitize_user( $_POST['username'] );
231 $user_object = get_user_by('login', $username );
232 if ( $user_object === false ) {
233 $msg = sprintf( esc_html__('User [%s] does not exist.', 'code-profiler'), $username);
234 $response['message'] = $msg;
235 code_profiler_log_error( $msg );
236 code_profiler_wp_send_json( $response );
237 }
238 $cp_options['mem_username'] = strtolower( $username );
239 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( $user_object->ID, time() + 180, $scheme );
240 if ( empty( $headers['cookies'][ $cookie_auth ] ) ) {
241 $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler');
242 code_profiler_log_error( $msg );
243 $response['message'] = $msg;
244 code_profiler_wp_send_json( $response );
245 }
246 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $user_object->ID, time() + 180, 'logged_in');
247 if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) {
248 $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler');
249 code_profiler_log_error( $msg );
250 $response['message'] = $msg;
251 code_profiler_wp_send_json( $response );
252 }
253 // WP CLI
254 } else {
255 $id = get_current_user_id();
256 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( $id, time() + 180, $scheme );
257 if ( empty( $headers['cookies'][ $cookie_auth ] ) ) {
258 $msg = esc_html__('Unable to create the authentication cookie', 'code-profiler');
259 code_profiler_log_error( $msg );
260 $response['message'] = $msg;
261 code_profiler_wp_send_json( $response );
262 }
263 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $id, time() + 180, 'logged_in');
264 if ( empty( $headers['cookies'][ LOGGED_IN_COOKIE ] ) ) {
265 $msg = esc_html__('Unable to create the "logged_in" cookie', 'code-profiler');
266 code_profiler_log_error( $msg );
267 $response['message'] = $msg;
268 code_profiler_wp_send_json( $response );
269 }
270 }
271 $session_id = session_id();
272 if ( $session_id !== false ) {
273 $session_name = session_name();
274 $headers['cookies'][ $session_name ] = $session_id;
275 }
276 }
277
278 if ( function_exists('opcache_reset') ) {
279 code_profiler_log_debug(
280 esc_html__('Clearing opcode cache', 'code-profiler')
281 );
282 opcache_reset();
283 }
284
285 // GET or POST method
286 if (! empty( $_POST['method'] ) && $_POST['method'] == 'post') {
287 $safe_method = 'wp_safe_remote_post';
288 $cp_options['mem_method'] = 'post';
289
290 // Optional POST payload
291 if (! empty( $_POST['payload'] ) ) {
292
293 code_profiler_log_debug(
294 esc_html__('Building POST payload', 'code-profiler')
295 );
296
297 $payload_array = explode( PHP_EOL, trim( $_POST['payload'] ) );
298 foreach( $payload_array as $item ) {
299 $payload = explode('=', trim( $item ), 2 );
300 if ( isset( $payload[1] ) ) {
301 $payload[0] = trim( $payload[0] );
302 $payload[1] = trim( $payload[1] );
303 $headers['body'][ $payload[0] ] = $payload[1];
304 }
305 }
306 $cp_options['payload'] = json_encode( $_POST['payload'] );
307 $cp_options['mem_payload'] = $_POST['payload'];
308 } else {
309 // POST request without a payload
310 unset( $cp_options['payload'] );
311 $cp_options['mem_payload'] = '';
312 }
313 } else {
314 $safe_method = 'wp_safe_remote_get';
315 $cp_options['mem_method'] = 'get';
316 }
317
318 // Optional user-defined cookies
319 if (! empty( $_POST['cookies'] ) ) {
320
321 code_profiler_log_debug(
322 esc_html__('Building HTTP Cookies', 'code-profiler')
323 );
324
325 $cookies_array = explode( PHP_EOL, trim( $_POST['cookies'] ) );
326 foreach( $cookies_array as $item ) {
327 $cookie = explode('=', trim( $item ), 2 );
328 if ( isset( $cookie[1] ) ) {
329 $cookie[0] = trim( $cookie[0] );
330 $cookie[1] = trim( $cookie[1] );
331 $headers['cookies'][ $cookie[0] ] = $cookie[1];
332 }
333 }
334 $cp_options['cookies'] = json_encode( $_POST['cookies'] );
335 } else {
336 unset( $cp_options['cookies'] );
337 }
338
339 update_option('code-profiler', $cp_options );
340
341 code_profiler_log_debug(
342 esc_html__('Sending HTTP request', 'code-profiler')
343 );
344
345 // We must allow developers to run the profiler
346 // on a local IP (e.g, http://127.0.0.1/)
347 add_filter('http_request_host_is_external', '__return_true');
348 $res = $safe_method( $url, $headers );
349
350 // Connection error
351 if ( is_wp_error( $res ) ) {
352 $msg = esc_html__('Cannot connect to the requested page: %s', 'code-profiler');
353 $response['message'] = sprintf(
354 $msg,
355 esc_html( $res->get_error_message() )
356 );
357 code_profiler_log_error( sprintf( $msg, $res->get_error_message() ) );
358 code_profiler_wp_send_json( $response );
359 }
360
361 code_profiler_log_debug(
362 esc_html__('Fetching HTTP response', 'code-profiler')
363 );
364
365 // HTTP status code
366 if (! empty( $cp_options['http_response'] ) ) {
367 if ( preg_match( "/{$cp_options['http_response']}/", $res['response']['code'] ) ) {
368 $msg = esc_html__('The website returned the following HTTP status code: %s %s.', 'code-profiler').
369 ' '.
370 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');
371 $response['message'] = sprintf(
372 $msg,
373 (int) $res['response']['code'],
374 esc_html( $res['response']['message'] )
375 );
376 code_profiler_log_error( sprintf( $msg, $res['response']['code'], $res['response']['message'] ) );
377 code_profiler_wp_send_json( $response );
378 }
379 }
380
381 code_profiler_log_debug(
382 esc_html__('Decoding body', 'code-profiler')
383 );
384
385 // Check response
386 $message = json_decode( $res['body'], true );
387 if ( isset( $message['status'] ) && isset( $message['message'] ) ) {
388 $response['status'] = $message['status'];
389 $response['message'] = $message['message'];
390 code_profiler_wp_send_json( $response );
391 }
392 code_profiler_log_info(
393 esc_html__('Collecting data to analyze', 'code-profiler')
394 );
395 // Return success
396 $response = ['status' => 'success'];
397 $response['message'] = 'success';
398 $response['microtime'] = $microtime;
399
400 code_profiler_log_debug(
401 esc_html__('Leaving AJAX endpoint', 'code-profiler')
402 );
403
404 // AJAX action?
405 if ( defined('DOING_AJAX') && DOING_AJAX ) {
406 code_profiler_wp_send_json( $response );
407 }
408
409 return json_encode( $response );
410
411 }
412
413 // =====================================================================
414
415 add_action('wp_ajax_codeprofiler_prepare_report', 'codeprofiler_prepare_report');
416
417 function codeprofiler_prepare_report() {
418
419 $response = ['status' => 'error'];
420
421 code_profiler_log_debug(
422 esc_html__('Entering AJAX endpoint (report preparation)', 'code-profiler')
423 );
424
425 // If this is an AJAX call, make sure it comes from an admin/superadmin.
426 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_prepare_report') {
427 // Admin/Superadmin only
428 if (! is_super_admin() ) {
429 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
430 $response['message'] = $msg;
431 code_profiler_log_error( $msg );
432 code_profiler_wp_send_json( $response );
433 }
434 }
435
436 code_profiler_log_debug(
437 esc_html__('Verifying security nonce', 'code-profiler')
438 );
439
440 // Verify the security nonce
441 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
442 $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler');
443 $response['message'] = $msg;
444 code_profiler_log_error( $msg );
445 code_profiler_wp_send_json( $response );
446 }
447
448 code_profiler_log_debug(
449 esc_html__('Retrieving profile ID', 'code-profiler')
450 );
451
452 if ( empty( $_POST['microtime'] ) || ! preg_match('/^\d{10}\.\d+$/', $_POST['microtime'] ) ) {
453 $msg = esc_html__('Missing parameter (microtime).', 'code-profiler');
454 $response['message'] = $msg;
455 code_profiler_log_error( $msg );
456 code_profiler_wp_send_json( $response );
457 }
458 $microtime = sanitize_text_field( $_POST['microtime'] );
459
460 code_profiler_log_debug(
461 esc_html__('Retrieving profile name', 'code-profiler')
462 );
463
464 $profile = sanitize_file_name( $_POST['profile'] );
465 if ( empty( $profile ) ) {
466 $msg = esc_html__('Missing profile name.', 'code-profiler');
467 $response['message'] = $msg;
468 code_profiler_log_error( $msg );
469 code_profiler_wp_send_json( $response );
470 }
471
472 code_profiler_log_info(
473 esc_html__('Preparing the report', 'code-profiler')
474 );
475 require 'class-report.php';
476 $report = new CodeProfiler_Report( $profile, $microtime );
477 $report->prepare_report();
478
479 // Take a 1s break so that we can spot any potential error
480 // in the backend before AJAX refresh the page
481 usleep(1000000);
482
483 // Clear hash
484 $cp_options = get_option('code-profiler');
485 unset( $cp_options['hash'] );
486 update_option('code-profiler', $cp_options );
487
488 code_profiler_log_info(
489 esc_html__('All done, exiting profiler', 'code-profiler')
490 );
491 $response['cp_profile'] = $microtime;
492 $response['status'] = 'success';
493 $response['message'] = 'success';
494
495 code_profiler_log_debug(
496 esc_html__('Leaving AJAX endpoint', 'code-profiler')
497 );
498
499 // AJAX action?
500 if ( defined('DOING_AJAX') && DOING_AJAX ) {
501 code_profiler_wp_send_json( $response );
502 }
503
504 return json_encode( $response );
505
506 }
507
508 // =====================================================================
509 // Rename a profile.
510
511 add_action('wp_ajax_codeprofiler_rename', 'codeprofiler_rename');
512
513 function codeprofiler_rename() {
514
515 $response = ['status' => 'error'];
516
517 // Admin/Superadmin only
518 if (! is_super_admin() ) {
519 $response['message'] = esc_html__('You are not allowed to performed this action', 'code-profiler');
520 wp_send_json( $response );
521 }
522
523 // Verify the security nonce
524 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'rename-profile') ) {
525 $response['message'] = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler');
526 wp_send_json( $response );
527 }
528
529 if ( empty( $_POST['new_name'] ) ) {
530 $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler');
531 wp_send_json( $response );
532 }
533 $new_name = sanitize_file_name( $_POST['new_name'] );
534 if ( strlen( $new_name ) > 100 ) {
535 $new_name = substr( $new_name, 0, 100 );
536 }
537 if ( empty( $new_name ) ) {
538 $response['message'] = esc_html__('Please enter a name for this profile.', 'code-profiler');
539 wp_send_json( $response );
540 }
541
542 if ( empty( $_POST['profile'] ) || ! preg_match('/^\d{10}\.\d{4}$/', $_POST['profile'] ) ) {
543 $response['message'] = esc_html__('Missing profile identifier.', 'code-profiler');
544 wp_send_json( $response );
545 }
546 $profile = $_POST['profile'];
547
548 $glob = glob( CODE_PROFILER_UPLOAD_DIR ."/$profile*" );
549 if ( is_array( $glob ) ) {
550 foreach( $glob as $path ) {
551 // preg_quote is needed for Windows servers because ABSPATH will contain backslashes
552 if ( preg_match('`^'. preg_quote( CODE_PROFILER_UPLOAD_DIR ) .'/(\d{10}\.\d{4})\..+?\.([a-z]+?\.profile)$`', $path, $match ) ) {
553 rename( $path, CODE_PROFILER_UPLOAD_DIR . "/{$match[1]}.$new_name.{$match[2]}" );
554 }
555 }
556 }
557
558 $response['status'] = 'success';
559 $response['newname'] = $new_name;
560 wp_send_json( $response );
561
562 }
563
564 // =====================================================================
565 // EOF
566