PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.7
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.7
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 0.0.2 All 97 releases
sureforms / inc / export.php

export.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.7, at inc/export.php

397 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sureforms export.
4 *
5 * @package sureforms.
6 * @since 0.0.1
7 */
8
9 namespace SRFM\Inc;
10
11 use SRFM\Inc\Traits\Get_Instance;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Load Defaults Class.
19 *
20 * @since 0.0.1
21 */
22 class Export {
23 use Get_Instance;
24
25 /**
26 * Unserialized post metas.
27 *
28 * @var array<string>
29 */
30 public $unserialized_post_metas = [
31 '_srfm_conditional_logic',
32 '_srfm_email_notification',
33 '_srfm_form_confirmation',
34 '_srfm_compliance',
35 '_srfm_forms_styling',
36 '_srfm_integrations_webhooks',
37 '_srfm_instant_form_settings',
38 '_srfm_page_break_settings',
39 '_srfm_conversational_form',
40 '_srfm_premium_common',
41 '_srfm_forms_styling_starter',
42 '_srfm_user_registration_settings',
43 ];
44
45 /**
46 * Constructor
47 *
48 * @since 0.0.1
49 */
50 public function __construct() {
51 // Modern REST API endpoints are registered in rest-api.php.
52 }
53
54 /**
55 * Get unserialized post meta keys.
56 *
57 * Retrieves the list of post meta keys that need to be unserialized during export.
58 * Allows filtering of meta keys via 'srfm_export_and_import_post_meta_keys' filter.
59 *
60 * @since 1.9.0
61 * @return array<string> Array of post meta keys to unserialize.
62 */
63 public function get_unserialized_post_metas() {
64 return Helper::apply_filters_as_array( 'srfm_export_and_import_post_meta_keys', $this->unserialized_post_metas );
65 }
66
67 /**
68 * Get forms with meta by post IDs.
69 * Uses:
70 * - On websitedemos.net, for exporting the Spectra Block Patterns & Pages with SureForms form.
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.
75 */
76 public function get_forms_with_meta( $post_ids = [] ) {
77 $posts = [];
78
79 foreach ( $post_ids as $post_id ) {
80 $post_id = intval( $post_id );
81 $post = get_post( $post_id );
82 $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[] = [
92 'post' => $post,
93 'post_meta' => $post_meta,
94 ];
95 }
96
97 // Unserialize the post metas that are serialized.
98 // This is needed because the post metas are serialized before saving.
99 foreach ( $posts as $key => $post ) {
100 $post_metas = isset( $post['post_meta'] ) && is_array( $post['post_meta'] ) ? $post['post_meta'] : [];
101
102 foreach ( $this->get_unserialized_post_metas() as $meta_key ) {
103 if ( isset( $post_metas[ $meta_key ] ) && is_array( $post_metas[ $meta_key ] ) ) {
104 $post_metas[ $meta_key ] = maybe_unserialize( $post_metas[ $meta_key ][0] );
105 }
106 }
107 $posts[ $key ]['post_meta'] = $post_metas;
108 }
109
110 return $posts;
111 }
112
113 /**
114 * Handle Export form via REST API
115 *
116 * @param \WP_REST_Request $request Full details about the request.
117 * @since 2.0.0
118 * @return \WP_REST_Response|\WP_Error
119 */
120 public function handle_export_form_rest( $request ) {
121 $nonce = sanitize_text_field( Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ) );
122
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 }
130
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 ]
157 );
158 }
159
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 ]
186 );
187 }
188
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 );
201 }
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 }
212 }
213
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 // sanitize the data before saving.
245 $old_id = intval( $form_data['post']['ID'] );
246 $post_content = wp_kses_post( $form_data['post']['post_content'] );
247 $post_title = sanitize_text_field( $form_data['post']['post_title'] );
248 $post_meta = $form_data['post_meta'];
249 $post_type = sanitize_text_field( $form_data['post']['post_type'] );
250
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 }
263
264 $post_content = wp_slash( $post_content );
265
266 // Check if sureforms/form exists in post_content.
267 if ( 'sureforms_form' === $post_type ) {
268 $new_post = [
269 'post_title' => $post_title,
270 'post_status' => $default_status,
271 'post_type' => 'sureforms_form',
272 ];
273
274 $post_id = wp_insert_post( $new_post );
275
276 // 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 );
282
283 // update the post content.
284 wp_update_post(
285 [
286 'ID' => $post_id,
287 'post_content' => $post_content,
288 ]
289 );
290
291 if ( ! $post_id ) {
292 return new \WP_Error( 'import_forms_failed', __( 'Unable to import form.', 'sureforms' ) );
293 }
294
295 $forms_mapping[ $old_id ] = $post_id;
296
297 // 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 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 ) );
321 } 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 );
327 }
328 add_post_meta( $post_id, $meta_key, wp_slash( $raw_value ) );
329 }
330 }
331 } else {
332 return new \WP_Error( 'import_forms_invalid_post_type', __( 'Unable to import form.', 'sureforms' ) );
333 }
334 }
335
336 return $forms_mapping;
337 }
338
339 /**
340 * Get the list of meta keys allowed during import.
341 *
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>
347 */
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 }
391 );
392
393 return array_merge( $this->get_unserialized_post_metas(), $scalar_metas );
394 }
395
396 }
397