PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.9.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.9.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
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.9.1, at includes/classes/abstract-request-handler.php

501 lines 17.8 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'] : '';
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 $payload[ $key ][ $type_key ][ $type_value_key ] = Sanitizer::sanitize_json_form_data( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] );
265 break;
266 case 'hex_color':
267 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
268 break;
269 case 'hex_color_no_hash':
270 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
271 break;
272 case 'key':
273 $payload[ $key ][ $type_key ][ $type_value_key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ][ $type_key ][ $type_value_key ] ) );
274 break;
275 case 'safe_text':
276 $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 );
277 break;
278 default:
279 if ( is_array( $payload[ $key ][ $type_key ][ $type_value_key ] ) ) {
280 $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
281 } else {
282 $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
283 }
284 break;
285 }//end switch
286
287 if ( $decode3_type && ! empty( $payload[ $key ][ $type_key ][ $type_value_key ] ) ) {
288 $payload[ $key ] = $this->maybe_decode( $payload[ $key ][ $type_key ][ $type_value_key ], $decode3_type );
289 }
290 }//end if
291 }//end foreach
292 } else {
293 $decode2_type = null;
294
295 if ( str_contains( $type_value, '|' ) ) {
296 list( $decode2_type, $type_value ) = explode( '|', $type_value, 2 );
297 }
298
299 switch ( strtolower( $type_value ) ) {
300 case 'absint':
301 case 'id':
302 $payload[ $key ][ $type_key ] = absint( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
303 break;
304 case 'int':
305 case 'integer':
306 $payload[ $key ][ $type_key ] = intval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
307 break;
308 case 'double':
309 case 'float':
310 $payload[ $key ][ $type_key ] = floatval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) );
311 break;
312 case 'url':
313 $payload[ $key ][ $type_key ] = esc_url_raw( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
314 break;
315 case 'bool':
316 case 'boolean':
317 $payload[ $key ][ $type_key ] = (bool) filter_var( sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ), FILTER_VALIDATE_BOOLEAN );
318 break;
319 case 'post':
320 $payload[ $key ][ $type_key ] = wp_kses_post( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
321 break;
322 case 'slug':
323 $payload[ $key ][ $type_key ] = sanitize_title( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
324 break;
325 case 'email':
326 $payload[ $key ][ $type_key ] = sanitize_email( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
327 break;
328 case 'user':
329 $payload[ $key ][ $type_key ] = sanitize_user( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
330 break;
331 case 'textarea':
332 $payload[ $key ][ $type_key ] = sanitize_textarea_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
333 break;
334 case 'text':
335 case 'string':
336 $payload[ $key ][ $type_key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
337 break;
338 case 'json':
339 $payload[ $key ][ $type_key ] = Sanitizer::sanitize_json_form_data( $_REQUEST[ $key ][ $type_key ] );
340 break;
341 case 'hex_color':
342 $payload[ $key ][ $type_key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
343 break;
344 case 'hex_color_no_hash':
345 $payload[ $key ][ $type_key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
346 break;
347 case 'key':
348 $payload[ $key ][ $type_key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) );
349 break;
350 case 'safe_text':
351 $payload[ $key ][ $type_key ] = wp_kses( force_balance_tags( stripslashes( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) ), $this->safe_text_kses_rules );
352 break;
353 default:
354 if ( is_array( $payload[ $key ][ $type_key ] ) ) {
355 $payload[ $key ][ $type_key ] = wp_kses_post_deep( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
356 } else {
357 $payload[ $key ][ $type_key ] = wp_kses_post( trim( wp_unslash( $_REQUEST[ $key ][ $type_key ] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
358 }
359 break;
360 }//end switch
361
362 if ( $decode2_type && ! empty( $payload[ $key ][ $type_key ] ) ) {
363 $payload[ $key ] = $this->maybe_decode( $payload[ $key ][ $type_key ], $decode2_type );
364 }
365 }//end if
366 }//end if
367 }//end foreach
368 } else {
369 $decode_type = null;
370
371 if ( str_contains( $type, '|' ) ) {
372 list( $decode_type, $type ) = explode( '|', $type, 2 );
373 }
374
375 switch ( strtolower( $type ) ) {
376 case 'absint':
377 case 'id':
378 $payload[ $key ] = absint( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
379 break;
380 case 'int':
381 case 'integer':
382 $payload[ $key ] = intval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
383 break;
384 case 'double':
385 case 'float':
386 $payload[ $key ] = floatval( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ) );
387 break;
388 case 'url':
389 $payload[ $key ] = esc_url_raw( wp_unslash( $_REQUEST[ $key ] ) );
390 break;
391 case 'bool':
392 case 'boolean':
393 $payload[ $key ] = (bool) filter_var( sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) ), FILTER_VALIDATE_BOOLEAN );
394 break;
395 case 'post':
396 $payload[ $key ] = wp_kses_post( wp_unslash( $_REQUEST[ $key ] ) );
397 break;
398 case 'slug':
399 $payload[ $key ] = sanitize_title( wp_unslash( $_REQUEST[ $key ] ) );
400 break;
401 case 'email':
402 $payload[ $key ] = sanitize_email( wp_unslash( $_REQUEST[ $key ] ) );
403 break;
404 case 'user':
405 $payload[ $key ] = sanitize_user( wp_unslash( $_REQUEST[ $key ] ) );
406 break;
407 case 'textarea':
408 $payload[ $key ] = sanitize_textarea_field( wp_unslash( $_REQUEST[ $key ] ) );
409 break;
410 case 'text':
411 case 'string':
412 $payload[ $key ] = sanitize_text_field( wp_unslash( $_REQUEST[ $key ] ) );
413 break;
414 case 'hex_color':
415 $payload[ $key ] = sanitize_hex_color( wp_unslash( $_REQUEST[ $key ] ) );
416 break;
417 case 'hex_color_no_hash':
418 $payload[ $key ] = sanitize_hex_color_no_hash( wp_unslash( $_REQUEST[ $key ] ) );
419 break;
420 case 'key':
421 $payload[ $key ] = sanitize_key( wp_unslash( $_REQUEST[ $key ] ) );
422 break;
423 case 'safe_text':
424 $payload[ $key ] = wp_kses( force_balance_tags( stripslashes( wp_unslash( $_REQUEST[ $key ] ) ) ), $this->safe_text_kses_rules );
425 break;
426 case 'array-string':
427 $payload[ $key ] = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST[ $key ] ) );
428 break;
429 default:
430 if ( is_array( $_REQUEST[ $key ] ) ) {
431 $payload[ $key ] = wp_kses_post_deep( wp_unslash( $_REQUEST[ $key ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
432 } else {
433 $payload[ $key ] = wp_kses_post( trim( wp_unslash( $_REQUEST[ $key ] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
434 }
435 break;
436 }//end switch
437
438 if ( $decode_type && ! empty( $payload[ $key ] ) ) {
439 $payload[ $key ] = $this->maybe_decode( $payload[ $key ], $decode_type );
440 }
441 }//end if
442 }//end if
443 }//end foreach
444 }//end if
445
446 return $this->respond( $details['callback'], $payload );
447 }
448
449 protected function maybe_decode( $payload, $type ) {
450 if ( 'serialize' === $type || 'unserialize' === $type || 'php' === $type ) {
451 return maybe_unserialize( $payload );
452 }
453
454 if ( str_starts_with( $payload, '[' ) || str_starts_with( $payload, '{' ) ) {
455 if ( 'array' === $type ) {
456 return json_decode( $payload, true );
457 }
458
459 return json_decode( $payload );
460 }
461
462 return $payload;
463 }
464
465 /**
466 * Run action callback.
467 *
468 * @param array|string $callback
469 * @param array $payload
470 *
471 * @return WP_Error|array|stdClass|string
472 *
473 * @throws StoreEngineException
474 * @throws Exception
475 */
476 final protected function respond( $callback, array $payload ) {
477 return call_user_func( $callback, $payload );
478 }
479
480 /**
481 * @param string $capability
482 * @param bool $allow_visitors
483 *
484 * @return WP_Error|true
485 */
486 protected function check_permission( string $capability, bool $allow_visitors = false ) {
487 if ( ( ! is_user_logged_in() && ! $allow_visitors ) || ( is_user_logged_in() && $capability && ! current_user_can( $capability ) ) ) {
488 return new WP_Error(
489 'forbidden_action',
490 __( 'You do not have permission to access this page.', 'ablocks' ),
491 [
492 'status' => rest_authorization_required_code(),
493 'title' => __( 'Insufficient permission!', 'ablocks' ),
494 ]
495 );
496 }
497
498 return true;
499 }
500 }
501