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

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