PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
sureforms / inc / single-form-settings / form-settings-api.php

form-settings-api.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.8, at inc/single-form-settings/form-settings-api.php

194 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * SureForms single form settings - REST endpoint for saving scoped meta.
4 *
5 * @package sureforms
6 * @since 2.9.0
7 */
8
9 namespace SRFM\Inc\Single_Form_Settings;
10
11 use SRFM\Inc\Compatibility\Multilingual\String_Collector;
12 use SRFM\Inc\Helper;
13 use SRFM\Inc\Traits\Get_Instance;
14 use WP_Error;
15 use WP_REST_Request;
16 use WP_REST_Response;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Saves a scoped subset of `sureforms_form` post meta in one round-trip.
24 * Used by the form-settings dialog's per-tab Save button so a tab can
25 * persist only its own meta keys without dirtying meta the user didn't
26 * touch.
27 *
28 * @since 2.9.0
29 */
30 class Form_Settings_Api {
31 use Get_Instance;
32
33 /**
34 * Constructor.
35 *
36 * @since 2.9.0
37 */
38 public function __construct() {
39 add_filter( 'srfm_rest_api_endpoints', [ $this, 'register_endpoint' ] );
40 }
41
42 /**
43 * Register the form-settings endpoint via the existing route filter.
44 *
45 * @param array<string,array<string,mixed>> $endpoints Existing endpoints.
46 * @since 2.9.0
47 * @return array<string,array<string,mixed>>
48 */
49 public function register_endpoint( $endpoints ) {
50 $endpoints['form-settings'] = [
51 'methods' => 'POST',
52 'callback' => [ $this, 'save_form_settings' ],
53 'permission_callback' => [ $this, 'permission_check' ],
54 'args' => [
55 'post_id' => [
56 'required' => true,
57 'type' => 'integer',
58 'sanitize_callback' => 'absint',
59 'validate_callback' => static function ( $value ) {
60 return is_numeric( $value ) && (int) $value > 0;
61 },
62 ],
63 'meta_data' => [
64 'required' => true,
65 'type' => 'object',
66 'validate_callback' => static function ( $value ) {
67 return is_array( $value ) && ! empty( $value );
68 },
69 ],
70 ],
71 ];
72
73 return $endpoints;
74 }
75
76 /**
77 * Permission check for the endpoint. Verifies the REST nonce and
78 * the user's capability to edit the target form post.
79 *
80 * @param WP_REST_Request $request Request.
81 * @since 2.9.0
82 * @return bool|WP_Error
83 */
84 public function permission_check( $request ) {
85 $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
86 if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
87 return new WP_Error(
88 'srfm_invalid_nonce',
89 __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ),
90 [ 'status' => 403 ]
91 );
92 }
93
94 $post_id = absint( $request->get_param( 'post_id' ) );
95 // Belt-and-braces: `edit_post` on a `sureforms_form` resolves to
96 // `manage_options` today via the CPT's capability map, but a
97 // future change there shouldn't be able to silently widen this
98 // endpoint to lower roles. Require both explicitly.
99 if (
100 ! $post_id ||
101 ! current_user_can( 'manage_options' ) ||
102 ! current_user_can( 'edit_post', $post_id )
103 ) {
104 return new WP_Error(
105 'srfm_cannot_edit_post',
106 __( 'You do not have permission to edit this form.', 'sureforms' ),
107 [ 'status' => 403 ]
108 );
109 }
110
111 return true;
112 }
113
114 /**
115 * Save the supplied meta keys against the target form post.
116 *
117 * @param WP_REST_Request $request Request.
118 * @since 2.9.0
119 * @return WP_Error|WP_REST_Response
120 */
121 public function save_form_settings( $request ) {
122 $post_id = absint( $request->get_param( 'post_id' ) );
123 $meta_data = (array) $request->get_param( 'meta_data' );
124
125 $post = get_post( $post_id );
126 // Guard against ID=0 / non-existent post / wrong post type. The
127 // permission check already validates `edit_post` on the supplied
128 // ID, but a brand-new draft can resolve to a post with ID 0 if
129 // the caller raced ahead of autosave.
130 if ( ! $post || ! $post->ID || SRFM_FORMS_POST_TYPE !== $post->post_type ) {
131 return new WP_Error(
132 'srfm_invalid_form_id',
133 __( 'Invalid form id.', 'sureforms' ),
134 [ 'status' => 404 ]
135 );
136 }
137
138 // Allowlist incoming keys so the endpoint can't be used to write
139 // arbitrary meta on the form post even though the caller has
140 // `edit_post`. Pro extends this list via the
141 // `srfm_form_settings_allowed_meta_keys` filter.
142 $default_keys = [
143 '_srfm_email_notification',
144 '_srfm_form_confirmation',
145 '_srfm_captcha_security_type',
146 '_srfm_form_recaptcha',
147 '_srfm_form_custom_css',
148 '_srfm_form_restriction',
149 '_srfm_compliance',
150 ];
151 // Restrict the merged result to keys that match our prefix so a
152 // third-party filter callback can't enable writes to core meta
153 // like `_edit_lock` or `_thumbnail_id` through this endpoint.
154 $allowed_keys = array_values(
155 array_filter(
156 (array) apply_filters( 'srfm_form_settings_allowed_meta_keys', $default_keys ),
157 static function ( $key ) {
158 return is_string( $key ) && 0 === strpos( $key, '_srfm_' );
159 }
160 )
161 );
162
163 $saved = [];
164 foreach ( $meta_data as $meta_key => $value ) {
165 $meta_key = sanitize_key( $meta_key );
166 if ( '' === $meta_key || ! \in_array( $meta_key, $allowed_keys, true ) ) {
167 continue;
168 }
169
170 update_post_meta( $post_id, $meta_key, wp_slash( $value ) );
171
172 // Read back the persisted value so the client can re-baseline
173 // against whatever sanitize_callbacks returned.
174 $saved[ $meta_key ] = get_post_meta( $post_id, $meta_key, true );
175 }
176
177 // The settings dialog persists meta via update_post_meta() without firing
178 // save_post, so String_Collector::on_form_save() never runs for these edits.
179 // Re-collect explicitly so confirmation / notification / restriction strings
180 // are (re-)registered with the active multilingual provider. collect() is
181 // provider-gated and idempotent, and a no-op when no provider is active.
182 String_Collector::get_instance()->collect( $post_id );
183
184 return new WP_REST_Response(
185 [
186 'success' => true,
187 'message' => __( 'Form settings saved.', 'sureforms' ),
188 'meta' => $saved,
189 ],
190 200
191 );
192 }
193 }
194