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

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