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

766 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 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 } else {
446 /**
447 * Allow the $where to be prepared before we receive 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 compatibility
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 sanitizing.
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, true ) ) {
530 $order_by = 'asc';
531 }
532 }
533
534 /**
535 * @since 2.05.06
536 * @param string $limit
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 * @param string $starts_with
575 * @param array|string $where
576 * @return string
577 */
578 public static function prepend_and_or_where( $starts_with = ' WHERE ', $where = '' ) {
579 if ( empty( $where ) ) {
580 $where = '';
581 } elseif ( is_array( $where ) ) {
582 global $wpdb;
583 self::get_where_clause_and_values( $where, $starts_with );
584 $where = $wpdb->prepare( $where['where'], $where['values'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
585 } else {
586 $where = $starts_with . $where;
587 }
588
589 /**
590 * Allows modifying where clause when using FrmDb::prepend_and_or_where() method.
591 *
592 * @since 5.0.16
593 *
594 * @param string $where Where string.
595 * @param string $starts_with The start of where string.
596 */
597 return apply_filters( 'frm_prepend_and_or_where', $where, $starts_with );
598 }
599
600 /**
601 * Prepare and save settings in styles and actions
602 *
603 * @since 2.05.06
604 * @param array $settings
605 * @param string $group
606 * @return int|WP_Error
607 */
608 public static function save_settings( $settings, $group ) {
609 $settings = (array) $settings;
610 $settings['post_content'] = FrmAppHelper::prepare_and_encode( $settings['post_content'] );
611
612 if ( empty( $settings['ID'] ) ) {
613 unset( $settings['ID'] );
614 }
615
616 // delete all caches for this group
617 self::cache_delete_group( $group );
618
619 return self::save_json_post( $settings );
620 }
621
622 /**
623 * Since actions are JSON encoded, we don't want any filters messing with it.
624 * Remove the filters and then add them back in case any posts or views are
625 * also being imported.
626 *
627 * Used when saving form actions and styles
628 *
629 * @since 2.05.06
630 *
631 * @param array $settings
632 * @return int|WP_Error
633 */
634 public static function save_json_post( $settings ) {
635 global $wp_filter;
636 if ( isset( $wp_filter['content_save_pre'] ) ) {
637 $filters = $wp_filter['content_save_pre'];
638 }
639
640 // Remove the balanceTags filter in case WordPress is trying to validate the XHTML
641 remove_all_filters( 'content_save_pre' );
642
643 $post = wp_insert_post( $settings );
644
645 // add the content filters back for views or posts
646 if ( isset( $filters ) ) {
647 $wp_filter['content_save_pre'] = $filters;
648 }
649
650 return $post;
651 }
652
653 /**
654 * Check cache before fetching values and saving to cache
655 *
656 * @since 2.05.06
657 *
658 * @param string $cache_key The unique name for this cache.
659 * @param string $group The name of the cache group.
660 * @param string $query If blank, don't run a db call.
661 * @param string $type The wpdb function to use with this query.
662 *
663 * @return mixed $results The cache or query results
664 */
665 public static function check_cache( $cache_key, $group = '', $query = '', $type = 'get_var', $time = 300 ) {
666 $results = wp_cache_get( $cache_key, $group );
667 if ( ! FrmAppHelper::is_empty_value( $results, false ) || empty( $query ) ) {
668 return $results;
669 }
670
671 if ( 'get_posts' == $type ) {
672 $results = get_posts( $query );
673 } elseif ( 'get_associative_results' == $type ) {
674 global $wpdb;
675 $results = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
676 } else {
677 global $wpdb;
678 $results = $wpdb->{$type}( $query );
679 }
680
681 self::set_cache( $cache_key, $results, $group, $time );
682
683 return $results;
684 }
685
686 /**
687 * @since 2.05.06
688 */
689 public static function set_cache( $cache_key, $results, $group = '', $time = 300 ) {
690 if ( ! FrmAppHelper::prevent_caching() ) {
691 self::add_key_to_group_cache( $cache_key, $group );
692 wp_cache_set( $cache_key, $results, $group, $time );
693 }
694 }
695
696 /**
697 * Keep track of the keys cached in each group so they can be deleted
698 * in Redis and Memcache
699 *
700 * @since 2.05.06
701 */
702 public static function add_key_to_group_cache( $key, $group ) {
703 $cached = self::get_group_cached_keys( $group );
704 $cached[ $key ] = $key;
705 wp_cache_set( 'cached_keys', $cached, $group, 300 );
706 }
707
708 /**
709 * @since 2.05.06
710 */
711 public static function get_group_cached_keys( $group ) {
712 $cached = wp_cache_get( 'cached_keys', $group );
713 if ( ! $cached || ! is_array( $cached ) ) {
714 $cached = array();
715 }
716
717 return $cached;
718 }
719
720 /**
721 * @since 2.05.06
722 *
723 * @param string $cache_key
724 */
725 public static function delete_cache_and_transient( $cache_key, $group = 'default' ) {
726 delete_transient( $cache_key );
727 wp_cache_delete( $cache_key, $group );
728 }
729
730 /**
731 * Delete all caching in a single group
732 *
733 * @since 2.05.06
734 *
735 * @param string $group The name of the cache group.
736 */
737 public static function cache_delete_group( $group ) {
738 $cached_keys = self::get_group_cached_keys( $group );
739
740 if ( ! empty( $cached_keys ) ) {
741 foreach ( $cached_keys as $key ) {
742 wp_cache_delete( $key, $group );
743 }
744
745 wp_cache_delete( 'cached_keys', $group );
746 }
747 }
748
749 /**
750 * Checks if a DB column exists.
751 *
752 * @since 6.7
753 *
754 * @param string $table Table name without `$wpdb->prefix`.
755 * @param string $column Column name.
756 * @return bool
757 */
758 public static function db_column_exists( $table, $column ) {
759 global $wpdb;
760
761 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
762 $result = $wpdb->get_results( $wpdb->prepare( 'SHOW COLUMNS FROM ' . $wpdb->prefix . $table . ' LIKE %s', $column ) );
763 return ! empty( $result );
764 }
765 }
766