PluginProbe
Tutor LMS – eLearning and online course solution / 3.4.2
Tutor LMS – eLearning and online course solution v3.4.2
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 All 191 releases
← All changes | helpers/QueryHelper.php +180 -702 trunk3.4.2 View file →
@@ -3,9 +3,9 @@
3 3 * Query helper class contains static helper methods to perform basic
4 4 * operations
5 5 *
6 6 * @package Tutor\Helper
7 - * @since 2.0.7
7 + * @since v2.0.7
8 8 */
9 9
10 10 namespace Tutor\Helpers;
11 11
@@ -31,10 +31,9 @@
31 31 */
32 32 public static function insert( string $table, array $data, array $sanitize_mapping = array() ): int {
33 33 global $wpdb;
34 34
35 - $table = self::prepare_table_name( $table );
36 - $data = \TUTOR\Input::sanitize_array( $data, $sanitize_mapping );
35 + $data = \TUTOR\Input::sanitize_array( $data, $sanitize_mapping );
37 36
38 37 $insert = $wpdb->insert(
39 38 $table,
40 39 $data
@@ -61,11 +60,10 @@
61 60 */
62 61 public static function update( string $table, array $data, array $where ): bool {
63 62 global $wpdb;
64 63
65 - $table = self::prepare_table_name( $table );
66 64 $set_clause = self::prepare_set_clause( $data );
67 - $where_clause = self::prepare_where_clause( $where );
65 + $where_clause = self::build_where_clause( $where );
68 66
69 67 // phpcs:ignore
70 68 $query = $wpdb->prepare( "UPDATE {$table} {$set_clause} WHERE {$where_clause} AND 1 = %d", 1 );
71 69
@@ -80,10 +78,9 @@
80 78 return true;
81 79 }
82 80
83 81 /**
84 - * Delete a row from table with where clause.
85 - * Limitation: It can only delete one row by wpdb::delete
82 + * Delete rows from table
86 83 *
87 84 * @param string $table table name.
88 85 * @param array $where key value pairs.Where key is the name of
89 86 * column & value is the value to match.
@@ -92,10 +89,8 @@
92 89 * @since v2.0.7
93 90 */
94 91 public static function delete( string $table, array $where ): bool {
95 92 global $wpdb;
96 -
97 - $table = self::prepare_table_name( $table );
98 93 $delete = $wpdb->delete(
99 94 $table,
100 95 $where
101 96 );
@@ -102,27 +97,8 @@
102 97 return $delete ? true : false;
103 98 }
104 99
105 100 /**
106 - * Bulk record delete by where clause.
107 - *
108 - * @since 3.7.0
109 - *
110 - * @param string $table table name.
111 - * @param array $where where clause.
112 - *
113 - * @return int|boolean
114 - */
115 - public static function bulk_delete( $table, array $where ): bool {
116 - global $wpdb;
117 -
118 - $table = self::prepare_table_name( $table );
119 - $where_clause = self::prepare_where_clause( $where );
120 -
121 - return $wpdb->query( "DELETE FROM {$table} WHERE {$where_clause}" ); //phpcs:ignore --$where clause sanitized.
122 - }
123 -
124 - /**
125 101 * Delete rows from table
126 102 *
127 103 * @since 3.0.0
128 104 *
@@ -137,10 +113,9 @@
137 113 */
138 114 public static function bulk_delete_by_ids( string $table, array $ids ): bool {
139 115 global $wpdb;
140 116
141 - $table = self::prepare_table_name( $table );
142 - $ids = self::prepare_in_clause( $ids );
117 + $ids = self::prepare_in_clause( $ids );
143 118 //phpcs:ignore --ids already sanitized.
144 119 $wpdb->query( "DELETE FROM {$table} WHERE id IN ( $ids )");
145 120
146 121 if ( $wpdb->last_error ) {
@@ -160,10 +135,8 @@
160 135 * @return bool
161 136 */
162 137 public static function table_clean( string $table ): bool {
163 138 global $wpdb;
164 -
165 - $table = self::prepare_table_name( $table );
166 139 $delete = $wpdb->query(
167 140 //phpcs:ignore
168 141 $wpdb->prepare( "DELETE FROM {$table} WHERE 1 = %d", 1 )
169 142 );
@@ -173,29 +146,20 @@
173 146 /**
174 147 * Insert multiple rows without knowing key value
175 148 *
176 149 * @since v2.0.7
177 - * @since 3.6.0 param $return_ids added.
178 150 *
179 151 * @param string $table table name.
180 152 * @param array $request two dimensional array
181 153 * for ex: [ [id => 1], [id => 2] ].
182 - * @param bool $return_ids if true returns the last inserted data ids.
183 - * @param bool $do_sanitize sanitize data or not.
184 154 *
185 155 * @return mixed wpdb response true or int on success, false on failure.
186 156 * @throws \Exception If error occur.
187 157 */
188 - public static function insert_multiple_rows( $table, $request, $return_ids = false, $do_sanitize = true ) {
158 + public static function insert_multiple_rows( $table, $request ) {
189 159 global $wpdb;
190 -
191 - if ( ! tutor_utils()->is_multi_dimensional_array( $request ) ) {
192 - return self::insert( $table, $request );
193 - }
194 -
195 - $table = self::prepare_table_name( $table );
196 160 $column_keys = '';
197 - $column_values = array();
161 + $column_values = '';
198 162 $sql = '';
199 163 $last_key = array_key_last( $request );
200 164 $first_key = array_key_first( $request );
201 165 foreach ( $request as $k => $value ) {
@@ -200,48 +164,31 @@
200 164 $first_key = array_key_first( $request );
201 165 foreach ( $request as $k => $value ) {
202 166 $keys = array_keys( $value );
203 167
204 - $value_placeholder = array();
205 -
206 168 // Prepare column keys & values.
207 169 foreach ( $keys as $v ) {
208 170 $column_keys .= sanitize_key( $v ) . ',';
209 - $sanitize_value = $value[ $v ];
210 - if ( $sanitize_value && $do_sanitize ) {
211 - $sanitize_value = sanitize_text_field( $sanitize_value );
212 - }
213 -
214 - $column_values[] = $sanitize_value;
215 -
216 - $value_placeholder[] = '%s';
171 + $sanitize_value = is_null( $value[ $v ] ) ? $value[ $v ] : sanitize_text_field( $value[ $v ] );
172 + $column_values .= is_numeric( $sanitize_value ) ? $sanitize_value . ',' : "'$sanitize_value'" . ',';
217 173 }
218 -
219 - $value_placeholder = implode( ',', $value_placeholder );
220 -
221 174 // Trim trailing comma.
222 175 $column_keys = rtrim( $column_keys, ',' );
223 - $column_values = $wpdb->prepare( $value_placeholder, $column_values ); // Escape values.
224 -
176 + $column_values = rtrim( $column_values, ',' );
225 177 if ( $first_key === $k ) {
226 - $sql .= "INSERT INTO
227 - {$table}
228 - ($column_keys) VALUES ($column_values)
229 - ";
230 -
178 + $sql .= "INSERT INTO {$table} ($column_keys) VALUES ($column_values)";
231 179 if ( count( $request ) > 1 ) {
232 180 $sql .= ',';
233 181 }
234 182 } elseif ( $last_key == $k ) {
235 - $sql .= "( $column_values )";
183 + $sql .= "($column_values)";
236 184 } else {
237 - $sql .= "( $column_values ),";
238 -
185 + $sql .= "($column_values),";
239 186 }
240 187
241 188 // Reset keys & values to avoid duplication.
242 189 $column_keys = '';
243 - $column_values = array();
190 + $column_values = '';
244 191 }
245 192
246 193 $wpdb->query( $sql );//phpcs:ignore
247 194
@@ -246,21 +193,11 @@
246 193 $wpdb->query( $sql );//phpcs:ignore
247 194
248 195 // If error occurred then throw new exception.
249 196 if ( $wpdb->last_error ) {
250 - throw new \Exception( esc_html( $wpdb->last_error ) );
197 + throw new \Exception( $wpdb->last_error );
251 198 }
252 199
253 - if ( $return_ids ) {
254 - $query_ids = $wpdb->get_results(
255 - //phpcs:ignore
256 - "SELECT ID FROM {$table} WHERE ID >= LAST_INSERT_ID()",
257 - 'ARRAY_N'
258 - );
259 -
260 - return $query_ids;
261 - }
262 -
263 200 return true;
264 201 }
265 202
266 203 /**
@@ -269,9 +206,8 @@
269 206 * If the operator is IN then make the clause like `WHERE column_name IN (value1, value2, ...)`
270 207 * Otherwise the clause would be `WHERE column_name = 'value'`
271 208 *
272 209 * @since 3.0.0
273 - * @since 3.9.7 added prepared statement for value.
274 210 *
275 211 * @param array $where The where clause array. e.g. array( 'id', 'IN', array(1, 2, 3) ) or array( 'id', '=', 1 ).
276 212 *
277 213 * @return string
@@ -278,131 +214,42 @@
278 214 */
279 215 public static function make_clause( array $where ) {
280 216 list ( $field, $operator, $value ) = $where;
281 217
282 - $upper_operator = strtoupper( $operator );
283 -
284 - if ( in_array( $upper_operator, array( 'IN', 'NOT IN' ), true ) ) {
218 + if ( 'IN' === strtoupper( $operator ) ) {
285 219 $value = '(' . self::prepare_in_clause( $value ) . ')';
286 - } elseif ( in_array( $upper_operator, array( 'BETWEEN', 'NOT BETWEEN' ), true ) ) {
287 - $value = array_map( fn( $val ) => self::prepare_value( $val ), $value );
288 - $value = implode( ' AND ', $value );
289 - } elseif ( strtoupper( $value ) === 'NULL' ) {
290 - $value = 'NULL';
291 - } else {
292 - $value = self::prepare_value( $value );
293 220 }
294 221
295 - return "{$field} {$upper_operator} {$value}";
222 + return "{$field} {$operator} {$value}";
296 223 }
297 224
298 225 /**
299 - * Check operator is supported.
226 + * Build where clause string
300 227 *
301 - * @since 3.5.0
228 + * @param array $where assoc array with field and value.
229 + * @return string
302 230 *
303 - * @param string $operator operator like =, !=, > , < etc.
304 - *
305 - * @return boolean
306 - */
307 - public static function is_support_operator( $operator ) {
308 - $operator = strtoupper( $operator );
309 -
310 - return in_array(
311 - $operator,
312 - array(
313 - '=',
314 - '!=',
315 - '<>',
316 - '>',
317 - '<',
318 - '>=',
319 - '<=',
320 - 'LIKE',
321 - 'NOT LIKE',
322 - 'IN',
323 - 'NOT IN',
324 - 'IS',
325 - 'IS NOT',
326 - 'BETWEEN',
327 - 'NOT BETWEEN',
328 - 'RAW',
329 - ),
330 - true
331 - );
332 - }
333 -
334 - /**
335 - * Prepare where clause string
336 - *
337 231 * @since 2.0.9
338 - * @since 3.0.0 Null value support added, if need to check with null: [name => 'null']
339 - * @since 3.5.0 All common SQL comparison operators support added.
340 - * $where = array(
341 - * 'id' => ['BETWEEN', [10, 20]],
342 - * 'status' => ['!=', 'draft'],
343 - * 'email' => ['LIKE', '%@gmail.com'],
344 - * 'type' => ['NOT IN', ['test', 'sample']],
345 - * 'age' => ['>=', 18],
346 - * 'active' => true,
347 - * 'deleted_at' => 'null',
348 - * 'role' => 'editor',
349 - * )
350 - * @since 3.6.0 Added raw query support. Make sure the query written is not sql injectable.
351 - * $where = array(
352 - * 'username = %s' => [ 'RAW' , array( 'test' ) ]
353 - * )
354 - * @param array $where assoc array with field and value.
355 232 *
356 - * @return string
233 + * @since 3.0.0
234 + * Null value support added, if need to check with
235 + * null: [name => 'null'] we can pass
357 236 */
358 - public static function prepare_where_clause( array $where ) {
237 + public static function build_where_clause( array $where ) {
359 238 $arr = array();
360 239 foreach ( $where as $field => $value ) {
361 - $operator = null;
362 - if ( is_array( $value ) && isset( $value[0] ) && is_string( $value[0] ) && self::is_support_operator( $value[0] ) ) {
363 - $operator = strtoupper( $value[0] );
364 - $val = $value[1];
365 - switch ( $operator ) {
366 - case 'IN':
367 - case 'NOT IN':
368 - if ( is_array( $val ) ) {
369 - $clause = array( $field, $operator, $val );
370 - }
371 - break;
372 -
373 - case 'BETWEEN':
374 - case 'NOT BETWEEN':
375 - if ( is_array( $val ) && count( $val ) === 2 ) {
376 - $clause = array( $field, $operator, $val );
377 - }
378 - break;
379 -
380 - case 'IS':
381 - case 'IS NOT':
382 - $val = strtoupper( $val ) === 'NULL' ? 'NULL' : $val;
383 - $clause = array( $field, $operator, $val );
384 - break;
385 - case 'RAW':
386 - $final_query = '';
387 - if ( ! empty( $field ) && is_array( $val ) ) {
388 - $final_query = self::prepare_raw_query( $field, $val );
389 - }
390 - $clause = $final_query;
391 - break;
392 - default: // =, !=, <, >, <=, >=, LIKE, NOT LIKE, <>
393 - $clause = array( $field, $operator, $val );
394 - break;
240 + if ( is_array( $value ) ) {
241 + $value = array( $field, 'IN', $value );
242 + } else {
243 + if ( 'null' == $value ) {
244 + $value = array( $field, 'IS', 'NULL' );
245 + } else {
246 + $value = is_numeric( $value ) ? $value : "'" . $value . "'";
247 + $value = array( $field, '=', $value );
395 248 }
396 - } elseif ( is_array( $value ) ) {
397 - $clause = array( $field, 'IN', $value );
398 - } elseif ( 'null' === strtolower( $value ) ) {
399 - $clause = array( $field, 'IS', 'NULL' );
400 - } else {
401 - $clause = array( $field, '=', $value );
402 249 }
403 250
404 - $arr[] = ( 'RAW' === $operator ) ? $clause : self::make_clause( $clause );
251 + $arr[] = self::make_clause( $value );
405 252 }
406 253
407 254 return implode( ' AND ', $arr );
408 255 }
@@ -407,41 +254,10 @@
407 254 return implode( ' AND ', $arr );
408 255 }
409 256
410 257 /**
411 - * Prepare raw query for query helper.
258 + * Build like clause string with or
412 259 *
413 - * @since 3.6.0
414 - *
415 - * @param string $raw_query the query to execute.
416 - * @param array $parameters the parameters to pass to the query.
417 - *
418 - * @return string
419 - */
420 - public static function prepare_raw_query( $raw_query, $parameters ) {
421 - /**
422 - * Not allowed unsafe SQL control characters [;, --, /*]
423 - * Allowed safe SQL control characters only.
424 - */
425 - $is_safe = preg_match( '/^[a-zA-Z0-9_%\.=\s\'"<>\(\)\-\[\],]+$/', $raw_query );
426 - if ( ! $is_safe ) {
427 - return '';
428 - }
429 -
430 - if ( ! count( $parameters ) ) {
431 - return $raw_query;
432 - }
433 -
434 - global $wpdb;
435 -
436 - $final_query = $wpdb->prepare( $raw_query, $parameters ); //phpcs:ignore
437 -
438 - return $final_query;
439 - }
440 -
441 - /**
442 - * Prepare like clause string with or
443 - *
444 260 * @since 1.0.0
445 261 *
446 262 * @param array $where assoc array with field and value.
447 263 * @param string $relation default is OR.
@@ -447,9 +263,9 @@
447 263 * @param string $relation default is OR.
448 264 *
449 265 * @return string
450 266 */
451 - public static function prepare_like_clause( array $where, $relation = 'OR' ) {
267 + public static function build_like_clause( array $where, $relation = 'OR' ) {
452 268 global $wpdb;
453 269
454 270 $like_conditions = array();
455 271
@@ -493,17 +309,17 @@
493 309 if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) {
494 310 return false;
495 311 }
496 312
497 - $where = self::prepare_where_clause( self::sanitize_assoc_array( $where ) );
313 + $where = self::build_where_clause( self::sanitize_assoc_array( $where ) );
498 314
499 315 global $wpdb;
500 316 $ids = $wpdb->get_col( "SELECT comment_id FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore
501 317
502 318 if ( is_array( $ids ) && count( $ids ) ) {
503 - $in_clause = self::prepare_in_clause( $ids );
319 + $ids_str = "'" . implode( "','", $ids ) . "'";
504 320 // delete comment metas.
505 - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN({$in_clause}) " ) );//phpcs:ignore
321 + $wpdb->query( "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN({$ids_str}) " );//phpcs:ignore
506 322 // delete comment.
507 323 $wpdb->query( "DELETE FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore
508 324
509 325 return true;
@@ -525,17 +341,17 @@
525 341 if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) {
526 342 return false;
527 343 }
528 344
529 - $where = self::prepare_where_clause( self::sanitize_assoc_array( $where ) );
345 + $where = self::build_where_clause( self::sanitize_assoc_array( $where ) );
530 346
531 347 global $wpdb;
532 348 $ids = $wpdb->get_col( "SELECT id FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore
533 349
534 350 if ( is_array( $ids ) && count( $ids ) ) {
535 - $in_clause = self::prepare_in_clause( $ids );
351 + $ids_str = "'" . implode( "','", $ids ) . "'";
536 352 // delete post metas.
537 - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN({$in_clause}) " ) );//phpcs:ignore
353 + $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN({$ids_str}) " );//phpcs:ignore
538 354 // delete post.
539 355 $wpdb->query( "DELETE FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore
540 356
541 357 return true;
@@ -544,254 +360,8 @@
544 360 return false;
545 361 }
546 362
547 363 /**
548 - * Prepare SELECT clause.
549 - *
550 - * @since 3.8.0
551 - *
552 - * @param mixed $columns Column name or list of columns.
553 - *
554 - * @return string
555 - */
556 - protected static function prepare_select_clause( $columns = '' ) {
557 - if ( empty( $columns ) ) {
558 - return '*';
559 - }
560 -
561 - if ( is_array( $columns ) ) {
562 - return implode( ',', $columns );
563 - }
564 -
565 - return $columns;
566 - }
567 -
568 - /**
569 - * Prepare JOIN clause.
570 - *
571 - * @since 3.8.0
572 - *
573 - * @param array $joins Array of joins, each item:
574 - * - type: join type (LEFT, INNER, RIGHT etc).
575 - * - table: table name.
576 - * - on: join condition.
577 - *
578 - * @return string
579 - */
580 - protected static function prepare_join_clause( $joins = array() ) {
581 - if ( empty( $joins ) || ! is_array( $joins ) ) {
582 - return '';
583 - }
584 -
585 - $clause = '';
586 - foreach ( $joins as $join ) {
587 - $type = strtoupper( $join['type'] ?? 'LEFT' );
588 - $table = self::prepare_table_name( $join['table'] );
589 - $on = $join['on'];
590 - if ( $table && $on ) {
591 - $clause .= " {$type} JOIN {$table} ON {$on} ";
592 - }
593 - }
594 -
595 - return $clause;
596 - }
597 -
598 - /**
599 - * Prepare WHERE + SEARCH clause together.
600 - *
601 - * @since 3.8.0
602 - *
603 - * @param array $where Array of key => value pairs.
604 - * @param array $search Array of key => search string pairs.
605 - * @param string $search_operator Operator for search conditions (AND/OR).
606 - *
607 - * @return string
608 - */
609 - protected static function prepare_where_search_clause( $where = array(), $search = array(), $search_operator = 'OR' ) {
610 - $clauses = array();
611 -
612 - // Handle WHERE conditions.
613 - if ( ! empty( $where ) && is_array( $where ) ) {
614 - $clauses[] = self::prepare_where_clause( $where );
615 - }
616 -
617 - // Handle SEARCH conditions.
618 - if ( ! empty( $search ) && is_array( $search ) ) {
619 - $clauses[] = self::prepare_like_clause( $search, $search_operator );
620 - }
621 -
622 - if ( empty( $clauses ) ) {
623 - return '';
624 - }
625 -
626 - return 'WHERE ' . implode( ' AND ', $clauses );
627 - }
628 -
629 - /**
630 - * Prepare order by clause.
631 - *
632 - * @since 3.8.0
633 - *
634 - * @param string $orderby order by column.
635 - * @param string $order order ASC|DESC.
636 - *
637 - * @return string
638 - */
639 - protected static function prepare_order_clause( $orderby = '', $order = 'DESC' ) {
640 - if ( empty( $orderby ) ) {
641 - return '';
642 - }
643 -
644 - // Allowed: foo, foo_bar, _foo, foo.bar etc.
645 - if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9._]*$/', $orderby ) ) {
646 - return '';
647 - }
648 -
649 - $order = strtoupper( $order ) === 'ASC' ? 'ASC' : 'DESC';
650 - return "ORDER BY {$orderby} {$order}";
651 - }
652 -
653 - /**
654 - * Prepare LIMIT clause.
655 - *
656 - * @since 3.8.0
657 - *
658 - * @param int $limit limit.
659 - * @param int $offset offset.
660 - *
661 - * @return string
662 - */
663 - protected static function prepare_limit_clause( $limit = 0, $offset = 0 ) {
664 - if ( $limit < 1 || $offset < 0 ) {
665 - return '';
666 - }
667 -
668 - return sprintf( 'LIMIT %d OFFSET %d', $limit, $offset );
669 - }
670 -
671 - /**
672 - * Run a database query with flexible arguments.
673 - *
674 - * Supports SELECT, JOIN, WHERE, SEARCH, GROUP BY, HAVING, ORDER BY,
675 - * LIMIT (pagination), and can return count, single row or full result set.
676 - *
677 - * @since 3.8.0
678 - *
679 - * @param string $table table name.
680 - * @param array $args {
681 - * Query arguments.
682 - *
683 - * @type string|array $select Columns to select, defaults to "*".
684 - * @type string $alias Table alias.
685 - * @type array $where WHERE conditions [ 'col' => 'val', ... ].
686 - * @type array $search LIKE conditions [ 'col' => 'keyword', ... ].
687 - * @type array $joins JOIN clauses [ [ 'type' => 'LEFT', 'table' => '...', 'on' => '...' ], ... ].
688 - * @type string $groupby GROUP BY clause.
689 - * @type string $having HAVING clause.
690 - * @type string $orderby Column to order by.
691 - * @type string $order ASC|DESC, default DESC.
692 - * @type int $limit Limit.
693 - * @type int $offset Offset.
694 - * @type int $per_page Results per page for pagination.
695 - * @type int $page Current page number for pagination.
696 - * @type bool $count If true, return only total count.
697 - * @type bool $single If true, return only single row.
698 - * @type string $output OBJECT|ARRAY_A default is OBJECT.
699 - * }
700 - *
701 - * @return mixed Result set, count or single row.
702 - */
703 - public static function query( $table, $args = array() ) {
704 - // Flags.
705 - $count = isset( $args['count'] ) && $args['count'];
706 - $single = isset( $args['single'] ) && $args['single'];
707 - $pagination = isset( $args['per_page'], $args['page'] );
708 - $output = $args['output'] ?? 'OBJECT';
709 -
710 - // Primary table.
711 - $table = self::prepare_table_name( $table );
712 - $alias = $args['alias'] ?? 'main';
713 - $table_with_alias = "{$table} AS {$alias}";
714 -
715 - // Build clauses.
716 - $select_clause = self::prepare_select_clause( $args['select'] ?? '' );
717 - $join_clause = self::prepare_join_clause( $args['joins'] ?? array() );
718 - $where_clause = self::prepare_where_search_clause( $args['where'] ?? array(), $args['search'] ?? array() );
719 - $groupby_clause = empty( $args['groupby'] ) ? '' : 'GROUP BY ' . $args['groupby'];
720 - $having_clause = empty( $args['having'] ) ? '' : 'HAVING ' . $args['having'];
721 - $order_by_clause = self::prepare_order_clause( $args['orderby'] ?? '', $args['order'] ?? 'DESC' );
722 -
723 - global $wpdb;
724 -
725 - // Count only.
726 - if ( $count ) {
727 - $sql_query = "SELECT COUNT(*)
728 - FROM {$table_with_alias}
729 - {$join_clause}
730 - {$where_clause}
731 - {$groupby_clause}
732 - {$having_clause}";
733 -
734 - return (int) $wpdb->get_var( $sql_query ); //phpcs:ignore
735 - }
736 -
737 - // Single record.
738 - if ( $single ) {
739 - $sql_query = "SELECT {$select_clause}
740 - FROM {$table_with_alias}
741 - {$join_clause}
742 - {$where_clause}
743 - {$groupby_clause}
744 - {$having_clause}
745 - {$order_by_clause}
746 - LIMIT 1";
747 -
748 - return $wpdb->get_row( $sql_query, $output ); //phpcs:ignore
749 - }
750 -
751 - $calc_found_rows = $pagination ? 'SQL_CALC_FOUND_ROWS' : '';
752 - $limit = isset( $args['limit'] ) ? (int) $args['limit'] : 0;
753 - $offset = isset( $args['offset'] ) ? (int) $args['offset'] : 0;
754 -
755 - if ( $pagination ) {
756 - $limit = (int) $args['per_page'];
757 - $offset = (int) ( $args['page'] - 1 ) * $limit;
758 - }
759 -
760 - $limit_clause = self::prepare_limit_clause( $limit, $offset );
761 -
762 - $sql_query = "SELECT {$calc_found_rows} {$select_clause}
763 - FROM {$table_with_alias}
764 - {$join_clause}
765 - {$where_clause}
766 - {$groupby_clause}
767 - {$having_clause}
768 - {$order_by_clause}
769 - {$limit_clause}";
770 -
771 - $rows = $wpdb->get_results( $sql_query, $output ); //phpcs:ignore
772 -
773 - if ( $pagination ) {
774 - $has_records = is_array( $rows ) && count( $rows );
775 - $page = (int) $args['page'];
776 - $per_page = (int) $args['per_page'];
777 - $total_record = (int) $has_records ? $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0;
778 - $total_page = (int) ceil( $total_record / $per_page );
779 -
780 - return array(
781 - 'total_record' => (int) $total_record,
782 - 'per_page' => $per_page,
783 - 'current_page' => $page,
784 - 'total_page' => $total_page,
785 - 'data' => $rows,
786 - );
787 -
788 - }
789 -
790 - return $rows;
791 - }
792 -
793 - /**
794 364 * Get a single row from any table with where clause
795 365 *
796 366 * @param string $table table name with prefix.
797 367 *
@@ -804,10 +374,9 @@
804 374 */
805 375 public static function get_row( string $table, array $where, string $order_by, string $order = 'DESC', string $output = 'OBJECT' ) {
806 376 global $wpdb;
807 377
808 - $table = self::prepare_table_name( $table );
809 - $where_clause = self::prepare_where_clause( $where );
378 + $where_clause = self::build_where_clause( $where );
810 379
811 380 //phpcs:disable
812 381 $query = $wpdb->prepare(
813 382 "SELECT *
@@ -844,14 +413,9 @@
844 413 */
845 414 public static function get_all( string $table, array $where, string $order_by, $limit = 1000, string $order = 'DESC', string $output = 'OBJECT' ) {
846 415 global $wpdb;
847 416
848 - $table = self::prepare_table_name( $table );
849 - $where_clause = self::prepare_where_clause( $where );
850 - if ( ! empty( $where_clause ) ) {
851 - $where_clause = "WHERE {$where_clause}";
852 - }
853 -
417 + $where_clause = self::build_where_clause( $where );
854 418 $limit = (int) sanitize_text_field( $limit );
855 419 $limit_clause = ( -1 === $limit ) ? '' : 'LIMIT ' . $limit;
856 420
857 421 //phpcs:disable
@@ -856,9 +420,9 @@
856 420
857 421 //phpcs:disable
858 422 $query = "SELECT *
859 423 FROM {$table}
860 - {$where_clause}
424 + WHERE {$where_clause}
861 425 ORDER BY {$order_by} {$order}
862 426 {$limit_clause}";
863 427
864 428 return $wpdb->get_results(
@@ -883,10 +447,8 @@
883 447 * @return bool true on success, false on failure
884 448 */
885 449 public static function update_where_in( string $table, array $data, string $where_in, string $where_col = 'ID' ) {
886 450 global $wpdb;
887 -
888 - $table = self::prepare_table_name( $table );
889 451 if ( empty( $where_in ) || empty( $where_col ) ) {
890 452 return false;
891 453 }
892 454 $set_clause = self::prepare_set_clause( $data );
@@ -937,42 +499,33 @@
937 499 return rtrim( $set, ',' );
938 500 }
939 501
940 502 /**
941 - * Prepare value before using in query.
942 - *
943 - * @since 3.9.7
944 - *
945 - * @param string|int|float $value the value to prepare.
946 - *
947 - * @return mixed
948 - */
949 - public static function prepare_value( $value ) {
950 - global $wpdb;
951 - $escaped_value = null;
952 - if ( is_int( $value ) ) {
953 - $escaped_value = $wpdb->prepare( '%d', $value );
954 - } elseif ( is_float( $value ) ) {
955 - list( $whole, $decimal ) = explode( '.', $value );
956 - $expression = '%.'. strlen( $decimal ) . 'f';
957 - $escaped_value = $wpdb->prepare( $expression, $value );
958 - } else {
959 - $escaped_value = $wpdb->prepare( '%s', $value );
960 - }
961 - return $escaped_value;
962 - }
963 -
964 - /**
965 503 * Make sanitized SQL IN clause value from an array
966 504 *
505 + * @param array $arr a sequentital array.
506 + * @return string
967 507 * @since 2.1.1
968 - *
969 - * @param array $arr a sequential array.
970 - *
971 - * @return string
972 508 */
973 509 public static function prepare_in_clause( array $arr ) {
974 - $escaped = array_map( fn( $value ) => self::prepare_value( $value ), $arr );
510 + $escaped = array_map(
511 + function( $value ) {
512 + global $wpdb;
513 + $escaped_value = null;
514 + if ( is_int( $value ) ) {
515 + $escaped_value = $wpdb->prepare( '%d', $value );
516 + } else if( is_float( $value ) ) {
517 + list( $whole, $decimal ) = explode( '.', $value );
518 + $expression = '%.'. strlen( $decimal ) . 'f';
519 + $escaped_value = $wpdb->prepare( $expression, $value );
520 + } else {
521 + $escaped_value = $wpdb->prepare( '%s', $value );
522 + }
523 + return $escaped_value;
524 + },
525 + $arr
526 + );
527 +
975 528 return implode( ',', $escaped );
976 529 }
977 530
978 531 /**
@@ -985,11 +538,9 @@
985 538 * @return bool
986 539 */
987 540 public static function table_exists( $table ) {
988 541 global $wpdb;
989 -
990 - $table = self::prepare_table_name( $table );
991 - $sql = "SHOW TABLES LIKE '{$table}'";
542 + $sql = "SHOW TABLES LIKE '{$table}'";
992 543 return $wpdb->get_var( $sql ) === $table;
993 544 }
994 545
995 546 /**
@@ -1003,11 +554,9 @@
1003 554 * @return bool
1004 555 */
1005 556 public static function column_exist( $table, $column ) {
1006 557 global $wpdb;
1007 -
1008 - $table = self::prepare_table_name( $table );
1009 - $sql = "SHOW COLUMNS FROM {$table} LIKE '{$column}'";
558 + $sql = "SHOW COLUMNS FROM {$table} LIKE '{$column}'";
1010 559 return $wpdb->get_var( $sql ) === $column;
1011 560 }
1012 561
1013 562 /**
@@ -1015,9 +564,8 @@
1015 564 *
1016 565 * Argument should be SQL escaped.
1017 566 *
1018 567 * @since 3.0.0
1019 - * @since 3.8.2 param $get_row added.
1020 568 *
1021 569 * @param string $primary_table The primary table name with prefix.
1022 570 * @param array $joining_tables An array of join relations. Each relation should be an array with keys 'type', 'table', 'on'.
1023 571 * @param array $select_columns An array of columns to select.
@@ -1027,9 +575,8 @@
1027 575 * @param int $limit Maximum number of rows to return.
1028 576 * @param int $offset Offset for pagination.
1029 577 * @param string $order DESC or ASC, default is DESC.
1030 578 * @param string $output Expected output type, default is OBJECT.
1031 - * @param bool $get_row Get a single row.
1032 579 *
1033 580 * @throws \Exception If an error occurred during the query execution.
1034 581 *
1035 582 * @return mixed Based on output param, default OBJECT.
@@ -1043,44 +590,74 @@
1043 590 string $order_by = '',
1044 591 $limit = 10,
1045 592 $offset = 0,
1046 593 string $order = 'DESC',
1047 - string $output = 'OBJECT',
1048 - bool $get_row = false
594 + string $output = 'OBJECT'
1049 595 ) {
1050 596 global $wpdb;
1051 597
1052 - $select_clause = implode( ', ', $select_columns );
1053 - $from_clause = self::prepare_table_name( $primary_table );
1054 - $join_clauses = self::prepare_join_clause( $joining_tables );
1055 - $where_clause = self::prepare_where_search_clause( $where, $search );
1056 - $order_by_clause = self::prepare_order_clause( $order_by, $order );
1057 - $limit_clause = self::prepare_limit_clause( $limit, $offset );
598 + $select_clause = implode(', ', $select_columns);
1058 599
1059 - $query = "SELECT SQL_CALC_FOUND_ROWS
600 + $from_clause = $primary_table;
601 +
602 + $join_clauses = '';
603 + foreach ($joining_tables as $relation) {
604 + $join_clauses .= " {$relation['type']} JOIN {$relation['table']} ON {$relation['on']}";
605 + }
606 +
607 + $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
608 +
609 + if (!empty($search)) {
610 + $search_clause = self::build_like_clause( $search );
611 + // foreach ($search as $column => $value) {
612 + // $search_clauses[] = $wpdb->prepare("{$column} LIKE %s", '%' . $wpdb->esc_like($value) . '%');
613 + // }
614 + $where_clause .= !empty($where_clause) ? ' AND (' . $search_clause . ')' : 'WHERE ' . $search_clause;
615 + }
616 +
617 + $order_by_clause = !empty($order_by) ? "ORDER BY {$order_by} {$order}" : '';
618 +
619 + // Query to get total count.
620 + $count_query = "
621 + SELECT COUNT(*) as total_count
622 + FROM {$from_clause}
623 + {$join_clauses}
624 + {$where_clause}
625 + ";
626 +
627 + $total_count = $wpdb->get_var($count_query);
628 +
629 + if (empty($limit) && empty($offset)) {
630 + $query = "SELECT
1060 631 {$select_clause}
1061 632 FROM {$from_clause}
1062 633 {$join_clauses}
1063 634 {$where_clause}
635 + {$order_by_clause}";
636 + } else {
637 + $query = $wpdb->prepare(
638 + "SELECT {$select_clause}
639 + FROM {$from_clause}
640 + {$join_clauses}
641 + {$where_clause}
1064 642 {$order_by_clause}
1065 - {$limit_clause}";
643 + LIMIT %d OFFSET %d",
644 + $limit,
645 + $offset
646 + );
647 + }
1066 648
1067 - if ( $get_row ) {
1068 - return $wpdb->get_row( $query, $output );
1069 - }
1070 649
1071 - $results = $wpdb->get_results( $query, $output );
1072 - $has_records = is_array( $results ) && count( $results );
1073 - $total_count = $has_records ? (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0;
650 + $results = $wpdb->get_results($query, $output);
1074 651
1075 652 // Throw exception if error occurred.
1076 - if ( $wpdb->last_error ) {
1077 - throw new \Exception( $wpdb->last_error );
653 + if ($wpdb->last_error) {
654 + throw new \Exception($wpdb->last_error);
1078 655 }
1079 656
1080 657 // Prepare response array.
1081 658 $response = array(
1082 - 'total_count' => $total_count,
659 + 'total_count' => (int) $total_count,
1083 660 'results' => $results,
1084 661 );
1085 662
1086 663 return $response;
@@ -1102,11 +679,19 @@
1102 679 */
1103 680 public static function get_count( $table, $where = [], $search = [], $count_column = 'id' ): int {
1104 681 global $wpdb;
1105 682
1106 - $table = self::prepare_table_name( $table );
1107 - $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' );
683 + $where_clause = !empty( $where ) ? 'WHERE ' . self::build_where_clause( $where ) : '';
684 + $search_clause = !empty( $search ) ? self::build_like_clause( $search, 'AND' ) : '';
1108 685
686 + if ( !empty( $search_clause ) ) {
687 + if ( !empty( $where_clause ) ) {
688 + $where_clause .= ' AND (' . $search_clause . ')';
689 + } else {
690 + $where_clause = 'WHERE ' . $search_clause;
691 + }
692 + }
693 +
1109 694 $count = $wpdb->get_var(
1110 695 "SELECT COUNT($count_column)
1111 696 FROM $table
1112 697 {$where_clause}"
@@ -1137,12 +722,26 @@
1137 722 */
1138 723 public static function get_joined_count(string $primary_table, array $joining_tables, array $where = [], array $search = [], string $count_column = '*'): int {
1139 724 global $wpdb;
1140 725
1141 - $from_clause = self::prepare_table_name( $primary_table );
1142 - $join_clauses = self::prepare_join_clause( $joining_tables );
1143 - $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' );
726 + $from_clause = $primary_table;
727 +
728 + $join_clauses = '';
729 + foreach ($joining_tables as $relation) {
730 + $join_clauses .= " {$relation['type']} JOIN {$relation['table']} ON {$relation['on']}";
731 + }
732 +
733 + $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
734 + $search_clause = !empty($search) ? self::build_like_clause($search, 'AND') : '';
1144 735
736 + if (!empty($search_clause)) {
737 + if (!empty($where_clause)) {
738 + $where_clause .= ' AND (' . $search_clause . ')';
739 + } else {
740 + $where_clause = 'WHERE ' . $search_clause;
741 + }
742 + }
743 +
1145 744 $count_query = "
1146 745 SELECT COUNT($count_column) as total_count
1147 746 FROM {$from_clause}
1148 747 {$join_clauses}
@@ -1148,13 +747,13 @@
1148 747 {$join_clauses}
1149 748 {$where_clause}
1150 749 ";
1151 750
1152 - $total_count = $wpdb->get_var( $count_query );
751 + $total_count = $wpdb->get_var($count_query);
1153 752
1154 753 // If error occurred then throw new exception.
1155 - if ( $wpdb->last_error ) {
1156 - throw new \Exception( $wpdb->last_error );
754 + if ($wpdb->last_error) {
755 + throw new \Exception($wpdb->last_error);
1157 756 }
1158 757
1159 758 return (int) $total_count;
1160 759 }
@@ -1176,40 +775,56 @@
1176 775 * @throws \Exception Throw exception if error occurred during query execution.
1177 776 *
1178 777 * @return mixed Based on output param, default OBJECT.
1179 778 */
1180 - public static function get_all_with_search( string $table, array $where, array $search, string $order_by, $limit = 10, $offset = 0, string $order = 'DESC', string $output = 'OBJECT' ): array {
779 + public static function get_all_with_search(string $table, array $where, array $search, string $order_by, $limit = 10, $offset = 0, string $order = 'DESC', string $output = 'OBJECT'): array {
1181 780 global $wpdb;
1182 -
1183 - $table = self::prepare_table_name( $table );
1184 - $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' );
1185 - $order_by_clause = self::prepare_order_clause( $order_by, $order );
1186 - $limit_clause = self::prepare_limit_clause( $limit, $offset );
1187 781
782 + $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
783 + $search_clause = !empty($search) ? self::build_like_clause($search, 'AND') : '';
784 +
785 + if (!empty($search_clause)) {
786 + if (!empty($where_clause)) {
787 + $where_clause .= ' AND (' . $search_clause . ')';
788 + } else {
789 + $where_clause = 'WHERE ' . $search_clause;
790 + }
791 + }
792 +
793 + // Query to get total count
794 + $count_query = "
795 + SELECT COUNT(*)
796 + FROM {$table}
797 + {$where_clause}
798 + ";
799 + $total_count = $wpdb->get_var($count_query);
800 +
1188 801 // If error occurred then throw new exception.
1189 - if ( $wpdb->last_error ) {
1190 - throw new \Exception( $wpdb->last_error );
802 + if ($wpdb->last_error) {
803 + throw new \Exception($wpdb->last_error);
1191 804 }
1192 805
1193 - $query = "SELECT SQL_CALC_FOUND_ROWS *
806 + $query = $wpdb->prepare(
807 + "SELECT *
1194 808 FROM {$table}
1195 809 {$where_clause}
1196 - {$order_by_clause}
1197 - {$limit_clause}";
810 + ORDER BY {$order_by} {$order}
811 + LIMIT %d OFFSET %d",
812 + $limit,
813 + $offset
814 + );
1198 815
1199 - $results = $wpdb->get_results( $query, $output );
1200 - $has_records = is_array( $results ) && count( $results );
1201 - $total_count = $has_records ? (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0;
816 + $results = $wpdb->get_results($query, $output);
1202 817
1203 818 // If error occurred then throw new exception.
1204 - if ( $wpdb->last_error ) {
1205 - throw new \Exception( $wpdb->last_error );
819 + if ($wpdb->last_error) {
820 + throw new \Exception($wpdb->last_error);
1206 821 }
1207 822
1208 823 // Prepare response array.
1209 824 $response = array(
1210 - 'results' => $results,
1211 - 'total_count' => $total_count,
825 + 'results' => $results,
826 + 'total_count' => (int) $total_count,
1212 827 );
1213 828
1214 829 return $response;
1215 830 }
@@ -1230,9 +845,9 @@
1230 845 case 'today':
1231 846 $period_clause = "AND DATE($column) = CURDATE()";
1232 847 break;
1233 848 case 'monthly':
1234 - $period_clause = "AND MONTH($column) = MONTH(CURDATE()) AND YEAR($column) = YEAR(CURDATE())";
849 + $period_clause = "AND MONTH($column) = MONTH(CURDATE())";
1235 850 break;
1236 851 case 'yearly':
1237 852 $period_clause = "AND YEAR($column) = YEAR(CURDATE())";
1238 853 break;
@@ -1249,144 +864,7 @@
1249 864 break;
1250 865 }
1251 866
1252 867 return $period_clause;
1253 - }
1254 -
1255 - /**
1256 - * Get last executed SQL query.
1257 - *
1258 - * @since 3.6.0
1259 - *
1260 - * @return string
1261 - */
1262 - public static function get_last_query(){
1263 - global $wpdb;
1264 - return $wpdb->last_query;
1265 - }
1266 -
1267 - /**
1268 - * Get table prefix.
1269 - *
1270 - * @since 3.7.0
1271 - *
1272 - * @return string
1273 - */
1274 - public static function get_table_prefix() {
1275 - global $wpdb;
1276 - return $wpdb->prefix;
1277 - }
1278 -
1279 - /**
1280 - * Prepare table name with prefix.
1281 - *
1282 - * @since 3.7.0
1283 - *
1284 - * @param string $table_name table name.
1285 - *
1286 - * @return string
1287 - */
1288 - public static function prepare_table_name( string $table_name ) {
1289 - $table_prefix = self::get_table_prefix();
1290 - if ( strpos( $table_name,$table_prefix ) !== 0 ) {
1291 - $table_name = $table_prefix . $table_name;
1292 - }
1293 -
1294 - return $table_name;
1295 - }
1296 -
1297 - /**
1298 - * Duplicate a row with modification callback support.
1299 - *
1300 - * @since 3.7.0
1301 - *
1302 - * @param string $table_name name of the database table (with prefix if needed).
1303 - * @param array $where associative array of WHERE conditions.
1304 - * @param callable|null $modifier optional callback to modify or exclude fields before insertion.
1305 - *
1306 - * @return int|WP_Error New row ID on success, or WP_Error on failure.
1307 - */
1308 - public static function duplicate_row( $table_name, array $where, ?callable $modifier = null ) {
1309 - global $wpdb;
1310 -
1311 - $table_name = self::prepare_table_name( $table_name );
1312 - if ( empty( $where ) ) {
1313 - return new \WP_Error( 'missing_where', 'No WHERE condition provided.' );
1314 - }
1315 -
1316 - $where_clause = self::prepare_where_clause( $where );
1317 - $sql = $wpdb->prepare( "SELECT * FROM `$table_name` WHERE {$where_clause} LIMIT %d", 1 );
1318 - $row = $wpdb->get_row( $sql, ARRAY_A );
1319 -
1320 - if ( ! $row ) {
1321 - return new \WP_Error( 'not_found', 'No matching row found to duplicate.' );
1322 - }
1323 -
1324 - // Apply user-defined modifications (ex: remove ID, change field value)
1325 - if ( is_callable( $modifier ) ) {
1326 - $row = call_user_func( $modifier, $row );
1327 -
1328 - if ( ! is_array( $row ) || empty( $row ) ) {
1329 - return new \WP_Error( 'invalid_modified_row', 'Modified row is invalid or empty.' );
1330 - }
1331 - }
1332 -
1333 - // Prepare insert
1334 - $columns = array_keys( $row );
1335 - $placeholders = array_fill( 0, count( $columns ), '%s' );
1336 - $values = array_values( $row );
1337 -
1338 - $insert_sql = $wpdb->prepare(
1339 - "INSERT INTO `$table_name` (`" . implode( '`, `', $columns ) . "`)
1340 - VALUES (" . implode( ', ', $placeholders ) . ")",
1341 - ...$values
1342 - );
1343 -
1344 - $result = $wpdb->query( $insert_sql );
1345 -
1346 - if ( false === $result ) {
1347 - return new \WP_Error( 'insert_failed', 'Failed to insert duplicate row.' );
1348 - }
1349 -
1350 - return $wpdb->insert_id;
1351 - }
1352 -
1353 - /**
1354 - * Get valid sort order.
1355 - *
1356 - * @since 3.7.1
1357 - *
1358 - * @param string $order order.
1359 - *
1360 - * @return string
1361 - */
1362 - public static function get_valid_sort_order( $order ) {
1363 - return 'ASC' === strtoupper( $order ) ? 'ASC' : 'DESC';
1364 - }
1365 -
1366 - /**
1367 - * Get the schema of a database table.
1368 - *
1369 - * @since 3.8.1
1370 - *
1371 - * @param string $table_name The name of the database table.
1372 - *
1373 - * @throws \Exception Throws an exception if there is a database error.
1374 - *
1375 - * @return array Returns an array of table columns and their details.
1376 - */
1377 - public static function get_table_schema( $table_name) {
1378 -
1379 - global $wpdb;
1380 -
1381 - $result = $wpdb->get_results( "DESCRIBE {$table_name}", ARRAY_A );
1382 -
1383 - // If error occurred then throw new exception.
1384 - if ($wpdb->last_error) {
1385 - throw new \Exception($wpdb->last_error);
1386 - }
1387 -
1388 -
1389 - return $result;
1390 868 }
1391 869
1392 870 }