PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.25
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.25
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 / FrmForm.php

FrmForm.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.25, at classes/models/FrmForm.php

1,282 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 FrmForm {
7
8 /**
9 * @param array $values
10 * @return bool|int id on success or false on failure.
11 */
12 public static function create( $values ) {
13 global $wpdb;
14
15 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
16
17 $new_values = array(
18 'form_key' => FrmAppHelper::get_unique_key( $values['form_key'], $wpdb->prefix . 'frm_forms', 'form_key' ),
19 'name' => $values['name'],
20 'description' => $values['description'],
21 'status' => $values['status'] ?? 'published',
22 'logged_in' => $values['logged_in'] ?? 0,
23 'is_template' => isset( $values['is_template'] ) ? (int) $values['is_template'] : 0,
24 'parent_form_id' => isset( $values['parent_form_id'] ) ? absint( $values['parent_form_id'] ) : 0,
25 'editable' => isset( $values['editable'] ) ? (int) $values['editable'] : 0,
26 'created_at' => $values['created_at'] ?? current_time( 'mysql', 1 ),
27 );
28
29 $options = isset( $values['options'] ) ? (array) $values['options'] : array();
30 FrmFormsHelper::fill_form_options( $options, $values );
31
32 $options['before_html'] = $values['options']['before_html'] ?? FrmFormsHelper::get_default_html( 'before' );
33 $options['after_html'] = $values['options']['after_html'] ?? FrmFormsHelper::get_default_html( 'after' );
34 $options['submit_html'] = $values['options']['submit_html'] ?? FrmFormsHelper::get_default_html( 'submit' );
35
36 /**
37 * Allows modifying form options before updating or creating.
38 *
39 * @since 5.4 Add the third param.
40 *
41 * @param array $options Form options.
42 * @param array $values Form data.
43 * @param bool $update Is form updating or creating. It's `true` if is updating.
44 */
45 $options = apply_filters( 'frm_form_options_before_update', $options, $values, false );
46 $options = self::maybe_filter_form_options( $options );
47 $new_values['options'] = serialize( $options );
48
49 $wpdb->insert( $wpdb->prefix . 'frm_forms', $new_values );
50
51 $id = $wpdb->insert_id;
52
53 // Clear form caching
54 self::clear_form_cache();
55
56 return $id;
57 }
58
59 /**
60 * @since 5.0.08
61 *
62 * @param array $options
63 * @return array
64 */
65 private static function maybe_filter_form_options( $options ) {
66 if ( ! FrmAppHelper::allow_unfiltered_html() && ! empty( $options['submit_html'] ) ) {
67 $options['submit_html'] = FrmAppHelper::kses_submit_button( $options['submit_html'] );
68 }
69 return FrmAppHelper::maybe_filter_array( $options, array( 'submit_value', 'success_msg', 'before_html', 'after_html' ) );
70 }
71
72 /**
73 * @return bool|int ID on success or false on failure
74 */
75 public static function duplicate( $id, $template = false, $copy_keys = false, $blog_id = false ) {
76 global $wpdb;
77
78 $values = self::getOne( $id, $blog_id );
79 if ( ! $values ) {
80 return false;
81 }
82
83 $new_key = $copy_keys ? $values->form_key : '';
84
85 $new_values = array(
86 'form_key' => FrmAppHelper::get_unique_key( $new_key, $wpdb->prefix . 'frm_forms', 'form_key' ),
87 'name' => $values->name,
88 'description' => $values->description,
89 'status' => $values->status ? $values->status : 'published',
90 'logged_in' => $values->logged_in ? $values->logged_in : 0,
91 'editable' => $values->editable ? $values->editable : 0,
92 'created_at' => current_time( 'mysql', 1 ),
93 'is_template' => $template ? 1 : 0,
94 );
95
96 if ( $blog_id ) {
97 $new_values['status'] = 'published';
98 $new_options = $values->options;
99 FrmAppHelper::unserialize_or_decode( $new_options );
100 $new_options['email_to'] = get_option( 'admin_email' );
101 $new_options['copy'] = false;
102 $new_values['options'] = $new_options;
103 } else {
104 $new_values['options'] = $values->options;
105 }
106
107 if ( is_array( $new_values['options'] ) ) {
108 $new_values['options'] = serialize( $new_values['options'] );
109 }
110
111 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_forms', $new_values );
112
113 if ( $query_results ) {
114 // Clear form caching
115 self::clear_form_cache();
116
117 $form_id = $wpdb->insert_id;
118 FrmField::duplicate( $id, $form_id, $copy_keys, $blog_id );
119
120 // update form settings after fields are created
121 do_action( 'frm_after_duplicate_form', $form_id, $new_values, array( 'old_id' => $id ) );
122
123 return $form_id;
124 }
125
126 return false;
127 }
128
129 public static function after_duplicate( $form_id, $values ) {
130 $new_opts = $values['options'];
131 FrmAppHelper::unserialize_or_decode( $new_opts );
132 $values['options'] = $new_opts;
133
134 if ( isset( $new_opts['success_msg'] ) ) {
135 $new_opts['success_msg'] = FrmFieldsHelper::switch_field_ids( $new_opts['success_msg'] );
136 }
137
138 $new_opts = apply_filters( 'frm_after_duplicate_form_values', $new_opts, $form_id );
139
140 if ( $new_opts != $values['options'] ) {
141 global $wpdb;
142 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'options' => maybe_serialize( $new_opts ) ), array( 'id' => $form_id ) );
143 }
144
145 self::switch_field_ids_in_fields( $form_id );
146 }
147
148 /**
149 * Switches field ID in fields.
150 *
151 * @since 5.3
152 *
153 * @param int $form_id Form ID.
154 */
155 private static function switch_field_ids_in_fields( $form_id ) {
156 global $wpdb;
157
158 // Keys of fields that you want to check to replace field ID.
159 $keys = array( 'default_value', 'field_options' );
160 $sql_cols = 'fi.id';
161 foreach ( $keys as $key ) {
162 $sql_cols .= ',fi.' . $key;
163 }
164
165 $fields = FrmDb::get_results(
166 "{$wpdb->prefix}frm_fields AS fi LEFT OUTER JOIN {$wpdb->prefix}frm_forms AS fr ON fi.form_id = fr.id",
167 array(
168 'or' => 1,
169 'fi.form_id' => $form_id,
170 'fr.parent_form_id' => $form_id,
171 ),
172 $sql_cols
173 );
174
175 if ( ! $fields || ! is_array( $fields ) ) {
176 return;
177 }
178
179 foreach ( $fields as $field ) {
180 self::switch_field_ids_in_field( (array) $field );
181 }
182 }
183
184 /**
185 * Switches field ID in a field.
186 *
187 * @since 5.3
188 *
189 * @param array $field Field array.
190 */
191 private static function switch_field_ids_in_field( $field ) {
192 $new_values = array();
193 foreach ( $field as $key => $value ) {
194 if ( 'id' === $key || ! $value ) {
195 continue;
196 }
197
198 if ( ! is_string( $value ) && ! is_array( $value ) ) {
199 continue;
200 }
201
202 if ( 'field_options' === $key ) {
203 // Need to loop through field_options to prevent breaking serialized string when length changed.
204 FrmAppHelper::unserialize_or_decode( $value );
205 $new_val = FrmFieldsHelper::switch_field_ids( $value );
206 $new_val = serialize( $new_val );
207 } else {
208 $new_val = FrmFieldsHelper::switch_field_ids( $value );
209 }
210
211 if ( $new_val !== $value ) {
212 $new_values[ $key ] = $new_val;
213 }
214 }//end foreach
215
216 if ( ! empty( $new_values ) ) {
217 FrmField::update( $field['id'], $new_values );
218 }
219 }
220
221 /**
222 * @return bool|int
223 */
224 public static function update( $id, $values, $create_link = false ) {
225 global $wpdb;
226
227 $values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
228
229 if ( ! isset( $values['status'] ) && ( $create_link || isset( $values['options'] ) || isset( $values['item_meta'] ) || isset( $values['field_options'] ) ) ) {
230 $values['status'] = 'published';
231 }
232
233 if ( isset( $values['form_key'] ) ) {
234 $values['form_key'] = FrmAppHelper::get_unique_key( $values['form_key'], $wpdb->prefix . 'frm_forms', 'form_key', $id );
235 }
236
237 $form_fields = array( 'form_key', 'name', 'description', 'status', 'parent_form_id' );
238
239 $new_values = self::set_update_options( array(), $values, array( 'form_id' => $id ) );
240
241 foreach ( $values as $value_key => $value ) {
242 if ( $value_key && in_array( $value_key, $form_fields ) ) {
243 $new_values[ $value_key ] = $value;
244 }
245 }
246
247 if ( ! empty( $values['new_status'] ) ) {
248 $new_values['status'] = $values['new_status'];
249 }
250
251 if ( ! empty( $new_values ) ) {
252 $query_results = $wpdb->update( $wpdb->prefix . 'frm_forms', $new_values, array( 'id' => $id ) );
253 if ( $query_results ) {
254 self::clear_form_cache();
255 }
256 } else {
257 $query_results = true;
258 }
259 unset( $new_values );
260
261 $values = self::update_fields( $id, $values );
262
263 do_action( 'frm_update_form', $id, $values );
264 do_action( 'frm_update_form_' . $id, $values );
265
266 return $query_results;
267 }
268
269 /**
270 * @param array $new_values
271 * @param array $values
272 * @param array $args
273 * @return array
274 */
275 public static function set_update_options( $new_values, $values, $args = array() ) {
276 if ( ! isset( $values['options'] ) ) {
277 return $new_values;
278 }
279
280 $options = ! empty( $values['options'] ) ? (array) $values['options'] : array();
281 FrmFormsHelper::fill_form_options( $options, $values );
282
283 $options['custom_style'] = $values['options']['custom_style'] ?? 0;
284 $options['before_html'] = $values['options']['before_html'] ?? FrmFormsHelper::get_default_html( 'before' );
285 $options['after_html'] = $values['options']['after_html'] ?? FrmFormsHelper::get_default_html( 'after' );
286 $options['submit_html'] = isset( $values['options']['submit_html'] ) && '' !== $values['options']['submit_html'] ? $values['options']['submit_html'] : FrmFormsHelper::get_default_html( 'submit' );
287
288 /**
289 * Allows modifying form options before updating or creating.
290 *
291 * @since 5.4 Added the third param.
292 *
293 * @param array $options Form options.
294 * @param array $values Form data.
295 * @param bool $update Is form updating or creating. It's `true` if is updating.
296 */
297 $options = apply_filters( 'frm_form_options_before_update', $options, $values, true );
298 $options = self::maybe_filter_form_options( $options );
299 $new_values['options'] = serialize( $options );
300
301 return $new_values;
302 }
303
304 /**
305 * @return array
306 */
307 public static function update_fields( $id, $values ) {
308
309 if ( ! isset( $values['item_meta'] ) && ! isset( $values['field_options'] ) ) {
310 return $values;
311 }
312
313 $all_fields = FrmField::get_all_for_form( $id );
314 if ( empty( $all_fields ) ) {
315 return $values;
316 }
317
318 if ( ! isset( $values['item_meta'] ) ) {
319 $values['item_meta'] = array();
320 }
321
322 $field_array = array();
323 $existing_keys = array_keys( $values['item_meta'] );
324 foreach ( $all_fields as $fid ) {
325 if ( ! in_array( $fid->id, $existing_keys ) && ( isset( $values['frm_fields_submitted'] ) && in_array( $fid->id, $values['frm_fields_submitted'] ) ) || isset( $values['options'] ) ) {
326 $values['item_meta'][ $fid->id ] = '';
327 }
328 $field_array[ $fid->id ] = $fid;
329 }
330 unset( $all_fields );
331
332 foreach ( $values['item_meta'] as $field_id => $default_value ) {
333 if ( isset( $field_array[ $field_id ] ) ) {
334 $field = $field_array[ $field_id ];
335 } else {
336 $field = FrmField::getOne( $field_id );
337 }
338
339 if ( ! $field ) {
340 continue;
341 }
342
343 $is_settings_page = ( isset( $values['options'] ) || isset( $values['field_options'][ 'custom_html_' . $field_id ] ) );
344 if ( $is_settings_page ) {
345 self::get_settings_page_html( $values, $field );
346
347 if ( ! defined( 'WP_IMPORTING' ) ) {
348 continue;
349 }
350 }
351
352 // Updating the form.
353 $update_options = FrmFieldsHelper::get_default_field_options_from_field( $field );
354 // Don't check for POST html.
355 unset( $update_options['custom_html'] );
356 $update_options = apply_filters( 'frm_field_options_to_update', $update_options );
357
358 if ( ! is_array( $field->field_options ) ) {
359 $field->field_options = array();
360 }
361
362 foreach ( $update_options as $opt => $default ) {
363 $field->field_options[ $opt ] = $values['field_options'][ $opt . '_' . $field_id ] ?? $default;
364 self::sanitize_field_opt( $opt, $field->field_options[ $opt ] );
365 }
366
367 $field->field_options = apply_filters( 'frm_update_field_options', $field->field_options, $field, $values );
368
369 $new_field = array(
370 'field_options' => $field->field_options,
371 'default_value' => isset( $values[ 'default_value_' . $field_id ] ) ? FrmAppHelper::maybe_json_encode( $values[ 'default_value_' . $field_id ] ) : '',
372 );
373
374 if ( ! FrmAppHelper::allow_unfiltered_html() && isset( $values['field_options'][ 'options_' . $field_id ] ) && is_array( $values['field_options'][ 'options_' . $field_id ] ) ) {
375 foreach ( $values['field_options'][ 'options_' . $field_id ] as $option_key => $option ) {
376 if ( is_array( $option ) ) {
377 foreach ( $option as $key => $item ) {
378 $values['field_options'][ 'options_' . $field_id ][ $option_key ][ $key ] = FrmAppHelper::kses( $item, 'all' );
379 }
380 }
381 }
382 }
383
384 self::prepare_field_update_values( $field, $values, $new_field );
385 self::maybe_update_max_option( $field, $values, $new_field );
386
387 FrmField::update( $field_id, $new_field );
388
389 FrmField::delete_form_transient( $field->form_id );
390 }//end foreach
391 self::clear_form_cache();
392
393 return $values;
394 }
395
396 /**
397 * Resets the 'max' option of a field when changing paragraph field type to other field types like text, email etc.
398 *
399 * @since 6.7
400 *
401 * @param array $field
402 * @param array $values
403 * @param array $new_field
404 * @return void
405 */
406 private static function maybe_update_max_option( $field, $values, &$new_field ) {
407 if ( $field->type === 'textarea' &&
408 ! empty( $values['field_options'][ 'type_' . $field->id ] ) &&
409 in_array( $values['field_options'][ 'type_' . $field->id ], array( 'text', 'email', 'url', 'password', 'phone' ), true ) ) {
410
411 $new_field['field_options']['max'] = '';
412
413 /**
414 * Update posted field setting so that new 'max' option is displayed after form is saved and page reloads.
415 * FrmFieldsHelper::fill_default_field_opts populates field options by calling self::get_posted_field_setting.
416 */
417 $_POST['field_options'][ 'max_' . $field->id ] = '';
418 }
419 }
420
421 /**
422 * @param string $opt
423 * @param mixed $value
424 * @return void
425 */
426 private static function sanitize_field_opt( $opt, &$value ) {
427 if ( ! is_string( $value ) ) {
428 return;
429 }
430
431 /**
432 * Allow the option to turn off sanitization for a field. This way a custom rule can be used instead.
433 * Make sure to add custom sanitization using the frm_update_field_options filter as the data will no longer be sanitized.
434 *
435 * @since 6.0
436 *
437 * @param bool $should_sanitize
438 * @param string $opt
439 */
440 $should_sanitize = apply_filters( 'frm_should_sanitize_field_opt_string', true, $opt );
441
442 if ( ! $should_sanitize ) {
443 return;
444 }
445
446 if ( $opt === 'calc' ) {
447 $value = self::sanitize_calc( $value );
448 } else {
449 $value = FrmAppHelper::kses( $value, 'all' );
450 }
451
452 $value = trim( $value );
453 }
454
455 /**
456 * @param string $value
457 * @return string
458 */
459 private static function sanitize_calc( $value ) {
460 if ( false !== strpos( $value, '<' ) ) {
461 $value = self::normalize_calc_spaces( $value );
462 }
463 // Allow <= and >=.
464 $allow = array( '<= ', ' >=' );
465 $temp = array( '< = ', ' > =' );
466 $value = str_replace( $allow, $temp, $value );
467 $value = strip_tags( $value );
468 $value = str_replace( $temp, $allow, $value );
469 return $value;
470 }
471
472 /**
473 * Format a comparison like 5<10 to 5 < 10. Also works on 5< 10, 5 <10, 5<=10 variations.
474 * This is to avoid an issue with unspaced calculations being recognized as HTML that gets removed when strip_tags is called.
475 *
476 * @param string $calc
477 * @return string
478 */
479 private static function normalize_calc_spaces( $calc ) {
480 // Check for a pattern with 5 parts
481 // $1 \d|\] the first comparison digit or the end of a comparison shortcode.
482 // $2 a space (optional).
483 // $3 an equals sign (optional) that follows the < operator for <= comparisons.
484 // $4 another space (optional).
485 // $5 \d|\[ the second comparison digit or the start of a comparison shortcode.
486 $calc = preg_replace( '/(\d|\])( ){0,1}<(=){0,1}( ){0,1}(\d|\[)/', '$1 <$3 $5', $calc );
487
488 return $calc;
489 }
490
491 /**
492 * Updating the settings page
493 */
494 private static function get_settings_page_html( $values, &$field ) {
495 if ( isset( $values['field_options'][ 'custom_html_' . $field->id ] ) ) {
496 $prev_opts = array();
497 $fallback_html = $field->field_options['custom_html'] ?? FrmFieldsHelper::get_default_html( $field->type );
498
499 $field->field_options['custom_html'] = $values['field_options'][ 'custom_html_' . $field->id ] ?? $fallback_html;
500 } elseif ( $field->type === 'hidden' || $field->type === 'user_id' ) {
501 $prev_opts = $field->field_options;
502 }
503
504 if ( isset( $prev_opts ) ) {
505 $field->field_options = apply_filters( 'frm_update_form_field_options', $field->field_options, $field, $values );
506 if ( $prev_opts != $field->field_options ) {
507 FrmField::update( $field->id, array( 'field_options' => $field->field_options ) );
508 }
509 }
510 }
511
512 private static function prepare_field_update_values( $field, $values, &$new_field ) {
513 $field_cols = array(
514 'field_order' => 0,
515 'field_key' => '',
516 'required' => false,
517 'type' => '',
518 'description' => '',
519 'options' => '',
520 'name' => '',
521 );
522 foreach ( $field_cols as $col => $default ) {
523 $default = $default === '' ? $field->{$col} : $default;
524 $new_field[ $col ] = $values['field_options'][ $col . '_' . $field->id ] ?? $default;
525 }
526
527 if ( $field->type === 'submit' && isset( $new_field['field_order'] ) && (int) $new_field['field_order'] === FrmSubmitHelper::DEFAULT_ORDER ) {
528 $new_field['field_order'] = $field->field_order;
529 }
530
531 // Don't save the template option.
532 if ( is_array( $new_field['options'] ) && isset( $new_field['options']['000'] ) ) {
533 unset( $new_field['options']['000'] );
534 }
535 }
536
537 /**
538 * Get a list of all form settings that should be translated
539 * on a multilingual site.
540 *
541 * @since 3.06.01
542 * @param object $form The form object.
543 */
544 public static function translatable_strings( $form ) {
545 $strings = array(
546 'name',
547 'description',
548 'submit_value',
549 'submit_msg',
550 'success_msg',
551 'invalid_msg',
552 'failed_msg',
553 'login_msg',
554 'admin_permission',
555 );
556
557 return apply_filters( 'frm_form_strings', $strings, $form );
558 }
559
560 /**
561 * @param int $id
562 * @param string $status
563 *
564 * @return bool|int
565 */
566 public static function set_status( $id, $status ) {
567 if ( 'trash' == $status ) {
568 return self::trash( $id );
569 }
570
571 $statuses = array( 'published', 'draft', 'trash' );
572 if ( ! in_array( $status, $statuses, true ) ) {
573 return false;
574 }
575
576 global $wpdb;
577
578 if ( is_array( $id ) ) {
579 $where = array(
580 'id' => $id,
581 'parent_form_id' => $id,
582 'or' => 1,
583 );
584 FrmDb::get_where_clause_and_values( $where );
585 array_unshift( $where['values'], $status );
586
587 $query_results = $wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->prefix . 'frm_forms SET status = %s ' . $where['where'], $where['values'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
588 } else {
589 $query_results = $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'status' => $status ), array( 'id' => $id ) );
590 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'status' => $status ), array( 'parent_form_id' => $id ) );
591 }
592
593 if ( $query_results ) {
594 self::clear_form_cache();
595 }
596
597 return $query_results;
598 }
599
600 /**
601 * @return bool|int
602 */
603 public static function trash( $id ) {
604 if ( ! EMPTY_TRASH_DAYS ) {
605 return self::destroy( $id );
606 }
607
608 $form = self::getOne( $id );
609 if ( ! $form ) {
610 return false;
611 }
612
613 if ( $form->status === 'trash' ) {
614 return false;
615 }
616
617 $options = $form->options;
618 $options['trash_time'] = time();
619
620 global $wpdb;
621 $query_results = $wpdb->update(
622 $wpdb->prefix . 'frm_forms',
623 array(
624 'status' => 'trash',
625 'options' => serialize( $options ),
626 ),
627 array(
628 'id' => $id,
629 )
630 );
631
632 $wpdb->update(
633 $wpdb->prefix . 'frm_forms',
634 array(
635 'status' => 'trash',
636 'options' => serialize( $options ),
637 ),
638 array(
639 'parent_form_id' => $id,
640 )
641 );
642
643 if ( $query_results ) {
644 self::clear_form_cache();
645 }
646
647 return $query_results;
648 }
649
650 /**
651 * @return bool|int
652 */
653 public static function destroy( $id ) {
654 global $wpdb;
655
656 $form = self::getOne( $id );
657 if ( ! $form ) {
658 return false;
659 }
660 $id = $form->id;
661
662 // Disconnect the entries from this form
663 $entries = FrmDb::get_col( $wpdb->prefix . 'frm_items', array( 'form_id' => $id ) );
664 foreach ( $entries as $entry_id ) {
665 FrmEntry::destroy( $entry_id );
666 unset( $entry_id );
667 }
668
669 // Disconnect the fields from this form
670 $wpdb->query( $wpdb->prepare( 'DELETE fi FROM ' . $wpdb->prefix . 'frm_fields AS fi LEFT JOIN ' . $wpdb->prefix . 'frm_forms fr ON (fi.form_id = fr.id) WHERE fi.form_id=%d OR parent_form_id=%d', $id, $id ) );
671
672 $query_results = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_forms WHERE id=%d OR parent_form_id=%d', $id, $id ) );
673 if ( $query_results ) {
674 // Delete all form actions linked to this form
675 /**
676 * @var FrmFormAction
677 */
678 $action_control = FrmFormActionsController::get_form_actions( 'email' );
679 $action_control->destroy( $id, 'all' );
680
681 // Clear form caching
682 self::clear_form_cache();
683
684 do_action( 'frm_destroy_form', $id );
685 do_action( 'frm_destroy_form_' . $id );
686 }
687
688 return $query_results;
689 }
690
691 /**
692 * Delete trashed forms based on how long they have been trashed
693 *
694 * @return int The number of forms deleted
695 */
696 public static function scheduled_delete( $delete_timestamp = '' ) {
697 global $wpdb;
698
699 $trash_forms = FrmDb::get_results( $wpdb->prefix . 'frm_forms', array( 'status' => 'trash' ), 'id, parent_form_id, options' );
700
701 if ( ! $trash_forms ) {
702 return 0;
703 }
704
705 if ( empty( $delete_timestamp ) ) {
706 $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
707 }
708
709 $count = 0;
710 foreach ( $trash_forms as $form ) {
711 FrmAppHelper::unserialize_or_decode( $form->options );
712 if ( ! isset( $form->options['trash_time'] ) || $form->options['trash_time'] < $delete_timestamp ) {
713 self::destroy( $form->id );
714 if ( empty( $form->parent_form_id ) ) {
715 ++$count;
716 }
717 }
718
719 unset( $form );
720 }
721
722 return $count;
723 }
724
725 /**
726 * @return string form name
727 */
728 public static function getName( $id ) {
729 $form = FrmDb::check_cache( $id, 'frm_form' );
730 if ( $form ) {
731 $r = stripslashes( $form->name );
732
733 return $r;
734 }
735
736 $query_key = is_numeric( $id ) ? 'id' : 'form_key';
737 $r = FrmDb::get_var( 'frm_forms', array( $query_key => $id ), 'name' );
738
739 // An empty form name can result in a null value.
740 $r = is_null( $r ) ? '' : stripslashes( $r );
741
742 return $r;
743 }
744
745 /**
746 * @since 3.0
747 *
748 * @param string $key
749 *
750 * @return int form id
751 */
752 public static function get_id_by_key( $key ) {
753 return (int) FrmDb::get_var( 'frm_forms', array( 'form_key' => sanitize_title( $key ) ) );
754 }
755
756 /**
757 * @since 3.0
758 *
759 * @param int $id
760 *
761 * @return string form key
762 */
763 public static function get_key_by_id( $id ) {
764 $id = (int) $id;
765 $cache = FrmDb::check_cache( $id, 'frm_form' );
766 if ( $cache ) {
767 return $cache->form_key;
768 }
769
770 $key = FrmDb::get_var( 'frm_forms', array( 'id' => $id ), 'form_key' );
771
772 return $key;
773 }
774
775 /**
776 * If $form is numeric, get the form object
777 *
778 * @since 2.0.9
779 * @param int|object $form
780 */
781 public static function maybe_get_form( &$form ) {
782 if ( ! is_object( $form ) && ! is_array( $form ) && ! empty( $form ) ) {
783 $form = self::getOne( $form );
784 }
785 }
786
787 /**
788 * @param int|string $id
789 * @param false|int $blog_id
790 * @return stdClass|null
791 */
792 public static function getOne( $id, $blog_id = false ) {
793 global $wpdb;
794
795 if ( $blog_id && is_multisite() ) {
796 global $wpmuBaseTablePrefix;
797 $prefix = $wpmuBaseTablePrefix ? $wpmuBaseTablePrefix . $blog_id . '_' : $wpdb->get_blog_prefix( $blog_id );
798
799 $table_name = $prefix . 'frm_forms';
800 } else {
801 $table_name = $wpdb->prefix . 'frm_forms';
802 $cache = wp_cache_get( $id, 'frm_form' );
803 if ( $cache ) {
804 if ( isset( $cache->options ) ) {
805 FrmAppHelper::unserialize_or_decode( $cache->options );
806 }
807 return self::prepare_form_row_data( $cache );
808 }
809 }
810
811 if ( is_numeric( $id ) ) {
812 $where = array( 'id' => $id );
813 } else {
814 $where = array( 'form_key' => $id );
815 }
816
817 $results = FrmDb::get_row( $table_name, $where );
818
819 if ( isset( $results->options ) ) {
820 FrmDb::set_cache( $results->id, $results, 'frm_form' );
821 FrmAppHelper::unserialize_or_decode( $results->options );
822 }
823
824 return self::prepare_form_row_data( $results );
825 }
826
827 /**
828 * Make sure that if $row is an object, that $row->options is an array and not a string.
829 *
830 * @since 6.8.3
831 *
832 * @param stdClass|null $row The database row for a target form.
833 * @return stdClass|null
834 */
835 private static function prepare_form_row_data( $row ) {
836 $row = wp_unslash( $row );
837 if ( ! is_object( $row ) ) {
838 return $row;
839 }
840
841 if ( ! is_array( $row->options ) ) {
842 $row->options = FrmFormsHelper::get_default_opts();
843 }
844
845 /**
846 * @since 4.03.02
847 *
848 * @param stdClass $row
849 */
850 return apply_filters( 'frm_form_object', $row );
851 }
852
853 /**
854 * @return array|object of objects
855 */
856 public static function getAll( $where = array(), $order_by = '', $limit = '' ) {
857 if ( is_array( $where ) && ! empty( $where ) ) {
858 if ( ! empty( $where['is_template'] ) && ! isset( $where['status'] ) && ! isset( $where['status !'] ) ) {
859 // don't get trashed templates
860 $where['status'] = array( null, '', 'published' );
861 }
862
863 $results = FrmDb::get_results( 'frm_forms', $where, '*', compact( 'order_by', 'limit' ) );
864 } else {
865 global $wpdb;
866
867 // The query has already been prepared if this is not an array.
868 $query = 'SELECT * FROM ' . $wpdb->prefix . 'frm_forms' . FrmDb::prepend_and_or_where( ' WHERE ', $where ) . FrmDb::esc_order( $order_by ) . FrmDb::esc_limit( $limit );
869 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
870 }
871
872 if ( $results ) {
873 foreach ( $results as $result ) {
874 FrmDb::set_cache( $result->id, $result, 'frm_form' );
875 FrmAppHelper::unserialize_or_decode( $result->options );
876 }
877 }
878
879 if ( $limit === ' LIMIT 1' || $limit == 1 ) {
880 // return the first form object if we are only getting one form
881 $results = reset( $results );
882 }
883
884 return wp_unslash( $results );
885 }
886
887 /**
888 * Get all published forms
889 *
890 * @since 2.0
891 *
892 * @param array $query
893 * @param int $limit
894 * @param string $inc_children
895 * @return array|object of forms A single form object would be passed if $limit was set to 1.
896 */
897 public static function get_published_forms( $query = array(), $limit = 999, $inc_children = 'exclude' ) {
898 $query['is_template'] = 0;
899 $query['status'] = array( null, '', 'published' );
900 if ( $inc_children === 'exclude' ) {
901 $query['parent_form_id'] = array( null, 0 );
902 }
903
904 $forms = self::getAll( $query, 'name', $limit );
905
906 return $forms;
907 }
908
909 /**
910 * @return object count of forms
911 */
912 public static function get_count() {
913 global $wpdb;
914
915 $cache_key = 'frm_form_counts';
916
917 $counts = wp_cache_get( $cache_key, 'frm_form' );
918 if ( false !== $counts ) {
919 return $counts;
920 }
921
922 $results = (array) FrmDb::get_results(
923 'frm_forms',
924 array(
925 'or' => 1,
926 'parent_form_id' => null,
927 'parent_form_id <' => 0,
928 ),
929 'status, is_template'
930 );
931
932 $statuses = array( 'published', 'draft', 'template', 'trash' );
933 $counts = array_fill_keys( $statuses, 0 );
934
935 foreach ( $results as $row ) {
936 if ( 'trash' != $row->status ) {
937 if ( $row->is_template ) {
938 ++$counts['template'];
939 } else {
940 ++$counts['published'];
941 }
942 } else {
943 ++$counts['trash'];
944 }
945
946 if ( 'draft' == $row->status ) {
947 ++$counts['draft'];
948 }
949
950 unset( $row );
951 }
952
953 $counts = (object) $counts;
954 FrmDb::set_cache( $cache_key, $counts, 'frm_form' );
955
956 return $counts;
957 }
958
959 /**
960 * Clear form caching
961 * Called when a form is created, updated, duplicated, or deleted
962 * or when the form status is changed
963 *
964 * @since 2.0.4
965 */
966 public static function clear_form_cache() {
967 FrmDb::cache_delete_group( 'frm_form' );
968 }
969
970 /**
971 * @return array of errors
972 */
973 public static function validate( $values ) {
974 $errors = array();
975
976 return apply_filters( 'frm_validate_form', $errors, $values );
977 }
978
979 public static function get_params( $form = null ) {
980 global $frm_vars;
981
982 if ( ! $form ) {
983 $form = self::getAll( array(), 'name', 1 );
984 } else {
985 self::maybe_get_form( $form );
986 }
987
988 if ( isset( $frm_vars['form_params'] ) && is_array( $frm_vars['form_params'] ) && isset( $frm_vars['form_params'][ $form->id ] ) ) {
989 return $frm_vars['form_params'][ $form->id ];
990 }
991
992 $action_var = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
993 $action = apply_filters( 'frm_show_new_entry_page', FrmAppHelper::get_param( $action_var, 'new', 'get', 'sanitize_title' ), $form );
994
995 $default_values = array(
996 'id' => '',
997 'form_name' => '',
998 'paged' => 1,
999 'form' => $form->id,
1000 'form_id' => $form->id,
1001 'field_id' => '',
1002 'search' => '',
1003 'sort' => '',
1004 'sdir' => '',
1005 'action' => $action,
1006 );
1007
1008 $values = array();
1009 $values['posted_form_id'] = FrmAppHelper::get_param( 'form_id', '', 'get', 'absint' );
1010 if ( ! $values['posted_form_id'] ) {
1011 $values['posted_form_id'] = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
1012 }
1013
1014 if ( $form->id == $values['posted_form_id'] ) {
1015 // If there are two forms on the same page, make sure not to submit both.
1016 foreach ( $default_values as $var => $default ) {
1017 if ( $var === 'action' ) {
1018 $values[ $var ] = FrmAppHelper::get_param( $action_var, $default, 'get', 'sanitize_title' );
1019 } else {
1020 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1021 }
1022 unset( $var, $default );
1023 }
1024 } else {
1025 foreach ( $default_values as $var => $default ) {
1026 $values[ $var ] = $default;
1027 unset( $var, $default );
1028 }
1029 }
1030
1031 if ( in_array( $values['action'], array( 'create', 'update' ) ) &&
1032 ( ! $_POST || ( ! isset( $_POST['action'] ) && ! isset( $_POST['frm_action'] ) ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
1033 ) {
1034 $values['action'] = 'new';
1035 }
1036
1037 return $values;
1038 }
1039
1040 public static function list_page_params() {
1041 $values = array();
1042 $defaults = array(
1043 'template' => 0,
1044 'id' => '',
1045 'paged' => 1,
1046 'form' => '',
1047 'search' => '',
1048 'sort' => '',
1049 'sdir' => '',
1050 );
1051 foreach ( $defaults as $var => $default ) {
1052 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1053 }
1054
1055 return $values;
1056 }
1057
1058 public static function get_admin_params( $form = null ) {
1059 $form_id = $form;
1060 if ( $form === null ) {
1061 $form_id = self::get_current_form_id();
1062 } elseif ( $form && is_object( $form ) ) {
1063 $form_id = $form->id;
1064 }
1065
1066 $values = array();
1067 $defaults = array(
1068 'id' => '',
1069 'form_name' => '',
1070 'paged' => 1,
1071 'form' => $form_id,
1072 'field_id' => '',
1073 'search' => '',
1074 'sort' => '',
1075 'sdir' => '',
1076 'fid' => '',
1077 'keep_post' => '',
1078 );
1079 foreach ( $defaults as $var => $default ) {
1080 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1081 }
1082
1083 return $values;
1084 }
1085
1086 public static function get_current_form_id( $default_form = 'none' ) {
1087 if ( 'first' === $default_form ) {
1088 $form = self::get_current_form();
1089 } else {
1090 $form = self::maybe_get_current_form();
1091 }
1092 $form_id = $form ? $form->id : 0;
1093
1094 return $form_id;
1095 }
1096
1097 public static function maybe_get_current_form( $form_id = 0 ) {
1098 global $frm_vars;
1099
1100 if ( ! empty( $frm_vars['current_form'] ) && ( ! $form_id || $form_id == $frm_vars['current_form']->id ) ) {
1101 return $frm_vars['current_form'];
1102 }
1103
1104 $form_id = FrmAppHelper::get_param( 'form', $form_id, 'get', 'absint' );
1105 if ( $form_id ) {
1106 $form_id = self::set_current_form( $form_id );
1107 }
1108
1109 return $form_id;
1110 }
1111
1112 public static function get_current_form( $form_id = 0 ) {
1113 $form = self::maybe_get_current_form( $form_id );
1114 if ( is_numeric( $form ) ) {
1115 $form = self::set_current_form( $form );
1116 }
1117
1118 return $form;
1119 }
1120
1121 public static function set_current_form( $form_id ) {
1122 global $frm_vars;
1123
1124 $query = array();
1125 if ( $form_id ) {
1126 $query['id'] = $form_id;
1127 }
1128
1129 $frm_vars['current_form'] = self::get_published_forms( $query, 1 );
1130
1131 return $frm_vars['current_form'];
1132 }
1133
1134 public static function is_form_loaded( $form, $this_load, $global_load ) {
1135 global $frm_vars;
1136 $small_form = new stdClass();
1137 foreach ( array( 'id', 'form_key', 'name' ) as $var ) {
1138 $small_form->{$var} = $form->{$var};
1139 unset( $var );
1140 }
1141
1142 $frm_vars['forms_loaded'][] = $small_form;
1143
1144 if ( $this_load && empty( $global_load ) ) {
1145 $global_load = true;
1146 $frm_vars['load_css'] = true;
1147 }
1148
1149 return empty( $frm_vars['css_loaded'] ) && $global_load;
1150 }
1151
1152 /**
1153 * @since 4.06.03
1154 *
1155 * @param object $form
1156 *
1157 * @return bool
1158 */
1159 public static function &is_visible_to_user( $form ) {
1160 if ( $form->logged_in && isset( $form->options['logged_in_role'] ) ) {
1161 $visible = FrmAppHelper::user_has_permission( $form->options['logged_in_role'] );
1162 } else {
1163 $visible = true;
1164 }
1165
1166 /**
1167 * @since 6.25
1168 *
1169 * @param bool $visible
1170 * @param object $form
1171 */
1172 $visible = (bool) apply_filters( 'frm_form_is_visible', $visible, $form );
1173
1174 return $visible;
1175 }
1176
1177 public static function show_submit( $form ) {
1178 $show = ( ! $form->is_template && $form->status === 'published' && ! FrmAppHelper::is_admin() );
1179 $show = apply_filters( 'frm_show_submit_button', $show, $form );
1180
1181 return $show;
1182 }
1183
1184 /**
1185 * @since 2.3
1186 */
1187 public static function get_option( $atts ) {
1188 $form = $atts['form'];
1189 $default = $atts['default'] ?? '';
1190
1191 return $form->options[ $atts['option'] ] ?? $default;
1192 }
1193
1194 /**
1195 * Get the link to edit this form.
1196 *
1197 * @since 4.0
1198 * @param int $form_id The id of the form.
1199 */
1200 public static function get_edit_link( $form_id ) {
1201 return admin_url( 'admin.php?page=formidable&frm_action=edit&id=' . $form_id );
1202 }
1203
1204 /**
1205 * Check if the "Submit this form with AJAX" setting is toggled on.
1206 *
1207 * @since 6.2
1208 *
1209 * @param stdClass $form
1210 * @return bool
1211 */
1212 public static function is_ajax_on( $form ) {
1213 return ! empty( $form->options['ajax_submit'] );
1214 }
1215
1216 /**
1217 * Get the latest form available.
1218 *
1219 * @since 6.8
1220 * @return object
1221 */
1222 public static function get_latest_form() {
1223
1224 $args = array(
1225 array(
1226 'or' => 1,
1227 'parent_form_id' => null,
1228 'parent_form_id <' => 1,
1229 ),
1230 'is_template' => 0,
1231 'status !' => 'trash',
1232 );
1233
1234 return self::getAll( $args, 'created_at desc', 1 );
1235 }
1236
1237 /**
1238 * Count and return total forms.
1239 *
1240 * @since 6.8
1241 * @return int
1242 */
1243 public static function get_forms_count() {
1244
1245 $args = array(
1246 array(
1247 'or' => 1,
1248 'parent_form_id' => null,
1249 'parent_form_id <' => 1,
1250 ),
1251 'is_template' => 0,
1252 'status !' => 'trash',
1253 );
1254
1255 return FrmDb::get_count( 'frm_forms', $args );
1256 }
1257
1258 /**
1259 * @deprecated 2.03.05 This is still referenced in a few add ons (API, locations).
1260 * @codeCoverageIgnore
1261 *
1262 * @param string $key
1263 * @return int form id
1264 */
1265 public static function getIdByKey( $key ) {
1266 _deprecated_function( __FUNCTION__, '2.03.05', 'FrmForm::get_id_by_key' );
1267 return self::get_id_by_key( $key );
1268 }
1269
1270 /**
1271 * @deprecated 2.03.05 This is still referenced in the API add on as of v1.13.
1272 * @codeCoverageIgnore
1273 *
1274 * @param int|string $id
1275 * @return string
1276 */
1277 public static function getKeyById( $id ) {
1278 _deprecated_function( __FUNCTION__, '2.03.05', 'FrmForm::get_key_by_id' );
1279 return self::get_key_by_id( $id );
1280 }
1281 }
1282