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

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