PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.8.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.8.2
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 / abilities / forms / update-form.php
update-form.php
423 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update Form Ability.
4 *
5 * @package sureforms
6 * @since 2.5.2
7 */
8
9 namespace SRFM\Inc\Abilities\Forms;
10
11 use SRFM\Inc\Abilities\Abstract_Ability;
12 use SRFM\Inc\AI_Form_Builder\Field_Mapping;
13 use SRFM\Inc\Create_New_Form;
14 use SRFM\Inc\Helper;
15 use WP_REST_Request;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Update_Form ability class.
23 *
24 * Updates an existing SureForms form's title, status, fields, and/or metadata.
25 *
26 * @since 2.5.2
27 */
28 class Update_Form extends Abstract_Ability {
29 use Form_Field_Schema;
30 use Form_Metadata;
31
32 /**
33 * Constructor.
34 *
35 * @since 2.5.2
36 */
37 public function __construct() {
38 $this->id = 'sureforms/update-form';
39 $this->label = __( 'Update SureForms Form', 'sureforms' );
40 $this->description = __( 'Update an existing SureForms form title, status (publish/draft/private/trash), fields, and/or metadata settings. Use status "trash" to trash a form, or change from "trash" to another status to restore it. Providing formFields replaces all existing fields.', 'sureforms' );
41 $this->capability = 'manage_options';
42 $this->gated = 'srfm_abilities_api_edit';
43 }
44
45 /**
46 * {@inheritDoc}
47 *
48 * @since 2.5.2
49 */
50 public function get_annotations() {
51 return [
52 'readonly' => false,
53 'destructive' => true,
54 'idempotent' => true,
55 'priority' => 2.0,
56 'openWorldHint' => false,
57 'instructions' => 'Confirm the changes with the user before executing. Setting status to trash moves the form out of production. Providing formFields replaces ALL existing fields.',
58 ];
59 }
60
61 /**
62 * {@inheritDoc}
63 *
64 * @since 2.5.2
65 */
66 public function get_input_schema() {
67 $field_properties = $this->get_form_field_schema();
68
69 return [
70 'type' => 'object',
71 'additionalProperties' => false,
72 'properties' => [
73 'form_id' => [
74 'type' => 'integer',
75 'description' => __( 'The ID of the form to update.', 'sureforms' ),
76 ],
77 'title' => [
78 'type' => 'string',
79 'description' => __( 'New title for the form.', 'sureforms' ),
80 ],
81 'status' => [
82 'type' => 'string',
83 'description' => __( 'New status for the form.', 'sureforms' ),
84 'enum' => [ 'publish', 'draft', 'private', 'trash' ],
85 ],
86 'formFields' => [
87 'type' => 'array',
88 'description' => __( 'Array of form field definitions. Providing this replaces all existing fields.', 'sureforms' ),
89 'items' => [
90 'type' => 'object',
91 'properties' => $field_properties,
92 'required' => [ 'label', 'fieldType' ],
93 ],
94 ],
95 'formMetaData' => [
96 'type' => 'object',
97 'description' => __( 'Optional form metadata including confirmation, compliance, and styling settings. Same schema as create-form.', 'sureforms' ),
98 'properties' => [
99 'formConfirmation' => [
100 'type' => 'object',
101 'properties' => [
102 'confirmationMessage' => [
103 'type' => 'string',
104 'description' => __( 'Message displayed after successful submission.', 'sureforms' ),
105 ],
106 ],
107 ],
108 'compliance' => [
109 'type' => 'object',
110 'properties' => [
111 'enableCompliance' => [ 'type' => 'boolean' ],
112 'neverStoreEntries' => [ 'type' => 'boolean' ],
113 'autoDeleteEntries' => [ 'type' => 'boolean' ],
114 'autoDeleteEntriesDays' => [ 'type' => 'string' ],
115 ],
116 ],
117 'instantForm' => [
118 'type' => 'object',
119 'properties' => [
120 'instantForm' => [ 'type' => 'boolean' ],
121 'showTitle' => [ 'type' => 'boolean' ],
122 'formBackgroundColor' => [ 'type' => 'string' ],
123 'formWidth' => [ 'type' => 'integer' ],
124 ],
125 ],
126 'general' => [
127 'type' => 'object',
128 'properties' => [
129 'useLabelAsPlaceholder' => [ 'type' => 'boolean' ],
130 'submitText' => [ 'type' => 'string' ],
131 ],
132 ],
133 'styling' => [
134 'type' => 'object',
135 'properties' => [
136 'submitAlignment' => [ 'type' => 'string' ],
137 ],
138 ],
139 'formStyling' => [
140 'type' => 'object',
141 'description' => __( 'Form styling settings including colors and spacing.', 'sureforms' ),
142 'properties' => [
143 'primaryColor' => [
144 'type' => 'string',
145 'description' => __( 'Primary/accent color as hex (e.g. #111C44).', 'sureforms' ),
146 ],
147 'textColor' => [
148 'type' => 'string',
149 'description' => __( 'Text color as hex (e.g. #1E1E1E).', 'sureforms' ),
150 ],
151 'textColorOnPrimary' => [
152 'type' => 'string',
153 'description' => __( 'Text color on primary backgrounds as hex (e.g. #FFFFFF).', 'sureforms' ),
154 ],
155 'fieldSpacing' => [
156 'type' => 'string',
157 'description' => __( 'Spacing between fields.', 'sureforms' ),
158 'enum' => [ 'small', 'medium', 'large' ],
159 ],
160 ],
161 ],
162 'emailNotification' => [
163 'type' => 'object',
164 'description' => __( 'Email notification settings for the first notification. Merges with existing.', 'sureforms' ),
165 'properties' => [
166 'status' => [
167 'type' => 'boolean',
168 'description' => __( 'Enable or disable the notification.', 'sureforms' ),
169 ],
170 'name' => [
171 'type' => 'string',
172 'description' => __( 'Notification name.', 'sureforms' ),
173 ],
174 'emailTo' => [
175 'type' => 'string',
176 'description' => __( 'Recipient email. Supports smart tags like {admin_email}.', 'sureforms' ),
177 ],
178 'emailReplyTo' => [ 'type' => 'string' ],
179 'fromName' => [
180 'type' => 'string',
181 'description' => __( 'Sender name. Supports smart tags like {site_title}.', 'sureforms' ),
182 ],
183 'fromEmail' => [ 'type' => 'string' ],
184 'emailCc' => [ 'type' => 'string' ],
185 'emailBcc' => [ 'type' => 'string' ],
186 'subject' => [
187 'type' => 'string',
188 'description' => __( 'Email subject. Supports smart tags like {form_title}.', 'sureforms' ),
189 ],
190 'emailBody' => [
191 'type' => 'string',
192 'description' => __( 'Email body. Supports smart tags like {all_data}.', 'sureforms' ),
193 ],
194 ],
195 ],
196 'formRestriction' => [
197 'type' => 'object',
198 'description' => __( 'Form submission restrictions and scheduling.', 'sureforms' ),
199 'properties' => [
200 'status' => [
201 'type' => 'boolean',
202 'description' => __( 'Enable entry limit restriction.', 'sureforms' ),
203 ],
204 'maxEntries' => [
205 'type' => 'integer',
206 'description' => __( 'Maximum number of entries allowed (0 = unlimited).', 'sureforms' ),
207 ],
208 'date' => [
209 'type' => 'string',
210 'description' => __( 'Expiry date (YYYY-MM-DD).', 'sureforms' ),
211 ],
212 'hours' => [ 'type' => 'string' ],
213 'minutes' => [ 'type' => 'string' ],
214 'meridiem' => [
215 'type' => 'string',
216 'enum' => [ 'AM', 'PM' ],
217 ],
218 'message' => [
219 'type' => 'string',
220 'description' => __( 'Message shown when form is closed.', 'sureforms' ),
221 ],
222 'schedulingStatus' => [
223 'type' => 'boolean',
224 'description' => __( 'Enable form scheduling.', 'sureforms' ),
225 ],
226 'startDate' => [
227 'type' => 'string',
228 'description' => __( 'Schedule start date (YYYY-MM-DD).', 'sureforms' ),
229 ],
230 'startHours' => [ 'type' => 'string' ],
231 'startMinutes' => [ 'type' => 'string' ],
232 'startMeridiem' => [
233 'type' => 'string',
234 'enum' => [ 'AM', 'PM' ],
235 ],
236 'schedulingNotStartedMessage' => [ 'type' => 'string' ],
237 'schedulingEndedMessage' => [ 'type' => 'string' ],
238 ],
239 ],
240 'formCustomCss' => [
241 'type' => 'string',
242 'description' => __( 'Custom CSS for the form.', 'sureforms' ),
243 ],
244 ],
245 ],
246 ],
247 'required' => [ 'form_id' ],
248 ];
249 }
250
251 /**
252 * {@inheritDoc}
253 *
254 * @since 2.5.2
255 */
256 public function get_output_schema() {
257 return [
258 'type' => 'object',
259 'properties' => [
260 'form_id' => [ 'type' => 'integer' ],
261 'title' => [ 'type' => 'string' ],
262 'status' => [ 'type' => 'string' ],
263 'previous_status' => [ 'type' => 'string' ],
264 'edit_url' => [ 'type' => 'string' ],
265 'updated_fields' => [
266 'type' => 'array',
267 'items' => [ 'type' => 'string' ],
268 ],
269 ],
270 ];
271 }
272
273 /**
274 * Execute the update-form ability.
275 *
276 * @param array<string,mixed> $input Validated input data.
277 * @since 2.5.2
278 * @return array<string,mixed>|\WP_Error
279 */
280 public function execute( $input ) {
281 $form_id = Helper::get_integer_value( $input['form_id'] ?? 0 );
282 $post = get_post( $form_id );
283
284 if ( ! $post || SRFM_FORMS_POST_TYPE !== $post->post_type ) {
285 return new \WP_Error(
286 'srfm_form_not_found',
287 __( 'Form not found.', 'sureforms' ),
288 [ 'status' => 404 ]
289 );
290 }
291
292 $previous_status = $post->post_status;
293 $updated_fields = [];
294
295 // Handle status changes.
296 if ( ! empty( $input['status'] ) ) {
297 $new_status = sanitize_text_field( Helper::get_string_value( $input['status'] ) );
298 $allowed_status = [ 'publish', 'draft', 'private', 'trash' ];
299
300 if ( in_array( $new_status, $allowed_status, true ) ) {
301 if ( 'trash' === $new_status && 'trash' !== $previous_status ) {
302 wp_trash_post( $form_id );
303 $updated_fields[] = 'status';
304 } elseif ( 'trash' !== $new_status && 'trash' === $previous_status ) {
305 wp_untrash_post( $form_id );
306 // After untrash, set the desired status.
307 wp_update_post(
308 [
309 'ID' => $form_id,
310 'post_status' => $new_status,
311 ]
312 );
313 $updated_fields[] = 'status';
314 } elseif ( $new_status !== $previous_status ) {
315 wp_update_post(
316 [
317 'ID' => $form_id,
318 'post_status' => $new_status,
319 ]
320 );
321 $updated_fields[] = 'status';
322 }
323 }
324 }
325
326 // Handle title changes.
327 if ( ! empty( $input['title'] ) ) {
328 $new_title = sanitize_text_field( Helper::get_string_value( $input['title'] ) );
329
330 if ( $new_title !== $post->post_title ) {
331 wp_update_post(
332 [
333 'ID' => $form_id,
334 'post_title' => $new_title,
335 ]
336 );
337 $updated_fields[] = 'title';
338 }
339 }
340
341 // Handle metadata changes.
342 if ( ! empty( $input['formMetaData'] ) && is_array( $input['formMetaData'] ) ) {
343 $current_metas = [];
344 $meta_keys = Create_New_Form::get_default_meta_keys();
345
346 foreach ( array_keys( $meta_keys ) as $meta_key ) {
347 $current_metas[ $meta_key ] = get_post_meta( $form_id, $meta_key, true );
348 }
349
350 // Load serialized object metas needed for metadata overrides.
351 $instant_raw = get_post_meta( $form_id, '_srfm_instant_form_settings', true );
352 $confirmation_raw = get_post_meta( $form_id, '_srfm_form_confirmation', true );
353 $compliance_raw = get_post_meta( $form_id, '_srfm_compliance', true );
354
355 $current_metas['_srfm_instant_form_settings'] = Helper::get_array_value( is_array( $instant_raw ) ? $instant_raw : [] );
356 $current_metas['_srfm_form_confirmation'] = Helper::get_array_value( is_array( $confirmation_raw ) ? $confirmation_raw : [] );
357 $current_metas['_srfm_compliance'] = Helper::get_array_value( is_array( $compliance_raw ) ? $compliance_raw : [] );
358
359 // Load additional serialized metas.
360 $styling_raw = get_post_meta( $form_id, '_srfm_forms_styling', true );
361 $notif_raw = get_post_meta( $form_id, '_srfm_email_notification', true );
362
363 $current_metas['_srfm_forms_styling'] = Helper::get_array_value( is_array( $styling_raw ) ? $styling_raw : [] );
364 $current_metas['_srfm_email_notification'] = Helper::get_array_value( is_array( $notif_raw ) ? $notif_raw : [] );
365 $current_metas['_srfm_form_restriction'] = Helper::get_string_value( get_post_meta( $form_id, '_srfm_form_restriction', true ) );
366 $current_metas['_srfm_form_custom_css'] = Helper::get_string_value( get_post_meta( $form_id, '_srfm_form_custom_css', true ) );
367
368 $updated_metas = $this->apply_metadata_overrides( $current_metas, $input['formMetaData'] );
369
370 foreach ( $updated_metas as $meta_key => $meta_value ) {
371 if ( isset( $current_metas[ $meta_key ] ) && $current_metas[ $meta_key ] === $meta_value ) {
372 continue;
373 }
374 update_post_meta( $form_id, $meta_key, $meta_value );
375 }
376
377 $updated_fields[] = 'metadata';
378 }
379
380 // Handle field changes.
381 if ( ! empty( $input['formFields'] ) && is_array( $input['formFields'] ) ) {
382 // Sanitize form fields before passing to Field_Mapping.
383 $form_fields = $this->sanitize_form_fields( $input['formFields'] );
384
385 $request = new WP_REST_Request( 'POST' );
386 $request->set_param(
387 'form_data',
388 [
389 'form' => [ 'formFields' => $form_fields ],
390 ]
391 );
392
393 $post_content = Field_Mapping::generate_gutenberg_fields_from_questions( $request );
394
395 if ( is_wp_error( $post_content ) ) {
396 return $post_content;
397 }
398
399 if ( ! empty( $post_content ) ) {
400 wp_update_post(
401 [
402 'ID' => $form_id,
403 'post_content' => $post_content,
404 ]
405 );
406 $updated_fields[] = 'fields';
407 }
408 }
409
410 // Re-fetch post to get the current state.
411 $updated_post = get_post( $form_id );
412
413 return [
414 'form_id' => $form_id,
415 'title' => $updated_post ? $updated_post->post_title : $post->post_title,
416 'status' => $updated_post ? $updated_post->post_status : $post->post_status,
417 'previous_status' => $previous_status,
418 'edit_url' => admin_url( 'post.php?post=' . $form_id . '&action=edit' ),
419 'updated_fields' => $updated_fields,
420 ];
421 }
422 }
423