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

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