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

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