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

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

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