PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.4
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.4
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / includes / class-wpo-ajax.php

class-wpo-ajax.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.5.4, at includes/class-wpo-ajax.php

461 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('Access denied.');
4
5 if (!class_exists('WPO_Ajax')) :
6
7 class WPO_Ajax {
8
9 private $nonce;
10
11 private $subaction;
12
13 private $data;
14
15 private $commands;
16
17 private $results;
18
19 const HEARTBEAT_INTERVAL = 15; // in seconds
20
21 /**
22 * Constructor
23 */
24 private function __construct() {
25 add_action('wp_ajax_wp_optimize_ajax', array($this, 'handle_ajax_requests'));
26 add_filter('wp_optimize_heartbeat_ajax', array($this, 'handle_heartbeat_requests'));
27 add_filter('wp_optimize_is_heartbeat_valid_ajax_command', array($this, 'is_heartbeat_command_valid'));
28 }
29
30 /**
31 * Check if a command is valid for this class
32 *
33 * @param string $command
34 * @return bool
35 */
36 public function is_heartbeat_command_valid($command) {
37 $this->set_heartbeat_subaction($command);
38 $this->set_commands();
39 return !$this->is_invalid_command();
40 }
41
42 /**
43 * Return singleton instance
44 *
45 * @return WPO_Ajax Returns WPO_Ajax object
46 */
47 public static function get_instance() {
48 static $instance = null;
49 if (null === $instance) {
50 $instance = new self();
51 }
52 return $instance;
53 }
54
55 /**
56 * Handles heartbeat requests
57 *
58 * @param string $action The action we want to run
59 * @return mixed
60 */
61 public function handle_heartbeat_requests($action) {
62 $this->set_heartbeat_subaction($action);
63
64 if (!$this->is_user_capable()) {
65 return wp_json_encode($this->send_user_capability_error_response(false));
66 }
67
68 if (is_multisite() && !current_user_can('manage_network_options')) {
69 if (!$this->is_valid_multisite_command()) {
70 return wp_json_encode($this->send_invalid_multisite_command_error_response(false));
71 }
72 }
73
74 $this->set_commands();
75 if ($this->is_invalid_command()) {
76 $this->add_invalid_command_error_log_entry();
77 $this->set_invalid_command_error_response();
78 } else {
79 $this->execute_command();
80 $this->maybe_fix_status_box_content();
81 $this->set_error_response_on_wp_error();
82 $this->maybe_set_results_as_null();
83 }
84
85 $this->json_encode_results();
86
87 $json_last_error = json_last_error();
88 if ($json_last_error) {
89 $this->set_error_response_on_json_encode_error($json_last_error);
90 }
91
92 return $this->results;
93 }
94
95 /**
96 * Handles ajax requests
97 *
98 * @return void
99 */
100 public function handle_ajax_requests() {
101 $this->set_nonce();
102 $this->set_subaction();
103 $this->set_data();
104
105 if (!$this->is_valid_request()) {
106 $this->send_security_check_failed_error_response();
107 }
108
109 if (!$this->is_user_capable()) {
110 $this->send_user_capability_error_response();
111 }
112
113 if (is_multisite() && !current_user_can('manage_network_options')) {
114 if (!$this->is_valid_multisite_command()) {
115 $this->send_invalid_multisite_command_error_response();
116 }
117 }
118
119 if ($this->is_subaction_a_dismissed_notice()) {
120 $this->handle_notice_dismissals();
121 } else {
122 $this->set_commands();
123 if ($this->is_invalid_command()) {
124 $this->add_invalid_command_error_log_entry();
125 $this->set_invalid_command_error_response();
126 } else {
127 $this->execute_command();
128 $this->maybe_fix_status_box_content();
129 $this->set_error_response_on_wp_error();
130 $this->maybe_set_results_as_null();
131 }
132 }
133
134 $this->json_encode_results();
135
136 $json_last_error = json_last_error();
137 if ($json_last_error) {
138 $this->set_error_response_on_json_encode_error($json_last_error);
139 }
140
141 echo $this->results; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output already escaped
142 die;
143 }
144
145 /**
146 * Sets nonce property value
147 */
148 private function set_nonce() {
149 $this->nonce = empty($_POST['nonce']) ? '' : sanitize_key(wp_unslash($_POST['nonce'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- is_valid_request() checks nonce
150 }
151
152 /**
153 * Sets subaction property value
154 */
155 private function set_subaction() {
156 $this->subaction = empty($_POST['subaction']) ? '' : sanitize_key(wp_unslash($_POST['subaction'])); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- is_valid_request() checks nonce
157 }
158
159 /**
160 * Sets heartbeat subaction property value
161 *
162 * @param string $action_name The name of the heartbeat action to run
163 */
164 private function set_heartbeat_subaction($action_name) {
165 $this->subaction = $action_name;
166 }
167
168 /**
169 * Sets data property value
170 */
171 private function set_data() {
172 $this->data = isset($_POST['data']) ? stripslashes_deep($_POST['data']) : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- is_valid_request() checks nonce, sanitization takes place later
173 }
174
175 /**
176 * Checks whether the request is valid or not
177 *
178 * @return bool
179 */
180 private function is_valid_request() {
181 return wp_verify_nonce($this->nonce, 'wp-optimize-ajax-nonce') && !empty($this->subaction);
182 }
183
184 /**
185 * Send security check failed error response to browser and die
186 */
187 private function send_security_check_failed_error_response() {
188 wp_send_json(array(
189 'result' => false,
190 'error_code' => 'security_check',
191 'error_message' => __('The security check failed; try refreshing the page.', 'wp-optimize')
192 ));
193 }
194
195
196 /**
197 * Checks whether current user capable of doing this action or not
198 *
199 * @return bool
200 */
201 private function is_user_capable() {
202 return current_user_can(WP_Optimize()->capability_required());
203 }
204
205 /**
206 * Send user capability check failed error response to browser and possibly die
207 *
208 * @param boolean $send - if true, then the response is output; otherwise, it is returned
209 */
210 private function send_user_capability_error_response($send = true) {
211 $error = array(
212 'result' => false,
213 'error_code' => 'security_check',
214 'error_message' => __('You are not allowed to run this command.', 'wp-optimize')
215 );
216
217 if ($send) {
218 wp_send_json($error);
219 } else {
220 return $error;
221 }
222 }
223
224 /**
225 * Checks whether subaction is a valid multisite command
226 *
227 * @return bool
228 */
229 private function is_valid_multisite_command() {
230 /**
231 * Filters the commands allowed to the sub site admins. Other commands are only available to network admin. Only used in a multisite context.
232 */
233 $allowed_multisite_commands = apply_filters('wpo_multisite_allowed_commands', array('check_server_status', 'compress_single_image', 'restore_single_image'));
234 return in_array($this->subaction, $allowed_multisite_commands);
235 }
236
237 /**
238 * Send invalid multisite command error response to browser and die
239 */
240 private function send_invalid_multisite_command_error_response($send = true) {
241 $error = array(
242 'result' => false,
243 'error_code' => 'update_failed',
244 'error_message' => __('Options can only be saved by network admin', 'wp-optimize')
245 );
246
247 if ($send) {
248 wp_send_json($error);
249 } else {
250 return $error;
251 }
252 }
253
254 /**
255 * Checks if subaction is a notice dismissal or not
256 *
257 * @return bool True for notice dismiss actions, false otherwise
258 */
259 private function is_subaction_a_dismissed_notice() {
260 $dismiss_actions = $this->get_dismiss_actions();
261 return in_array($this->subaction, $dismiss_actions);
262 }
263
264 /**
265 * Returns an array of notice dismiss action names
266 *
267 * @return array An array of notice dismiss actions
268 */
269 private function get_dismiss_actions() {
270 return array(
271 'dismiss_dash_notice_until',
272 'dismiss_season',
273 'dismiss_page_notice_until',
274 'dismiss_notice',
275 'dismiss_review_notice',
276 );
277 }
278
279 /**
280 * Handles notice dismissals
281 */
282 private function handle_notice_dismissals() {
283 $options = WP_Optimize()->get_options();
284 // Some commands that are available via AJAX only.
285 if (in_array($this->subaction, array('dismiss_dash_notice_until', 'dismiss_season'))) {
286 $options->update_option($this->subaction, (time() + 366 * 86400));
287 } elseif (in_array($this->subaction, array('dismiss_page_notice_until', 'dismiss_notice'))) {
288 $options->update_option($this->subaction, (time() + 84 * 86400));
289 } elseif ('dismiss_review_notice' === $this->subaction) {
290 if (empty($this->data['dismiss_forever'])) {
291 $options->update_option($this->subaction, time() + 84 * 86400);
292 } else {
293 $options->update_option($this->subaction, 100 * (365.25 * 86400));
294 }
295 }
296 }
297
298 /**
299 * Sets commands property value
300 */
301 private function set_commands() {
302 $this->commands = apply_filters('wpo_premium_ajax_commands', new WP_Optimize_Commands());
303
304 $minify_commands = $this->get_minify_commands();
305 if ($this->is_subaction_a_minify_command($minify_commands)) {
306 $this->commands = $minify_commands;
307 }
308
309 $cache_commands = $this->get_cache_commands();
310 if ($this->is_subaction_a_cache_command($cache_commands)) {
311 $this->commands = $cache_commands;
312 }
313 }
314
315 /**
316 * Gets minify commands
317 *
318 * @return WP_Optimize_Minify_Commands
319 */
320 private function get_minify_commands() {
321 return new WP_Optimize_Minify_Commands();
322 }
323
324 /**
325 * Gets cache commands
326 *
327 * @return WP_Optimize_Cache_Commands|WP_Optimize_Cache_Commands_Premium
328 */
329 private function get_cache_commands() {
330 if (WP_Optimize::is_premium()) {
331 $cache_commands = new WP_Optimize_Cache_Commands_Premium();
332 } else {
333 $cache_commands = new WP_Optimize_Cache_Commands();
334 }
335 return $cache_commands;
336 }
337
338 /**
339 * Checks if applied ajax command is a minify command or not
340 *
341 * @param WP_Optimize_Minify_Commands $minify_commands an instance of minify commands class
342 *
343 * @return bool Returns true if ajax command is a minify command, false otherwise
344 */
345 private function is_subaction_a_minify_command($minify_commands) {
346 return !is_callable(array($this->commands, $this->subaction)) && is_callable(array($minify_commands, $this->subaction));
347 }
348
349 /**
350 * Checks if applied ajax command is a cache command or not
351 *
352 * @param WP_Optimize_Cache_Commands|WP_Optimize_Cache_Commands_Premium $cache_commands an instance of cache commands
353 *
354 * @return bool Returns true if ajax command is a cache command, false otherwise
355 */
356 private function is_subaction_a_cache_command($cache_commands) {
357 return !is_callable(array($this->commands, $this->subaction)) && is_callable(array($cache_commands, $this->subaction));
358 }
359
360 /**
361 * Checks if applied ajax command is an invalid command or not
362 *
363 * @return bool Returns true if ajax command is an invalid command, false otherwise
364 */
365 private function is_invalid_command() {
366 return !is_callable(array($this->commands, $this->subaction));
367 }
368
369 /**
370 * Log an error message for invalid ajax command
371 */
372 private function add_invalid_command_error_log_entry() {
373 error_log("WP-Optimize: ajax_handler: no such command (" . $this->subaction . ")"); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Edge case, used for debugging
374 }
375
376 /**
377 * Set `results` property with error response array for invalid ajax command
378 *
379 * @return void
380 */
381 private function set_invalid_command_error_response() {
382 $this->results = array(
383 'result' => false,
384 'error_code' => 'command_not_found',
385 // translators: %s is an ajax command name
386 'error_message' => sprintf(__('The command "%s" was not found', 'wp-optimize'), $this->subaction)
387 );
388 }
389
390 /**
391 * Execute the ajax command
392 */
393 private function execute_command() {
394 $this->results = call_user_func(array($this->commands, $this->subaction), $this->data);
395 }
396
397 /**
398 * If status box content is present, fix it.
399 */
400 private function maybe_fix_status_box_content() {
401 // clean status box content, it broke json sometimes.
402 // Git commit wp-optimize/-/commit/c05686b39959b863f4e168af3fa54421c4870470
403 if (isset($this->results['status_box_contents'])) {
404 $this->results['status_box_contents'] = str_replace(array("\n", "\t"), '', $this->results['status_box_contents']);
405 }
406 }
407
408 /**
409 * Set `results` property with error message
410 */
411 private function set_error_response_on_wp_error() {
412 if (is_wp_error($this->results)) {
413 $this->results = array(
414 'result' => false,
415 'error_code' => $this->results->get_error_code(),
416 'error_message' => $this->results->get_error_message(),
417 'error_data' => $this->results->get_error_data(),
418 );
419 }
420 }
421
422 /**
423 * Set `results` property to null, if it is not yet set
424 */
425 private function maybe_set_results_as_null() {
426 // if nothing was returned for some reason, set as result null.
427 if (empty($this->results)) {
428 $this->results = array(
429 'result' => null
430 );
431 }
432 }
433
434 /**
435 * Sets `results` property with json encode error
436 *
437 * @param int $json_last_error
438 *
439 * @return void
440 */
441 private function set_error_response_on_json_encode_error($json_last_error) {
442 $this->results = array(
443 'result' => false,
444 'error_code' => $json_last_error,
445 'error_message' => 'json_encode error : ' . $json_last_error,
446 'error_data' => '',
447 );
448
449 $this->results = wp_json_encode($this->results);
450 }
451
452 /**
453 * Json encode the `results` property value
454 */
455 private function json_encode_results() {
456 $this->results = wp_json_encode($this->results);
457 }
458 }
459
460 endif;
461