PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.12
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.12
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 / models / FrmField.php

FrmField.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.12, at classes/models/FrmField.php

1,466 lines 42.7 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 FrmField {
7
8 public static $use_cache = true;
9 public static $transient_size = 200;
10
11 public static function field_selection() {
12 $fields = array(
13 'text' => array(
14 'name' => __( 'Text', 'formidable' ),
15 'icon' => 'frm_icon_font frm_text2_icon',
16 ),
17 'textarea' => array(
18 'name' => __( 'Paragraph', 'formidable' ),
19 'icon' => 'frm_icon_font frm_paragraph_icon',
20 ),
21 'checkbox' => array(
22 'name' => __( 'Checkboxes', 'formidable' ),
23 'icon' => 'frm_icon_font frm_check_square_icon',
24 ),
25 'radio' => array(
26 'name' => __( 'Radio Buttons', 'formidable' ),
27 'icon' => 'frm_icon_font frm_radio_checked_icon',
28 ),
29 'select' => array(
30 'name' => __( 'Dropdown', 'formidable' ),
31 'icon' => 'frm_icon_font frm_caret_square_down_icon',
32 ),
33 'email' => array(
34 'name' => __( 'Email', 'formidable' ),
35 'icon' => 'frm_icon_font frm_email_icon',
36 ),
37 'url' => array(
38 'name' => __( 'Website/URL', 'formidable' ),
39 'icon' => 'frm_icon_font frm_link_icon',
40 ),
41 'number' => array(
42 'name' => __( 'Number', 'formidable' ),
43 'icon' => 'frm_icon_font frm_hashtag_icon',
44 ),
45 'name' => array(
46 'name' => __( 'Name', 'formidable' ),
47 'icon' => 'frm_icon_font frm_user_name_icon',
48 ),
49 'phone' => array(
50 'name' => __( 'Phone', 'formidable' ),
51 'icon' => 'frm_icon_font frm_phone_icon',
52 ),
53 'html' => array(
54 'name' => __( 'HTML', 'formidable' ),
55 'icon' => 'frm_icon_font frm_code_icon',
56 ),
57 'hidden' => array(
58 'name' => __( 'Hidden', 'formidable' ),
59 'icon' => 'frm_icon_font frm_eye_slash_icon',
60 ),
61 'user_id' => array(
62 'name' => __( 'User ID', 'formidable' ),
63 'icon' => 'frm_icon_font frm_user_icon',
64 ),
65 'captcha' => array(
66 'name' => self::get_captcha_field_name(),
67 'icon' => 'frm_icon_font frm_shield_check_icon',
68 ),
69 'credit_card' => array(
70 'name' => __( 'Payment', 'formidable' ),
71 'icon' => 'frm_icon_font frm_credit_card_icon',
72 ),
73 FrmSubmitHelper::FIELD_TYPE => array(
74 'name' => __( 'Submit', 'formidable' ),
75 'hide' => true,
76 ),
77 );
78
79 /**
80 * @param array $fields
81 */
82 return apply_filters( 'frm_available_fields', $fields );
83 }
84
85 /**
86 * Get the name of the Captcha field based on the global Captcha setting.
87 *
88 * @return string
89 */
90 private static function get_captcha_field_name() {
91 return 'Captcha';
92 }
93
94 public static function pro_field_selection() {
95 $images_url = FrmAppHelper::plugin_url() . '/images/';
96 $fields = array(
97 'file' => array(
98 'name' => __( 'File Upload', 'formidable' ),
99 'icon' => 'frm_icon_font frm_upload_icon',
100 'message' => __( 'Add file uploads to save time and cut down on back-and-forth. Upgrade to Pro to get Upload fields and more.', 'formidable' ),
101 ),
102 'ranking' => array(),
103 'rte' => array(
104 'name' => __( 'Rich Text', 'formidable' ),
105 'icon' => 'frm_icon_font frm_align_right_icon',
106 ),
107 'date' => array(
108 'name' => __( 'Date', 'formidable' ),
109 'icon' => 'frm_icon_font frm_calendar_icon',
110 ),
111 'time' => array(
112 'name' => __( 'Time', 'formidable' ),
113 'icon' => 'frm_icon_font frm_clock_icon',
114 ),
115 'scale' => array(
116 'name' => __( 'Scale', 'formidable' ),
117 'icon' => 'frm_icon_font frm_linear_scale_icon',
118 'message' => esc_html__( 'Add a set of radio buttons with whatever range you choose.', 'formidable' ) . '<img src="' . esc_url( $images_url ) . 'scale_field.png" alt="' . esc_attr__( 'Scale Field', 'formidable' ) . '" />',
119 ),
120 'star' => array(
121 'name' => __( 'Star Rating', 'formidable' ),
122 'icon' => 'frm_icon_font frm_star_icon',
123 ),
124 'range' => array(
125 'name' => __( 'Slider', 'formidable' ),
126 'icon' => 'frm_icon_font frm_code_commit_icon',
127 ),
128 'toggle' => array(
129 'name' => __( 'Toggle', 'formidable' ),
130 'icon' => 'frm_icon_font frm_toggle_on_icon',
131 ),
132 'data' => array(
133 'name' => __( 'Dynamic', 'formidable' ),
134 'icon' => 'frm_icon_font frm_sitemap_icon',
135 'message' => __( 'Create relationships between multiple forms. You can link a member to a team, a rating to a product, a comment to a submission, and much more.', 'formidable' ),
136 ),
137 'lookup' => array(
138 'name' => __( 'Lookup', 'formidable' ),
139 'icon' => 'frm_icon_font frm_search_icon',
140 'message' => esc_html__( 'Filter the options in the next field and automatically add values to other fields. Upgrade to Pro to get Lookup fields and more.', 'formidable' ) . ' <img src="' . esc_url( $images_url ) . 'look-up_year-make-model.gif" alt="' . esc_attr__( 'cascading lookup fields', 'formidable' ) . '" />',
141 ),
142 'divider|repeat' => array(
143 'name' => __( 'Repeater', 'formidable' ),
144 'icon' => 'frm_icon_font frm_repeater_icon',
145 'message' => esc_html__( 'Allow your visitors to add new sets of fields while filling out forms. Increase conversions while saving building time and server resources.', 'formidable' ) . ' <img src="' . esc_url( $images_url ) . 'repeatable-section_frontend.gif" alt="' . esc_attr__( 'Dynamically Add Form Fields with repeatable sections', 'formidable' ) . '" />',
146 ),
147 'end_divider' => array(
148 'name' => __( 'Section Buttons', 'formidable' ),
149 'switch_from' => 'divider',
150 ),
151 'divider' => array(
152 'name' => __( 'Section', 'formidable' ),
153 'icon' => 'frm_icon_font frm_header_icon',
154 ),
155 'break' => array(
156 'name' => __( 'Page Break', 'formidable' ),
157 'icon' => 'frm_icon_font frm_page_break_icon',
158 'message' => __( 'Get multi-paged forms with progress bars. Did you know you can upgrade to PRO to unlock multi-step forms with more awesome features?', 'formidable' ),
159 ),
160 'form' => array(
161 'name' => __( 'Embed Form', 'formidable' ),
162 'icon' => 'frm_icon_font frm_file_text_icon',
163 ),
164 'likert' => array(
165 'name' => __( 'Likert Scale', 'formidable' ),
166 'icon' => 'frm_icon_font frm_likert_scale frm_show_upgrade',
167 'addon' => 'surveys',
168 ),
169 'nps' => array(
170 'name' => __( 'NPS', 'formidable' ),
171 'icon' => 'frm_icon_font frm_nps frm_show_upgrade',
172 'addon' => 'surveys',
173 ),
174 'password' => array(
175 'name' => __( 'Password', 'formidable' ),
176 'icon' => 'frm_icon_font frm_lock_open_icon',
177 ),
178 'tag' => array(
179 'name' => __( 'Tags', 'formidable' ),
180 'icon' => 'frm_icon_font frm_price_tags_icon',
181 ),
182 // This is no longer a Pro field, but without this here, Pro triggers "undefined index" notices.
183 // Right now it leaves a gap. Maybe we can skip anything without a name or something.
184 'credit_card' => array(
185 'name' => '',
186 'icon' => '',
187 ),
188 'address' => array(
189 'name' => __( 'Address', 'formidable' ),
190 'icon' => 'frm_icon_font frm_location_icon',
191 ),
192 'summary' => array(
193 'name' => __( 'Summary', 'formidable' ),
194 'icon' => 'frm_icon_font frm_file_text_icon',
195 'message' => __( 'Allow visitors to review their responses before a form is submitted. Upgrade to Pro to get Summary fields and more.', 'formidable' ),
196 ),
197 'signature' => array(
198 'name' => __( 'Signature', 'formidable' ),
199 'icon' => 'frm_icon_font frm_signature_icon frm_show_upgrade',
200 'addon' => 'signature',
201 ),
202 'ai' => array(
203 'name' => __( 'AI', 'formidable' ),
204 'icon' => 'frm_icon_font frm_eye_icon frm_show_upgrade',
205 'addon' => 'ai',
206 'message' => __( 'Streamline workflows and reclaim valuable time with the power of AI. You can effortlessly respond to your visitors in real-time with ChatGPT as your automated assistant. Upgrade to Pro and unlock AI-powered fields.', 'formidable' ),
207 ),
208 'ssa-appointment' => array(
209 'name' => __( 'Appointment', 'formidable' ),
210 'icon' => 'frm_icon_font frm_calendar_icon frm_show_upgrade',
211 'require' => 'Simply Schedule Appointments',
212 'message' => sprintf(
213 /* translators: %1$s: Link opening HTML, %2$s: Link tag closing */
214 esc_html__( 'Appointment fields are an integration with %1$sSimply Schedule Appointments%2$s. Get started now to schedule appointments directly from your forms.', 'formidable' ),
215 '<a href="https://simplyscheduleappointments.com/meet/formidable/">',
216 '</a>'
217 ) . '<img src="' . esc_url( $images_url ) . 'appointments.png" alt="' . esc_attr__( 'Scheduling', 'formidable' ) . '" />',
218 'link' => 'https://simplyscheduleappointments.com/meet/formidable/',
219 ),
220 'product' => array(
221 'name' => __( 'Product', 'formidable' ),
222 'icon' => 'frm_icon_font frm_product_icon',
223 'section' => 'pricing',
224 ),
225 'quantity' => array(
226 'name' => __( 'Quantity', 'formidable' ),
227 'icon' => 'frm_icon_font frm_quantity_icon',
228 'section' => 'pricing',
229 ),
230 'total' => array(
231 'name' => __( 'Total', 'formidable' ),
232 'icon' => 'frm_icon_font frm_total_icon',
233 'section' => 'pricing',
234 ),
235 );
236
237 if ( self::include_ranking_fields() ) {
238 $fields['ranking'] = array(
239 'name' => __( 'Ranking', 'formidable' ),
240 'icon' => 'frm_icon_font frm_chart_bar_icon frm_show_upgrade',
241 'message' => __( 'Now you can effortlessly gather insights, preferences, and opinions by allowing users to rank options.', 'formidable' ),
242 'upsell_image' => esc_url( $images_url ) . 'ranking-field.svg',
243 'addon' => 'surveys',
244 'is_new' => self::field_is_new( 'ranking' ),
245 );
246 } else {
247 unset( $fields['ranking'] );
248 }
249
250 if ( ! FrmAppHelper::show_new_feature( 'ai' ) ) {
251 unset( $fields['ai'] );
252 }
253
254 // Since the signature field may be in a different section, don't show it twice.
255 $lite_fields = self::field_selection();
256 if ( isset( $lite_fields['signature'] ) ) {
257 unset( $fields['signature'] );
258 }
259
260 return apply_filters( 'frm_pro_available_fields', $fields );
261 }
262
263 /**
264 * Check if we should show ranking fields in the builder.
265 * This is based on the active version coming from our API data.
266 * If Surveys v1.1 is not released yet, we don't want to display ranking fields yet.
267 *
268 * @since 6.8.3
269 *
270 * @return bool
271 */
272 private static function include_ranking_fields() {
273 if ( class_exists( 'FrmSurveys\models\fields\Ranking' ) ) {
274 // Always return true if Ranking fields exist.
275 return true;
276 }
277
278 $plugin = 'formidable-surveys/formidable-surveys.php';
279 $expected_version = '1.1';
280
281 return self::installed_plugin_meets_version( $plugin, $expected_version ) || self::api_meets_version( $plugin, $expected_version );
282 }
283
284 /**
285 * @since 6.8.3
286 *
287 * @param string $plugin
288 * @param string $expected_version
289 * @return bool
290 */
291 private static function installed_plugin_meets_version( $plugin, $expected_version ) {
292 $installed_version = self::get_installed_version( $plugin );
293 return $installed_version && version_compare( $installed_version, $expected_version, '>=' );
294 }
295
296 /**
297 * @since 6.8.3
298 *
299 * @param string $plugin
300 * @return false|string String version. False if the plugin is not installed.
301 */
302 private static function get_installed_version( $plugin ) {
303 if ( ! function_exists( 'get_plugins' ) ) {
304 require_once ABSPATH . 'wp-admin/includes/plugin.php';
305 }
306 $plugins = get_plugins();
307 if ( isset( $plugins[ $plugin ] ) && ! empty( $plugins[ $plugin ]['Version'] ) ) {
308 return $plugins[ $plugin ]['Version'];
309 }
310 return false;
311 }
312
313 /**
314 * @since 6.8.3
315 *
316 * @param string $plugin
317 * @param string $expected_version
318 * @return bool
319 */
320 private static function api_meets_version( $plugin, $expected_version ) {
321 $api = new FrmFormApi();
322 $addons = $api->get_api_info();
323 $matches = wp_list_filter( $addons, array( 'plugin' => $plugin ) );
324 if ( ! $matches ) {
325 return false;
326 }
327
328 $match = reset( $matches );
329 if ( empty( $match['new_version'] ) ) {
330 return false;
331 }
332
333 $api_version = $match['new_version'];
334 return version_compare( $api_version, $expected_version, '>=' );
335 }
336
337 /**
338 * Consider a field new for 90 days after the release date.
339 *
340 * @since 6.8.3
341 *
342 * @param string $type
343 * @return bool
344 */
345 private static function field_is_new( $type ) {
346 if ( 'ranking' === $type ) {
347 $ranking_release_date = '2024-03-12';
348 $three_months_after_release = gmdate( 'Y-m-d', strtotime( $ranking_release_date . ' + 90 days' ) );
349 return gmdate( 'Y-m-d' ) < $three_months_after_release;
350 }
351 return false;
352 }
353
354 /**
355 * @since 4.0
356 *
357 * @return array
358 */
359 public static function all_field_selection() {
360 $pro_field_selection = self::pro_field_selection();
361 return array_merge( $pro_field_selection, self::field_selection() );
362 }
363
364 /**
365 * Create a field.
366 *
367 * @param array $values
368 * @param bool $return
369 * @return false|int
370 */
371 public static function create( $values, $return = true ) {
372 global $wpdb, $frm_duplicate_ids;
373
374 $new_values = array();
375 $key = isset( $values['field_key'] ) ? $values['field_key'] : $values['name'];
376 $new_values['field_key'] = FrmAppHelper::get_unique_key( $key, $wpdb->prefix . 'frm_fields', 'field_key' );
377
378 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
379
380 foreach ( array( 'name', 'description', 'type', 'default_value' ) as $col ) {
381 $new_values[ $col ] = $values[ $col ];
382 }
383
384 $new_values['options'] = self::maybe_filter_options( $values['options'] );
385 $new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null;
386 $new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0;
387 $new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null;
388 $new_values['field_options'] = $values['field_options'];
389 $new_values['created_at'] = current_time( 'mysql', 1 );
390
391 if ( isset( $values['id'] ) ) {
392 $frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key'];
393 $new_values = apply_filters( 'frm_duplicated_field', $new_values );
394 }
395
396 self::preserve_format_option_backslashes( $new_values );
397
398 foreach ( $new_values as $k => $v ) {
399 if ( is_array( $v ) ) {
400 if ( $k === 'default_value' ) {
401 $new_values[ $k ] = FrmAppHelper::maybe_json_encode( $v );
402 } else {
403 $new_values[ $k ] = serialize( $v );
404 }
405 }
406 unset( $k, $v );
407 }
408
409 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_fields', $new_values );
410
411 if ( ! $query_results ) {
412 return false;
413 }
414
415 self::delete_form_transient( $new_values['form_id'] );
416 $new_id = $wpdb->insert_id;
417
418 if ( ! $return ) {
419 return false;
420 }
421
422 if ( isset( $values['id'] ) ) {
423 $frm_duplicate_ids[ $values['id'] ] = $new_id;
424 }
425
426 return $new_id;
427 }
428
429 /**
430 * Maybe filter HTML in field options data.
431 * HTML is only filtered when unsafe HTML is disallowed.
432 * See FrmAppHelper::allow_unfiltered_html.
433 *
434 * @since 5.0.08
435 *
436 * @param array $options
437 * @return array
438 */
439 private static function maybe_filter_options( $options ) {
440 $options = FrmAppHelper::maybe_filter_array( $options, array( 'custom_html' ) );
441
442 if ( ! empty( $options['custom_html'] ) ) {
443 $options['custom_html'] = self::maybe_filter_custom_html_input_attributes( $options['custom_html'] );
444 }
445
446 return $options;
447 }
448
449 /**
450 * Prevent users who do not have permission to insert JavaScript attributes in input elements.
451 * This is triggered when a field is updated.
452 *
453 * @since 6.11.2
454 *
455 * @param string $html
456 * @return string
457 */
458 private static function maybe_filter_custom_html_input_attributes( $html ) {
459 if ( FrmAppHelper::allow_unfiltered_html() ) {
460 return $html;
461 }
462
463 $pattern = get_shortcode_regex( array( 'input' ) );
464 return preg_replace_callback(
465 "/$pattern/",
466 /**
467 * @param array $match Shortcode data.
468 * @return string
469 */
470 function ( $match ) {
471 $attr = shortcode_parse_atts( $match[3] );
472
473 if ( ! is_array( $attr ) ) {
474 // In old versions of WordPress (older than 6.5), this might not be an array.
475 return '[input]';
476 }
477
478 $safe_atts = array();
479 foreach ( $attr as $attr_key => $att ) {
480 if ( ! is_numeric( $attr_key ) ) {
481 // opt=1 without parentheses for example is mapped like 'opt' => 1.
482 $key = $attr_key;
483 $value = $att;
484 } else {
485 // Some data is mapped like 0 => 'placeholder="Placeholder"'.
486 $split = explode( '=', $att, 2 );
487 if ( 2 !== count( $split ) ) {
488 continue;
489 }
490 $key = trim( $split[0] );
491 $value = trim( $split[1], '"' );
492 }
493
494 if ( FrmAppHelper::input_key_is_safe( $key, 'update' ) ) {
495 $safe_atts[ $key ] = $value;
496 }
497 }
498
499 if ( ! $safe_atts ) {
500 return '[input]';
501 }
502
503 return '[input ' . FrmAppHelper::array_to_html_params( $safe_atts ) . ']';
504 },
505 $html
506 );
507 }
508
509 /**
510 * Process the field duplication.
511 *
512 * @since 5.0.05
513 */
514 public static function duplicate_single_field( $field_id, $form_id ) {
515 $copy_field = self::getOne( $field_id );
516 if ( ! $copy_field ) {
517 return false;
518 }
519
520 do_action( 'frm_duplicate_field', $copy_field, $form_id );
521 do_action( 'frm_duplicate_field_' . $copy_field->type, $copy_field, $form_id );
522
523 $values = array(
524 'id' => $copy_field->id,
525 );
526 FrmFieldsHelper::fill_field( $values, $copy_field, $copy_field->form_id );
527 $values = apply_filters( 'frm_prepare_single_field_for_duplication', $values );
528
529 $field_id = self::create( $values );
530
531 /**
532 * Fires after duplicating a field.
533 *
534 * @since 5.0.04
535 *
536 * @param array $args {
537 * The arguments.
538 *
539 * @type int $field_id New field ID.
540 * @type array $values Values before inserting.
541 * @type object $copy_field Copy field data.
542 * @type int $form_id Form ID.
543 * }
544 */
545 do_action( 'frm_after_duplicate_field', compact( 'field_id', 'values', 'copy_field', 'form_id' ) );
546
547 return compact( 'field_id', 'values' );
548 }
549
550 public static function duplicate( $old_form_id, $form_id, $copy_keys = false, $blog_id = false ) {
551 global $frm_duplicate_ids;
552
553 $where = array(
554 array(
555 'or' => 1,
556 'fi.form_id' => $old_form_id,
557 'fr.parent_form_id' => $old_form_id,
558 ),
559 );
560 $fields = self::getAll( $where, 'field_order', '', $blog_id );
561
562 foreach ( (array) $fields as $field ) {
563 $new_key = $copy_keys ? $field->field_key : '';
564 if ( $copy_keys && substr( $field->field_key, - 1 ) == 2 ) {
565 $new_key = rtrim( $new_key, 2 );
566 }
567
568 $values = array();
569 FrmFieldsHelper::fill_field( $values, $field, $form_id, $new_key );
570
571 // If this is a repeating section, create new form
572 if ( self::is_repeating_field( $field ) ) {
573 // create the repeatable form
574 $new_repeat_form_id = apply_filters(
575 'frm_create_repeat_form',
576 0,
577 array(
578 'parent_form_id' => $form_id,
579 'field_name' => $field->name,
580 )
581 );
582
583 // Save old form_select
584 $old_repeat_form_id = $field->field_options['form_select'];
585
586 // Update form_select for repeating field
587 $values['field_options']['form_select'] = $new_repeat_form_id;
588 }
589
590 // If this is a field inside of a repeating section, associate it with the correct form
591 if ( $field->form_id != $old_form_id && isset( $old_repeat_form_id ) && isset( $new_repeat_form_id ) && $field->form_id == $old_repeat_form_id ) {
592 $values['form_id'] = $new_repeat_form_id;
593 }
594
595 $values['description'] = FrmFieldsHelper::switch_field_ids( $values['description'] );
596
597 $values = apply_filters( 'frm_duplicated_field', $values );
598 $new_id = self::create( $values );
599 $frm_duplicate_ids[ $field->id ] = $new_id;
600 $frm_duplicate_ids[ $field->field_key ] = $new_id;
601 unset( $field );
602 }//end foreach
603 }
604
605 /**
606 * @param int|string $id
607 * @param array $values
608 * @return false|int
609 */
610 public static function update( $id, $values ) {
611 global $wpdb;
612
613 $id = absint( $id );
614 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
615
616 if ( isset( $values['field_key'] ) ) {
617 $values['field_key'] = FrmAppHelper::get_unique_key( $values['field_key'], $wpdb->prefix . 'frm_fields', 'field_key', $id );
618 }
619
620 if ( isset( $values['required'] ) ) {
621 $values['required'] = (int) $values['required'];
622 }
623
624 self::preserve_format_option_backslashes( $values );
625
626 if ( isset( $values['type'] ) ) {
627 if ( 'dropdown' === $values['type'] ) {
628 // To avoid conflicts with security plugins the value "dropdown" is sent for select fields.
629 // This is because "select" gets matched for SQL injection attempts.
630 $values['type'] = 'select';
631 }
632
633 /**
634 * @since 6.9 The Field ID param was added.
635 *
636 * @param array $values
637 * @param int $id Field ID.
638 */
639 $values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values, $id );
640
641 if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
642 // don't keep the old placeholder setting for hidden fields
643 $values['field_options']['clear_on_focus'] = 0;
644 }
645 }
646
647 // serialize array values
648 foreach ( array( 'field_options', 'options' ) as $opt ) {
649 if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
650 if ( 'field_options' === $opt ) {
651 $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
652 }
653 $values[ $opt ] = serialize( $values[ $opt ] );
654 }
655 }
656 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
657 $values['default_value'] = json_encode( $values['default_value'] );
658 }
659
660 $query_results = $wpdb->update( $wpdb->prefix . 'frm_fields', $values, array( 'id' => $id ) );
661
662 $form_id = 0;
663 if ( isset( $values['form_id'] ) ) {
664 $form_id = absint( $values['form_id'] );
665 } else {
666 $field = self::getOne( $id );
667 if ( $field ) {
668 $form_id = $field->form_id;
669 }
670 unset( $field );
671 }
672 unset( $values );
673
674 if ( $query_results ) {
675 wp_cache_delete( $id, 'frm_field' );
676 if ( $form_id ) {
677 self::delete_form_transient( $form_id );
678 }
679 }
680
681 return $query_results;
682 }
683
684 /**
685 * Keep backslashes in the phone format option
686 *
687 * @since 2.0.8
688 *
689 * @param array $values Pass by reference.
690 */
691 private static function preserve_format_option_backslashes( &$values ) {
692 if ( isset( $values['field_options']['format'] ) ) {
693 $values['field_options']['format'] = FrmAppHelper::preserve_backslashes( $values['field_options']['format'] );
694 }
695 }
696
697 public static function destroy( $id ) {
698 global $wpdb;
699
700 do_action( 'frm_before_destroy_field', $id );
701
702 wp_cache_delete( $id, 'frm_field' );
703 $field = self::getOne( $id );
704 if ( ! $field ) {
705 return false;
706 }
707
708 self::delete_form_transient( $field->form_id );
709
710 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id=%d', $id ) );
711
712 return $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $id ) );
713 }
714
715 public static function delete_form_transient( $form_id ) {
716 $form_id = absint( $form_id );
717 delete_transient( 'frm_form_fields_' . $form_id . 'excludeinclude' );
718 delete_transient( 'frm_form_fields_' . $form_id . 'includeinclude' );
719 delete_transient( 'frm_form_fields_' . $form_id . 'includeexclude' );
720 delete_transient( 'frm_form_fields_' . $form_id . 'excludeexclude' );
721
722 global $wpdb;
723 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s', '_transient_timeout_frm_form_fields_' . $form_id . 'ex%', '_transient_frm_form_fields_' . $form_id . 'ex%', '_transient_timeout_frm_form_fields_' . $form_id . 'in%', '_transient_frm_form_fields_' . $form_id . 'in%' ) );
724
725 FrmDb::cache_delete_group( 'frm_field' );
726
727 $form = FrmForm::getOne( $form_id );
728 if ( $form && $form->parent_form_id && $form->parent_form_id != $form_id ) {
729 self::delete_form_transient( $form->parent_form_id );
730 }
731 }
732
733 /**
734 * If $field is numeric, get the field object
735 *
736 * @param int|object|string $field
737 * @return void
738 */
739 public static function maybe_get_field( &$field ) {
740 if ( ! is_object( $field ) ) {
741 $field = self::getOne( $field );
742 }
743 }
744
745 /**
746 * @param int|string $id The field id or key.
747 * @param bool $filter When true, run the frm_field filter.
748 */
749 public static function getOne( $id, $filter = false ) {
750 if ( empty( $id ) ) {
751 return null;
752 }
753
754 global $wpdb;
755
756 $where = is_numeric( $id ) ? 'id=%d' : 'field_key=%s';
757 $query = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'frm_fields WHERE ' . $where, $id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
758
759 $results = FrmDb::check_cache( $id, 'frm_field', $query, 'get_row', 0 );
760
761 if ( empty( $results ) ) {
762 self::filter_field( $filter, $results );
763 return $results;
764 }
765
766 if ( is_numeric( $id ) ) {
767 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
768 } elseif ( $results ) {
769 FrmDb::set_cache( $results->id, $results, 'frm_field' );
770 }
771
772 self::prepare_options( $results );
773 self::filter_field( $filter, $results );
774
775 return wp_unslash( $results );
776 }
777
778 /**
779 * @since 3.06.01
780 * @param bool $filter When true, run the frm_field filter.
781 * @param object $results
782 */
783 private static function filter_field( $filter, &$results ) {
784 if ( $filter ) {
785 /**
786 * @since 3.06.01
787 */
788 $results = apply_filters( 'frm_field', $results );
789 }
790 }
791
792 /**
793 * Get the field type by key or id
794 *
795 * @param int|string $id The field id or key.
796 * @param mixed $col The name of the column in the fields database table.
797 */
798 public static function get_type( $id, $col = 'type' ) {
799 $field = FrmDb::check_cache( $id, 'frm_field' );
800 if ( $field ) {
801 $type = $field->{$col};
802 } else {
803 $where = array(
804 'or' => 1,
805 'id' => $id,
806 'field_key' => $id,
807 );
808 $type = FrmDb::get_var( 'frm_fields', $where, $col );
809 }
810
811 return $type;
812 }
813
814 /**
815 * @param int|string $form_id
816 * @param string $type
817 * @param int|string $limit
818 * @param string $inc_sub
819 */
820 public static function get_all_types_in_form( $form_id, $type, $limit = '', $inc_sub = 'exclude' ) {
821 if ( ! $form_id ) {
822 return array();
823 }
824
825 $results = self::get_fields_from_transients(
826 $form_id,
827 array(
828 'inc_embed' => $inc_sub,
829 'inc_repeat' => $inc_sub,
830 )
831 );
832 if ( ! empty( $results ) ) {
833 $fields = array();
834 $count = 0;
835 foreach ( $results as $result ) {
836 if ( $type != $result->type ) {
837 continue;
838 }
839
840 $fields[ $result->id ] = $result;
841 ++$count;
842 if ( $limit == 1 ) {
843 $fields = $result;
844 break;
845 }
846
847 if ( ! empty( $limit ) && $count >= $limit ) {
848 break;
849 }
850
851 unset( $result );
852 }
853
854 return wp_unslash( $fields );
855 }//end if
856
857 self::$use_cache = false;
858
859 $where = array(
860 'fi.form_id' => (int) $form_id,
861 'fi.type' => $type,
862 );
863 self::maybe_include_repeating_fields( $inc_sub, $where );
864 $results = self::getAll( $where, 'field_order', $limit );
865 self::$use_cache = true;
866 self::include_sub_fields( $results, $inc_sub, $type, $form_id );
867
868 return $results;
869 }
870
871 /**
872 * @param int|string $form_id
873 * @param int|string $limit
874 * @param string $inc_embed
875 * @param string $inc_repeat
876 * @return array
877 */
878 public static function get_all_for_form( $form_id, $limit = '', $inc_embed = 'exclude', $inc_repeat = 'include' ) {
879 if ( ! (int) $form_id ) {
880 return array();
881 }
882
883 $results = self::get_fields_from_transients( $form_id, compact( 'inc_embed', 'inc_repeat' ) );
884 if ( ! empty( $results ) ) {
885 if ( empty( $limit ) ) {
886 return $results;
887 }
888
889 $fields = array();
890 $count = 0;
891 foreach ( $results as $result ) {
892 ++$count;
893 $fields[ $result->id ] = $result;
894 if ( ! empty( $limit ) && $count >= $limit ) {
895 break;
896 }
897 }
898
899 return $fields;
900 }
901
902 self::$use_cache = false;
903
904 $where = array( 'fi.form_id' => absint( $form_id ) );
905 self::maybe_include_repeating_fields( $inc_repeat, $where );
906 $results = self::getAll( $where, 'field_order', $limit );
907
908 self::$use_cache = true;
909
910 self::include_sub_fields( $results, $inc_embed, 'all', $form_id );
911
912 if ( empty( $limit ) ) {
913 self::set_field_transient( $results, $form_id, 0, compact( 'inc_embed', 'inc_repeat' ) );
914 }
915
916 return $results;
917 }
918
919 /**
920 * If repeating fields should be included, adjust $where accordingly
921 *
922 * @param string $inc_repeat
923 * @param array $where Pass by reference.
924 */
925 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
926 if ( $inc_repeat === 'include' ) {
927 $form_id = $where['fi.form_id'];
928 $where[] = array(
929 'or' => 1,
930 'fi.form_id' => $form_id,
931 'fr.parent_form_id' => $form_id,
932 );
933 unset( $where['fi.form_id'] );
934 }
935 }
936
937 public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
938 $no_sub_forms = empty( $results ) && $type === 'all';
939 if ( 'include' != $inc_embed || $no_sub_forms ) {
940 return;
941 }
942
943 $form_fields = $results;
944 $should_get_subforms = ( $type !== 'all' && $type !== 'form' && ! empty( $form_id ) );
945 if ( $should_get_subforms ) {
946 $form_fields = self::get_all_types_in_form( $form_id, 'form' );
947 }
948
949 $index_offset = 1;
950 foreach ( $form_fields as $k => $field ) {
951 if ( 'form' != $field->type || ! isset( $field->field_options['form_select'] ) ) {
952 continue;
953 }
954
955 if ( $type === 'all' ) {
956 $sub_fields = self::get_all_for_form( $field->field_options['form_select'] );
957 } else {
958 $sub_fields = self::get_all_types_in_form( $field->field_options['form_select'], $type );
959 }
960
961 if ( ! empty( $sub_fields ) ) {
962 $index = $k + $index_offset;
963 $index_offset += count( $sub_fields );
964 array_splice( $results, $index, 0, $sub_fields );
965 }
966 unset( $field, $sub_fields );
967 }
968 }
969
970 public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
971 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
972 if ( self::$use_cache ) {
973 // make sure old cache doesn't get saved as a transient
974 $results = wp_cache_get( $cache_key, 'frm_field' );
975 if ( false !== $results ) {
976 return wp_unslash( $results );
977 }
978 }
979
980 global $wpdb;
981
982 if ( $blog_id && is_multisite() ) {
983 global $wpmuBaseTablePrefix;
984 if ( $wpmuBaseTablePrefix ) {
985 $prefix = $wpmuBaseTablePrefix . $blog_id . '_';
986 } else {
987 $prefix = $wpdb->get_blog_prefix( $blog_id );
988 }
989
990 $table_name = $prefix . 'frm_fields';
991 $form_table_name = $prefix . 'frm_forms';
992 } else {
993 $table_name = $wpdb->prefix . 'frm_fields';
994 $form_table_name = $wpdb->prefix . 'frm_forms';
995 }
996
997 if ( ! empty( $order_by ) && strpos( $order_by, 'ORDER BY' ) === false ) {
998 $order_by = ' ORDER BY ' . $order_by;
999 }
1000
1001 $limit = FrmDb::esc_limit( $limit );
1002
1003 $query = "SELECT fi.*, fr.name as form_name FROM {$table_name} fi LEFT OUTER JOIN {$form_table_name} fr ON fi.form_id=fr.id";
1004 $query_type = $limit === ' LIMIT 1' || $limit == 1 ? 'row' : 'results';
1005
1006 if ( is_array( $where ) ) {
1007 $args = array(
1008 'order_by' => $order_by,
1009 'limit' => $limit,
1010 );
1011 $results = FrmDb::get_var( $table_name . ' fi LEFT OUTER JOIN ' . $form_table_name . ' fr ON fi.form_id=fr.id', $where, 'fi.*, fr.name as form_name', $args, '', $query_type );
1012 } else {
1013 // if the query is not an array, then it has already been prepared
1014 $query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
1015
1016 $function_name = $query_type === 'row' ? 'get_row' : 'get_results';
1017 $results = $wpdb->$function_name( $query );
1018 }
1019 unset( $where );
1020
1021 self::format_field_results( $results );
1022
1023 FrmDb::set_cache( $cache_key, $results, 'frm_field' );
1024
1025 return wp_unslash( $results );
1026 }
1027
1028 /**
1029 * @since 2.0.8
1030 */
1031 private static function format_field_results( &$results ) {
1032 if ( is_array( $results ) ) {
1033 foreach ( $results as $r_key => $result ) {
1034 FrmDb::set_cache( $result->id, $result, 'frm_field' );
1035 FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
1036
1037 self::prepare_options( $result );
1038 $results[ $r_key ]->field_options = $result->field_options;
1039 $results[ $r_key ]->options = $result->options;
1040 $results[ $r_key ]->default_value = $result->default_value;
1041
1042 unset( $r_key, $result );
1043 }
1044 } elseif ( $results ) {
1045 FrmDb::set_cache( $results->id, $results, 'frm_field' );
1046 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
1047
1048 self::prepare_options( $results );
1049 }
1050 }
1051
1052 /**
1053 * Unserialize all the serialized field data
1054 *
1055 * @since 2.0
1056 */
1057 private static function prepare_options( &$results ) {
1058 FrmAppHelper::unserialize_or_decode( $results->field_options );
1059 FrmAppHelper::unserialize_or_decode( $results->options );
1060
1061 // Allow a single box to be checked for the default value.
1062 $before = $results->default_value;
1063 FrmAppHelper::unserialize_or_decode( $results->default_value );
1064 if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
1065 $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
1066 }
1067 }
1068
1069 /**
1070 * If a form has too many fields, they won't all save into a single transient.
1071 * We'll break them into groups of 200
1072 *
1073 * @since 2.0.1
1074 */
1075 private static function get_fields_from_transients( $form_id, $args ) {
1076 $fields = array();
1077 self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
1078
1079 return $fields;
1080 }
1081
1082 /**
1083 * Called by get_fields_from_transients
1084 *
1085 * @since 2.0.1
1086 */
1087 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
1088 $name = $next ? $base_name . $next : $base_name;
1089 $next_fields = get_transient( $name );
1090
1091 if ( $next_fields ) {
1092 $fields = array_merge( $fields, $next_fields );
1093
1094 if ( count( $next_fields ) >= self::$transient_size ) {
1095 // if this transient is full, check for another
1096 ++$next;
1097 self::get_next_transient( $fields, $base_name, $next );
1098 }
1099 }
1100 }
1101
1102 /**
1103 * Save the transients in chunks for large forms
1104 *
1105 * @since 2.0.1
1106 */
1107 private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
1108 $base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
1109 $field_chunks = array_chunk( $fields, self::$transient_size );
1110
1111 foreach ( $field_chunks as $field ) {
1112 $name = $next ? $base_name . $next : $base_name;
1113 $set = set_transient( $name, $field, 60 * 60 * 6 );
1114 if ( ! $set ) {
1115 // the transient didn't save
1116 if ( $name != $base_name ) {
1117 // if the first saved an others fail, this will show an incmoplete form
1118 self::delete_form_transient( $form_id );
1119 }
1120
1121 return;
1122 }
1123
1124 ++$next;
1125 }
1126 }
1127
1128 /**
1129 * @param string $type
1130 * @return bool
1131 */
1132 public static function is_no_save_field( $type ) {
1133 return in_array( $type, self::no_save_fields(), true );
1134 }
1135
1136 /**
1137 * @return string[]
1138 */
1139 public static function no_save_fields() {
1140 return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary', FrmSubmitHelper::FIELD_TYPE );
1141 }
1142
1143 /**
1144 * Check if this field can hold an array of values
1145 *
1146 * @since 2.0.9
1147 *
1148 * @param array|object $field
1149 *
1150 * @return bool
1151 */
1152 public static function is_field_with_multiple_values( $field ) {
1153 if ( ! $field ) {
1154 return false;
1155 }
1156
1157 $field_type = self::get_original_field_type( $field );
1158
1159 $is_multi_value_field = (
1160 self::is_checkbox( $field ) ||
1161 $field_type === 'address' ||
1162 self::is_multiple_select( $field )
1163 );
1164
1165 return $is_multi_value_field;
1166 }
1167
1168 /**
1169 * @since 3.0
1170 * @param array|object $field
1171 * @return string
1172 */
1173 public static function get_field_type( $field ) {
1174 return is_array( $field ) ? $field['type'] : $field->type;
1175 }
1176
1177 /**
1178 * @since 3.0
1179 * @return string
1180 */
1181 public static function get_original_field_type( $field ) {
1182 $field_type = self::get_field_type( $field );
1183 $original_type = self::get_option( $field, 'original_type' );
1184
1185 if ( ! empty( $original_type ) && $original_type != $field_type ) {
1186 // Check the original type for arrays.
1187 $field_type = $original_type;
1188 }
1189
1190 return $field_type;
1191 }
1192
1193 /**
1194 * Check if this is a multiselect dropdown field
1195 *
1196 * @since 2.0.9
1197 * @return bool
1198 */
1199 public static function is_multiple_select( $field ) {
1200 $field_type = self::get_field_type( $field );
1201 $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
1202
1203 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
1204 }
1205
1206 /**
1207 * Check if a field is read only. Read only can be set in the field options,
1208 * but disabled with the shortcode options
1209 *
1210 * @since 2.0.9
1211 *
1212 * @param array|object $field
1213 * @return bool
1214 */
1215 public static function is_read_only( $field ) {
1216 global $frm_vars;
1217 return self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] !== 'disabled' );
1218 }
1219
1220 /**
1221 * @since 2.0.9
1222 *
1223 * @param array $field
1224 * @return bool
1225 */
1226 public static function is_required( $field ) {
1227 $required = $field['required'] != '0';
1228
1229 /**
1230 * @param bool $required
1231 * @param array $field
1232 */
1233 $required = (bool) apply_filters( 'frm_is_field_required', $required, $field );
1234
1235 return $required;
1236 }
1237
1238 /**
1239 * @since 2.0.9
1240 *
1241 * @param array|object $field
1242 * @param string $option
1243 * @return bool
1244 */
1245 public static function is_option_true( $field, $option ) {
1246 if ( is_array( $field ) ) {
1247 return self::is_option_true_in_array( $field, $option );
1248 }
1249 return self::is_option_true_in_object( $field, $option );
1250 }
1251
1252 /**
1253 * @since 2.0.9
1254 *
1255 * @param array|object $field
1256 * @param string $option
1257 * @return bool
1258 */
1259 public static function is_option_empty( $field, $option ) {
1260 if ( is_array( $field ) ) {
1261 return self::is_option_empty_in_array( $field, $option );
1262 }
1263 return self::is_option_empty_in_object( $field, $option );
1264 }
1265
1266 /**
1267 * @param array $field
1268 * @param string $option
1269 * @return bool
1270 */
1271 public static function is_option_true_in_array( $field, $option ) {
1272 return ! empty( $field[ $option ] );
1273 }
1274
1275 /**
1276 * @param object $field
1277 * @param string $option
1278 * @return bool
1279 */
1280 public static function is_option_true_in_object( $field, $option ) {
1281 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
1282 }
1283
1284 /**
1285 * @param array $field
1286 * @param string $option
1287 * @return bool
1288 */
1289 public static function is_option_empty_in_array( $field, $option ) {
1290 return empty( $field[ $option ] );
1291 }
1292
1293 /**
1294 * @param object $field
1295 * @param string $option
1296 * @return bool
1297 */
1298 public static function is_option_empty_in_object( $field, $option ) {
1299 return empty( $field->field_options[ $option ] );
1300 }
1301
1302 /**
1303 * @param stdClass $field
1304 * @param string $option
1305 * @return bool
1306 */
1307 public static function is_option_value_in_object( $field, $option ) {
1308 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
1309 }
1310
1311 /**
1312 * @since 2.0.18
1313 *
1314 * @param array|object $field
1315 * @param string $option
1316 * @return mixed
1317 */
1318 public static function get_option( $field, $option ) {
1319 if ( is_array( $field ) ) {
1320 $option = self::get_option_in_array( $field, $option );
1321 } else {
1322 $option = self::get_option_in_object( $field, $option );
1323 }
1324
1325 return $option;
1326 }
1327
1328 /**
1329 * @param array $field
1330 * @param string $option
1331 * @return mixed
1332 */
1333 public static function get_option_in_array( $field, $option ) {
1334 if ( isset( $field[ $option ] ) ) {
1335 $this_option = $field[ $option ];
1336 } elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
1337 $this_option = $field['field_options'][ $option ];
1338 } else {
1339 $this_option = '';
1340 }
1341
1342 return $this_option;
1343 }
1344
1345 /**
1346 * @param object $field
1347 * @param string $option
1348 * @return mixed
1349 */
1350 public static function get_option_in_object( $field, $option ) {
1351 return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
1352 }
1353
1354 /**
1355 * @since 2.0.09
1356 *
1357 * @param array|object $field
1358 * @return bool
1359 */
1360 public static function is_repeating_field( $field ) {
1361 if ( is_array( $field ) ) {
1362 $is_repeating_field = ( 'divider' === $field['type'] );
1363 } else {
1364 $is_repeating_field = ( 'divider' === $field->type );
1365 }
1366
1367 return $is_repeating_field && self::is_option_true( $field, 'repeat' );
1368 }
1369
1370 /**
1371 * @param string $key
1372 *
1373 * @return int field id
1374 */
1375 public static function get_id_by_key( $key ) {
1376 $id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
1377
1378 return (int) $id;
1379 }
1380
1381 /**
1382 * @param string $id
1383 *
1384 * @return string|null
1385 */
1386 public static function get_key_by_id( $id ) {
1387 return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
1388 }
1389
1390 public static function is_image( $field ) {
1391 $type = self::get_field_type( $field );
1392
1393 return ( $type === 'url' && self::get_option( $field, 'show_image' ) );
1394 }
1395
1396 /**
1397 * Check if field is radio or Dynamic radio
1398 *
1399 * @since 3.0
1400 *
1401 * @param array|object $field
1402 *
1403 * @return bool true if field type is radio or Dynamic radio
1404 */
1405 public static function is_radio( $field ) {
1406 return self::is_field_type( $field, 'radio' );
1407 }
1408
1409 /**
1410 * Check if field is checkbox or Dynamic checkbox
1411 *
1412 * @since 3.0
1413 *
1414 * @param array|object $field
1415 *
1416 * @return bool true if field type is checkbox or Dynamic checkbox
1417 */
1418 public static function is_checkbox( $field ) {
1419 return self::is_field_type( $field, 'checkbox' );
1420 }
1421
1422 /**
1423 * Check if field is checkbox or radio
1424 *
1425 * @since 3.0
1426 *
1427 * @param array|object $field
1428 * @param string $is_type Options include radio, checkbox, text.
1429 *
1430 * @return bool true if field type is checkbox or Dynamic checkbox
1431 */
1432 public static function is_field_type( $field, $is_type ) {
1433 $field_type = self::get_original_field_type( $field );
1434 $data_type = self::get_option( $field, 'data_type' );
1435
1436 $is_field_type = (
1437 $is_type === $field_type ||
1438 ( 'data' === $field_type && $is_type === $data_type ) ||
1439 ( 'lookup' === $field_type && $is_type === $data_type ) ||
1440 ( 'product' === $field_type && $is_type === $data_type )
1441 );
1442
1443 /**
1444 * When a field type is checked, allow individual fields
1445 * to set the type.
1446 *
1447 * @since 4.04
1448 */
1449 return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
1450 }
1451
1452 /**
1453 * Checks if the given field array is a combo field.
1454 *
1455 * @since 4.10.02
1456 *
1457 * @param array $field Field array.
1458 * @return bool
1459 */
1460 public static function is_combo_field( $field ) {
1461 $field_type_obj = FrmFieldFactory::get_field_factory( $field );
1462
1463 return ! empty( $field_type_obj->is_combo_field );
1464 }
1465 }
1466