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

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