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

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