PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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 / FrmEntryMeta.php

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

661 lines 19.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 FrmEntryMeta {
7
8 /**
9 * @since 6.35 Added the $field parameter.
10 *
11 * @param int $entry_id
12 * @param int $field_id
13 * @param string $meta_key usually set to '' as this parameter is no longer used.
14 * @param mixed $meta_value
15 * @param stdClass|null $field The field $field_id belongs to, when the caller already has
16 * it loaded. Looked up here when it is not passed.
17 *
18 * @return int
19 */
20 public static function add_entry_meta( $entry_id, $field_id, $meta_key, $meta_value, $field = null ) {
21 global $wpdb;
22
23 if ( FrmAppHelper::is_empty_value( $meta_value ) ) {
24 // Don't save blank fields
25 return 0;
26 }
27
28 $new_values = array(
29 'meta_value' => is_array( $meta_value ) ? serialize( array_filter( $meta_value, 'FrmAppHelper::is_not_empty_value' ) ) : trim( $meta_value ),
30 'item_id' => $entry_id,
31 'field_id' => $field_id,
32 'created_at' => current_time( 'mysql', 1 ),
33 );
34
35 self::set_value_before_save( $new_values, $field );
36 $new_values = apply_filters( 'frm_add_entry_meta', $new_values );
37 $query_results = $wpdb->insert( $wpdb->prefix . 'frm_item_metas', $new_values );
38
39 if ( $query_results ) {
40 self::clear_cache();
41 wp_cache_delete( $entry_id, 'frm_entry' );
42 return $wpdb->insert_id;
43 }
44
45 return 0;
46 }
47
48 /**
49 * @since 6.35 Added the $field parameter.
50 *
51 * @param int $entry_id
52 * @param int $field_id
53 * @param string $meta_key Deprecated.
54 * @param array|string $meta_value
55 * @param stdClass|null $field The field $field_id belongs to, when the caller already has
56 * it loaded. Looked up here when it is not passed.
57 *
58 * @return bool|int
59 */
60 public static function update_entry_meta( $entry_id, $field_id, $meta_key, $meta_value, $field = null ) {
61 if ( ! $field_id ) {
62 return false;
63 }
64
65 global $wpdb;
66
67 $values = array(
68 'item_id' => $entry_id,
69 'field_id' => $field_id,
70 );
71 $where_values = $values;
72 $values['meta_value'] = $meta_value;
73 self::set_value_before_save( $values, $field );
74 $values = apply_filters( 'frm_update_entry_meta', $values );
75
76 if ( is_array( $values['meta_value'] ) ) {
77 $values['meta_value'] = array_filter( $values['meta_value'], 'FrmAppHelper::is_not_empty_value' );
78 }
79
80 $meta_value = maybe_serialize( $values['meta_value'] );
81
82 wp_cache_delete( $entry_id, 'frm_entry' );
83 self::clear_cache();
84
85 return $wpdb->update( $wpdb->prefix . 'frm_item_metas', array( 'meta_value' => $meta_value ), $where_values );
86 }
87
88 /**
89 * @since 3.0
90 * @since 6.35 Added the $field parameter.
91 *
92 * @param array $values
93 * @param stdClass|null $field The field for $values['field_id'], when the caller already has
94 * it loaded. Looked up here when it is not passed.
95 *
96 * @return void
97 */
98 private static function set_value_before_save( &$values, $field = null ) {
99 if ( ! is_object( $field ) ) {
100 $field = FrmField::getOne( $values['field_id'] );
101 }
102
103 if ( ! $field ) {
104 return;
105 }
106
107 $field_obj = FrmFieldFactory::get_field_object( $field );
108 $values['meta_value'] = $field_obj->set_value_before_save( $values['meta_value'] );
109 }
110
111 /**
112 * @since 3.0
113 *
114 * @param array $atts
115 * @param mixed $value
116 *
117 * @return void
118 */
119 private static function get_value_to_save( $atts, &$value ) {
120 if ( is_object( $atts['field'] ) ) {
121 $field_obj = FrmFieldFactory::get_field_object( $atts['field'] );
122 $value = $field_obj->get_value_to_save(
123 $value,
124 array(
125 'entry_id' => $atts['entry_id'],
126 'field_id' => $atts['field_id'],
127 )
128 );
129 }
130
131 $value = apply_filters( 'frm_prepare_data_before_db', $value, $atts['field_id'], $atts['entry_id'], array( 'field' => $atts['field'] ) );
132 }
133
134 /**
135 * @param int|string $entry_id
136 * @param array $values Either indexed by field ID or field key.
137 *
138 * @return void
139 */
140 public static function update_entry_metas( $entry_id, $values ) {
141 global $wpdb;
142
143 $previous_field_ids = FrmDb::get_col(
144 'frm_item_metas',
145 array(
146 'item_id' => $entry_id,
147 'field_id !' => 0,
148 ),
149 'field_id'
150 );
151
152 $values_indexed_by_field_id = array();
153
154 foreach ( $values as $field_id_or_key => $meta_value ) {
155 $field_id = $field_id_or_key;
156 $field = null;
157
158 if ( $field_id_or_key ) {
159 $field = FrmField::getOne( $field_id_or_key );
160
161 if ( is_object( $field ) ) {
162 $field_id = $field->id;
163 }
164 }
165
166 $values_indexed_by_field_id[ $field_id ] = $meta_value;
167
168 self::get_value_to_save( compact( 'field', 'field_id', 'entry_id' ), $meta_value );
169
170 if ( ! $previous_field_ids || ! in_array( $field_id, $previous_field_ids, true ) ) {
171 // If value does not exist, then create it
172 self::add_entry_meta( $entry_id, $field_id, '', $meta_value, $field );
173 continue;
174 }
175
176 if ( $meta_value === array() || ( ! is_array( $meta_value ) && trim( $meta_value ) === '' ) ) {
177 // Remove blank fields.
178 unset( $values_indexed_by_field_id[ $field_id ] );
179 } else {
180 // If value exists, then update it
181 self::update_entry_meta( $entry_id, $field_id, '', $meta_value, $field );
182 }
183 }//end foreach
184
185 if ( ! $previous_field_ids ) {
186 return;
187 }
188
189 $field_ids_to_remove = array_diff( $previous_field_ids, array_keys( $values_indexed_by_field_id ) );
190
191 if ( ! $field_ids_to_remove ) {
192 return;
193 }
194
195 // Prepare the query
196 $where = array(
197 'item_id' => $entry_id,
198 'field_id' => $field_ids_to_remove,
199 );
200 FrmDb::get_where_clause_and_values( $where );
201
202 // Delete any leftovers
203 $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas ' . $where['where'], $where['values'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, SlevomatCodingStandard.Files.LineLength.LineTooLong
204 self::clear_cache();
205 }
206
207 /**
208 * @param int $old_id
209 * @param int $new_id
210 *
211 * @return void
212 */
213 public static function duplicate_entry_metas( $old_id, $new_id ) {
214 $metas = self::get_entry_meta_info( $old_id );
215
216 /**
217 * Allows changing entry duplicate values before save.
218 *
219 * @since 5.4.4
220 *
221 * @param array $metas The list of entry meta values.
222 */
223 $metas = apply_filters( 'frm_before_duplicate_entry_values', $metas );
224
225 foreach ( $metas as $meta ) {
226 self::add_entry_meta( $new_id, $meta->field_id, '', $meta->meta_value );
227 unset( $meta );
228 }
229 self::clear_cache();
230 }
231
232 /**
233 * @param int $entry_id
234 * @param int $field_id
235 *
236 * @return false|int
237 */
238 public static function delete_entry_meta( $entry_id, $field_id ) {
239 global $wpdb;
240 self::clear_cache();
241
242 return $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}frm_item_metas WHERE field_id=%d AND item_id=%d", $field_id, $entry_id ) );
243 }
244
245 /**
246 * Clear entry meta caching
247 * Called when a meta is added or changed
248 *
249 * @since 2.0.5
250 *
251 * @return void
252 */
253 public static function clear_cache() {
254 FrmDb::cache_delete_group( 'frm_entry_meta' );
255 FrmDb::cache_delete_group( 'frm_item_meta' );
256 }
257
258 /**
259 * @since 2.0.9
260 *
261 * @param stdClass $entry
262 * @param int|string $field_id
263 *
264 * @return mixed
265 */
266 public static function get_meta_value( $entry, $field_id ) {
267 if ( isset( $entry->metas ) ) {
268 return $entry->metas[ $field_id ] ?? false;
269 }
270 return self::get_entry_meta_by_field( $entry->id, $field_id );
271 }
272
273 /**
274 * @param int|object|string $entry_id
275 * @param int|string $field_id This function supports field keys as field id.
276 *
277 * @return mixed
278 */
279 public static function get_entry_meta_by_field( $entry_id, $field_id ) {
280 global $wpdb;
281
282 if ( is_object( $entry_id ) ) {
283 $entry = $entry_id;
284 $entry_id = $entry->id;
285 $cached = $entry;
286 } else {
287 $entry_id = (int) $entry_id;
288 $cached = FrmDb::check_cache( $entry_id, 'frm_entry' );
289 }
290
291 if ( $cached && isset( $cached->metas ) && isset( $cached->metas[ $field_id ] ) ) {
292 $result = $cached->metas[ $field_id ];
293 return wp_unslash( $result );
294 }
295
296 $get_table = $wpdb->prefix . 'frm_item_metas';
297 $query = array( 'item_id' => $entry_id );
298
299 if ( is_numeric( $field_id ) ) {
300 // Query by field ID.
301 $query['field_id'] = $field_id;
302 } else {
303 // Query by field key.
304 $get_table .= ' it JOIN ' . $wpdb->prefix . 'frm_fields fi ON it.field_id=fi.id';
305 $query['fi.field_key'] = $field_id;
306 }
307
308 $result = FrmDb::get_var( $get_table, $query, 'meta_value' );
309 $field_type = FrmField::get_type( $field_id );
310 FrmFieldsHelper::prepare_field_value( $result, $field_type );
311
312 return wp_unslash( $result );
313 }
314
315 /**
316 * @param int|string $field_id
317 * @param string $order
318 * @param string $limit
319 * @param array $args
320 *
321 * @return array
322 */
323 public static function get_entry_metas_for_field( $field_id, $order = '', $limit = '', $args = array() ) {
324 $defaults = array(
325 'value' => false,
326 'unique' => false,
327 'stripslashes' => true,
328 'is_draft' => false,
329 );
330 $args = wp_parse_args( $args, $defaults );
331 $query = array();
332 self::meta_field_query( $field_id, $order, $limit, $args, $query );
333 $query = implode( ' ', $query );
334 $cache_key = 'entry_metas_for_field_' . $field_id . $order . $limit . FrmAppHelper::maybe_json_encode( $args );
335 $values = FrmDb::check_cache( $cache_key, 'frm_entry', $query, 'get_col' );
336
337 if ( ! $args['stripslashes'] ) {
338 return $values;
339 }
340
341 foreach ( $values as $k => $v ) {
342 FrmAppHelper::unserialize_or_decode( $v );
343 $values[ $k ] = $v;
344 unset( $k, $v );
345 }
346
347 return wp_unslash( $values );
348 }
349
350 /**
351 * @param int|string $field_id
352 * @param string $order
353 * @param string $limit
354 * @param array $args
355 * @param array $query
356 *
357 * @return void
358 */
359 private static function meta_field_query( $field_id, $order, $limit, $args, array &$query ) {
360 global $wpdb;
361 $query[] = 'SELECT';
362 $query[] = $args['unique'] ? 'DISTINCT(em.meta_value)' : 'em.meta_value';
363 $query[] = 'FROM ' . $wpdb->prefix . 'frm_item_metas em ';
364
365 if ( ! $args['is_draft'] ) {
366 $query[] = 'INNER JOIN ' . $wpdb->prefix . 'frm_items e ON (e.id=em.item_id)';
367 }
368
369 if ( is_numeric( $field_id ) ) {
370 $query[] = $wpdb->prepare( 'WHERE em.field_id=%d', $field_id );
371 } else {
372 $query[] = $wpdb->prepare( 'LEFT JOIN ' . $wpdb->prefix . 'frm_fields fi ON (em.field_id = fi.id) WHERE fi.field_key=%s', $field_id );
373 }
374
375 if ( ! $args['is_draft'] ) {
376 $query[] = 'AND e.is_draft=0';
377 }
378
379 if ( $args['value'] ) {
380 $query[] = $wpdb->prepare( ' AND meta_value=%s', $args['value'] );
381 }
382
383 $query[] = $order . $limit;
384 }
385
386 /**
387 * @param int|string $entry_id
388 *
389 * @return array
390 */
391 public static function get_entry_meta_info( $entry_id ) {
392 return FrmDb::get_results( 'frm_item_metas', array( 'item_id' => $entry_id ) );
393 }
394
395 /**
396 * @param array $where
397 * @param string $order_by
398 * @param string $limit
399 * @param bool $stripslashes
400 *
401 * @return mixed
402 */
403 public static function getAll( $where = array(), $order_by = '', $limit = '', $stripslashes = false ) {
404 global $wpdb;
405 // phpcs:disable Generic.WhiteSpace.ScopeIndent
406 $query = 'SELECT it.*, fi.type as field_type, fi.field_key as field_key,
407 fi.required as required, fi.form_id as field_form_id, fi.name as field_name, fi.options as fi_options
408 FROM ' . $wpdb->prefix . 'frm_item_metas it LEFT OUTER JOIN ' . $wpdb->prefix . 'frm_fields fi ON it.field_id=fi.id' .
409 FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
410 // phpcs:enable Generic.WhiteSpace.ScopeIndent
411
412 $cache_key = 'all_' . FrmAppHelper::maybe_json_encode( $where ) . $order_by . $limit;
413 $results = FrmDb::check_cache( $cache_key, 'frm_entry', $query, $limit === ' LIMIT 1' ? 'get_row' : 'get_results' );
414
415 if ( ! $results || ! $stripslashes ) {
416 return $results;
417 }
418
419 foreach ( $results as $k => $result ) {
420 FrmAppHelper::unserialize_or_decode( $result->meta_value );
421 $results[ $k ]->meta_value = wp_unslash( $result->meta_value );
422 unset( $k, $result );
423 }
424
425 return $results;
426 }
427
428 /**
429 * @param array|string $where
430 * @param string $order_by
431 * @param string $limit
432 * @param bool $unique
433 * @param array $args
434 *
435 * @return array|string|null
436 */
437 public static function getEntryIds( $where = array(), $order_by = '', $limit = '', $unique = true, $args = array() ) {
438 $defaults = array(
439 'is_draft' => false,
440 'user_id' => '',
441 'group_by' => '',
442 );
443 $args = wp_parse_args( $args, $defaults );
444 $query = array();
445 self::get_ids_query( $where, $order_by, $limit, $unique, $args, $query );
446 $query = implode( ' ', $query );
447 $cache_key = 'ids_' . FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'u' . $unique . FrmAppHelper::maybe_json_encode( $args );
448 $type = 'get_' . ( ' LIMIT 1' === $limit ? 'var' : 'col' );
449 return FrmDb::check_cache( $cache_key, 'frm_entry', $query, $type );
450 }
451
452 /**
453 * Given a query including a form id and its child form ids, output an array of matching entry ids
454 * If a child entry id is matched, its parent will be returned in its place
455 *
456 * @param array $query
457 * @param array $args
458 *
459 * @return array
460 */
461 public static function get_top_level_entry_ids( $query, $args ) {
462 $args['return_parent_id_if_0_return_id'] = true;
463 return self::getEntryIds( $query, '', '', true, $args );
464 }
465
466 /**
467 * Returns true if the where clause refers to a field table column that is not form_id. It also updates
468 * the where clause to refer to the entry table for form_id if fields table should not be joined.
469 *
470 * @since 6.16.1
471 *
472 * @param array|string $where
473 *
474 * @return bool
475 */
476 private static function should_join_fields_table( &$where ) {
477 if ( is_string( $where ) ) {
478 if ( preg_match( '/\bfi\.(?!form_id)\w+/i', $where ) ) {
479 return true;
480 }
481
482 $where = str_ireplace( 'fi.form_id', 'e.form_id', $where );
483 return false;
484 }
485
486 $where_fields = array_keys( $where );
487
488 foreach ( $where_fields as $where_field ) {
489 if ( str_starts_with( $where_field, 'fi.' ) && 'fi.form_id' !== $where_field ) {
490 return true;
491 }
492 }
493
494 if ( isset( $where['fi.form_id'] ) ) {
495 $where['e.form_id'] = $where['fi.form_id'];
496 unset( $where['fi.form_id'] );
497 }
498
499 return false;
500 }
501
502 /**
503 * @param array|string $where
504 * @param string $order_by
505 * @param string $limit
506 * @param bool $unique
507 * @param array $args
508 * @param array $query
509 *
510 * @return void
511 */
512 private static function get_ids_query( $where, $order_by, $limit, $unique, $args, array &$query ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh, SlevomatCodingStandard.Files.LineLength.LineTooLong
513 global $wpdb;
514 $query[] = 'SELECT';
515 $defaults = array(
516 'return_parent_id' => false,
517 'return_parent_id_if_0_return_id' => false,
518 );
519 $args = array_merge( $defaults, $args );
520
521 if ( $unique ) {
522 $query[] = 'DISTINCT';
523 }
524
525 if ( $args['return_parent_id_if_0_return_id'] ) {
526 $query[] = 'IF ( e.parent_item_id = 0, it.item_id, e.parent_item_id )';
527 } elseif ( $args['return_parent_id'] ) {
528 $query[] = 'e.parent_item_id';
529 } else {
530 $query[] = 'it.item_id';
531 }
532
533 $from = 'FROM ' . $wpdb->prefix . 'frm_item_metas it';
534
535 if ( self::should_join_fields_table( $where ) || self::should_join_fields_table( $order_by ) ) {
536 $from .= ' LEFT OUTER JOIN ' . $wpdb->prefix . 'frm_fields fi ON it.field_id=fi.id';
537 }
538
539 $query[] = $from;
540 $query[] = 'INNER JOIN ' . $wpdb->prefix . 'frm_items e ON (e.id=it.item_id)';
541
542 if ( is_array( $where ) ) {
543 if ( ! $args['is_draft'] ) {
544 $where['e.is_draft'] = 0;
545 } elseif ( is_numeric( $args['is_draft'] ) ) {
546 $where['e.is_draft'] = class_exists( 'FrmAbandonmentHooksController', false ) ? absint( $args['is_draft'] ) : 1;
547 } elseif ( 'both' === $args['is_draft'] && class_exists( 'FrmAbandonmentHooksController', false ) ) {
548 $where['e.is_draft'] = array( 0, 1 );
549 } elseif ( str_contains( $args['is_draft'], ',' ) ) {
550 $is_draft = array_reduce(
551 explode( ',', $args['is_draft'] ),
552 function ( $total, $current ) {
553 if ( is_numeric( $current ) ) {
554 $total[] = absint( $current );
555 }
556 return $total;
557 },
558 array()
559 );
560
561 if ( $is_draft ) {
562 $where['e.is_draft'] = $is_draft;
563 }
564 }//end if
565
566 if ( ! empty( $args['user_id'] ) ) {
567 $where['e.user_id'] = $args['user_id'];
568 }
569
570 $query[] = FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
571
572 if ( $args['group_by'] ) {
573 $query[] = ' GROUP BY ' . sanitize_text_field( $args['group_by'] );
574 }
575
576 return;
577 }//end if
578
579 $draft_where = '';
580 $user_where = '';
581
582 if ( ! $args['is_draft'] ) {
583 $draft_where = $wpdb->prepare( ' AND e.is_draft=%d', 0 );
584 } elseif ( $args['is_draft'] == 1 ) { // phpcs:ignore Universal.Operators.StrictComparisons
585 $draft_where = $wpdb->prepare( ' AND e.is_draft=%d', 1 );
586 }
587
588 if ( ! empty( $args['user_id'] ) ) {
589 $user_where = $wpdb->prepare( ' AND e.user_id=%d', $args['user_id'] );
590 }
591
592 if ( str_contains( $where, ' GROUP BY ' ) ) {
593 // Don't inject WHERE filtering after GROUP BY
594 $parts = explode( ' GROUP BY ', $where );
595 $where = $parts[0];
596 $where .= $draft_where . $user_where;
597 $where .= ' GROUP BY ' . $parts[1];
598 } else {
599 $where .= $draft_where . $user_where;
600 }
601
602 // The query has already been prepared
603 $query[] = FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
604 }
605
606 /**
607 * @param array|string $search
608 * @param int|string $field_id
609 * @param string $operator
610 *
611 * @return array
612 */
613 public static function search_entry_metas( $search, $field_id, $operator ) {
614 $cache_key = 'search_' . FrmAppHelper::maybe_json_encode( $search ) . $field_id . $operator;
615 $results = wp_cache_get( $cache_key, 'frm_entry' );
616
617 if ( false !== $results ) {
618 return $results;
619 }
620
621 global $wpdb;
622
623 if ( is_array( $search ) ) {
624 $where = '';
625
626 foreach ( $search as $field => $value ) {
627 if ( $value <= 0 || ! in_array( $field, array( 'year', 'month', 'day' ), true ) ) {
628 continue;
629 }
630
631 switch ( $field ) {
632 case 'year':
633 $value = '%' . $value;
634 break;
635 case 'month':
636 $value .= '%';
637 break;
638 case 'day':
639 $value = '%' . $value . '%';
640 }
641
642 $where .= $wpdb->prepare( ' meta_value ' . $operator . ' %s and', $value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
643 }
644
645 $where .= $wpdb->prepare( ' field_id=%d', $field_id );
646 $query = 'SELECT DISTINCT item_id FROM ' . $wpdb->prefix . 'frm_item_metas' . FrmDb::prepend_and_or_where( ' WHERE ', $where );
647 } else {
648 if ( $operator === 'LIKE' ) {
649 $search = '%' . $search . '%';
650 }
651
652 $query = $wpdb->prepare( "SELECT DISTINCT item_id FROM {$wpdb->prefix}frm_item_metas WHERE meta_value {$operator} %s and field_id = %d", $search, $field_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, SlevomatCodingStandard.Files.LineLength.LineTooLong
653 }//end if
654
655 $results = $wpdb->get_col( $query, 0 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
656 FrmDb::set_cache( $cache_key, $results, 'frm_entry' );
657
658 return $results;
659 }
660 }
661