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

1,198 lines 35.6 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 }//end foreach
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 if ( 'dropdown' === $values['type'] ) {
447 // To avoid conflicts with security plugins the value "dropdown" is sent for select fields.
448 // This is because "select" gets matched for SQL injection attempts.
449 $values['type'] = 'select';
450 }
451
452 $values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values );
453
454 if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
455 // don't keep the old placeholder setting for hidden fields
456 $values['field_options']['clear_on_focus'] = 0;
457 }
458 }
459
460 // serialize array values
461 foreach ( array( 'field_options', 'options' ) as $opt ) {
462 if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
463 if ( 'field_options' === $opt ) {
464 $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
465 }
466 $values[ $opt ] = serialize( $values[ $opt ] );
467 }
468 }
469 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
470 $values['default_value'] = json_encode( $values['default_value'] );
471 }
472
473 $query_results = $wpdb->update( $wpdb->prefix . 'frm_fields', $values, array( 'id' => $id ) );
474
475 $form_id = 0;
476 if ( isset( $values['form_id'] ) ) {
477 $form_id = absint( $values['form_id'] );
478 } else {
479 $field = self::getOne( $id );
480 if ( $field ) {
481 $form_id = $field->form_id;
482 }
483 unset( $field );
484 }
485 unset( $values );
486
487 if ( $query_results ) {
488 wp_cache_delete( $id, 'frm_field' );
489 if ( $form_id ) {
490 self::delete_form_transient( $form_id );
491 }
492 }
493
494 return $query_results;
495 }
496
497 /**
498 * Keep backslashes in the phone format option
499 *
500 * @since 2.0.8
501 *
502 * @param array $values Pass by reference.
503 */
504 private static function preserve_format_option_backslashes( &$values ) {
505 if ( isset( $values['field_options']['format'] ) ) {
506 $values['field_options']['format'] = FrmAppHelper::preserve_backslashes( $values['field_options']['format'] );
507 }
508 }
509
510 public static function destroy( $id ) {
511 global $wpdb;
512
513 do_action( 'frm_before_destroy_field', $id );
514
515 wp_cache_delete( $id, 'frm_field' );
516 $field = self::getOne( $id );
517 if ( ! $field ) {
518 return false;
519 }
520
521 self::delete_form_transient( $field->form_id );
522
523 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id=%d', $id ) );
524
525 return $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $id ) );
526 }
527
528 public static function delete_form_transient( $form_id ) {
529 $form_id = absint( $form_id );
530 delete_transient( 'frm_form_fields_' . $form_id . 'excludeinclude' );
531 delete_transient( 'frm_form_fields_' . $form_id . 'includeinclude' );
532 delete_transient( 'frm_form_fields_' . $form_id . 'includeexclude' );
533 delete_transient( 'frm_form_fields_' . $form_id . 'excludeexclude' );
534
535 global $wpdb;
536 $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%' ) );
537
538 FrmDb::cache_delete_group( 'frm_field' );
539
540 $form = FrmForm::getOne( $form_id );
541 if ( $form && $form->parent_form_id && $form->parent_form_id != $form_id ) {
542 self::delete_form_transient( $form->parent_form_id );
543 }
544 }
545
546 /**
547 * If $field is numeric, get the field object
548 */
549 public static function maybe_get_field( &$field ) {
550 if ( ! is_object( $field ) ) {
551 $field = self::getOne( $field );
552 }
553 }
554
555 /**
556 * @param string|int $id The field id or key.
557 * @param bool $filter When true, run the frm_field filter.
558 */
559 public static function getOne( $id, $filter = false ) {
560 if ( empty( $id ) ) {
561 return null;
562 }
563
564 global $wpdb;
565
566 $where = is_numeric( $id ) ? 'id=%d' : 'field_key=%s';
567 $query = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'frm_fields WHERE ' . $where, $id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
568
569 $results = FrmDb::check_cache( $id, 'frm_field', $query, 'get_row', 0 );
570
571 if ( empty( $results ) ) {
572 self::filter_field( $filter, $results );
573 return $results;
574 }
575
576 if ( is_numeric( $id ) ) {
577 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
578 } elseif ( $results ) {
579 FrmDb::set_cache( $results->id, $results, 'frm_field' );
580 }
581
582 self::prepare_options( $results );
583 self::filter_field( $filter, $results );
584
585 return wp_unslash( $results );
586 }
587
588 /**
589 * @since 3.06.01
590 * @param bool $filter When true, run the frm_field filter.
591 * @param object $results
592 */
593 private static function filter_field( $filter, &$results ) {
594 if ( $filter ) {
595 /**
596 * @since 3.06.01
597 */
598 $results = apply_filters( 'frm_field', $results );
599 }
600 }
601
602 /**
603 * Get the field type by key or id
604 *
605 * @param int|string $id The field id or key.
606 * @param mixed $col The name of the column in the fields database table.
607 */
608 public static function get_type( $id, $col = 'type' ) {
609 $field = FrmDb::check_cache( $id, 'frm_field' );
610 if ( $field ) {
611 $type = $field->{$col};
612 } else {
613 $where = array(
614 'or' => 1,
615 'id' => $id,
616 'field_key' => $id,
617 );
618 $type = FrmDb::get_var( 'frm_fields', $where, $col );
619 }
620
621 return $type;
622 }
623
624 public static function get_all_types_in_form( $form_id, $type, $limit = '', $inc_sub = 'exclude' ) {
625 if ( ! $form_id ) {
626 return array();
627 }
628
629 $results = self::get_fields_from_transients(
630 $form_id,
631 array(
632 'inc_embed' => $inc_sub,
633 'inc_repeat' => $inc_sub,
634 )
635 );
636 if ( ! empty( $results ) ) {
637 $fields = array();
638 $count = 0;
639 foreach ( $results as $result ) {
640 if ( $type != $result->type ) {
641 continue;
642 }
643
644 $fields[ $result->id ] = $result;
645 $count ++;
646 if ( $limit == 1 ) {
647 $fields = $result;
648 break;
649 }
650
651 if ( ! empty( $limit ) && $count >= $limit ) {
652 break;
653 }
654
655 unset( $result );
656 }
657
658 return wp_unslash( $fields );
659 }//end if
660
661 self::$use_cache = false;
662
663 $where = array(
664 'fi.form_id' => (int) $form_id,
665 'fi.type' => $type,
666 );
667 self::maybe_include_repeating_fields( $inc_sub, $where );
668 $results = self::getAll( $where, 'field_order', $limit );
669 self::$use_cache = true;
670 self::include_sub_fields( $results, $inc_sub, $type, $form_id );
671
672 return $results;
673 }
674
675 public static function get_all_for_form( $form_id, $limit = '', $inc_embed = 'exclude', $inc_repeat = 'include' ) {
676 if ( ! (int) $form_id ) {
677 return array();
678 }
679
680 $results = self::get_fields_from_transients( $form_id, compact( 'inc_embed', 'inc_repeat' ) );
681 if ( ! empty( $results ) ) {
682 if ( empty( $limit ) ) {
683 return $results;
684 }
685
686 $fields = array();
687 $count = 0;
688 foreach ( $results as $result ) {
689 $count ++;
690 $fields[ $result->id ] = $result;
691 if ( ! empty( $limit ) && $count >= $limit ) {
692 break;
693 }
694 }
695
696 return $fields;
697 }
698
699 self::$use_cache = false;
700
701 $where = array( 'fi.form_id' => absint( $form_id ) );
702 self::maybe_include_repeating_fields( $inc_repeat, $where );
703 $results = self::getAll( $where, 'field_order', $limit );
704
705 self::$use_cache = true;
706
707 self::include_sub_fields( $results, $inc_embed, 'all', $form_id );
708
709 if ( empty( $limit ) ) {
710 self::set_field_transient( $results, $form_id, 0, compact( 'inc_embed', 'inc_repeat' ) );
711 }
712
713 return $results;
714 }
715
716 /**
717 * If repeating fields should be included, adjust $where accordingly
718 *
719 * @param string $inc_repeat
720 * @param array $where Pass by reference.
721 */
722 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
723 if ( $inc_repeat == 'include' ) {
724 $form_id = $where['fi.form_id'];
725 $where[] = array(
726 'or' => 1,
727 'fi.form_id' => $form_id,
728 'fr.parent_form_id' => $form_id,
729 );
730 unset( $where['fi.form_id'] );
731 }
732 }
733
734 public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
735 $no_sub_forms = empty( $results ) && $type === 'all';
736 if ( 'include' != $inc_embed || $no_sub_forms ) {
737 return;
738 }
739
740 $form_fields = $results;
741 $should_get_subforms = ( $type !== 'all' && $type !== 'form' && ! empty( $form_id ) );
742 if ( $should_get_subforms ) {
743 $form_fields = self::get_all_types_in_form( $form_id, 'form' );
744 }
745
746 $index_offset = 1;
747 foreach ( $form_fields as $k => $field ) {
748 if ( 'form' != $field->type || ! isset( $field->field_options['form_select'] ) ) {
749 continue;
750 }
751
752 if ( $type == 'all' ) {
753 $sub_fields = self::get_all_for_form( $field->field_options['form_select'] );
754 } else {
755 $sub_fields = self::get_all_types_in_form( $field->field_options['form_select'], $type );
756 }
757
758 if ( ! empty( $sub_fields ) ) {
759 $index = $k + $index_offset;
760 $index_offset += count( $sub_fields );
761 array_splice( $results, $index, 0, $sub_fields );
762 }
763 unset( $field, $sub_fields );
764 }
765 }
766
767 public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
768 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
769 if ( self::$use_cache ) {
770 // make sure old cache doesn't get saved as a transient
771 $results = wp_cache_get( $cache_key, 'frm_field' );
772 if ( false !== $results ) {
773 return wp_unslash( $results );
774 }
775 }
776
777 global $wpdb;
778
779 if ( $blog_id && is_multisite() ) {
780 global $wpmuBaseTablePrefix;
781 if ( $wpmuBaseTablePrefix ) {
782 $prefix = $wpmuBaseTablePrefix . $blog_id . '_';
783 } else {
784 $prefix = $wpdb->get_blog_prefix( $blog_id );
785 }
786
787 $table_name = $prefix . 'frm_fields';
788 $form_table_name = $prefix . 'frm_forms';
789 } else {
790 $table_name = $wpdb->prefix . 'frm_fields';
791 $form_table_name = $wpdb->prefix . 'frm_forms';
792 }
793
794 if ( ! empty( $order_by ) && strpos( $order_by, 'ORDER BY' ) === false ) {
795 $order_by = ' ORDER BY ' . $order_by;
796 }
797
798 $limit = FrmDb::esc_limit( $limit );
799
800 $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";
801 $query_type = ( $limit == ' LIMIT 1' || $limit == 1 ) ? 'row' : 'results';
802
803 if ( is_array( $where ) ) {
804 $args = array(
805 'order_by' => $order_by,
806 'limit' => $limit,
807 );
808 $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 );
809 } else {
810 // if the query is not an array, then it has already been prepared
811 $query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
812
813 $function_name = ( $query_type == 'row' ) ? 'get_row' : 'get_results';
814 $results = $wpdb->$function_name( $query );
815 }
816 unset( $where );
817
818 self::format_field_results( $results );
819
820 FrmDb::set_cache( $cache_key, $results, 'frm_field' );
821
822 return wp_unslash( $results );
823 }
824
825 /**
826 * @since 2.0.8
827 */
828 private static function format_field_results( &$results ) {
829 if ( is_array( $results ) ) {
830 foreach ( $results as $r_key => $result ) {
831 FrmDb::set_cache( $result->id, $result, 'frm_field' );
832 FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
833
834 self::prepare_options( $result );
835 $results[ $r_key ]->field_options = $result->field_options;
836 $results[ $r_key ]->options = $result->options;
837 $results[ $r_key ]->default_value = $result->default_value;
838
839 unset( $r_key, $result );
840 }
841 } elseif ( $results ) {
842 FrmDb::set_cache( $results->id, $results, 'frm_field' );
843 FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
844
845 self::prepare_options( $results );
846 }
847 }
848
849 /**
850 * Unserialize all the serialized field data
851 *
852 * @since 2.0
853 */
854 private static function prepare_options( &$results ) {
855 FrmAppHelper::unserialize_or_decode( $results->field_options );
856 FrmAppHelper::unserialize_or_decode( $results->options );
857
858 // Allow a single box to be checked for the default value.
859 $before = $results->default_value;
860 FrmAppHelper::unserialize_or_decode( $results->default_value );
861 if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
862 $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
863 }
864 }
865
866 /**
867 * If a form has too many fields, thay won't all save into a single transient.
868 * We'll break them into groups of 200
869 *
870 * @since 2.0.1
871 */
872 private static function get_fields_from_transients( $form_id, $args ) {
873 $fields = array();
874 self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
875
876 return $fields;
877 }
878
879 /**
880 * Called by get_fields_from_transients
881 *
882 * @since 2.0.1
883 */
884 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
885 $name = $next ? $base_name . $next : $base_name;
886 $next_fields = get_transient( $name );
887
888 if ( $next_fields ) {
889 $fields = array_merge( $fields, $next_fields );
890
891 if ( count( $next_fields ) >= self::$transient_size ) {
892 // if this transient is full, check for another
893 $next ++;
894 self::get_next_transient( $fields, $base_name, $next );
895 }
896 }
897 }
898
899 /**
900 * Save the transients in chunks for large forms
901 *
902 * @since 2.0.1
903 */
904 private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
905 $base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
906 $field_chunks = array_chunk( $fields, self::$transient_size );
907
908 foreach ( $field_chunks as $field ) {
909 $name = $next ? $base_name . $next : $base_name;
910 $set = set_transient( $name, $field, 60 * 60 * 6 );
911 if ( ! $set ) {
912 // the transient didn't save
913 if ( $name != $base_name ) {
914 // if the first saved an others fail, this will show an incmoplete form
915 self::delete_form_transient( $form_id );
916 }
917
918 return;
919 }
920
921 $next ++;
922 }
923 }
924
925 public static function is_no_save_field( $type ) {
926 return in_array( $type, self::no_save_fields() );
927 }
928
929 public static function no_save_fields() {
930 return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary' );
931 }
932
933 /**
934 * Check if this field can hold an array of values
935 *
936 * @since 2.0.9
937 *
938 * @param array|object $field
939 *
940 * @return boolean
941 */
942 public static function is_field_with_multiple_values( $field ) {
943 if ( ! $field ) {
944 return false;
945 }
946
947 $field_type = self::get_original_field_type( $field );
948
949 $is_multi_value_field = (
950 self::is_checkbox( $field ) ||
951 $field_type == 'address' ||
952 self::is_multiple_select( $field )
953 );
954
955 return $is_multi_value_field;
956 }
957
958 /**
959 * @since 3.0
960 * @return string
961 */
962 public static function get_field_type( $field ) {
963 return is_array( $field ) ? $field['type'] : $field->type;
964 }
965
966 /**
967 * @since 3.0
968 * @return string
969 */
970 public static function get_original_field_type( $field ) {
971 $field_type = self::get_field_type( $field );
972 $original_type = self::get_option( $field, 'original_type' );
973
974 if ( ! empty( $original_type ) && $original_type != $field_type ) {
975 // Check the original type for arrays.
976 $field_type = $original_type;
977 }
978
979 return $field_type;
980 }
981
982 /**
983 * Check if this is a multiselect dropdown field
984 *
985 * @since 2.0.9
986 * @return boolean
987 */
988 public static function is_multiple_select( $field ) {
989 $field_type = self::get_field_type( $field );
990 $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
991
992 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
993 }
994
995 /**
996 * Check if a field is read only. Read only can be set in the field options,
997 * but disabled with the shortcode options
998 *
999 * @since 2.0.9
1000 */
1001 public static function is_read_only( $field ) {
1002 global $frm_vars;
1003
1004 return ( self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] != 'disabled' ) );
1005 }
1006
1007 /**
1008 * @since 2.0.9
1009 */
1010 public static function is_required( $field ) {
1011 $required = ( $field['required'] != '0' );
1012 $required = apply_filters( 'frm_is_field_required', $required, $field );
1013
1014 return $required;
1015 }
1016
1017 /**
1018 * @since 2.0.9
1019 */
1020 public static function is_option_true( $field, $option ) {
1021 if ( is_array( $field ) ) {
1022 return self::is_option_true_in_array( $field, $option );
1023 } else {
1024 return self::is_option_true_in_object( $field, $option );
1025 }
1026 }
1027
1028 /**
1029 * @since 2.0.9
1030 */
1031 public static function is_option_empty( $field, $option ) {
1032 if ( is_array( $field ) ) {
1033 return self::is_option_empty_in_array( $field, $option );
1034 } else {
1035 return self::is_option_empty_in_object( $field, $option );
1036 }
1037 }
1038
1039 public static function is_option_true_in_array( $field, $option ) {
1040 return isset( $field[ $option ] ) && $field[ $option ];
1041 }
1042
1043 public static function is_option_true_in_object( $field, $option ) {
1044 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
1045 }
1046
1047 public static function is_option_empty_in_array( $field, $option ) {
1048 return ! isset( $field[ $option ] ) || empty( $field[ $option ] );
1049 }
1050
1051 public static function is_option_empty_in_object( $field, $option ) {
1052 return ! isset( $field->field_options[ $option ] ) || empty( $field->field_options[ $option ] );
1053 }
1054
1055 public static function is_option_value_in_object( $field, $option ) {
1056 return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
1057 }
1058
1059 /**
1060 * @since 2.0.18
1061 */
1062 public static function get_option( $field, $option ) {
1063 if ( is_array( $field ) ) {
1064 $option = self::get_option_in_array( $field, $option );
1065 } else {
1066 $option = self::get_option_in_object( $field, $option );
1067 }
1068
1069 return $option;
1070 }
1071
1072 public static function get_option_in_array( $field, $option ) {
1073
1074 if ( isset( $field[ $option ] ) ) {
1075 $this_option = $field[ $option ];
1076 } elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
1077 $this_option = $field['field_options'][ $option ];
1078 } else {
1079 $this_option = '';
1080 }
1081
1082 return $this_option;
1083 }
1084
1085 public static function get_option_in_object( $field, $option ) {
1086 return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
1087 }
1088
1089 /**
1090 * @since 2.0.09
1091 */
1092 public static function is_repeating_field( $field ) {
1093 if ( is_array( $field ) ) {
1094 $is_repeating_field = ( 'divider' == $field['type'] );
1095 } else {
1096 $is_repeating_field = ( 'divider' == $field->type );
1097 }
1098
1099 return ( $is_repeating_field && self::is_option_true( $field, 'repeat' ) );
1100 }
1101
1102 /**
1103 * @param string $key
1104 *
1105 * @return int field id
1106 */
1107 public static function get_id_by_key( $key ) {
1108 $id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
1109
1110 return (int) $id;
1111 }
1112
1113 /**
1114 * @param string $id
1115 *
1116 * @return null|string
1117 */
1118 public static function get_key_by_id( $id ) {
1119 return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
1120 }
1121
1122 public static function is_image( $field ) {
1123 $type = self::get_field_type( $field );
1124
1125 return ( $type == 'url' && self::get_option( $field, 'show_image' ) );
1126 }
1127
1128 /**
1129 * Check if field is radio or Dynamic radio
1130 *
1131 * @since 3.0
1132 *
1133 * @param array|object $field
1134 *
1135 * @return boolean true if field type is radio or Dynamic radio
1136 */
1137 public static function is_radio( $field ) {
1138 return self::is_field_type( $field, 'radio' );
1139 }
1140
1141 /**
1142 * Check if field is checkbox or Dynamic checkbox
1143 *
1144 * @since 3.0
1145 *
1146 * @param array|object $field
1147 *
1148 * @return boolean true if field type is checkbox or Dynamic checkbox
1149 */
1150 public static function is_checkbox( $field ) {
1151 return self::is_field_type( $field, 'checkbox' );
1152 }
1153
1154 /**
1155 * Check if field is checkbox or radio
1156 *
1157 * @since 3.0
1158 *
1159 * @param array|object $field
1160 * @param string $is_type Options include radio, checkbox, text.
1161 *
1162 * @return boolean true if field type is checkbox or Dynamic checkbox
1163 */
1164 public static function is_field_type( $field, $is_type ) {
1165 $field_type = self::get_original_field_type( $field );
1166 $data_type = self::get_option( $field, 'data_type' );
1167
1168 $is_field_type = (
1169 $is_type === $field_type ||
1170 ( 'data' === $field_type && $is_type === $data_type ) ||
1171 ( 'lookup' === $field_type && $is_type === $data_type ) ||
1172 ( 'product' === $field_type && $is_type === $data_type )
1173 );
1174
1175 /**
1176 * When a field type is checked, allow individual fields
1177 * to set the type.
1178 *
1179 * @since 4.04
1180 */
1181 return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
1182 }
1183
1184 /**
1185 * Checks if the given field array is a combo field.
1186 *
1187 * @since 4.10.02
1188 *
1189 * @param array $field Field array.
1190 * @return bool
1191 */
1192 public static function is_combo_field( $field ) {
1193 $field_type_obj = FrmFieldFactory::get_field_factory( $field );
1194
1195 return ! empty( $field_type_obj->is_combo_field );
1196 }
1197 }
1198