PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.34
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.34
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 6.5.4 All 140 releases
formidable / classes / models / FrmForm.php

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

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