PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 1.12.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v1.12.0
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 0.0.2 0.0.3 All 96 releases
← All changes | inc/export.php +90 -241 trunk1.12.0 View file →
@@ -8,8 +8,9 @@
8 8
9 9 namespace SRFM\Inc;
10 10
11 11 use SRFM\Inc\Traits\Get_Instance;
12 +use WP_REST_Server;
12 13
13 14 if ( ! defined( 'ABSPATH' ) ) {
14 15 exit; // Exit if accessed directly.
15 16 }
@@ -47,9 +48,10 @@
47 48 *
48 49 * @since 0.0.1
49 50 */
50 51 public function __construct() {
51 - // Modern REST API endpoints are registered in rest-api.php.
52 + add_action( 'wp_ajax_export_form', [ $this, 'handle_export_form' ] );
53 + add_action( 'rest_api_init', [ $this, 'register_custom_endpoint' ] );
52 54 }
53 55
54 56 /**
55 57 * Get unserialized post meta keys.
@@ -64,17 +66,38 @@
64 66 return Helper::apply_filters_as_array( 'srfm_export_and_import_post_meta_keys', $this->unserialized_post_metas );
65 67 }
66 68
67 69 /**
68 - * Get forms with meta by post IDs.
69 - * Uses:
70 - * - On websitedemos.net, for exporting the Spectra Block Patterns & Pages with SureForms form.
70 + * Handle Export form
71 71 *
72 - * @since 1.13.0
73 - * @param array<int,string>|array<int, int> $post_ids Array of post IDs to retrieve forms for.
74 - * @return array Array of forms with their post data and meta data.
72 + * @since 0.0.1
73 + * @return void
75 74 */
76 - public function get_forms_with_meta( $post_ids = [] ) {
75 + public function handle_export_form() {
76 + if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'export_form_nonce' ) ) {
77 + $error_message = __( 'Nonce verification failed.', 'sureforms' );
78 +
79 + $error_data = [
80 + 'error' => $error_message,
81 + ];
82 + wp_send_json_error( $error_data );
83 + }
84 +
85 + // check if the user has permission to export forms.
86 + if ( ! Helper::current_user_can( 'manage_options' ) ) {
87 + wp_send_json_error(
88 + [
89 + 'error' => __( 'You do not have permission to export forms.', 'sureforms' ),
90 + ]
91 + );
92 + }
93 +
94 + if ( isset( $_POST['post_id'] ) ) {
95 + $post_ids = explode( ',', sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) );
96 + } else {
97 + $post_ids = [];
98 + }
99 +
77 100 $posts = [];
78 101
79 102 foreach ( $post_ids as $post_id ) {
80 103 $post_id = intval( $post_id );
@@ -79,17 +102,9 @@
79 102 foreach ( $post_ids as $post_id ) {
80 103 $post_id = intval( $post_id );
81 104 $post = get_post( $post_id );
82 105 $post_meta = get_post_meta( $post_id );
83 -
84 - // The view counter belongs to this site's traffic, not to the form. These
85 - // payloads feed shared starter templates, so shipping it would hand every
86 - // importer a stranger's numbers. The import side already refuses the key,
87 - // so this is about not exporting it in the first place.
88 - if ( is_array( $post_meta ) ) {
89 - unset( $post_meta[ \SRFM\Inc\Form_Views::META_KEY ] );
90 - }
91 - $posts[] = [
106 + $posts[] = [
92 107 'post' => $post,
93 108 'post_meta' => $post_meta,
94 109 ];
95 110 }
@@ -106,169 +121,66 @@
106 121 }
107 122 $posts[ $key ]['post_meta'] = $post_metas;
108 123 }
109 124
110 - return $posts;
125 + wp_send_json( $posts );
111 126 }
112 127
113 128 /**
114 - * Handle Export form via REST API
129 + * Handle Import Form
115 130 *
116 131 * @param \WP_REST_Request $request Full details about the request.
117 - * @since 2.0.0
118 - * @return \WP_REST_Response|\WP_Error
132 + *
133 + * @since 0.0.1
134 + * @return void
119 135 */
120 - public function handle_export_form_rest( $request ) {
121 - $nonce = sanitize_text_field( Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ) );
136 + public function handle_import_form( $request ) {
122 137
123 - if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
124 - return new \WP_Error(
125 - 'invalid_nonce',
126 - __( 'Nonce verification failed.', 'sureforms' ),
127 - [ 'status' => 403 ]
128 - );
129 - }
138 + $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
130 139
131 - $params = $request->get_params();
132 - $post_ids = [];
133 -
134 - // Handle post_ids parameter - can be array or comma-separated string.
135 - if ( isset( $params['post_ids'] ) ) {
136 - if ( is_array( $params['post_ids'] ) ) {
137 - $post_ids = array_map( 'intval', $params['post_ids'] );
138 - } else {
139 - $post_ids = array_map( 'intval', explode( ',', sanitize_text_field( Helper::get_string_value( $params['post_ids'] ) ) ) );
140 - }
141 - }
142 -
143 - // Validate that all post IDs are valid sureforms_form posts.
144 - $validated_post_ids = [];
145 - foreach ( $post_ids as $post_id ) {
146 - $post = get_post( $post_id );
147 - if ( $post && 'sureforms_form' === $post->post_type ) {
148 - $validated_post_ids[] = $post_id;
149 - }
150 - }
151 -
152 - if ( empty( $validated_post_ids ) ) {
153 - return new \WP_Error(
154 - 'no_valid_forms',
155 - __( 'No valid forms found for export.', 'sureforms' ),
156 - [ 'status' => 400 ]
140 + if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
141 + wp_send_json_error(
142 + [
143 + 'data' => __( 'Nonce verification failed.', 'sureforms' ),
144 + 'status' => false,
145 + ]
157 146 );
158 147 }
159 148
160 - $posts = $this->get_forms_with_meta( $validated_post_ids );
161 -
162 - return new \WP_REST_Response(
163 - [
164 - 'success' => true,
165 - 'data' => $posts,
166 - 'count' => count( $posts ),
167 - ]
168 - );
169 - }
170 -
171 - /**
172 - * Handle Import form via REST API
173 - *
174 - * @param \WP_REST_Request $request Full details about the request.
175 - * @since 2.0.0
176 - * @return \WP_REST_Response|\WP_Error
177 - */
178 - public function handle_import_form_rest( $request ) {
179 - $nonce = sanitize_text_field( Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ) );
180 -
181 - if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
182 - return new \WP_Error(
183 - 'invalid_nonce',
184 - __( 'Nonce verification failed.', 'sureforms' ),
185 - [ 'status' => 403 ]
149 + if ( ! current_user_can( 'manage_options' ) ) {
150 + // Return error if user does not have permissions to manage options.
151 + wp_send_json_error(
152 + [
153 + 'data' => esc_html__( 'You do not have permissions to manage options.', 'sureforms' ),
154 + 'status' => false,
155 + ]
186 156 );
187 157 }
188 158
189 - $params = $request->get_params();
190 -
191 - // Get forms data from the request.
192 - $forms_data = isset( $params['forms_data'] ) && is_array( $params['forms_data'] ) ? $params['forms_data'] : [];
193 - $default_status = isset( $params['default_status'] ) ? sanitize_text_field( Helper::get_string_value( $params['default_status'] ) ) : 'draft';
194 -
195 - if ( empty( $forms_data ) ) {
196 - return new \WP_Error(
197 - 'no_forms_data',
198 - __( 'No forms data provided for import.', 'sureforms' ),
199 - [ 'status' => 400 ]
200 - );
159 + // Get the raw POST data.
160 + $post_data = file_get_contents( 'php://input' );
161 + if ( ! $post_data ) {
162 + wp_send_json_error( __( 'Failed to import form.', 'sureforms' ) );
201 163 }
202 -
203 - // Validate forms data structure.
204 - foreach ( $forms_data as $form_data ) {
205 - if ( ! is_array( $form_data ) || ! isset( $form_data['post'] ) || ! isset( $form_data['post_meta'] ) ) {
206 - return new \WP_Error(
207 - 'invalid_form_data',
208 - __( 'Invalid form data structure provided.', 'sureforms' ),
209 - [ 'status' => 400 ]
210 - );
211 - }
164 + $data = json_decode( $post_data, true );
165 + if ( ! is_iterable( $data ) ) {
166 + wp_send_json_error( __( 'Failed to import form.', 'sureforms' ) );
212 167 }
168 + foreach ( $data as $form_data ) {
213 169
214 - $result = $this->import_forms_with_meta( $forms_data, $default_status );
215 -
216 - if ( is_wp_error( $result ) ) {
217 - return $result;
218 - }
219 -
220 - return new \WP_REST_Response(
221 - [
222 - 'success' => true,
223 - 'message' => __( 'Forms imported successfully!', 'sureforms' ),
224 - 'forms_mapping' => $result,
225 - 'imported_count' => count( $result ),
226 - ]
227 - );
228 - }
229 -
230 - /**
231 - * Import Forms with Meta
232 - * Uses:
233 - * - In Design Library for importing the Spectra Block Patterns and Pages with SureForms form.
234 - *
235 - * @param array<array<array<string>>> $data Form data to import.
236 - * @param string $default_status Default post status for imported forms. Default is 'draft'.
237 - *
238 - * @since 1.13.0
239 - * @return array<int, int>|\WP_Error Returns mapping array on success, WP_Error on failure.
240 - */
241 - public function import_forms_with_meta( $data, $default_status = 'draft' ) {
242 - $forms_mapping = [];
243 - foreach ( $data as $form_data ) {
244 170 // sanitize the data before saving.
245 - $old_id = intval( $form_data['post']['ID'] );
246 171 $post_content = wp_kses_post( $form_data['post']['post_content'] );
247 172 $post_title = sanitize_text_field( $form_data['post']['post_title'] );
248 173 $post_meta = $form_data['post_meta'];
249 174 $post_type = sanitize_text_field( $form_data['post']['post_type'] );
250 175
251 - // Remove percent-encoded slugs from imported form content.
252 - // Non-Latin labels produce broken slugs like %e3%83%95%e3%83%aa
253 - // via sanitize_title(). Clearing them lets process_blocks()
254 - // regenerate clean block-name-based slugs on save.
255 - $cleaned_content = preg_replace(
256 - '/"slug":"(%[a-fA-F0-9]{2}[^"]*)"/',
257 - '"slug":""',
258 - $post_content
259 - );
260 - if ( is_string( $cleaned_content ) ) {
261 - $post_content = $cleaned_content;
262 - }
176 + $post_content = addslashes( $post_content );
263 177
264 - $post_content = wp_slash( $post_content );
265 -
266 178 // Check if sureforms/form exists in post_content.
267 179 if ( 'sureforms_form' === $post_type ) {
268 180 $new_post = [
269 181 'post_title' => $post_title,
270 - 'post_status' => $default_status,
182 + 'post_status' => 'draft',
271 183 'post_type' => 'sureforms_form',
272 184 ];
273 185
274 186 $post_id = wp_insert_post( $new_post );
@@ -273,13 +185,9 @@
273 185
274 186 $post_id = wp_insert_post( $new_post );
275 187
276 188 // Update the post content formId to the new post id.
277 - $post_content = str_replace(
278 - '\"formId\":' . intval( $form_data['post']['ID'] ),
279 - '\"formId\":' . intval( $post_id ),
280 - $post_content
281 - );
189 + $post_content = str_replace( '\"formId\":' . $form_data['post']['ID'], '\"formId\":' . $post_id, $post_content );
282 190
283 191 // update the post content.
284 192 wp_update_post(
285 193 [
@@ -288,109 +196,50 @@
288 196 ]
289 197 );
290 198
291 199 if ( ! $post_id ) {
292 - return new \WP_Error( 'import_forms_failed', __( 'Unable to import form.', 'sureforms' ) );
200 + http_response_code( 400 );
201 + wp_send_json_error( __( 'Failed to import form.', 'sureforms' ) );
293 202 }
294 -
295 - $forms_mapping[ $old_id ] = $post_id;
296 -
297 203 // Update post meta.
298 - $allowed_keys = $this->get_allowed_import_meta_keys();
299 - $unserialized_meta_keys = $this->get_unserialized_post_metas();
300 - $registered = get_registered_meta_keys( 'post', SRFM_FORMS_POST_TYPE );
301 204 foreach ( $post_meta as $meta_key => $meta_value ) {
302 - // 1. Whitelist check — skip unknown keys from crafted import files.
303 - if ( ! in_array( $meta_key, $allowed_keys, true ) ) {
304 - continue;
305 - }
306 -
307 - // Note: add_post_meta() internally runs wp_unslash() on the value before
308 - // invoking the registered sanitize_callback. Imported values are unslashed,
309 - // so without re-slashing, backslashes are stripped — corrupting JSON-string
310 - // metas (e.g. _srfm_save_resume, _srfm_conditional_confirmation) whose escaped
311 - // quotes (\") then fail json_decode() in their sanitizers, wiping the value to
312 - // an empty string. wp_slash() pre-escapes so wp_unslash() restores the original.
313 - if ( in_array( $meta_key, $unserialized_meta_keys, true ) ) {
314 - // Complex array metas — sanitize_callback registered via register_post_meta()
315 - // is automatically invoked by add_post_meta() → update_metadata() pipeline.
316 - // When Pro is inactive, some keys may lack a registered callback — apply fallback.
317 - if ( empty( $registered[ $meta_key ]['sanitize_callback'] ) ) {
318 - $meta_value = Helper::sanitize_by_type( $meta_value );
319 - }
320 - add_post_meta( $post_id, $meta_key, wp_slash( $meta_value ) );
205 + // Check if the meta key is one of the unserialized post metas then add it as is.
206 + if ( in_array( $meta_key, $this->get_unserialized_post_metas(), true ) ) {
207 + add_post_meta( $post_id, $meta_key, $meta_value );
321 208 } else {
322 - // Scalar metas — unwrap single-element arrays produced by get_post_meta().
323 - $raw_value = is_array( $meta_value ) && isset( $meta_value[0] ) ? $meta_value[0] : $meta_value;
324 - // Fallback sanitization — skip when a registered callback already handles it.
325 - if ( is_string( $raw_value ) && empty( $registered[ $meta_key ]['sanitize_callback'] ) ) {
326 - $raw_value = sanitize_text_field( $raw_value );
209 + if ( is_array( $meta_value ) && isset( $meta_value[0] ) ) {
210 + add_post_meta( $post_id, $meta_key, $meta_value[0] );
211 + } else {
212 + add_post_meta( $post_id, $meta_key, $meta_value );
327 213 }
328 - add_post_meta( $post_id, $meta_key, wp_slash( $raw_value ) );
329 214 }
330 215 }
331 216 } else {
332 - return new \WP_Error( 'import_forms_invalid_post_type', __( 'Unable to import form.', 'sureforms' ) );
217 + http_response_code( 400 );
218 + wp_send_json_error( __( 'Failed to import form.', 'sureforms' ) );
333 219 }
334 220 }
335 221
336 - return $forms_mapping;
222 + // Return the responses.
223 + wp_send_json_success();
337 224 }
338 225
339 226 /**
340 - * Get the list of meta keys allowed during import.
227 + * Add custom API Route submit-form
341 228 *
342 - * Only meta keys present in this list will be written to the DB during import.
343 - * Unknown keys from crafted import files are silently ignored.
344 - *
345 - * @since 2.8.0
346 - * @return array<string>
229 + * @return void
230 + * @since 0.0.1
347 231 */
348 - private function get_allowed_import_meta_keys(): array {
349 - $scalar_metas = [
350 - '_srfm_additional_classes',
351 - '_srfm_bg_color',
352 - '_srfm_bg_image',
353 - '_srfm_bg_type',
354 - '_srfm_button_border_radius',
355 - '_srfm_captcha_security_type',
356 - '_srfm_cover_image',
357 - '_srfm_form_container_width',
358 - '_srfm_form_custom_css',
359 - '_srfm_form_recaptcha',
360 - '_srfm_form_restriction',
361 - '_srfm_inherit_theme_button',
362 - '_srfm_instant_form',
363 - '_srfm_is_ai_generated',
364 - '_srfm_is_inline_button',
365 - '_srfm_single_page_form_title',
366 - '_srfm_submit_alignment',
367 - '_srfm_submit_alignment_backend',
368 - '_srfm_submit_button_text',
369 - '_srfm_submit_type',
370 - '_srfm_submit_width',
371 - '_srfm_submit_width_backend',
372 - '_srfm_use_label_as_placeholder',
373 - ];
374 -
375 - /**
376 - * Filter the list of scalar meta keys allowed during import.
377 - *
378 - * Pro and other extensions can hook into this to add their own scalar meta keys.
379 - *
380 - * @since 2.8.0
381 - * @param array<string> $scalar_metas List of scalar meta keys.
382 - */
383 - $scalar_metas = apply_filters( 'srfm_import_scalar_meta_keys', $scalar_metas );
384 -
385 - // Ensure filter consumers cannot inject non-SureForms meta keys.
386 - $scalar_metas = array_filter(
387 - $scalar_metas,
388 - static function ( $key ) {
389 - return str_starts_with( $key, '_srfm_' );
390 - }
232 + public function register_custom_endpoint() {
233 + register_rest_route(
234 + 'sureforms/v1',
235 + '/sureforms_import',
236 + [
237 + 'methods' => WP_REST_Server::EDITABLE,
238 + 'callback' => [ $this, 'handle_import_form' ],
239 + 'permission_callback' => static function () {
240 + return Helper::current_user_can( 'manage_options' );
241 + },
242 + ]
391 243 );
392 -
393 - return array_merge( $this->get_unserialized_post_metas(), $scalar_metas );
394 244 }
395 -
396 245 }