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

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