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

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