PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.34
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.34
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 6.5.4 All 140 releases
formidable / classes / models / FrmDb.php

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

831 lines 19.9 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 FrmDb {
7
8 /**
9 * Change array into format $wpdb->prepare can use
10 *
11 * @param array $args
12 * @param string $starts_with
13 *
14 * @return void
15 */
16 public static function get_where_clause_and_values( &$args, $starts_with = ' WHERE ' ) {
17 if ( ! $args ) {
18 // Add an arg to prevent prepare from failing
19 $args = array(
20 'where' => $starts_with . '1=%d',
21 'values' => array( 1 ),
22 );
23
24 return;
25 }
26
27 $where = '';
28 $values = array();
29
30 if ( is_array( $args ) ) {
31 $base_where = $starts_with;
32 self::parse_where_from_array( $args, $base_where, $where, $values );
33 }
34
35 $args = compact( 'where', 'values' );
36 }
37
38 /**
39 * @param array $args
40 * @param string $base_where
41 * @param string $where
42 * @param array $values
43 *
44 * @return void
45 */
46 public static function parse_where_from_array( $args, $base_where, &$where, &$values ) {
47 $condition = ' AND';
48
49 if ( isset( $args['or'] ) ) {
50 $condition = ' OR';
51 unset( $args['or'] );
52 }
53
54 foreach ( $args as $key => $value ) {
55 $where .= $where ? $condition : $base_where;
56 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
57 $array_inc_null = ! is_numeric( $key ) && is_array( $value ) && in_array( null, $value );
58
59 if ( is_numeric( $key ) || $array_inc_null ) {
60 $where .= ' ( ';
61 $nested_where = '';
62
63 if ( $array_inc_null ) {
64 foreach ( $value as $val ) {
65 $parse_where = array(
66 $key => $val,
67 'or' => 1,
68 );
69 self::parse_where_from_array( $parse_where, '', $nested_where, $values );
70 }
71 } else {
72 self::parse_where_from_array( $value, '', $nested_where, $values );
73 }
74
75 $where .= $nested_where;
76 $where .= ' ) ';
77 } else {
78 self::interpret_array_to_sql( $key, $value, $where, $values );
79 }//end if
80 }//end foreach
81 }
82
83 /**
84 * @param string $key
85 * @param array|string|null $value
86 * @param string $where
87 * @param array $values
88 *
89 * @return void
90 */
91 private static function interpret_array_to_sql( $key, $value, &$where, &$values ) {
92 $key = trim( $key );
93
94 if ( str_contains( $key, 'created_at' ) || str_contains( $key, 'updated_at' ) ) {
95 $k = explode( ' ', $key );
96 $where .= ' CAST(' . reset( $k ) . ' as CHAR) ' . str_replace( reset( $k ), '', $key );
97 } else {
98 $where .= ' ' . $key;
99 }
100
101 $lowercase_key = explode( ' ', strtolower( $key ) );
102 $lowercase_key = end( $lowercase_key );
103
104 if ( is_array( $value ) ) {
105 // Translate array of values to "in"
106 if ( str_contains( $lowercase_key, 'like' ) ) {
107 $where = preg_replace( '/' . $key . '$/', '', $where );
108 $where .= '(';
109 $start = true;
110
111 foreach ( $value as $v ) {
112 if ( ! $start ) {
113 $where .= ' OR ';
114 }
115
116 $start = false;
117 $where .= $key . ' %s';
118 $values[] = '%' . self::esc_like( $v ) . '%';
119 }
120
121 $where .= ')';
122 } elseif ( $value ) {
123 $where .= ' in (' . self::prepare_array_values( $value, '%s' ) . ')';
124 $values = array_merge( $values, $value );
125 }
126 } elseif ( str_contains( $lowercase_key, 'like' ) ) {
127 /**
128 * Allow string to start or end with the value
129 * If the key is like% then skip the first % for starts with
130 * If the key is %like then skip the last % for ends with
131 */
132 $start = '%';
133 $end = '%';
134
135 if ( $lowercase_key === 'like%' ) {
136 $start = '';
137 $where = rtrim( $where, '%' );
138 } elseif ( $lowercase_key === '%like' ) {
139 $end = '';
140 $where = rtrim( rtrim( $where, '%like' ), '%LIKE' );
141 $where .= 'like';
142 }
143
144 $where .= ' %s';
145 $values[] = $start . self::esc_like( $value ) . $end;
146 } elseif ( $value === null ) {
147 $where .= ' IS NULL';
148 } else {
149 // allow a - to prevent = from being added
150 if ( str_ends_with( $key, '-' ) ) {
151 $where = rtrim( $where, '-' );
152 } else {
153 $where .= '=';
154 }
155
156 self::add_query_placeholder( $key, $value, $where );
157
158 $values[] = $value;
159 }//end if
160 }
161
162 /**
163 * Add %d, or %s to query
164 *
165 * @since 2.02.05
166 *
167 * @param string $key
168 * @param int|string $value
169 * @param string $where
170 *
171 * @return void
172 */
173 private static function add_query_placeholder( $key, $value, &$where ) {
174 if ( is_numeric( $value ) && ( ! str_contains( $key, 'meta_value' ) || str_contains( $key, '+0' ) ) ) {
175 // Switch string to number.
176 $value = $value + 0;
177 $where .= is_float( $value ) ? '%f' : '%d';
178 } else {
179 $where .= '%s';
180 }
181 }
182
183 /**
184 * @param string $table
185 * @param array $where
186 * @param array $args
187 *
188 * @return int
189 */
190 public static function get_count( $table, $where = array(), $args = array() ) {
191 $count = self::get_var( $table, $where, 'COUNT(*)', $args );
192 return (int) $count;
193 }
194
195 /**
196 * @param string $table
197 * @param array $where
198 * @param string $field
199 * @param array $args
200 * @param string $limit
201 * @param string $type
202 *
203 * @return array|object|string|null
204 */
205 public static function get_var( $table, $where = array(), $field = 'id', $args = array(), $limit = '', $type = 'var' ) {
206 $group = '';
207 self::get_group_and_table_name( $table, $group );
208 self::convert_options_to_array( $args, '', $limit );
209
210 if ( $type === 'var' && ! isset( $args['limit'] ) ) {
211 $args['limit'] = 1;
212 }
213
214 $query = self::generate_query_string_from_pieces( $field, $table, $where, $args );
215 $cache_key = self::generate_cache_key( $where, $args, $field, $type );
216
217 return self::check_cache( $cache_key, $group, $query, 'get_' . $type );
218 }
219
220 /**
221 * Generate a cache key from the where query, field, type, and other arguments
222 *
223 * @since 2.03.07
224 *
225 * @param array $where
226 * @param array $args
227 * @param string $field
228 * @param string $type
229 *
230 * @return string
231 */
232 public static function generate_cache_key( $where, $args, $field, $type ) {
233 $cache_key = '';
234 $where = FrmAppHelper::array_flatten( $where );
235
236 foreach ( $where as $key => $value ) {
237 $cache_key .= $key . '_' . $value;
238 }
239
240 $cache_key .= implode( '_', $args ) . $field . '_' . $type;
241
242 return str_replace( array( ' ', ',' ), '_', $cache_key );
243 }
244
245 /**
246 * @param string $table
247 * @param array $where
248 * @param string $field
249 * @param array $args
250 * @param string $limit
251 *
252 * @return array
253 */
254 public static function get_col( $table, $where = array(), $field = 'id', $args = array(), $limit = '' ) {
255 return self::get_var( $table, $where, $field, $args, $limit, 'col' );
256 }
257
258 /**
259 * @since 2.0
260 *
261 * @param string $table
262 * @param array $where
263 * @param string $fields
264 * @param array $args
265 *
266 * @return mixed
267 */
268 public static function get_row( $table, $where = array(), $fields = '*', $args = array() ) {
269 $args['limit'] = 1;
270 return self::get_var( $table, $where, $fields, $args, '', 'row' );
271 }
272
273 /**
274 * Prepare a key/value array before DB call
275 *
276 * @since 2.0
277 *
278 * @param string $table
279 * @param array $where
280 * @param string $fields
281 * @param array $args
282 *
283 * @return array
284 */
285 public static function get_results( $table, $where = array(), $fields = '*', $args = array() ) {
286 return self::get_var( $table, $where, $fields, $args, '', 'results' );
287 }
288
289 /**
290 * Check for like, not like, in, not in, =, !=, >, <, <=, >=
291 * Return a value to append to the where array key
292 *
293 * @param string $where_is
294 *
295 * @return string
296 */
297 public static function append_where_is( $where_is ) {
298 $switch_to = array(
299 '=' => '',
300 '!=' => '!',
301 '<=' => '<',
302 '>=' => '>',
303 'like' => 'like',
304 'not like' => 'not like',
305 'in' => '',
306 'not in' => 'not',
307 'like%' => 'like%',
308 '%like' => '%like',
309 );
310
311 $where_is = strtolower( $where_is );
312
313 if ( isset( $switch_to[ $where_is ] ) ) {
314 return ' ' . $switch_to[ $where_is ];
315 }
316
317 // > and < need a little more work since we don't want them switched to >= and <=
318 if ( $where_is === '>' || $where_is === '<' ) {
319 // The - indicates that the = should not be added later.
320 return ' ' . $where_is . '-';
321 }
322
323 // fallback to = if the query is none of these
324 return '';
325 }
326
327 /**
328 * Get 'frm_forms' from wp_frm_forms or a longer table param that includes a join
329 * Also add the wpdb->prefix to the table if it's missing
330 *
331 * @param string $table
332 * @param string $group
333 *
334 * @return void
335 */
336 private static function get_group_and_table_name( &$table, &$group ) {
337 global $wpdb;
338
339 $table_parts = explode( ' ', $table );
340 $group = reset( $table_parts );
341 self::maybe_remove_prefix( $wpdb->prefix, $group );
342
343 $prefix = $wpdb->base_prefix;
344 self::maybe_remove_prefix( $prefix, $group );
345
346 if ( $group === $table ) {
347 $table = $wpdb->prefix . $table;
348 }
349
350 // Switch to singular group name
351 $group = rtrim( $group, 's' );
352 }
353
354 /**
355 * Only remove the db prefix when at the beginning.
356 *
357 * @since 4.04.02
358 *
359 * @param string $prefix Prefix to remove.
360 * @param string $name Name to strip prefix from, passed by reference.
361 *
362 * @return void
363 */
364 private static function maybe_remove_prefix( $prefix, &$name ) {
365 if ( str_starts_with( $name, $prefix ) ) {
366 $name = substr( $name, strlen( $prefix ) );
367 }
368 }
369
370 /**
371 * @param array|string $args
372 * @param string $order_by
373 * @param int|string $limit
374 *
375 * @return void
376 */
377 private static function convert_options_to_array( &$args, $order_by = '', $limit = '' ) {
378 if ( ! is_array( $args ) ) {
379 $args = array( 'order_by' => $args );
380 }
381
382 if ( $order_by ) {
383 $args['order_by'] = $order_by;
384 }
385
386 if ( $limit ) {
387 $args['limit'] = $limit;
388 }
389
390 $temp_args = $args;
391
392 foreach ( $temp_args as $k => $v ) {
393 // phpcs:ignore Universal.Operators.StrictComparisons
394 if ( $v == '' ) {
395 unset( $args[ $k ] );
396 continue;
397 }
398
399 $db_name = strtoupper( str_replace( '_', ' ', $k ) );
400
401 if ( ! str_contains( $v, $db_name ) ) {
402 $args[ $k ] = $db_name . ' ' . $v;
403 }
404 }
405
406 // Make sure LIMIT is the last argument
407 if ( ! isset( $args['order_by'] ) || ! isset( $args['limit'] ) ) {
408 return;
409 }
410
411 $temp_limit = $args['limit'];
412 unset( $args['limit'] );
413 $args['limit'] = $temp_limit;
414 }
415
416 /**
417 * Get the associative array results for the given columns, table, and where query
418 *
419 * @since 2.02.05
420 *
421 * @param string $columns
422 * @param string $table
423 * @param array $where
424 *
425 * @return mixed
426 */
427 public static function get_associative_array_results( $columns, $table, $where ) {
428 $group = '';
429 self::get_group_and_table_name( $table, $group );
430
431 $query = self::generate_query_string_from_pieces( $columns, $table, $where );
432 $cache_key = str_replace( array( ' ', ',' ), '_', trim( implode( '_', FrmAppHelper::array_flatten( $where ) ) . $columns . '_results_ARRAY_A', ' WHERE' ) );
433
434 return self::check_cache( $cache_key, $group, $query, 'get_associative_results' );
435 }
436
437 /**
438 * Combine the pieces of a query to form a full, prepared query
439 *
440 * @since 2.02.05
441 *
442 * @param string $columns
443 * @param string $table
444 * @param mixed $where
445 * @param array $args
446 *
447 * @return string
448 */
449 private static function generate_query_string_from_pieces( $columns, $table, $where, $args = array() ) {
450 $query = 'SELECT ' . $columns . ' FROM ' . $table;
451
452 self::esc_query_args( $args );
453
454 if ( ! is_array( $where ) && $where ) {
455 return $query;
456 }
457
458 self::get_where_clause_and_values( $where );
459 global $wpdb;
460
461 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
462 return $wpdb->prepare( $query . $where['where'] . ' ' . implode( ' ', $args ), $where['values'] );
463 }
464
465 /**
466 * @since 2.05.07
467 *
468 * @param array $args Query arguments, passed by reference.
469 *
470 * @return void
471 */
472 private static function esc_query_args( &$args ) {
473 foreach ( $args as $param => $value ) {
474 if ( $param === 'order_by' ) {
475 $args[ $param ] = self::esc_order( $value );
476 } elseif ( $param === 'limit' ) {
477 $args[ $param ] = self::esc_limit( $value );
478 }
479
480 // phpcs:ignore Universal.Operators.StrictComparisons
481 if ( $args[ $param ] == '' ) {
482 unset( $args[ $param ] );
483 }
484 }
485 }
486
487 /**
488 * Added for < WP 4.0 compatibility
489 *
490 * @since 2.05.06
491 *
492 * @param string $term The value to escape.
493 *
494 * @return string The escaped value
495 */
496 public static function esc_like( $term ) {
497 global $wpdb;
498 return $wpdb->esc_like( $term );
499 }
500
501 /**
502 * @since 2.05.06
503 *
504 * @param string $order_query
505 *
506 * @return string
507 */
508 public static function esc_order( $order_query ) {
509 if ( ! $order_query ) {
510 return '';
511 }
512
513 // Remove ORDER BY before sanitizing.
514 $order_query = strtolower( $order_query );
515
516 if ( str_contains( $order_query, 'order by' ) ) {
517 $order_query = str_replace( 'order by', '', $order_query );
518 }
519
520 $order_query = explode( ' ', trim( $order_query ) );
521 $order = trim( reset( $order_query ) );
522 $safe_order = array( 'count(*)' );
523
524 if ( ! in_array( strtolower( $order ), $safe_order, true ) ) {
525 $order = preg_replace( '/[^a-zA-Z0-9\-\_\.\+]/', '', $order );
526 }
527
528 $order_by = '';
529
530 if ( count( $order_query ) > 1 ) {
531 $order_by = end( $order_query );
532 self::esc_order_by( $order_by );
533 }
534
535 return ' ORDER BY ' . $order . ' ' . $order_by;
536 }
537
538 /**
539 * Make sure this is ordering by either ASC or DESC
540 *
541 * @since 2.05.06
542 *
543 * @param string $order_by Sort direction, passed by reference.
544 *
545 * @return void
546 */
547 public static function esc_order_by( &$order_by ) {
548 $sort_options = array( 'asc', 'desc' );
549
550 if ( ! in_array( strtolower( $order_by ), $sort_options, true ) ) {
551 $order_by = 'asc';
552 }
553 }
554
555 /**
556 * @since 2.05.06
557 *
558 * @param string $limit
559 *
560 * @return string
561 */
562 public static function esc_limit( $limit ) {
563 if ( ! $limit ) {
564 return '';
565 }
566
567 $limit = trim( str_replace( 'limit ', '', strtolower( $limit ) ) );
568
569 if ( is_numeric( $limit ) ) {
570 return ' LIMIT ' . $limit;
571 }
572
573 $limit = explode( ',', trim( $limit ) );
574
575 foreach ( $limit as $k => $l ) {
576 if ( is_numeric( $l ) ) {
577 $limit[ $k ] = $l;
578 }
579 }
580
581 $limit = implode( ',', $limit );
582
583 return ' LIMIT ' . $limit;
584 }
585
586 /**
587 * Get an array of values ready to go through $wpdb->prepare
588 *
589 * @since 2.05.06
590 *
591 * @param array $array Array of values.
592 * @param string $type Placeholder type.
593 *
594 * @return string
595 */
596 public static function prepare_array_values( $array, $type = '%s' ) {
597 $placeholders = array_fill( 0, count( $array ), $type );
598 return implode( ', ', $placeholders );
599 }
600
601 /**
602 * @since 2.05.06
603 *
604 * @param string $starts_with
605 * @param array|string $where
606 *
607 * @return string
608 */
609 public static function prepend_and_or_where( $starts_with = ' WHERE ', $where = '' ) {
610 if ( ! $where ) {
611 $where = '';
612 } elseif ( is_array( $where ) ) {
613 global $wpdb;
614 self::get_where_clause_and_values( $where, $starts_with );
615 $where = $wpdb->prepare( $where['where'], $where['values'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
616 } else {
617 $where = $starts_with . $where;
618 }
619
620 /**
621 * Allows modifying where clause when using FrmDb::prepend_and_or_where() method.
622 *
623 * @since 5.0.16
624 *
625 * @param string $where Where string.
626 * @param string $starts_with The start of where string.
627 */
628 return apply_filters( 'frm_prepend_and_or_where', $where, $starts_with );
629 }
630
631 /**
632 * Prepare and save settings in styles and actions
633 *
634 * @since 2.05.06
635 *
636 * @param array|object $settings
637 * @param string $group
638 *
639 * @return int|WP_Error
640 */
641 public static function save_settings( $settings, $group ) {
642 $settings = (array) $settings;
643 $settings['post_content'] = FrmAppHelper::prepare_and_encode( $settings['post_content'] );
644
645 if ( empty( $settings['ID'] ) ) {
646 unset( $settings['ID'] );
647 }
648
649 // Delete all caches for this group
650 self::cache_delete_group( $group );
651
652 return self::save_json_post( $settings );
653 }
654
655 /**
656 * Since actions are JSON encoded, we don't want any filters messing with it.
657 * Remove the filters and then add them back in case any posts or views are
658 * also being imported.
659 *
660 * Used when saving form actions and styles
661 *
662 * @since 2.05.06
663 *
664 * @param array $settings
665 *
666 * @return int|WP_Error
667 */
668 public static function save_json_post( $settings ) {
669 global $wp_filter;
670
671 if ( isset( $wp_filter['content_save_pre'] ) ) {
672 $filters = $wp_filter['content_save_pre'];
673 }
674
675 // Remove the balanceTags filter in case WordPress is trying to validate the XHTML
676 remove_all_filters( 'content_save_pre' );
677
678 $post = wp_insert_post( $settings );
679
680 // Add the content filters back for views or posts
681 if ( isset( $filters ) ) {
682 $wp_filter['content_save_pre'] = $filters;
683 }
684
685 return $post;
686 }
687
688 /**
689 * Check cache before fetching values and saving to cache
690 *
691 * @since 2.05.06
692 *
693 * @param string $cache_key The unique name for this cache.
694 * @param string $group The name of the cache group.
695 * @param string $query If blank, don't run a db call.
696 * @param string $type The wpdb function to use with this query.
697 * @param int $time Cache expiration time in seconds.
698 *
699 * @return mixed $results The cache or query results
700 */
701 public static function check_cache( $cache_key, $group = '', $query = '', $type = 'get_var', $time = 300 ) {
702 $found = null;
703 $results = wp_cache_get( $cache_key, $group, false, $found );
704
705 if ( ( $found === true && $results !== false ) || ! $query ) {
706 return $results;
707 }
708
709 if ( 'get_posts' === $type ) {
710 $results = get_posts( $query );
711 } elseif ( 'get_associative_results' === $type ) {
712 global $wpdb;
713 $results = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
714 } else {
715 global $wpdb;
716 $results = $wpdb->{$type}( $query );
717 }
718
719 self::set_cache( $cache_key, $results, $group, $time );
720
721 return $results;
722 }
723
724 /**
725 * @since 2.05.06
726 *
727 * @param string $cache_key The unique name for this cache.
728 * @param mixed $results Cached results.
729 * @param string $group The name of the cache group.
730 * @param int $time Cache expiration time in seconds.
731 *
732 * @return void
733 */
734 public static function set_cache( $cache_key, $results, $group = '', $time = 300 ) {
735 if ( FrmAppHelper::prevent_caching() ) {
736 return;
737 }
738
739 self::add_key_to_group_cache( $cache_key, $group );
740 wp_cache_set( $cache_key, $results, $group, $time );
741 }
742
743 /**
744 * Keep track of the keys cached in each group so they can be deleted
745 * in Redis and Memcache
746 *
747 * @since 2.05.06
748 *
749 * @param string $key Cache key.
750 * @param string $group Cache group name.
751 *
752 * @return void
753 */
754 public static function add_key_to_group_cache( $key, $group ) {
755 $cached = self::get_group_cached_keys( $group );
756 $cached[ $key ] = $key;
757 wp_cache_set( 'cached_keys', $cached, $group, 300 );
758 }
759
760 /**
761 * @since 2.05.06
762 *
763 * @param string $group Cache group name.
764 *
765 * @return array
766 */
767 public static function get_group_cached_keys( $group ) {
768 $cached = wp_cache_get( 'cached_keys', $group );
769
770 if ( ! $cached || ! is_array( $cached ) ) {
771 return array();
772 }
773
774 return $cached;
775 }
776
777 /**
778 * @since 2.05.06
779 *
780 * @param string $cache_key Cache key to delete.
781 * @param string $group Cache group name.
782 *
783 * @return void
784 */
785 public static function delete_cache_and_transient( $cache_key, $group = 'default' ) {
786 delete_transient( $cache_key );
787 wp_cache_delete( $cache_key, $group );
788 }
789
790 /**
791 * Delete all caching in a single group
792 *
793 * @since 2.05.06
794 *
795 * @param string $group The name of the cache group.
796 *
797 * @return void
798 */
799 public static function cache_delete_group( $group ) {
800 $cached_keys = self::get_group_cached_keys( $group );
801
802 if ( ! $cached_keys ) {
803 return;
804 }
805
806 foreach ( $cached_keys as $key ) {
807 wp_cache_delete( $key, $group );
808 }
809
810 wp_cache_delete( 'cached_keys', $group );
811 }
812
813 /**
814 * Checks if a DB column exists.
815 *
816 * @since 6.7
817 *
818 * @param string $table Table name without `$wpdb->prefix`.
819 * @param string $column Column name.
820 *
821 * @return bool
822 */
823 public static function db_column_exists( $table, $column ) {
824 global $wpdb;
825
826 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
827 $result = $wpdb->get_results( $wpdb->prepare( 'SHOW COLUMNS FROM ' . $wpdb->prefix . $table . ' LIKE %s', $column ) );
828 return ! empty( $result );
829 }
830 }
831