PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 1.0.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v1.0.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 / helper.php
helper.php
970 lines 32.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sureforms Submit Class file.
4 *
5 * @package sureforms.
6 * @since 0.0.1
7 */
8
9 namespace SRFM\Inc;
10
11 use SRFM\Inc\Database\Tables\Entries;
12 use SRFM\Inc\Traits\Get_Instance;
13 use WP_Error;
14 use WP_Post_Type;
15 use WP_Post;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Sureforms Helper Class.
23 *
24 * @since 0.0.1
25 */
26 class Helper {
27 use Get_Instance;
28
29 /**
30 * Sureforms SVGs.
31 *
32 * @var mixed srfm_svgs
33 */
34 private static $srfm_svgs = null;
35
36 /**
37 * Get common error message.
38 *
39 * @since 0.0.2
40 * @return array<string>
41 */
42 public static function get_common_err_msg() {
43 return [
44 'required' => __( 'This field is required.', 'sureforms' ),
45 'unique' => __( 'Value needs to be unique.', 'sureforms' ),
46 ];
47 }
48
49
50 /**
51 * Checks if current value is string or else returns default value
52 *
53 * @param mixed $data data which need to be checked if is string.
54 *
55 * @since 0.0.1
56 * @return string
57 */
58 public static function get_string_value( $data ) {
59 if ( is_scalar( $data ) ) {
60 return (string) $data;
61 } elseif ( is_object( $data ) && method_exists( $data, '__toString' ) ) {
62 return $data->__toString();
63 } elseif ( is_null( $data ) ) {
64 return '';
65 } else {
66 return '';
67 }
68 }
69 /**
70 * Checks if current value is number or else returns default value
71 *
72 * @param mixed $value data which need to be checked if is string.
73 * @param int $base value can be set is $data is not a string, defaults to empty string.
74 *
75 * @since 0.0.1
76 * @return int
77 */
78 public static function get_integer_value( $value, $base = 10 ) {
79 if ( is_numeric( $value ) ) {
80 return (int) $value;
81 } elseif ( is_string( $value ) ) {
82 $trimmed_value = trim( $value );
83 return intval( $trimmed_value, $base );
84 } else {
85 return 0;
86 }
87 }
88
89 /**
90 * Checks if current value is an array or else returns default value
91 *
92 * @param mixed $data Data which needs to be checked if it is an array.
93 *
94 * @since 0.0.3
95 * @return array<mixed>
96 */
97 public static function get_array_value( $data ) {
98 if ( is_array( $data ) ) {
99 return $data;
100 } elseif ( is_null( $data ) ) {
101 return [];
102 } else {
103 return (array) $data;
104 }
105 }
106
107 /**
108 * Extracts the field type from the dynamic field key ( or field slug ).
109 *
110 * @param string $field_key Dynamic field key.
111 * @since 0.0.6
112 * @return string Extracted field type.
113 */
114 public static function get_field_type_from_key( $field_key ) {
115
116 if ( false === strpos( $field_key, '-lbl-' ) ) {
117 return '';
118 }
119
120 return trim( explode( '-', $field_key )[1] );
121 }
122
123 /**
124 * Returns the proper sanitize callback functions according to the field type.
125 *
126 * @param string $field_type HTML field type.
127 * @since 0.0.6
128 * @return callable Returns sanitize callbacks according to the provided field type.
129 */
130 public static function get_field_type_sanitize_function( $field_type ) {
131 $callbacks = apply_filters(
132 'srfm_field_type_sanitize_functions',
133 [
134 'url' => 'esc_url_raw',
135 'input' => 'sanitize_text_field',
136 'number' => [ __CLASS__, 'sanitize_number' ],
137 'email' => 'sanitize_email',
138 'textarea' => 'sanitize_textarea_field',
139 ]
140 );
141
142 return isset( $callbacks[ $field_type ] ) ? $callbacks[ $field_type ] : 'sanitize_text_field';
143
144 }
145
146 /**
147 * Sanitizes a numeric value.
148 *
149 * This function checks if the input value is numeric. If it is numeric, it sanitizes
150 * the value to ensure it's a float or integer, allowing for fractions and thousand separators.
151 * If the value is not numeric, it sanitizes it as a text field.
152 *
153 * @param mixed $value The value to be sanitized.
154 * @since 0.0.6
155 * @return integer|float|string The sanitized value.
156 */
157 public static function sanitize_number( $value ) {
158 if ( ! is_numeric( $value ) ) {
159 // phpcs:ignore /** @phpstan-ignore-next-line */
160 return sanitize_text_field( $value ); // If it is not numeric, then let user get some sanitized data to view.
161 }
162
163 // phpcs:ignore /** @phpstan-ignore-next-line */
164 return sanitize_text_field( filter_var( $value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND ) );
165 }
166
167 /**
168 * This function sanitizes the submitted form data according to the field type.
169 *
170 * @param array<mixed> $form_data $form_data User submitted form data.
171 * @since 0.0.6
172 * @return array<mixed> $result Sanitized form data.
173 */
174 public static function sanitize_by_field_type( $form_data ) {
175 $result = [];
176
177 if ( empty( $form_data ) || ! is_array( $form_data ) ) {
178 return $result;
179 }
180
181 foreach ( $form_data as $field_key => &$value ) {
182 $field_type = self::get_field_type_from_key( $field_key );
183 $sanitize_function = self::get_field_type_sanitize_function( $field_type );
184 $sanitized_data = is_array( $value ) ? self::sanitize_by_field_type( $value ) : call_user_func( $sanitize_function, $value );
185
186 $result[ $field_key ] = $sanitized_data;
187 }
188
189 return $result;
190
191 }
192
193 /**
194 * This function performs array_map for multi dimensional array
195 *
196 * @param string $function function name to be applied on each element on array.
197 * @param array<mixed> $data_array array on which function needs to be performed.
198 * @return array<mixed>
199 * @since 0.0.1
200 */
201 public static function sanitize_recursively( $function, $data_array ) {
202 $response = [];
203 if ( is_array( $data_array ) ) {
204 if ( ! is_callable( $function ) ) {
205 return $data_array;
206 }
207 foreach ( $data_array as $key => $data ) {
208 $val = is_array( $data ) ? self::sanitize_recursively( $function, $data ) : $function( $data );
209 $response[ $key ] = $val;
210 }
211 }
212
213 return $response;
214 }
215
216 /**
217 * Generates common markup liked label, etc
218 *
219 * @param int|string $form_id form id.
220 * @param string $type Type of form markup.
221 * @param string $label Label for the form markup.
222 * @param string $slug Slug for the form markup.
223 * @param string $block_id Block id for the form markup.
224 * @param bool $required If field is required or not.
225 * @param string $help Help for the form markup.
226 * @param string $error_msg Error message for the form markup.
227 * @param bool $is_unique Check if the field is unique.
228 * @param string $duplicate_msg Duplicate message for field.
229 * @param bool $override Override for error markup.
230 * @return string
231 * @since 0.0.1
232 */
233 public static function generate_common_form_markup( $form_id, $type, $label = '', $slug = '', $block_id = '', $required = false, $help = '', $error_msg = '', $is_unique = false, $duplicate_msg = '', $override = false ) {
234 $duplicate_msg = $duplicate_msg ? ' data-unique-msg="' . esc_attr( $duplicate_msg ) . '"' : '';
235
236 $markup = '';
237 $show_labels_as_placeholder = get_post_meta( self::get_integer_value( $form_id ), '_srfm_use_label_as_placeholder', true );
238 $show_labels_as_placeholder = $show_labels_as_placeholder ? self::get_string_value( $show_labels_as_placeholder ) : false;
239
240 switch ( $type ) {
241 case 'label':
242 $markup = $label ? '<label for="srfm-' . $slug . '-' . esc_attr( $block_id ) . '" class="srfm-block-label">' . htmlspecialchars_decode( esc_html( $label ) ) . ( $required ? '<span class="srfm-required"> *</span>' : '' ) . '</label>' : '';
243 break;
244 case 'help':
245 $markup = $help ? '<div class="srfm-description" id="srfm-description-' . esc_attr( $block_id ) . '">' . esc_html( $help ) . '</div>' : '';
246 break;
247 case 'error':
248 $markup = $required || $override ? '<div class="srfm-error-message" id="srfm-error-' . esc_attr( $block_id ) . '" data-error-msg="' . esc_attr( $error_msg ) . '"' . $duplicate_msg . '>' . esc_html( $error_msg ) . '</div>' : '';
249 break;
250 case 'is_unique':
251 $markup = $is_unique ? '<div class="srfm-error">' . esc_html( $duplicate_msg ) . '</div>' : '';
252 break;
253 case 'placeholder':
254 $markup = $label && '1' === $show_labels_as_placeholder ? $label . ( $required ? ' *' : '' ) : '';
255 break;
256 default:
257 $markup = '';
258 }
259
260 return $markup;
261 }
262
263
264 /**
265 * Get an SVG Icon
266 *
267 * @since 0.0.1
268 * @param string $icon the icon name.
269 * @param string $class if the baseline class should be added.
270 * @param string $html Custom attributes inside svg wrapper.
271 * @return string
272 */
273 public static function fetch_svg( $icon = '', $class = '', $html = '' ) {
274 $class = $class ? ' ' . $class : '';
275
276 $output = '<span class="srfm-icon' . $class . '" ' . $html . '>';
277 if ( ! self::$srfm_svgs ) {
278 ob_start();
279
280 include_once SRFM_DIR . 'assets/svg/svgs.json';
281 self::$srfm_svgs = json_decode( self::get_string_value( ob_get_clean() ), true );
282 self::$srfm_svgs = apply_filters( 'srfm_svg_icons', self::$srfm_svgs );
283 }
284
285 $output .= isset( self::$srfm_svgs[ $icon ] ) ? self::$srfm_svgs[ $icon ] : '';
286 $output .= '</span>';
287
288 return $output;
289 }
290
291
292 /**
293 * Encrypt data using base64.
294 *
295 * @param string $input The input string which needs to be encrypted.
296 * @since 0.0.1
297 * @return string The encrypted string.
298 */
299 public static function encrypt( $input ) {
300 // If the input is empty or not a string, then abandon ship.
301 if ( empty( $input ) || ! is_string( $input ) ) {
302 return '';
303 }
304
305 // Encrypt the input and return it.
306 $base_64 = base64_encode( $input ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
307 $encode = rtrim( $base_64, '=' );
308 return $encode;
309 }
310
311 /**
312 * Decrypt data using base64.
313 *
314 * @param string $input The input string which needs to be decrypted.
315 * @since 0.0.1
316 * @return string The decrypted string.
317 */
318 public static function decrypt( $input ) {
319 // If the input is empty or not a string, then abandon ship.
320 if ( empty( $input ) || ! is_string( $input ) ) {
321 return '';
322 }
323
324 // Decrypt the input and return it.
325 $base_64 = $input . str_repeat( '=', strlen( $input ) % 4 );
326 $decode = base64_decode( $base_64 ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
327 return $decode;
328 }
329
330 /**
331 * Update an option from the database.
332 *
333 * @param string $key The option key.
334 * @param mixed $value The value to update.
335 * @param bool $network_override Whether to allow the network_override admin setting to be overridden on subsites.
336 * @since 0.0.1
337 * @return bool True if the option was updated, false otherwise.
338 */
339 public static function update_admin_settings_option( $key, $value, $network_override = false ) {
340 // Update the site-wide option if we're in the network admin, and return the updated status.
341 return $network_override && is_multisite() ? update_site_option( $key, $value ) : update_option( $key, $value );
342 }
343
344 /**
345 * Update an option from the database.
346 *
347 * @param int|string $post_id post id / form id.
348 * @param string $key meta key name.
349 * @param bool $single single or multiple.
350 * @param mixed $default default value.
351 *
352 * @since 0.0.1
353 * @return string Meta value.
354 */
355 public static function get_meta_value( $post_id, $key, $single = true, $default = '' ) {
356 $srfm_live_mode_data = self::get_instant_form_live_data();
357
358 if ( isset( $srfm_live_mode_data[ $key ] ) ) {
359 // Give priority to live mode data if we have one set from the Instant Form.
360 return self::get_string_value( $srfm_live_mode_data[ $key ] );
361 }
362
363 $meta_value = get_post_meta( self::get_integer_value( $post_id ), $key, $single ) ? self::get_string_value( get_post_meta( self::get_integer_value( $post_id ), $key, $single ) ) : self::get_string_value( $default );
364 return $meta_value;
365 }
366
367 /**
368 * Wrapper for the WordPress's get_post_meta function with the support for default values.
369 *
370 * @param int|string $post_id Post ID.
371 * @param string $key The meta key to retrieve.
372 * @param mixed $default Default value.
373 * @param boolean $single Optional. Whether to return a single value.
374 * @since 0.0.8
375 * @return mixed Meta value.
376 */
377 public static function get_post_meta( $post_id, $key, $default = null, $single = true ) {
378 $meta_value = get_post_meta( self::get_integer_value( $post_id ), $key, $single );
379 return $meta_value ? $meta_value : $default;
380 }
381
382 /**
383 * Returns query params data for instant form live preview.
384 *
385 * @since 0.0.8
386 * @return array<mixed> Live preview data.
387 */
388 public static function get_instant_form_live_data() {
389 $srfm_live_mode_data = isset( $_GET['live_mode'] ) && current_user_can( 'edit_posts' ) ? self::sanitize_recursively( 'sanitize_text_field', wp_unslash( $_GET ) ) : []; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
390
391 return $srfm_live_mode_data ? array_map(
392 // Normalize falsy values.
393 function( $live_data ) {
394 return 'false' === $live_data ? false : $live_data;
395 },
396 $srfm_live_mode_data
397 ) : [];
398 }
399
400
401 /**
402 * Default dynamic block value.
403 *
404 * @since 0.0.1
405 * @return string[] Meta value.
406 */
407 public static function default_dynamic_block_option() {
408
409 $common_err_msg = self::get_common_err_msg();
410
411 $default_values = [
412 'srfm_url_block_required_text' => $common_err_msg['required'],
413 'srfm_input_block_required_text' => $common_err_msg['required'],
414 'srfm_input_block_unique_text' => $common_err_msg['unique'],
415 'srfm_address_block_required_text' => $common_err_msg['required'],
416 'srfm_phone_block_required_text' => $common_err_msg['required'],
417 'srfm_phone_block_unique_text' => $common_err_msg['unique'],
418 'srfm_number_block_required_text' => $common_err_msg['required'],
419 'srfm_textarea_block_required_text' => $common_err_msg['required'],
420 'srfm_multi_choice_block_required_text' => $common_err_msg['required'],
421 'srfm_checkbox_block_required_text' => $common_err_msg['required'],
422 'srfm_gdpr_block_required_text' => $common_err_msg['required'],
423 'srfm_email_block_required_text' => $common_err_msg['required'],
424 'srfm_email_block_unique_text' => $common_err_msg['unique'],
425 'srfm_dropdown_block_required_text' => $common_err_msg['required'],
426 'srfm_rating_block_required_text' => $common_err_msg['required'],
427 ];
428
429 return apply_filters( 'srfm_default_dynamic_block_option', $default_values, $common_err_msg );
430
431 }
432
433 /**
434 * Get default dynamic block value.
435 *
436 * @param string $key meta key name.
437 * @since 0.0.1
438 * @return string Meta value.
439 */
440 public static function get_default_dynamic_block_option( $key ) {
441 $default_dynamic_values = self::default_dynamic_block_option();
442 $option = get_option( 'srfm_default_dynamic_block_option', $default_dynamic_values );
443
444 if ( is_array( $option ) && array_key_exists( $key, $option ) ) {
445 return $option[ $key ];
446 } else {
447 return '';
448 }
449 }
450
451 /**
452 * Checks whether a given request has appropriate permissions.
453 *
454 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
455 * @since 0.0.1
456 */
457 public static function get_items_permissions_check() {
458 if ( current_user_can( 'edit_posts' ) ) {
459 return true;
460 }
461
462 foreach ( get_post_types( [ 'show_in_rest' => true ], 'objects' ) as $post_type ) {
463 /**
464 * The post type.
465 *
466 * @var WP_Post_Type $post_type
467 */
468 if ( current_user_can( $post_type->cap->edit_posts ) ) {
469 return true;
470 }
471 }
472
473 return new WP_Error(
474 'rest_cannot_view',
475 __( 'Sorry, you are not allowed to perform this action.', 'sureforms' ),
476 [ 'status' => \rest_authorization_required_code() ]
477 );
478 }
479
480 /**
481 * Check if the current user has a given capability.
482 *
483 * @param string $capability The capability to check.
484 * @since 0.0.3
485 * @return bool Whether the current user has the given capability or role.
486 */
487 public static function current_user_can( $capability = '' ) {
488
489 if ( ! function_exists( 'current_user_can' ) ) {
490 return false;
491 }
492
493 if ( ! is_string( $capability ) || empty( $capability ) ) {
494 $capability = 'edit_posts';
495 }
496
497 return current_user_can( $capability );
498 }
499
500 /**
501 * Get all the entries for the given form ids. The entries are older than the given days_old.
502 *
503 * @param int $days_old The number of days old the entries should be.
504 * @param array<int> $sf_form_ids The form ids for which the entries need to be fetched.
505 * @since 0.0.2
506 * @return array<mixed> the entries matching the criteria.
507 */
508 public static function get_entries_from_form_ids( $days_old = 0, $sf_form_ids = [] ) {
509
510 $entries = [];
511 $days_old_date = ( new \DateTime() )->modify( "-{$days_old} days" )->format( 'Y-m-d H:i:s' );
512
513 foreach ( $sf_form_ids as $form_id ) {
514 // args according to the get_all() function in the Entries class.
515 $args = [
516 'where' => [
517 [
518 [
519 'key' => 'form_id',
520 'value' => $form_id,
521 'compare' => '=',
522 ],
523 [
524 'key' => 'created_at',
525 'value' => $days_old_date,
526 'compare' => '<=',
527 ],
528 ],
529 ],
530 ];
531
532 // store all the entries in a single array.
533 $entries = array_merge( $entries, Entries::get_all( $args, false ) );
534 }
535 return $entries;
536 }
537
538 /**
539 * Decode block attributes.
540 * The function reverses the effect of serialize_block_attributes()
541 *
542 * @link https://developer.wordpress.org/reference/functions/serialize_block_attributes/
543 * @param string $encoded_data the encoded block attribute.
544 * @since 0.0.2
545 * @return string decoded block attribute
546 */
547 public static function decode_block_attribute( $encoded_data = '' ) {
548 $decoded_data = preg_replace( '/\\\\u002d\\\\u002d/', '--', self::get_string_value( $encoded_data ) );
549 $decoded_data = preg_replace( '/\\\\u003c/', '<', self::get_string_value( $decoded_data ) );
550 $decoded_data = preg_replace( '/\\\\u003e/', '>', self::get_string_value( $decoded_data ) );
551 $decoded_data = preg_replace( '/\\\\u0026/', '&', self::get_string_value( $decoded_data ) );
552 $decoded_data = preg_replace( '/\\\\\\\\"/', '"', self::get_string_value( $decoded_data ) );
553 return self::get_string_value( $decoded_data );
554 }
555
556 /**
557 * Map slugs to submission data.
558 *
559 * @param array<mixed> $submission_data submission_data.
560 * @since 0.0.3
561 * @return array<mixed>
562 */
563 public static function map_slug_to_submission_data( $submission_data = [] ) {
564 $mapped_data = [];
565 foreach ( $submission_data as $key => $value ) {
566 if ( false === strpos( $key, '-lbl-' ) ) {
567 continue;
568 }
569 $label = explode( '-lbl-', $key )[1];
570 $slug = implode( '-', array_slice( explode( '-', $label ), 1 ) );
571 $mapped_data[ $slug ] = $value;
572 }
573 return $mapped_data;
574 }
575
576 /**
577 * Get forms options. Shows all the available forms in the dropdown.
578 *
579 * @since 0.0.5
580 * @param string $key Determines the type of data to return.
581 * @return array<mixed>
582 */
583 public static function get_sureforms( $key = '' ) {
584 $forms = get_posts(
585 apply_filters(
586 'srfm_get_sureforms_query_args',
587 [
588 'post_type' => SRFM_FORMS_POST_TYPE,
589 'posts_per_page' => -1,
590 'post_status' => 'publish',
591 ]
592 )
593 );
594
595 $options = [];
596
597 foreach ( $forms as $form ) {
598 if ( $form instanceof WP_Post ) {
599 if ( 'all' === $key ) {
600 $options[ $form->ID ] = $form;
601 } elseif ( ! empty( $key ) && is_string( $key ) && isset( $form->$key ) ) {
602 $options[ $form->ID ] = $form->$key;
603 } else {
604 $options[ $form->ID ] = $form->post_title;
605 }
606 }
607 }
608
609 return $options;
610 }
611
612 /**
613 * Get all the forms.
614 *
615 * @since 0.0.5
616 * @return array<mixed>
617 */
618 public static function get_sureforms_title_with_ids() {
619 $form_options = self::get_sureforms();
620
621 foreach ( $form_options as $key => $value ) {
622 $form_options[ $key ] = $value . ' #' . $key;
623 }
624
625 return $form_options;
626 }
627
628 /**
629 * Get the CSS variables based on different field spacing sizes.
630 *
631 * @param string|null $field_spacing The field spacing size or boolean false to return complete sizes array.
632 *
633 * @since 0.0.7
634 * @return array<string|mixed>
635 */
636 public static function get_css_vars( $field_spacing = null ) {
637 /**
638 * $sizes - Field Spacing Sizes Variables.
639 * The array contains the CSS variables for different field spacing sizes.
640 * Each key corresponds to the field spacing size, and the value is an array of CSS variables.
641 *
642 * For future variables depending on the field spacing size, add the variable to the array respectively.
643 */
644 $sizes = apply_filters(
645 'srfm_css_vars_sizes',
646 [
647 'small' => [
648 '--srfm-row-gap-between-blocks' => '16px',
649 // Address block gap and spacing variables.
650 '--srfm-col-gap-between-fields' => '12px',
651 '--srfm-row-gap-between-fields' => '12px',
652 '--srfm-gap-below-address-label' => '12px',
653 // Dropdown Variables.
654 '--srfm-dropdown-font-size' => '14px',
655 '--srfm-dropdown-gap-between-input-menu' => '4px',
656 '--srfm-dropdown-badge-padding' => '2px 6px',
657 '--srfm-dropdown-multiselect-font-size' => '12px',
658 '--srfm-dropdown-multiselect-line-height' => '16px',
659 '--srfm-dropdown-padding-right' => '12px',
660 // initial padding and from 20px - 12px for dropdown arrow width and 8px for gap before dropdown arrow.
661 '--srfm-dropdown-padding-right-icon' => 'calc( var( --srfm-dropdown-padding-right ) + 20px )',
662 '--srfm-dropdown-multiselect-padding' => '8px var( --srfm-dropdown-padding-right-icon ) 8px 8px',
663 // Input Field Variables.
664 '--srfm-input-height' => '40px',
665 '--srfm-input-field-padding' => '10px 12px',
666 '--srfm-input-field-font-size' => '14px',
667 '--srfm-input-field-line-height' => '20px',
668 '--srfm-input-field-margin' => '4px 0',
669 // Checkbox and GDPR Variables.
670 '--srfm-check-ctn-width' => '16px',
671 '--srfm-check-ctn-height' => '16px',
672 '--srfm-check-svg-size' => '10px',
673 '--srfm-checkbox-margin-top-frontend' => '2px',
674 '--srfm-checkbox-margin-top-editor' => '3px',
675 '--srfm-check-gap' => '8px',
676 '--srfm-checkbox-description-margin-left' => '24px',
677 // Phone Number field variables.
678 '--srfm-flag-section-padding' => '10px 0 10px 12px',
679 '--srfm-gap-between-icon-text' => '8px',
680 // Label Variables.
681 '--srfm-label-font-size' => '14px',
682 '--srfm-label-line-height' => '20px',
683 // Description Variables.
684 '--srfm-description-font-size' => '12px',
685 '--srfm-description-line-height' => '16px',
686 // Button Variables.
687 '--srfm-btn-padding' => '8px 14px',
688 '--srfm-btn-font-size' => '14px',
689 '--srfm-btn-line-height' => '20px',
690 // Multi Choice Variables.
691 '--srfm-multi-choice-horizontal-padding' => '16px',
692 '--srfm-multi-choice-vertical-padding' => '16px',
693 '--srfm-multi-choice-internal-option-gap' => '8px',
694 '--srfm-multi-choice-vertical-svg-size' => '32px',
695 '--srfm-multi-choice-horizontal-image-size' => '20px',
696 '--srfm-multi-choice-vertical-image-size' => '100px',
697 '--srfm-multi-choice-outer-padding' => '0',
698 ],
699 'medium' => [
700 '--srfm-row-gap-between-blocks' => '18px',
701 // Address block gap and spacing variables.
702 '--srfm-col-gap-between-fields' => '16px',
703 '--srfm-row-gap-between-fields' => '16px',
704 '--srfm-gap-below-address-label' => '14px',
705 // Input Field Variables.
706 '--srfm-input-height' => '44px',
707 '--srfm-input-field-font-size' => '16px',
708 '--srfm-input-field-line-height' => '24px',
709 '--srfm-input-field-margin' => '6px 0',
710 // Checkbox and GDPR Variables.
711 '--srfm-checkbox-margin-top-frontend' => '4px',
712 '--srfm-checkbox-margin-top-editor' => '6px',
713 '--srfm-checkbox-description-margin-left' => '24px',
714 // Label Variables.
715 '--srfm-label-font-size' => '16px',
716 '--srfm-label-line-height' => '24px',
717 // Description Variables.
718 '--srfm-description-font-size' => '14px',
719 '--srfm-description-line-height' => '20px',
720 // Button Variables.
721 '--srfm-btn-padding' => '10px 14px',
722 '--srfm-btn-font-size' => '16px',
723 '--srfm-btn-line-height' => '24px',
724 // Multi Choice Variables.
725 '--srfm-multi-choice-horizontal-padding' => '20px',
726 '--srfm-multi-choice-vertical-padding' => '20px',
727 '--srfm-multi-choice-vertical-svg-size' => '40px',
728 '--srfm-multi-choice-horizontal-image-size' => '24px',
729 '--srfm-multi-choice-vertical-image-size' => '120px',
730 '--srfm-multi-choice-outer-padding' => '2px',
731 ],
732 'large' => [
733 '--srfm-row-gap-between-blocks' => '20px',
734 // Address Block Gap and Spacing Variables.
735 '--srfm-col-gap-between-fields' => '16px',
736 '--srfm-row-gap-between-fields' => '20px',
737 '--srfm-gap-below-address-label' => '16px',
738 // Dropdown Variables.
739 '--srfm-dropdown-font-size' => '16px',
740 '--srfm-dropdown-gap-between-input-menu' => '6px',
741 '--srfm-dropdown-badge-padding' => '6px 6px',
742 '--srfm-dropdown-multiselect-font-size' => '14px',
743 '--srfm-dropdown-multiselect-line-height' => '20px',
744 '--srfm-dropdown-padding-right' => '14px',
745 // Input Field Variables.
746 '--srfm-input-height' => '48px',
747 '--srfm-input-field-padding' => '10px 14px',
748 '--srfm-input-field-font-size' => '18px',
749 '--srfm-input-field-line-height' => '28px',
750 '--srfm-input-field-margin' => '8px 0',
751 // Checkbox and GDPR Variables.
752 '--srfm-check-ctn-width' => '20px',
753 '--srfm-check-ctn-height' => '20px',
754 '--srfm-check-svg-size' => '14px',
755 '--srfm-check-gap' => '10px',
756 '--srfm-checkbox-margin-top-frontend' => '4px',
757 '--srfm-checkbox-margin-top-editor' => '5px',
758 '--srfm-checkbox-description-margin-left' => '30px',
759 // Label Variables.
760 '--srfm-label-font-size' => '18px',
761 '--srfm-label-line-height' => '28px',
762 // Description Variables.
763 '--srfm-description-font-size' => '16px',
764 '--srfm-description-line-height' => '24px',
765 // Button Variables.
766 '--srfm-btn-padding' => '10px 14px',
767 '--srfm-btn-font-size' => '18px',
768 '--srfm-btn-line-height' => '28px',
769 // Multi Choice Variables.
770 '--srfm-multi-choice-horizontal-padding' => '24px',
771 '--srfm-multi-choice-vertical-padding' => '24px',
772 '--srfm-multi-choice-internal-option-gap' => '12px',
773 '--srfm-multi-choice-vertical-svg-size' => '48px',
774 '--srfm-multi-choice-horizontal-image-size' => '28px',
775 '--srfm-multi-choice-vertical-image-size' => '140px',
776 '--srfm-multi-choice-outer-padding' => '4px',
777 ],
778 ]
779 );
780 // Return complete sizes array if field_spacing is false. Required in case of JS for Editor changes.
781 if ( ! $field_spacing ) {
782 return $sizes;
783 }
784
785 $selected_size = $sizes['small'];
786 if ( 'small' !== $field_spacing && isset( $sizes[ $field_spacing ] ) ) {
787 $selected_size = array_merge( $selected_size, $sizes[ $field_spacing ] );
788 }
789
790 return $selected_size;
791 }
792
793 /**
794 * Array of SureForms blocks which get have user input.
795 *
796 * @since 0.0.10
797 * @return array<string>
798 */
799 public static function get_sureforms_blocks() {
800 return apply_filters(
801 'srfm_blocks',
802 [
803 'srfm/input',
804 'srfm/email',
805 'srfm/textarea',
806 'srfm/number',
807 'srfm/checkbox',
808 'srfm/gdpr',
809 'srfm/phone',
810 'srfm/address',
811 'srfm/dropdown',
812 'srfm/multi-choice',
813 'srfm/radio',
814 'srfm/submit',
815 'srfm/url',
816 ]
817 );
818 }
819
820 /**
821 * Process blocks and inner blocks.
822 *
823 * @param array<array<array<mixed>>> $blocks The block data.
824 * @param array<string> $slugs The array of existing slugs.
825 * @param bool $updated The array of existing slugs.
826 * @param string $prefix The array of existing slugs.
827 * @param boolean $skip_checking_existing_slug Skips the checking of existing slug if passed true. More information documented inside this function.
828 * @since 0.0.10
829 * @return array{array<array<array<mixed>>>,array<string>,bool}
830 */
831 public static function process_blocks( $blocks, &$slugs, &$updated, $prefix = '', $skip_checking_existing_slug = false ) {
832
833 if ( ! is_array( $blocks ) ) {
834 return [ $blocks, $slugs, $updated ];
835 }
836
837 foreach ( $blocks as $index => $block ) {
838
839 if ( ! is_array( $block ) ) {
840 continue;
841 }
842 // Checking only for SureForms blocks which can have user input.
843 if ( empty( $block['blockName'] ) || ! in_array( $block['blockName'], self::get_sureforms_blocks(), true ) ) {
844 continue;
845 }
846
847 /**
848 * Lets continue if slug already exists.
849 * This will ensure that we don't update already existing slugs.
850 */
851 if ( isset( $block['attrs'] ) && ! empty( $block['attrs']['slug'] ) && ! in_array( $block['attrs']['slug'], $slugs, true ) ) {
852
853 // Made it associative array, so that we can directly check it using block_id rather than mapping or using "in_array" for the checks.
854 $slugs[ $block['attrs']['block_id'] ] = self::get_string_value( $block['attrs']['slug'] );
855 continue;
856 }
857
858 if ( $skip_checking_existing_slug && empty( $block['innerBlocks'] ) && isset( $slugs[ $block['attrs']['block_id'] ] ) ) {
859 /**
860 * Skip re-processing of the already process or existing slugs if above parameter "$skip_checking_existing_slug" is passed as true.
861 * This is helpful in the scenarios where we need to compare and verify between already saved blocks and new unsaved blocks parsed
862 * from the contents.
863 *
864 * However, it is also necessary to make sure if that current block is not a parent / wrapper block
865 * by checking "$block['innerBlocks']" empty.
866 *
867 * And finally, checking if the block-id "$block['attrs']['block_id']" is already set in the list of "$slugs",
868 * making sure that we are only processing the new blocks.
869 */
870 continue;
871 }
872
873 if ( is_array( $blocks[ $index ]['attrs'] ) ) {
874
875 $blocks[ $index ]['attrs']['slug'] = self::generate_unique_block_slug( $block, $slugs, $prefix );
876 $slugs[ $block['attrs']['block_id'] ] = $blocks[ $index ]['attrs']['slug']; // Made it associative array, so that we can directly check it using block_id rather than mapping or using "in_array" for the checks.
877 $updated = true;
878 if ( is_array( $block['innerBlocks'] ) && ! empty( $block['innerBlocks'] ) ) {
879
880 list( $blocks[ $index ]['innerBlocks'], $slugs, $updated ) = self::process_blocks( $block['innerBlocks'], $slugs, $updated, $blocks[ $index ]['attrs']['slug'] );
881
882 }
883 }
884 }
885 return [ $blocks, $slugs, $updated ];
886 }
887
888 /**
889 * Generates slug based on the provided block and existing slugs.
890 *
891 * @param array<mixed> $block The block data.
892 * @param array<string> $slugs The array of existing slugs.
893 * @param string $prefix The array of existing slugs.
894 * @since 0.0.10
895 * @return string The generated unique block slug.
896 */
897 public static function generate_unique_block_slug( $block, $slugs, $prefix ) {
898 $slug = is_string( $block['blockName'] ) ? $block['blockName'] : '';
899
900 if ( ! empty( $block['attrs']['label'] ) && is_string( $block['attrs']['label'] ) ) {
901 $slug = sanitize_title( $block['attrs']['label'] );
902 }
903
904 if ( ! empty( $prefix ) ) {
905 $slug = $prefix . '-' . $slug;
906 }
907
908 $slug = self::generate_slug( $slug, $slugs );
909
910 return $slug;
911 }
912
913 /**
914 * This function ensures that the slug is unique.
915 * If the slug is already taken, it appends a number to the slug to make it unique.
916 *
917 * @param string $slug test to be converted to slug.
918 * @param array<string> $slugs An array of existing slugs.
919 * @since 0.0.10
920 * @return string The unique slug.
921 */
922 public static function generate_slug( $slug, $slugs ) {
923 $slug = sanitize_title( $slug );
924
925 if ( ! in_array( $slug, $slugs, true ) ) {
926 return $slug;
927 }
928
929 $index = 1;
930
931 while ( in_array( $slug . '-' . $index, $slugs, true ) ) {
932 $index++;
933 }
934
935 return $slug . '-' . $index;
936 }
937
938 /**
939 * Encode data to JSON. This function will encode the data with JSON_UNESCAPED_SLASHES and JSON_UNESCAPED_UNICODE.
940 *
941 * @since 0.0.11
942 * @param array<mixed> $data The data to encode.
943 * @return string|false The JSON representation of the value on success or false on failure.
944 */
945 public static function encode_json( $data ) {
946 return wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
947 }
948
949 /**
950 * Returns true if SureTriggers plugin is ready for the custom app.
951 *
952 * @since x.x.x
953 * @return bool Returns true if SureTriggers plugin is ready for the custom app.
954 */
955 public static function is_suretriggers_ready() {
956 if ( ! defined( 'SURE_TRIGGERS_FILE' ) ) {
957 // Probably plugin is de-activated or not installed at all.
958 return false;
959 }
960
961 $suretriggers_data = get_option( 'suretrigger_options', [] );
962 if ( ! is_array( $suretriggers_data ) || empty( $suretriggers_data['secret_key'] ) || ! is_string( $suretriggers_data['secret_key'] ) ) {
963 // SureTriggers is not authenticated yet.
964 return false;
965 }
966
967 return true;
968 }
969 }
970