PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.1
2.13.0 2.13.1 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 All 80 releases
← All changes | includes/classes/abstract-request-handler.php +505 -0 2.7.62.13.1 View file →
@@ -1,0 +1,505 @@
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 +}