PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.31
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.31
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 / FrmEntry.php

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

1,304 lines 33.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 FrmEntry {
7
8 /**
9 * @since 6.16.3
10 *
11 * @var array
12 */
13 private static $unique_id_match_checks = array();
14
15 /**
16 * Create a new entry
17 *
18 * @param array $values
19 *
20 * @return bool|int Entry ID.
21 */
22 public static function create( $values ) {
23 return self::create_entry( $values, 'standard' );
24 }
25
26 /**
27 * Create a new entry with some differences depending on type
28 *
29 * @param array $values
30 * @param string $type
31 *
32 * @return bool|int Entry ID.
33 */
34 private static function create_entry( $values, $type ) {
35 $new_values = self::before_insert_entry_in_database( $values, $type );
36
37 // Don't check XML entries for duplicates
38 if ( $type !== 'xml' && self::is_duplicate( $new_values, $values ) ) {
39 return false;
40 }
41
42 return self::continue_to_create_entry( $values, $new_values );
43 }
44
45 /**
46 * Flag the memoized unique id check after a new entry is created.
47 * This prevents possibly DB requests and helps avoid issues when creating repeater entries.
48 *
49 * @since 6.16.3
50 *
51 * @param string $unique_id
52 *
53 * @return void
54 */
55 private static function flag_new_unique_key( $unique_id ) {
56 if ( ! isset( self::$unique_id_match_checks[ $unique_id ] ) ) {
57 self::$unique_id_match_checks[ $unique_id ] = false;
58 }
59 }
60
61 /**
62 * Check for duplicate entries created in the last minute
63 *
64 * @param array $new_values New values.
65 * @param array $values Values.
66 *
67 * @return bool
68 */
69 public static function is_duplicate( $new_values, $values ) {
70 $duplicate_entry_time = apply_filters( 'frm_time_to_check_duplicates', 60, $new_values );
71
72 if ( false === self::is_duplicate_check_needed( $values, $duplicate_entry_time ) ) {
73 return false;
74 }
75
76 if ( self::maybe_check_for_unique_id_match( $values, $new_values['created_at'] ) ) {
77 return true;
78 }
79
80 $check_val = $new_values;
81 $check_val['created_at >'] = gmdate( 'Y-m-d H:i:s', strtotime( $new_values['created_at'] ) - absint( $duplicate_entry_time ) );
82
83 unset( $check_val['created_at'], $check_val['updated_at'], $check_val['is_draft'], $check_val['id'], $check_val['item_key'] );
84
85 // phpcs:ignore Universal.Operators.StrictComparisons
86 if ( $new_values['item_key'] == $new_values['name'] ) {
87 unset( $check_val['name'] );
88 }
89
90 $check_val = apply_filters( 'frm_duplicate_check_val', $check_val );
91
92 if ( ! isset( $values['item_meta'] ) ) {
93 return false;
94 }
95
96 $entry_exists = FrmDb::get_col( 'frm_items', $check_val, 'id', array( 'order_by' => 'created_at DESC' ) );
97
98 if ( ! $entry_exists ) {
99 return false;
100 }
101
102 global $frm_vars;
103 $frm_vars['checking_duplicates'] = true;
104 $is_duplicate = false;
105
106 foreach ( $entry_exists as $entry_exist ) {
107 $is_duplicate = true;
108
109 // Make sure it's a duplicate
110 $metas = FrmEntryMeta::get_entry_meta_info( $entry_exist );
111 $field_metas = array();
112
113 foreach ( $metas as $meta ) {
114 if ( 0 === (int) $meta->field_id ) {
115 continue;
116 }
117
118 $field_metas[ $meta->field_id ] = $meta->meta_value;
119 }
120
121 $filtered_vals = array_filter( $values['item_meta'] );
122 $filtered_vals = self::convert_values_to_their_saved_value( $filtered_vals, $entry_exist );
123 $field_metas = array_filter( $field_metas );
124
125 // If prev entry is empty and current entry is not, they are not duplicates
126 if ( ! $field_metas && $filtered_vals ) {
127 return false;
128 }
129
130 // Compare serialized values and not arrays
131 $new_meta = array_map( 'maybe_serialize', $filtered_vals );
132
133 if ( $field_metas === $new_meta ) {
134 $is_duplicate = true;
135 break;
136 }
137
138 if ( count( $field_metas ) !== count( $new_meta ) ) {
139 // TODO: compare values saved in the post also
140 $is_duplicate = false;
141 continue;
142 }
143
144 $diff = array_diff_assoc( $field_metas, $new_meta );
145
146 foreach ( $diff as $meta_value ) {
147 if ( $meta_value ) {
148 $is_duplicate = false;
149 }
150 }
151
152 if ( $is_duplicate ) {
153 break;
154 }
155 }//end foreach
156
157 $frm_vars['checking_duplicates'] = false;
158
159 return $is_duplicate;
160 }
161
162 /**
163 * @since 6.16.3
164 *
165 * @param array $values POST request data.
166 * @param string $created_at The timestamp of the entry we are checking for.
167 *
168 * @return bool
169 */
170 private static function maybe_check_for_unique_id_match( $values, $created_at ) {
171 if ( ! self::should_check_for_unique_id_match() ) {
172 return false;
173 }
174
175 if ( empty( $values['unique_id'] ) ) {
176 return false;
177 }
178
179 $unique_id = sanitize_key( $values['unique_id'] );
180
181 if ( ! $unique_id ) {
182 // Only continue if a unique ID was generated on form submit.
183 return false;
184 }
185
186 if ( isset( self::$unique_id_match_checks[ $unique_id ] ) ) {
187 return self::$unique_id_match_checks[ $unique_id ];
188 }
189
190 $timestamp = strtotime( $created_at );
191
192 if ( false === $timestamp ) {
193 $timestamp = time();
194 }
195
196 self::$unique_id_match_checks[ $unique_id ] = (bool) FrmDb::get_var(
197 'frm_item_metas',
198 array(
199 'field_id' => 0,
200 'meta_value' => serialize( compact( 'unique_id' ) ),
201 'created_at >' => gmdate( 'Y-m-d H:i:s', $timestamp - MONTH_IN_SECONDS ),
202 ),
203 'id'
204 );
205
206 return self::$unique_id_match_checks[ $unique_id ];
207 }
208
209 /**
210 * @since 6.16.3
211 *
212 * @return bool
213 */
214 private static function should_check_for_unique_id_match() {
215 /**
216 * Allow users to opt out of the DB query, in case it causes performance issues.
217 *
218 * @since 6.16.3
219 *
220 * @param bool $should_extend
221 */
222 $should_check = apply_filters( 'frm_check_for_unique_id_match', true );
223 return (bool) $should_check;
224 }
225
226 /**
227 * Convert form data to the actual value that would be saved into the database.
228 *
229 * This is important for the duplicate check as something like 'a:2:{s:5:"typed";s:0:"";s:6:"output";s:0:"";}'
230 * (a signature value) is actually an empty string and does not get saved.
231 *
232 * @param array $filter_vals
233 * @param int $entry_id
234 *
235 * @return array
236 */
237 private static function convert_values_to_their_saved_value( $filter_vals, $entry_id ) {
238 $reduced = array();
239
240 foreach ( $filter_vals as $field_id => $value ) {
241 $field = FrmFieldFactory::get_field_object( $field_id );
242 $reduced[ $field_id ] = $field->get_value_to_save( $value, array( 'entry_id' => $entry_id ) );
243 $reduced[ $field_id ] = $field->set_value_before_save( $reduced[ $field_id ] );
244
245 if ( '' === $reduced[ $field_id ] || array() === $reduced[ $field_id ] ) {
246 unset( $reduced[ $field_id ] );
247 }
248 }
249
250 return $reduced;
251 }
252
253 /**
254 * Determine if an entry needs to be checked as a possible duplicate
255 *
256 * @since 2.0.23
257 *
258 * @param array $values
259 * @param int $duplicate_entry_time
260 *
261 * @return bool
262 */
263 private static function is_duplicate_check_needed( $values, $duplicate_entry_time ) {
264 // If time for checking duplicates is set to an empty value, don't check for duplicates
265 if ( ! $duplicate_entry_time ) {
266 return false;
267 }
268
269 // If CSV is importing, don't check for duplicates
270 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
271 return false;
272 }
273
274 // If repeating field entries are getting created, don't check for duplicates
275 return empty( $values['parent_form_id'] );
276 }
277
278 /**
279 * @param int|string $id
280 *
281 * @return false|int
282 */
283 public static function duplicate( $id ) {
284 global $wpdb;
285
286 $values = self::getOne( $id );
287 $new_values = array();
288 $new_values['item_key'] = FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_items', 'item_key' );
289 $new_values['name'] = $values->name;
290 $new_values['is_draft'] = $values->is_draft;
291 $new_values['user_id'] = (int) $values->user_id;
292 $new_values['updated_by'] = (int) $values->user_id;
293 $new_values['form_id'] = $values->form_id ? (int) $values->form_id : null;
294 $new_values['created_at'] = current_time( 'mysql', 1 );
295 $new_values['updated_at'] = $new_values['created_at'];
296
297 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_items', $new_values );
298
299 if ( ! $query_results ) {
300 return false;
301 }
302
303 $entry_id = $wpdb->insert_id;
304
305 global $frm_vars;
306
307 if ( ! isset( $frm_vars['saved_entries'] ) ) {
308 $frm_vars['saved_entries'] = array();
309 }
310 $frm_vars['saved_entries'][] = (int) $entry_id;
311
312 FrmEntryMeta::duplicate_entry_metas( $id, $entry_id );
313 self::clear_cache();
314
315 do_action( 'frm_after_duplicate_entry', $entry_id, $new_values['form_id'], array( 'old_id' => $id ) );
316
317 return $entry_id;
318 }
319
320 /**
321 * Update an entry (not via XML)
322 *
323 * @param int $id
324 * @param array $values
325 *
326 * @return bool|int Update results.
327 */
328 public static function update( $id, $values ) {
329 return self::update_entry( $id, $values, 'standard' );
330 }
331
332 /**
333 * Update an entry with some differences depending on the update type
334 *
335 * @since 2.0.16
336 *
337 * @param int $id
338 * @param array $values
339 * @param string $update_type
340 *
341 * @return bool|int Query results.
342 */
343 private static function update_entry( $id, $values, $update_type ) {
344 global $wpdb;
345
346 $update = self::before_update_entry( $id, $values, $update_type );
347
348 if ( ! $update ) {
349 return false;
350 }
351
352 $new_values = self::package_entry_to_update( $id, $values );
353 $query_results = $wpdb->update( $wpdb->prefix . 'frm_items', $new_values, compact( 'id' ) );
354
355 self::after_update_entry( $query_results, $id, $values, $new_values );
356
357 return $query_results;
358 }
359
360 /**
361 * Delete an entry.
362 *
363 * @param int|string $id
364 *
365 * @return bool True on success, false if nothing was deleted.
366 */
367 public static function destroy( $id ) {
368 global $wpdb;
369 $id = (int) $id;
370
371 // Item meta is required for conditional logic in actions with 'delete' events.
372 $entry = self::getOne( $id, true );
373
374 if ( ! $entry ) {
375 return false;
376 }
377
378 /**
379 * Trigger an action to run custom logic before the entry is deleted.
380 *
381 * @param int $id The id of the entry that was destroyed.
382 * @param stdClass $entry The entry object.
383 */
384 do_action( 'frm_before_destroy_entry', $id, $entry );
385
386 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE item_id=%d', $id ) );
387 $result = $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_items WHERE id=%d', $id ) );
388
389 self::clear_cache();
390
391 /**
392 * Trigger an action to run custom logic after the entry is deleted.
393 * Use this hook if you need to update caching after an entry is deleted.
394 *
395 * @since 5.4.1
396 *
397 * @param int $id The id of the entry that was destroyed.
398 * @param stdClass $entry The entry object.
399 */
400 do_action( 'frm_after_destroy_entry', $id, $entry );
401
402 return $result;
403 }
404
405 /**
406 * @param int $id
407 * @param mixed $value
408 * @param int|string $form_id
409 *
410 * @return false|int
411 */
412 public static function update_form( $id, $value, $form_id ) {
413 global $wpdb;
414 $form_id = isset( $value ) ? $form_id : null;
415 $result = $wpdb->update( $wpdb->prefix . 'frm_items', array( 'form_id' => $form_id ), array( 'id' => $id ) );
416
417 if ( $result ) {
418 self::clear_cache();
419 }
420
421 return $result;
422 }
423
424 /**
425 * Clear entry caching
426 * Called when an entry is changed
427 *
428 * @since 2.0.5
429 *
430 * @return void
431 */
432 public static function clear_cache() {
433 FrmDb::cache_delete_group( 'frm_entry' );
434 FrmDb::cache_delete_group( 'frm_item' );
435 FrmDb::cache_delete_group( 'frm_entry_meta' );
436 FrmDb::cache_delete_group( 'frm_item_meta' );
437 }
438
439 /**
440 * After switching to the wp_loaded hook for processing entries,
441 * we can no longer use 'name', but check it as a fallback
442 *
443 * @since 2.0.11
444 *
445 * @param array $values
446 * @param array|string $default
447 *
448 * @return string
449 */
450 public static function get_new_entry_name( $values, $default = '' ) {
451 $name = $values['item_name'] ?? $values['name'] ?? $default;
452 return is_array( $name ) ? reset( $name ) : $name;
453 }
454
455 /**
456 * If $entry is numeric, get the entry object
457 *
458 * @since 2.0.9
459 *
460 * @param int|object|string $entry By reference.
461 *
462 * @return void
463 */
464 public static function maybe_get_entry( &$entry ) {
465 if ( $entry && is_numeric( $entry ) ) {
466 $entry = self::getOne( $entry );
467 } elseif ( ! $entry || 'false' === $entry ) {
468 $entry = false;
469 }
470 }
471
472 /**
473 * @param int|string $id
474 * @param bool $meta
475 *
476 * @return object|null
477 */
478 public static function getOne( $id, $meta = false ) {
479 global $wpdb;
480
481 $query = "SELECT it.*, fr.name as form_name, fr.form_key as form_key FROM {$wpdb->prefix}frm_items it
482 LEFT OUTER JOIN {$wpdb->prefix}frm_forms fr ON it.form_id=fr.id WHERE ";
483
484 $query .= is_numeric( $id ) ? 'it.id=%d' : 'it.item_key=%s';
485 $query_args = array( $id );
486 $query = $wpdb->prepare( $query, $query_args ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
487
488 if ( ! $meta ) {
489 $entry = FrmDb::check_cache( $id . '_nometa', 'frm_entry', $query, 'get_row' );
490 self::prepare_entry( $entry );
491 return $entry;
492 }
493
494 $entry = FrmDb::check_cache( $id, 'frm_entry' );
495
496 if ( $entry !== false ) {
497 self::prepare_entry( $entry );
498 return $entry;
499 }
500
501 $entry = $wpdb->get_row( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
502 $entry = self::get_meta( $entry );
503 self::prepare_entry( $entry );
504
505 return $entry;
506 }
507
508 /**
509 * @since 4.02.03
510 *
511 * @param object $entry
512 *
513 * @return void
514 */
515 private static function prepare_entry( &$entry ) {
516 if ( ! $entry ) {
517 return;
518 }
519
520 FrmAppHelper::unserialize_or_decode( $entry->description );
521 // TODO: Remove slashes on input only, not output.
522 $entry = wp_unslash( $entry );
523 }
524
525 /**
526 * @since 4.02.03
527 *
528 * @param array $entries
529 *
530 * @return void
531 */
532 private static function prepare_entries( &$entries ) {
533 foreach ( $entries as $k => $entry ) {
534 self::prepare_entry( $entry );
535 $entries[ $k ] = $entry;
536 }
537 }
538
539 /**
540 * @param object|null $entry
541 *
542 * @return object|null
543 */
544 public static function get_meta( $entry ) {
545 if ( ! $entry ) {
546 return $entry;
547 }
548
549 global $wpdb;
550 $metas = FrmDb::get_results(
551 $wpdb->prefix . 'frm_item_metas m LEFT JOIN ' . $wpdb->prefix . 'frm_fields f ON m.field_id=f.id',
552 array(
553 'item_id' => $entry->id,
554 'field_id !' => 0,
555 ),
556 'field_id, meta_value, field_key, item_id, f.type'
557 );
558
559 $entry->metas = array();
560
561 $include_key = apply_filters( 'frm_include_meta_keys', false, array( 'form_id' => $entry->form_id ) );
562
563 foreach ( $metas as $meta_val ) {
564 FrmFieldsHelper::prepare_field_value( $meta_val->meta_value, $meta_val->type );
565
566 if ( (int) $meta_val->item_id === (int) $entry->id ) {
567 $entry->metas[ $meta_val->field_id ] = $meta_val->meta_value;
568
569 if ( $include_key ) {
570 $entry->metas[ $meta_val->field_key ] = $entry->metas[ $meta_val->field_id ];
571 }
572 continue;
573 }
574
575 // Include sub entries in an array
576 if ( ! isset( $entry->metas[ $meta_val->field_id ] ) ) {
577 $entry->metas[ $meta_val->field_id ] = array();
578 }
579
580 $entry->metas[ $meta_val->field_id ][] = $meta_val->meta_value;
581
582 unset( $meta_val );
583 }//end foreach
584 unset( $metas );
585
586 FrmDb::set_cache( $entry->id, $entry, 'frm_entry' );
587
588 return $entry;
589 }
590
591 /**
592 * @param string $id
593 *
594 * @return bool
595 */
596 public static function exists( $id ) {
597 if ( FrmDb::check_cache( $id, 'frm_entry' ) ) {
598 return true;
599 }
600
601 $where = is_numeric( $id ) ? array( 'id' => $id ) : array( 'item_key' => $id );
602 $id = FrmDb::get_var( 'frm_items', $where );
603
604 return $id && $id > 0;
605 }
606
607 /**
608 * @param array|string $where
609 * @param string $order_by
610 * @param string $limit
611 * @param bool $meta
612 * @param bool $inc_form
613 *
614 * @return array
615 */
616 public static function getAll( $where, $order_by = '', $limit = '', $meta = false, $inc_form = true ) {
617 global $wpdb;
618
619 $limit = FrmDb::esc_limit( $limit );
620 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . $limit . $inc_form;
621 $entries = wp_cache_get( $cache_key, 'frm_entry' );
622
623 if ( false === $entries ) {
624 $fields = 'it.id, it.item_key, it.name, it.ip, it.form_id, it.post_id, it.user_id, it.parent_item_id, it.updated_by, it.created_at, it.updated_at, it.is_draft, it.description'; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
625 $table = $wpdb->prefix . 'frm_items it ';
626
627 if ( $inc_form ) {
628 $fields = 'it.*, fr.name as form_name,fr.form_key as form_key';
629 $table .= 'LEFT OUTER JOIN ' . $wpdb->prefix . 'frm_forms fr ON it.form_id=fr.id ';
630 }
631
632 if ( preg_match( '/ meta_([0-9]+)/', $order_by, $order_matches ) ) {
633 $fields .= self::sort_by_field( $order_matches[1] );
634 unset( $order_matches );
635 }
636
637 // Prepare the query
638 $query = 'SELECT ' . $fields . ' FROM ' . $table . FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
639
640 $entries = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
641 unset( $query );
642
643 FrmDb::set_cache( $cache_key, $entries, 'frm_entry' );
644 }//end if
645
646 if ( ! $meta || ! $entries ) {
647 self::prepare_entries( $entries );
648 return $entries;
649 }
650 unset( $meta );
651
652 if ( ! is_array( $where ) && preg_match( '/^it\.form_id=\d+$/', $where ) ) {
653 $where = array( 'it.form_id' => substr( $where, 11 ) );
654 }
655
656 $meta_where = array( 'field_id !' => 0 );
657
658 // phpcs:ignore Universal.Operators.StrictComparisons
659 if ( $limit == '' && is_array( $where ) && count( $where ) === 1 && isset( $where['it.form_id'] ) ) {
660 $meta_where['fi.form_id'] = $where['it.form_id'];
661 } else {
662 $meta_where['item_id'] = array_keys( $entries );
663 }
664
665 $metas = FrmDb::get_results(
666 $wpdb->prefix . 'frm_item_metas it LEFT OUTER JOIN ' . $wpdb->prefix . 'frm_fields fi ON it.field_id = fi.id',
667 $meta_where,
668 'item_id, meta_value, field_id, field_key, form_id, fi.type'
669 );
670
671 unset( $meta_where );
672
673 if ( ! $metas ) {
674 self::prepare_entries( $entries );
675 return $entries;
676 }
677
678 foreach ( $metas as $m_key => $meta_val ) {
679 if ( ! isset( $entries[ $meta_val->item_id ] ) ) {
680 continue;
681 }
682
683 if ( ! isset( $entries[ $meta_val->item_id ]->metas ) ) {
684 $entries[ $meta_val->item_id ]->metas = array();
685 }
686
687 FrmFieldsHelper::prepare_field_value( $meta_val->meta_value, $meta_val->type );
688 $entries[ $meta_val->item_id ]->metas[ $meta_val->field_id ] = $meta_val->meta_value;
689 unset( $m_key, $meta_val );
690 }
691
692 if ( ! FrmAppHelper::prevent_caching() ) {
693 foreach ( $entries as $entry ) {
694 FrmDb::set_cache( $entry->id, $entry, 'frm_entry' );
695 unset( $entry );
696 }
697 }
698
699 self::prepare_entries( $entries );
700 return $entries;
701 }
702
703 /**
704 * @param int|string $field_id
705 *
706 * @return string
707 */
708 private static function sort_by_field( $field_id ) {
709 global $wpdb;
710 $field_id = (int) $field_id;
711 $field_options = FrmDb::get_var( 'frm_fields', array( 'id' => $field_id ), 'field_options' );
712 FrmAppHelper::unserialize_or_decode( $field_options );
713
714 if ( empty( $field_options['post_field'] ) ) {
715 $sort = ', (SELECT meta_value FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id = ' . $field_id . ' AND item_id = it.id) as meta_' . $field_id;
716 } else {
717 $sort = '';
718 }
719
720 return apply_filters( 'frm_handle_field_column_sort', $sort, $field_id, $field_options );
721 }
722
723 // Pagination Methods
724 /**
725 * @param array|int|string $where If int, use the form id.
726 *
727 * @return int|string
728 */
729 public static function getRecordCount( $where = '' ) {
730 global $wpdb;
731 $table_join = $wpdb->prefix . 'frm_items it JOIN ' . $wpdb->prefix . 'frm_forms fr ON it.form_id=fr.id';
732
733 if ( is_numeric( $where ) ) {
734 $table_join = 'frm_items';
735 $where = array( 'form_id' => $where );
736 }
737
738 if ( is_array( $where ) ) {
739 return FrmDb::get_count( $table_join, $where );
740 }
741
742 $cache_key = 'count_' . FrmAppHelper::maybe_json_encode( $where );
743 $query = 'SELECT COUNT(*) FROM ' . $table_join . FrmDb::prepend_and_or_where( ' WHERE ', $where );
744
745 return FrmDb::check_cache( $cache_key, 'frm_entry', $query, 'get_var' );
746 }
747
748 /**
749 * @param int|string $p_size
750 * @param array|int|string $where
751 *
752 * @return int
753 */
754 public static function getPageCount( $p_size, $where = '' ) {
755 $p_size = (int) $p_size;
756
757 if ( $p_size ) {
758 if ( ! is_numeric( $where ) ) {
759 $where = self::getRecordCount( $where );
760 }
761
762 return ceil( (int) $where / $p_size );
763 }
764
765 return 1;
766 }
767
768 /**
769 * Prepare the data before inserting it into the database
770 *
771 * @since 2.0.16
772 *
773 * @param array $values
774 * @param string $type
775 *
776 * @return array New values.
777 */
778 private static function before_insert_entry_in_database( &$values, $type ) {
779 self::sanitize_entry_post( $values );
780
781 if ( $type !== 'xml' ) {
782 $values = apply_filters( 'frm_pre_create_entry', $values );
783 }
784
785 return self::package_entry_data( $values );
786 }
787
788 /**
789 * Create an entry and perform after create actions
790 *
791 * @since 2.0.16
792 *
793 * @param array $values
794 * @param array $new_values
795 *
796 * @return bool|int Entry ID.
797 */
798 private static function continue_to_create_entry( $values, $new_values ) {
799 $entry_id = self::insert_entry_into_database( $new_values );
800
801 if ( ! $entry_id ) {
802 return false;
803 }
804
805 self::after_insert_entry_in_database( $values, $new_values, $entry_id );
806
807 return $entry_id;
808 }
809
810 /**
811 * Sanitize the POST values before we use them
812 *
813 * @since 2.0
814 *
815 * @param array $values The POST values by reference.
816 *
817 * @return void
818 */
819 public static function sanitize_entry_post( &$values ) {
820 $sanitize_method = array(
821 'form_id' => 'absint',
822 'frm_action' => 'sanitize_title',
823 'form_key' => 'sanitize_title',
824 'item_key' => 'sanitize_title',
825 'item_name' => 'sanitize_text_field',
826 'frm_saving_draft' => 'absint',
827 'is_draft' => 'absint',
828 'post_id' => 'absint',
829 'parent_item_id' => 'absint',
830 'created_at' => 'sanitize_text_field',
831 'updated_at' => 'sanitize_text_field',
832 );
833
834 FrmAppHelper::sanitize_request( $sanitize_method, $values );
835 }
836
837 /**
838 * Prepare the new values for inserting into the database
839 *
840 * @since 2.0.16
841 *
842 * @param array $values
843 *
844 * @return array New values.
845 */
846 private static function package_entry_data( &$values ) {
847 global $wpdb;
848
849 if ( ! isset( $values['item_key'] ) ) {
850 $values['item_key'] = '';
851 }
852
853 $item_name = self::get_new_entry_name( $values, $values['item_key'] );
854 $new_values = array(
855 'item_key' => FrmAppHelper::get_unique_key( $values['item_key'], $wpdb->prefix . 'frm_items', 'item_key' ),
856 'name' => FrmAppHelper::truncate( $item_name, 255, 1, '', true ),
857 'ip' => self::get_ip( $values ),
858 'is_draft' => self::get_is_draft_value( $values ),
859 'form_id' => (int) self::get_entry_value( $values, 'form_id', null ),
860 'post_id' => (int) self::get_entry_value( $values, 'post_id', 0 ),
861 'parent_item_id' => (int) self::get_entry_value( $values, 'parent_item_id', 0 ),
862 'created_at' => self::get_created_at( $values ),
863 'updated_at' => self::get_updated_at( $values ),
864 'description' => self::get_entry_description( $values ),
865 'user_id' => self::get_entry_user_id( $values ),
866 );
867
868 $new_values['updated_by'] = $values['updated_by'] ?? $new_values['user_id'];
869
870 return $new_values;
871 }
872
873 /**
874 * @param array $values
875 * @param string $name
876 * @param mixed $default
877 *
878 * @return mixed
879 */
880 private static function get_entry_value( $values, $name, $default ) {
881 return $values[ $name ] ?? $default;
882 }
883
884 /**
885 * Get the ip for a new entry.
886 * Allow the import to override the value.
887 *
888 * @since 2.03.10
889 *
890 * @param array $values
891 *
892 * @return string
893 */
894 private static function get_ip( $values ) {
895 if ( ! FrmAppHelper::ips_saved() ) {
896 return '';
897 }
898
899 $ip = FrmAppHelper::get_ip_address();
900
901 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
902 return self::get_entry_value( $values, 'ip', $ip );
903 }
904
905 return $ip;
906 }
907
908 /**
909 * Get the is_draft value for a new entry
910 *
911 * @since 2.0.16
912 *
913 * @param array $values
914 *
915 * @return int
916 */
917 private static function get_is_draft_value( $values ) {
918 if ( isset( $values['frm_saving_draft'] ) && FrmEntriesHelper::DRAFT_ENTRY_STATUS === (int) $values['frm_saving_draft'] ) {
919 return FrmEntriesHelper::DRAFT_ENTRY_STATUS;
920 }
921
922 return isset( $values['is_draft'] ) ? absint( $values['is_draft'] ) : FrmEntriesHelper::SUBMITTED_ENTRY_STATUS;
923 }
924
925 /**
926 * Get the created_at value for a new entry
927 *
928 * @since 2.0.16
929 *
930 * @param array $values
931 *
932 * @return string
933 */
934 private static function get_created_at( $values ) {
935 return self::get_entry_value( $values, 'created_at', current_time( 'mysql', 1 ) );
936 }
937
938 /**
939 * Get the updated_at value for a new entry
940 *
941 * @since 2.0.16
942 *
943 * @param array $values
944 *
945 * @return string
946 */
947 private static function get_updated_at( $values ) {
948 return $values['updated_at'] ?? self::get_created_at( $values );
949 }
950
951 /**
952 * Get the description value for a new entry
953 *
954 * @since 2.0.16
955 *
956 * @param array $values
957 *
958 * @return string
959 */
960 private static function get_entry_description( $values ) {
961 if ( ! empty( $values['description'] ) ) {
962 return FrmAppHelper::maybe_json_encode( $values['description'] );
963 }
964
965 return json_encode(
966 array(
967 'browser' => FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' ),
968 'referrer' => FrmAppHelper::get_server_value( 'HTTP_REFERER' ),
969 )
970 );
971 }
972
973 /**
974 * Get the user_id value for a new entry
975 *
976 * @since 2.0.16
977 *
978 * @param array $values
979 *
980 * @return int
981 */
982 private static function get_entry_user_id( $values ) {
983 if ( isset( $values['frm_user_id'] ) && ( is_numeric( $values['frm_user_id'] ) || FrmAppHelper::is_admin() ) ) {
984 return $values['frm_user_id'];
985 }
986
987 $current_user_id = get_current_user_id();
988 return $current_user_id ? $current_user_id : 0;
989 }
990
991 /**
992 * Insert new entry into the database
993 *
994 * @since 2.0.16
995 *
996 * @param array $new_values
997 *
998 * @return bool|int Entry ID.
999 */
1000 private static function insert_entry_into_database( $new_values ) {
1001 global $wpdb;
1002
1003 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_items', $new_values );
1004
1005 return $query_results ? $wpdb->insert_id : false;
1006 }
1007
1008 /**
1009 * Add the new entry to global $frm_vars
1010 *
1011 * @since 2.0.16
1012 *
1013 * @param int|string $entry_id
1014 *
1015 * @return void
1016 */
1017 private static function add_new_entry_to_frm_vars( $entry_id ) {
1018 global $frm_vars;
1019
1020 if ( ! isset( $frm_vars['saved_entries'] ) ) {
1021 $frm_vars['saved_entries'] = array();
1022 }
1023
1024 $frm_vars['saved_entries'][] = (int) $entry_id;
1025 }
1026
1027 /**
1028 * Add entry metas, if there are any
1029 *
1030 * @since 2.0.16
1031 *
1032 * @param array $values
1033 * @param int|string $entry_id
1034 *
1035 * @return void
1036 */
1037 private static function maybe_add_entry_metas( $values, $entry_id ) {
1038 if ( isset( $values['item_meta'] ) ) {
1039 FrmEntryMeta::update_entry_metas( $entry_id, $values['item_meta'] );
1040 self::maybe_add_unique_id_meta( $values, $entry_id );
1041 }
1042 self::maybe_add_captcha_meta( (int) $values['form_id'], (int) $entry_id );
1043 }
1044
1045 /**
1046 * @since 6.16.3
1047 *
1048 * @param array $values
1049 * @param int $entry_id
1050 *
1051 * @return void
1052 */
1053 private static function maybe_add_unique_id_meta( $values, $entry_id ) {
1054 if ( ! empty( $values['parent_form_id'] ) || empty( $values['unique_id'] ) || ! self::should_check_for_unique_id_match() ) {
1055 return;
1056 }
1057
1058 // This unique ID is inserted with JS on form submit.
1059 // It is used to check for duplicate entries.
1060 $unique_id = sanitize_key( $values['unique_id'] );
1061
1062 if ( ! $unique_id ) {
1063 return;
1064 }
1065
1066 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', compact( 'unique_id' ) );
1067 self::flag_new_unique_key( $unique_id );
1068 }
1069
1070 /**
1071 * @since 5.0.15
1072 *
1073 * @param int $form_id
1074 * @param int $entry_id
1075 *
1076 * @return void
1077 */
1078 private static function maybe_add_captcha_meta( $form_id, $entry_id ) {
1079 global $frm_vars;
1080
1081 if ( ! array_key_exists( 'captcha_scores', $frm_vars ) || ! array_key_exists( $form_id, $frm_vars['captcha_scores'] ) ) {
1082 return;
1083 }
1084
1085 $captcha_score_meta = array( 'captcha_score' => $frm_vars['captcha_scores'][ $form_id ] );
1086 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', maybe_serialize( $captcha_score_meta ) );
1087 }
1088
1089 /**
1090 * Trigger frm_after_create_entry hooks
1091 *
1092 * @since 2.0.16
1093 *
1094 * @param int $entry_id
1095 * @param array $values
1096 * @param array $new_values
1097 *
1098 * @return void
1099 */
1100 private static function after_entry_created_actions( $entry_id, $values, $new_values ) {
1101 // This is a child entry.
1102 $is_child = isset( $values['parent_nonce'] ) && ! empty( $values['parent_form_id'] ) && wp_verify_nonce( $values['parent_nonce'], 'parent' );
1103
1104 do_action( 'frm_after_create_entry', $entry_id, $new_values['form_id'], compact( 'is_child' ) );
1105 do_action( 'frm_after_create_entry_' . $new_values['form_id'], $entry_id, compact( 'is_child' ) );
1106
1107 if ( ! empty( $values['form_key'] ) ) {
1108 /**
1109 * @since 6.30
1110 *
1111 * @param int $entry_id
1112 * @param array $is_child
1113 */
1114 do_action( 'frm_after_create_entry_' . $values['form_key'], $entry_id, compact( 'is_child' ) );
1115 }
1116 }
1117
1118 /**
1119 * Actions to perform immediately after an entry is inserted in the frm_items database
1120 *
1121 * @since 2.0.16
1122 *
1123 * @param array $values
1124 * @param array $new_values
1125 * @param int $entry_id
1126 *
1127 * @return void
1128 */
1129 private static function after_insert_entry_in_database( $values, $new_values, $entry_id ) {
1130 self::add_new_entry_to_frm_vars( $entry_id );
1131
1132 self::maybe_add_entry_metas( $values, $entry_id );
1133
1134 self::clear_cache();
1135
1136 self::after_entry_created_actions( $entry_id, $values, $new_values );
1137 }
1138
1139 /**
1140 * Perform some actions right before updating an entry
1141 *
1142 * @since 2.0.16
1143 *
1144 * @param int|string $id
1145 * @param array $values
1146 * @param string $update_type
1147 *
1148 * @return bool Update.
1149 */
1150 private static function before_update_entry( $id, &$values, $update_type ) {
1151 $update = true;
1152
1153 global $frm_vars;
1154
1155 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
1156 if ( isset( $frm_vars['saved_entries'] ) && is_array( $frm_vars['saved_entries'] ) && in_array( (int) $id, $frm_vars['saved_entries'] ) ) {
1157 $update = false;
1158 }
1159
1160 if ( $update && $update_type !== 'xml' ) {
1161 $values = apply_filters( 'frm_pre_update_entry', $values, $id );
1162 }
1163
1164 return $update;
1165 }
1166
1167 /**
1168 * Package the entry data for updating
1169 *
1170 * @since 2.0.16
1171 *
1172 * @param int $id
1173 * @param array $values
1174 *
1175 * @return array New values.
1176 */
1177 private static function package_entry_to_update( $id, $values ) {
1178 global $wpdb;
1179
1180 $new_values = array(
1181 'name' => FrmAppHelper::truncate( self::get_new_entry_name( $values ), 255, 1, '', true ),
1182 'form_id' => (int) self::get_entry_value( $values, 'form_id', null ),
1183 'is_draft' => self::get_is_draft_value( $values ),
1184 'updated_at' => current_time( 'mysql', 1 ),
1185 'updated_by' => $values['updated_by'] ?? get_current_user_id(),
1186 );
1187
1188 if ( isset( $values['post_id'] ) ) {
1189 $new_values['post_id'] = (int) $values['post_id'];
1190 }
1191
1192 if ( isset( $values['item_key'] ) ) {
1193 $new_values['item_key'] = FrmAppHelper::get_unique_key( $values['item_key'], $wpdb->prefix . 'frm_items', 'item_key', $id );
1194 }
1195
1196 if ( isset( $values['parent_item_id'] ) ) {
1197 $new_values['parent_item_id'] = (int) $values['parent_item_id'];
1198 }
1199
1200 if ( isset( $values['frm_user_id'] ) && is_numeric( $values['frm_user_id'] ) ) {
1201 $new_values['user_id'] = $values['frm_user_id'];
1202 }
1203
1204 return apply_filters( 'frm_update_entry', $new_values, $id );
1205 }
1206
1207 /**
1208 * Perform some actions right after updating an entry
1209 *
1210 * @since 2.0.16
1211 *
1212 * @param bool|int $query_results
1213 * @param int|string $id
1214 * @param array $values
1215 * @param array $new_values
1216 *
1217 * @return void
1218 */
1219 private static function after_update_entry( $query_results, $id, $values, $new_values ) {
1220 if ( $query_results ) {
1221 self::clear_cache();
1222 }
1223
1224 global $frm_vars;
1225
1226 if ( ! isset( $frm_vars['saved_entries'] ) ) {
1227 $frm_vars['saved_entries'] = array();
1228 }
1229
1230 $frm_vars['saved_entries'][] = (int) $id;
1231
1232 if ( isset( $values['item_meta'] ) ) {
1233 FrmEntryMeta::update_entry_metas( $id, $values['item_meta'] );
1234 }
1235
1236 do_action( 'frm_after_update_entry', $id, $new_values['form_id'] );
1237 do_action( 'frm_after_update_entry_' . $new_values['form_id'], $id );
1238
1239 if ( ! empty( $values['form_key'] ) ) {
1240 /**
1241 * @since 6.30
1242 *
1243 * @param int $entry_id
1244 */
1245 do_action( 'frm_after_update_entry_' . $values['form_key'], $id );
1246 }
1247 }
1248
1249 /**
1250 * Create entry from an XML import
1251 * Certain actions aren't necessary when importing (like saving sub entries, checking for duplicates, etc.)
1252 *
1253 * @since 2.0.16
1254 *
1255 * @param array $values
1256 *
1257 * @return bool|int Entry ID.
1258 */
1259 public static function create_entry_from_xml( $values ) {
1260 return self::create_entry( $values, 'xml' );
1261 }
1262
1263 /**
1264 * Update entry from an XML import
1265 * Certain actions aren't necessary when importing (like saving sub entries and modifying other vals)
1266 *
1267 * @since 2.0.16
1268 *
1269 * @param int $id
1270 * @param array $values
1271 *
1272 * @return bool|int Updated.
1273 */
1274 public static function update_entry_from_xml( $id, $values ) {
1275 return self::update_entry( $id, $values, 'xml' );
1276 }
1277
1278 /**
1279 * @param string $key
1280 *
1281 * @return int entry_id
1282 */
1283 public static function get_id_by_key( $key ) {
1284 $entry_id = FrmDb::get_var( 'frm_items', array( 'item_key' => sanitize_title( $key ) ) );
1285 return (int) $entry_id;
1286 }
1287
1288 /**
1289 * Get entries count.
1290 *
1291 * @since 6.8
1292 *
1293 * @return int|string
1294 */
1295 public static function get_entries_count() {
1296 $args = array(
1297 'or' => 1,
1298 'parent_form_id' => null,
1299 'parent_form_id <' => 1,
1300 );
1301 return self::getRecordCount( $args );
1302 }
1303 }
1304