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

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