PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.33
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.33
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / helpers / FrmFieldsHelper.php

FrmFieldsHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.33, at classes/helpers/FrmFieldsHelper.php

3,001 lines 84.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmFieldsHelper {
7
8 /**
9 * The context is memoized for re-use as the context is checked for each field.
10 *
11 * @var bool|null
12 */
13 private static $context_is_safe_to_load_field_options_from_request_data;
14
15 /**
16 * @param string $type
17 * @param int|string $form_id
18 *
19 * @return array
20 */
21 public static function setup_new_vars( $type = '', $form_id = '' ) {
22 if ( str_contains( $type, '|' ) ) {
23 list( $type, $setting ) = explode( '|', $type );
24 }
25
26 $values = self::get_default_field( $type );
27
28 global $wpdb;
29 $field_count = FrmDb::get_var(
30 'frm_fields',
31 array( 'form_id' => $form_id ),
32 'field_order',
33 array( 'order_by' => 'field_order DESC' )
34 );
35
36 $values['field_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_fields', 'field_key' );
37 $values['form_id'] = $form_id;
38 $values['field_order'] = $field_count + 1;
39
40 $values['field_options']['custom_html'] = self::get_default_html( $type );
41
42 if ( ! empty( $setting ) ) {
43 if ( in_array( $type, array( 'data', 'lookup' ), true ) ) {
44 $values['field_options']['data_type'] = $setting;
45 } else {
46 $values['field_options'][ $setting ] = 1;
47 }
48 }
49
50 // Increase the field order of submit field and fields in the same row.
51 FrmSubmitHelper::update_last_row_fields_order_when_adding_field( $field_count );
52
53 return $values;
54 }
55
56 /**
57 * @param array $field
58 * @param string $plus
59 *
60 * @return string
61 */
62 public static function get_html_id( $field, $plus = '' ) {
63 return apply_filters( 'frm_field_html_id', 'field_' . $field['field_key'] . $plus, $field );
64 }
65
66 /**
67 * @param object $field
68 * @param bool $doing_ajax
69 *
70 * @return array
71 */
72 public static function setup_edit_vars( $field, $doing_ajax = false ) {
73 $values = self::field_object_to_array( $field );
74 return apply_filters( 'frm_setup_edit_field_vars', $values, array( 'doing_ajax' => $doing_ajax ) );
75 }
76
77 /**
78 * @param object $field
79 *
80 * @return array
81 */
82 public static function field_object_to_array( $field ) {
83 $values = (array) $field;
84
85 self::fill_field_array( $field, $values );
86
87 $values['custom_html'] = $field->field_options['custom_html'] ?? self::get_default_html( $field->type );
88
89 return $values;
90 }
91
92 /**
93 * @param object $field
94 * @param array $field_array
95 *
96 * @return void
97 */
98 private static function fill_field_array( $field, array &$field_array ) {
99 $field_array['options'] = $field->options;
100 $field_array['value'] = $field->default_value;
101
102 self::prepare_edit_front_field( $field_array, $field );
103
104 $field_array = array_merge( (array) $field->field_options, $field_array );
105 }
106
107 /**
108 * Prepare field while creating a new entry.
109 *
110 * @since 3.0
111 *
112 * @param array $field_array Field values array, passed by reference.
113 * @param stdClass $field Field object.
114 * @param array $args Additional arguments.
115 *
116 * @return void
117 */
118 public static function prepare_new_front_field( &$field_array, $field, $args = array() ) {
119 $args['action'] = 'new';
120 self::prepare_front_field( $field_array, $field, $args );
121 }
122
123 /**
124 * Prepare field while editing an entry.
125 *
126 * @since 3.0
127 *
128 * @param array $field_array Field values array, passed by reference.
129 * @param array|stdClass $field Field object or array.
130 * @param int $entry_id Entry ID.
131 * @param array $args Additional arguments.
132 *
133 * @return void
134 */
135 public static function prepare_edit_front_field( &$field_array, $field, $entry_id = 0, $args = array() ) {
136 $args['entry_id'] = $entry_id;
137 $args['action'] = 'edit';
138 self::prepare_front_field( $field_array, $field, $args );
139 }
140
141 /**
142 * Prepare field while creating a new entry
143 *
144 * @since 3.0
145 *
146 * @param array $field_array
147 * @param stdClass $field
148 * @param array $args
149 *
150 * @return void
151 */
152 private static function prepare_front_field( &$field_array, $field, $args ) {
153 self::fill_default_field_opts( $field, $field_array );
154 self::fill_cleared_strings( $field, $field_array );
155
156 // Track the original field's type
157 $field_array['original_type'] = $field->field_options['original_type'] ?? $field->type;
158
159 self::prepare_field_options_for_display( $field_array, $field, $args );
160
161 if ( $args['action'] === 'edit' ) {
162 /**
163 * @param array $field_array
164 * @param stdClass $field
165 * @param int|string $entry_id
166 * @param array $args
167 */
168 $field_array = apply_filters( 'frm_setup_edit_fields_vars', $field_array, $field, $args['entry_id'], $args );
169
170 return;
171 }
172
173 /**
174 * @param array $field_array
175 * @param stdClass $field
176 * @param array $args
177 */
178 $field_array = apply_filters( 'frm_setup_new_fields_vars', $field_array, $field, $args );
179 }
180
181 /**
182 * @since 3.0
183 *
184 * @param string $type
185 *
186 * @return array
187 */
188 public static function get_default_field_options( $type ) {
189 $field_type = FrmFieldFactory::get_field_type( $type );
190 return $field_type->get_default_field_options();
191 }
192
193 /**
194 * @since 3.0
195 *
196 * @param object $field
197 * @param array $values
198 *
199 * @return void
200 */
201 private static function fill_default_field_opts( $field, array &$values ) {
202 $check_post = self::context_is_safe_to_load_field_options_from_request_data();
203 $defaults = self::get_default_field_options_from_field( $field, $values );
204
205 if ( ! $check_post ) {
206 $defaults['required_indicator'] = '';
207 $defaults['original_type'] = $field->type;
208 }
209
210 foreach ( $defaults as $opt => $default ) {
211 $values[ $opt ] = $field->field_options[ $opt ] ?? $default;
212
213 if ( $check_post ) {
214 self::get_posted_field_setting( $opt . '_' . $field->id, $values[ $opt ] );
215 }
216 }
217 }
218
219 /**
220 * The fill_default_field_opts method is called when loading a field.
221 * This is used to preserve the $_POST data after updating settings for a field.
222 * To prevent this from happening when creating an entry, we need to check the context.
223 *
224 * @return bool
225 */
226 private static function context_is_safe_to_load_field_options_from_request_data() {
227 if ( isset( self::$context_is_safe_to_load_field_options_from_request_data ) ) {
228 return self::$context_is_safe_to_load_field_options_from_request_data;
229 }
230
231 $function = function () {
232 if ( ! FrmAppHelper::is_admin_page() ) {
233 return false;
234 }
235
236 if ( ! $_POST || ! isset( $_POST['field_options'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
237 return false;
238 }
239
240 if ( ! current_user_can( 'frm_edit_forms' ) ) {
241 return false;
242 }
243
244 $action = FrmAppHelper::get_post_param( 'action', '', 'sanitize_title' );
245
246 if ( 'frm_forms_preview' === $action ) {
247 // Never trigger when previewing.
248 return false;
249 }
250
251 // Confirm an allowed action is being used, and that the correct nonce is being used.
252 if ( 'update' === $action ) {
253 $nonce = FrmAppHelper::get_post_param( 'frm_save_form', '', 'sanitize_text_field' );
254 return wp_verify_nonce( $nonce, 'frm_save_form_nonce' );
255 }
256
257 $action = FrmAppHelper::get_post_param( 'frm_action', '', 'sanitize_title' );
258
259 if ( 'update_settings' === $action ) {
260 $nonce = FrmAppHelper::get_post_param( 'process_form', '', 'sanitize_text_field' );
261 return wp_verify_nonce( $nonce, 'process_form_nonce' );
262 }
263 };
264
265 self::$context_is_safe_to_load_field_options_from_request_data = $function();
266
267 return self::$context_is_safe_to_load_field_options_from_request_data;
268 }
269
270 /**
271 * Fill the required message, invalid message,
272 * and refill the HTML when cleared
273 *
274 * @since 3.0
275 *
276 * @param object $field
277 * @param array $field_array
278 *
279 * @return void
280 */
281 private static function fill_cleared_strings( $field, array &$field_array ) {
282 // phpcs:ignore Universal.Operators.StrictComparisons
283 if ( '' == $field_array['blank'] && '1' === $field_array['required'] ) {
284 $field_array['blank'] = self::default_blank_msg();
285 }
286
287 if ( '' === $field_array['invalid'] ) {
288 if ( 'captcha' === $field->type ) {
289 $frm_settings = FrmAppHelper::get_settings();
290 $field_array['invalid'] = $frm_settings->re_msg;
291 } else {
292 $field_array['invalid'] = self::default_invalid_msg();
293 }
294 }
295
296 // phpcs:ignore Universal.Operators.StrictComparisons
297 if ( '' == $field_array['custom_html'] ) {
298 $field_array['custom_html'] = self::get_default_html( $field->type );
299 }
300 }
301
302 /**
303 * @since 6.8.3
304 *
305 * @return string
306 */
307 public static function default_invalid_msg() {
308 /* translators: %s: [field_name] shortcode (Which gets replaced by a Field Name) */
309 return sprintf( __( '%s is invalid', 'formidable' ), '[field_name]' );
310 }
311
312 /**
313 * @since 6.8.3
314 *
315 * @return string
316 */
317 public static function default_unique_msg() {
318 $frm_settings = FrmAppHelper::get_settings();
319 $unique_message = $frm_settings->unique_msg;
320 return str_replace( 'This value', '[field_name]', $unique_message );
321 }
322
323 /**
324 * @since 6.8.3
325 *
326 * @return string
327 */
328 public static function default_blank_msg() {
329 $frm_settings = FrmAppHelper::get_settings();
330 $blank_message = $frm_settings->blank_msg;
331 return str_replace( 'This field', '[field_name]', $blank_message );
332 }
333
334 /**
335 * When loading settings for a field, check the $_POST data and possibly use that instead of the DB value.
336 *
337 * @since 3.0
338 *
339 * @param string $setting
340 * @param mixed $value
341 *
342 * @return void
343 */
344 private static function get_posted_field_setting( $setting, &$value ) {
345 if ( ! isset( $_POST['field_options'][ $setting ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
346 return;
347 }
348
349 if ( str_contains( $setting, 'html' ) ) {
350 $value = wp_unslash( $_POST['field_options'][ $setting ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing, SlevomatCodingStandard.Files.LineLength.LineTooLong
351
352 // Conditionally strip script tags if the user sending $_POST data is not allowed to use unfiltered HTML.
353 if ( ! FrmAppHelper::allow_unfiltered_html() ) {
354 $value = FrmAppHelper::kses( $value, 'all' );
355 }
356 } elseif ( str_starts_with( $setting, 'format_' ) ) {
357 // TODO: Remove stripslashes on output, and use on input only.
358 $value = sanitize_text_field( $_POST['field_options'][ $setting ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Missing, SlevomatCodingStandard.Files.LineLength.LineTooLong
359 } else {
360 $value = wp_unslash( $_POST['field_options'][ $setting ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing, SlevomatCodingStandard.Files.LineLength.LineTooLong
361 FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
362 }
363 }
364
365 /**
366 * @since 3.0
367 *
368 * @param object $field
369 * @param array $values The field array is needed for hooks.
370 *
371 * @return array
372 */
373 public static function get_default_field_options_from_field( $field, $values = array() ) {
374 $field_type = self::get_original_field( $field );
375 $opts = $field_type->get_default_field_options();
376 $opts = apply_filters( 'frm_default_field_opts', $opts, $values, $field );
377
378 return apply_filters( 'frm_default_' . $field->type . '_field_opts', $opts, $values, $field );
379 }
380
381 /**
382 * @since 3.0
383 *
384 * @param object $field
385 *
386 * @return FrmFieldType
387 */
388 private static function get_original_field( $field ) {
389 $original_type = FrmField::get_option( $field, 'original_type' );
390
391 if ( $original_type && $field->type !== $original_type ) {
392 $field->type = $original_type;
393 }
394
395 return FrmFieldFactory::get_field_object( $field );
396 }
397
398 /**
399 * @since 3.0
400 *
401 * @param array $field_array
402 * @param object $field
403 * @param array $atts
404 *
405 * @return void
406 */
407 private static function prepare_field_options_for_display( &$field_array, $field, $atts ) {
408 $field_obj = FrmFieldFactory::get_field_object( $field );
409 $field_array = $field_obj->prepare_front_field( $field_array, $atts );
410 }
411
412 /**
413 * @since 3.0
414 *
415 * @param string $type
416 *
417 * @return array
418 */
419 public static function get_default_field( $type ) {
420 $field_type = FrmFieldFactory::get_field_type( $type );
421 return $field_type->get_new_field_defaults();
422 }
423
424 /**
425 * @param array $values
426 * @param object $field
427 * @param int|string $form_id
428 * @param string $new_key
429 *
430 * @return void
431 */
432 public static function fill_field( &$values, $field, $form_id, $new_key = '' ) {
433 global $wpdb;
434
435 $values['field_key'] = FrmAppHelper::get_unique_key( $new_key, $wpdb->prefix . 'frm_fields', 'field_key' );
436 $values['form_id'] = $form_id;
437 $values['options'] = maybe_serialize( $field->options );
438 $values['default_value'] = FrmAppHelper::maybe_json_encode( $field->default_value );
439
440 foreach ( array( 'name', 'description', 'type', 'field_order', 'field_options', 'required' ) as $col ) {
441 $values[ $col ] = $field->{$col};
442 }
443 }
444
445 /**
446 * @since 2.0
447 *
448 * @param array|object $field
449 * @param string $error
450 *
451 * @return string
452 */
453 public static function get_error_msg( $field, $error ) {
454 $frm_settings = FrmAppHelper::get_settings();
455 $conf_msg = __( 'The entered values do not match', 'formidable' );
456 $defaults = array(
457 'unique_msg' => array(
458 'full' => self::default_unique_msg(),
459 /* translators: %s: Field name */
460 'part' => sprintf( __( '%s must be unique', 'formidable' ), '[field_name]' ),
461 ),
462 'invalid' => array(
463 'full' => __( 'This field is invalid', 'formidable' ),
464 /* translators: %s: Field name */
465 'part' => sprintf( __( '%s is invalid', 'formidable' ), '[field_name]' ),
466 ),
467 'blank' => array(
468 'full' => $frm_settings->blank_msg,
469 'part' => $frm_settings->blank_msg,
470 ),
471 'conf_msg' => array(
472 'full' => $conf_msg,
473 'part' => $conf_msg,
474 ),
475 );
476
477 /**
478 * @since 6.28
479 *
480 * @param array $defaults
481 * @param array|object $field
482 */
483 $defaults = apply_filters( 'frm_default_field_validation_messages', $defaults, $field );
484
485 $msg = FrmField::get_option( $field, $error );
486 $msg = $msg ? $msg : $defaults[ $error ]['part'];
487 $msg = do_shortcode( $msg );
488
489 return self::maybe_replace_substrings_with_field_name( $msg, $error, $field );
490 }
491
492 /**
493 * @since 6.8.3
494 *
495 * @param string $msg
496 * @param string $error
497 * @param array|object $field
498 *
499 * @return string
500 */
501 private static function maybe_replace_substrings_with_field_name( $msg, $error, $field ) {
502 $field_name = is_array( $field ) ? $field['name'] : $field->name;
503 $field_name = FrmAppHelper::maybe_kses( $field_name );
504 $substrings = self::get_substrings_to_replace_with_field_name( $field_name, compact( 'msg', 'error', 'field' ) );
505
506 // Use the "This value"/"This field" placeholder strings if field name is empty.
507 if ( ! $field_name ) {
508 $field_name = 'unique_msg' === $error ? __( 'This value', 'formidable' ) : __( 'This field', 'formidable' );
509 }
510
511 return str_replace( $substrings, $field_name, $msg );
512 }
513
514 /**
515 * @since 6.8.3
516 *
517 * @param string $field_name
518 * @param array $filter_args {
519 * Filter arguments.
520 *
521 * @type string $msg The current error message before the substrings are replaced.
522 * @type string $error A key including 'unique_msg', 'invalid', 'blank', or 'conf_msg'.
523 * @type array|object $field The field with the error.
524 * }
525 *
526 * @return array
527 */
528 private static function get_substrings_to_replace_with_field_name( $field_name, $filter_args ) {
529 $substrings = array( '[field_name]' );
530
531 if ( $field_name ) {
532 array_push( $substrings, 'This value', 'This field' );
533 }
534
535 /**
536 * @since 6.8.3
537 *
538 * @param array<string> $substrings
539 * @param array $filter_args
540 */
541 $filtered_substrings = apply_filters( 'frm_error_substrings_to_replace_with_field_name', $substrings, $filter_args );
542
543 if ( is_array( $filtered_substrings ) ) {
544 $substrings = $filtered_substrings;
545 } else {
546 _doing_it_wrong( __METHOD__, 'Only arrays should be returned when using the frm_error_substrings_to_replace_with_field_name filter.', '6.8.3' );
547 }
548
549 return $substrings;
550 }
551
552 /**
553 * @param int|string $form_id
554 * @param array $error
555 *
556 * @return array
557 */
558 public static function get_form_fields( $form_id, $error = array() ) {
559 $fields = FrmField::get_all_for_form( $form_id );
560
561 FrmFieldName::track_first_name_field( $fields );
562
563 return apply_filters( 'frm_get_paged_fields', $fields, $form_id, $error );
564 }
565
566 /**
567 * @param string $type
568 *
569 * @return string
570 */
571 public static function get_default_html( $type = 'text' ) {
572 $field = FrmFieldFactory::get_field_type( $type );
573 $default_html = $field->default_html();
574
575 // These hooks are here for reverse compatibility since 3.0
576 if ( ! apply_filters( 'frm_normal_field_type_html', true, $type ) ) {
577 $default_html = apply_filters( 'frm_other_custom_html', '', $type );
578 }
579
580 return apply_filters( 'frm_custom_html', $default_html, $type );
581 }
582
583 /**
584 * @param array $fields
585 * @param array $errors
586 * @param object $form
587 * @param object $form_action
588 *
589 * @return void
590 */
591 public static function show_fields( $fields, $errors, $form, $form_action ) {
592 foreach ( $fields as $field ) {
593 if ( ! is_array( $field ) ) {
594 continue;
595 }
596
597 $field_obj = FrmFieldFactory::get_field_type( $field['type'], $field );
598 $field_obj->show_field( compact( 'errors', 'form', 'form_action' ) );
599 }
600 }
601
602 /**
603 * @since 3.0
604 *
605 * @param array $atts
606 * @param array|string $value
607 *
608 * @return void
609 */
610 public static function run_wpautop( $atts, &$value ) {
611 $autop = $atts['wpautop'] ?? true;
612
613 if ( ! apply_filters( 'frm_use_wpautop', $autop ) ) {
614 return;
615 }
616
617 if ( is_array( $value ) ) {
618 $value = implode( "\n", $value );
619 }
620
621 $value = wpautop( $value );
622 }
623
624 /**
625 * Get the class to use for the label position
626 *
627 * @since 2.05
628 *
629 * @param string $position
630 * @param array $field
631 * @param bool|object|string $form
632 *
633 * @return string
634 */
635 public static function label_position( $position, $field, $form ) {
636 if ( $position ) {
637 if ( $position === 'inside' && ! self::is_placeholder_field_type( $field['type'] ) ) {
638 return 'top';
639 }
640
641 return $position;
642 }
643
644 $position = FrmStylesController::get_style_val( 'position', $form );
645
646 if ( $position === 'none' ) {
647 $position = 'top';
648 } elseif ( $position === 'no_label' ) {
649 $position = 'none';
650 } elseif ( $position === 'inside' && ! self::is_placeholder_field_type( $field['type'] ) ) {
651 $position = 'top';
652 }
653
654 $position = apply_filters( 'frm_html_label_position', $position, $field, $form );
655 return $position ? $position : 'top';
656 }
657
658 /**
659 * Check if this field type allows placeholders
660 *
661 * @since 2.05
662 *
663 * @param string $type
664 *
665 * @return bool
666 */
667 public static function is_placeholder_field_type( $type ) {
668 return ! in_array( $type, array( 'radio', 'checkbox', 'hidden', 'file' ), true );
669 }
670
671 /**
672 * @param array $field
673 * @param int|string $opt_key
674 * @param string $type
675 *
676 * @return string
677 */
678 public static function get_checkbox_id( $field, $opt_key, $type = 'checkbox' ) {
679 $id = $field['id'];
680
681 if ( ! empty( $field['in_section'] ) && ! FrmAppHelper::is_admin_page( 'formidable' ) ) {
682 $id .= '-' . $field['in_section'];
683 }
684
685 return 'frm_' . $type . '_' . $id . '-' . $opt_key;
686 }
687
688 /**
689 * @param array $field
690 *
691 * @return void
692 */
693 public static function show_single_option( $field ) {
694 self::hidden_field_option( $field );
695
696 if ( ! is_array( $field['options'] ) ) {
697 return;
698 }
699
700 $base_name = 'default_value_' . $field['id'];
701 $html_id = $field['html_id'] ?? self::get_html_id( $field );
702 $default_type = self::get_default_value_type( $field );
703 $options_count = count( $field['options'] );
704
705 foreach ( $field['options'] as $opt_key => $opt ) {
706 $field_val = self::get_value_from_array( $opt, $opt_key, $field );
707 $opt = self::get_label_from_array( $opt, $opt_key, $field );
708 $field_name = $base_name . ( $default_type === 'checkbox' ? '[' . $opt_key . ']' : '' );
709
710 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict, Universal.Operators.StrictComparisons, SlevomatCodingStandard.Files.LineLength.LineTooLong
711 $checked = isset( $field['default_value'] ) && ( is_array( $field['default_value'] ) ? in_array( $field_val, $field['default_value'] ) : $field['default_value'] == $field_val );
712
713 // If this is an "Other" option, get the HTML for it.
714 if ( self::is_other_opt( $opt_key ) ) {
715 if ( FrmAppHelper::pro_is_installed() ) {
716 require FrmProAppHelper::plugin_path() . '/classes/views/frmpro-fields/other-option.php';
717 }
718 } else {
719 require FrmAppHelper::plugin_path() . '/classes/views/frm-fields/single-option.php';
720 }
721
722 unset( $checked );
723 }
724 }
725
726 /**
727 * Include hidden row for javascript to duplicate.
728 *
729 * @since 4.0
730 *
731 * @param array $field
732 *
733 * @return void
734 */
735 private static function hidden_field_option( $field ) {
736 // Don't duplicate during an ajax add option.
737 $ajax_action = FrmAppHelper::get_param( 'action', '', 'post', 'sanitize_text_field' );
738
739 if ( $ajax_action === 'frm_add_field_option' ) {
740 return;
741 }
742
743 $opt_key = '000';
744 $field_val = __( 'New Option', 'formidable' );
745 $opt = __( 'New Option', 'formidable' );
746 $checked = false;
747 $field_name = 'default_value_' . $field['id'];
748 $html_id = $field['html_id'] ?? self::get_html_id( $field );
749 $default_type = self::get_default_value_type( $field );
750 $field_name .= $default_type === 'checkbox' ? '[' . $opt_key . ']' : '';
751
752 require FrmAppHelper::plugin_path() . '/classes/views/frm-fields/single-option.php';
753 }
754
755 /**
756 * @since 4.0
757 *
758 * @param array $field
759 *
760 * @return string radio or checkbox
761 */
762 private static function get_default_value_type( $field ) {
763 if ( $field['type'] === 'select' ) {
764 return FrmField::is_multiple_select( $field ) ? 'checkbox' : 'radio';
765 }
766 return $field['type'];
767 }
768
769 /**
770 * @param array|string $opt
771 * @param int|string $opt_key
772 * @param array $field
773 *
774 * @return mixed
775 */
776 public static function get_value_from_array( $opt, $opt_key, $field ) {
777 $opt = apply_filters( 'frm_field_value_saved', $opt, $opt_key, $field );
778 return FrmFieldsController::check_value( $opt, $opt_key, $field );
779 }
780
781 /**
782 * @param array|string $opt
783 * @param int|string $opt_key
784 * @param array $field
785 *
786 * @return string
787 */
788 public static function get_label_from_array( $opt, $opt_key, $field ) {
789 $opt = apply_filters( 'frm_field_label_seen', $opt, $opt_key, $field );
790 return FrmFieldsController::check_label( $opt );
791 }
792
793 /**
794 * Shows the inline modal.
795 *
796 * @since 4.0
797 * @since 6.4.1 Added `inside_class` in the arguments.
798 *
799 * @param array $args The arguments.
800 *
801 * @return void
802 */
803 public static function inline_modal( $args ) {
804 $defaults = array(
805 'id' => '',
806 'class' => '',
807 'show' => 0,
808 'callback' => array(),
809 'args' => array(),
810 'title' => '',
811 'inside_class' => 'inside',
812 'dismiss-icon' => true,
813 );
814 $args = array_merge( $defaults, $args );
815
816 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/inline-modal.php';
817 }
818
819 /**
820 * @since 4.0
821 *
822 * @return void
823 */
824 public static function smart_values() {
825 $continue = apply_filters( 'frm_smart_values_box', true );
826
827 if ( $continue !== true ) {
828 return;
829 }
830
831 $upgrade_link = array(
832 'medium' => 'builder',
833 'content' => 'smart-tags',
834 );
835 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/smart-values.php';
836 }
837
838 /**
839 * @since 4.0
840 *
841 * @return void
842 */
843 public static function input_mask() {
844 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/input-mask-info.php';
845 }
846
847 /**
848 * @since 4.0
849 *
850 * @return void
851 */
852 public static function layout_classes() {
853 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/layout-classes.php';
854 }
855
856 /**
857 * @param int $tax_id
858 *
859 * @return string
860 */
861 public static function get_term_link( $tax_id ) {
862 $tax = get_taxonomy( $tax_id );
863
864 if ( ! $tax ) {
865 return '';
866 }
867
868 return sprintf(
869 /* translators: %1$s: Start HTML link, %2$s: Content type label, %3$s: Content type, %4$s: End HTML link */
870 esc_html__( 'Options are dynamically created from your %1$s%2$s: %3$s%4$s', 'formidable' ),
871 '<a href="' . esc_url( admin_url( 'edit-tags.php?taxonomy=' . $tax->name ) ) . '" target="_blank">',
872 esc_html__( 'taxonomy', 'formidable' ),
873 ! empty( $tax->labels->name ) ? $tax->labels->name : esc_html__( 'Categories', 'formidable' ),
874 '</a>'
875 );
876 }
877
878 /**
879 * @param array|string $observed_value
880 * @param string $cond
881 * @param array|string $hide_opt
882 *
883 * @return bool
884 */
885 public static function value_meets_condition( $observed_value, $cond, $hide_opt ) {
886 $hide_opt = self::get_value_for_comparison( $hide_opt );
887 $observed_value = self::get_value_for_comparison( $observed_value );
888
889 if ( is_array( $observed_value ) ) {
890 return self::array_value_condition( $observed_value, $cond, $hide_opt );
891 }
892
893 $m = false;
894
895 if ( $cond === '==' ) {
896 $m = $observed_value == $hide_opt; // phpcs:ignore Universal.Operators.StrictComparisons
897 } elseif ( $cond === '!=' ) {
898 $m = $observed_value != $hide_opt; // phpcs:ignore Universal.Operators.StrictComparisons
899 } elseif ( $cond === '>' ) {
900 $m = $observed_value > $hide_opt;
901 } elseif ( $cond === '>=' ) {
902 $m = $observed_value >= $hide_opt;
903 } elseif ( $cond === '<' ) {
904 $m = $observed_value < $hide_opt;
905 } elseif ( $cond === '<=' ) {
906 $m = $observed_value <= $hide_opt;
907 } elseif ( $cond === 'LIKE' || $cond === 'not LIKE' ) {
908 $m = stripos( $observed_value, $hide_opt );
909 $m = $cond === 'not LIKE' ? $m === false : $m !== false;
910 } elseif ( $cond === '%LIKE' ) {
911 // Ends with
912 $length = strlen( $hide_opt );
913 $substr = substr( $observed_value, strlen( $observed_value ) - $length );
914 $m = 0 === strcasecmp( $substr, $hide_opt );
915 } elseif ( 'LIKE%' === $cond ) {
916 // Starts with
917 $length = strlen( $hide_opt );
918 $substr = substr( $observed_value, 0, $length );
919 $m = 0 === strcasecmp( $substr, $hide_opt );
920 }//end if
921
922 return $m;
923 }
924
925 /**
926 * Trim and sanitize the values
927 *
928 * @since 2.05
929 *
930 * @param array|string $value
931 *
932 * @return array|string
933 */
934 private static function get_value_for_comparison( $value ) {
935 // Remove white space from hide_opt
936 if ( ! is_array( $value ) ) {
937 $value = trim( $value );
938 }
939
940 FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
941
942 return $value;
943 }
944
945 /**
946 * @param array $observed_value
947 * @param string $cond
948 * @param array|string $hide_opt
949 *
950 * @return bool
951 */
952 public static function array_value_condition( $observed_value, $cond, $hide_opt ) {
953 $m = false;
954
955 if ( $cond === '==' ) {
956 if ( is_array( $hide_opt ) ) {
957 $m = array_intersect( $hide_opt, $observed_value );
958 $m = $m !== array();
959 } else {
960 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
961 $m = in_array( $hide_opt, $observed_value );
962 }
963 } elseif ( $cond === '!=' ) {
964 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
965 $m = ! in_array( $hide_opt, $observed_value );
966 } elseif ( $cond === '>' ) {
967 $m = min( $observed_value ) > $hide_opt;
968 } elseif ( $cond === '<' ) {
969 $m = max( $observed_value ) < $hide_opt;
970 } elseif ( $cond === 'LIKE' || $cond === 'not LIKE' ) {
971 foreach ( $observed_value as $ob ) {
972 $m = strpos( $ob, $hide_opt );
973
974 if ( $m !== false ) {
975 $m = true;
976 break;
977 }
978 }
979
980 if ( $cond === 'not LIKE' ) {
981 $m = $m === false;
982 }
983 } elseif ( $cond === '%LIKE' ) {
984 // Ends with
985 foreach ( $observed_value as $ob ) {
986 if ( $hide_opt === substr( $ob, strlen( $ob ) - strlen( $hide_opt ) ) ) {
987 $m = true;
988 break;
989 }
990 }
991 } elseif ( $cond === 'LIKE%' ) {
992 // Starts with
993 foreach ( $observed_value as $ob ) {
994 if ( str_starts_with( $ob, $hide_opt ) ) {
995 $m = true;
996 break;
997 }
998 }
999 }//end if
1000
1001 return $m;
1002 }
1003
1004 /**
1005 * Replace a few basic shortcodes and field ids
1006 *
1007 * @since 2.0
1008 *
1009 * @param string $value
1010 * @param object $form
1011 * @param object $entry
1012 *
1013 * @return string
1014 */
1015 public static function basic_replace_shortcodes( $value, $form, $entry ) {
1016 if ( str_contains( $value, '[sitename]' ) ) {
1017 $new_value = wp_specialchars_decode( FrmAppHelper::site_name(), ENT_QUOTES );
1018 $value = str_replace( '[sitename]', $new_value, $value );
1019 }
1020
1021 $value = apply_filters( 'frm_content', $value, $form, $entry );
1022
1023 return do_shortcode( $value );
1024 }
1025
1026 /**
1027 * @param string $content
1028 * @param int|string $form_id
1029 *
1030 * @return array
1031 */
1032 public static function get_shortcodes( $content, $form_id ) {
1033 if ( FrmAppHelper::pro_is_installed() ) {
1034 return FrmProDisplaysHelper::get_shortcodes( $content, $form_id );
1035 }
1036
1037 $fields = FrmField::getAll(
1038 array(
1039 'fi.form_id' => (int) $form_id,
1040 'fi.type not' => FrmField::no_save_fields(),
1041 )
1042 );
1043
1044 $tagregexp = self::allowed_shortcodes( $fields );
1045
1046 preg_match_all( "/\[(if )?($tagregexp)\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?/s", $content, $matches, PREG_PATTERN_ORDER );
1047
1048 return $matches;
1049 }
1050
1051 /**
1052 * @param array $fields
1053 *
1054 * @return string
1055 */
1056 public static function allowed_shortcodes( $fields = array() ) {
1057 $tagregexp = array(
1058 'editlink',
1059 'id',
1060 'key',
1061 'ip',
1062 'siteurl',
1063 'sitename',
1064 'admin_email',
1065 'default-email',
1066 'default-from-email',
1067 'post[-|_]id',
1068 'created[-|_]at',
1069 'updated[-|_]at',
1070 'updated[-|_]by',
1071 'parent[-|_]id',
1072 'form_name',
1073 );
1074
1075 foreach ( $fields as $field ) {
1076 $tagregexp[] = $field->id;
1077 $tagregexp[] = $field->field_key;
1078 }
1079
1080 return implode( '|', $tagregexp );
1081 }
1082
1083 /**
1084 * @param string $content
1085 * @param false|object $entry
1086 * @param array $shortcodes
1087 *
1088 * @return string
1089 */
1090 public static function replace_content_shortcodes( $content, $entry, $shortcodes ) {
1091 foreach ( $shortcodes[0] as $short_key => $tag ) {
1092 if ( ! $tag ) {
1093 continue;
1094 }
1095
1096 $atts = FrmShortcodeHelper::get_shortcode_attribute_array( $shortcodes[3][ $short_key ] );
1097 $tag = FrmShortcodeHelper::get_shortcode_tag( $shortcodes, $short_key );
1098
1099 $atts['entry'] = $entry;
1100 $atts['tag'] = $tag;
1101 $replace_with = self::get_value_for_shortcode( $atts );
1102
1103 if ( $replace_with !== null ) {
1104 $replace_with = self::trigger_shortcode_atts( $replace_with, $atts );
1105 self::sanitize_embedded_shortcodes( compact( 'entry' ), $replace_with );
1106 $content = str_replace( $shortcodes[0][ $short_key ], $replace_with, $content );
1107 }
1108
1109 unset( $atts, $replace_with );
1110 }
1111
1112 return $content;
1113 }
1114
1115 /**
1116 * @param string $replace_with
1117 * @param array $atts
1118 *
1119 * @return string
1120 */
1121 private static function trigger_shortcode_atts( $replace_with, $atts ) {
1122 $supported_atts = array( 'remove_accents', 'sanitize', 'sanitize_url' );
1123 $included_atts = array_intersect( $supported_atts, array_keys( $atts ) );
1124
1125 foreach ( $included_atts as $included_att ) {
1126 if ( '0' === $atts[ $included_att ] ) {
1127 // Skip any option that uses 0 so sanitize_url=0 does not encode.
1128 continue;
1129 }
1130
1131 $function = 'atts_' . $included_att;
1132 $replace_with = self::$function( $replace_with );
1133 }
1134
1135 return $replace_with;
1136 }
1137
1138 /**
1139 * Converts all accent characters to ASCII characters.
1140 *
1141 * @since 6.3.1
1142 *
1143 * @param string $replace_with The text to remove accents from.
1144 *
1145 * @return string
1146 */
1147 public static function atts_remove_accents( $replace_with ) {
1148 return remove_accents( $replace_with );
1149 }
1150
1151 /**
1152 * @param string $replace_with
1153 *
1154 * @return string
1155 */
1156 private static function atts_sanitize( $replace_with ) {
1157 return sanitize_title_with_dashes( $replace_with );
1158 }
1159
1160 /**
1161 * @param string $replace_with
1162 *
1163 * @return string
1164 */
1165 private static function atts_sanitize_url( $replace_with ) {
1166 return urlencode( $replace_with );
1167 }
1168
1169 /**
1170 * Prevent shortcodes in fields from being processed
1171 *
1172 * @since 3.01.02
1173 *
1174 * @param array $atts Includes entry object.
1175 * @param string|null $value
1176 *
1177 * @return void
1178 */
1179 public static function sanitize_embedded_shortcodes( $atts, &$value ) {
1180 if ( is_null( $value ) ) {
1181 return;
1182 }
1183
1184 $atts['value'] = $value;
1185 $should_sanitize = apply_filters( 'frm_sanitize_shortcodes', true, $atts );
1186
1187 if ( $should_sanitize ) {
1188 $value = str_replace( '[', '&#91;', $value );
1189 }
1190 }
1191
1192 /**
1193 * @since 3.0
1194 *
1195 * @param array $atts
1196 *
1197 * @return string
1198 */
1199 private static function get_value_for_shortcode( $atts ) {
1200 $clean_tag = str_replace( '-', '_', $atts['tag'] );
1201
1202 $shortcode_values = array(
1203 'id' => $atts['entry']->id,
1204 'key' => $atts['entry']->item_key,
1205 'ip' => $atts['entry']->ip,
1206 );
1207
1208 $dynamic_default = array( 'admin_email', 'siteurl', 'frmurl', 'sitename', 'get', 'default-email', 'default-from-email' );
1209
1210 if ( isset( $shortcode_values[ $atts['tag'] ] ) ) {
1211 $replace_with = $shortcode_values[ $atts['tag'] ];
1212 } elseif ( in_array( $atts['tag'], $dynamic_default, true ) ) {
1213 $replace_with = self::dynamic_default_values( $atts['tag'], $atts );
1214 } elseif ( $clean_tag === 'user_agent' ) {
1215 $description = $atts['entry']->description;
1216 $replace_with = FrmEntriesHelper::get_browser( $description['browser'] );
1217 } elseif ( $clean_tag === 'created_at' || $clean_tag === 'updated_at' ) {
1218 $atts['tag'] = $clean_tag;
1219 $replace_with = self::get_entry_timestamp( $atts );
1220 } elseif ( $clean_tag === 'created_by' || $clean_tag === 'updated_by' ) {
1221 $replace_with = self::get_display_value( $atts['entry']->{$clean_tag}, (object) array( 'type' => 'user_id' ), $atts );
1222 } else {
1223 $replace_with = self::get_field_shortcode_value( $atts );
1224 }
1225
1226 return $replace_with;
1227 }
1228
1229 /**
1230 * @since 3.0
1231 *
1232 * @param array $atts
1233 *
1234 * @return string
1235 */
1236 private static function get_entry_timestamp( $atts ) {
1237 if ( isset( $atts['format'] ) ) {
1238 $time_format = ' ';
1239 } else {
1240 $atts['format'] = get_option( 'date_format' );
1241 $time_format = '';
1242 }
1243
1244 return FrmAppHelper::get_formatted_time( $atts['entry']->{$atts['tag']}, $atts['format'], $time_format );
1245 }
1246
1247 /**
1248 * @since 3.0
1249 *
1250 * @param array $atts
1251 *
1252 * @return string|null
1253 */
1254 private static function get_field_shortcode_value( $atts ) {
1255 $field = FrmField::getOne( $atts['tag'] );
1256
1257 if ( ! $field ) {
1258 return null;
1259 }
1260
1261 if ( isset( $atts['show'] ) && $atts['show'] === 'field_label' ) {
1262 $replace_with = $field->name;
1263 } elseif ( isset( $atts['show'] ) && $atts['show'] === 'description' ) {
1264 $replace_with = $field->description;
1265 } else {
1266 $replace_with = FrmEntryMeta::get_meta_value( $atts['entry'], $field->id );
1267 $string_value = $replace_with;
1268
1269 if ( is_array( $replace_with ) ) {
1270 $sep = $atts['sep'] ?? ', ';
1271 $string_value = FrmAppHelper::safe_implode( $sep, $replace_with );
1272 }
1273
1274 // phpcs:ignore Universal.Operators.StrictComparisons
1275 if ( ! $string_value && $string_value != '0' ) {
1276 $replace_with = '';
1277 } else {
1278 $atts['entry_id'] = $atts['entry']->id;
1279 $atts['entry_key'] = $atts['entry']->item_key;
1280 $replace_with = self::get_display_value( $replace_with, $field, $atts );
1281 }
1282 }//end if
1283
1284 return $replace_with;
1285 }
1286
1287 /**
1288 * Get the value to replace a few standard shortcodes
1289 *
1290 * @since 2.0
1291 *
1292 * @param string $tag
1293 * @param array $atts
1294 * @param bool $return_array
1295 *
1296 * @return string
1297 */
1298 public static function dynamic_default_values( $tag, $atts = array(), $return_array = false ) {
1299 $new_value = '';
1300 switch ( $tag ) {
1301 case 'admin_email':
1302 $new_value = get_option( 'admin_email' );
1303 break;
1304 case 'default-email':
1305 $frm_settings = FrmAppHelper::get_settings();
1306 $new_value = ! empty( $frm_settings->default_email ) && is_email( $frm_settings->default_email ) ? $frm_settings->default_email : get_option( 'admin_email' );
1307 break;
1308 case 'default-from-email':
1309 $frm_settings = FrmAppHelper::get_settings();
1310 $new_value = ! empty( $frm_settings->from_email ) && is_email( $frm_settings->from_email ) ? $frm_settings->from_email : get_option( 'admin_email' );
1311 break;
1312 case 'siteurl':
1313 $new_value = FrmAppHelper::site_url();
1314 break;
1315 case 'frmurl':
1316 $new_value = FrmAppHelper::plugin_url();
1317 break;
1318 case 'sitename':
1319 $new_value = FrmAppHelper::site_name();
1320 break;
1321 case 'get':
1322 $new_value = self::process_get_shortcode( $atts, $return_array );
1323 }//end switch
1324
1325 return $new_value;
1326 }
1327
1328 /**
1329 * Process the [get] shortcode
1330 *
1331 * @since 2.0
1332 *
1333 * @param array $atts
1334 * @param bool $return_array
1335 *
1336 * @return array|string
1337 */
1338 public static function process_get_shortcode( $atts, $return_array = false ) {
1339 if ( ! isset( $atts['param'] ) ) {
1340 return '';
1341 }
1342
1343 if ( str_contains( $atts['param'], '&#91;' ) ) {
1344 $atts['param'] = str_replace( '&#91;', '[', $atts['param'] );
1345 $atts['param'] = str_replace( '&#93;', ']', $atts['param'] );
1346 }
1347
1348 $new_value = FrmAppHelper::get_param( $atts['param'], '', 'get', 'sanitize_text_field' );
1349 $new_value = FrmAppHelper::get_query_var( $new_value, $atts['param'] );
1350
1351 // phpcs:ignore Universal.Operators.StrictComparisons
1352 if ( $new_value == '' ) {
1353 if ( ! isset( $atts['prev_val'] ) ) {
1354 $atts['prev_val'] = '';
1355 }
1356
1357 $new_value = $atts['default'] ?? $atts['prev_val'];
1358 }
1359
1360 if ( is_array( $new_value ) && ! $return_array ) {
1361 return implode( ', ', $new_value );
1362 }
1363
1364 return $new_value;
1365 }
1366
1367 /**
1368 * @param mixed $value
1369 * @param object $field
1370 * @param array $atts
1371 *
1372 * @return mixed
1373 */
1374 public static function get_display_value( $value, $field, $atts = array() ) {
1375 $value = apply_filters( 'frm_get_' . $field->type . '_display_value', $value, $field, $atts );
1376 $value = apply_filters( 'frm_get_display_value', $value, $field, $atts );
1377 $value = self::get_unfiltered_display_value( compact( 'value', 'field', 'atts' ) );
1378
1379 return apply_filters( 'frm_display_value', $value, $field, $atts );
1380 }
1381
1382 /**
1383 * @param array $atts Includes value, field, and atts.
1384 *
1385 * @return mixed
1386 */
1387 public static function get_unfiltered_display_value( $atts ) {
1388 $value = $atts['value'];
1389 $field = $atts['field'];
1390 $atts = $atts['atts'] ?? $atts;
1391
1392 if ( is_array( $field ) ) {
1393 $field = $field['id'];
1394 }
1395
1396 $field_obj = FrmFieldFactory::get_field_object( $field );
1397
1398 return $field_obj->get_display_value( $value, $atts );
1399 }
1400
1401 /**
1402 * Get a value from the user profile from the user ID
1403 *
1404 * @since 3.0
1405 *
1406 * @param int|string $user_id
1407 * @param string $user_info
1408 * @param array $args
1409 *
1410 * @return string
1411 */
1412 public static function get_user_display_name( $user_id, $user_info = 'display_name', $args = array() ) {
1413 $defaults = array(
1414 'blank' => false,
1415 'link' => false,
1416 'size' => 96,
1417 );
1418
1419 $args = wp_parse_args( $args, $defaults );
1420 $user = get_userdata( $user_id );
1421 $info = '';
1422
1423 if ( $user ) {
1424 if ( $user_info === 'avatar' ) {
1425 $info = get_avatar( $user_id, $args['size'] );
1426 } elseif ( $user_info === 'author_link' ) {
1427 $info = get_author_posts_url( $user_id );
1428 } else {
1429 $info = $user->$user_info ?? '';
1430 }
1431
1432 if ( 'display_name' === $user_info && ! $info && ! $args['blank'] ) {
1433 $info = $user->user_login;
1434 }
1435 }
1436
1437 if ( $args['link'] ) {
1438 return '<a href="' . esc_url( admin_url( 'user-edit.php?user_id=' . $user_id ) ) . '">' . $info . '</a>';
1439 }
1440
1441 return $info;
1442 }
1443
1444 /**
1445 * @param string $type
1446 *
1447 * @return array
1448 */
1449 public static function get_field_types( $type ) {
1450 $single_input = self::single_input_fields();
1451 $multiple_input = array( 'radio', 'checkbox', 'select', 'scale', 'star', 'lookup' );
1452 $field_selection = FrmField::all_field_selection();
1453 $field_types = array();
1454
1455 if ( in_array( $type, $single_input, true ) ) {
1456 self::field_types_for_input( $single_input, $field_selection, $field_types );
1457 } elseif ( in_array( $type, $multiple_input, true ) ) {
1458 self::field_types_for_input( $multiple_input, $field_selection, $field_types );
1459 } elseif ( isset( $field_selection[ $type ] ) ) {
1460 $field_types[ $type ] = $field_selection[ $type ];
1461 }
1462
1463 return apply_filters( 'frm_switch_field_types', $field_types, compact( 'type', 'field_selection' ) );
1464 }
1465
1466 /**
1467 * Get a list of all fields that use a single value input.
1468 *
1469 * @since 4.0
1470 *
1471 * @return array
1472 */
1473 public static function single_input_fields() {
1474 $fields = array(
1475 'text',
1476 'textarea',
1477 'rte',
1478 'number',
1479 'email',
1480 'url',
1481 'date',
1482 'phone',
1483 'hidden',
1484 'time',
1485 'tag',
1486 'password',
1487 'gdpr',
1488 );
1489 return apply_filters( 'frm_single_input_fields', $fields );
1490 }
1491
1492 /**
1493 * @param string[] $inputs
1494 * @param array $fields
1495 * @param array $field_types
1496 *
1497 * @return void
1498 */
1499 private static function field_types_for_input( $inputs, $fields, &$field_types ) {
1500 foreach ( $inputs as $input ) {
1501 // This may not be set if a field type was removed using the frm_available_fields or frm_pro_available_fields filters.
1502 if ( array_key_exists( $input, $fields ) ) {
1503 $field_types[ $input ] = $fields[ $input ];
1504 }
1505 unset( $input );
1506 }
1507 }
1508
1509 /**
1510 * Check if current field option is an "other" option
1511 *
1512 * @since 2.0.6
1513 *
1514 * @param string $opt_key
1515 *
1516 * @return bool Returns true if current field option is an "Other" option
1517 */
1518 public static function is_other_opt( $opt_key ) {
1519 return $opt_key && str_starts_with( $opt_key, 'other_' );
1520 }
1521
1522 /**
1523 * Get value that belongs in "Other" text box
1524 *
1525 * @since 2.0.6
1526 *
1527 * @param array $args
1528 *
1529 * @return string
1530 */
1531 public static function get_other_val( $args ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh
1532 $defaults = array(
1533 'opt_key' => 0,
1534 'field' => array(),
1535 'parent' => false,
1536 'pointer' => false,
1537 );
1538 $args = wp_parse_args( $args, $defaults );
1539 $opt_key = $args['opt_key'];
1540 $field = $args['field'];
1541 $parent = $args['parent'];
1542 $pointer = $args['pointer'];
1543 $other_val = '';
1544
1545 // If option is an "other" option and there is a value set for this field,
1546 // Check if the value belongs in the current "Other" option text field
1547 if ( ! self::is_other_opt( $opt_key ) || ! FrmField::is_option_true( $field, 'value' ) ) {
1548 return $other_val;
1549 }
1550
1551 // Check posted vals before checking saved values
1552 // For fields inside repeating sections - note, don't check if $pointer is true because it will often be zero
1553 if ( $parent && isset( $_POST['item_meta'][ $parent ][ $pointer ]['other'][ $field['id'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1554 if ( FrmField::is_field_with_multiple_values( $field ) ) {
1555 $other_val = isset( $_POST['item_meta'][ $parent ][ $pointer ]['other'][ $field['id'] ][ $opt_key ] ) ? sanitize_text_field( wp_unslash( $_POST['item_meta'][ $parent ][ $pointer ]['other'][ $field['id'] ][ $opt_key ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, SlevomatCodingStandard.Files.LineLength.LineTooLong
1556 } else {
1557 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1558 $other_val = sanitize_text_field( wp_unslash( $_POST['item_meta'][ $parent ][ $pointer ]['other'][ $field['id'] ] ) );
1559 }
1560
1561 return $other_val;
1562 }
1563
1564 if ( isset( $field['id'] ) && isset( $_POST['item_meta']['other'][ $field['id'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1565 // For normal fields
1566
1567 if ( FrmField::is_field_with_multiple_values( $field ) ) {
1568 // phpcs:ignore WordPress.Security.NonceVerification.Missing, SlevomatCodingStandard.Files.LineLength.LineTooLong
1569 $other_val = isset( $_POST['item_meta']['other'][ $field['id'] ][ $opt_key ] ) ? sanitize_text_field( wp_unslash( $_POST['item_meta']['other'][ $field['id'] ][ $opt_key ] ) ) : '';
1570 } else {
1571 $other_val = sanitize_text_field( wp_unslash( $_POST['item_meta']['other'][ $field['id'] ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1572 }
1573
1574 return $other_val;
1575 }//end if
1576
1577 // For checkboxes
1578 if ( $field['type'] === 'checkbox' && is_array( $field['value'] ) ) {
1579 // Check if there is an "other" val in saved value and make sure the
1580 // "other" val is not equal to the Other checkbox option
1581 // phpcs:ignore Universal.Operators.StrictComparisons
1582 if ( isset( $field['value'][ $opt_key ] ) && $field['options'][ $opt_key ] != $field['value'][ $opt_key ] ) {
1583 $other_val = $field['value'][ $opt_key ];
1584 }
1585 } else {
1586 /**
1587 * For radio buttons and dropdowns
1588 * Check if saved value equals any of the options. If not, set it as the other value.
1589 */
1590 foreach ( $field['options'] as $opt_val ) {
1591 $temp_val = is_array( $opt_val ) ? $opt_val['value'] : $opt_val;
1592
1593 // Multi-select dropdowns - key is not preserved
1594 if ( is_array( $field['value'] ) ) {
1595 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
1596 $o_key = array_search( $temp_val, $field['value'] );
1597
1598 if ( isset( $field['value'][ $o_key ] ) ) {
1599 unset( $field['value'][ $o_key ], $o_key );
1600 }
1601 } elseif ( $temp_val == $field['value'] ) { // phpcs:ignore Universal.Operators.StrictComparisons
1602 // For radio and regular dropdowns
1603 return '';
1604 } else {
1605 $other_val = $field['value'];
1606 }
1607 unset( $opt_val, $temp_val );
1608 }
1609
1610 // For multi-select dropdowns only
1611 if ( is_array( $field['value'] ) && $field['value'] ) {
1612 $other_val = reset( $field['value'] );
1613 }
1614 }//end if
1615
1616 return $other_val;
1617 }
1618
1619 /**
1620 * Check if there is a saved value for the "Other" text field. If so, set it as the $other_val.
1621 * Intended for front-end use
1622 *
1623 * @since 2.0.6
1624 *
1625 * @param array $args Should include field, opt_key and field name.
1626 * @param bool $other_opt
1627 * @param string $checked
1628 *
1629 * @return array Other args.
1630 */
1631 public static function prepare_other_input( $args, &$other_opt, &$checked ) {
1632 $other_args = array(
1633 'name' => '',
1634 'value' => '',
1635 );
1636
1637 // Check if this is an "Other" option.
1638 if ( ! self::is_other_opt( $args['opt_key'] ) ) {
1639 return $other_args;
1640 }
1641
1642 $other_opt = true;
1643
1644 self::set_other_name( $args, $other_args );
1645 self::set_other_value( $args, $other_args );
1646
1647 if ( '' !== $other_args['value'] ) {
1648 $checked = 'checked="checked" ';
1649 }
1650
1651 // If 'other' is selected as one of the default values for a checkbox field, 'checked' attribute should be on.
1652 if ( ! $checked &&
1653 $args['field']['type'] === 'checkbox' &&
1654 is_array( $args['field']['value'] ) &&
1655 isset( $args['field_val'] ) &&
1656 in_array( $args['field_val'], $args['field']['value'], true )
1657 ) {
1658 $checked = 'checked="checked" ';
1659 }
1660
1661 return $other_args;
1662 }
1663
1664 /**
1665 * @since 2.0.6
1666 *
1667 * @param array $args
1668 * @param array $other_args
1669 *
1670 * @return void
1671 */
1672 private static function set_other_name( $args, &$other_args ) {
1673 // Set up name for other field
1674 $other_args['name'] = str_replace( '[]', '', $args['field_name'] );
1675 $other_args['name'] = preg_replace( '/\[' . $args['field']['id'] . '\]$/', '', $other_args['name'] );
1676 $other_args['name'] = $other_args['name'] . '[other][' . $args['field']['id'] . ']';
1677
1678 // Converts item_meta[field_id] => item_meta[other][field_id] and
1679 // item_meta[parent][0][field_id] => item_meta[parent][0][other][field_id]
1680 if ( FrmField::is_field_with_multiple_values( $args['field'] ) ) {
1681 $other_args['name'] .= '[' . $args['opt_key'] . ']';
1682 }
1683 }
1684
1685 /**
1686 * Find the parent and pointer, and get text for "other" text field
1687 *
1688 * @since 2.0.6
1689 *
1690 * @param array $args
1691 * @param array $other_args
1692 *
1693 * @return void
1694 */
1695 private static function set_other_value( $args, &$other_args ) {
1696 $parent = '';
1697 $pointer = '';
1698
1699 // Check for parent ID and pointer
1700 $temp_array = explode( '[', $args['field_name'] );
1701
1702 // Count should only be greater than 3 if inside of a repeating section
1703 if ( count( $temp_array ) > 3 ) {
1704 $parent = str_replace( ']', '', $temp_array[1] );
1705 $pointer = str_replace( ']', '', $temp_array[2] );
1706 }
1707
1708 // Get text for "other" text field
1709 $other_args['value'] = self::get_other_val(
1710 array(
1711 'opt_key' => $args['opt_key'],
1712 'field' => $args['field'],
1713 'parent' => $parent,
1714 'pointer' => $pointer,
1715 )
1716 );
1717 }
1718
1719 /**
1720 * If this field includes an other option, show it
1721 *
1722 * @since 2.0.6
1723 *
1724 * @param array $args
1725 *
1726 * @return void
1727 */
1728 public static function include_other_input( $args ) {
1729 if ( ! $args['other_opt'] ) {
1730 return;
1731 }
1732
1733 $classes = array( 'frm_other_input' );
1734
1735 if ( ! $args['checked'] || trim( $args['checked'] ) === '' ) {
1736 // Hide the field if the other option is not selected
1737 $classes[] = 'frm_pos_none';
1738 }
1739
1740 if ( $args['field']['type'] === 'select' && ! empty( $args['field']['multiple'] ) ) {
1741 $classes[] = 'frm_other_full';
1742 }
1743
1744 // Set up HTML ID for Other field
1745 $other_id = self::get_other_field_html_id( $args['field']['type'], $args['html_id'], $args['opt_key'] );
1746
1747 $label = $args['opt_label'] ?? $args['field']['name'];
1748
1749 // phpcs:disable Generic.WhiteSpace.ScopeIndent
1750 echo '<label for="' . esc_attr( $other_id ) . '" class="frm_screen_reader frm_hidden">' .
1751 esc_html( $label ) .
1752 '</label>' .
1753 '<input type="text" id="' . esc_attr( $other_id ) . '" class="' . esc_attr( implode( ' ', $classes ) ) . '" ' .
1754 ( $args['read_only'] ? ' readonly="readonly" disabled="disabled"' : '' ) .
1755 ' name="' . esc_attr( $args['name'] ) . '" value="' . esc_attr( $args['value'] ) . '" />';
1756 // phpcs:enable Generic.WhiteSpace.ScopeIndent
1757 }
1758
1759 /**
1760 * Get the HTML id for an "Other" text field
1761 * Note: This does not affect fields in repeating sections
1762 *
1763 * @since 2.0.08
1764 *
1765 * @param string $type Field type.
1766 * @param string $html_id
1767 * @param bool|string $opt_key
1768 *
1769 * @return string Other ID.
1770 */
1771 public static function get_other_field_html_id( $type, $html_id, $opt_key = false ) {
1772 $other_id = $html_id;
1773
1774 // If hidden radio field, add an opt key of 0
1775 if ( $type === 'radio' && $opt_key === false ) {
1776 $opt_key = 0;
1777 }
1778
1779 if ( $opt_key !== false ) {
1780 $other_id .= '-' . $opt_key;
1781 }
1782
1783 return $other_id . '-otext';
1784 }
1785
1786 /**
1787 * @param array|string $val
1788 *
1789 * @return array|string
1790 */
1791 public static function switch_field_ids( $val ) {
1792 global $frm_duplicate_ids;
1793 $replace = array();
1794 $replace_with = array();
1795
1796 foreach ( (array) $frm_duplicate_ids as $old => $new ) {
1797 $replace[] = '[if ' . $old . ']';
1798 $replace_with[] = '[if ' . $new . ']';
1799 $replace[] = '[if ' . $old . ' ';
1800 $replace_with[] = '[if ' . $new . ' ';
1801 $replace[] = '[/if ' . $old . ']';
1802 $replace_with[] = '[/if ' . $new . ']';
1803 $replace[] = '[\/if ' . $old . ']';
1804 $replace_with[] = '[\/if ' . $new . ']';
1805 $replace[] = '[foreach ' . $old . ']';
1806 $replace_with[] = '[foreach ' . $new . ']';
1807 $replace[] = '[/foreach ' . $old . ']';
1808 $replace_with[] = '[/foreach ' . $new . ']';
1809 $replace[] = '[\/foreach ' . $old . ']';
1810 $replace_with[] = '[\/foreach ' . $new . ']';
1811 $replace[] = '[' . $old . ']';
1812 $replace_with[] = '[' . $new . ']';
1813 $replace[] = '[' . $old . ' ';
1814 $replace_with[] = '[' . $new . ' ';
1815 $replace[] = 'field_id="' . $old . '"';
1816 $replace_with[] = 'field_id="' . $new . '"';
1817 $replace[] = 'field_id=\"' . $old . '\"';
1818 $replace_with[] = 'field_id=\"' . $new . '\"';
1819 // This covers conditional logic.
1820 $replace[] = '_field":"' . $old . '","';
1821 $replace_with[] = '_field":"' . $new . '","';
1822 unset( $old, $new );
1823 }//end foreach
1824
1825 if ( ! is_array( $val ) ) {
1826 return str_replace( $replace, $replace_with, $val );
1827 }
1828
1829 foreach ( $val as $k => $v ) {
1830 if ( ! is_string( $v ) ) {
1831 continue;
1832 }
1833
1834 if ( 'custom_html' === $k ) {
1835 $val[ $k ] = self::switch_ids_except_strings( $replace, $replace_with, array( '[if description]', '[description]', '[/if description]' ), $v );
1836 unset( $k, $v );
1837 continue;
1838 }
1839
1840 $val[ $k ] = str_replace( $replace, $replace_with, $v );
1841 unset( $k, $v );
1842 }
1843
1844 return $val;
1845 }
1846
1847 /**
1848 * Removes exception strings from replacement arrays and replaces the rest in the provided value string.
1849 *
1850 * @since 6.14
1851 *
1852 * @param array $replace Values to be replaced.
1853 * @param array $replace_with Replacement values.
1854 * @param array $exceptions Array of strings to skip.
1855 * @param string $value Value to be updated.
1856 *
1857 * @return string
1858 */
1859 private static function switch_ids_except_strings( $replace, $replace_with, $exceptions, $value ) {
1860 foreach ( $exceptions as $exception ) {
1861 $index = array_search( $exception, $replace, true );
1862
1863 if ( false === $index ) {
1864 continue;
1865 }
1866 unset( $replace[ $index ] );
1867 unset( $replace_with[ $index ] );
1868 }
1869
1870 return str_replace( $replace, $replace_with, $value );
1871 }
1872
1873 /**
1874 * @since 4.0
1875 *
1876 * @return void
1877 */
1878 public static function bulk_options_overlay() {
1879 $prepop = array();
1880 self::get_bulk_prefilled_opts( $prepop, true );
1881
1882 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/bulk-options-overlay.php';
1883 }
1884
1885 /**
1886 * @return array
1887 */
1888 public static function get_us_states() {
1889 $states = array(
1890 'AL' => 'Alabama',
1891 'AK' => 'Alaska',
1892 'AR' => 'Arkansas',
1893 'AZ' => 'Arizona',
1894 'CA' => 'California',
1895 'CO' => 'Colorado',
1896 'CT' => 'Connecticut',
1897 'DE' => 'Delaware',
1898 'DC' => 'District of Columbia',
1899 'FL' => 'Florida',
1900 'GA' => 'Georgia',
1901 'HI' => 'Hawaii',
1902 'ID' => 'Idaho',
1903 'IL' => 'Illinois',
1904 'IN' => 'Indiana',
1905 'IA' => 'Iowa',
1906 'KS' => 'Kansas',
1907 'KY' => 'Kentucky',
1908 'LA' => 'Louisiana',
1909 'ME' => 'Maine',
1910 'MD' => 'Maryland',
1911 'MA' => 'Massachusetts',
1912 'MI' => 'Michigan',
1913 'MN' => 'Minnesota',
1914 'MS' => 'Mississippi',
1915 'MO' => 'Missouri',
1916 'MT' => 'Montana',
1917 'NE' => 'Nebraska',
1918 'NV' => 'Nevada',
1919 'NH' => 'New Hampshire',
1920 'NJ' => 'New Jersey',
1921 'NM' => 'New Mexico',
1922 'NY' => 'New York',
1923 'NC' => 'North Carolina',
1924 'ND' => 'North Dakota',
1925 'OH' => 'Ohio',
1926 'OK' => 'Oklahoma',
1927 'OR' => 'Oregon',
1928 'PA' => 'Pennsylvania',
1929 'RI' => 'Rhode Island',
1930 'SC' => 'South Carolina',
1931 'SD' => 'South Dakota',
1932 'TN' => 'Tennessee',
1933 'TX' => 'Texas',
1934 'UT' => 'Utah',
1935 'VT' => 'Vermont',
1936 'VA' => 'Virginia',
1937 'WA' => 'Washington',
1938 'WV' => 'West Virginia',
1939 'WI' => 'Wisconsin',
1940 'WY' => 'Wyoming',
1941 );
1942
1943 return apply_filters( 'frm_us_states', $states );
1944 }
1945
1946 /**
1947 * @return array
1948 */
1949 public static function get_countries() {
1950 $countries = array(
1951 __( 'Afghanistan', 'formidable' ),
1952 __( 'Aland Islands', 'formidable' ),
1953 __( 'Albania', 'formidable' ),
1954 __( 'Algeria', 'formidable' ),
1955 __( 'American Samoa', 'formidable' ),
1956 __( 'Andorra', 'formidable' ),
1957 __( 'Angola', 'formidable' ),
1958 __( 'Anguilla', 'formidable' ),
1959 __( 'Antarctica', 'formidable' ),
1960 __( 'Antigua and Barbuda', 'formidable' ),
1961 __( 'Argentina', 'formidable' ),
1962 __( 'Armenia', 'formidable' ),
1963 __( 'Aruba', 'formidable' ),
1964 __( 'Australia', 'formidable' ),
1965 __( 'Austria', 'formidable' ),
1966 __( 'Azerbaijan', 'formidable' ),
1967 __( 'Bahamas', 'formidable' ),
1968 __( 'Bahrain', 'formidable' ),
1969 __( 'Bangladesh', 'formidable' ),
1970 __( 'Barbados', 'formidable' ),
1971 __( 'Belarus', 'formidable' ),
1972 __( 'Belgium', 'formidable' ),
1973 __( 'Belize', 'formidable' ),
1974 __( 'Benin', 'formidable' ),
1975 __( 'Bermuda', 'formidable' ),
1976 __( 'Bhutan', 'formidable' ),
1977 __( 'Bolivia', 'formidable' ),
1978 __( 'Bonaire, Sint Eustatius and Saba', 'formidable' ),
1979 __( 'Bosnia and Herzegovina', 'formidable' ),
1980 __( 'Botswana', 'formidable' ),
1981 __( 'Bouvet Island', 'formidable' ),
1982 __( 'Brazil', 'formidable' ),
1983 __( 'British Indian Ocean Territory', 'formidable' ),
1984 __( 'Brunei', 'formidable' ),
1985 __( 'Bulgaria', 'formidable' ),
1986 __( 'Burkina Faso', 'formidable' ),
1987 __( 'Burundi', 'formidable' ),
1988 __( 'Cambodia', 'formidable' ),
1989 __( 'Cameroon', 'formidable' ),
1990 __( 'Canada', 'formidable' ),
1991 __( 'Cape Verde', 'formidable' ),
1992 __( 'Cayman Islands', 'formidable' ),
1993 __( 'Central African Republic', 'formidable' ),
1994 __( 'Chad', 'formidable' ),
1995 __( 'Chile', 'formidable' ),
1996 __( 'China', 'formidable' ),
1997 __( 'Christmas Island', 'formidable' ),
1998 __( 'Cocos (Keeling) Islands', 'formidable' ),
1999 __( 'Colombia', 'formidable' ),
2000 __( 'Comoros', 'formidable' ),
2001 __( 'Congo', 'formidable' ),
2002 __( 'Cook Islands', 'formidable' ),
2003 __( 'Costa Rica', 'formidable' ),
2004 __( 'C&ocirc;te d\'Ivoire', 'formidable' ),
2005 __( 'Croatia', 'formidable' ),
2006 __( 'Cuba', 'formidable' ),
2007 __( 'Curacao', 'formidable' ),
2008 __( 'Cyprus', 'formidable' ),
2009 __( 'Czech Republic', 'formidable' ),
2010 __( 'Denmark', 'formidable' ),
2011 __( 'Djibouti', 'formidable' ),
2012 __( 'Dominica', 'formidable' ),
2013 __( 'Dominican Republic', 'formidable' ),
2014 __( 'East Timor', 'formidable' ),
2015 __( 'Ecuador', 'formidable' ),
2016 __( 'Egypt', 'formidable' ),
2017 __( 'El Salvador', 'formidable' ),
2018 __( 'Equatorial Guinea', 'formidable' ),
2019 __( 'Eritrea', 'formidable' ),
2020 __( 'Estonia', 'formidable' ),
2021 __( 'Ethiopia', 'formidable' ),
2022 __( 'Falkland Islands (Malvinas)', 'formidable' ),
2023 __( 'Faroe Islands', 'formidable' ),
2024 __( 'Fiji', 'formidable' ),
2025 __( 'Finland', 'formidable' ),
2026 __( 'France', 'formidable' ),
2027 __( 'French Guiana', 'formidable' ),
2028 __( 'French Polynesia', 'formidable' ),
2029 __( 'French Southern Territories', 'formidable' ),
2030 __( 'Gabon', 'formidable' ),
2031 __( 'Gambia', 'formidable' ),
2032 __( 'Georgia', 'formidable' ),
2033 __( 'Germany', 'formidable' ),
2034 __( 'Ghana', 'formidable' ),
2035 __( 'Gibraltar', 'formidable' ),
2036 __( 'Greece', 'formidable' ),
2037 __( 'Greenland', 'formidable' ),
2038 __( 'Grenada', 'formidable' ),
2039 __( 'Guadeloupe', 'formidable' ),
2040 __( 'Guam', 'formidable' ),
2041 __( 'Guatemala', 'formidable' ),
2042 __( 'Guernsey', 'formidable' ),
2043 __( 'Guinea', 'formidable' ),
2044 __( 'Guinea-Bissau', 'formidable' ),
2045 __( 'Guyana', 'formidable' ),
2046 __( 'Haiti', 'formidable' ),
2047 __( 'Heard Island and McDonald Islands', 'formidable' ),
2048 __( 'Holy See', 'formidable' ),
2049 __( 'Honduras', 'formidable' ),
2050 __( 'Hong Kong', 'formidable' ),
2051 __( 'Hungary', 'formidable' ),
2052 __( 'Iceland', 'formidable' ),
2053 __( 'India', 'formidable' ),
2054 __( 'Indonesia', 'formidable' ),
2055 __( 'Iran', 'formidable' ),
2056 __( 'Iraq', 'formidable' ),
2057 __( 'Ireland', 'formidable' ),
2058 __( 'Israel', 'formidable' ),
2059 __( 'Isle of Man', 'formidable' ),
2060 __( 'Italy', 'formidable' ),
2061 __( 'Jamaica', 'formidable' ),
2062 __( 'Japan', 'formidable' ),
2063 __( 'Jersey', 'formidable' ),
2064 __( 'Jordan', 'formidable' ),
2065 __( 'Kazakhstan', 'formidable' ),
2066 __( 'Kenya', 'formidable' ),
2067 __( 'Kiribati', 'formidable' ),
2068 __( 'North Korea', 'formidable' ),
2069 __( 'South Korea', 'formidable' ),
2070 __( 'Kosovo', 'formidable' ),
2071 __( 'Kuwait', 'formidable' ),
2072 __( 'Kyrgyzstan', 'formidable' ),
2073 __( 'Laos', 'formidable' ),
2074 __( 'Latvia', 'formidable' ),
2075 __( 'Lebanon', 'formidable' ),
2076 __( 'Lesotho', 'formidable' ),
2077 __( 'Liberia', 'formidable' ),
2078 __( 'Libya', 'formidable' ),
2079 __( 'Liechtenstein', 'formidable' ),
2080 __( 'Lithuania', 'formidable' ),
2081 __( 'Luxembourg', 'formidable' ),
2082 __( 'Macao', 'formidable' ),
2083 __( 'Macedonia', 'formidable' ),
2084 __( 'Madagascar', 'formidable' ),
2085 __( 'Malawi', 'formidable' ),
2086 __( 'Malaysia', 'formidable' ),
2087 __( 'Maldives', 'formidable' ),
2088 __( 'Mali', 'formidable' ),
2089 __( 'Malta', 'formidable' ),
2090 __( 'Marshall Islands', 'formidable' ),
2091 __( 'Martinique', 'formidable' ),
2092 __( 'Mauritania', 'formidable' ),
2093 __( 'Mauritius', 'formidable' ),
2094 __( 'Mayotte', 'formidable' ),
2095 __( 'Mexico', 'formidable' ),
2096 __( 'Micronesia', 'formidable' ),
2097 __( 'Moldova', 'formidable' ),
2098 __( 'Monaco', 'formidable' ),
2099 __( 'Mongolia', 'formidable' ),
2100 __( 'Montenegro', 'formidable' ),
2101 __( 'Montserrat', 'formidable' ),
2102 __( 'Morocco', 'formidable' ),
2103 __( 'Mozambique', 'formidable' ),
2104 __( 'Myanmar', 'formidable' ),
2105 __( 'Namibia', 'formidable' ),
2106 __( 'Nauru', 'formidable' ),
2107 __( 'Nepal', 'formidable' ),
2108 __( 'Netherlands', 'formidable' ),
2109 __( 'New Caledonia', 'formidable' ),
2110 __( 'New Zealand', 'formidable' ),
2111 __( 'Nicaragua', 'formidable' ),
2112 __( 'Niger', 'formidable' ),
2113 __( 'Nigeria', 'formidable' ),
2114 __( 'Niue', 'formidable' ),
2115 __( 'Norfolk Island', 'formidable' ),
2116 __( 'Northern Mariana Islands', 'formidable' ),
2117 __( 'Norway', 'formidable' ),
2118 __( 'Oman', 'formidable' ),
2119 __( 'Pakistan', 'formidable' ),
2120 __( 'Palau', 'formidable' ),
2121 __( 'Palestine', 'formidable' ),
2122 __( 'Panama', 'formidable' ),
2123 __( 'Papua New Guinea', 'formidable' ),
2124 __( 'Paraguay', 'formidable' ),
2125 __( 'Peru', 'formidable' ),
2126 __( 'Philippines', 'formidable' ),
2127 __( 'Pitcairn', 'formidable' ),
2128 __( 'Poland', 'formidable' ),
2129 __( 'Portugal', 'formidable' ),
2130 __( 'Puerto Rico', 'formidable' ),
2131 __( 'Qatar', 'formidable' ),
2132 __( 'Reunion', 'formidable' ),
2133 __( 'Romania', 'formidable' ),
2134 __( 'Russia', 'formidable' ),
2135 __( 'Rwanda', 'formidable' ),
2136 __( 'Saint Barthelemy', 'formidable' ),
2137 __( 'Saint Helena, Ascension and Tristan da Cunha', 'formidable' ),
2138 __( 'Saint Kitts and Nevis', 'formidable' ),
2139 __( 'Saint Lucia', 'formidable' ),
2140 __( 'Saint Martin (French part)', 'formidable' ),
2141 __( 'Saint Pierre and Miquelon', 'formidable' ),
2142 __( 'Saint Vincent and the Grenadines', 'formidable' ),
2143 __( 'Samoa', 'formidable' ),
2144 __( 'San Marino', 'formidable' ),
2145 __( 'Sao Tome and Principe', 'formidable' ),
2146 __( 'Saudi Arabia', 'formidable' ),
2147 __( 'Senegal', 'formidable' ),
2148 __( 'Serbia', 'formidable' ),
2149 __( 'Seychelles', 'formidable' ),
2150 __( 'Sierra Leone', 'formidable' ),
2151 __( 'Singapore', 'formidable' ),
2152 __( 'Sint Maarten (Dutch part)', 'formidable' ),
2153 __( 'Slovakia', 'formidable' ),
2154 __( 'Slovenia', 'formidable' ),
2155 __( 'Solomon Islands', 'formidable' ),
2156 __( 'Somalia', 'formidable' ),
2157 __( 'South Africa', 'formidable' ),
2158 __( 'South Georgia and the South Sandwich Islands', 'formidable' ),
2159 __( 'South Sudan', 'formidable' ),
2160 __( 'Spain', 'formidable' ),
2161 __( 'Sri Lanka', 'formidable' ),
2162 __( 'Sudan', 'formidable' ),
2163 __( 'Suriname', 'formidable' ),
2164 __( 'Svalbard and Jan Mayen', 'formidable' ),
2165 __( 'Swaziland', 'formidable' ),
2166 __( 'Sweden', 'formidable' ),
2167 __( 'Switzerland', 'formidable' ),
2168 __( 'Syria', 'formidable' ),
2169 __( 'Taiwan', 'formidable' ),
2170 __( 'Tajikistan', 'formidable' ),
2171 __( 'Tanzania', 'formidable' ),
2172 __( 'Thailand', 'formidable' ),
2173 __( 'Timor-Leste', 'formidable' ),
2174 __( 'Togo', 'formidable' ),
2175 __( 'Tokelau', 'formidable' ),
2176 __( 'Tonga', 'formidable' ),
2177 __( 'Trinidad and Tobago', 'formidable' ),
2178 __( 'Tunisia', 'formidable' ),
2179 __( 'Turkey', 'formidable' ),
2180 __( 'Turkmenistan', 'formidable' ),
2181 __( 'Turks and Caicos Islands', 'formidable' ),
2182 __( 'Tuvalu', 'formidable' ),
2183 __( 'Uganda', 'formidable' ),
2184 __( 'Ukraine', 'formidable' ),
2185 __( 'United Arab Emirates', 'formidable' ),
2186 __( 'United Kingdom', 'formidable' ),
2187 __( 'United States', 'formidable' ),
2188 __( 'United States Minor Outlying Islands', 'formidable' ),
2189 __( 'Uruguay', 'formidable' ),
2190 __( 'Uzbekistan', 'formidable' ),
2191 __( 'Vanuatu', 'formidable' ),
2192 __( 'Vatican City', 'formidable' ),
2193 __( 'Venezuela', 'formidable' ),
2194 __( 'Vietnam', 'formidable' ),
2195 __( 'Virgin Islands, British', 'formidable' ),
2196 __( 'Virgin Islands, U.S.', 'formidable' ),
2197 __( 'Wallis and Futuna', 'formidable' ),
2198 __( 'Western Sahara', 'formidable' ),
2199 __( 'Yemen', 'formidable' ),
2200 __( 'Zambia', 'formidable' ),
2201 __( 'Zimbabwe', 'formidable' ),
2202 );
2203
2204 sort( $countries, SORT_LOCALE_STRING );
2205 return apply_filters( 'frm_countries', $countries );
2206 }
2207
2208 /**
2209 * Gets bulk prefilled options.
2210 *
2211 * @since 5.0.04 Add `$include_class` param.
2212 *
2213 * @param array $prepop Bulk options.
2214 * @param array|false $include_class Include the class in the bulk options.
2215 *
2216 * @return void
2217 */
2218 public static function get_bulk_prefilled_opts( array &$prepop, $include_class = false ) {
2219 // Countries.
2220 $countries = self::get_countries();
2221
2222 if ( $include_class ) {
2223 $countries['class'] = 'frm-countries-opts';
2224 }
2225
2226 $prepop[ __( 'Countries', 'formidable' ) ] = $countries;
2227
2228 // State abv.
2229 $states = self::get_us_states();
2230 $state_abv = array_keys( $states );
2231 sort( $state_abv );
2232
2233 if ( $include_class ) {
2234 $state_abv['class'] = 'frm-state-abv-opts';
2235 }
2236
2237 $prepop[ __( 'U.S. State Abbreviations', 'formidable' ) ] = $state_abv;
2238
2239 // States.
2240 $states = array_values( $states );
2241 sort( $states );
2242
2243 if ( $include_class ) {
2244 $states['class'] = 'frm-states-opts';
2245 }
2246
2247 $prepop[ __( 'U.S. States', 'formidable' ) ] = $states;
2248 unset( $state_abv, $states );
2249
2250 // Age.
2251 $ages = array(
2252 __( 'Under 18', 'formidable' ),
2253 __( '18-24', 'formidable' ),
2254 __( '25-34', 'formidable' ),
2255 __( '35-44', 'formidable' ),
2256 __( '45-54', 'formidable' ),
2257 __( '55-64', 'formidable' ),
2258 __( '65 or Above', 'formidable' ),
2259 __( 'Prefer Not to Answer', 'formidable' ),
2260 );
2261
2262 if ( $include_class ) {
2263 $ages['class'] = 'frm-age-opts';
2264 }
2265
2266 $prepop[ __( 'Age', 'formidable' ) ] = $ages;
2267
2268 // Satisfaction.
2269 $satisfaction = array(
2270 __( 'Very Unsatisfied', 'formidable' ),
2271 __( 'Unsatisfied', 'formidable' ),
2272 __( 'Neutral', 'formidable' ),
2273 __( 'Satisfied', 'formidable' ),
2274 __( 'Very Satisfied', 'formidable' ),
2275 __( 'N/A', 'formidable' ),
2276 );
2277
2278 if ( $include_class ) {
2279 $satisfaction['class'] = 'frm-satisfaction-opts';
2280 }
2281
2282 $prepop[ __( 'Satisfaction', 'formidable' ) ] = $satisfaction;
2283
2284 // Importance.
2285 $importance = array(
2286 __( 'Not at all Important', 'formidable' ),
2287 __( 'Somewhat Important', 'formidable' ),
2288 __( 'Neutral', 'formidable' ),
2289 __( 'Important', 'formidable' ),
2290 __( 'Very Important', 'formidable' ),
2291 __( 'N/A', 'formidable' ),
2292 );
2293
2294 if ( $include_class ) {
2295 $importance['class'] = 'frm-importance-opts';
2296 }
2297
2298 $prepop[ __( 'Importance', 'formidable' ) ] = $importance;
2299
2300 // Agreement.
2301 $agreement = array(
2302 __( 'Strongly Disagree', 'formidable' ),
2303 __( 'Disagree', 'formidable' ),
2304 __( 'Neutral', 'formidable' ),
2305 __( 'Agree', 'formidable' ),
2306 __( 'Strongly Agree', 'formidable' ),
2307 __( 'N/A', 'formidable' ),
2308 );
2309
2310 if ( $include_class ) {
2311 $agreement['class'] = 'frm-agreement-opts';
2312 }
2313
2314 $prepop[ __( 'Agreement', 'formidable' ) ] = $agreement;
2315
2316 // Likely.
2317 $likely = array(
2318 __( 'Extremely Unlikely', 'formidable' ),
2319 __( 'Unlikely', 'formidable' ),
2320 __( 'Neutral', 'formidable' ),
2321 __( 'Likely', 'formidable' ),
2322 __( 'Extremely Likely', 'formidable' ),
2323 __( 'N/A', 'formidable' ),
2324 );
2325
2326 if ( $include_class ) {
2327 $likely['class'] = 'frm-likely-opts';
2328 }
2329
2330 $prepop[ __( 'Likely', 'formidable' ) ] = $likely;
2331
2332 $prepop = apply_filters( 'frm_bulk_field_choices', $prepop );
2333 }
2334
2335 /**
2336 * Display a field value selector
2337 *
2338 * @since 2.03.05
2339 *
2340 * @param int $selector_field_id
2341 * @param array $selector_args
2342 *
2343 * @return void
2344 */
2345 public static function display_field_value_selector( $selector_field_id, $selector_args ) {
2346 $field_value_selector = FrmFieldFactory::create_field_value_selector( $selector_field_id, $selector_args );
2347 $field_value_selector->display();
2348 }
2349
2350 /**
2351 * Convert a field object to a flat array
2352 *
2353 * @since 2.03.05
2354 *
2355 * @param object $field
2356 *
2357 * @return array
2358 */
2359 public static function convert_field_object_to_flat_array( $field ) {
2360 $field_options = is_array( $field->field_options ) ? $field->field_options : array();
2361 $field_array = get_object_vars( $field );
2362 unset( $field_array['field_options'] );
2363
2364 return $field_array + $field_options;
2365 }
2366
2367 /**
2368 * Shows add field link.
2369 *
2370 * @since 6.30
2371 *
2372 * @param array $field_type See file `classes/views/frm-forms/add_field_links.php`.
2373 *
2374 * @return void
2375 */
2376 public static function show_add_field_link( $field_type ) {
2377 $field_label = FrmFormsHelper::get_field_link_name( $field_type );
2378 $classes = 'frmbutton frm6 frm_t' . $field_type['key'];
2379
2380 if ( ! empty( $field_type['hide'] ) ) {
2381 $classes .= ' frm_hidden';
2382 }
2383 ?>
2384 <li class="<?php echo esc_attr( $classes ); ?>" id="<?php echo esc_attr( $field_type['key'] ); ?>">
2385 <a href="#" class="frm_add_field" title="<?php echo esc_attr( $field_label ); ?>" role="button" aria-label="<?php echo esc_attr( $field_label ); ?>">
2386 <?php FrmAppHelper::icon_by_class( FrmFormsHelper::get_field_link_icon( $field_type ) ); ?>
2387 <span><?php echo esc_html( $field_label ); ?></span>
2388 <?php
2389 if ( 'credit_card' === $field_type['key'] && ! FrmTransLiteAppHelper::payments_table_exists() ) {
2390 FrmAppHelper::show_pill_text();
2391 }
2392 ?>
2393 </a>
2394 </li>
2395 <?php
2396 }
2397
2398 /**
2399 * @since 4.04
2400 *
2401 * @param array $args
2402 *
2403 * @return void
2404 */
2405 public static function show_add_field_buttons( $args ) {
2406 $field_key = $args['field_key'];
2407 $field_type = $args['field_type'];
2408 $field_label = FrmAppHelper::icon_by_class( FrmFormsHelper::get_field_link_icon( $field_type ), array( 'echo' => false ) );
2409 $field_name = FrmFormsHelper::get_field_link_name( $field_type );
2410 $field_label .= ' <span>' . $field_name . '</span>';
2411
2412 if ( ! empty( $field_type['is_new'] ) ) {
2413 ob_start();
2414 FrmAppHelper::show_pill_text();
2415 $field_label .= ob_get_clean();
2416 }
2417
2418 // If the individual field isn't allowed, disable it.
2419 $link = isset( $field_type['link'] ) ? esc_url_raw( $field_type['link'] ) : '';
2420
2421 list(
2422 $run_filter,
2423 $single_no_allow,
2424 $install_data,
2425 $requires,
2426 $has_show_upgrade_class,
2427 $has_show_update_class,
2428 $upgrading,
2429 $update_addon_name
2430 ) = self::get_field_upgrade_state( $field_type );
2431
2432 $show_upgrade = $has_show_upgrade_class || $has_show_update_class || str_contains( $args['no_allow_class'], 'frm_show_upgrade' );
2433 $upgrade_label = '';
2434 $upgrade_message = '';
2435
2436 if ( $show_upgrade ) {
2437 /* translators: %s: Field name */
2438 $upgrade_label = sprintf( esc_html__( '%s fields', 'formidable' ), $field_name );
2439
2440 if ( isset( $field_type['message'] ) ) {
2441 $upgrade_message = FrmAppHelper::kses( $field_type['message'], array( 'a', 'img' ) );
2442 }
2443 }
2444
2445 $li_params = array(
2446 'class' => 'frmbutton frm6 ' . $args['no_allow_class'] . $single_no_allow . ' frm_t' . str_replace( '|', '-', $field_key ),
2447 'id' => $field_key,
2448 'data-upgrade' => $upgrade_label,
2449 'data-link' => $link,
2450 'data-medium' => 'builder',
2451 'data-oneclick' => $install_data,
2452 'data-content' => $field_key,
2453 'data-requires' => $requires,
2454 );
2455
2456 if ( ! empty( $field_type['hide'] ) ) {
2457 $li_params['class'] .= ' frm_hidden';
2458 }
2459
2460 if ( ! $show_upgrade && ! empty( $field_type['limit'] ) ) {
2461 $li_params = self::update_params_with_limit_data( $li_params, $args['id'], $field_key, $field_type['limit'] );
2462 }
2463
2464 if ( ! empty( $field_type['upsell_image'] ) ) {
2465 $li_params['data-upsell-image'] = $field_type['upsell_image'];
2466 }
2467
2468 if ( ! empty( $field_type['learn-more'] ) ) {
2469 $li_params['data-learn-more'] = FrmAppHelper::get_doc_url(
2470 $field_type['learn-more'],
2471 'form-builder-add-fields',
2472 str_contains( $field_type['learn-more'], '/' )
2473 );
2474 }
2475
2476 if ( $has_show_update_class ) {
2477 $li_params['data-message'] = sprintf(
2478 // translators: %1$s: Add-on name, %2$s: Field type name.
2479 esc_html__( 'You need a newer version of %1$s for %2$s fields.', 'formidable' ),
2480 $update_addon_name,
2481 $field_name
2482 );
2483 } elseif ( isset( $upgrading['url'] ) ) {
2484 $li_params['data-message'] = sprintf(
2485 // translators: %s: Field name
2486 esc_html__( 'You already have access to %s fields, you\'ll just need to activate to start using them.', 'formidable' ),
2487 $field_name
2488 );
2489 } elseif ( $upgrade_message ) {
2490 $li_params['data-message'] = $upgrade_message;
2491 }
2492 // phpcs:disable Generic.WhiteSpace.ScopeIndent
2493 ?>
2494 <li <?php FrmAppHelper::array_to_html_params( $li_params, true ); ?>>
2495 <?php
2496 if ( $run_filter ) {
2497 $field_label = apply_filters( 'frmpro_field_links', $field_label, $args['id'], $field_key );
2498 }
2499
2500 FrmAppHelper::kses_echo( $field_label, array( 'a', 'i', 'span', 'use', 'svg' ) );
2501 ?>
2502 </li>
2503 <?php
2504 // phpcs:enable Generic.WhiteSpace.ScopeIndent
2505 }
2506
2507 /**
2508 * Parse the upgrade and update flags from a field type's icon class.
2509 *
2510 * @since 6.29
2511 *
2512 * @param array $field_type Field type configuration, modified in place to strip icon flag classes.
2513 *
2514 * @return array Upgrade and update state values for the field button, in positional order.
2515 */
2516 private static function get_field_upgrade_state( &$field_type ) {
2517 $single_no_allow = ' ';
2518 $run_filter = true;
2519 $has_show_upgrade_class = false;
2520 $has_show_update_class = false;
2521 $install_data = '';
2522 $requires = '';
2523 $upgrading = array();
2524 $update_addon_name = '';
2525
2526 if ( isset( $field_type['icon'] ) ) {
2527 $has_show_upgrade_class = str_contains( $field_type['icon'], ' frm_show_upgrade' );
2528 $has_show_update_class = str_contains( $field_type['icon'], ' frm_show_update' );
2529 }
2530
2531 if ( $has_show_upgrade_class ) {
2532 $single_no_allow .= 'frm_show_upgrade';
2533 $field_type['icon'] = str_replace( ' frm_show_upgrade', '', $field_type['icon'] );
2534 $run_filter = false;
2535
2536 if ( isset( $field_type['addon'] ) ) {
2537 $upgrading = FrmAddonsController::install_link( $field_type['addon'] );
2538
2539 if ( isset( $upgrading['url'] ) ) {
2540 $install_data = json_encode( $upgrading );
2541 }
2542
2543 $requires = FrmFormsHelper::get_plan_required( $upgrading );
2544 } elseif ( isset( $field_type['require'] ) ) {
2545 $requires = $field_type['require'];
2546 }
2547 }
2548
2549 if ( $has_show_update_class ) {
2550 $single_no_allow .= ' frm_show_update';
2551 $field_type['icon'] = str_replace( ' frm_show_update', '', $field_type['icon'] );
2552 $run_filter = false;
2553 $addon_slug = $field_type['addon'] ?? 'pro';
2554
2555 if ( 'pro' === $addon_slug ) {
2556 $update_addon_name = __( 'Formidable Pro', 'formidable' );
2557 } else {
2558 $addon_data = FrmAddonsController::get_addon( $addon_slug );
2559 $update_addon_name = $addon_data && ! empty( $addon_data['title'] ) ? $addon_data['title'] : ucfirst( $addon_slug );
2560 }
2561
2562 $install_data = FrmAddonsController::get_update_install_data( $addon_slug );
2563 }
2564
2565 return array(
2566 $run_filter,
2567 $single_no_allow,
2568 $install_data,
2569 $requires,
2570 $has_show_upgrade_class,
2571 $has_show_update_class,
2572 $upgrading,
2573 $update_addon_name,
2574 );
2575 }
2576
2577 /**
2578 * Updates the params with limit data (the data-limit attribute, and possibly the frm_at_limit class).
2579 * Some field types are limited to a certain number per form, including coupon fields.
2580 *
2581 * @since 6.27
2582 *
2583 * @param array $li_params The params.
2584 * @param int $form_id The form ID.
2585 * @param string $field_type The field type.
2586 * @param int $limit The limit.
2587 *
2588 * @return array The updated params.
2589 */
2590 private static function update_params_with_limit_data( $li_params, $form_id, $field_type, $limit ) {
2591 $fields_in_form = FrmDb::get_count(
2592 'frm_fields',
2593 array(
2594 'form_id' => $form_id,
2595 'type' => $field_type,
2596 )
2597 );
2598
2599 if ( $fields_in_form >= $limit ) {
2600 $li_params['class'] .= ' frm_at_limit';
2601 }
2602
2603 $li_params['data-limit'] = $limit;
2604
2605 return $li_params;
2606 }
2607
2608 /**
2609 * Shows Display format option.
2610 *
2611 * @since 5.0.04
2612 *
2613 * @param array $field Field data.
2614 *
2615 * @return void
2616 */
2617 public static function show_radio_display_format( $field ) {
2618 $options = self::get_display_format_options( $field );
2619 $args = self::get_display_format_args( $field, $options );
2620
2621 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/radio-display-format.php';
2622 }
2623
2624 /**
2625 * Creates an array that contains variables used for display format options setting.
2626 *
2627 * @since 6.3.2
2628 *
2629 * @param array $field The field.
2630 *
2631 * @return array
2632 */
2633 public static function get_display_format_options( $field ) {
2634 $options = array(
2635 '0' => array(
2636 'text' => __( 'Simple', 'formidable' ),
2637 'svg' => 'frm_simple_radio',
2638 ),
2639 '1' => array(
2640 'text' => __( 'Images', 'formidable' ),
2641 'svg' => 'frm_image_as_option',
2642 'addon' => 'pro',
2643 'upgrade' => __( 'Image Options', 'formidable' ),
2644 'message' => __( 'Show images instead of radio buttons or check boxes. This is ideal for polls, surveys, segmenting questionnaires and more.', 'formidable' ) . '<img src="' . esc_url( FrmAppHelper::plugin_url() ) . '/images/image-options.png" />', // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
2645 'content' => 'image-options',
2646 ),
2647 'buttons' => array(
2648 'text' => __( 'Buttons', 'formidable' ),
2649 'svg' => 'frm_button_as_option',
2650 'addon' => 'surveys',
2651 'upgrade' => __( 'Button Options', 'formidable' ),
2652 'message' => __( 'Show buttons for radio buttons or check boxes. This is ideal for polls, surveys, segmenting questionnaires and more.', 'formidable' ),
2653 'content' => 'button-options',
2654 ),
2655 );
2656
2657 if ( 'product' === FrmField::get_field_type( $field ) ) {
2658 unset( $options['buttons'] );
2659 }
2660
2661 /**
2662 * Allows modifying the options of Display format setting of Radio field.
2663 *
2664 * @since 5.0.04
2665 *
2666 * @param array $options Options.
2667 * @param array $field
2668 */
2669 return apply_filters( 'frm_' . $field['type'] . '_display_format_options', $options, $field );
2670 }
2671
2672 /**
2673 * Gets display format arguments to pass to the images_dropdown() method.
2674 *
2675 * @since 5.0.04
2676 *
2677 * @param array $field Field data.
2678 * @param array $options Options array.
2679 *
2680 * @return array
2681 */
2682 public static function get_display_format_args( $field, $options ) {
2683 $args = array(
2684 'selected' => '0',
2685 'options' => array(),
2686 'name' => 'field_options[image_options_' . $field['id'] . ']',
2687 'input_attrs' => array(
2688 'class' => 'frm_toggle_image_options',
2689 ),
2690 );
2691
2692 self::fill_image_setting_options( $options, $args );
2693
2694 /**
2695 * Allows modifying the arguments of Display format setting of Radio field.
2696 *
2697 * @since 5.0.04
2698 *
2699 * @param array $args Arguments.
2700 * @param array $method_args The arguments from the method. Contains `field`, `options`.
2701 */
2702 return apply_filters( 'frm_' . $field['type'] . '_display_format_args', $args, compact( 'field', 'options' ) );
2703 }
2704
2705 /**
2706 * @since 5.0.04
2707 *
2708 * @param array $options Options.
2709 * @param array $args Arguments.
2710 *
2711 * @return void
2712 */
2713 private static function fill_image_setting_options( $options, &$args ) {
2714 foreach ( $options as $key => $option ) {
2715 $args['options'][ $key ] = $option;
2716
2717 if ( ! empty( $option['addon'] ) ) {
2718 $args['options'][ $key ]['custom_attrs'] = self::fill_image_setting_addon_link( $option );
2719 }
2720
2721 unset( $args['options'][ $key ]['addon'] );
2722 $fill = array( 'upgrade', 'message', 'content' );
2723
2724 foreach ( $fill as $f ) {
2725 unset( $args['options'][ $key ][ $f ], $f );
2726 }
2727 }
2728 }
2729
2730 /**
2731 * @since 5.0.04
2732 *
2733 * @param array $option Option.
2734 *
2735 * @return array
2736 */
2737 private static function fill_image_setting_addon_link( $option ) {
2738 $custom_attrs = array(
2739 'class' => 'frm_noallow frm_show_upgrade',
2740 'data-medium' => 'builder',
2741 );
2742
2743 // translators: Add-on name.
2744 $custom_attrs['data-upgrade'] = sprintf( __( 'Formidable %s', 'formidable' ), ucwords( $option['addon'] ) );
2745
2746 $fill = array( 'upgrade', 'message', 'content' );
2747
2748 foreach ( $fill as $f ) {
2749 if ( isset( $option[ $f ] ) ) {
2750 $custom_attrs[ 'data-' . $f ] = $option[ $f ];
2751 }
2752 }
2753
2754 if ( 'pro' === $option['addon'] ) {
2755 return $custom_attrs;
2756 }
2757
2758 $upgrading = FrmAddonsController::install_link( $option['addon'] );
2759
2760 $custom_attrs['data-oneclick'] = isset( $upgrading['url'] ) ? wp_json_encode( $upgrading ) : '';
2761 $custom_attrs['data-requires'] = FrmFormsHelper::get_plan_required( $upgrading );
2762
2763 return $custom_attrs;
2764 }
2765
2766 /**
2767 * Maybe adjust a field value based on type.
2768 * Some types require unserializing an array.
2769 * These types are defined by a array_allowed property on their field model class.
2770 *
2771 * @since 6.2
2772 *
2773 * @param mixed $value
2774 * @param string $field_type
2775 *
2776 * @return void
2777 */
2778 public static function prepare_field_value( &$value, $field_type ) {
2779 $field_object = FrmFieldFactory::get_field_type( $field_type );
2780 $value = $field_object->maybe_decode_value( $value );
2781 }
2782
2783 /**
2784 * @since 6.8
2785 *
2786 * @param int|string $form_id
2787 * @param array $field_ids If this is not empty, the results will be filtered by field id.
2788 *
2789 * @return array
2790 */
2791 public static function get_draft_field_results( $form_id, $field_ids = array() ) {
2792 if ( FrmAppHelper::pro_is_installed() ) {
2793 $child_form_ids = FrmDb::get_col( 'frm_forms', array( 'parent_form_id' => $form_id ) );
2794 $form_ids = array_merge( array( $form_id ), $child_form_ids );
2795 } else {
2796 $form_ids = array( $form_id );
2797 }
2798
2799 $where = array(
2800 'form_id' => $form_ids,
2801 // Do a soft check for fields that look like drafts only.
2802 'field_options LIKE' => 's:5:"draft";i:1;',
2803 );
2804
2805 if ( $field_ids ) {
2806 $where['id'] = $field_ids;
2807 }
2808
2809 $rows = FrmDb::get_results( 'frm_fields', $where, 'id, field_options' );
2810
2811 return array_filter(
2812 $rows,
2813 function ( $row ) {
2814 FrmAppHelper::unserialize_or_decode( $row->field_options );
2815 return is_array( $row->field_options ) && ! empty( $row->field_options['draft'] );
2816 }
2817 );
2818 }
2819
2820 /**
2821 * This is called when loading the form builder.
2822 * Any unsaved draft fields get added to a hidden draft_fields input on load.
2823 *
2824 * @since 6.8
2825 *
2826 * @param int|string $form_id
2827 *
2828 * @return array
2829 */
2830 public static function get_all_draft_field_ids( $form_id ) {
2831 $draft_field_rows = self::get_draft_field_results( $form_id );
2832 return wp_list_pluck( $draft_field_rows, 'id' );
2833 }
2834
2835 /**
2836 * Render AI generate options button.
2837 *
2838 * @since 6.24
2839 *
2840 * @param array $args Field arguments.
2841 * @param bool $should_hide_bulk_edit Whether to hide bulk edit.
2842 *
2843 * @return void
2844 */
2845 public static function render_ai_generate_options_button( $args, $should_hide_bulk_edit = false ) {
2846 $attributes = array(
2847 'type' => 'button',
2848 );
2849
2850 $attributes['class'] = ! empty( $args['class'] ) ? $args['class'] : self::get_ai_generate_options_button_class();
2851
2852 if ( $should_hide_bulk_edit ) {
2853 $attributes['class'] .= ' frm-force-hidden';
2854 }
2855
2856 $data = FrmAppHelper::get_upgrade_data_params(
2857 'ai',
2858 array(
2859 'requires' => 'Business',
2860 'upgrade' => $args['upgrade_text'] ?? __( 'Generate options with AI', 'formidable' ),
2861 'medium' => 'builder',
2862 'content' => 'generate-options-with-ai',
2863 ),
2864 true
2865 );
2866
2867 if ( in_array( FrmAddonsController::license_type(), array( 'elite', 'business' ), true ) && 'active' === $data['plugin-status'] ) {
2868 // Backwards compatibility "@since 6.24".
2869 if ( method_exists( 'FrmAIAppController', 'get_ai_generated_options_summary' ) ) {
2870 $attributes['class'] .= ' frm-ai-generate-options-modal-trigger';
2871 $attributes['data-fid'] = $args['likert_id'] ?? $args['field']['id'];
2872 } else {
2873 $data = array(
2874 'modal-title' => __( 'Generate options with AI', 'formidable' ),
2875 'modal-content' => __( 'Update the Formidable AI add-on to the last version to use this feature.', 'formidable' ),
2876 );
2877 }
2878 }
2879
2880 if ( empty( $attributes['data-fid'] ) ) {
2881 unset( $data['plugin-status'] );
2882
2883 foreach ( $data as $key => $value ) {
2884 $attributes[ 'data-' . $key ] = $value;
2885 }
2886 }
2887
2888 include FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/generate-options-with-ai.php';
2889 }
2890
2891 /**
2892 * Get AI generate options button class.
2893 *
2894 * @since 6.24
2895 *
2896 * @return string Button class.
2897 */
2898 private static function get_ai_generate_options_button_class() {
2899 return implode(
2900 ' ',
2901 array(
2902 'frm_form_field',
2903 'frm6',
2904 'frm6_followed',
2905 'frm-h-stack',
2906 'button',
2907 'frm-button-secondary',
2908 'frm-button-gradient',
2909 'frm-rounded-6',
2910 'frm-max-w-fit',
2911 'frm-font-normal',
2912 'frm-py-2xs',
2913 'frm-px-xs',
2914 'frm-mt-xs',
2915 'frm-mb-12',
2916 )
2917 );
2918 }
2919
2920 /**
2921 * Checks if the field choice should be hidden due to choice limit being reached.
2922 *
2923 * @since 6.28
2924 *
2925 * @param string $choice_key
2926 * @param array $field
2927 *
2928 * @return bool
2929 */
2930 public static function should_hide_field_choice( $choice_key, $field ) {
2931 /**
2932 * @since 6.28
2933 *
2934 * @param bool $hide_field_choice
2935 * @param string $choice_key
2936 * @param array $field
2937 */
2938 return (bool) apply_filters( 'frm_hide_field_choice', false, $choice_key, $field );
2939 }
2940
2941 /**
2942 * @since 6.28
2943 *
2944 * @param array $field
2945 *
2946 * @return bool
2947 */
2948 public static function should_skip_rendering_choices_for_field( $field ) {
2949 /**
2950 * @since 6.28
2951 *
2952 * @param bool $skip_rendering_options_for_field
2953 * @param array $field
2954 */
2955 return (bool) apply_filters( 'frm_should_skip_rendering_choices_for_field', false, $field );
2956 }
2957
2958 /**
2959 * Determine if 'disabled' attribute should be echoed in a field choice's HTML.
2960 *
2961 * @since 6.28
2962 *
2963 * @param string $choice_key
2964 * @param bool $is_selected_choice
2965 * @param array $field
2966 *
2967 * @return bool
2968 */
2969 public static function should_disable_choice( $choice_key, $is_selected_choice, $field ) {
2970 /**
2971 * @since 6.28
2972 *
2973 * @param bool $echo_disabled_attribute
2974 * @param string $choice_key
2975 * @param bool $is_selected_choice
2976 * @param array $field
2977 */
2978 return (bool) apply_filters( 'frm_disable_choice', false, $choice_key, $is_selected_choice, $field );
2979 }
2980
2981 /**
2982 * @since 6.28
2983 *
2984 * @param array $field
2985 * @param string $choice_key
2986 *
2987 * @return void
2988 */
2989 public static function after_choice_input( $field, $choice_key ) {
2990 /**
2991 * Allows adding content after checkbox, radio button, or dropdown fields.
2992 *
2993 * @since 6.28
2994 *
2995 * @param array $field The field data.
2996 * @param string $choice_key The option key.
2997 */
2998 do_action( 'frm_after_choice_input', $field, $choice_key );
2999 }
3000 }
3001