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/api/form-builder-controller.php +732 -0 2.7.72.13.1 View file →
@@ -1,0 +1,732 @@
1 +<?php
2 +
3 +namespace ABlocks\API;
4 +
5 +use WP_REST_Request;
6 +use WP_REST_Response;
7 +use WP_Error;
8 +use ABlocks\Helper;
9 +use ABlocks\Blocks\FormBuilder\ValidateFormData;
10 +
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
15 +class FormBuilderController {
16 +
17 + /**
18 + * Verify the REST request nonce to protect the public form endpoints
19 + * against CSRF.
20 + *
21 + * The frontend sends the standard WordPress REST nonce via the
22 + * `X-WP-Nonce` header (see `ABlocksGlobal.nonce`, generated with
23 + * `wp_create_nonce( 'wp_rest' )`).
24 + *
25 + * @param WP_REST_Request $request
26 + *
27 + * @return true|WP_REST_Response True when valid, error response otherwise.
28 + */
29 + private function verify_nonce( WP_REST_Request $request ) {
30 + $nonce = $request->get_header( 'X-WP-Nonce' );
31 +
32 + if ( empty( $nonce ) ) {
33 + $nonce = $request->get_param( 'security' );
34 + }
35 +
36 + if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
37 + return new WP_REST_Response(
38 + [
39 + 'success' => false,
40 + 'data' => [
41 + 'message' => __( 'Security check failed. Please reload the page and try again.', 'ablocks' ),
42 + ],
43 + ],
44 + 403
45 + );
46 + }
47 +
48 + return true;
49 + }
50 +
51 + public function register_routes() {
52 +
53 + register_rest_route(
54 + ABLOCKS_REST_NAMESPACE,
55 + '/form-builder/login',
56 + [
57 + 'methods' => 'POST',
58 + 'callback' => [ $this, 'login' ],
59 + 'permission_callback' => '__return_true',
60 + 'args' => $this->login_schema(),
61 + ]
62 + );
63 +
64 + register_rest_route(
65 + ABLOCKS_REST_NAMESPACE,
66 + '/form-builder/registration',
67 + [
68 + 'methods' => 'POST',
69 + 'callback' => [ $this, 'register' ],
70 + 'permission_callback' => '__return_true',
71 + 'args' => $this->register_schema(),
72 + ]
73 + );
74 +
75 + register_rest_route(
76 + ABLOCKS_REST_NAMESPACE,
77 + '/form-builder/forget_password',
78 + [
79 + 'methods' => 'POST',
80 + 'callback' => [ $this, 'forget_password' ],
81 + 'permission_callback' => '__return_true',
82 + 'args' => $this->forget_schema(),
83 + ]
84 + );
85 +
86 + register_rest_route(
87 + ABLOCKS_REST_NAMESPACE,
88 + '/form-builder/submit',
89 + [
90 + 'methods' => 'POST',
91 + 'callback' => [ $this, 'submit' ],
92 + 'permission_callback' => '__return_true',
93 + 'args' => $this->submit_schema(),
94 + ]
95 + );
96 +
97 + register_rest_route(
98 + ABLOCKS_REST_NAMESPACE,
99 + '/form-builder/subscription',
100 + [
101 + 'methods' => 'POST',
102 + 'callback' => [ $this, 'submit' ],
103 + 'permission_callback' => '__return_true',
104 + 'args' => $this->submit_schema(),
105 + ]
106 + );
107 + }
108 +
109 + private function prepare_res( array $data, array $block_data, string $redirect_url ) : array {
110 +
111 + $formType = $block_data['parentAttributes']['formType'] ?? '';
112 +
113 + if ( $formType === 'login' ) {
114 + $confirmationType = ( $block_data['parentAttributes']['loginRedirect'] ?? false )
115 + ? 'redirect'
116 + : 'success';
117 +
118 + } elseif ( $formType === 'registration' ) {
119 + $confirmationType = ( $block_data['parentAttributes']['registerRedirect'] ?? false )
120 + ? 'redirect'
121 + : 'success';
122 +
123 + } else {
124 + $confirmationType = $block_data['parentAttributes']['confirmationType'] ?? 'success';
125 + }
126 +
127 + return array_merge(
128 + $data,
129 + [
130 + 'afterFormSubmission' => $block_data['parentAttributes']['afterFormSubmission'] ?? 'reset',
131 + 'confirmationType' => $confirmationType,
132 + 'confirmationNotice' => $block_data['parentAttributes']['confirmationNotice']
133 + ?? $data['message']
134 + ?? __( 'Form successfully submitted!', 'ablocks' ),
135 + 'redirect_url' => esc_url( $redirect_url ),
136 + 'no_follow' => $block_data['parentAttributes']['link']['noFollow'] ?? '',
137 + 'link_target' => $block_data['parentAttributes']['link']['linkTarget'] ?? '',
138 + 'formType' => $formType,
139 + ]
140 + );
141 + }
142 +
143 + public function login( WP_REST_Request $request ) {
144 +
145 + $nonce_check = $this->verify_nonce( $request );
146 + if ( true !== $nonce_check ) {
147 + return $nonce_check;
148 + }
149 +
150 + $params = $request->get_params();
151 +
152 + $block_data = Helper::get_block_attributes(
153 + $params['current_post_id'],
154 + $params['block_id'],
155 + 'ablocks/form-builder'
156 + );
157 +
158 + $redirect_url = '';
159 +
160 + if ( $block_data['parentAttributes']['loginRedirect'] ?? false ) {
161 + $redirect_url =
162 + \ABlocks\Blocks\FormBuilder\Helper::merge_query_params(
163 + $block_data['parentAttributes']['link']['href'] ?? '',
164 + $block_data['parentAttributes']['link']['keyValue'] ?? '',
165 + true
166 + );
167 + }
168 +
169 + $user = wp_signon(
170 + [
171 + 'user_login' => $params['username'],
172 + 'user_password' => $params['password'],
173 + 'remember' => (bool) $params['rememberme'],
174 + ],
175 + is_ssl()
176 + );
177 +
178 + if ( is_wp_error( $user ) ) {
179 + return new WP_REST_Response(
180 + [
181 + 'success' => false,
182 + 'data' => $this->prepare_res(
183 + [ 'message' => $user->get_error_message() ],
184 + $block_data,
185 + $redirect_url
186 + ),
187 + ],
188 + 400
189 + );
190 + }
191 +
192 + wp_set_current_user( $user->ID );
193 +
194 + if ( empty( $redirect_url ) ) {
195 + $redirect_url = home_url( '/' );
196 + }
197 +
198 + return new WP_REST_Response(
199 + [
200 + 'success' => true,
201 + 'data' => $this->prepare_res(
202 + [
203 + 'message' => __( 'You have logged in successfully. Redirecting...', 'ablocks' ),
204 + ],
205 + $block_data,
206 + $redirect_url
207 + ),
208 + ],
209 + 200
210 + );
211 + }
212 +
213 + public function register( WP_REST_Request $request ) {
214 +
215 + $nonce_check = $this->verify_nonce( $request );
216 + if ( true !== $nonce_check ) {
217 + return $nonce_check;
218 + }
219 +
220 + $params = $request->get_params();
221 +
222 + $post_id = $params['current_post_id'];
223 +
224 + if ( is_numeric( $post_id ) &&
225 + ! current_user_can( 'edit_post', $post_id ) &&
226 + get_post_status( $post_id ) !== 'publish'
227 + ) {
228 + return new WP_REST_Response(
229 + [
230 + 'success' => false,
231 + 'data' => [ 'message' => __( 'Invalid post.', 'ablocks' ) ],
232 + ],
233 + 400
234 + );
235 + }
236 +
237 + $block_data = Helper::get_block_attributes(
238 + $post_id,
239 + $params['block_id'],
240 + 'ablocks/form-builder'
241 + );
242 +
243 + $redirect_url = '';
244 +
245 + if ( $block_data['parentAttributes']['registerRedirect'] ?? false ) {
246 + $redirect_url =
247 + \ABlocks\Blocks\FormBuilder\Helper::merge_query_params(
248 + $block_data['parentAttributes']['link']['href'] ?? '',
249 + $block_data['parentAttributes']['link']['keyValue'] ?? '',
250 + true
251 + );
252 + }
253 +
254 + if ( ! get_option( 'users_can_register' ) ) {
255 + return new WP_REST_Response(
256 + [
257 + 'success' => false,
258 + 'data' => $this->prepare_res(
259 + [ 'message' => __( 'User registration is turned off.', 'ablocks' ) ],
260 + $block_data,
261 + $redirect_url
262 + ),
263 + ],
264 + 403
265 + );
266 + }
267 +
268 + if ( ! empty( $params['confirm_password'] ) && $params['password'] !== $params['confirm_password'] ) {
269 + return new WP_REST_Response(
270 + [
271 + 'success' => false,
272 + 'data' => $this->prepare_res(
273 + [ 'message' => __( 'Passwords do not match.', 'ablocks' ) ],
274 + $block_data,
275 + $redirect_url
276 + ),
277 + ],
278 + 400
279 + );
280 + }
281 +
282 + if ( username_exists( $params['username'] ) ) {
283 + return new WP_REST_Response(
284 + [
285 + 'success' => false,
286 + 'data' => $this->prepare_res(
287 + [ 'message' => __( 'Username already exists.', 'ablocks' ) ],
288 + $block_data,
289 + $redirect_url
290 + ),
291 + ],
292 + 400
293 + );
294 + }
295 +
296 + if ( email_exists( $params['email'] ) ) {
297 + return new WP_REST_Response(
298 + [
299 + 'success' => false,
300 + 'data' => $this->prepare_res(
301 + [ 'message' => __( 'Email already exists.', 'ablocks' ) ],
302 + $block_data,
303 + $redirect_url
304 + ),
305 + ],
306 + 400
307 + );
308 + }
309 +
310 + if ( strlen( $params['password'] ) < 6 ) {
311 + return new WP_REST_Response(
312 + [
313 + 'success' => false,
314 + 'data' => $this->prepare_res(
315 + [ 'message' => __( 'Password must be at least 6 characters.', 'ablocks' ) ],
316 + $block_data,
317 + $redirect_url
318 + ),
319 + ],
320 + 400
321 + );
322 + }
323 +
324 + $user_id = wp_create_user(
325 + $params['username'],
326 + $params['password'],
327 + $params['email']
328 + );
329 +
330 + if ( is_wp_error( $user_id ) ) {
331 + return new WP_REST_Response(
332 + [
333 + 'success' => false,
334 + 'data' => $this->prepare_res(
335 + [ 'message' => $user_id->get_error_message() ],
336 + $block_data,
337 + $redirect_url
338 + ),
339 + ],
340 + 400
341 + );
342 + }
343 +
344 + // Assign role if defined
345 + $role = $block_data['parentAttributes']['roleSlug'] ?? '';
346 +
347 + if (
348 + ! empty( $role ) &&
349 + strtolower( $role ) !== 'default' &&
350 + array_key_exists( $role, wp_roles()->roles ) &&
351 + $this->is_safe_registration_role( $role )
352 + ) {
353 + ( new \WP_User( $user_id ) )->set_role( $role );
354 + }
355 +
356 + // Save custom fields
357 + $reserved = [ 'username', 'email', 'password', 'current_post_id', 'block_id', 'confirm_password' ];
358 + $custom = array_diff_key( $params, array_flip( $reserved ) );
359 +
360 + foreach ( $custom as $key => $value ) {
361 + update_user_meta(
362 + $user_id,
363 + 'ablocks_' . sanitize_key( $key ),
364 + sanitize_text_field( $value )
365 + );
366 + }
367 +
368 + wp_set_current_user( $user_id );
369 + wp_set_auth_cookie( $user_id );
370 +
371 + if ( empty( $redirect_url ) ) {
372 + $redirect_url = home_url( '/' );
373 + }
374 +
375 + return new WP_REST_Response(
376 + [
377 + 'success' => true,
378 + 'data' => $this->prepare_res(
379 + [
380 + 'message' => __( 'Registration completed successfully. Redirecting...', 'ablocks' ),
381 + ],
382 + $block_data,
383 + $redirect_url
384 + ),
385 + ],
386 + 201
387 + );
388 + }
389 +
390 + public function forget_password( WP_REST_Request $request ) {
391 +
392 + $nonce_check = $this->verify_nonce( $request );
393 + if ( true !== $nonce_check ) {
394 + return $nonce_check;
395 + }
396 +
397 + $params = $request->get_params();
398 +
399 + $block_data = Helper::get_block_attributes(
400 + $params['current_post_id'],
401 + $params['block_id'],
402 + 'ablocks/form-builder'
403 + );
404 +
405 + if ( empty( $block_data ) ) {
406 + return new WP_REST_Response(
407 + [
408 + 'success' => false,
409 + 'data' => [
410 + 'message' => __( 'Invalid form block.', 'ablocks' ),
411 + ],
412 + ],
413 + 400
414 + );
415 + }
416 +
417 + $redirect_url = '';
418 +
419 + if ( $block_data['parentAttributes']['registerRedirect'] ?? false ) {
420 + $redirect_url = \ABlocks\Blocks\FormBuilder\Helper::merge_query_params(
421 + $block_data['parentAttributes']['link']['href'] ?? '',
422 + $block_data['parentAttributes']['link']['keyValue'] ?? '',
423 + true
424 + );
425 + }
426 +
427 + if ( empty( $params['email'] ?? '' ) ) {
428 + return new WP_REST_Response(
429 + [
430 + 'success' => false,
431 + 'data' => $this->prepare_res(
432 + [ 'message' => __( 'Email field is required', 'ablocks' ) ],
433 + $block_data,
434 + $redirect_url
435 + ),
436 + ],
437 + 400
438 + );
439 + }
440 +
441 + if ( ! is_email( $params['email'] ) ) {
442 + return new WP_REST_Response(
443 + [
444 + 'success' => false,
445 + 'data' => $this->prepare_res(
446 + [ 'message' => __( 'Provide a valid email', 'ablocks' ) ],
447 + $block_data,
448 + $redirect_url
449 + ),
450 + ],
451 + 400
452 + );
453 + }
454 +
455 + // Generic response used whether or not the account exists, to avoid
456 + // leaking which emails are registered (user enumeration).
457 + $generic_response = new WP_REST_Response(
458 + [
459 + 'success' => true,
460 + 'data' => $this->prepare_res(
461 + [
462 + 'message' => __( 'If an account exists for that email, a password reset link has been sent.', 'ablocks' ),
463 + ],
464 + $block_data,
465 + $redirect_url
466 + ),
467 + ],
468 + 200
469 + );
470 +
471 + if ( ! email_exists( $params['email'] ) ) {
472 + return $generic_response;
473 + }
474 +
475 + // Ignore the result: a failure (e.g. an invalid user) must not reveal
476 + // account existence, so we still return the generic response.
477 + retrieve_password( $params['email'] );
478 +
479 + return $generic_response;
480 + }
481 +
482 +
483 + public function submit( WP_REST_Request $request ) {
484 +
485 + $nonce_check = $this->verify_nonce( $request );
486 + if ( true !== $nonce_check ) {
487 + return $nonce_check;
488 + }
489 +
490 + $params = $request->get_params();
491 +
492 + $block_data = Helper::get_block_attributes(
493 + $params['current_post_id'],
494 + $params['block_id'],
495 + 'ablocks/form-builder'
496 + );
497 +
498 + if ( empty( $block_data ) ) {
499 + return new WP_Error(
500 + 'invalid_block',
501 + __( 'Invalid form block.', 'ablocks' ),
502 + [ 'status' => 400 ]
503 + );
504 + }
505 +
506 + $fields_to_skip = [ 'current_post_id', 'block_id' ];
507 + $all_fields = array_diff_key( $params, array_flip( $fields_to_skip ) );
508 +
509 + $actions = apply_filters(
510 + 'ablocks/form_builder/actions',
511 + [
512 + \ABlocks\Blocks\FormBuilder\Actions\SendEmails::class,
513 + \ABlocks\Blocks\FormBuilder\Actions\SaveFormData::class,
514 + \ABlocks\Blocks\FormBuilder\Actions\SendEmail::class,
515 + \ABlocks\Blocks\FormBuilder\Actions\Subscribe::class,
516 + ]
517 + );
518 +
519 + $validate = new ValidateFormData( $block_data, $all_fields );
520 + $validate->actions( $actions );
521 +
522 + $output = $validate->get_output();
523 + $output['afterFormSubmission'] = $block_data['parentAttributes']['afterFormSubmission'] ?? 'reset';
524 + $output['confirmationType'] = $block_data['parentAttributes']['confirmationType'] ?? 'success';
525 + $output['formType'] = $block_data['parentAttributes']['formType'] ?? '';
526 + $output['redirect_url'] = $block_data['parentAttributes']['link']['href'] ?? '';
527 + $output['link_target'] = $block_data['parentAttributes']['link']['linkTarget'] ?? '';
528 + if ( $validate->has_error() ) {
529 + $output['message'] = $validate->get_error_message();
530 + wp_send_json_error( $output );
531 + } elseif ( $validate->has_message() ) {
532 + $output['confirmationNotice'] = $validate->apply_vars( $block_data['parentAttributes']['confirmationNotice'] ?? __( 'Form successfully submitted!', 'ablocks' ) );
533 + $output['message'] = $validate->get_message();
534 +
535 + /**
536 + * Fires after a form-builder submission has been validated and processed
537 + * successfully. Third-party automations (e.g. Zaplane) can hook this to
538 + * react to submissions.
539 + *
540 + * @param array $form_info { 'info' => [ type, postId, email, actions, config ], 'data' => [ field => [ 'value' => mixed ] ] }.
541 + * @param array $block_data Resolved form block attributes/inner blocks.
542 + * @param ValidateFormData $validate The validation object ( state_data holds submission_id ).
543 + */
544 + do_action( 'ablocks/form_builder/after_submission', $validate->form_info, $block_data, $validate );
545 +
546 + wp_send_json_success( $output );
547 + }
548 +
549 + wp_send_json_error( [ 'message' => __( 'Action is not defined.', 'ablocks' ) ] );
550 +
551 + if ( $validate->has_error() ) {
552 + return new WP_Error(
553 + 'form_error',
554 + $validate->get_error_message(),
555 + [ 'status' => 400 ]
556 + );
557 + }
558 +
559 + return new WP_REST_Response( $output, 200 );
560 + }
561 +
562 + private function login_schema() {
563 + return [
564 +
565 + 'username' => [
566 + 'required' => true,
567 + 'type' => 'string',
568 + 'minLength' => 3,
569 + 'maxLength' => 60,
570 + 'sanitize_callback' => 'sanitize_user',
571 + 'validate_callback' => function( $value ) {
572 + return validate_username( $value );
573 + },
574 + ],
575 +
576 + 'password' => [
577 + 'required' => true,
578 + 'type' => 'string',
579 + 'minLength' => 6,
580 + 'maxLength' => 128,
581 + ],
582 +
583 + 'rememberme' => [
584 + 'required' => false,
585 + 'type' => 'boolean',
586 + 'sanitize_callback' => 'rest_sanitize_boolean',
587 + ],
588 +
589 + 'current_post_id' => [
590 + 'required' => true,
591 + 'type' => 'integer',
592 + 'sanitize_callback' => 'absint',
593 + 'validate_callback' => function( $value ) {
594 + return $value > 0 && get_post( $value );
595 + },
596 + ],
597 +
598 + 'block_id' => [
599 + 'required' => true,
600 + 'type' => 'string',
601 + 'sanitize_callback' => 'sanitize_text_field',
602 + 'validate_callback' => function( $value ) {
603 + return preg_match( '/^[a-zA-Z0-9_\-]+$/', $value );
604 + },
605 + ],
606 + ];
607 + }
608 +
609 +
610 + private function register_schema() {
611 + return [
612 +
613 + 'username' => [
614 + 'required' => true,
615 + 'type' => 'string',
616 + 'minLength' => 3,
617 + 'maxLength' => 60,
618 + 'sanitize_callback' => 'sanitize_user',
619 + 'validate_callback' => function( $value ) {
620 + return validate_username( $value );
621 + },
622 + ],
623 +
624 + 'email' => [
625 + 'required' => true,
626 + 'type' => 'string',
627 + 'sanitize_callback' => 'sanitize_email',
628 + 'validate_callback' => function( $value ) {
629 + return is_email( $value );
630 + },
631 + ],
632 +
633 + 'password' => [
634 + 'required' => true,
635 + 'type' => 'string',
636 + 'minLength' => 6,
637 + 'maxLength' => 128,
638 + 'validate_callback' => function( $value ) {
639 + return strlen( $value ) >= 6;
640 + },
641 + ],
642 +
643 + 'current_post_id' => [
644 + 'required' => true,
645 + 'type' => 'integer',
646 + 'sanitize_callback' => 'absint',
647 + 'validate_callback' => function( $value ) {
648 + return $value > 0 && get_post( $value );
649 + },
650 + ],
651 +
652 + 'block_id' => [
653 + 'required' => true,
654 + 'type' => 'string',
655 + 'sanitize_callback' => 'sanitize_text_field',
656 + 'validate_callback' => function( $value ) {
657 + return preg_match( '/^[a-zA-Z0-9_\-]+$/', $value );
658 + },
659 + ],
660 + ];
661 + }
662 +
663 +
664 + private function forget_schema() {
665 + return [
666 + 'email' => [
667 + 'required' => true,
668 + 'type' => 'string',
669 + 'sanitize_callback' => 'sanitize_email',
670 + ],
671 + 'current_post_id' => [
672 + 'required' => true,
673 + 'type' => 'integer',
674 + 'sanitize_callback' => 'absint',
675 + ],
676 + 'block_id' => [
677 + 'required' => true,
678 + 'type' => 'string',
679 + 'sanitize_callback' => 'sanitize_text_field',
680 + ],
681 + ];
682 + }
683 +
684 +
685 + private function is_safe_registration_role( string $role ) : bool {
686 + $role_obj = get_role( $role );
687 + if ( ! $role_obj ) {
688 + return false;
689 + }
690 + $privileged_caps = [
691 + 'manage_options',
692 + 'edit_users',
693 + 'delete_users',
694 + 'create_users',
695 + 'promote_users',
696 + 'edit_theme_options',
697 + ];
698 + foreach ( $privileged_caps as $cap ) {
699 + if ( ! empty( $role_obj->capabilities[ $cap ] ) ) {
700 + return false;
701 + }
702 + }
703 +
704 + // The checks above read the role's stored capabilities, which never
705 + // include anything the permission map grants — those are added per
706 + // request and are invisible here. A role configured for the Site Editor
707 + // would sail through, so a self-registration form could hand a visitor
708 + // edit_theme_options. Ask the permission map directly.
709 + $bridged = \ABlocks\Permissions\Caps::native_bridge();
710 + $grants = \ABlocks\Permissions::get_role_grants( $role );
711 + if ( array_intersect( array_keys( $bridged ), $grants ) ) {
712 + return false;
713 + }
714 +
715 + return true;
716 + }
717 +
718 + private function submit_schema() {
719 + return [
720 + 'current_post_id' => [
721 + 'required' => true,
722 + 'type' => 'string',
723 + 'sanitize_callback' => 'sanitize_text_field',
724 + ],
725 + 'block_id' => [
726 + 'required' => true,
727 + 'type' => 'string',
728 + 'sanitize_callback' => 'sanitize_text_field',
729 + ],
730 + ];
731 + }
732 +}