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

1,490 lines 43.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class 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( 'FrmFormsHelper::sanitize_layout_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 self::add_slashes_to_format_before_setting_field_cache( $result );
1039
1040 FrmDb::set_cache( $result->id, $result, 'frm_field' );
1041 FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
1042
1043 self::prepare_options( $result );
1044 $results[ $r_key ]->field_options = $result->field_options;
1045 $results[ $r_key ]->options = $result->options;
1046 $results[ $r_key ]->default_value = $result->default_value;
1047
1048 unset( $r_key, $result );
1049 }
1050 } elseif ( $results ) {
1051 FrmDb::set_cache( $results->id, $results, 'frm_field' );
1052 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
1053
1054 self::prepare_options( $results );
1055 }
1056 }
1057
1058 /**
1059 * When $result->field_options is an array and not a serialized string there is only a single backslash.
1060 * Cached results are unslashed in FrmField::getAll, so we need to make sure that the cached object has an extra backslash.
1061 * Otherwise the backslash is stripped away on load.
1062 *
1063 * @since 6.15
1064 *
1065 * @param stdClass $result
1066 * @return void
1067 */
1068 private static function add_slashes_to_format_before_setting_field_cache( $result ) {
1069 if ( ! isset( $result->field_options ) || ! is_array( $result->field_options ) || empty( $result->field_options['format'] ) ) {
1070 return;
1071 }
1072
1073 $result->field_options['format'] = addslashes( $result->field_options['format'] );
1074 }
1075
1076 /**
1077 * Unserialize all the serialized field data
1078 *
1079 * @since 2.0
1080 */
1081 private static function prepare_options( &$results ) {
1082 FrmAppHelper::unserialize_or_decode( $results->field_options );
1083 FrmAppHelper::unserialize_or_decode( $results->options );
1084
1085 // Allow a single box to be checked for the default value.
1086 $before = $results->default_value;
1087 FrmAppHelper::unserialize_or_decode( $results->default_value );
1088 if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
1089 $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
1090 }
1091 }
1092
1093 /**
1094 * If a form has too many fields, they won't all save into a single transient.
1095 * We'll break them into groups of 200
1096 *
1097 * @since 2.0.1
1098 */
1099 private static function get_fields_from_transients( $form_id, $args ) {
1100 $fields = array();
1101 self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
1102
1103 return $fields;
1104 }
1105
1106 /**
1107 * Called by get_fields_from_transients
1108 *
1109 * @since 2.0.1
1110 */
1111 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
1112 $name = $next ? $base_name . $next : $base_name;
1113 $next_fields = get_transient( $name );
1114
1115 if ( $next_fields ) {
1116 $fields = array_merge( $fields, $next_fields );
1117
1118 if ( count( $next_fields ) >= self::$transient_size ) {
1119 // if this transient is full, check for another
1120 ++$next;
1121 self::get_next_transient( $fields, $base_name, $next );
1122 }
1123 }
1124 }
1125
1126 /**
1127 * Save the transients in chunks for large forms
1128 *
1129 * @since 2.0.1
1130 */
1131 private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
1132 $base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
1133 $field_chunks = array_chunk( $fields, self::$transient_size );
1134
1135 foreach ( $field_chunks as $field ) {
1136 $name = $next ? $base_name . $next : $base_name;
1137 $set = set_transient( $name, $field, 60 * 60 * 6 );
1138 if ( ! $set ) {
1139 // the transient didn't save
1140 if ( $name != $base_name ) {
1141 // if the first saved an others fail, this will show an incmoplete form
1142 self::delete_form_transient( $form_id );
1143 }
1144
1145 return;
1146 }
1147
1148 ++$next;
1149 }
1150 }
1151
1152 /**
1153 * @param string $type
1154 * @return bool
1155 */
1156 public static function is_no_save_field( $type ) {
1157 return in_array( $type, self::no_save_fields(), true );
1158 }
1159
1160 /**
1161 * @return string[]
1162 */
1163 public static function no_save_fields() {
1164 return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary', FrmSubmitHelper::FIELD_TYPE );
1165 }
1166
1167 /**
1168 * Check if this field can hold an array of values
1169 *
1170 * @since 2.0.9
1171 *
1172 * @param array|object $field
1173 *
1174 * @return bool
1175 */
1176 public static function is_field_with_multiple_values( $field ) {
1177 if ( ! $field ) {
1178 return false;
1179 }
1180
1181 $field_type = self::get_original_field_type( $field );
1182
1183 $is_multi_value_field = (
1184 self::is_checkbox( $field ) ||
1185 $field_type === 'address' ||
1186 self::is_multiple_select( $field )
1187 );
1188
1189 return $is_multi_value_field;
1190 }
1191
1192 /**
1193 * @since 3.0
1194 * @param array|object $field
1195 * @return string
1196 */
1197 public static function get_field_type( $field ) {
1198 return is_array( $field ) ? $field['type'] : $field->type;
1199 }
1200
1201 /**
1202 * @since 3.0
1203 * @return string
1204 */
1205 public static function get_original_field_type( $field ) {
1206 $field_type = self::get_field_type( $field );
1207 $original_type = self::get_option( $field, 'original_type' );
1208
1209 if ( ! empty( $original_type ) && $original_type != $field_type ) {
1210 // Check the original type for arrays.
1211 $field_type = $original_type;
1212 }
1213
1214 return $field_type;
1215 }
1216
1217 /**
1218 * Check if this is a multiselect dropdown field
1219 *
1220 * @since 2.0.9
1221 * @return bool
1222 */
1223 public static function is_multiple_select( $field ) {
1224 $field_type = self::get_field_type( $field );
1225 $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
1226
1227 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
1228 }
1229
1230 /**
1231 * Check if a field is read only. Read only can be set in the field options,
1232 * but disabled with the shortcode options
1233 *
1234 * @since 2.0.9
1235 *
1236 * @param array|object $field
1237 * @return bool
1238 */
1239 public static function is_read_only( $field ) {
1240 global $frm_vars;
1241 return self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] !== 'disabled' );
1242 }
1243
1244 /**
1245 * @since 2.0.9
1246 *
1247 * @param array $field
1248 * @return bool
1249 */
1250 public static function is_required( $field ) {
1251 $required = $field['required'] != '0';
1252
1253 /**
1254 * @param bool $required
1255 * @param array $field
1256 */
1257 $required = (bool) apply_filters( 'frm_is_field_required', $required, $field );
1258
1259 return $required;
1260 }
1261
1262 /**
1263 * @since 2.0.9
1264 *
1265 * @param array|object $field
1266 * @param string $option
1267 * @return bool
1268 */
1269 public static function is_option_true( $field, $option ) {
1270 if ( is_array( $field ) ) {
1271 return self::is_option_true_in_array( $field, $option );
1272 }
1273 return self::is_option_true_in_object( $field, $option );
1274 }
1275
1276 /**
1277 * @since 2.0.9
1278 *
1279 * @param array|object $field
1280 * @param string $option
1281 * @return bool
1282 */
1283 public static function is_option_empty( $field, $option ) {
1284 if ( is_array( $field ) ) {
1285 return self::is_option_empty_in_array( $field, $option );
1286 }
1287 return self::is_option_empty_in_object( $field, $option );
1288 }
1289
1290 /**
1291 * @param array $field
1292 * @param string $option
1293 * @return bool
1294 */
1295 public static function is_option_true_in_array( $field, $option ) {
1296 return ! empty( $field[ $option ] );
1297 }
1298
1299 /**
1300 * @param object $field
1301 * @param string $option
1302 * @return bool
1303 */
1304 public static function is_option_true_in_object( $field, $option ) {
1305 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
1306 }
1307
1308 /**
1309 * @param array $field
1310 * @param string $option
1311 * @return bool
1312 */
1313 public static function is_option_empty_in_array( $field, $option ) {
1314 return empty( $field[ $option ] );
1315 }
1316
1317 /**
1318 * @param object $field
1319 * @param string $option
1320 * @return bool
1321 */
1322 public static function is_option_empty_in_object( $field, $option ) {
1323 return empty( $field->field_options[ $option ] );
1324 }
1325
1326 /**
1327 * @param stdClass $field
1328 * @param string $option
1329 * @return bool
1330 */
1331 public static function is_option_value_in_object( $field, $option ) {
1332 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
1333 }
1334
1335 /**
1336 * @since 2.0.18
1337 *
1338 * @param array|object $field
1339 * @param string $option
1340 * @return mixed
1341 */
1342 public static function get_option( $field, $option ) {
1343 if ( is_array( $field ) ) {
1344 $option = self::get_option_in_array( $field, $option );
1345 } else {
1346 $option = self::get_option_in_object( $field, $option );
1347 }
1348
1349 return $option;
1350 }
1351
1352 /**
1353 * @param array $field
1354 * @param string $option
1355 * @return mixed
1356 */
1357 public static function get_option_in_array( $field, $option ) {
1358 if ( isset( $field[ $option ] ) ) {
1359 $this_option = $field[ $option ];
1360 } elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
1361 $this_option = $field['field_options'][ $option ];
1362 } else {
1363 $this_option = '';
1364 }
1365
1366 return $this_option;
1367 }
1368
1369 /**
1370 * @param object $field
1371 * @param string $option
1372 * @return mixed
1373 */
1374 public static function get_option_in_object( $field, $option ) {
1375 return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
1376 }
1377
1378 /**
1379 * @since 2.0.09
1380 *
1381 * @param array|object $field
1382 * @return bool
1383 */
1384 public static function is_repeating_field( $field ) {
1385 if ( is_array( $field ) ) {
1386 $is_repeating_field = ( 'divider' === $field['type'] );
1387 } else {
1388 $is_repeating_field = ( 'divider' === $field->type );
1389 }
1390
1391 return $is_repeating_field && self::is_option_true( $field, 'repeat' );
1392 }
1393
1394 /**
1395 * @param string $key
1396 *
1397 * @return int field id
1398 */
1399 public static function get_id_by_key( $key ) {
1400 $id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
1401
1402 return (int) $id;
1403 }
1404
1405 /**
1406 * @param string $id
1407 *
1408 * @return string|null
1409 */
1410 public static function get_key_by_id( $id ) {
1411 return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
1412 }
1413
1414 public static function is_image( $field ) {
1415 $type = self::get_field_type( $field );
1416
1417 return ( $type === 'url' && self::get_option( $field, 'show_image' ) );
1418 }
1419
1420 /**
1421 * Check if field is radio or Dynamic radio
1422 *
1423 * @since 3.0
1424 *
1425 * @param array|object $field
1426 *
1427 * @return bool true if field type is radio or Dynamic radio
1428 */
1429 public static function is_radio( $field ) {
1430 return self::is_field_type( $field, 'radio' );
1431 }
1432
1433 /**
1434 * Check if field is checkbox or Dynamic checkbox
1435 *
1436 * @since 3.0
1437 *
1438 * @param array|object $field
1439 *
1440 * @return bool true if field type is checkbox or Dynamic checkbox
1441 */
1442 public static function is_checkbox( $field ) {
1443 return self::is_field_type( $field, 'checkbox' );
1444 }
1445
1446 /**
1447 * Check if field is checkbox or radio
1448 *
1449 * @since 3.0
1450 *
1451 * @param array|object $field
1452 * @param string $is_type Options include radio, checkbox, text.
1453 *
1454 * @return bool true if field type is checkbox or Dynamic checkbox
1455 */
1456 public static function is_field_type( $field, $is_type ) {
1457 $field_type = self::get_original_field_type( $field );
1458 $data_type = self::get_option( $field, 'data_type' );
1459
1460 $is_field_type = (
1461 $is_type === $field_type ||
1462 ( 'data' === $field_type && $is_type === $data_type ) ||
1463 ( 'lookup' === $field_type && $is_type === $data_type ) ||
1464 ( 'product' === $field_type && $is_type === $data_type )
1465 );
1466
1467 /**
1468 * When a field type is checked, allow individual fields
1469 * to set the type.
1470 *
1471 * @since 4.04
1472 */
1473 return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
1474 }
1475
1476 /**
1477 * Checks if the given field array is a combo field.
1478 *
1479 * @since 4.10.02
1480 *
1481 * @param array $field Field array.
1482 * @return bool
1483 */
1484 public static function is_combo_field( $field ) {
1485 $field_type_obj = FrmFieldFactory::get_field_factory( $field );
1486
1487 return ! empty( $field_type_obj->is_combo_field );
1488 }
1489 }
1490