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

1,261 lines 34.9 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
519 $new_field[ $col ] = isset( $values['field_options'][ $col . '_' . $field->id ] ) ? $values['field_options'][ $col . '_' . $field->id ] : $default;
520 }
521
522 // Don't save the template option.
523 if ( is_array( $new_field['options'] ) && isset( $new_field['options']['000'] ) ) {
524 unset( $new_field['options']['000'] );
525 }
526 }
527
528 /**
529 * Get a list of all form settings that should be translated
530 * on a multilingual site.
531 *
532 * @since 3.06.01
533 * @param object $form The form object.
534 */
535 public static function translatable_strings( $form ) {
536 $strings = array(
537 'name',
538 'description',
539 'submit_value',
540 'submit_msg',
541 'success_msg',
542 'invalid_msg',
543 'failed_msg',
544 'login_msg',
545 'admin_permission',
546 );
547
548 return apply_filters( 'frm_form_strings', $strings, $form );
549 }
550
551 /**
552 * @param int $id
553 * @param string $status
554 *
555 * @return bool|int
556 */
557 public static function set_status( $id, $status ) {
558 if ( 'trash' == $status ) {
559 return self::trash( $id );
560 }
561
562 $statuses = array( 'published', 'draft', 'trash' );
563 if ( ! in_array( $status, $statuses, true ) ) {
564 return false;
565 }
566
567 global $wpdb;
568
569 if ( is_array( $id ) ) {
570 $where = array(
571 'id' => $id,
572 'parent_form_id' => $id,
573 'or' => 1,
574 );
575 FrmDb::get_where_clause_and_values( $where );
576 array_unshift( $where['values'], $status );
577
578 $query_results = $wpdb->query( $wpdb->prepare( 'UPDATE ' . $wpdb->prefix . 'frm_forms SET status = %s ' . $where['where'], $where['values'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
579 } else {
580 $query_results = $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'status' => $status ), array( 'id' => $id ) );
581 $wpdb->update( $wpdb->prefix . 'frm_forms', array( 'status' => $status ), array( 'parent_form_id' => $id ) );
582 }
583
584 if ( $query_results ) {
585 self::clear_form_cache();
586 }
587
588 return $query_results;
589 }
590
591 /**
592 * @return bool|int
593 */
594 public static function trash( $id ) {
595 if ( ! EMPTY_TRASH_DAYS ) {
596 return self::destroy( $id );
597 }
598
599 $form = self::getOne( $id );
600 if ( ! $form ) {
601 return false;
602 }
603
604 $options = $form->options;
605 $options['trash_time'] = time();
606
607 global $wpdb;
608 $query_results = $wpdb->update(
609 $wpdb->prefix . 'frm_forms',
610 array(
611 'status' => 'trash',
612 'options' => serialize( $options ),
613 ),
614 array(
615 'id' => $id,
616 )
617 );
618
619 $wpdb->update(
620 $wpdb->prefix . 'frm_forms',
621 array(
622 'status' => 'trash',
623 'options' => serialize( $options ),
624 ),
625 array(
626 'parent_form_id' => $id,
627 )
628 );
629
630 if ( $query_results ) {
631 self::clear_form_cache();
632 }
633
634 return $query_results;
635 }
636
637 /**
638 * @return bool|int
639 */
640 public static function destroy( $id ) {
641 global $wpdb;
642
643 $form = self::getOne( $id );
644 if ( ! $form ) {
645 return false;
646 }
647 $id = $form->id;
648
649 // Disconnect the entries from this form
650 $entries = FrmDb::get_col( $wpdb->prefix . 'frm_items', array( 'form_id' => $id ) );
651 foreach ( $entries as $entry_id ) {
652 FrmEntry::destroy( $entry_id );
653 unset( $entry_id );
654 }
655
656 // Disconnect the fields from this form
657 $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 ) );
658
659 $query_results = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_forms WHERE id=%d OR parent_form_id=%d', $id, $id ) );
660 if ( $query_results ) {
661 // Delete all form actions linked to this form
662 /**
663 * @var FrmFormAction
664 */
665 $action_control = FrmFormActionsController::get_form_actions( 'email' );
666 $action_control->destroy( $id, 'all' );
667
668 // Clear form caching
669 self::clear_form_cache();
670
671 do_action( 'frm_destroy_form', $id );
672 do_action( 'frm_destroy_form_' . $id );
673 }
674
675 return $query_results;
676 }
677
678 /**
679 * Delete trashed forms based on how long they have been trashed
680 *
681 * @return int The number of forms deleted
682 */
683 public static function scheduled_delete( $delete_timestamp = '' ) {
684 global $wpdb;
685
686 $trash_forms = FrmDb::get_results( $wpdb->prefix . 'frm_forms', array( 'status' => 'trash' ), 'id, parent_form_id, options' );
687
688 if ( ! $trash_forms ) {
689 return 0;
690 }
691
692 if ( empty( $delete_timestamp ) ) {
693 $delete_timestamp = time() - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
694 }
695
696 $count = 0;
697 foreach ( $trash_forms as $form ) {
698 FrmAppHelper::unserialize_or_decode( $form->options );
699 if ( ! isset( $form->options['trash_time'] ) || $form->options['trash_time'] < $delete_timestamp ) {
700 self::destroy( $form->id );
701 if ( empty( $form->parent_form_id ) ) {
702 ++$count;
703 }
704 }
705
706 unset( $form );
707 }
708
709 return $count;
710 }
711
712 /**
713 * @return string form name
714 */
715 public static function getName( $id ) {
716 $form = FrmDb::check_cache( $id, 'frm_form' );
717 if ( $form ) {
718 $r = stripslashes( $form->name );
719
720 return $r;
721 }
722
723 $query_key = is_numeric( $id ) ? 'id' : 'form_key';
724 $r = FrmDb::get_var( 'frm_forms', array( $query_key => $id ), 'name' );
725
726 // An empty form name can result in a null value.
727 $r = is_null( $r ) ? '' : stripslashes( $r );
728
729 return $r;
730 }
731
732 /**
733 * @since 3.0
734 *
735 * @param string $key
736 *
737 * @return int form id
738 */
739 public static function get_id_by_key( $key ) {
740 return (int) FrmDb::get_var( 'frm_forms', array( 'form_key' => sanitize_title( $key ) ) );
741 }
742
743 /**
744 * @since 3.0
745 *
746 * @param int $id
747 *
748 * @return string form key
749 */
750 public static function get_key_by_id( $id ) {
751 $id = (int) $id;
752 $cache = FrmDb::check_cache( $id, 'frm_form' );
753 if ( $cache ) {
754 return $cache->form_key;
755 }
756
757 $key = FrmDb::get_var( 'frm_forms', array( 'id' => $id ), 'form_key' );
758
759 return $key;
760 }
761
762 /**
763 * If $form is numeric, get the form object
764 *
765 * @since 2.0.9
766 * @param int|object $form
767 */
768 public static function maybe_get_form( &$form ) {
769 if ( ! is_object( $form ) && ! is_array( $form ) && ! empty( $form ) ) {
770 $form = self::getOne( $form );
771 }
772 }
773
774 /**
775 * @param int|string $id
776 * @param false|int $blog_id
777 * @return stdClass|null
778 */
779 public static function getOne( $id, $blog_id = false ) {
780 global $wpdb;
781
782 if ( $blog_id && is_multisite() ) {
783 global $wpmuBaseTablePrefix;
784 $prefix = $wpmuBaseTablePrefix ? $wpmuBaseTablePrefix . $blog_id . '_' : $wpdb->get_blog_prefix( $blog_id );
785
786 $table_name = $prefix . 'frm_forms';
787 } else {
788 $table_name = $wpdb->prefix . 'frm_forms';
789 $cache = wp_cache_get( $id, 'frm_form' );
790 if ( $cache ) {
791 if ( isset( $cache->options ) ) {
792 FrmAppHelper::unserialize_or_decode( $cache->options );
793 }
794 return self::prepare_form_row_data( $cache );
795 }
796 }
797
798 if ( is_numeric( $id ) ) {
799 $where = array( 'id' => $id );
800 } else {
801 $where = array( 'form_key' => $id );
802 }
803
804 $results = FrmDb::get_row( $table_name, $where );
805
806 if ( isset( $results->options ) ) {
807 FrmDb::set_cache( $results->id, $results, 'frm_form' );
808 FrmAppHelper::unserialize_or_decode( $results->options );
809 }
810
811 return self::prepare_form_row_data( $results );
812 }
813
814 /**
815 * Make sure that if $row is an object, that $row->options is an array and not a string.
816 *
817 * @since 6.8.3
818 *
819 * @param stdClass|null $row The database row for a target form.
820 * @return stdClass|null
821 */
822 private static function prepare_form_row_data( $row ) {
823 $row = wp_unslash( $row );
824 if ( ! is_object( $row ) ) {
825 return $row;
826 }
827
828 if ( ! is_array( $row->options ) ) {
829 $row->options = FrmFormsHelper::get_default_opts();
830 }
831
832 /**
833 * @since 4.03.02
834 *
835 * @param stdClass $row
836 */
837 return apply_filters( 'frm_form_object', $row );
838 }
839
840 /**
841 * @return array|object of objects
842 */
843 public static function getAll( $where = array(), $order_by = '', $limit = '' ) {
844 if ( is_array( $where ) && ! empty( $where ) ) {
845 if ( isset( $where['is_template'] ) && $where['is_template'] && ! isset( $where['status'] ) && ! isset( $where['status !'] ) ) {
846 // don't get trashed templates
847 $where['status'] = array( null, '', 'published' );
848 }
849
850 $results = FrmDb::get_results( 'frm_forms', $where, '*', compact( 'order_by', 'limit' ) );
851 } else {
852 global $wpdb;
853
854 // The query has already been prepared if this is not an array.
855 $query = 'SELECT * FROM ' . $wpdb->prefix . 'frm_forms' . FrmDb::prepend_and_or_where( ' WHERE ', $where ) . FrmDb::esc_order( $order_by ) . FrmDb::esc_limit( $limit );
856 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
857 }
858
859 if ( $results ) {
860 foreach ( $results as $result ) {
861 FrmDb::set_cache( $result->id, $result, 'frm_form' );
862 FrmAppHelper::unserialize_or_decode( $result->options );
863 }
864 }
865
866 if ( $limit === ' LIMIT 1' || $limit == 1 ) {
867 // return the first form object if we are only getting one form
868 $results = reset( $results );
869 }
870
871 return wp_unslash( $results );
872 }
873
874 /**
875 * Get all published forms
876 *
877 * @since 2.0
878 *
879 * @param array $query
880 * @param int $limit
881 * @param string $inc_children
882 * @return array|object of forms A single form object would be passed if $limit was set to 1.
883 */
884 public static function get_published_forms( $query = array(), $limit = 999, $inc_children = 'exclude' ) {
885 $query['is_template'] = 0;
886 $query['status'] = array( null, '', 'published' );
887 if ( $inc_children === 'exclude' ) {
888 $query['parent_form_id'] = array( null, 0 );
889 }
890
891 $forms = self::getAll( $query, 'name', $limit );
892
893 return $forms;
894 }
895
896 /**
897 * @return object count of forms
898 */
899 public static function get_count() {
900 global $wpdb;
901
902 $cache_key = 'frm_form_counts';
903
904 $counts = wp_cache_get( $cache_key, 'frm_form' );
905 if ( false !== $counts ) {
906 return $counts;
907 }
908
909 $results = (array) FrmDb::get_results(
910 'frm_forms',
911 array(
912 'or' => 1,
913 'parent_form_id' => null,
914 'parent_form_id <' => 0,
915 ),
916 'status, is_template'
917 );
918
919 $statuses = array( 'published', 'draft', 'template', 'trash' );
920 $counts = array_fill_keys( $statuses, 0 );
921
922 foreach ( $results as $row ) {
923 if ( 'trash' != $row->status ) {
924 if ( $row->is_template ) {
925 ++$counts['template'];
926 } else {
927 ++$counts['published'];
928 }
929 } else {
930 ++$counts['trash'];
931 }
932
933 if ( 'draft' == $row->status ) {
934 ++$counts['draft'];
935 }
936
937 unset( $row );
938 }
939
940 $counts = (object) $counts;
941 FrmDb::set_cache( $cache_key, $counts, 'frm_form' );
942
943 return $counts;
944 }
945
946 /**
947 * Clear form caching
948 * Called when a form is created, updated, duplicated, or deleted
949 * or when the form status is changed
950 *
951 * @since 2.0.4
952 */
953 public static function clear_form_cache() {
954 FrmDb::cache_delete_group( 'frm_form' );
955 }
956
957 /**
958 * @return array of errors
959 */
960 public static function validate( $values ) {
961 $errors = array();
962
963 return apply_filters( 'frm_validate_form', $errors, $values );
964 }
965
966 public static function get_params( $form = null ) {
967 global $frm_vars;
968
969 if ( ! $form ) {
970 $form = self::getAll( array(), 'name', 1 );
971 } else {
972 self::maybe_get_form( $form );
973 }
974
975 if ( isset( $frm_vars['form_params'] ) && is_array( $frm_vars['form_params'] ) && isset( $frm_vars['form_params'][ $form->id ] ) ) {
976 return $frm_vars['form_params'][ $form->id ];
977 }
978
979 $action_var = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
980 $action = apply_filters( 'frm_show_new_entry_page', FrmAppHelper::get_param( $action_var, 'new', 'get', 'sanitize_title' ), $form );
981
982 $default_values = array(
983 'id' => '',
984 'form_name' => '',
985 'paged' => 1,
986 'form' => $form->id,
987 'form_id' => $form->id,
988 'field_id' => '',
989 'search' => '',
990 'sort' => '',
991 'sdir' => '',
992 'action' => $action,
993 );
994
995 $values = array();
996 $values['posted_form_id'] = FrmAppHelper::get_param( 'form_id', '', 'get', 'absint' );
997 if ( ! $values['posted_form_id'] ) {
998 $values['posted_form_id'] = FrmAppHelper::get_param( 'form', '', 'get', 'absint' );
999 }
1000
1001 if ( $form->id == $values['posted_form_id'] ) {
1002 // If there are two forms on the same page, make sure not to submit both.
1003 foreach ( $default_values as $var => $default ) {
1004 if ( $var === 'action' ) {
1005 $values[ $var ] = FrmAppHelper::get_param( $action_var, $default, 'get', 'sanitize_title' );
1006 } else {
1007 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1008 }
1009 unset( $var, $default );
1010 }
1011 } else {
1012 foreach ( $default_values as $var => $default ) {
1013 $values[ $var ] = $default;
1014 unset( $var, $default );
1015 }
1016 }
1017
1018 if ( in_array( $values['action'], array( 'create', 'update' ) ) &&
1019 ( ! $_POST || ( ! isset( $_POST['action'] ) && ! isset( $_POST['frm_action'] ) ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
1020 ) {
1021 $values['action'] = 'new';
1022 }
1023
1024 return $values;
1025 }
1026
1027 public static function list_page_params() {
1028 $values = array();
1029 $defaults = array(
1030 'template' => 0,
1031 'id' => '',
1032 'paged' => 1,
1033 'form' => '',
1034 'search' => '',
1035 'sort' => '',
1036 'sdir' => '',
1037 );
1038 foreach ( $defaults as $var => $default ) {
1039 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1040 }
1041
1042 return $values;
1043 }
1044
1045 public static function get_admin_params( $form = null ) {
1046 $form_id = $form;
1047 if ( $form === null ) {
1048 $form_id = self::get_current_form_id();
1049 } elseif ( $form && is_object( $form ) ) {
1050 $form_id = $form->id;
1051 }
1052
1053 $values = array();
1054 $defaults = array(
1055 'id' => '',
1056 'form_name' => '',
1057 'paged' => 1,
1058 'form' => $form_id,
1059 'field_id' => '',
1060 'search' => '',
1061 'sort' => '',
1062 'sdir' => '',
1063 'fid' => '',
1064 'keep_post' => '',
1065 );
1066 foreach ( $defaults as $var => $default ) {
1067 $values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
1068 }
1069
1070 return $values;
1071 }
1072
1073 public static function get_current_form_id( $default_form = 'none' ) {
1074 if ( 'first' === $default_form ) {
1075 $form = self::get_current_form();
1076 } else {
1077 $form = self::maybe_get_current_form();
1078 }
1079 $form_id = $form ? $form->id : 0;
1080
1081 return $form_id;
1082 }
1083
1084 public static function maybe_get_current_form( $form_id = 0 ) {
1085 global $frm_vars;
1086
1087 if ( isset( $frm_vars['current_form'] ) && $frm_vars['current_form'] && ( ! $form_id || $form_id == $frm_vars['current_form']->id ) ) {
1088 return $frm_vars['current_form'];
1089 }
1090
1091 $form_id = FrmAppHelper::get_param( 'form', $form_id, 'get', 'absint' );
1092 if ( $form_id ) {
1093 $form_id = self::set_current_form( $form_id );
1094 }
1095
1096 return $form_id;
1097 }
1098
1099 public static function get_current_form( $form_id = 0 ) {
1100 $form = self::maybe_get_current_form( $form_id );
1101 if ( is_numeric( $form ) ) {
1102 $form = self::set_current_form( $form );
1103 }
1104
1105 return $form;
1106 }
1107
1108 public static function set_current_form( $form_id ) {
1109 global $frm_vars;
1110
1111 $query = array();
1112 if ( $form_id ) {
1113 $query['id'] = $form_id;
1114 }
1115
1116 $frm_vars['current_form'] = self::get_published_forms( $query, 1 );
1117
1118 return $frm_vars['current_form'];
1119 }
1120
1121 public static function is_form_loaded( $form, $this_load, $global_load ) {
1122 global $frm_vars;
1123 $small_form = new stdClass();
1124 foreach ( array( 'id', 'form_key', 'name' ) as $var ) {
1125 $small_form->{$var} = $form->{$var};
1126 unset( $var );
1127 }
1128
1129 $frm_vars['forms_loaded'][] = $small_form;
1130
1131 if ( $this_load && empty( $global_load ) ) {
1132 $global_load = true;
1133 $frm_vars['load_css'] = true;
1134 }
1135
1136 return ( ( ! isset( $frm_vars['css_loaded'] ) || ! $frm_vars['css_loaded'] ) && $global_load );
1137 }
1138
1139 /**
1140 * @since 4.06.03
1141 *
1142 * @param object $form
1143 *
1144 * @return bool
1145 */
1146 public static function &is_visible_to_user( $form ) {
1147 if ( $form->logged_in && isset( $form->options['logged_in_role'] ) ) {
1148 $visible = FrmAppHelper::user_has_permission( $form->options['logged_in_role'] );
1149 } else {
1150 $visible = true;
1151 }
1152
1153 return $visible;
1154 }
1155
1156 public static function show_submit( $form ) {
1157 $show = ( ! $form->is_template && $form->status === 'published' && ! FrmAppHelper::is_admin() );
1158 $show = apply_filters( 'frm_show_submit_button', $show, $form );
1159
1160 return $show;
1161 }
1162
1163 /**
1164 * @since 2.3
1165 */
1166 public static function get_option( $atts ) {
1167 $form = $atts['form'];
1168 $default = isset( $atts['default'] ) ? $atts['default'] : '';
1169
1170 return isset( $form->options[ $atts['option'] ] ) ? $form->options[ $atts['option'] ] : $default;
1171 }
1172
1173 /**
1174 * Get the link to edit this form.
1175 *
1176 * @since 4.0
1177 * @param int $form_id The id of the form.
1178 */
1179 public static function get_edit_link( $form_id ) {
1180 return admin_url( 'admin.php?page=formidable&frm_action=edit&id=' . $form_id );
1181 }
1182
1183 /**
1184 * Check if the "Submit this form with AJAX" setting is toggled on.
1185 *
1186 * @since 6.2
1187 *
1188 * @param stdClass $form
1189 * @return bool
1190 */
1191 public static function is_ajax_on( $form ) {
1192 return ! empty( $form->options['ajax_submit'] );
1193 }
1194
1195 /**
1196 * Get the latest form available.
1197 *
1198 * @since 6.8
1199 * @return object
1200 */
1201 public static function get_latest_form() {
1202
1203 $args = array(
1204 array(
1205 'or' => 1,
1206 'parent_form_id' => null,
1207 'parent_form_id <' => 1,
1208 ),
1209 'is_template' => 0,
1210 'status !' => 'trash',
1211 );
1212
1213 return self::getAll( $args, 'created_at desc', 1 );
1214 }
1215
1216 /**
1217 * Count and return total forms.
1218 *
1219 * @since 6.8
1220 * @return int
1221 */
1222 public static function get_forms_count() {
1223
1224 $args = array(
1225 array(
1226 'or' => 1,
1227 'parent_form_id' => null,
1228 'parent_form_id <' => 1,
1229 ),
1230 'is_template' => 0,
1231 'status !' => 'trash',
1232 );
1233
1234 return FrmDb::get_count( 'frm_forms', $args );
1235 }
1236
1237 /**
1238 * @deprecated 2.03.05 This is still referenced in a few add ons (API, locations).
1239 * @codeCoverageIgnore
1240 *
1241 * @param string $key
1242 * @return int form id
1243 */
1244 public static function getIdByKey( $key ) {
1245 _deprecated_function( __FUNCTION__, '2.03.05', 'FrmForm::get_id_by_key' );
1246 return self::get_id_by_key( $key );
1247 }
1248
1249 /**
1250 * @deprecated 2.03.05 This is still referenced in the API add on as of v1.13.
1251 * @codeCoverageIgnore
1252 *
1253 * @param int|string $id
1254 * @return string
1255 */
1256 public static function getKeyById( $id ) {
1257 _deprecated_function( __FUNCTION__, '2.03.05', 'FrmForm::get_key_by_id' );
1258 return self::get_key_by_id( $id );
1259 }
1260 }
1261