PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.6.2
Tutor LMS – eLearning and online course solution v3.6.2
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 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / helpers / QueryHelper.php
tutor / helpers Last commit date
DateTimeHelper.php 1 year ago HttpHelper.php 1 year ago PluginInstaller.php 1 year ago QueryHelper.php 1 year ago SessionHelper.php 3 years ago TemplateImportHelper.php 1 year ago ValidationHelper.php 1 year ago
QueryHelper.php
1015 lines
1 <?php
2 /**
3 * Query helper class contains static helper methods to perform basic
4 * operations
5 *
6 * @package Tutor\Helper
7 * @since v2.0.7
8 */
9
10 namespace Tutor\Helpers;
11
12 /**
13 * Do the common db operations through helper
14 * methods
15 */
16 class QueryHelper {
17
18 /**
19 * Insert data in the table
20 *
21 * @since 2.0.7
22 * @since 3.2.0 sanitize_mapping param added to override sanitize function to specific keys.
23 *
24 * @param string $table table name.
25 * @param array $data | data to insert in the table.
26 * @param array $sanitize_mapping sanitize mapping.
27 *
28 * @return int inserted id.
29 *
30 * @throws \Exception Database error if occur.
31 */
32 public static function insert( string $table, array $data, array $sanitize_mapping = array() ): int {
33 global $wpdb;
34
35 $data = \TUTOR\Input::sanitize_array( $data, $sanitize_mapping );
36
37 $insert = $wpdb->insert(
38 $table,
39 $data
40 );
41
42 if ( $wpdb->last_error ) {
43 throw new \Exception( $wpdb->last_error );
44 }
45
46 return $insert ? $wpdb->insert_id : 0;
47 }
48
49 /**
50 * Update data
51 *
52 * @since 2.0.7
53 * @since 3.2.0 IN clause support added.
54 *
55 * @param string $table table name.
56 * @param array $data | data to update in the table.
57 * @param array $where | condition array.
58 *
59 * @return bool true on success false on failure
60 */
61 public static function update( string $table, array $data, array $where ): bool {
62 global $wpdb;
63
64 $set_clause = self::prepare_set_clause( $data );
65 $where_clause = self::build_where_clause( $where );
66
67 // phpcs:ignore
68 $query = $wpdb->prepare( "UPDATE {$table} {$set_clause} WHERE {$where_clause} AND 1 = %d", 1 );
69
70 // phpcs:ignore
71 $wpdb->query( $query );
72
73 if ( $wpdb->last_error ) {
74 error_log( $wpdb->last_error );
75 return false;
76 }
77
78 return true;
79 }
80
81 /**
82 * Delete rows from table
83 *
84 * @param string $table table name.
85 * @param array $where key value pairs.Where key is the name of
86 * column & value is the value to match.
87 * For ex: [ 'id' => 1 ].
88 *
89 * @since v2.0.7
90 */
91 public static function delete( string $table, array $where ): bool {
92 global $wpdb;
93 $delete = $wpdb->delete(
94 $table,
95 $where
96 );
97 return $delete ? true : false;
98 }
99
100 /**
101 * Delete rows from table
102 *
103 * @since 3.0.0
104 *
105 * @param string $table table name.
106 * @param array $ids array of ids.
107 *
108 * @see prepare_in_clause
109 *
110 * @throws \Exception Throw database error if occurred.
111 *
112 * @return true on success
113 */
114 public static function bulk_delete_by_ids( string $table, array $ids ): bool {
115 global $wpdb;
116
117 $ids = self::prepare_in_clause( $ids );
118 //phpcs:ignore --ids already sanitized.
119 $wpdb->query( "DELETE FROM {$table} WHERE id IN ( $ids )");
120
121 if ( $wpdb->last_error ) {
122 throw new \Exception( $wpdb->last_error );
123 }
124
125 return true;
126 }
127
128 /**
129 * Clean everything from table
130 *
131 * @since v2.0.7
132 *
133 * @param string $table table name.
134 *
135 * @return bool
136 */
137 public static function table_clean( string $table ): bool {
138 global $wpdb;
139 $delete = $wpdb->query(
140 //phpcs:ignore
141 $wpdb->prepare( "DELETE FROM {$table} WHERE 1 = %d", 1 )
142 );
143 return $delete ? true : false;
144 }
145
146 /**
147 * Insert multiple rows without knowing key value
148 *
149 * @since v2.0.7
150 * @since 3.6.0 param $return_ids added.
151 *
152 * @param string $table table name.
153 * @param array $request two dimensional array
154 * for ex: [ [id => 1], [id => 2] ].
155 * @param bool $return_ids if true returns the last inserted data ids.
156 *
157 * @return mixed wpdb response true or int on success, false on failure.
158 * @throws \Exception If error occur.
159 */
160 public static function insert_multiple_rows( $table, $request, $return_ids = false, $do_sanitize = true ) {
161 global $wpdb;
162 $column_keys = '';
163 $column_values = '';
164 $sql = '';
165 $last_key = array_key_last( $request );
166 $first_key = array_key_first( $request );
167 foreach ( $request as $k => $value ) {
168 $keys = array_keys( $value );
169
170 // Prepare column keys & values.
171 foreach ( $keys as $v ) {
172 $column_keys .= sanitize_key( $v ) . ',';
173 $sanitize_value = $value[$v];
174 if ( $sanitize_value && $do_sanitize ) {
175 $sanitize_value = sanitize_text_field( $sanitize_value );
176 }
177 $column_values .= is_numeric( $sanitize_value ) ? $sanitize_value . ',' : "'$sanitize_value'" . ',';
178 }
179 // Trim trailing comma.
180 $column_keys = rtrim( $column_keys, ',' );
181 $column_values = rtrim( $column_values, ',' );
182 if ( $first_key === $k ) {
183 $sql .= "INSERT INTO {$table} ($column_keys) VALUES ($column_values)";
184 if ( count( $request ) > 1 ) {
185 $sql .= ',';
186 }
187 } elseif ( $last_key == $k ) {
188 $sql .= "($column_values)";
189 } else {
190 $sql .= "($column_values),";
191 }
192
193 // Reset keys & values to avoid duplication.
194 $column_keys = '';
195 $column_values = '';
196 }
197
198 $wpdb->query( $sql );//phpcs:ignore
199
200 // If error occurred then throw new exception.
201 if ( $wpdb->last_error ) {
202 throw new \Exception( $wpdb->last_error );
203 }
204
205 if ( $return_ids ) {
206 $query_ids = $wpdb->get_results(
207 "SELECT ID FROM {$table} WHERE ID >= LAST_INSERT_ID()",
208 'ARRAY_N'
209 );
210
211 return $query_ids;
212 }
213
214 return true;
215 }
216
217 /**
218 * Make tge where clause base on its column operator and values.
219 *
220 * If the operator is IN then make the clause like `WHERE column_name IN (value1, value2, ...)`
221 * Otherwise the clause would be `WHERE column_name = 'value'`
222 *
223 * @since 3.0.0
224 *
225 * @param array $where The where clause array. e.g. array( 'id', 'IN', array(1, 2, 3) ) or array( 'id', '=', 1 ).
226 *
227 * @return string
228 */
229 public static function make_clause( array $where ) {
230 list ( $field, $operator, $value ) = $where;
231
232 $upper_operator = strtoupper( $operator );
233 if ( in_array( $upper_operator, array( 'IN', 'NOT IN' ), true ) ) {
234 $value = '(' . self::prepare_in_clause( $value ) . ')';
235 }
236
237 return "{$field} {$upper_operator} {$value}";
238 }
239
240 /**
241 * Check operator is supported.
242 *
243 * @since 3.5.0
244 *
245 * @param string $operator operator like =, !=, > , < etc.
246 *
247 * @return boolean
248 */
249 public static function is_support_operator( $operator ) {
250 $operator = strtoupper( $operator );
251
252 return in_array(
253 $operator,
254 array(
255 '=',
256 '!=',
257 '<>',
258 '>',
259 '<',
260 '>=',
261 '<=',
262 'LIKE',
263 'NOT LIKE',
264 'IN',
265 'NOT IN',
266 'IS',
267 'IS NOT',
268 'BETWEEN',
269 'NOT BETWEEN',
270 'RAW',
271 ),
272 true
273 );
274 }
275
276 /**
277 * Build where clause string
278 *
279 * @since 2.0.9
280 * @since 3.0.0 Null value support added, if need to check with null: [name => 'null']
281 * @since 3.5.0 All common SQL comparison operators support added.
282 * $where = array(
283 * 'id' => ['BETWEEN', [10, 20]],
284 * 'status' => ['!=', 'draft'],
285 * 'email' => ['LIKE', '%@gmail.com'],
286 * 'type' => ['NOT IN', ['test', 'sample']],
287 * 'age' => ['>=', 18],
288 * 'active' => true,
289 * 'deleted_at' => 'null',
290 * 'role' => 'editor',
291 * )
292 * @since 3.6.0 Added raw query support. Make sure the query written is not sql injectable.
293 * $where = array(
294 * 'username = %s' => [ 'RAW' , array( 'test' ) ]
295 * )
296 * @param array $where assoc array with field and value.
297 *
298 * @return string
299 */
300 public static function build_where_clause( array $where ) {
301 $arr = array();
302 foreach ( $where as $field => $value ) {
303 $operator = null;
304 if ( is_array( $value ) && isset( $value[0] ) && is_string( $value[0] ) && self::is_support_operator( $value[0] ) ) {
305 $operator = strtoupper( $value[0] );
306 $val = $value[1];
307 switch ( $operator ) {
308 case 'IN':
309 case 'NOT IN':
310 if ( is_array( $val ) ) {
311 $clause = array( $field, $operator, $val );
312 }
313 break;
314
315 case 'BETWEEN':
316 case 'NOT BETWEEN':
317 if ( is_array( $val ) && count( $val ) === 2 ) {
318 $val1 = is_numeric( $val[0] ) ? $val[0] : "'" . $val[0] . "'";
319 $val2 = is_numeric( $val[1] ) ? $val[1] : "'" . $val[1] . "'";
320 $clause = array( $field, $operator, "{$val1} AND {$val2}" );
321 }
322 break;
323
324 case 'IS':
325 case 'IS NOT':
326 $val = strtoupper( $val ) === 'NULL' ? 'NULL' : "'" . $val . "'";
327 $clause = array( $field, $operator, $val );
328 break;
329 case 'RAW':
330 $final_query = '';
331 if ( ! empty( $field ) && is_array( $val ) ) {
332 $final_query = self::prepare_raw_query( $field, $val );
333 }
334 $clause = $final_query;
335 break;
336 default: // =, !=, <, >, <=, >=, LIKE, NOT LIKE, <>
337 $val = is_numeric( $val ) ? $val : "'" . $val . "'";
338 $clause = array( $field, $operator, $val );
339 break;
340 }
341 } elseif ( is_array( $value ) ) {
342 $clause = array( $field, 'IN', $value );
343 } else {
344 if ( 'null' === strtolower( $value ) ) {
345 $clause = array( $field, 'IS', 'NULL' );
346 } else {
347 $value = is_numeric( $value ) ? $value : "'" . $value . "'";
348 $clause = array( $field, '=', $value );
349 }
350 }
351
352 $arr[] = ( 'RAW' === $operator ) ? $clause : self::make_clause( $clause );
353 }
354
355 return implode( ' AND ', $arr );
356 }
357
358 /**
359 * Prepare raw query for query helper.
360 *
361 * @since 3.6.0
362 *
363 * @param string $raw_query the query to execute.
364 * @param array $parameters the parameters to pass to the query.
365 *
366 * @return string
367 */
368 public static function prepare_raw_query( $raw_query, $parameters ) {
369 /**
370 * Not allowed unsafe SQL control characters [;, --, /*]
371 * Allowed safe SQL control characters only.
372 */
373 $is_safe = preg_match( '/^[a-zA-Z0-9_%\.=\s\'"<>\(\)\-\[\],]+$/', $raw_query );
374 if ( ! $is_safe ) {
375 return '';
376 }
377
378 if ( ! count( $parameters ) ) {
379 return $raw_query;
380 }
381
382 global $wpdb;
383
384 $final_query = $wpdb->prepare( $raw_query, $parameters ); //phpcs:ignore
385
386 return $final_query;
387 }
388
389 /**
390 * Build like clause string with or
391 *
392 * @since 1.0.0
393 *
394 * @param array $where assoc array with field and value.
395 * @param string $relation default is OR.
396 *
397 * @return string
398 */
399 public static function build_like_clause( array $where, $relation = 'OR' ) {
400 global $wpdb;
401
402 $like_conditions = array();
403
404 foreach ( $where as $column_name => $term ) {
405 //phpcs:ignore
406 $like_conditions[] = $wpdb->prepare( "$column_name LIKE %s", '%' . $wpdb->esc_like( $term ) . '%' );
407 }
408
409 $where_clause = implode( ' OR ', $like_conditions );
410
411 return $where_clause;
412 }
413
414 /**
415 * Sanitize assoc array
416 *
417 * @param array $array an assoc array.
418 * @return array
419 *
420 * @since 2.0.9
421 */
422 private static function sanitize_assoc_array( array $array ) {
423 return array_map(
424 function ( $value ) {
425 return sanitize_text_field( $value );
426 },
427 $array
428 );
429 }
430
431 /**
432 * Delete comment with associate meta data
433 *
434 * @param array $where associative array with field and value.
435 * Example: array( 'comment_type' => 'comment', 'comment_id' => 1 ).
436 * @return bool
437 *
438 * @since 2.0.9
439 */
440 public static function delete_comment_with_meta( array $where ) {
441 if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) {
442 return false;
443 }
444
445 $where = self::build_where_clause( self::sanitize_assoc_array( $where ) );
446
447 global $wpdb;
448 $ids = $wpdb->get_col( "SELECT comment_id FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore
449
450 if ( is_array( $ids ) && count( $ids ) ) {
451 $ids_str = "'" . implode( "','", $ids ) . "'";
452 // delete comment metas.
453 $wpdb->query( "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN({$ids_str}) " );//phpcs:ignore
454 // delete comment.
455 $wpdb->query( "DELETE FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore
456
457 return true;
458 }
459
460 return false;
461 }
462
463 /**
464 * Delete post with associate meta data
465 *
466 * @param array $where associative array with field and value.
467 * Example: array( 'post_type' => 'post', 'id' => 1 ).
468 * @return bool
469 *
470 * @since 2.0.9
471 */
472 public static function delete_post_with_meta( array $where ) {
473 if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) {
474 return false;
475 }
476
477 $where = self::build_where_clause( self::sanitize_assoc_array( $where ) );
478
479 global $wpdb;
480 $ids = $wpdb->get_col( "SELECT id FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore
481
482 if ( is_array( $ids ) && count( $ids ) ) {
483 $ids_str = "'" . implode( "','", $ids ) . "'";
484 // delete post metas.
485 $wpdb->query( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN({$ids_str}) " );//phpcs:ignore
486 // delete post.
487 $wpdb->query( "DELETE FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore
488
489 return true;
490 }
491
492 return false;
493 }
494
495 /**
496 * Get a single row from any table with where clause
497 *
498 * @param string $table table name with prefix.
499 *
500 * @param array $where assoc_array. For ex: [col_name => value ].
501 * @param string $order_by order by column name.
502 * @param string $order DESC or ASC, default is DESC.
503 * @param string $output expected output type, default is object.
504 *
505 * @return mixed based on output param, default object
506 */
507 public static function get_row( string $table, array $where, string $order_by, string $order = 'DESC', string $output = 'OBJECT' ) {
508 global $wpdb;
509
510 $where_clause = self::build_where_clause( $where );
511
512 //phpcs:disable
513 $query = $wpdb->prepare(
514 "SELECT *
515 FROM {$table}
516 WHERE {$where_clause}
517 ORDER BY {$order_by} {$order}
518 LIMIT %d
519 ",
520 1
521 );
522
523 return $wpdb->get_row(
524 $query,
525 $output
526 );
527 //phpcs:enable
528 }
529
530 /**
531 * Get all row from any table with where clause
532 *
533 * @since 2.2.1
534 * @since 3.0.0 added support for -1 value in the limit parameter.
535 *
536 * @param string $table table name with prefix.
537 *
538 * @param array $where assoc_array. For ex: [col_name => value ].
539 * @param string $order_by order by column name.
540 * @param int $limit default is 1000, -1 for no limit.
541 * @param string $order DESC or ASC, default is DESC.
542 * @param string $output expected output type, default is object.
543 *
544 * @return mixed based on output param, default object
545 */
546 public static function get_all( string $table, array $where, string $order_by, $limit = 1000, string $order = 'DESC', string $output = 'OBJECT' ) {
547 global $wpdb;
548
549 $where_clause = self::build_where_clause( $where );
550 $limit = (int) sanitize_text_field( $limit );
551 $limit_clause = ( -1 === $limit ) ? '' : 'LIMIT ' . $limit;
552
553 //phpcs:disable
554 $query = "SELECT *
555 FROM {$table}
556 WHERE {$where_clause}
557 ORDER BY {$order_by} {$order}
558 {$limit_clause}";
559
560 return $wpdb->get_results(
561 $query,
562 $output
563 );
564 //phpcs:enable
565 }
566
567 /**
568 * Update multiple rows by using where in
569 * clause
570 *
571 * @since v2.1.0
572 *
573 * @param string $table table name.
574 * @param array $data assoc_array data to update
575 * ex: [id => 2, name => 'john' ].
576 * @param string $where_in comma separated values, ex: 1,2,3.
577 * @param string $where_col default is ID but could be other.
578 *
579 * @return bool true on success, false on failure
580 */
581 public static function update_where_in( string $table, array $data, string $where_in, string $where_col = 'ID' ) {
582 global $wpdb;
583 if ( empty( $where_in ) || empty( $where_col ) ) {
584 return false;
585 }
586 $set_clause = self::prepare_set_clause( $data );
587 if ( '' === $set_clause ) {
588 return false;
589 }
590 // @codingStandardsIgnoreStart
591 $query = $wpdb->prepare(
592 "UPDATE {$table}
593 {$set_clause}
594 WHERE $where_col IN ( $where_in )
595 AND 1 = %d
596 ",
597 1
598 );
599 return $wpdb->query( $query ) ? true : false;
600 }
601
602 /**
603 * Prepare MySQL SET clause for update query
604 *
605 * @since v2.1.0
606 *
607 * @param array $data single dimension assoc_array.
608 *
609 * @return string
610 */
611 public static function prepare_set_clause( array $data ) {
612 $set = '';
613 foreach ( $data as $key => $value ) {
614 if ( $key === array_key_first ( $data ) ) {
615 $set .= "SET ";
616 }
617 // Multi dimension not allowed.
618 if ( is_array( $value ) ) {
619 continue;
620 }
621
622 if ( is_null( $value ) ) {
623 $set .= "$key = null";
624 } else {
625 $value = esc_sql( sanitize_text_field( $value ) );
626 $set .= is_numeric( $value ) ? "$key = $value" : "$key = '" . $value ."'";
627 }
628
629 $set .= ",";
630 }
631 return rtrim( $set, ',' );
632 }
633
634 /**
635 * Make sanitized SQL IN clause value from an array
636 *
637 * @param array $arr a sequential array.
638 * @return string
639 * @since 2.1.1
640 */
641 public static function prepare_in_clause( array $arr ) {
642 $escaped = array_map(
643 function( $value ) {
644 global $wpdb;
645 $escaped_value = null;
646 if ( is_int( $value ) ) {
647 $escaped_value = $wpdb->prepare( '%d', $value );
648 } else if( is_float( $value ) ) {
649 list( $whole, $decimal ) = explode( '.', $value );
650 $expression = '%.'. strlen( $decimal ) . 'f';
651 $escaped_value = $wpdb->prepare( $expression, $value );
652 } else {
653 $escaped_value = $wpdb->prepare( '%s', $value );
654 }
655 return $escaped_value;
656 },
657 $arr
658 );
659
660 return implode( ',', $escaped );
661 }
662
663 /**
664 * Check table exist in database.
665 *
666 * @since 2.5.0
667 *
668 * @param string $table table name.
669 *
670 * @return bool
671 */
672 public static function table_exists( $table ) {
673 global $wpdb;
674 $sql = "SHOW TABLES LIKE '{$table}'";
675 return $wpdb->get_var( $sql ) === $table;
676 }
677
678 /**
679 * Check column exist in a table
680 *
681 * @since 3.0.0
682 *
683 * @param string $table table name.
684 * @param string $column column name.
685 *
686 * @return bool
687 */
688 public static function column_exist( $table, $column ) {
689 global $wpdb;
690 $sql = "SHOW COLUMNS FROM {$table} LIKE '{$column}'";
691 return $wpdb->get_var( $sql ) === $column;
692 }
693
694 /**
695 * Get data by joining multiple tables with specified join relations.
696 *
697 * Argument should be SQL escaped.
698 *
699 * @since 3.0.0
700 *
701 * @param string $primary_table The primary table name with prefix.
702 * @param array $joining_tables An array of join relations. Each relation should be an array with keys 'type', 'table', 'on'.
703 * @param array $select_columns An array of columns to select.
704 * @param array $where An associative array for the WHERE clause. For example: [col_name => value]. Without sql esc.
705 * @param array $search An associative array for the search clause. For example: [col_name => value]. Without sql esc.
706 * @param string $order_by Order by column name.
707 * @param int $limit Maximum number of rows to return.
708 * @param int $offset Offset for pagination.
709 * @param string $order DESC or ASC, default is DESC.
710 * @param string $output Expected output type, default is OBJECT.
711 *
712 * @throws \Exception If an error occurred during the query execution.
713 *
714 * @return mixed Based on output param, default OBJECT.
715 */
716 public static function get_joined_data(
717 string $primary_table,
718 array $joining_tables,
719 array $select_columns,
720 array $where = [],
721 array $search = [],
722 string $order_by = '',
723 $limit = 10,
724 $offset = 0,
725 string $order = 'DESC',
726 string $output = 'OBJECT'
727 ) {
728 global $wpdb;
729
730 $select_clause = implode(', ', $select_columns);
731
732 $from_clause = $primary_table;
733
734 $join_clauses = '';
735 foreach ($joining_tables as $relation) {
736 $join_clauses .= " {$relation['type']} JOIN {$relation['table']} ON {$relation['on']}";
737 }
738
739 $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
740
741 if (!empty($search)) {
742 $search_clause = self::build_like_clause( $search );
743 // foreach ($search as $column => $value) {
744 // $search_clauses[] = $wpdb->prepare("{$column} LIKE %s", '%' . $wpdb->esc_like($value) . '%');
745 // }
746 $where_clause .= !empty($where_clause) ? ' AND (' . $search_clause . ')' : 'WHERE ' . $search_clause;
747 }
748
749 $order_by_clause = !empty($order_by) ? "ORDER BY {$order_by} {$order}" : '';
750
751 // Query to get total count.
752 $count_query = "
753 SELECT COUNT(*) as total_count
754 FROM {$from_clause}
755 {$join_clauses}
756 {$where_clause}
757 ";
758
759 $total_count = $wpdb->get_var($count_query);
760
761 if (empty($limit) && empty($offset)) {
762 $query = "SELECT
763 {$select_clause}
764 FROM {$from_clause}
765 {$join_clauses}
766 {$where_clause}
767 {$order_by_clause}";
768 } else {
769 $query = $wpdb->prepare(
770 "SELECT {$select_clause}
771 FROM {$from_clause}
772 {$join_clauses}
773 {$where_clause}
774 {$order_by_clause}
775 LIMIT %d OFFSET %d",
776 $limit,
777 $offset
778 );
779 }
780
781
782 $results = $wpdb->get_results($query, $output);
783
784 // Throw exception if error occurred.
785 if ($wpdb->last_error) {
786 throw new \Exception($wpdb->last_error);
787 }
788
789 // Prepare response array.
790 $response = array(
791 'total_count' => (int) $total_count,
792 'results' => $results,
793 );
794
795 return $response;
796 }
797
798 /**
799 * Get count var
800 *
801 * Argument should be SQL escaped.
802 *
803 * @since 1.0.0
804 *
805 * @param string $table table name with prefix.
806 * @param array $where array of where condition.
807 * @param array $search array of search conditions for LIKE operator.
808 * @param string $count_column column name to count, default id.
809 *
810 * @return int
811 */
812 public static function get_count( $table, $where = [], $search = [], $count_column = 'id' ): int {
813 global $wpdb;
814
815 $where_clause = !empty( $where ) ? 'WHERE ' . self::build_where_clause( $where ) : '';
816 $search_clause = !empty( $search ) ? self::build_like_clause( $search, 'AND' ) : '';
817
818 if ( !empty( $search_clause ) ) {
819 if ( !empty( $where_clause ) ) {
820 $where_clause .= ' AND (' . $search_clause . ')';
821 } else {
822 $where_clause = 'WHERE ' . $search_clause;
823 }
824 }
825
826 $count = $wpdb->get_var(
827 "SELECT COUNT($count_column)
828 FROM $table
829 {$where_clause}"
830 );
831
832 // If error occurred then throw new exception.
833 if ( $wpdb->last_error ) {
834 throw new \Exception( $wpdb->last_error );
835 }
836
837 return (int) $count;
838 }
839
840 /**
841 * Get count by joining multiple tables with specified join relations.
842 *
843 * Argument should be SQL escaped.
844 *
845 * @since 3.0.0
846 *
847 * @param string $primary_table The primary table name with prefix.
848 * @param array $joining_tables An array of join relations. Each relation should be an array with keys 'type', 'table', 'on'.
849 * @param array $where array of where conditions.
850 * @param array $search array of search conditions for LIKE operator.
851 * @param string $count_column column name to count, default id.
852 *
853 * @return int
854 */
855 public static function get_joined_count(string $primary_table, array $joining_tables, array $where = [], array $search = [], string $count_column = '*'): int {
856 global $wpdb;
857
858 $from_clause = $primary_table;
859
860 $join_clauses = '';
861 foreach ($joining_tables as $relation) {
862 $join_clauses .= " {$relation['type']} JOIN {$relation['table']} ON {$relation['on']}";
863 }
864
865 $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
866 $search_clause = !empty($search) ? self::build_like_clause($search, 'AND') : '';
867
868 if (!empty($search_clause)) {
869 if (!empty($where_clause)) {
870 $where_clause .= ' AND (' . $search_clause . ')';
871 } else {
872 $where_clause = 'WHERE ' . $search_clause;
873 }
874 }
875
876 $count_query = "
877 SELECT COUNT($count_column) as total_count
878 FROM {$from_clause}
879 {$join_clauses}
880 {$where_clause}
881 ";
882
883 $total_count = $wpdb->get_var($count_query);
884
885 // If error occurred then throw new exception.
886 if ($wpdb->last_error) {
887 throw new \Exception($wpdb->last_error);
888 }
889
890 return (int) $total_count;
891 }
892
893 /**
894 * Get all rows from any table with where and search clauses.
895 *
896 * @since 3.0.0
897 *
898 * @param string $table Table name with prefix.
899 * @param array $where Assoc array for exact match. For example: [col_name => value]. Without sql esc.
900 * @param array $search Assoc array for LIKE match. For example: [col_name => search_term]. Without sql esc.
901 * @param string $order_by Order by column name.
902 * @param int $limit Maximum number of rows to return, default is 10.
903 * @param int $offset Offset for pagination, default is 0.
904 * @param string $order DESC or ASC, default is DESC.
905 * @param string $output Expected output type, default is OBJECT.
906 *
907 * @throws \Exception Throw exception if error occurred during query execution.
908 *
909 * @return mixed Based on output param, default OBJECT.
910 */
911 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 {
912 global $wpdb;
913
914 $where_clause = !empty($where) ? 'WHERE ' . self::build_where_clause($where) : '';
915 $search_clause = !empty($search) ? self::build_like_clause($search, 'AND') : '';
916
917 if (!empty($search_clause)) {
918 if (!empty($where_clause)) {
919 $where_clause .= ' AND (' . $search_clause . ')';
920 } else {
921 $where_clause = 'WHERE ' . $search_clause;
922 }
923 }
924
925 // Query to get total count
926 $count_query = "
927 SELECT COUNT(*)
928 FROM {$table}
929 {$where_clause}
930 ";
931 $total_count = $wpdb->get_var($count_query);
932
933 // If error occurred then throw new exception.
934 if ($wpdb->last_error) {
935 throw new \Exception($wpdb->last_error);
936 }
937
938 $query = $wpdb->prepare(
939 "SELECT *
940 FROM {$table}
941 {$where_clause}
942 ORDER BY {$order_by} {$order}
943 LIMIT %d OFFSET %d",
944 $limit,
945 $offset
946 );
947
948 $results = $wpdb->get_results($query, $output);
949
950 // If error occurred then throw new exception.
951 if ($wpdb->last_error) {
952 throw new \Exception($wpdb->last_error);
953 }
954
955 // Prepare response array.
956 $response = array(
957 'results' => $results,
958 'total_count' => (int) $total_count,
959 );
960
961 return $response;
962 }
963
964 /**
965 * Get period clause based on the provided period.
966 *
967 * @since 3.0.0
968 *
969 * @param string $column Table.column name, ex: table.created_at.
970 * @param string $period Period for filter refund data.
971 *
972 * @return string
973 */
974 public static function get_period_clause( string $column, string $period = '' ) {
975 $period_clause = '';
976 switch ( $period ) {
977 case 'today':
978 $period_clause = "AND DATE($column) = CURDATE()";
979 break;
980 case 'monthly':
981 $period_clause = "AND MONTH($column) = MONTH(CURDATE())";
982 break;
983 case 'yearly':
984 $period_clause = "AND YEAR($column) = YEAR(CURDATE())";
985 break;
986 case 'last30days':
987 $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND CURDATE()";
988 break;
989 case 'last90days':
990 $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 90 DAY) AND CURDATE()";
991 break;
992 case 'last365days':
993 $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 365 DAY) AND CURDATE()";
994 break;
995 default:
996 break;
997 }
998
999 return $period_clause;
1000 }
1001
1002 /**
1003 * Get last executed SQL query.
1004 *
1005 * @since 3.6.0
1006 *
1007 * @return string
1008 */
1009 public static function get_last_query(){
1010 global $wpdb;
1011 return $wpdb->last_query;
1012 }
1013
1014 }
1015