PluginProbe
Code Profiler – WordPress Performance Profiling and Debugging Made Easy / 1.5
Code Profiler – WordPress Performance Profiling and Debugging Made Easy v1.5
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.5, at lib/ajax.php

377 lines 12.9 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 // If this is an AJAX call, make sure it comes from an admin/superadmin.
28 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_start_profiler') {
29 // Admin/Superadmin only
30 if (! is_super_admin() ) {
31 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
32 $response['message'] = $msg;
33 code_profiler_log_error( $msg );
34 code_profiler_wp_send_json( $response );
35 }
36 }
37
38 // Verify the security nonce
39 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
40 $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler');
41 $response['message'] = $msg;
42 code_profiler_log_error( $msg );
43 code_profiler_wp_send_json( $response );
44 }
45
46 // Verify the MU plugin is loaded
47 if (! defined('CODE_PROFILER_MU_ON') ) {
48 $msg = esc_html__('The MU plugin is not loaded, please check the log', 'code-profiler');
49 $response['message'] = $msg;
50 code_profiler_log_error( $msg );
51 code_profiler_wp_send_json( $response );
52 }
53
54 // Clean-up temp files left in the profiles folder
55 code_profiler_cleantmpfiles();
56
57 // Frontend or backend
58 if ( empty( $_POST['where'] ) || ! in_array( $_POST['where'], ['frontend', 'backend', 'custom'] ) ) {
59 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'where');
60 $response['message'] = $msg;
61 code_profiler_log_error( $msg );
62 code_profiler_wp_send_json( $response );
63 }
64 $cp_options['mem_where'] = $_POST['where'];
65
66 if ( empty( $_POST['post'] ) ) {
67 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'post');
68 $response['message'] = $msg;
69 code_profiler_log_error( $msg );
70 code_profiler_wp_send_json( $response );
71 }
72 $cp_options['mem_post'] = $_POST['post'];
73
74 // Make sure we have no more that 4 decimals, because when returning
75 // it via AJAX, it will display more decimals than that
76 $microtime = number_format( microtime( true ), 4, '.', '');
77
78 // Authentication
79 if ( empty( $_POST['user'] ) || ! in_array( $_POST['user'], ['authenticated', 'unauthenticated'] ) ) {
80 $msg = sprintf( esc_html__('Missing or incorrect parameter (%s)', 'code-profiler'), 'user');
81 $response['message'] = $msg;
82 code_profiler_log_error( $msg );
83 code_profiler_wp_send_json( $response );
84 }
85 $cp_options['mem_user'] = $_POST['user'];
86
87 if ( empty( $_POST['profile'] ) || strlen( $_POST['profile'] ) > 100 ) {
88 $profile = code_profiler_profile_name();
89 } else {
90 $profile = sanitize_file_name( $_POST['profile'] );
91 }
92
93 // URI to profile
94 $url = esc_url_raw( $_POST['post'] );
95 code_profiler_log_info( sprintf(
96 esc_html__('Starting Code Profiler v%s for %s (profile: %s)', 'code-profiler'),
97 CODE_PROFILER_VERSION,
98 $url,
99 $profile
100 ) );
101
102 // User-agent
103 if ( empty( $_POST['ua'] ) ) {
104 $ua = 'Firefox';
105 } else {
106 $ua = sanitize_text_field( $_POST['ua'] );
107 }
108 foreach( CODE_PROFILER_UA as $types => $types_array ) {
109 foreach( $types_array as $name => $value ) {
110 if ( $ua == $name ) {
111 $ua_signature = $value;
112 break;
113 }
114 }
115 }
116 if ( empty( $ua_signature ) ) {
117 $ua_signature = CODE_PROFILER_UA['Desktop']['Firefox'];
118 }
119 $cp_options['ua'] = $ua;
120
121 // Create security key
122 $profiler_key = bin2hex( random_bytes( 16 ) );
123 $cp_options['hash'] = sha1( $profiler_key );
124
125 // Build query
126 $url = add_query_arg( [
127 'CODE_PROFILER_ON' => $microtime,
128 'profiler_key' => $profiler_key
129 ], $url );
130
131 global $wp_version;
132 $headers = [
133 'Cache-Control' => 'no-cache, no-store, must-revalidate',
134 'Pragma' => 'no-cache',
135 'Expires' => '0',
136 'httpversion' => '1.1',
137 // Devs must be allowed to use it on localhost over TLS too
138 'sslverify' => apply_filters('https_local_ssl_verify', false ),
139 'timeout' => 180, // 180-second timeout instead of the default 5s
140 'redirection' => 0, // We don't want to be redirected
141 'headers' => [
142 'code-profiler-key' => $profiler_key,
143 'Accept-Language' => 'en-US,en;q=0.5',
144 'User-Agent' => $ua_signature
145 ]
146 ];
147
148 // Forward basic authentication if any (not available from WP CLI)
149 if ( function_exists('apache_request_headers') ) {
150 $apache_headers = apache_request_headers();
151 if ( isset( $apache_headers['Authorization'] ) ) {
152 $headers['headers']['Authorization'] = $apache_headers['Authorization'];
153 }
154 // WP-CLI (wp code-profiler run --u=FOO --p=BAR)
155 } elseif ( defined('WP_CLI') && ! empty( $_POST['Authorization'] ) ) {
156 $headers['headers']['Authorization'] = $_POST['Authorization'];
157 }
158
159 if ( $_POST['user'] == 'authenticated') {
160 // Used for authentication
161 if ( is_ssl() ) {
162 $cookie_auth = SECURE_AUTH_COOKIE;
163 $scheme = 'secure_auth';
164 } else {
165 $cookie_auth = AUTH_COOKIE;
166 $scheme = 'auth';
167 }
168
169 // Retrieve the user name (since 1.4.3)
170 if (! defined('WP_CLI') ) {
171 if ( empty( $_POST['username'] ) ) {
172 $msg = esc_html__('Missing authenticated username', 'code-profiler');
173 $response['message'] = $msg;
174 code_profiler_log_error( $msg );
175 code_profiler_wp_send_json( $response );
176 }
177 $username = sanitize_user( $_POST['username'] );
178 $user_object = get_user_by('login', $username );
179 if ( $user_object === false ) {
180 $msg = sprintf( esc_html__('User [%s] does not exist.', 'code-profiler'), $username);
181 $response['message'] = $msg;
182 code_profiler_log_error( $msg );
183 code_profiler_wp_send_json( $response );
184 }
185 $cp_options['mem_username'] = strtolower( $username );
186 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( $user_object->ID, time() + 180, $scheme );
187 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $user_object->ID, time() + 180, 'logged_in');
188 // WP CLI
189 } else {
190 $id = get_current_user_id();
191 $headers['cookies'][ $cookie_auth ] = wp_generate_auth_cookie( $id, time() + 180, $scheme );
192 $headers['cookies'][ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( $id, time() + 180, 'logged_in');
193 }
194 $session_id = session_id();
195 if ( $session_id !== false ) {
196 $session_name = session_name();
197 $headers['cookies'][ $session_name ] = $session_id;
198 }
199 }
200
201 if ( function_exists('opcache_reset') ) {
202 opcache_reset();
203 }
204
205 // GET or POST method
206 if (! empty( $_POST['method'] ) && $_POST['method'] == 'post') {
207 $safe_method = 'wp_safe_remote_post';
208 $cp_options['mem_method'] = 'post';
209
210 // Optional POST payload
211 if (! empty( $_POST['payload'] ) ) {
212 $payload_array = explode( PHP_EOL, trim( $_POST['payload'] ) );
213 foreach( $payload_array as $item ) {
214 $payload = explode('=', trim( $item ), 2 );
215 if ( isset( $payload[1] ) ) {
216 $payload[0] = trim( $payload[0] );
217 $payload[1] = trim( $payload[1] );
218 $headers['body'][ $payload[0] ] = $payload[1];
219 }
220 }
221 $cp_options['payload'] = json_encode( $_POST['payload'] );
222 $cp_options['mem_payload'] = $_POST['payload'];
223 } else {
224 // POST request without a payload
225 unset( $cp_options['payload'] );
226 $cp_options['mem_payload'] = '';
227 }
228 } else {
229 $safe_method = 'wp_safe_remote_get';
230 $cp_options['mem_method'] = 'get';
231 }
232
233 // Optional user-defined cookies
234 if (! empty( $_POST['cookies'] ) ) {
235 $cookies_array = explode( PHP_EOL, trim( $_POST['cookies'] ) );
236 foreach( $cookies_array as $item ) {
237 $cookie = explode('=', trim( $item ), 2 );
238 if ( isset( $cookie[1] ) ) {
239 $cookie[0] = trim( $cookie[0] );
240 $cookie[1] = trim( $cookie[1] );
241 $headers['cookies'][ $cookie[0] ] = $cookie[1];
242 }
243 }
244 $cp_options['cookies'] = json_encode( $_POST['cookies'] );
245 } else {
246 unset( $cp_options['cookies'] );
247 }
248
249 update_option('code-profiler', $cp_options );
250
251 // We must allow developers to run the profiler
252 // on a local IP (e.g, http://127.0.0.1/)
253 add_filter('http_request_host_is_external', '__return_true');
254 $res = $safe_method( $url, $headers );
255
256 // Connection error
257 if ( is_wp_error( $res ) ) {
258 $msg = esc_html__('Cannot connect to the requested page: %s', 'code-profiler');
259 $response['message'] = sprintf(
260 $msg,
261 esc_html( $res->get_error_message() )
262 );
263 code_profiler_log_error( sprintf( $msg, $res->get_error_message() ) );
264 code_profiler_wp_send_json( $response );
265 }
266
267 // HTTP status code
268 if (! empty( $cp_options['http_response'] ) ) {
269 if ( preg_match( "/{$cp_options['http_response']}/", $res['response']['code'] ) ) {
270 $msg = esc_html__('The website returned the following HTTP status code: %s %s.', 'code-profiler').
271 ' '.
272 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');
273 $response['message'] = sprintf(
274 $msg,
275 (int) $res['response']['code'],
276 esc_html( $res['response']['message'] )
277 );
278 code_profiler_log_error( sprintf( $msg, $res['response']['code'], $res['response']['message'] ) );
279 code_profiler_wp_send_json( $response );
280 }
281 }
282 // Check response
283 $message = json_decode( $res['body'], true );
284 if ( isset( $message['status'] ) && isset( $message['message'] ) ) {
285 $response['status'] = $message['status'];
286 $response['message'] = $message['message'];
287 code_profiler_wp_send_json( $response );
288 }
289 code_profiler_log_info( esc_html__('Collecting data to analyze', 'code-profiler') );
290 // Return success
291 $response = ['status' => 'success'];
292 $response['message'] = 'success';
293 $response['microtime'] = $microtime;
294
295 // AJAX action?
296 if ( defined('DOING_AJAX') && DOING_AJAX ) {
297 code_profiler_wp_send_json( $response );
298 } else {
299 return json_encode( $response );
300 }
301
302 }
303
304 // =====================================================================
305
306 add_action('wp_ajax_codeprofiler_prepare_report', 'codeprofiler_prepare_report');
307
308 function codeprofiler_prepare_report() {
309
310 $response = ['status' => 'error'];
311
312 // If this is an AJAX call, make sure it comes from an admin/superadmin.
313 if ( isset( $_REQUEST['action'] ) && $_REQUEST['action'] == 'codeprofiler_prepare_report') {
314 // Admin/Superadmin only
315 if (! is_super_admin() ) {
316 $msg = esc_html__('You are not allowed to performed this action', 'code-profiler');
317 $response['message'] = $msg;
318 code_profiler_log_error( $msg );
319 code_profiler_wp_send_json( $response );
320 }
321 }
322
323 // Verify the security nonce
324 if ( empty( $_POST['cp_nonce'] ) || ! wp_verify_nonce( $_POST['cp_nonce'], 'start_profiler_nonce') ) {
325 $msg = esc_html__('Missing or wrong security nonce. Reload the page and try again', 'code-profiler');
326 $response['message'] = $msg;
327 code_profiler_log_error( $msg );
328 code_profiler_wp_send_json( $response );
329 }
330
331 if ( empty( $_POST['microtime'] ) || ! preg_match('/^\d{10}\.\d+$/', $_POST['microtime'] ) ) {
332 $msg = esc_html__('Missing parameter (microtime).', 'code-profiler');
333 $response['message'] = $msg;
334 code_profiler_log_error( $msg );
335 code_profiler_wp_send_json( $response );
336 }
337 $microtime = sanitize_text_field( $_POST['microtime'] );
338
339 $profile = sanitize_file_name( $_POST['profile'] );
340 if ( empty( $profile ) ) {
341 $msg = esc_html__('Missing profile name.', 'code-profiler');
342 $response['message'] = $msg;
343 code_profiler_log_error( $msg );
344 code_profiler_wp_send_json( $response );
345 }
346
347 code_profiler_log_info( esc_html__('Preparing report', 'code-profiler') );
348 require 'class-report.php';
349 $report = new CodeProfiler_Report( $profile, $microtime );
350 $report->prepare_report();
351
352 // Take a 1s break so that we can spot any potential error
353 // in the backend before AJAX refresh the page
354 usleep(1000000);
355
356 // Clear hash
357 $cp_options = get_option('code-profiler');
358 unset( $cp_options['hash'] );
359 update_option('code-profiler', $cp_options );
360
361 code_profiler_log_info( esc_html__('All done, exiting profiler', 'code-profiler') );
362 $response['cp_profile'] = $microtime;
363 $response['status'] = 'success';
364 $response['message'] = 'success';
365
366 // AJAX action?
367 if ( defined('DOING_AJAX') && DOING_AJAX ) {
368 code_profiler_wp_send_json( $response );
369 } else {
370 return json_encode( $response );
371 }
372
373 }
374
375 // =====================================================================
376 // EOF
377