PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.8.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.8.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 / field-validation.php
field-validation.php
454 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Field Validation Class
4 *
5 * Handles all field validation for SureForms
6 *
7 * @package SureForms
8 * @since 1.12.2
9 */
10
11 namespace SRFM\Inc;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Field Validation Class
19 */
20 class Field_Validation {
21 /**
22 * Add block configuration for form fields.
23 *
24 * This function processes blocks in a form and stores their configuration as post meta.
25 * It applies filters to allow extensions to modify block configs and stores processed
26 * values for blocks that need special handling (like upload fields).
27 *
28 * @param array<mixed> $blocks Array of blocks to process.
29 * @param int $form_id Form post ID.
30 * @return void
31 * @since 1.12.2
32 */
33 public static function add_block_config( $blocks, $form_id ) {
34 // Initialize array to store processed block configurations.
35 $block_config = [];
36
37 // Loop through each block.
38 foreach ( $blocks as $block ) {
39 // Ensure $block is an array and has the required structure.
40 if ( ! is_array( $block ) ) {
41 continue;
42 }
43 if ( ! isset( $block['blockName'] ) || ! isset( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
44 continue;
45 }
46 // Validate block id.
47 if ( ! array_key_exists( 'block_id', $block['attrs'] ) || empty( $block['attrs']['block_id'] ) || ! is_string( $block['attrs']['block_id'] ) ) {
48 continue;
49 }
50
51 $block_id = sanitize_text_field( $block['attrs']['block_id'] );
52 $block_name = $block['blockName'];
53
54 // Process specific block types.
55 $processed_config = null;
56
57 switch ( $block_name ) {
58 case 'srfm/payment':
59 $processed_config = self::process_payment_block( $block['attrs'], $blocks );
60 break;
61 case 'srfm/dropdown':
62 $processed_config = self::process_dropdown_block( $block['attrs'] );
63 break;
64 case 'srfm/multi-choice':
65 $processed_config = self::process_multichoice_block( $block['attrs'] );
66 break;
67 case 'srfm/number':
68 $processed_config = self::process_number_block( $block['attrs'] );
69 break;
70 }
71
72 // If block was processed, store its configuration.
73 if ( null !== $processed_config && ! empty( $processed_config ) ) {
74 $processed_config['block_name'] = $block_name;
75 // Add the slug to the configuration.
76 if ( isset( $block['attrs']['slug'] ) && ! empty( $block['attrs']['slug'] ) ) {
77 $processed_config['slug'] = sanitize_text_field( $block['attrs']['slug'] );
78 }
79
80 $block_config[ $block_id ] = $processed_config;
81 continue;
82 }
83
84 // Allow extensions to process and modify block config.
85 $config = apply_filters( 'srfm_block_config', [ 'block' => $block ] );
86
87 // If block was processed by a filter, add its processed value.
88 if ( isset( $config['processed_value'] ) && ! empty( $config['processed_value'] ) ) {
89 $block_config[ $block_id ] = $config['processed_value'];
90 continue;
91 }
92 }
93
94 // Only update meta if we have processed configurations.
95 if ( ! empty( $block_config ) ) {
96 update_post_meta( $form_id, '_srfm_block_config', $block_config );
97 }
98 }
99
100 /**
101 * Retrieve or migrate the block configuration for legacy forms.
102 *
103 * This function checks if the _srfm_block_config post meta exists for the given form ID.
104 * Example: get_post_meta( 123, '_srfm_block_config', true ) might return an array of block configs.
105 * If not found, it attempts to parse the form's post content and generate the block config.
106 * Example: If a legacy form with ID 123 has no _srfm_block_config, but its post_content contains blocks,
107 * the function will parse those blocks and call add_block_config() to generate and store the config.
108 *
109 * @param int $form_id The ID of the form post.
110 * @since 1.12.2
111 * @return array|null The block configuration array, or null if not found or invalid.
112 */
113 public static function get_or_migrate_block_config_for_legacy_form( $form_id ) {
114 // Validate that $form_id is a positive integer.
115 // Example: $form_id = 123 is valid; $form_id = -1 or 'abc' is not.
116 if ( ! is_int( $form_id ) || $form_id <= 0 ) {
117 return null;
118 }
119
120 // Retrieve the block config from post meta.
121 // Example: $block_config = [ 'block-1' => [ ... ], 'block-2' => [ ... ] ].
122 $block_config = get_post_meta( $form_id, '_srfm_block_config', true );
123 if ( ! empty( $block_config ) && is_array( $block_config ) ) {
124 // If it exists and is an array, return it directly (no migration needed).
125 // Example: Returning the existing $block_config array.
126 return $block_config;
127 }
128
129 // Get the post by ID and validate.
130 // Example: $post = get_post( 123 ); $post->post_content should contain block markup.
131 $post = get_post( $form_id );
132 if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
133 return null;
134 }
135
136 // Parse the blocks from the post content and attempt migration.
137 // Example: $blocks = parse_blocks( $post->post_content ); $blocks is an array of block arrays.
138 if ( function_exists( 'parse_blocks' ) ) {
139 $blocks = parse_blocks( $post->post_content );
140 if ( is_array( $blocks ) && ! empty( $blocks ) ) {
141 self::add_block_config( $blocks, $form_id );
142 }
143 }
144
145 // Retrieve the block config again after migration attempt.
146 // Example: After migration, $block_config should now be an array if successful.
147 $block_config = get_post_meta( $form_id, '_srfm_block_config', true );
148
149 return ! empty( $block_config ) && is_array( $block_config ) ? $block_config : null;
150 }
151
152 /**
153 * Prepare validation data for a given form.
154 *
155 * Retrieves the form block configuration from post meta and adds a 'name_with_id'
156 * key to each block, which is a unique identifier for the field (used for validation).
157 *
158 * @param int $current_form_id The ID of the form post.
159 * @since 1.12.2
160 * @return array|null The processed form configuration array, or null if not found.
161 */
162 public static function prepared_validation_data( $current_form_id ) {
163 // Retrieve the form block configuration from post meta.
164 $get_form_config = self::get_or_migrate_block_config_for_legacy_form( $current_form_id );
165
166 // If the configuration is an array, add a 'name_with_id' key to each block.
167 if ( is_array( $get_form_config ) ) {
168 foreach ( $get_form_config as $index => $block ) {
169 // Ensure both 'blockName' and 'block_id' exist before creating the identifier.
170 if ( isset( $block['blockName'] ) ) {
171 // 'name_with_id' is used as a unique field identifier for validation.
172 // Example: 'sureforms-input-abc123' for blockName 'sureforms/input' and block_id 'abc123'
173 $name_with_id = str_replace( '/', '-', $block['blockName'] ) . '-' . $index;
174
175 // Allow custom filter based on block type.
176 $name_with_id = apply_filters(
177 'srfm_block_config_name_with_id',
178 $name_with_id,
179 $block
180 );
181
182 $get_form_config[ $index ]['name_with_id'] = $name_with_id;
183 }
184 }
185 }
186
187 // Return the processed configuration array, or an empty array if not found.
188 return is_array( $get_form_config ) ? $get_form_config : [];
189 }
190
191 /**
192 * Validate form data for a given form.
193 *
194 * This function checks each field in the submitted form data (including uploaded files)
195 * and applies the 'srfm_validate_form_data' filter to validate each field according to
196 * its configuration. Only fields with keys containing '-lbl-' (SureForms fields) are processed.
197 * If a field fails validation, its error message is added to the $not_valid_fields array.
198 *
199 * @param array<mixed> $form_data The submitted form data (sanitized).
200 * @param int|mixed $current_form_id The ID of the form being validated.
201 * @since 1.12.2
202 * @return array An array of invalid fields and their error messages. Empty if all fields are valid.
203 */
204 public static function validate_form_data( $form_data, $current_form_id ) {
205 if ( ! is_array( $form_data ) || ! is_numeric( $current_form_id ) ) {
206 return [];
207 }
208
209 // Holds fields that are not valid. Example: [ 'srfm-email-c867d9d9-lbl-email' => 'This field is required.' ].
210 $not_valid_fields = [];
211
212 // Retrieve the processed form configuration for validation.
213 $get_form_config = self::prepared_validation_data( Helper::get_integer_value( $current_form_id ) );
214
215 $form_data = apply_filters( 'srfm_field_validation_data', $form_data );
216
217 // Iterate over each field in the form data.
218 foreach ( $form_data as $key => $value ) {
219 /**
220 * Only process SureForms fields.
221 * The '-lbl-' substring is mandatory in SureForms field keys.
222 * Example: $key = 'srfm-email-c867d9d9-lbl-email'
223 */
224 if ( false === strpos( $key, '-lbl-' ) ) {
225 continue;
226 }
227
228 $get_name_with_id = explode( '-lbl-', $key );
229 // Extract the part after the last '-' in the key, if it matches the pattern.
230 // Example: $get_name_with_id[0] = "srfm-email-c867d9d9".
231 // $extracted_id = "c867d9d9".
232 $extracted_id = '';
233 if ( is_string( $key ) && preg_match( '/-([a-zA-Z0-9]+)$/', $get_name_with_id[0], $matches ) ) {
234 $extracted_id = $matches[1];
235 // Now $extracted_id contains "c867d9d9" for "srfm-email-c867d9d9".
236 }
237
238 // $get_slug will be the slug after the first hyphen in the second part.
239 // Example: $get_name_with_id[1] = "email" or "field-email", $get_slug = "email".
240 $get_slug = isset( $get_name_with_id[1] ) ? preg_replace( '/^[^-]+-/', '', $get_name_with_id[1] ) : '';
241
242 // $get_field_name is the field name without the block id.
243 // Example: "srfm-email-c867d9d9" => "srfm-email".
244 $get_field_name = str_replace( '-' . $extracted_id, '', $get_name_with_id[0] );
245
246 // Apply the validation filter for the current field.
247 // Example: Passes all relevant field data to the filter for validation.
248 $field_validated = apply_filters(
249 'srfm_validate_form_data',
250 [
251 'field_key' => $key,
252 'field_value' => $value,
253 'form_id' => $current_form_id,
254 'form_config' => $get_form_config,
255 'block_id' => $extracted_id,
256 'block_slug' => $get_slug,
257 'name_with_id' => $get_name_with_id[0],
258 'field_name' => $get_field_name,
259 ]
260 );
261
262 // Check the result of the validation.
263 // Example: $field_validated = [ 'validated' => false, 'error' => 'This field is required.' ].
264 if ( isset( $field_validated['validated'] ) ) {
265 // If the field is valid, skip to the next field.
266 if ( true === $field_validated['validated'] ) {
267 continue;
268 }
269
270 // If the field is not valid, add the error message to the result array.
271 // Example: $not_valid_fields[ 'srfm-email-c867d9d9-lbl-email' ] = 'This field is required.'.
272 if ( false === $field_validated['validated'] ) {
273 $not_valid_fields[ $key ] = $field_validated['error'] ?? __( 'Field is not valid.', 'sureforms' );
274 }
275 }
276 }
277
278 // Return the array of invalid fields and their error messages.
279 // Example: [ 'srfm-email-c867d9d9-lbl-email' => 'This field is required.' ].
280 return $not_valid_fields;
281 }
282
283 /**
284 * Process payment block configuration.
285 *
286 * @param array<mixed> $attrs Block attributes.
287 * @param array<mixed> $blocks All blocks.
288 * @return array Processed payment configuration.
289 * @since 2.3.0
290 */
291 private static function process_payment_block( $attrs, $blocks ) {
292 $payment_config = [];
293
294 // Extract payment type (single or subscription).
295 $payment_config['payment_type'] = isset( $attrs['paymentType'] ) && is_string( $attrs['paymentType'] ) ? sanitize_text_field( $attrs['paymentType'] ) : 'one-time';
296
297 // Extract amount type (fixed or minimum).
298 $payment_config['amount_type'] = isset( $attrs['amountType'] ) && is_string( $attrs['amountType'] ) ? sanitize_text_field( $attrs['amountType'] ) : 'fixed';
299
300 $payment_config['fixed_amount'] = isset( $attrs['fixedAmount'] ) ? floatval( $attrs['fixedAmount'] ) : 10;
301
302 $payment_config['minimum_amount'] = isset( $attrs['minimumAmount'] ) ? floatval( $attrs['minimumAmount'] ) : 0;
303
304 // Extract variable amount field reference.
305 if ( isset( $attrs['variableAmountField'] ) ) {
306 $variable_amount_slug = sanitize_text_field( $attrs['variableAmountField'] );
307 $payment_config['variable_amount_field'] = $variable_amount_slug;
308
309 // Find and add the block name from which the variable amount field comes from.
310 if ( ! empty( $variable_amount_slug ) && is_array( $blocks ) ) {
311 foreach ( $blocks as $block ) {
312 if ( isset( $block['attrs']['slug'] ) && $block['attrs']['slug'] === $variable_amount_slug ) {
313 $payment_config['variable_amount_field_block_name'] = $block['blockName'];
314 break;
315 }
316 }
317 }
318 }
319
320 return $payment_config;
321 }
322
323 /**
324 * Process dropdown block configuration.
325 *
326 * @param array<mixed> $attrs Block attributes.
327 * @return array Processed dropdown configuration.
328 * @since 2.3.0
329 */
330 private static function process_dropdown_block( $attrs ) {
331 $dropdown_config = [];
332
333 // Extract required field.
334 $dropdown_config['required'] = isset( $attrs['required'] ) && ! empty( $attrs['required'] ) ? true : false;
335
336 // Extract options with their full structure (label, icon, value).
337 if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
338 $sanitized_options = [];
339 foreach ( $attrs['options'] as $option ) {
340 if ( is_array( $option ) ) {
341 $sanitized_options[] = [
342 'label' => isset( $option['label'] ) ? sanitize_text_field( $option['label'] ) : '',
343 'icon' => isset( $option['icon'] ) ? sanitize_text_field( $option['icon'] ) : '',
344 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
345 ];
346 }
347 }
348 $dropdown_config['options'] = $sanitized_options;
349 }
350
351 // Extract showValues flag.
352 $dropdown_config['show_values'] = isset( $attrs['showValues'] ) ? rest_sanitize_boolean( $attrs['showValues'] ) : false;
353
354 // Extract multiSelect flag.
355 if ( isset( $attrs['multiSelect'] ) ) {
356 $dropdown_config['multi_select'] = rest_sanitize_boolean( $attrs['multiSelect'] );
357 }
358
359 // Extract minValue for multi-select validation.
360 if ( isset( $attrs['minValue'] ) ) {
361 $dropdown_config['min_value'] = absint( $attrs['minValue'] );
362 }
363
364 // Extract maxValue for multi-select validation.
365 if ( isset( $attrs['maxValue'] ) ) {
366 $dropdown_config['max_value'] = absint( $attrs['maxValue'] );
367 }
368
369 return $dropdown_config;
370 }
371
372 /**
373 * Process multi-choice block configuration.
374 *
375 * @param array<mixed> $attrs Block attributes.
376 * @return array Processed multi-choice configuration.
377 * @since 2.3.0
378 */
379 private static function process_multichoice_block( $attrs ) {
380 $multichoice_config = [];
381
382 // Extract required field.
383 $multichoice_config['required'] = isset( $attrs['required'] ) && ! empty( $attrs['required'] ) ? true : false;
384
385 // Extract singleSelection flag.
386 if ( isset( $attrs['singleSelection'] ) ) {
387 $multichoice_config['single_selection'] = rest_sanitize_boolean( $attrs['singleSelection'] );
388 }
389
390 // Extract minValue for validation.
391 if ( isset( $attrs['minValue'] ) ) {
392 $multichoice_config['min_value'] = absint( $attrs['minValue'] );
393 }
394
395 // Extract maxValue for validation.
396 if ( isset( $attrs['maxValue'] ) ) {
397 $multichoice_config['max_value'] = absint( $attrs['maxValue'] );
398 }
399
400 // Extract options with their full structure (label, icon, value).
401 if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
402 $sanitized_options = [];
403 foreach ( $attrs['options'] as $option ) {
404 if ( is_array( $option ) ) {
405 $sanitized_options[] = [
406 'label' => isset( $option['optionTitle'] ) ? trim( sanitize_text_field( $option['optionTitle'] ) ) : '',
407 'icon' => isset( $option['icon'] ) ? sanitize_text_field( $option['icon'] ) : '',
408 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
409 ];
410 }
411 }
412 $multichoice_config['options'] = $sanitized_options;
413 }
414
415 // Extract showValues flag.
416 if ( isset( $attrs['showValues'] ) ) {
417 $multichoice_config['show_values'] = rest_sanitize_boolean( $attrs['showValues'] );
418 }
419
420 return $multichoice_config;
421 }
422
423 /**
424 * Process number block configuration.
425 *
426 * @param array<mixed> $attrs Block attributes.
427 * @return array Processed number configuration.
428 * @since 2.4.0
429 */
430 private static function process_number_block( $attrs ) {
431 $number_config = [];
432
433 // Extract required field.
434 if ( isset( $attrs['required'] ) ) {
435 $number_config['required'] = ! empty( $attrs['required'] ) ? true : false;
436 }
437
438 // Extract format type (us-style or eu-style).
439 $number_config['format_type'] = isset( $attrs['formatType'] ) && is_string( $attrs['formatType'] ) ? sanitize_text_field( $attrs['formatType'] ) : 'us-style';
440
441 // Extract min value.
442 if ( isset( $attrs['min'] ) ) {
443 $number_config['min'] = floatval( $attrs['min'] );
444 }
445
446 // Extract max value.
447 if ( isset( $attrs['max'] ) ) {
448 $number_config['max'] = floatval( $attrs['max'] );
449 }
450
451 return $number_config;
452 }
453 }
454