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

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