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

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