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

871 lines 20.7 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
8 /**
9 * The table name for Formidable Fields.
10 *
11 * @var string
12 */
13 public $fields;
14
15 /**
16 * The table name for Formidable Forms.
17 *
18 * @var string
19 */
20 public $forms;
21
22 /**
23 * The table name for Formidable Entries.
24 *
25 * @var string
26 */
27 public $entries;
28
29 /**
30 * The table name for Formidable Entry Metas.
31 *
32 * @var string
33 */
34 public $entry_metas;
35
36 public function __construct() {
37 if ( ! defined( 'ABSPATH' ) ) {
38 die( 'You are not allowed to call this page directly.' );
39 }
40
41 _deprecated_function( __METHOD__, '2.05.06', 'FrmMigrate' );
42 global $wpdb;
43 $this->fields = $wpdb->prefix . 'frm_fields';
44 $this->forms = $wpdb->prefix . 'frm_forms';
45 $this->entries = $wpdb->prefix . 'frm_items';
46 $this->entry_metas = $wpdb->prefix . 'frm_item_metas';
47 }
48
49 /**
50 * Change array into format $wpdb->prepare can use
51 *
52 * @param array $args
53 * @param string $starts_with
54 *
55 * @return void
56 */
57 public static function get_where_clause_and_values( &$args, $starts_with = ' WHERE ' ) {
58 if ( empty( $args ) ) {
59 // add an arg to prevent prepare from failing
60 $args = array(
61 'where' => $starts_with . '1=%d',
62 'values' => array( 1 ),
63 );
64
65 return;
66 }
67
68 $where = '';
69 $values = array();
70
71 if ( is_array( $args ) ) {
72 $base_where = $starts_with;
73 self::parse_where_from_array( $args, $base_where, $where, $values );
74 }
75
76 $args = compact( 'where', 'values' );
77 }
78
79 /**
80 * @param array $args
81 * @param string $base_where
82 * @param string $where
83 * @param array $values
84 *
85 * @return void
86 */
87 public static function parse_where_from_array( $args, $base_where, &$where, &$values ) {
88 $condition = ' AND';
89
90 if ( isset( $args['or'] ) ) {
91 $condition = ' OR';
92 unset( $args['or'] );
93 }
94
95 foreach ( $args as $key => $value ) {
96 $where .= empty( $where ) ? $base_where : $condition;
97 $array_inc_null = ( ! is_numeric( $key ) && is_array( $value ) && in_array( null, $value ) );
98
99 if ( is_numeric( $key ) || $array_inc_null ) {
100 $where .= ' ( ';
101 $nested_where = '';
102
103 if ( $array_inc_null ) {
104 foreach ( $value as $val ) {
105 $parse_where = array(
106 $key => $val,
107 'or' => 1,
108 );
109 self::parse_where_from_array( $parse_where, '', $nested_where, $values );
110 }
111 } else {
112 self::parse_where_from_array( $value, '', $nested_where, $values );
113 }
114
115 $where .= $nested_where;
116 $where .= ' ) ';
117 } else {
118 self::interpret_array_to_sql( $key, $value, $where, $values );
119 }//end if
120 }//end foreach
121 }
122
123 /**
124 * @param string $key
125 * @param array|string $value
126 * @param string $where
127 * @param array $values
128 *
129 * @return void
130 */
131 private static function interpret_array_to_sql( $key, $value, &$where, &$values ) {
132 $key = trim( $key );
133
134 if ( strpos( $key, 'created_at' ) !== false || strpos( $key, 'updated_at' ) !== false ) {
135 $k = explode( ' ', $key );
136 $where .= ' CAST(' . reset( $k ) . ' as CHAR) ' . str_replace( reset( $k ), '', $key );
137 } else {
138 $where .= ' ' . $key;
139 }
140
141 $lowercase_key = explode( ' ', strtolower( $key ) );
142 $lowercase_key = end( $lowercase_key );
143
144 if ( is_array( $value ) ) {
145 // translate array of values to "in"
146 if ( strpos( $lowercase_key, 'like' ) !== false ) {
147 $where = preg_replace( '/' . $key . '$/', '', $where );
148 $where .= '(';
149 $start = true;
150
151 foreach ( $value as $v ) {
152 if ( ! $start ) {
153 $where .= ' OR ';
154 }
155
156 $start = false;
157 $where .= $key . ' %s';
158 $values[] = '%' . self::esc_like( $v ) . '%';
159 }
160
161 $where .= ')';
162 } elseif ( ! empty( $value ) ) {
163 $where .= ' in (' . self::prepare_array_values( $value, '%s' ) . ')';
164 $values = array_merge( $values, $value );
165 }
166 } elseif ( strpos( $lowercase_key, 'like' ) !== false ) {
167 /**
168 * Allow string to start or end with the value
169 * If the key is like% then skip the first % for starts with
170 * If the key is %like then skip the last % for ends with
171 */
172 $start = '%';
173 $end = '%';
174
175 if ( $lowercase_key === 'like%' ) {
176 $start = '';
177 $where = rtrim( $where, '%' );
178 } elseif ( $lowercase_key === '%like' ) {
179 $end = '';
180 $where = rtrim( rtrim( $where, '%like' ), '%LIKE' );
181 $where .= 'like';
182 }
183
184 $where .= ' %s';
185 $values[] = $start . self::esc_like( $value ) . $end;
186
187 } elseif ( $value === null ) {
188 $where .= ' IS NULL';
189 } else {
190 // allow a - to prevent = from being added
191 if ( substr( $key, - 1 ) === '-' ) {
192 $where = rtrim( $where, '-' );
193 } else {
194 $where .= '=';
195 }
196
197 self::add_query_placeholder( $key, $value, $where );
198
199 $values[] = $value;
200 }//end if
201 }
202
203 /**
204 * Add %d, or %s to query
205 *
206 * @since 2.02.05
207 *
208 * @param string $key
209 * @param int|string $value
210 * @param string $where
211 *
212 * @return void
213 */
214 private static function add_query_placeholder( $key, $value, &$where ) {
215 if ( is_numeric( $value ) && ( strpos( $key, 'meta_value' ) === false || strpos( $key, '+0' ) !== false ) ) {
216 // Switch string to number.
217 $value = $value + 0;
218 $where .= is_float( $value ) ? '%f' : '%d';
219 } else {
220 $where .= '%s';
221 }
222 }
223
224 /**
225 * @param string $table
226 * @param array $where
227 * @param array $args
228 *
229 * @return int
230 */
231 public static function get_count( $table, $where = array(), $args = array() ) {
232 $count = self::get_var( $table, $where, 'COUNT(*)', $args );
233
234 return (int) $count;
235 }
236
237 /**
238 * @param string $table
239 * @param array $where
240 * @param string $field
241 * @param array $args
242 * @param string $limit
243 * @param string $type
244 *
245 * @return array|object|string|null
246 */
247 public static function get_var( $table, $where = array(), $field = 'id', $args = array(), $limit = '', $type = 'var' ) {
248 $group = '';
249 self::get_group_and_table_name( $table, $group );
250 self::convert_options_to_array( $args, '', $limit );
251
252 if ( $type === 'var' && ! isset( $args['limit'] ) ) {
253 $args['limit'] = 1;
254 }
255
256 $query = self::generate_query_string_from_pieces( $field, $table, $where, $args );
257
258 $cache_key = self::generate_cache_key( $where, $args, $field, $type );
259 $results = self::check_cache( $cache_key, $group, $query, 'get_' . $type );
260
261 return $results;
262 }
263
264 /**
265 * Generate a cache key from the where query, field, type, and other arguments
266 *
267 * @since 2.03.07
268 *
269 * @param array $where
270 * @param array $args
271 * @param string $field
272 * @param string $type
273 *
274 * @return string
275 */
276 public static function generate_cache_key( $where, $args, $field, $type ) {
277 $cache_key = '';
278 $where = FrmAppHelper::array_flatten( $where );
279
280 foreach ( $where as $key => $value ) {
281 $cache_key .= $key . '_' . $value;
282 }
283
284 $cache_key .= implode( '_', $args ) . $field . '_' . $type;
285 $cache_key = str_replace( array( ' ', ',' ), '_', $cache_key );
286
287 return $cache_key;
288 }
289
290 /**
291 * @param string $table
292 * @param array $where
293 * @param string $field
294 * @param array $args
295 * @param string $limit
296 *
297 * @return array
298 */
299 public static function get_col( $table, $where = array(), $field = 'id', $args = array(), $limit = '' ) {
300 return self::get_var( $table, $where, $field, $args, $limit, 'col' );
301 }
302
303 /**
304 * @since 2.0
305 *
306 * @param string $table
307 * @param array $where
308 * @param string $fields
309 * @param array $args
310 *
311 * @return mixed
312 */
313 public static function get_row( $table, $where = array(), $fields = '*', $args = array() ) {
314 $args['limit'] = 1;
315 return self::get_var( $table, $where, $fields, $args, '', 'row' );
316 }
317
318 /**
319 * Prepare a key/value array before DB call
320 *
321 * @since 2.0
322 *
323 * @param string $table
324 * @param array $where
325 * @param string $fields
326 * @param array $args
327 *
328 * @return array
329 */
330 public static function get_results( $table, $where = array(), $fields = '*', $args = array() ) {
331 return self::get_var( $table, $where, $fields, $args, '', 'results' );
332 }
333
334 /**
335 * Check for like, not like, in, not in, =, !=, >, <, <=, >=
336 * Return a value to append to the where array key
337 *
338 * @param string $where_is
339 *
340 * @return string
341 */
342 public static function append_where_is( $where_is ) {
343 $switch_to = array(
344 '=' => '',
345 '!=' => '!',
346 '<=' => '<',
347 '>=' => '>',
348 'like' => 'like',
349 'not like' => 'not like',
350 'in' => '',
351 'not in' => 'not',
352 'like%' => 'like%',
353 '%like' => '%like',
354 );
355
356 $where_is = strtolower( $where_is );
357
358 if ( isset( $switch_to[ $where_is ] ) ) {
359 return ' ' . $switch_to[ $where_is ];
360 }
361
362 // > and < need a little more work since we don't want them switched to >= and <=
363 if ( $where_is === '>' || $where_is === '<' ) {
364 // The - indicates that the = should not be added later.
365 return ' ' . $where_is . '-';
366 }
367
368 // fallback to = if the query is none of these
369 return '';
370 }
371
372 /**
373 * Get 'frm_forms' from wp_frm_forms or a longer table param that includes a join
374 * Also add the wpdb->prefix to the table if it's missing
375 *
376 * @param string $table
377 * @param string $group
378 *
379 * @return void
380 */
381 private static function get_group_and_table_name( &$table, &$group ) {
382 global $wpdb;
383
384 $table_parts = explode( ' ', $table );
385 $group = reset( $table_parts );
386 self::maybe_remove_prefix( $wpdb->prefix, $group );
387
388 $prefix = $wpdb->base_prefix;
389 self::maybe_remove_prefix( $prefix, $group );
390
391 if ( $group == $table ) {
392 $table = $wpdb->prefix . $table;
393 }
394
395 // switch to singular group name
396 $group = rtrim( $group, 's' );
397 }
398
399 /**
400 * Only remove the db prefix when at the beginning.
401 *
402 * @since 4.04.02
403 *
404 * @param string $prefix Prefix to remove.
405 * @param string $name Name to strip prefix from, passed by reference.
406 *
407 * @return void
408 */
409 private static function maybe_remove_prefix( $prefix, &$name ) {
410 if ( substr( $name, 0, strlen( $prefix ) ) === $prefix ) {
411 $name = substr( $name, strlen( $prefix ) );
412 }
413 }
414
415 /**
416 * @param array|string $args
417 * @param string $order_by
418 * @param int|string $limit
419 *
420 * @return void
421 */
422 private static function convert_options_to_array( &$args, $order_by = '', $limit = '' ) {
423 if ( ! is_array( $args ) ) {
424 $args = array( 'order_by' => $args );
425 }
426
427 if ( ! empty( $order_by ) ) {
428 $args['order_by'] = $order_by;
429 }
430
431 if ( ! empty( $limit ) ) {
432 $args['limit'] = $limit;
433 }
434
435 $temp_args = $args;
436
437 foreach ( $temp_args as $k => $v ) {
438 if ( $v == '' ) {
439 unset( $args[ $k ] );
440 continue;
441 }
442
443 $db_name = strtoupper( str_replace( '_', ' ', $k ) );
444
445 if ( strpos( $v, $db_name ) === false ) {
446 $args[ $k ] = $db_name . ' ' . $v;
447 }
448 }
449
450 // Make sure LIMIT is the last argument
451 if ( isset( $args['order_by'] ) && isset( $args['limit'] ) ) {
452 $temp_limit = $args['limit'];
453 unset( $args['limit'] );
454 $args['limit'] = $temp_limit;
455 }
456 }
457
458 /**
459 * Get the associative array results for the given columns, table, and where query
460 *
461 * @since 2.02.05
462 *
463 * @param string $columns
464 * @param string $table
465 * @param array $where
466 *
467 * @return mixed
468 */
469 public static function get_associative_array_results( $columns, $table, $where ) {
470 $group = '';
471 self::get_group_and_table_name( $table, $group );
472
473 $query = self::generate_query_string_from_pieces( $columns, $table, $where );
474
475 $cache_key = str_replace( array( ' ', ',' ), '_', trim( implode( '_', FrmAppHelper::array_flatten( $where ) ) . $columns . '_results_ARRAY_A', ' WHERE' ) );
476 $results = self::check_cache( $cache_key, $group, $query, 'get_associative_results' );
477
478 return $results;
479 }
480
481 /**
482 * Combine the pieces of a query to form a full, prepared query
483 *
484 * @since 2.02.05
485 *
486 * @param string $columns
487 * @param string $table
488 * @param mixed $where
489 * @param array $args
490 *
491 * @return string
492 */
493 private static function generate_query_string_from_pieces( $columns, $table, $where, $args = array() ) {
494 $query = 'SELECT ' . $columns . ' FROM ' . $table;
495
496 self::esc_query_args( $args );
497
498 if ( is_array( $where ) || empty( $where ) ) {
499 self::get_where_clause_and_values( $where );
500 global $wpdb;
501 $query = $wpdb->prepare( $query . $where['where'] . ' ' . implode( ' ', $args ), $where['values'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
502 }
503
504 return $query;
505 }
506
507 /**
508 * @since 2.05.07
509 *
510 * @param array $args Query arguments, passed by reference.
511 *
512 * @return void
513 */
514 private static function esc_query_args( &$args ) {
515 foreach ( $args as $param => $value ) {
516 if ( $param === 'order_by' ) {
517 $args[ $param ] = self::esc_order( $value );
518 } elseif ( $param === 'limit' ) {
519 $args[ $param ] = self::esc_limit( $value );
520 }
521
522 if ( $args[ $param ] == '' ) {
523 unset( $args[ $param ] );
524 }
525 }
526 }
527
528 /**
529 * Added for < WP 4.0 compatibility
530 *
531 * @since 2.05.06
532 *
533 * @param string $term The value to escape.
534 *
535 * @return string The escaped value
536 */
537 public static function esc_like( $term ) {
538 global $wpdb;
539
540 return $wpdb->esc_like( $term );
541 }
542
543 /**
544 * @since 2.05.06
545 *
546 * @param string $order_query
547 *
548 * @return string
549 */
550 public static function esc_order( $order_query ) {
551 if ( empty( $order_query ) ) {
552 return '';
553 }
554
555 // Remove ORDER BY before sanitizing.
556 $order_query = strtolower( $order_query );
557
558 if ( strpos( $order_query, 'order by' ) !== false ) {
559 $order_query = str_replace( 'order by', '', $order_query );
560 }
561
562 $order_query = explode( ' ', trim( $order_query ) );
563
564 $order = trim( reset( $order_query ) );
565 $safe_order = array( 'count(*)' );
566
567 if ( ! in_array( strtolower( $order ), $safe_order ) ) {
568 $order = preg_replace( '/[^a-zA-Z0-9\-\_\.\+]/', '', $order );
569 }
570
571 $order_by = '';
572
573 if ( count( $order_query ) > 1 ) {
574 $order_by = end( $order_query );
575 self::esc_order_by( $order_by );
576 }
577
578 return ' ORDER BY ' . $order . ' ' . $order_by;
579 }
580
581 /**
582 * Make sure this is ordering by either ASC or DESC
583 *
584 * @since 2.05.06
585 *
586 * @param string $order_by Sort direction, passed by reference.
587 *
588 * @return void
589 */
590 public static function esc_order_by( &$order_by ) {
591 $sort_options = array( 'asc', 'desc' );
592
593 if ( ! in_array( strtolower( $order_by ), $sort_options, true ) ) {
594 $order_by = 'asc';
595 }
596 }
597
598 /**
599 * @since 2.05.06
600 *
601 * @param string $limit
602 *
603 * @return string
604 */
605 public static function esc_limit( $limit ) {
606 if ( empty( $limit ) ) {
607 return '';
608 }
609
610 $limit = trim( str_replace( 'limit ', '', strtolower( $limit ) ) );
611
612 if ( is_numeric( $limit ) ) {
613 return ' LIMIT ' . $limit;
614 }
615
616 $limit = explode( ',', trim( $limit ) );
617
618 foreach ( $limit as $k => $l ) {
619 if ( is_numeric( $l ) ) {
620 $limit[ $k ] = $l;
621 }
622 }
623
624 $limit = implode( ',', $limit );
625
626 return ' LIMIT ' . $limit;
627 }
628
629 /**
630 * Get an array of values ready to go through $wpdb->prepare
631 *
632 * @since 2.05.06
633 *
634 * @param array $array Array of values.
635 * @param string $type Placeholder type.
636 *
637 * @return string
638 */
639 public static function prepare_array_values( $array, $type = '%s' ) {
640 $placeholders = array_fill( 0, count( $array ), $type );
641
642 return implode( ', ', $placeholders );
643 }
644
645 /**
646 * @since 2.05.06
647 *
648 * @param string $starts_with
649 * @param array|string $where
650 *
651 * @return string
652 */
653 public static function prepend_and_or_where( $starts_with = ' WHERE ', $where = '' ) {
654 if ( empty( $where ) ) {
655 $where = '';
656 } elseif ( is_array( $where ) ) {
657 global $wpdb;
658 self::get_where_clause_and_values( $where, $starts_with );
659 $where = $wpdb->prepare( $where['where'], $where['values'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
660 } else {
661 $where = $starts_with . $where;
662 }
663
664 /**
665 * Allows modifying where clause when using FrmDb::prepend_and_or_where() method.
666 *
667 * @since 5.0.16
668 *
669 * @param string $where Where string.
670 * @param string $starts_with The start of where string.
671 */
672 return apply_filters( 'frm_prepend_and_or_where', $where, $starts_with );
673 }
674
675 /**
676 * Prepare and save settings in styles and actions
677 *
678 * @since 2.05.06
679 *
680 * @param array|object $settings
681 * @param string $group
682 *
683 * @return int|WP_Error
684 */
685 public static function save_settings( $settings, $group ) {
686 $settings = (array) $settings;
687 $settings['post_content'] = FrmAppHelper::prepare_and_encode( $settings['post_content'] );
688
689 if ( empty( $settings['ID'] ) ) {
690 unset( $settings['ID'] );
691 }
692
693 // delete all caches for this group
694 self::cache_delete_group( $group );
695
696 return self::save_json_post( $settings );
697 }
698
699 /**
700 * Since actions are JSON encoded, we don't want any filters messing with it.
701 * Remove the filters and then add them back in case any posts or views are
702 * also being imported.
703 *
704 * Used when saving form actions and styles
705 *
706 * @since 2.05.06
707 *
708 * @param array $settings
709 *
710 * @return int|WP_Error
711 */
712 public static function save_json_post( $settings ) {
713 global $wp_filter;
714
715 if ( isset( $wp_filter['content_save_pre'] ) ) {
716 $filters = $wp_filter['content_save_pre'];
717 }
718
719 // Remove the balanceTags filter in case WordPress is trying to validate the XHTML
720 remove_all_filters( 'content_save_pre' );
721
722 $post = wp_insert_post( $settings );
723
724 // add the content filters back for views or posts
725 if ( isset( $filters ) ) {
726 $wp_filter['content_save_pre'] = $filters;
727 }
728
729 return $post;
730 }
731
732 /**
733 * Check cache before fetching values and saving to cache
734 *
735 * @since 2.05.06
736 *
737 * @param string $cache_key The unique name for this cache.
738 * @param string $group The name of the cache group.
739 * @param string $query If blank, don't run a db call.
740 * @param string $type The wpdb function to use with this query.
741 * @param int $time Cache expiration time in seconds.
742 *
743 * @return mixed $results The cache or query results
744 */
745 public static function check_cache( $cache_key, $group = '', $query = '', $type = 'get_var', $time = 300 ) {
746 $found = null;
747 $results = wp_cache_get( $cache_key, $group, false, $found );
748
749 if ( ( $found === true && $results !== false ) || empty( $query ) ) {
750 return $results;
751 }
752
753 if ( 'get_posts' == $type ) {
754 $results = get_posts( $query );
755 } elseif ( 'get_associative_results' === $type ) {
756 global $wpdb;
757 $results = $wpdb->get_results( $query, OBJECT_K ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
758 } else {
759 global $wpdb;
760 $results = $wpdb->{$type}( $query );
761 }
762
763 self::set_cache( $cache_key, $results, $group, $time );
764
765 return $results;
766 }
767
768 /**
769 * @since 2.05.06
770 *
771 * @param string $cache_key The unique name for this cache.
772 * @param mixed $results Cached results.
773 * @param string $group The name of the cache group.
774 * @param int $time Cache expiration time in seconds.
775 *
776 * @return void
777 */
778 public static function set_cache( $cache_key, $results, $group = '', $time = 300 ) {
779 if ( ! FrmAppHelper::prevent_caching() ) {
780 self::add_key_to_group_cache( $cache_key, $group );
781 wp_cache_set( $cache_key, $results, $group, $time );
782 }
783 }
784
785 /**
786 * Keep track of the keys cached in each group so they can be deleted
787 * in Redis and Memcache
788 *
789 * @since 2.05.06
790 *
791 * @param string $key Cache key.
792 * @param string $group Cache group name.
793 *
794 * @return void
795 */
796 public static function add_key_to_group_cache( $key, $group ) {
797 $cached = self::get_group_cached_keys( $group );
798 $cached[ $key ] = $key;
799 wp_cache_set( 'cached_keys', $cached, $group, 300 );
800 }
801
802 /**
803 * @since 2.05.06
804 *
805 * @param string $group Cache group name.
806 *
807 * @return array
808 */
809 public static function get_group_cached_keys( $group ) {
810 $cached = wp_cache_get( 'cached_keys', $group );
811
812 if ( ! $cached || ! is_array( $cached ) ) {
813 $cached = array();
814 }
815
816 return $cached;
817 }
818
819 /**
820 * @since 2.05.06
821 *
822 * @param string $cache_key Cache key to delete.
823 * @param string $group Cache group name.
824 *
825 * @return void
826 */
827 public static function delete_cache_and_transient( $cache_key, $group = 'default' ) {
828 delete_transient( $cache_key );
829 wp_cache_delete( $cache_key, $group );
830 }
831
832 /**
833 * Delete all caching in a single group
834 *
835 * @since 2.05.06
836 *
837 * @param string $group The name of the cache group.
838 *
839 * @return void
840 */
841 public static function cache_delete_group( $group ) {
842 $cached_keys = self::get_group_cached_keys( $group );
843
844 if ( ! empty( $cached_keys ) ) {
845 foreach ( $cached_keys as $key ) {
846 wp_cache_delete( $key, $group );
847 }
848
849 wp_cache_delete( 'cached_keys', $group );
850 }
851 }
852
853 /**
854 * Checks if a DB column exists.
855 *
856 * @since 6.7
857 *
858 * @param string $table Table name without `$wpdb->prefix`.
859 * @param string $column Column name.
860 *
861 * @return bool
862 */
863 public static function db_column_exists( $table, $column ) {
864 global $wpdb;
865
866 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
867 $result = $wpdb->get_results( $wpdb->prepare( 'SHOW COLUMNS FROM ' . $wpdb->prefix . $table . ' LIKE %s', $column ) );
868 return ! empty( $result );
869 }
870 }
871