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

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