PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
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.28, at classes/helpers/FrmFieldsHelper.php

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