PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.8.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.8.0
2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 All 78 releases
ablocks / includes / classes / abstract-request-handler.php

abstract-request-handler.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.8.0, at includes/classes/abstract-request-handler.php

506 lines 18.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ABlocks\Classes;
4
5 use Exception;
6 use stdClass;
7 use ABlocks\Classes\Exceptions\AblocksException;
8 use ABlocks\Helper;
9 use WP_Error;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 abstract class AbstractRequestHandler {
16 /**
17 * Default Nonce Action.
18 *
19 * @var string
20 */
21 protected string $nonce_action = 'ablocks_nonce';
22
23 /**
24 * Request namespace.
25 *
26 * @var string
27 */
28 protected $namespace = ABLOCKS_PLUGIN_SLUG;
29
30 /**
31 * Actions to handle.
32 *
33 * @var array
34 */
35 protected array $actions = array();
36
37 protected static string $current_wp_action;
38
39 protected ?bool $is_ajax = null;
40
41 protected ?bool $is_unauthenticated = null;
42
43 private array $safe_text_kses_rules = array(
44 'br' => true,
45 'img' => array(
46 'alt' => true,
47 'class' => true,
48 'src' => true,
49 'title' => true,
50 ),
51 'p' => array(
52 'class' => true,
53 ),
54 'span' => array(
55 'class' => true,
56 'title' => true,
57 ),
58 );
59
60 abstract public function __construct();
61
62 /**
63 * Run action hook.
64 *
65 * @return void
66 */
67 abstract public function dispatch_actions();
68
69 protected function is_ajax_request(): bool {
70 if ( null === $this->is_ajax ) {
71 $this->is_ajax = str_starts_with( static::$current_wp_action, 'wp_ajax_' );
72 }
73
74 return $this->is_ajax;
75 }
76
77 protected function is_unauthenticated_request(): bool {
78 if ( null === $this->is_unauthenticated ) {
79 $this->is_unauthenticated = ( str_starts_with( static::$current_wp_action, 'wp_ajax_' ) || str_starts_with( static::$current_wp_action, 'admin_post_' ) ) && str_contains( static::$current_wp_action, '_nopriv_' );
80 }
81
82 return $this->is_unauthenticated;
83 }
84
85 /**
86 * Handle action callback.
87 *
88 * @return void
89 */
90 final public function handle_request() {
91 try {
92 static::$current_wp_action = wp_unslash( current_action() );
93 // No caching.
94 nocache_headers();
95
96 $response = $this->prepare_response();
97 if ( $response && is_wp_error( $response ) ) {
98 $this->respond_error( $response );
99 }
100
101 $this->respond_success( $response );
102 } catch ( AblocksException $e ) {
103 $this->respond_error( $e->toWpError() );
104 } catch ( Exception $e ) {
105 $this->respond_error( new WP_Error( 'something-went-wrong', $e->getMessage(), [ 'code' => 500 ] ) );
106 }
107 }
108
109 /**
110 * Prepare error response.
111 *
112 * @param WP_Error $response
113 *
114 * @return void
115 */
116 protected function respond_error( WP_Error $response ) {
117 if ( $this->is_ajax_request() ) {
118 $data = $response->get_error_data();
119 wp_send_json_error( $response, $data['code'] ?? 400 );
120 } else {
121 wp_die( $response ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
122 }
123 }
124
125 /**
126 * Prepare success response.
127 *
128 * @param $response
129 *
130 * @return void
131 */
132 protected function respond_success( $response ) {
133 if ( $response ) {
134 if ( $this->is_ajax_request() ) {
135 wp_send_json_success( $response );
136 } elseif ( is_string( $response ) && Helper::is_valid_site_url( $response ) ) {
137 wp_safe_redirect( $response );
138 die(); // don't use wp_die...
139 } else {
140 // @XXX maybe another handler or just void.
141 wp_die( '', '', [ 'response' => null ] );
142 }
143 }
144 }
145
146 /**
147 * Prepare response for the request.
148 *
149 * @return WP_Error|array|stdClass|string
150 * @throws AblocksException
151 * @throws Exception
152 */
153 protected function prepare_response() {
154 $action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';
155 $action = explode( $this->namespace . '/', $action )[1];
156
157 if ( ! isset( $this->actions[ $action ] ) ) {
158 return new WP_Error(
159 'invalid_action',
160 __( 'Invalid action.', 'ablocks' ),
161 [
162 'status' => 400,
163 'title' => __( 'Invalid action.', 'ablocks' ),
164 ]
165 );
166 }
167
168 $details = $this->actions[ $action ];
169 $nonce = isset( $_REQUEST['security'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['security'] ) ) : '';
170
171 if ( empty( $nonce ) && isset( $_REQUEST['_wpnonce'] ) ) {
172 $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
173 }
174
175 if ( ! $nonce || ! wp_verify_nonce( $nonce, $this->nonce_action ) ) {
176 return new WP_Error(
177 'invalid_nonce',
178 __( 'Invalid nonce.', 'ablocks' ),
179 [
180 'status' => rest_authorization_required_code(),
181 'title' => __( 'Invalid nonce.', 'ablocks' ),
182 ]
183 );
184 }
185
186 $user_cap = ! empty( $details['capability'] ) ? (string) $details['capability'] : 'manage_options';
187 $allow_visitor = ! empty( $details['allow_visitor_action'] ) && (bool) $details['allow_visitor_action'];
188 $has_permission = $this->check_permission( $user_cap, $allow_visitor );
189
190 if ( is_wp_error( $has_permission ) ) {
191 return $has_permission;
192 }
193
194 if ( empty( $details['callback'] ) || ! is_callable( $details['callback'] ) ) {
195 return new WP_Error(
196 'not_implemented',
197 __( 'Requested method not implemented.', 'ablocks' ),
198 [
199 'status' => 501,
200 'title' => __( 'Not implemented!', 'ablocks' ),
201 ]
202 );
203 }
204
205 $fields = $details['fields'] ?? null;
206
207 $payload = [];
208
209 if ( is_array( $fields ) && ! empty( $fields ) ) {
210 foreach ( $fields as $key => $type ) {
211 if ( ! empty( $_REQUEST[ $key ] ) ) {
212 if ( is_array( $type ) ) {
213 foreach ( $type as $type_key => $type_value ) {
214 if ( ! empty( $_REQUEST[ $key ][ $type_key ] ) ) {
215 if ( is_array( $type_value ) ) {
216 foreach ( $type_value as $type_value_key => $type_value_value ) {
217 if ( ! empty( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) {
218 $decode3_type = null;
219
220 if ( str_contains( $type_value_value, '|' ) ) {
221 list( $decode3_type, $type_value_value ) = explode( '|', $type_value_value, 2 );
222 }
223
224 switch ( strtolower( $type_value_value ) ) {
225 case 'absint':
226 case 'id':
227 $payload[ $key ][ $type_key ][ $type_value_key ] = absint( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) );
228 break;
229 case 'int':
230 case 'integer':
231 $payload[ $key ][ $type_key ][ $type_value_key ] = intval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) );
232 break;
233 case 'double':
234 case 'float':
235 $payload[ $key ][ $type_key ][ $type_value_key ] = floatval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) );
236 break;
237 case 'url':
238 $payload[ $key ][ $type_key ][ $type_value_key ] = esc_url_raw( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
239 break;
240 case 'bool':
241 case 'boolean':
242 $payload[ $key ][ $type_key ][ $type_value_key ] = (bool) filter_var( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ), FILTER_VALIDATE_BOOLEAN );
243 break;
244 case 'post':
245 $payload[ $key ][ $type_key ][ $type_value_key ] = wp_kses_post( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
246 break;
247 case 'slug':
248 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_title( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
249 break;
250 case 'email':
251 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_email( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
252 break;
253 case 'user':
254 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_user( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
255 break;
256 case 'textarea':
257 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_textarea_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
258 break;
259 case 'text':
260 case 'string':
261 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
262 break;
263 case 'json':
264 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
265 $payload[ $key ][ $type_key ][ $type_value_key ] = Sanitizer::sanitize_json_form_data( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] );
266 break;
267 case 'hex_color':
268 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
269 break;
270 case 'hex_color_no_hash':
271 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
272 break;
273 case 'key':
274 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
275 break;
276 case 'safe_text':
277 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
278 $payload[ $key ][ $type_key ][ $type_value_key ] = wp_kses( force_balance_tags( stripslashes( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) ), $this->safe_text_kses_rules );
279 break;
280 default:
281 if ( is_array( $payload[ $key ][ $type_key ][ $type_value_key ] ) ) {
282 $payload[ $key ][ $type_key ][ $type_value_key ] = wp_kses_post_deep( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
283 } else {
284 $payload[ $key ][ $type_key ][ $type_value_key ] = wp_kses_post( trim( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
285 }
286 break;
287 }//end switch
288
289 if ( $decode3_type && ! empty( $payload[ $key ][ $type_key ][ $type_value_key ] ) ) {
290 $payload[ $key ] = $this->maybe_decode( $payload[ $key ][ $type_key ][ $type_value_key ], $decode3_type );
291 }
292 }//end if
293 }//end foreach
294 } else {
295 $decode2_type = null;
296
297 if ( str_contains( $type_value, '|' ) ) {
298 list( $decode2_type, $type_value ) = explode( '|', $type_value, 2 );
299 }
300
301 switch ( strtolower( $type_value ) ) {
302 case 'absint':
303 case 'id':
304 $payload[ $key ][ $type_key ] = absint( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
305 break;
306 case 'int':
307 case 'integer':
308 $payload[ $key ][ $type_key ] = intval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
309 break;
310 case 'double':
311 case 'float':
312 $payload[ $key ][ $type_key ] = floatval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
313 break;
314 case 'url':
315 $payload[ $key ][ $type_key ] = esc_url_raw( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
316 break;
317 case 'bool':
318 case 'boolean':
319 $payload[ $key ][ $type_key ] = (bool) filter_var( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ), FILTER_VALIDATE_BOOLEAN );
320 break;
321 case 'post':
322 $payload[ $key ][ $type_key ] = wp_kses_post( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
323 break;
324 case 'slug':
325 $payload[ $key ][ $type_key ] = sanitize_title( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
326 break;
327 case 'email':
328 $payload[ $key ][ $type_key ] = sanitize_email( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
329 break;
330 case 'user':
331 $payload[ $key ][ $type_key ] = sanitize_user( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
332 break;
333 case 'textarea':
334 $payload[ $key ][ $type_key ] = sanitize_textarea_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
335 break;
336 case 'text':
337 case 'string':
338 $payload[ $key ][ $type_key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
339 break;
340 case 'json':
341 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
342 $payload[ $key ][ $type_key ] = Sanitizer::sanitize_json_form_data( $_REQUEST[ $key ][ $type_key ] );
343 break;
344 case 'hex_color':
345 $payload[ $key ][ $type_key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
346 break;
347 case 'hex_color_no_hash':
348 $payload[ $key ][ $type_key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
349 break;
350 case 'key':
351 $payload[ $key ][ $type_key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
352 break;
353 case 'safe_text':
354 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
355 $payload[ $key ][ $type_key ] = wp_kses( force_balance_tags( stripslashes( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) ), $this->safe_text_kses_rules );
356 break;
357 default:
358 if ( is_array( $payload[ $key ][ $type_key ] ) ) {
359 $payload[ $key ][ $type_key ] = wp_kses_post_deep( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
360 } else {
361 $payload[ $key ][ $type_key ] = wp_kses_post( trim( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
362 }
363 break;
364 }//end switch
365
366 if ( $decode2_type && ! empty( $payload[ $key ][ $type_key ] ) ) {
367 $payload[ $key ] = $this->maybe_decode( $payload[ $key ][ $type_key ], $decode2_type );
368 }
369 }//end if
370 }//end if
371 }//end foreach
372 } else {
373 $decode_type = null;
374
375 if ( str_contains( $type, '|' ) ) {
376 list( $decode_type, $type ) = explode( '|', $type, 2 );
377 }
378
379 switch ( strtolower( $type ) ) {
380 case 'absint':
381 case 'id':
382 $payload[ $key ] = absint( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
383 break;
384 case 'int':
385 case 'integer':
386 $payload[ $key ] = intval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
387 break;
388 case 'double':
389 case 'float':
390 $payload[ $key ] = floatval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
391 break;
392 case 'url':
393 $payload[ $key ] = esc_url_raw( wp_unslash( $_REQUEST[ $key ] ) );
394 break;
395 case 'bool':
396 case 'boolean':
397 $payload[ $key ] = (bool) filter_var( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ), FILTER_VALIDATE_BOOLEAN );
398 break;
399 case 'post':
400 $payload[ $key ] = wp_kses_post( wp_unslash( $_REQUEST[ $key ] ) );
401 break;
402 case 'slug':
403 $payload[ $key ] = sanitize_title( wp_unslash( $_REQUEST[ $key ] ) );
404 break;
405 case 'email':
406 $payload[ $key ] = sanitize_email( wp_unslash( $_REQUEST[ $key ] ) );
407 break;
408 case 'user':
409 $payload[ $key ] = sanitize_user( wp_unslash( $_REQUEST[ $key ] ) );
410 break;
411 case 'textarea':
412 $payload[ $key ] = sanitize_textarea_field( wp_unslash( $_REQUEST[ $key ] ) );
413 break;
414 case 'text':
415 case 'string':
416 $payload[ $key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) );
417 break;
418 case 'hex_color':
419 $payload[ $key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ] ) );
420 break;
421 case 'hex_color_no_hash':
422 $payload[ $key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ] ) );
423 break;
424 case 'key':
425 $payload[ $key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ] ) );
426 break;
427 case 'safe_text':
428 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
429 $payload[ $key ] = wp_kses( force_balance_tags( stripslashes( wp_unslash( $_REQUEST[ $key ] ) ) ), $this->safe_text_kses_rules );
430 break;
431 case 'array-string':
432 $payload[ $key ] = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST[ $key ] ) );
433 break;
434 default:
435 if ( is_array( $_REQUEST[ $key ] ) ) {
436 $payload[ $key ] = wp_kses_post_deep( wp_unslash( $_REQUEST[ $key ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
437 } else {
438 $payload[ $key ] = wp_kses_post( trim( wp_unslash( $_REQUEST[ $key ] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
439 }
440 break;
441 }//end switch
442
443 if ( $decode_type && ! empty( $payload[ $key ] ) ) {
444 $payload[ $key ] = $this->maybe_decode( $payload[ $key ], $decode_type );
445 }
446 }//end if
447 }//end if
448 }//end foreach
449 }//end if
450
451 return $this->respond( $details['callback'], $payload );
452 }
453
454 protected function maybe_decode( $payload, $type ) {
455 if ( 'serialize' === $type || 'unserialize' === $type || 'php' === $type ) {
456 return maybe_unserialize( $payload );
457 }
458
459 if ( str_starts_with( $payload, '[' ) || str_starts_with( $payload, '{' ) ) {
460 if ( 'array' === $type ) {
461 return json_decode( $payload, true );
462 }
463
464 return json_decode( $payload );
465 }
466
467 return $payload;
468 }
469
470 /**
471 * Run action callback.
472 *
473 * @param array|string $callback
474 * @param array $payload
475 *
476 * @return WP_Error|array|stdClass|string
477 *
478 * @throws StoreEngineException
479 * @throws Exception
480 */
481 final protected function respond( $callback, array $payload ) {
482 return call_user_func( $callback, $payload );
483 }
484
485 /**
486 * @param string $capability
487 * @param bool $allow_visitors
488 *
489 * @return WP_Error|true
490 */
491 protected function check_permission( string $capability, bool $allow_visitors = false ) {
492 if ( ( ! is_user_logged_in() && ! $allow_visitors ) || ( is_user_logged_in() && $capability && ! current_user_can( $capability ) ) ) {
493 return new WP_Error(
494 'forbidden_action',
495 __( 'You do not have permission to access this page.', 'ablocks' ),
496 [
497 'status' => rest_authorization_required_code(),
498 'title' => __( 'Insufficient permission!', 'ablocks' ),
499 ]
500 );
501 }
502
503 return true;
504 }
505 }
506