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

1,197 lines 35.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class 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 );
74
75 /**
76 * @param array $fields
77 */
78 return apply_filters( 'frm_available_fields', $fields );
79 }
80
81 /**
82 * Get the name of the Captcha field based on the global Captcha setting.
83 *
84 * @return string
85 */
86 private static function get_captcha_field_name() {
87 $frm_settings = FrmAppHelper::get_settings();
88 $active_captcha = $frm_settings->active_captcha;
89 if ( ! FrmFieldCaptcha::should_show_captcha() ) {
90 $captcha_name = 'Captcha';
91 } elseif ( $active_captcha === 'recaptcha' ) {
92 $captcha_name = 'reCAPTCHA';
93 } else {
94 $captcha_name = 'hCaptcha';
95 }
96 return $captcha_name;
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_upload_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.',
106 ),
107 'rte' => array(
108 'name' => __( 'Rich Text', 'formidable' ),
109 'icon' => 'frm_icon_font frm_align_right_icon',
110 ),
111 'date' => array(
112 'name' => __( 'Date', 'formidable' ),
113 'icon' => 'frm_icon_font frm_calendar_icon',
114 ),
115 'time' => array(
116 'name' => __( 'Time', 'formidable' ),
117 'icon' => 'frm_icon_font frm_clock_icon',
118 ),
119 'scale' => array(
120 'name' => __( 'Scale', 'formidable' ),
121 'icon' => 'frm_icon_font frm_linear_scale_icon',
122 'message' => 'Add a set of radio buttons with whatever range you choose. <img src="' . esc_attr( $images_url ) . 'scale_field.png" alt="Scale Field" />',
123 ),
124 'star' => array(
125 'name' => __( 'Star Rating', 'formidable' ),
126 'icon' => 'frm_icon_font frm_star_icon',
127 ),
128 'range' => array(
129 'name' => __( 'Slider', 'formidable' ),
130 'icon' => 'frm_icon_font frm_code_commit_icon',
131 ),
132 'toggle' => array(
133 'name' => __( 'Toggle', 'formidable' ),
134 'icon' => 'frm_icon_font frm_toggle_on_icon',
135 ),
136 'data' => array(
137 'name' => __( 'Dynamic', 'formidable' ),
138 'icon' => 'frm_icon_font frm_sitemap_icon',
139 '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.',
140 ),
141 'lookup' => array(
142 'name' => __( 'Lookup', 'formidable' ),
143 'icon' => 'frm_icon_font frm_search_icon',
144 'message' => 'Filter the options in the next field and automatically add values to other fields. Upgrade to Pro to get Lookup fields and more. <img src="' . esc_attr( $images_url ) . 'look-up_year-make-model.gif" alt="cascading lookup fields" />',
145 ),
146 'divider|repeat' => array(
147 'name' => __( 'Repeater', 'formidable' ),
148 'icon' => 'frm_icon_font frm_repeater_icon',
149 'message' => 'Allow your visitors to add new sets of fields while filling out forms. Increase conversions while saving building time and server resources. <img src="' . esc_attr( $images_url ) . 'repeatable-section_frontend.gif" alt="Dynamically Add Form Fields with repeatable sections" />',
150 ),
151 'end_divider' => array(
152 'name' => __( 'Section Buttons', 'formidable' ),
153 'switch_from' => 'divider',
154 ),
155 'divider' => array(
156 'name' => __( 'Section', 'formidable' ),
157 'icon' => 'frm_icon_font frm_header_icon',
158 ),
159 'break' => array(
160 'name' => __( 'Page Break', 'formidable' ),
161 'icon' => 'frm_icon_font frm_page_break_icon',
162 '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?',
163 ),
164 'form' => array(
165 'name' => __( 'Embed Form', 'formidable' ),
166 'icon' => 'frm_icon_font frm_file_text_icon',
167 ),
168 'likert' => array(
169 'name' => __( 'Likert Scale', 'formidable' ),
170 'icon' => 'frm_icon_font frm_likert_scale frm_show_upgrade',
171 'addon' => 'surveys',
172 ),
173 'nps' => array(
174 'name' => __( 'NPS', 'formidable' ),
175 'icon' => 'frm_icon_font frm_nps frm_show_upgrade',
176 'addon' => 'surveys',
177 ),
178 'password' => array(
179 'name' => __( 'Password', 'formidable' ),
180 'icon' => 'frm_icon_font frm_lock_open_icon',
181 ),
182 'tag' => array(
183 'name' => __( 'Tags', 'formidable' ),
184 'icon' => 'frm_icon_font frm_price_tags_icon',
185 ),
186 // This is no longer a Pro field, but without this here, Pro triggers "undefined index" notices.
187 // Right now it leaves a gap. Maybe we can skip anything without a name or something.
188 'credit_card' => array(
189 'name' => '',
190 'icon' => '',
191 ),
192 'address' => array(
193 'name' => __( 'Address', 'formidable' ),
194 'icon' => 'frm_icon_font frm_location_icon',
195 ),
196 'summary' => array(
197 'name' => __( 'Summary', 'formidable' ),
198 'icon' => 'frm_icon_font frm_file_text_icon',
199 'message' => 'Allow visitors to review their responses before a form is submitted. Upgrade to Pro to get Summary fields and more.',
200 ),
201 'signature' => array(
202 'name' => __( 'Signature', 'formidable' ),
203 'icon' => 'frm_icon_font frm_signature_icon frm_show_upgrade',
204 'addon' => 'signature',
205 ),
206 'ai' => array(
207 'name' => __( 'AI', 'formidable' ),
208 'icon' => 'frm_icon_font frm_eye_icon frm_show_upgrade',
209 'addon' => 'ai',
210 '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.',
211 ),
212 'ssa-appointment' => array(
213 'name' => __( 'Appointment', 'formidable' ),
214 'icon' => 'frm_icon_font frm_calendar_icon frm_show_upgrade',
215 'require' => 'Simply Schedule Appointments',
216 'message' => 'Appointment fields are an integration with <a href="https://simplyscheduleappointments.com/meet/formidable/">Simply Schedule Appointments</a>. Get started now to schedule appointments directly from your forms.
217 <img src="' . esc_attr( $images_url ) . 'appointments.png" alt="Scheduling" />',
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 ( ! FrmAppHelper::show_new_feature( 'ai' ) ) {
238 unset( $fields['ai'] );
239 }
240
241 // Since the signature field may be in a different section, don't show it twice.
242 $lite_fields = self::field_selection();
243 if ( isset( $lite_fields['signature'] ) ) {
244 unset( $fields['signature'] );
245 }
246
247 return apply_filters( 'frm_pro_available_fields', $fields );
248 }
249
250 /**
251 * @since 4.0
252 */
253 public static function all_field_selection() {
254 $pro_field_selection = self::pro_field_selection();
255 return array_merge( $pro_field_selection, self::field_selection() );
256 }
257
258 /**
259 * Create a field.
260 *
261 * @param array $values
262 * @param bool $return
263 * @return int|false
264 */
265 public static function create( $values, $return = true ) {
266 global $wpdb, $frm_duplicate_ids;
267
268 $new_values = array();
269 $key = isset( $values['field_key'] ) ? $values['field_key'] : $values['name'];
270 $new_values['field_key'] = FrmAppHelper::get_unique_key( $key, $wpdb->prefix . 'frm_fields', 'field_key' );
271
272 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
273
274 foreach ( array( 'name', 'description', 'type', 'default_value' ) as $col ) {
275 $new_values[ $col ] = $values[ $col ];
276 }
277
278 $new_values['options'] = self::maybe_filter_options( $values['options'] );
279 $new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null;
280 $new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0;
281 $new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null;
282 $new_values['field_options'] = $values['field_options'];
283 $new_values['created_at'] = current_time( 'mysql', 1 );
284
285 if ( isset( $values['id'] ) ) {
286 $frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key'];
287 $new_values = apply_filters( 'frm_duplicated_field', $new_values );
288 }
289
290 self::preserve_format_option_backslashes( $new_values );
291
292 foreach ( $new_values as $k => $v ) {
293 if ( is_array( $v ) ) {
294 if ( $k === 'default_value' ) {
295 $new_values[ $k ] = FrmAppHelper::maybe_json_encode( $v );
296 } else {
297 $new_values[ $k ] = serialize( $v );
298 }
299 }
300 unset( $k, $v );
301 }
302
303 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_fields', $new_values );
304
305 if ( ! $query_results ) {
306 return false;
307 }
308
309 self::delete_form_transient( $new_values['form_id'] );
310 $new_id = $wpdb->insert_id;
311
312 if ( ! $return ) {
313 return false;
314 }
315
316 if ( isset( $values['id'] ) ) {
317 $frm_duplicate_ids[ $values['id'] ] = $new_id;
318 }
319
320 return $new_id;
321 }
322
323 /**
324 * @since 5.0.08
325 *
326 * @param array $options
327 * @return array
328 */
329 private static function maybe_filter_options( $options ) {
330 return FrmAppHelper::maybe_filter_array( $options, array( 'custom_html' ) );
331 }
332
333 /**
334 * Process the field duplication.
335 *
336 * @since 5.0.05
337 */
338 public static function duplicate_single_field( $field_id, $form_id ) {
339 $copy_field = self::getOne( $field_id );
340 if ( ! $copy_field ) {
341 return false;
342 }
343
344 do_action( 'frm_duplicate_field', $copy_field, $form_id );
345 do_action( 'frm_duplicate_field_' . $copy_field->type, $copy_field, $form_id );
346
347 $values = array(
348 'id' => $copy_field->id,
349 );
350 FrmFieldsHelper::fill_field( $values, $copy_field, $copy_field->form_id );
351 $values = apply_filters( 'frm_prepare_single_field_for_duplication', $values );
352
353 $field_id = self::create( $values );
354
355 /**
356 * Fires after duplicating a field.
357 *
358 * @since 5.0.04
359 *
360 * @param array $args {
361 * The arguments.
362 *
363 * @type int $field_id New field ID.
364 * @type array $values Values before inserting.
365 * @type object $copy_field Copy field data.
366 * @type int $form_id Form ID.
367 * }
368 */
369 do_action( 'frm_after_duplicate_field', compact( 'field_id', 'values', 'copy_field', 'form_id' ) );
370
371 return compact( 'field_id', 'values' );
372 }
373
374 public static function duplicate( $old_form_id, $form_id, $copy_keys = false, $blog_id = false ) {
375 global $frm_duplicate_ids;
376
377 $where = array(
378 array(
379 'or' => 1,
380 'fi.form_id' => $old_form_id,
381 'fr.parent_form_id' => $old_form_id,
382 ),
383 );
384 $fields = self::getAll( $where, 'field_order', '', $blog_id );
385
386 foreach ( (array) $fields as $field ) {
387 $new_key = $copy_keys ? $field->field_key : '';
388 if ( $copy_keys && substr( $field->field_key, - 1 ) == 2 ) {
389 $new_key = rtrim( $new_key, 2 );
390 }
391
392 $values = array();
393 FrmFieldsHelper::fill_field( $values, $field, $form_id, $new_key );
394
395 // If this is a repeating section, create new form
396 if ( self::is_repeating_field( $field ) ) {
397 // create the repeatable form
398 $new_repeat_form_id = apply_filters(
399 'frm_create_repeat_form',
400 0,
401 array(
402 'parent_form_id' => $form_id,
403 'field_name' => $field->name,
404 )
405 );
406
407 // Save old form_select
408 $old_repeat_form_id = $field->field_options['form_select'];
409
410 // Update form_select for repeating field
411 $values['field_options']['form_select'] = $new_repeat_form_id;
412 }
413
414 // If this is a field inside of a repeating section, associate it with the correct form
415 if ( $field->form_id != $old_form_id && isset( $old_repeat_form_id ) && isset( $new_repeat_form_id ) && $field->form_id == $old_repeat_form_id ) {
416 $values['form_id'] = $new_repeat_form_id;
417 }
418
419 $values['description'] = FrmFieldsHelper::switch_field_ids( $values['description'] );
420
421 $values = apply_filters( 'frm_duplicated_field', $values );
422 $new_id = self::create( $values );
423 $frm_duplicate_ids[ $field->id ] = $new_id;
424 $frm_duplicate_ids[ $field->field_key ] = $new_id;
425 unset( $field );
426 }
427 }
428
429 public static function update( $id, $values ) {
430 global $wpdb;
431
432 $id = absint( $id );
433 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
434
435 if ( isset( $values['field_key'] ) ) {
436 $values['field_key'] = FrmAppHelper::get_unique_key( $values['field_key'], $wpdb->prefix . 'frm_fields', 'field_key', $id );
437 }
438
439 if ( isset( $values['required'] ) ) {
440 $values['required'] = (int) $values['required'];
441 }
442
443 self::preserve_format_option_backslashes( $values );
444
445 if ( isset( $values['type'] ) ) {
446 if ( 'dropdown' === $values['type'] ) {
447 // To avoid conflicts with security plugins the value "dropdown" is sent for select fields.
448 // This is because "select" gets matched for SQL injection attempts.
449 $values['type'] = 'select';
450 }
451
452 $values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values );
453
454 if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
455 // don't keep the old placeholder setting for hidden fields
456 $values['field_options']['clear_on_focus'] = 0;
457 }
458 }
459
460 // serialize array values
461 foreach ( array( 'field_options', 'options' ) as $opt ) {
462 if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
463 if ( 'field_options' === $opt ) {
464 $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
465 }
466 $values[ $opt ] = serialize( $values[ $opt ] );
467 }
468 }
469 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
470 $values['default_value'] = json_encode( $values['default_value'] );
471 }
472
473 $query_results = $wpdb->update( $wpdb->prefix . 'frm_fields', $values, array( 'id' => $id ) );
474
475 $form_id = 0;
476 if ( isset( $values['form_id'] ) ) {
477 $form_id = absint( $values['form_id'] );
478 } else {
479 $field = self::getOne( $id );
480 if ( $field ) {
481 $form_id = $field->form_id;
482 }
483 unset( $field );
484 }
485 unset( $values );
486
487 if ( $query_results ) {
488 wp_cache_delete( $id, 'frm_field' );
489 if ( $form_id ) {
490 self::delete_form_transient( $form_id );
491 }
492 }
493
494 return $query_results;
495 }
496
497 /**
498 * Keep backslashes in the phone format option
499 *
500 * @since 2.0.8
501 *
502 * @param $values array - pass by reference
503 */
504 private static function preserve_format_option_backslashes( &$values ) {
505 if ( isset( $values['field_options']['format'] ) ) {
506 $values['field_options']['format'] = FrmAppHelper::preserve_backslashes( $values['field_options']['format'] );
507 }
508 }
509
510 public static function destroy( $id ) {
511 global $wpdb;
512
513 do_action( 'frm_before_destroy_field', $id );
514
515 wp_cache_delete( $id, 'frm_field' );
516 $field = self::getOne( $id );
517 if ( ! $field ) {
518 return false;
519 }
520
521 self::delete_form_transient( $field->form_id );
522
523 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id=%d', $id ) );
524
525 return $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $id ) );
526 }
527
528 public static function delete_form_transient( $form_id ) {
529 $form_id = absint( $form_id );
530 delete_transient( 'frm_form_fields_' . $form_id . 'excludeinclude' );
531 delete_transient( 'frm_form_fields_' . $form_id . 'includeinclude' );
532 delete_transient( 'frm_form_fields_' . $form_id . 'includeexclude' );
533 delete_transient( 'frm_form_fields_' . $form_id . 'excludeexclude' );
534
535 global $wpdb;
536 $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%' ) );
537
538 FrmDb::cache_delete_group( 'frm_field' );
539
540 $form = FrmForm::getOne( $form_id );
541 if ( $form && $form->parent_form_id && $form->parent_form_id != $form_id ) {
542 self::delete_form_transient( $form->parent_form_id );
543 }
544 }
545
546 /**
547 * If $field is numeric, get the field object
548 */
549 public static function maybe_get_field( &$field ) {
550 if ( ! is_object( $field ) ) {
551 $field = self::getOne( $field );
552 }
553 }
554
555 /**
556 * @param string|int $id The field id or key.
557 * @param bool $filter When true, run the frm_field filter.
558 */
559 public static function getOne( $id, $filter = false ) {
560 if ( empty( $id ) ) {
561 return null;
562 }
563
564 global $wpdb;
565
566 $where = is_numeric( $id ) ? 'id=%d' : 'field_key=%s';
567 $query = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'frm_fields WHERE ' . $where, $id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
568
569 $results = FrmDb::check_cache( $id, 'frm_field', $query, 'get_row', 0 );
570
571 if ( empty( $results ) ) {
572 self::filter_field( $filter, $results );
573 return $results;
574 }
575
576 if ( is_numeric( $id ) ) {
577 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
578 } elseif ( $results ) {
579 FrmDb::set_cache( $results->id, $results, 'frm_field' );
580 }
581
582 self::prepare_options( $results );
583 self::filter_field( $filter, $results );
584
585 return wp_unslash( $results );
586 }
587
588 /**
589 * @since 3.06.01
590 * @param bool $filter When true, run the frm_field filter.
591 * @param object $results
592 */
593 private static function filter_field( $filter, &$results ) {
594 if ( $filter ) {
595 /**
596 * @since 3.06.01
597 */
598 $results = apply_filters( 'frm_field', $results );
599 }
600 }
601
602 /**
603 * Get the field type by key or id
604 *
605 * @param int|string The field id or key
606 * @param mixed $col The name of the column in the fields database table
607 */
608 public static function get_type( $id, $col = 'type' ) {
609 $field = FrmDb::check_cache( $id, 'frm_field' );
610 if ( $field ) {
611 $type = $field->{$col};
612 } else {
613 $where = array(
614 'or' => 1,
615 'id' => $id,
616 'field_key' => $id,
617 );
618 $type = FrmDb::get_var( 'frm_fields', $where, $col );
619 }
620
621 return $type;
622 }
623
624 public static function get_all_types_in_form( $form_id, $type, $limit = '', $inc_sub = 'exclude' ) {
625 if ( ! $form_id ) {
626 return array();
627 }
628
629 $results = self::get_fields_from_transients(
630 $form_id,
631 array(
632 'inc_embed' => $inc_sub,
633 'inc_repeat' => $inc_sub,
634 )
635 );
636 if ( ! empty( $results ) ) {
637 $fields = array();
638 $count = 0;
639 foreach ( $results as $result ) {
640 if ( $type != $result->type ) {
641 continue;
642 }
643
644 $fields[ $result->id ] = $result;
645 $count ++;
646 if ( $limit == 1 ) {
647 $fields = $result;
648 break;
649 }
650
651 if ( ! empty( $limit ) && $count >= $limit ) {
652 break;
653 }
654
655 unset( $result );
656 }
657
658 return wp_unslash( $fields );
659 }
660
661 self::$use_cache = false;
662
663 $where = array(
664 'fi.form_id' => (int) $form_id,
665 'fi.type' => $type,
666 );
667 self::maybe_include_repeating_fields( $inc_sub, $where );
668 $results = self::getAll( $where, 'field_order', $limit );
669 self::$use_cache = true;
670 self::include_sub_fields( $results, $inc_sub, $type, $form_id );
671
672 return $results;
673 }
674
675 public static function get_all_for_form( $form_id, $limit = '', $inc_embed = 'exclude', $inc_repeat = 'include' ) {
676 if ( ! (int) $form_id ) {
677 return array();
678 }
679
680 $results = self::get_fields_from_transients( $form_id, compact( 'inc_embed', 'inc_repeat' ) );
681 if ( ! empty( $results ) ) {
682 if ( empty( $limit ) ) {
683 return $results;
684 }
685
686 $fields = array();
687 $count = 0;
688 foreach ( $results as $result ) {
689 $count ++;
690 $fields[ $result->id ] = $result;
691 if ( ! empty( $limit ) && $count >= $limit ) {
692 break;
693 }
694 }
695
696 return $fields;
697 }
698
699 self::$use_cache = false;
700
701 $where = array( 'fi.form_id' => absint( $form_id ) );
702 self::maybe_include_repeating_fields( $inc_repeat, $where );
703 $results = self::getAll( $where, 'field_order', $limit );
704
705 self::$use_cache = true;
706
707 self::include_sub_fields( $results, $inc_embed, 'all', $form_id );
708
709 if ( empty( $limit ) ) {
710 self::set_field_transient( $results, $form_id, 0, compact( 'inc_embed', 'inc_repeat' ) );
711 }
712
713 return $results;
714 }
715
716 /**
717 * If repeating fields should be included, adjust $where accordingly
718 *
719 * @param string $inc_repeat
720 * @param array $where - pass by reference
721 */
722 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
723 if ( $inc_repeat == 'include' ) {
724 $form_id = $where['fi.form_id'];
725 $where[] = array(
726 'or' => 1,
727 'fi.form_id' => $form_id,
728 'fr.parent_form_id' => $form_id,
729 );
730 unset( $where['fi.form_id'] );
731 }
732 }
733
734 public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
735 $no_sub_forms = empty( $results ) && $type === 'all';
736 if ( 'include' != $inc_embed || $no_sub_forms ) {
737 return;
738 }
739
740 $form_fields = $results;
741 $should_get_subforms = ( $type !== 'all' && $type !== 'form' && ! empty( $form_id ) );
742 if ( $should_get_subforms ) {
743 $form_fields = self::get_all_types_in_form( $form_id, 'form' );
744 }
745
746 $index_offset = 1;
747 foreach ( $form_fields as $k => $field ) {
748 if ( 'form' != $field->type || ! isset( $field->field_options['form_select'] ) ) {
749 continue;
750 }
751
752 if ( $type == 'all' ) {
753 $sub_fields = self::get_all_for_form( $field->field_options['form_select'] );
754 } else {
755 $sub_fields = self::get_all_types_in_form( $field->field_options['form_select'], $type );
756 }
757
758 if ( ! empty( $sub_fields ) ) {
759 $index = $k + $index_offset;
760 $index_offset += count( $sub_fields );
761 array_splice( $results, $index, 0, $sub_fields );
762 }
763 unset( $field, $sub_fields );
764 }
765 }
766
767 public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
768 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
769 if ( self::$use_cache ) {
770 // make sure old cache doesn't get saved as a transient
771 $results = wp_cache_get( $cache_key, 'frm_field' );
772 if ( false !== $results ) {
773 return wp_unslash( $results );
774 }
775 }
776
777 global $wpdb;
778
779 if ( $blog_id && is_multisite() ) {
780 global $wpmuBaseTablePrefix;
781 if ( $wpmuBaseTablePrefix ) {
782 $prefix = $wpmuBaseTablePrefix . $blog_id . '_';
783 } else {
784 $prefix = $wpdb->get_blog_prefix( $blog_id );
785 }
786
787 $table_name = $prefix . 'frm_fields';
788 $form_table_name = $prefix . 'frm_forms';
789 } else {
790 $table_name = $wpdb->prefix . 'frm_fields';
791 $form_table_name = $wpdb->prefix . 'frm_forms';
792 }
793
794 if ( ! empty( $order_by ) && strpos( $order_by, 'ORDER BY' ) === false ) {
795 $order_by = ' ORDER BY ' . $order_by;
796 }
797
798 $limit = FrmDb::esc_limit( $limit );
799
800 $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";
801 $query_type = ( $limit == ' LIMIT 1' || $limit == 1 ) ? 'row' : 'results';
802
803 if ( is_array( $where ) ) {
804 $args = array(
805 'order_by' => $order_by,
806 'limit' => $limit,
807 );
808 $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 );
809 } else {
810 // if the query is not an array, then it has already been prepared
811 $query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
812
813 $function_name = ( $query_type == 'row' ) ? 'get_row' : 'get_results';
814 $results = $wpdb->$function_name( $query );
815 }
816 unset( $where );
817
818 self::format_field_results( $results );
819
820 FrmDb::set_cache( $cache_key, $results, 'frm_field' );
821
822 return wp_unslash( $results );
823 }
824
825 /**
826 * @since 2.0.8
827 */
828 private static function format_field_results( &$results ) {
829 if ( is_array( $results ) ) {
830 foreach ( $results as $r_key => $result ) {
831 FrmDb::set_cache( $result->id, $result, 'frm_field' );
832 FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
833
834 self::prepare_options( $result );
835 $results[ $r_key ]->field_options = $result->field_options;
836 $results[ $r_key ]->options = $result->options;
837 $results[ $r_key ]->default_value = $result->default_value;
838
839 unset( $r_key, $result );
840 }
841 } elseif ( $results ) {
842 FrmDb::set_cache( $results->id, $results, 'frm_field' );
843 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
844
845 self::prepare_options( $results );
846 }
847 }
848
849 /**
850 * Unserialize all the serialized field data
851 *
852 * @since 2.0
853 */
854 private static function prepare_options( &$results ) {
855 FrmAppHelper::unserialize_or_decode( $results->field_options );
856 FrmAppHelper::unserialize_or_decode( $results->options );
857
858 // Allow a single box to be checked for the default value.
859 $before = $results->default_value;
860 FrmAppHelper::unserialize_or_decode( $results->default_value );
861 if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
862 $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
863 }
864 }
865
866 /**
867 * If a form has too many fields, thay won't all save into a single transient.
868 * We'll break them into groups of 200
869 *
870 * @since 2.0.1
871 */
872 private static function get_fields_from_transients( $form_id, $args ) {
873 $fields = array();
874 self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
875
876 return $fields;
877 }
878
879 /**
880 * Called by get_fields_from_transients
881 *
882 * @since 2.0.1
883 */
884 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
885 $name = $next ? $base_name . $next : $base_name;
886 $next_fields = get_transient( $name );
887
888 if ( $next_fields ) {
889 $fields = array_merge( $fields, $next_fields );
890
891 if ( count( $next_fields ) >= self::$transient_size ) {
892 // if this transient is full, check for another
893 $next ++;
894 self::get_next_transient( $fields, $base_name, $next );
895 }
896 }
897 }
898
899 /**
900 * Save the transients in chunks for large forms
901 *
902 * @since 2.0.1
903 */
904 private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
905 $base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
906 $field_chunks = array_chunk( $fields, self::$transient_size );
907
908 foreach ( $field_chunks as $field ) {
909 $name = $next ? $base_name . $next : $base_name;
910 $set = set_transient( $name, $field, 60 * 60 * 6 );
911 if ( ! $set ) {
912 // the transient didn't save
913 if ( $name != $base_name ) {
914 // if the first saved an others fail, this will show an incmoplete form
915 self::delete_form_transient( $form_id );
916 }
917
918 return;
919 }
920
921 $next ++;
922 }
923 }
924
925 public static function is_no_save_field( $type ) {
926 return in_array( $type, self::no_save_fields() );
927 }
928
929 public static function no_save_fields() {
930 return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary' );
931 }
932
933 /**
934 * Check if this field can hold an array of values
935 *
936 * @since 2.0.9
937 *
938 * @param array|object $field
939 *
940 * @return boolean
941 */
942 public static function is_field_with_multiple_values( $field ) {
943 if ( ! $field ) {
944 return false;
945 }
946
947 $field_type = self::get_original_field_type( $field );
948
949 $is_multi_value_field = (
950 self::is_checkbox( $field ) ||
951 $field_type == 'address' ||
952 self::is_multiple_select( $field )
953 );
954
955 return $is_multi_value_field;
956 }
957
958 /**
959 * @since 3.0
960 * @return string
961 */
962 public static function get_field_type( $field ) {
963 return is_array( $field ) ? $field['type'] : $field->type;
964 }
965
966 /**
967 * @since 3.0
968 * @return string
969 */
970 public static function get_original_field_type( $field ) {
971 $field_type = self::get_field_type( $field );
972 $original_type = self::get_option( $field, 'original_type' );
973
974 if ( ! empty( $original_type ) && $original_type != $field_type ) {
975 $field_type = $original_type; // check the original type for arrays
976 }
977
978 return $field_type;
979 }
980
981 /**
982 * Check if this is a multiselect dropdown field
983 *
984 * @since 2.0.9
985 * @return boolean
986 */
987 public static function is_multiple_select( $field ) {
988 $field_type = self::get_field_type( $field );
989 $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
990
991 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
992 }
993
994 /**
995 * Check if a field is read only. Read only can be set in the field options,
996 * but disabled with the shortcode options
997 *
998 * @since 2.0.9
999 */
1000 public static function is_read_only( $field ) {
1001 global $frm_vars;
1002
1003 return ( self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] != 'disabled' ) );
1004 }
1005
1006 /**
1007 * @since 2.0.9
1008 */
1009 public static function is_required( $field ) {
1010 $required = ( $field['required'] != '0' );
1011 $required = apply_filters( 'frm_is_field_required', $required, $field );
1012
1013 return $required;
1014 }
1015
1016 /**
1017 * @since 2.0.9
1018 */
1019 public static function is_option_true( $field, $option ) {
1020 if ( is_array( $field ) ) {
1021 return self::is_option_true_in_array( $field, $option );
1022 } else {
1023 return self::is_option_true_in_object( $field, $option );
1024 }
1025 }
1026
1027 /**
1028 * @since 2.0.9
1029 */
1030 public static function is_option_empty( $field, $option ) {
1031 if ( is_array( $field ) ) {
1032 return self::is_option_empty_in_array( $field, $option );
1033 } else {
1034 return self::is_option_empty_in_object( $field, $option );
1035 }
1036 }
1037
1038 public static function is_option_true_in_array( $field, $option ) {
1039 return isset( $field[ $option ] ) && $field[ $option ];
1040 }
1041
1042 public static function is_option_true_in_object( $field, $option ) {
1043 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
1044 }
1045
1046 public static function is_option_empty_in_array( $field, $option ) {
1047 return ! isset( $field[ $option ] ) || empty( $field[ $option ] );
1048 }
1049
1050 public static function is_option_empty_in_object( $field, $option ) {
1051 return ! isset( $field->field_options[ $option ] ) || empty( $field->field_options[ $option ] );
1052 }
1053
1054 public static function is_option_value_in_object( $field, $option ) {
1055 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
1056 }
1057
1058 /**
1059 * @since 2.0.18
1060 */
1061 public static function get_option( $field, $option ) {
1062 if ( is_array( $field ) ) {
1063 $option = self::get_option_in_array( $field, $option );
1064 } else {
1065 $option = self::get_option_in_object( $field, $option );
1066 }
1067
1068 return $option;
1069 }
1070
1071 public static function get_option_in_array( $field, $option ) {
1072
1073 if ( isset( $field[ $option ] ) ) {
1074 $this_option = $field[ $option ];
1075 } elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
1076 $this_option = $field['field_options'][ $option ];
1077 } else {
1078 $this_option = '';
1079 }
1080
1081 return $this_option;
1082 }
1083
1084 public static function get_option_in_object( $field, $option ) {
1085 return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
1086 }
1087
1088 /**
1089 * @since 2.0.09
1090 */
1091 public static function is_repeating_field( $field ) {
1092 if ( is_array( $field ) ) {
1093 $is_repeating_field = ( 'divider' == $field['type'] );
1094 } else {
1095 $is_repeating_field = ( 'divider' == $field->type );
1096 }
1097
1098 return ( $is_repeating_field && self::is_option_true( $field, 'repeat' ) );
1099 }
1100
1101 /**
1102 * @param string $key
1103 *
1104 * @return int field id
1105 */
1106 public static function get_id_by_key( $key ) {
1107 $id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
1108
1109 return (int) $id;
1110 }
1111
1112 /**
1113 * @param string $id
1114 *
1115 * @return null|string
1116 */
1117 public static function get_key_by_id( $id ) {
1118 return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
1119 }
1120
1121 public static function is_image( $field ) {
1122 $type = self::get_field_type( $field );
1123
1124 return ( $type == 'url' && self::get_option( $field, 'show_image' ) );
1125 }
1126
1127 /**
1128 * Check if field is radio or Dynamic radio
1129 *
1130 * @since 3.0
1131 *
1132 * @param array|object $field
1133 *
1134 * @return boolean true if field type is radio or Dynamic radio
1135 */
1136 public static function is_radio( $field ) {
1137 return self::is_field_type( $field, 'radio' );
1138 }
1139
1140 /**
1141 * Check if field is checkbox or Dynamic checkbox
1142 *
1143 * @since 3.0
1144 *
1145 * @param array|object $field
1146 *
1147 * @return boolean true if field type is checkbox or Dynamic checkbox
1148 */
1149 public static function is_checkbox( $field ) {
1150 return self::is_field_type( $field, 'checkbox' );
1151 }
1152
1153 /**
1154 * Check if field is checkbox or radio
1155 *
1156 * @since 3.0
1157 *
1158 * @param array|object $field
1159 * @param string $is_type Options include radio, checkbox, text
1160 *
1161 * @return boolean true if field type is checkbox or Dynamic checkbox
1162 */
1163 public static function is_field_type( $field, $is_type ) {
1164 $field_type = self::get_original_field_type( $field );
1165 $data_type = self::get_option( $field, 'data_type' );
1166
1167 $is_field_type = (
1168 $is_type === $field_type ||
1169 ( 'data' === $field_type && $is_type === $data_type ) ||
1170 ( 'lookup' === $field_type && $is_type === $data_type ) ||
1171 ( 'product' === $field_type && $is_type === $data_type )
1172 );
1173
1174 /**
1175 * When a field type is checked, allow individual fields
1176 * to set the type.
1177 *
1178 * @since 4.04
1179 */
1180 return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
1181 }
1182
1183 /**
1184 * Checks if the given field array is a combo field.
1185 *
1186 * @since 4.10.02
1187 *
1188 * @param array $field Field array.
1189 * @return bool
1190 */
1191 public static function is_combo_field( $field ) {
1192 $field_type_obj = FrmFieldFactory::get_field_factory( $field );
1193
1194 return ! empty( $field_type_obj->is_combo_field );
1195 }
1196 }
1197