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

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