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

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