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

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