PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.3.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.3.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
sureforms / inc / duplicate-form.php

duplicate-form.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.3.0, at inc/duplicate-form.php

268 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sureforms form duplication.
4 *
5 * @package sureforms.
6 * @since 2.3.0
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 * Duplicate Form Class.
19 *
20 * @since 2.3.0
21 */
22 class Duplicate_Form {
23 use Get_Instance;
24
25 /**
26 * Duplicate a form with all its metadata
27 *
28 * @param int $form_id Form ID to duplicate.
29 * @param string $title_suffix Suffix to append to title. Default ' (Copy)'.
30 * @return array<string, mixed>|\WP_Error Result with new form ID or error.
31 * @since 2.3.0
32 */
33 public function duplicate_form( $form_id, $title_suffix = ' (Copy)' ) {
34 // Validate form ID.
35 $form_id = intval( $form_id );
36 if ( $form_id <= 0 ) {
37 return new \WP_Error(
38 'invalid_form_id',
39 __( 'Invalid form ID provided.', 'sureforms' ),
40 [ 'status' => 400 ]
41 );
42 }
43
44 // Get source form.
45 $source_form = get_post( $form_id );
46
47 if ( ! $source_form ) {
48 return new \WP_Error(
49 'form_not_found',
50 __( 'Source form not found.', 'sureforms' ),
51 [ 'status' => 404 ]
52 );
53 }
54
55 // Verify it's a sureforms_form post type.
56 if ( SRFM_FORMS_POST_TYPE !== $source_form->post_type ) {
57 return new \WP_Error(
58 'invalid_post_type',
59 __( 'The specified post is not a SureForms form.', 'sureforms' ),
60 [ 'status' => 400 ]
61 );
62 }
63
64 // Get all post meta.
65 $post_meta = get_post_meta( $form_id );
66
67 // Create new form title with suffix.
68 $new_title = $this->generate_unique_title( $source_form->post_title, $title_suffix );
69
70 // Prepare new post data.
71 $new_post_args = [
72 'post_title' => $new_title,
73 'post_content' => $source_form->post_content,
74 'post_status' => 'draft', // Always create as draft for safety.
75 'post_type' => SRFM_FORMS_POST_TYPE,
76 'post_author' => get_current_user_id(), // Use current user as author.
77 ];
78
79 // Create the new post.
80 $new_form_id_or_error = wp_insert_post( $new_post_args );
81
82 // Check for WP_Error or invalid post ID.
83 if ( ! is_int( $new_form_id_or_error ) || $new_form_id_or_error <= 0 ) {
84 return new \WP_Error(
85 'duplication_failed',
86 __( 'Failed to create duplicate form.', 'sureforms' ),
87 [ 'status' => 500 ]
88 );
89 }
90
91 // At this point, we're certain $new_form_id_or_error is a valid post ID (int).
92 $new_form_id = Helper::get_integer_value( $new_form_id_or_error );
93
94 // Update formId in Gutenberg blocks.
95 $updated_content = $this->update_block_form_ids( $source_form->post_content, $form_id, $new_form_id );
96
97 // Update the post content with new formId.
98 wp_update_post(
99 [
100 'ID' => $new_form_id,
101 'post_content' => $updated_content,
102 ]
103 );
104
105 // Get list of unserialized meta keys.
106 $unserialized_metas = $this->get_unserialized_post_metas();
107
108 // Copy all post meta.
109 // Ensure $post_meta is an array before iterating.
110 if ( is_array( $post_meta ) ) {
111 foreach ( $post_meta as $meta_key => $meta_values ) {
112 // Ensure meta_key is a string.
113 if ( ! is_string( $meta_key ) ) {
114 continue;
115 }
116
117 // Skip WordPress internal meta keys.
118 if ( '_edit_lock' === $meta_key || '_edit_last' === $meta_key ) {
119 continue;
120 }
121
122 // Handle unserialized metas (these are already arrays/objects).
123 if ( in_array( $meta_key, $unserialized_metas, true ) ) {
124 if ( is_array( $meta_values ) && isset( $meta_values[0] ) ) {
125 // Ensure the value is a string before unserializing.
126 $first_value = $meta_values[0];
127 if ( is_string( $first_value ) ) {
128 $meta_value = maybe_unserialize( $first_value );
129 add_post_meta( $new_form_id, $meta_key, $meta_value );
130 }
131 }
132 } else {
133 // Handle serialized metas (get first value).
134 if ( is_array( $meta_values ) && isset( $meta_values[0] ) ) {
135 add_post_meta( $new_form_id, $meta_key, $meta_values[0] );
136 }
137 }
138 }
139 }
140
141 // Allow other plugins to hook after duplication.
142 do_action( 'srfm_after_form_duplicated', $new_form_id, $form_id );
143
144 // Get edit URL for the new form.
145 $edit_url = admin_url( 'admin.php?page=sureforms_form_editor&post=' . $new_form_id );
146
147 // Return success response.
148 return [
149 'success' => true,
150 'original_form_id' => $form_id,
151 'new_form_id' => $new_form_id,
152 'new_form_title' => $new_title,
153 'edit_url' => $edit_url,
154 ];
155 }
156
157 /**
158 * Handle duplicate form REST API request
159 *
160 * Infrastructure through the permission_callback.
161 *
162 * @param \WP_REST_Request $request Full details about the request.
163 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
164 * @since 2.3.0
165 */
166 public function handle_duplicate_form_rest( $request ) {
167 $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
168
169 if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
170 return new \WP_Error(
171 'invalid_nonce',
172 __( 'Nonce verification failed.', 'sureforms' ),
173 [ 'status' => 403 ]
174 );
175 }
176
177 $form_id = absint( $request->get_param( 'form_id' ) );
178 $title_suffix = sanitize_text_field( $request->get_param( 'title_suffix' ) );
179
180 // Duplicate the form.
181 $result = $this->duplicate_form( $form_id, $title_suffix );
182
183 if ( is_wp_error( $result ) ) {
184 return $result;
185 }
186
187 return new \WP_REST_Response( $result, 200 );
188 }
189
190 /**
191 * Generate unique title by appending suffix
192 *
193 * If a form with the same title already exists, append a number.
194 *
195 * @param string $base_title Original form title.
196 * @param string $suffix Suffix to append. Default ' (Copy)'.
197 * @return string Unique title.
198 * @since 2.3.0
199 */
200 private function generate_unique_title( $base_title, $suffix = ' (Copy)' ) {
201 $new_title = $base_title . $suffix;
202 $counter = 2;
203
204 // Check if a form with this title exists.
205 while ( $this->title_exists( $new_title ) ) {
206 $new_title = $base_title . $suffix . ' ' . $counter;
207 ++$counter;
208 }
209
210 return $new_title;
211 }
212
213 /**
214 * Check if a form title already exists
215 *
216 * @param string $title Title to check.
217 * @return bool True if title exists, false otherwise.
218 * @since 2.3.0
219 */
220 private function title_exists( $title ) {
221 global $wpdb;
222
223 $query = $wpdb->prepare(
224 "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'sureforms_form' AND post_status != 'trash' LIMIT 1",
225 $title
226 );
227
228 $existing = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
229
230 return ! empty( $existing );
231 }
232
233 /**
234 * Update formId in Gutenberg blocks
235 *
236 * Replaces the old form ID with new form ID in the block markup.
237 * This function performs a direct string replacement on the post content
238 * to update formId references in Gutenberg block attributes.
239 *
240 * @param string $content Post content with blocks.
241 * @param int $old_id Original form ID.
242 * @param int $new_id New form ID.
243 * @return string Updated content with new form ID references.
244 * @since 2.3.0
245 */
246 private function update_block_form_ids( $content, $old_id, $new_id ) {
247 // Direct string replacement - no escaping needed for this operation.
248 return str_replace(
249 '"formId":' . intval( $old_id ),
250 '"formId":' . intval( $new_id ),
251 $content
252 );
253 }
254
255 /**
256 * Get list of unserialized post meta keys
257 *
258 * These meta keys are already arrays/objects and don't need double unserializing.
259 *
260 * @return array<string> Array of meta keys.
261 * @since 2.3.0
262 */
263 private function get_unserialized_post_metas() {
264 $export_instance = Export::get_instance();
265 return $export_instance->get_unserialized_post_metas();
266 }
267 }
268