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

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