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

759 lines 19.2 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 array|string $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|object|string|null
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 }
446
447 return $query;
448 }
449
450 /**
451 * @since 2.05.07
452 */
453 private static function esc_query_args( &$args ) {
454 foreach ( $args as $param => $value ) {
455 if ( $param === 'order_by' ) {
456 $args[ $param ] = self::esc_order( $value );
457 } elseif ( $param === 'limit' ) {
458 $args[ $param ] = self::esc_limit( $value );
459 }
460
461 if ( $args[ $param ] == '' ) {
462 unset( $args[ $param ] );
463 }
464 }
465 }
466
467 /**
468 * Added for < WP 4.0 compatibility
469 *
470 * @since 2.05.06
471 *
472 * @param string $term The value to escape.
473 *
474 * @return string The escaped value
475 */
476 public static function esc_like( $term ) {
477 global $wpdb;
478
479 return $wpdb->esc_like( $term );
480 }
481
482 /**
483 * @since 2.05.06
484 *
485 * @param string $order_query
486 */
487 public static function esc_order( $order_query ) {
488 if ( empty( $order_query ) ) {
489 return '';
490 }
491
492 // Remove ORDER BY before sanitizing.
493 $order_query = strtolower( $order_query );
494 if ( strpos( $order_query, 'order by' ) !== false ) {
495 $order_query = str_replace( 'order by', '', $order_query );
496 }
497
498 $order_query = explode( ' ', trim( $order_query ) );
499
500 $order = trim( reset( $order_query ) );
501 $safe_order = array( 'count(*)' );
502 if ( ! in_array( strtolower( $order ), $safe_order ) ) {
503 $order = preg_replace( '/[^a-zA-Z0-9\-\_\.\+]/', '', $order );
504 }
505
506 $order_by = '';
507 if ( count( $order_query ) > 1 ) {
508 $order_by = end( $order_query );
509 self::esc_order_by( $order_by );
510 }
511
512 return ' ORDER BY ' . $order . ' ' . $order_by;
513 }
514
515 /**
516 * Make sure this is ordering by either ASC or DESC
517 *
518 * @since 2.05.06
519 */
520 public static function esc_order_by( &$order_by ) {
521 $sort_options = array( 'asc', 'desc' );
522 if ( ! in_array( strtolower( $order_by ), $sort_options, true ) ) {
523 $order_by = 'asc';
524 }
525 }
526
527 /**
528 * @since 2.05.06
529 * @param string $limit
530 */
531 public static function esc_limit( $limit ) {
532 if ( empty( $limit ) ) {
533 return '';
534 }
535
536 $limit = trim( str_replace( 'limit ', '', strtolower( $limit ) ) );
537 if ( is_numeric( $limit ) ) {
538 return ' LIMIT ' . $limit;
539 }
540
541 $limit = explode( ',', trim( $limit ) );
542 foreach ( $limit as $k => $l ) {
543 if ( is_numeric( $l ) ) {
544 $limit[ $k ] = $l;
545 }
546 }
547
548 $limit = implode( ',', $limit );
549
550 return ' LIMIT ' . $limit;
551 }
552
553 /**
554 * Get an array of values ready to go through $wpdb->prepare
555 *
556 * @since 2.05.06
557 */
558 public static function prepare_array_values( $array, $type = '%s' ) {
559 $placeholders = array_fill( 0, count( $array ), $type );
560
561 return implode( ', ', $placeholders );
562 }
563
564 /**
565 * @since 2.05.06
566 *
567 * @param string $starts_with
568 * @param array|string $where
569 * @return string
570 */
571 public static function prepend_and_or_where( $starts_with = ' WHERE ', $where = '' ) {
572 if ( empty( $where ) ) {
573 $where = '';
574 } elseif ( is_array( $where ) ) {
575 global $wpdb;
576 self::get_where_clause_and_values( $where, $starts_with );
577 $where = $wpdb->prepare( $where['where'], $where['values'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
578 } else {
579 $where = $starts_with . $where;
580 }
581
582 /**
583 * Allows modifying where clause when using FrmDb::prepend_and_or_where() method.
584 *
585 * @since 5.0.16
586 *
587 * @param string $where Where string.
588 * @param string $starts_with The start of where string.
589 */
590 return apply_filters( 'frm_prepend_and_or_where', $where, $starts_with );
591 }
592
593 /**
594 * Prepare and save settings in styles and actions
595 *
596 * @since 2.05.06
597 * @param array $settings
598 * @param string $group
599 * @return int|WP_Error
600 */
601 public static function save_settings( $settings, $group ) {
602 $settings = (array) $settings;
603 $settings['post_content'] = FrmAppHelper::prepare_and_encode( $settings['post_content'] );
604
605 if ( empty( $settings['ID'] ) ) {
606 unset( $settings['ID'] );
607 }
608
609 // delete all caches for this group
610 self::cache_delete_group( $group );
611
612 return self::save_json_post( $settings );
613 }
614
615 /**
616 * Since actions are JSON encoded, we don't want any filters messing with it.
617 * Remove the filters and then add them back in case any posts or views are
618 * also being imported.
619 *
620 * Used when saving form actions and styles
621 *
622 * @since 2.05.06
623 *
624 * @param array $settings
625 * @return int|WP_Error
626 */
627 public static function save_json_post( $settings ) {
628 global $wp_filter;
629 if ( isset( $wp_filter['content_save_pre'] ) ) {
630 $filters = $wp_filter['content_save_pre'];
631 }
632
633 // Remove the balanceTags filter in case WordPress is trying to validate the XHTML
634 remove_all_filters( 'content_save_pre' );
635
636 $post = wp_insert_post( $settings );
637
638 // add the content filters back for views or posts
639 if ( isset( $filters ) ) {
640 $wp_filter['content_save_pre'] = $filters;
641 }
642
643 return $post;
644 }
645
646 /**
647 * Check cache before fetching values and saving to cache
648 *
649 * @since 2.05.06
650 *
651 * @param string $cache_key The unique name for this cache.
652 * @param string $group The name of the cache group.
653 * @param string $query If blank, don't run a db call.
654 * @param string $type The wpdb function to use with this query.
655 *
656 * @return mixed $results The cache or query results
657 */
658 public static function check_cache( $cache_key, $group = '', $query = '', $type = 'get_var', $time = 300 ) {
659 $results = wp_cache_get( $cache_key, $group );
660 if ( ! FrmAppHelper::is_empty_value( $results, false ) || empty( $query ) ) {
661 return $results;
662 }
663
664 if ( 'get_posts' == $type ) {
665 $results = get_posts( $query );
666 } elseif ( 'get_associative_results' == $type ) {
667 global $wpdb;
668 $results = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
669 } else {
670 global $wpdb;
671 $results = $wpdb->{$type}( $query );
672 }
673
674 self::set_cache( $cache_key, $results, $group, $time );
675
676 return $results;
677 }
678
679 /**
680 * @since 2.05.06
681 */
682 public static function set_cache( $cache_key, $results, $group = '', $time = 300 ) {
683 if ( ! FrmAppHelper::prevent_caching() ) {
684 self::add_key_to_group_cache( $cache_key, $group );
685 wp_cache_set( $cache_key, $results, $group, $time );
686 }
687 }
688
689 /**
690 * Keep track of the keys cached in each group so they can be deleted
691 * in Redis and Memcache
692 *
693 * @since 2.05.06
694 */
695 public static function add_key_to_group_cache( $key, $group ) {
696 $cached = self::get_group_cached_keys( $group );
697 $cached[ $key ] = $key;
698 wp_cache_set( 'cached_keys', $cached, $group, 300 );
699 }
700
701 /**
702 * @since 2.05.06
703 */
704 public static function get_group_cached_keys( $group ) {
705 $cached = wp_cache_get( 'cached_keys', $group );
706 if ( ! $cached || ! is_array( $cached ) ) {
707 $cached = array();
708 }
709
710 return $cached;
711 }
712
713 /**
714 * @since 2.05.06
715 *
716 * @param string $cache_key
717 */
718 public static function delete_cache_and_transient( $cache_key, $group = 'default' ) {
719 delete_transient( $cache_key );
720 wp_cache_delete( $cache_key, $group );
721 }
722
723 /**
724 * Delete all caching in a single group
725 *
726 * @since 2.05.06
727 *
728 * @param string $group The name of the cache group.
729 */
730 public static function cache_delete_group( $group ) {
731 $cached_keys = self::get_group_cached_keys( $group );
732
733 if ( ! empty( $cached_keys ) ) {
734 foreach ( $cached_keys as $key ) {
735 wp_cache_delete( $key, $group );
736 }
737
738 wp_cache_delete( 'cached_keys', $group );
739 }
740 }
741
742 /**
743 * Checks if a DB column exists.
744 *
745 * @since 6.7
746 *
747 * @param string $table Table name without `$wpdb->prefix`.
748 * @param string $column Column name.
749 * @return bool
750 */
751 public static function db_column_exists( $table, $column ) {
752 global $wpdb;
753
754 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
755 $result = $wpdb->get_results( $wpdb->prepare( 'SHOW COLUMNS FROM ' . $wpdb->prefix . $table . ' LIKE %s', $column ) );
756 return ! empty( $result );
757 }
758 }
759