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

1,470 lines 42.8 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 if ( ! empty( $options['classes'] ) ) {
447 $options['classes'] = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $options['classes'] ) ) );
448 }
449
450 return $options;
451 }
452
453 /**
454 * Prevent users who do not have permission to insert JavaScript attributes in input elements.
455 * This is triggered when a field is updated.
456 *
457 * @since 6.11.2
458 *
459 * @param string $html
460 * @return string
461 */
462 private static function maybe_filter_custom_html_input_attributes( $html ) {
463 if ( FrmAppHelper::allow_unfiltered_html() ) {
464 return $html;
465 }
466
467 $pattern = get_shortcode_regex( array( 'input' ) );
468 return preg_replace_callback(
469 "/$pattern/",
470 /**
471 * @param array $match Shortcode data.
472 * @return string
473 */
474 function ( $match ) {
475 $attr = shortcode_parse_atts( $match[3] );
476
477 if ( ! is_array( $attr ) ) {
478 // In old versions of WordPress (older than 6.5), this might not be an array.
479 return '[input]';
480 }
481
482 $safe_atts = array();
483 foreach ( $attr as $attr_key => $att ) {
484 if ( ! is_numeric( $attr_key ) ) {
485 // opt=1 without parentheses for example is mapped like 'opt' => 1.
486 $key = $attr_key;
487 $value = $att;
488 } else {
489 // Some data is mapped like 0 => 'placeholder="Placeholder"'.
490 $split = explode( '=', $att, 2 );
491 if ( 2 !== count( $split ) ) {
492 continue;
493 }
494 $key = trim( $split[0] );
495 $value = trim( $split[1], '"' );
496 }
497
498 if ( FrmAppHelper::input_key_is_safe( $key, 'update' ) ) {
499 $safe_atts[ $key ] = $value;
500 }
501 }
502
503 if ( ! $safe_atts ) {
504 return '[input]';
505 }
506
507 return '[input ' . FrmAppHelper::array_to_html_params( $safe_atts ) . ']';
508 },
509 $html
510 );
511 }
512
513 /**
514 * Process the field duplication.
515 *
516 * @since 5.0.05
517 */
518 public static function duplicate_single_field( $field_id, $form_id ) {
519 $copy_field = self::getOne( $field_id );
520 if ( ! $copy_field ) {
521 return false;
522 }
523
524 do_action( 'frm_duplicate_field', $copy_field, $form_id );
525 do_action( 'frm_duplicate_field_' . $copy_field->type, $copy_field, $form_id );
526
527 $values = array(
528 'id' => $copy_field->id,
529 );
530 FrmFieldsHelper::fill_field( $values, $copy_field, $copy_field->form_id );
531 $values = apply_filters( 'frm_prepare_single_field_for_duplication', $values );
532
533 $field_id = self::create( $values );
534
535 /**
536 * Fires after duplicating a field.
537 *
538 * @since 5.0.04
539 *
540 * @param array $args {
541 * The arguments.
542 *
543 * @type int $field_id New field ID.
544 * @type array $values Values before inserting.
545 * @type object $copy_field Copy field data.
546 * @type int $form_id Form ID.
547 * }
548 */
549 do_action( 'frm_after_duplicate_field', compact( 'field_id', 'values', 'copy_field', 'form_id' ) );
550
551 return compact( 'field_id', 'values' );
552 }
553
554 public static function duplicate( $old_form_id, $form_id, $copy_keys = false, $blog_id = false ) {
555 global $frm_duplicate_ids;
556
557 $where = array(
558 array(
559 'or' => 1,
560 'fi.form_id' => $old_form_id,
561 'fr.parent_form_id' => $old_form_id,
562 ),
563 );
564 $fields = self::getAll( $where, 'field_order', '', $blog_id );
565
566 foreach ( (array) $fields as $field ) {
567 $new_key = $copy_keys ? $field->field_key : '';
568 if ( $copy_keys && substr( $field->field_key, - 1 ) == 2 ) {
569 $new_key = rtrim( $new_key, 2 );
570 }
571
572 $values = array();
573 FrmFieldsHelper::fill_field( $values, $field, $form_id, $new_key );
574
575 // If this is a repeating section, create new form
576 if ( self::is_repeating_field( $field ) ) {
577 // create the repeatable form
578 $new_repeat_form_id = apply_filters(
579 'frm_create_repeat_form',
580 0,
581 array(
582 'parent_form_id' => $form_id,
583 'field_name' => $field->name,
584 )
585 );
586
587 // Save old form_select
588 $old_repeat_form_id = $field->field_options['form_select'];
589
590 // Update form_select for repeating field
591 $values['field_options']['form_select'] = $new_repeat_form_id;
592 }
593
594 // If this is a field inside of a repeating section, associate it with the correct form
595 if ( $field->form_id != $old_form_id && isset( $old_repeat_form_id ) && isset( $new_repeat_form_id ) && $field->form_id == $old_repeat_form_id ) {
596 $values['form_id'] = $new_repeat_form_id;
597 }
598
599 $values['description'] = FrmFieldsHelper::switch_field_ids( $values['description'] );
600
601 $values = apply_filters( 'frm_duplicated_field', $values );
602 $new_id = self::create( $values );
603 $frm_duplicate_ids[ $field->id ] = $new_id;
604 $frm_duplicate_ids[ $field->field_key ] = $new_id;
605 unset( $field );
606 }//end foreach
607 }
608
609 /**
610 * @param int|string $id
611 * @param array $values
612 * @return false|int
613 */
614 public static function update( $id, $values ) {
615 global $wpdb;
616
617 $id = absint( $id );
618 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
619
620 if ( isset( $values['field_key'] ) ) {
621 $values['field_key'] = FrmAppHelper::get_unique_key( $values['field_key'], $wpdb->prefix . 'frm_fields', 'field_key', $id );
622 }
623
624 if ( isset( $values['required'] ) ) {
625 $values['required'] = (int) $values['required'];
626 }
627
628 self::preserve_format_option_backslashes( $values );
629
630 if ( isset( $values['type'] ) ) {
631 if ( 'dropdown' === $values['type'] ) {
632 // To avoid conflicts with security plugins the value "dropdown" is sent for select fields.
633 // This is because "select" gets matched for SQL injection attempts.
634 $values['type'] = 'select';
635 }
636
637 /**
638 * @since 6.9 The Field ID param was added.
639 *
640 * @param array $values
641 * @param int $id Field ID.
642 */
643 $values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values, $id );
644
645 if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
646 // don't keep the old placeholder setting for hidden fields
647 $values['field_options']['clear_on_focus'] = 0;
648 }
649 }
650
651 // serialize array values
652 foreach ( array( 'field_options', 'options' ) as $opt ) {
653 if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
654 if ( 'field_options' === $opt ) {
655 $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
656 }
657 $values[ $opt ] = serialize( $values[ $opt ] );
658 }
659 }
660 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
661 $values['default_value'] = json_encode( $values['default_value'] );
662 }
663
664 $query_results = $wpdb->update( $wpdb->prefix . 'frm_fields', $values, array( 'id' => $id ) );
665
666 $form_id = 0;
667 if ( isset( $values['form_id'] ) ) {
668 $form_id = absint( $values['form_id'] );
669 } else {
670 $field = self::getOne( $id );
671 if ( $field ) {
672 $form_id = $field->form_id;
673 }
674 unset( $field );
675 }
676 unset( $values );
677
678 if ( $query_results ) {
679 wp_cache_delete( $id, 'frm_field' );
680 if ( $form_id ) {
681 self::delete_form_transient( $form_id );
682 }
683 }
684
685 return $query_results;
686 }
687
688 /**
689 * Keep backslashes in the phone format option
690 *
691 * @since 2.0.8
692 *
693 * @param array $values Pass by reference.
694 */
695 private static function preserve_format_option_backslashes( &$values ) {
696 if ( isset( $values['field_options']['format'] ) ) {
697 $values['field_options']['format'] = FrmAppHelper::preserve_backslashes( $values['field_options']['format'] );
698 }
699 }
700
701 public static function destroy( $id ) {
702 global $wpdb;
703
704 do_action( 'frm_before_destroy_field', $id );
705
706 wp_cache_delete( $id, 'frm_field' );
707 $field = self::getOne( $id );
708 if ( ! $field ) {
709 return false;
710 }
711
712 self::delete_form_transient( $field->form_id );
713
714 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id=%d', $id ) );
715
716 return $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $id ) );
717 }
718
719 public static function delete_form_transient( $form_id ) {
720 $form_id = absint( $form_id );
721 delete_transient( 'frm_form_fields_' . $form_id . 'excludeinclude' );
722 delete_transient( 'frm_form_fields_' . $form_id . 'includeinclude' );
723 delete_transient( 'frm_form_fields_' . $form_id . 'includeexclude' );
724 delete_transient( 'frm_form_fields_' . $form_id . 'excludeexclude' );
725
726 global $wpdb;
727 $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%' ) );
728
729 FrmDb::cache_delete_group( 'frm_field' );
730
731 $form = FrmForm::getOne( $form_id );
732 if ( $form && $form->parent_form_id && $form->parent_form_id != $form_id ) {
733 self::delete_form_transient( $form->parent_form_id );
734 }
735 }
736
737 /**
738 * If $field is numeric, get the field object
739 *
740 * @param int|object|string $field
741 * @return void
742 */
743 public static function maybe_get_field( &$field ) {
744 if ( ! is_object( $field ) ) {
745 $field = self::getOne( $field );
746 }
747 }
748
749 /**
750 * @param int|string $id The field id or key.
751 * @param bool $filter When true, run the frm_field filter.
752 */
753 public static function getOne( $id, $filter = false ) {
754 if ( empty( $id ) ) {
755 return null;
756 }
757
758 global $wpdb;
759
760 $where = is_numeric( $id ) ? 'id=%d' : 'field_key=%s';
761 $query = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'frm_fields WHERE ' . $where, $id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
762
763 $results = FrmDb::check_cache( $id, 'frm_field', $query, 'get_row', 0 );
764
765 if ( empty( $results ) ) {
766 self::filter_field( $filter, $results );
767 return $results;
768 }
769
770 if ( is_numeric( $id ) ) {
771 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
772 } elseif ( $results ) {
773 FrmDb::set_cache( $results->id, $results, 'frm_field' );
774 }
775
776 self::prepare_options( $results );
777 self::filter_field( $filter, $results );
778
779 return wp_unslash( $results );
780 }
781
782 /**
783 * @since 3.06.01
784 * @param bool $filter When true, run the frm_field filter.
785 * @param object $results
786 */
787 private static function filter_field( $filter, &$results ) {
788 if ( $filter ) {
789 /**
790 * @since 3.06.01
791 */
792 $results = apply_filters( 'frm_field', $results );
793 }
794 }
795
796 /**
797 * Get the field type by key or id
798 *
799 * @param int|string $id The field id or key.
800 * @param mixed $col The name of the column in the fields database table.
801 */
802 public static function get_type( $id, $col = 'type' ) {
803 $field = FrmDb::check_cache( $id, 'frm_field' );
804 if ( $field ) {
805 $type = $field->{$col};
806 } else {
807 $where = array(
808 'or' => 1,
809 'id' => $id,
810 'field_key' => $id,
811 );
812 $type = FrmDb::get_var( 'frm_fields', $where, $col );
813 }
814
815 return $type;
816 }
817
818 /**
819 * @param int|string $form_id
820 * @param string $type
821 * @param int|string $limit
822 * @param string $inc_sub
823 */
824 public static function get_all_types_in_form( $form_id, $type, $limit = '', $inc_sub = 'exclude' ) {
825 if ( ! $form_id ) {
826 return array();
827 }
828
829 $results = self::get_fields_from_transients(
830 $form_id,
831 array(
832 'inc_embed' => $inc_sub,
833 'inc_repeat' => $inc_sub,
834 )
835 );
836 if ( ! empty( $results ) ) {
837 $fields = array();
838 $count = 0;
839 foreach ( $results as $result ) {
840 if ( $type != $result->type ) {
841 continue;
842 }
843
844 $fields[ $result->id ] = $result;
845 ++$count;
846 if ( $limit == 1 ) {
847 $fields = $result;
848 break;
849 }
850
851 if ( ! empty( $limit ) && $count >= $limit ) {
852 break;
853 }
854
855 unset( $result );
856 }
857
858 return wp_unslash( $fields );
859 }//end if
860
861 self::$use_cache = false;
862
863 $where = array(
864 'fi.form_id' => (int) $form_id,
865 'fi.type' => $type,
866 );
867 self::maybe_include_repeating_fields( $inc_sub, $where );
868 $results = self::getAll( $where, 'field_order', $limit );
869 self::$use_cache = true;
870 self::include_sub_fields( $results, $inc_sub, $type, $form_id );
871
872 return $results;
873 }
874
875 /**
876 * @param int|string $form_id
877 * @param int|string $limit
878 * @param string $inc_embed
879 * @param string $inc_repeat
880 * @return array
881 */
882 public static function get_all_for_form( $form_id, $limit = '', $inc_embed = 'exclude', $inc_repeat = 'include' ) {
883 if ( ! (int) $form_id ) {
884 return array();
885 }
886
887 $results = self::get_fields_from_transients( $form_id, compact( 'inc_embed', 'inc_repeat' ) );
888 if ( ! empty( $results ) ) {
889 if ( empty( $limit ) ) {
890 return $results;
891 }
892
893 $fields = array();
894 $count = 0;
895 foreach ( $results as $result ) {
896 ++$count;
897 $fields[ $result->id ] = $result;
898 if ( ! empty( $limit ) && $count >= $limit ) {
899 break;
900 }
901 }
902
903 return $fields;
904 }
905
906 self::$use_cache = false;
907
908 $where = array( 'fi.form_id' => absint( $form_id ) );
909 self::maybe_include_repeating_fields( $inc_repeat, $where );
910 $results = self::getAll( $where, 'field_order', $limit );
911
912 self::$use_cache = true;
913
914 self::include_sub_fields( $results, $inc_embed, 'all', $form_id );
915
916 if ( empty( $limit ) ) {
917 self::set_field_transient( $results, $form_id, 0, compact( 'inc_embed', 'inc_repeat' ) );
918 }
919
920 return $results;
921 }
922
923 /**
924 * If repeating fields should be included, adjust $where accordingly
925 *
926 * @param string $inc_repeat
927 * @param array $where Pass by reference.
928 */
929 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
930 if ( $inc_repeat === 'include' ) {
931 $form_id = $where['fi.form_id'];
932 $where[] = array(
933 'or' => 1,
934 'fi.form_id' => $form_id,
935 'fr.parent_form_id' => $form_id,
936 );
937 unset( $where['fi.form_id'] );
938 }
939 }
940
941 public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
942 $no_sub_forms = empty( $results ) && $type === 'all';
943 if ( 'include' != $inc_embed || $no_sub_forms ) {
944 return;
945 }
946
947 $form_fields = $results;
948 $should_get_subforms = ( $type !== 'all' && $type !== 'form' && ! empty( $form_id ) );
949 if ( $should_get_subforms ) {
950 $form_fields = self::get_all_types_in_form( $form_id, 'form' );
951 }
952
953 $index_offset = 1;
954 foreach ( $form_fields as $k => $field ) {
955 if ( 'form' != $field->type || ! isset( $field->field_options['form_select'] ) ) {
956 continue;
957 }
958
959 if ( $type === 'all' ) {
960 $sub_fields = self::get_all_for_form( $field->field_options['form_select'] );
961 } else {
962 $sub_fields = self::get_all_types_in_form( $field->field_options['form_select'], $type );
963 }
964
965 if ( ! empty( $sub_fields ) ) {
966 $index = $k + $index_offset;
967 $index_offset += count( $sub_fields );
968 array_splice( $results, $index, 0, $sub_fields );
969 }
970 unset( $field, $sub_fields );
971 }
972 }
973
974 public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
975 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
976 if ( self::$use_cache ) {
977 // make sure old cache doesn't get saved as a transient
978 $results = wp_cache_get( $cache_key, 'frm_field' );
979 if ( false !== $results ) {
980 return wp_unslash( $results );
981 }
982 }
983
984 global $wpdb;
985
986 if ( $blog_id && is_multisite() ) {
987 global $wpmuBaseTablePrefix;
988 if ( $wpmuBaseTablePrefix ) {
989 $prefix = $wpmuBaseTablePrefix . $blog_id . '_';
990 } else {
991 $prefix = $wpdb->get_blog_prefix( $blog_id );
992 }
993
994 $table_name = $prefix . 'frm_fields';
995 $form_table_name = $prefix . 'frm_forms';
996 } else {
997 $table_name = $wpdb->prefix . 'frm_fields';
998 $form_table_name = $wpdb->prefix . 'frm_forms';
999 }
1000
1001 if ( ! empty( $order_by ) && strpos( $order_by, 'ORDER BY' ) === false ) {
1002 $order_by = ' ORDER BY ' . $order_by;
1003 }
1004
1005 $limit = FrmDb::esc_limit( $limit );
1006
1007 $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";
1008 $query_type = $limit === ' LIMIT 1' || $limit == 1 ? 'row' : 'results';
1009
1010 if ( is_array( $where ) ) {
1011 $args = array(
1012 'order_by' => $order_by,
1013 'limit' => $limit,
1014 );
1015 $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 );
1016 } else {
1017 // if the query is not an array, then it has already been prepared
1018 $query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
1019
1020 $function_name = $query_type === 'row' ? 'get_row' : 'get_results';
1021 $results = $wpdb->$function_name( $query );
1022 }
1023 unset( $where );
1024
1025 self::format_field_results( $results );
1026
1027 FrmDb::set_cache( $cache_key, $results, 'frm_field' );
1028
1029 return wp_unslash( $results );
1030 }
1031
1032 /**
1033 * @since 2.0.8
1034 */
1035 private static function format_field_results( &$results ) {
1036 if ( is_array( $results ) ) {
1037 foreach ( $results as $r_key => $result ) {
1038 FrmDb::set_cache( $result->id, $result, 'frm_field' );
1039 FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
1040
1041 self::prepare_options( $result );
1042 $results[ $r_key ]->field_options = $result->field_options;
1043 $results[ $r_key ]->options = $result->options;
1044 $results[ $r_key ]->default_value = $result->default_value;
1045
1046 unset( $r_key, $result );
1047 }
1048 } elseif ( $results ) {
1049 FrmDb::set_cache( $results->id, $results, 'frm_field' );
1050 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
1051
1052 self::prepare_options( $results );
1053 }
1054 }
1055
1056 /**
1057 * Unserialize all the serialized field data
1058 *
1059 * @since 2.0
1060 */
1061 private static function prepare_options( &$results ) {
1062 FrmAppHelper::unserialize_or_decode( $results->field_options );
1063 FrmAppHelper::unserialize_or_decode( $results->options );
1064
1065 // Allow a single box to be checked for the default value.
1066 $before = $results->default_value;
1067 FrmAppHelper::unserialize_or_decode( $results->default_value );
1068 if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
1069 $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
1070 }
1071 }
1072
1073 /**
1074 * If a form has too many fields, they won't all save into a single transient.
1075 * We'll break them into groups of 200
1076 *
1077 * @since 2.0.1
1078 */
1079 private static function get_fields_from_transients( $form_id, $args ) {
1080 $fields = array();
1081 self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
1082
1083 return $fields;
1084 }
1085
1086 /**
1087 * Called by get_fields_from_transients
1088 *
1089 * @since 2.0.1
1090 */
1091 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
1092 $name = $next ? $base_name . $next : $base_name;
1093 $next_fields = get_transient( $name );
1094
1095 if ( $next_fields ) {
1096 $fields = array_merge( $fields, $next_fields );
1097
1098 if ( count( $next_fields ) >= self::$transient_size ) {
1099 // if this transient is full, check for another
1100 ++$next;
1101 self::get_next_transient( $fields, $base_name, $next );
1102 }
1103 }
1104 }
1105
1106 /**
1107 * Save the transients in chunks for large forms
1108 *
1109 * @since 2.0.1
1110 */
1111 private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
1112 $base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
1113 $field_chunks = array_chunk( $fields, self::$transient_size );
1114
1115 foreach ( $field_chunks as $field ) {
1116 $name = $next ? $base_name . $next : $base_name;
1117 $set = set_transient( $name, $field, 60 * 60 * 6 );
1118 if ( ! $set ) {
1119 // the transient didn't save
1120 if ( $name != $base_name ) {
1121 // if the first saved an others fail, this will show an incmoplete form
1122 self::delete_form_transient( $form_id );
1123 }
1124
1125 return;
1126 }
1127
1128 ++$next;
1129 }
1130 }
1131
1132 /**
1133 * @param string $type
1134 * @return bool
1135 */
1136 public static function is_no_save_field( $type ) {
1137 return in_array( $type, self::no_save_fields(), true );
1138 }
1139
1140 /**
1141 * @return string[]
1142 */
1143 public static function no_save_fields() {
1144 return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary', FrmSubmitHelper::FIELD_TYPE );
1145 }
1146
1147 /**
1148 * Check if this field can hold an array of values
1149 *
1150 * @since 2.0.9
1151 *
1152 * @param array|object $field
1153 *
1154 * @return bool
1155 */
1156 public static function is_field_with_multiple_values( $field ) {
1157 if ( ! $field ) {
1158 return false;
1159 }
1160
1161 $field_type = self::get_original_field_type( $field );
1162
1163 $is_multi_value_field = (
1164 self::is_checkbox( $field ) ||
1165 $field_type === 'address' ||
1166 self::is_multiple_select( $field )
1167 );
1168
1169 return $is_multi_value_field;
1170 }
1171
1172 /**
1173 * @since 3.0
1174 * @param array|object $field
1175 * @return string
1176 */
1177 public static function get_field_type( $field ) {
1178 return is_array( $field ) ? $field['type'] : $field->type;
1179 }
1180
1181 /**
1182 * @since 3.0
1183 * @return string
1184 */
1185 public static function get_original_field_type( $field ) {
1186 $field_type = self::get_field_type( $field );
1187 $original_type = self::get_option( $field, 'original_type' );
1188
1189 if ( ! empty( $original_type ) && $original_type != $field_type ) {
1190 // Check the original type for arrays.
1191 $field_type = $original_type;
1192 }
1193
1194 return $field_type;
1195 }
1196
1197 /**
1198 * Check if this is a multiselect dropdown field
1199 *
1200 * @since 2.0.9
1201 * @return bool
1202 */
1203 public static function is_multiple_select( $field ) {
1204 $field_type = self::get_field_type( $field );
1205 $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
1206
1207 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
1208 }
1209
1210 /**
1211 * Check if a field is read only. Read only can be set in the field options,
1212 * but disabled with the shortcode options
1213 *
1214 * @since 2.0.9
1215 *
1216 * @param array|object $field
1217 * @return bool
1218 */
1219 public static function is_read_only( $field ) {
1220 global $frm_vars;
1221 return self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] !== 'disabled' );
1222 }
1223
1224 /**
1225 * @since 2.0.9
1226 *
1227 * @param array $field
1228 * @return bool
1229 */
1230 public static function is_required( $field ) {
1231 $required = $field['required'] != '0';
1232
1233 /**
1234 * @param bool $required
1235 * @param array $field
1236 */
1237 $required = (bool) apply_filters( 'frm_is_field_required', $required, $field );
1238
1239 return $required;
1240 }
1241
1242 /**
1243 * @since 2.0.9
1244 *
1245 * @param array|object $field
1246 * @param string $option
1247 * @return bool
1248 */
1249 public static function is_option_true( $field, $option ) {
1250 if ( is_array( $field ) ) {
1251 return self::is_option_true_in_array( $field, $option );
1252 }
1253 return self::is_option_true_in_object( $field, $option );
1254 }
1255
1256 /**
1257 * @since 2.0.9
1258 *
1259 * @param array|object $field
1260 * @param string $option
1261 * @return bool
1262 */
1263 public static function is_option_empty( $field, $option ) {
1264 if ( is_array( $field ) ) {
1265 return self::is_option_empty_in_array( $field, $option );
1266 }
1267 return self::is_option_empty_in_object( $field, $option );
1268 }
1269
1270 /**
1271 * @param array $field
1272 * @param string $option
1273 * @return bool
1274 */
1275 public static function is_option_true_in_array( $field, $option ) {
1276 return ! empty( $field[ $option ] );
1277 }
1278
1279 /**
1280 * @param object $field
1281 * @param string $option
1282 * @return bool
1283 */
1284 public static function is_option_true_in_object( $field, $option ) {
1285 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
1286 }
1287
1288 /**
1289 * @param array $field
1290 * @param string $option
1291 * @return bool
1292 */
1293 public static function is_option_empty_in_array( $field, $option ) {
1294 return empty( $field[ $option ] );
1295 }
1296
1297 /**
1298 * @param object $field
1299 * @param string $option
1300 * @return bool
1301 */
1302 public static function is_option_empty_in_object( $field, $option ) {
1303 return empty( $field->field_options[ $option ] );
1304 }
1305
1306 /**
1307 * @param stdClass $field
1308 * @param string $option
1309 * @return bool
1310 */
1311 public static function is_option_value_in_object( $field, $option ) {
1312 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
1313 }
1314
1315 /**
1316 * @since 2.0.18
1317 *
1318 * @param array|object $field
1319 * @param string $option
1320 * @return mixed
1321 */
1322 public static function get_option( $field, $option ) {
1323 if ( is_array( $field ) ) {
1324 $option = self::get_option_in_array( $field, $option );
1325 } else {
1326 $option = self::get_option_in_object( $field, $option );
1327 }
1328
1329 return $option;
1330 }
1331
1332 /**
1333 * @param array $field
1334 * @param string $option
1335 * @return mixed
1336 */
1337 public static function get_option_in_array( $field, $option ) {
1338 if ( isset( $field[ $option ] ) ) {
1339 $this_option = $field[ $option ];
1340 } elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
1341 $this_option = $field['field_options'][ $option ];
1342 } else {
1343 $this_option = '';
1344 }
1345
1346 return $this_option;
1347 }
1348
1349 /**
1350 * @param object $field
1351 * @param string $option
1352 * @return mixed
1353 */
1354 public static function get_option_in_object( $field, $option ) {
1355 return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
1356 }
1357
1358 /**
1359 * @since 2.0.09
1360 *
1361 * @param array|object $field
1362 * @return bool
1363 */
1364 public static function is_repeating_field( $field ) {
1365 if ( is_array( $field ) ) {
1366 $is_repeating_field = ( 'divider' === $field['type'] );
1367 } else {
1368 $is_repeating_field = ( 'divider' === $field->type );
1369 }
1370
1371 return $is_repeating_field && self::is_option_true( $field, 'repeat' );
1372 }
1373
1374 /**
1375 * @param string $key
1376 *
1377 * @return int field id
1378 */
1379 public static function get_id_by_key( $key ) {
1380 $id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
1381
1382 return (int) $id;
1383 }
1384
1385 /**
1386 * @param string $id
1387 *
1388 * @return string|null
1389 */
1390 public static function get_key_by_id( $id ) {
1391 return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
1392 }
1393
1394 public static function is_image( $field ) {
1395 $type = self::get_field_type( $field );
1396
1397 return ( $type === 'url' && self::get_option( $field, 'show_image' ) );
1398 }
1399
1400 /**
1401 * Check if field is radio or Dynamic radio
1402 *
1403 * @since 3.0
1404 *
1405 * @param array|object $field
1406 *
1407 * @return bool true if field type is radio or Dynamic radio
1408 */
1409 public static function is_radio( $field ) {
1410 return self::is_field_type( $field, 'radio' );
1411 }
1412
1413 /**
1414 * Check if field is checkbox or Dynamic checkbox
1415 *
1416 * @since 3.0
1417 *
1418 * @param array|object $field
1419 *
1420 * @return bool true if field type is checkbox or Dynamic checkbox
1421 */
1422 public static function is_checkbox( $field ) {
1423 return self::is_field_type( $field, 'checkbox' );
1424 }
1425
1426 /**
1427 * Check if field is checkbox or radio
1428 *
1429 * @since 3.0
1430 *
1431 * @param array|object $field
1432 * @param string $is_type Options include radio, checkbox, text.
1433 *
1434 * @return bool true if field type is checkbox or Dynamic checkbox
1435 */
1436 public static function is_field_type( $field, $is_type ) {
1437 $field_type = self::get_original_field_type( $field );
1438 $data_type = self::get_option( $field, 'data_type' );
1439
1440 $is_field_type = (
1441 $is_type === $field_type ||
1442 ( 'data' === $field_type && $is_type === $data_type ) ||
1443 ( 'lookup' === $field_type && $is_type === $data_type ) ||
1444 ( 'product' === $field_type && $is_type === $data_type )
1445 );
1446
1447 /**
1448 * When a field type is checked, allow individual fields
1449 * to set the type.
1450 *
1451 * @since 4.04
1452 */
1453 return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
1454 }
1455
1456 /**
1457 * Checks if the given field array is a combo field.
1458 *
1459 * @since 4.10.02
1460 *
1461 * @param array $field Field array.
1462 * @return bool
1463 */
1464 public static function is_combo_field( $field ) {
1465 $field_type_obj = FrmFieldFactory::get_field_factory( $field );
1466
1467 return ! empty( $field_type_obj->is_combo_field );
1468 }
1469 }
1470