at path:
ROOT
/
wp-content
/
plugins
/
tutor
/
helpers
/
QueryHelper.php
run:
R
W
Run
DateTimeHelper.php
6.09 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
HttpHelper.php
3.72 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
PluginInstaller.php
3.17 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
QueryHelper.php
36.72 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
SessionHelper.php
1.81 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
TemplateImportHelper.php
3.5 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
ValidationHelper.php
10.91 KB
2026-04-14 05:34:27
R
W
Run
Delete
Rename
error_log
up
📄
QueryHelper.php
Save
<?php /** * Query helper class contains static helper methods to perform basic * operations * * @package Tutor\Helper * @since 2.0.7 */ namespace Tutor\Helpers; /** * Do the common db operations through helper * methods */ class QueryHelper { /** * Insert data in the table * * @since 2.0.7 * @since 3.2.0 sanitize_mapping param added to override sanitize function to specific keys. * * @param string $table table name. * @param array $data | data to insert in the table. * @param array $sanitize_mapping sanitize mapping. * * @return int inserted id. * * @throws \Exception Database error if occur. */ public static function insert( string $table, array $data, array $sanitize_mapping = array() ): int { global $wpdb; $table = self::prepare_table_name( $table ); $data = \TUTOR\Input::sanitize_array( $data, $sanitize_mapping ); $insert = $wpdb->insert( $table, $data ); if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } return $insert ? $wpdb->insert_id : 0; } /** * Update data * * @since 2.0.7 * @since 3.2.0 IN clause support added. * * @param string $table table name. * @param array $data | data to update in the table. * @param array $where | condition array. * * @return bool true on success false on failure */ public static function update( string $table, array $data, array $where ): bool { global $wpdb; $table = self::prepare_table_name( $table ); $set_clause = self::prepare_set_clause( $data ); $where_clause = self::prepare_where_clause( $where ); // phpcs:ignore $query = $wpdb->prepare( "UPDATE {$table} {$set_clause} WHERE {$where_clause} AND 1 = %d", 1 ); // phpcs:ignore $wpdb->query( $query ); if ( $wpdb->last_error ) { error_log( $wpdb->last_error ); return false; } return true; } /** * Delete a row from table with where clause. * Limitation: It can only delete one row by wpdb::delete * * @param string $table table name. * @param array $where key value pairs.Where key is the name of * column & value is the value to match. * For ex: [ 'id' => 1 ]. * * @since v2.0.7 */ public static function delete( string $table, array $where ): bool { global $wpdb; $table = self::prepare_table_name( $table ); $delete = $wpdb->delete( $table, $where ); return $delete ? true : false; } /** * Bulk record delete by where clause. * * @since 3.7.0 * * @param string $table table name. * @param array $where where clause. * * @return int|boolean */ public static function bulk_delete( $table, array $where ): bool { global $wpdb; $table = self::prepare_table_name( $table ); $where_clause = self::prepare_where_clause( $where ); return $wpdb->query( "DELETE FROM {$table} WHERE {$where_clause}" ); //phpcs:ignore --$where clause sanitized. } /** * Delete rows from table * * @since 3.0.0 * * @param string $table table name. * @param array $ids array of ids. * * @see prepare_in_clause * * @throws \Exception Throw database error if occurred. * * @return true on success */ public static function bulk_delete_by_ids( string $table, array $ids ): bool { global $wpdb; $table = self::prepare_table_name( $table ); $ids = self::prepare_in_clause( $ids ); //phpcs:ignore --ids already sanitized. $wpdb->query( "DELETE FROM {$table} WHERE id IN ( $ids )"); if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } return true; } /** * Clean everything from table * * @since v2.0.7 * * @param string $table table name. * * @return bool */ public static function table_clean( string $table ): bool { global $wpdb; $table = self::prepare_table_name( $table ); $delete = $wpdb->query( //phpcs:ignore $wpdb->prepare( "DELETE FROM {$table} WHERE 1 = %d", 1 ) ); return $delete ? true : false; } /** * Insert multiple rows without knowing key value * * @since v2.0.7 * @since 3.6.0 param $return_ids added. * * @param string $table table name. * @param array $request two dimensional array * for ex: [ [id => 1], [id => 2] ]. * @param bool $return_ids if true returns the last inserted data ids. * @param bool $do_sanitize sanitize data or not. * * @return mixed wpdb response true or int on success, false on failure. * @throws \Exception If error occur. */ public static function insert_multiple_rows( $table, $request, $return_ids = false, $do_sanitize = true ) { global $wpdb; $table = self::prepare_table_name( $table ); $column_keys = ''; $column_values = ''; $sql = ''; $last_key = array_key_last( $request ); $first_key = array_key_first( $request ); foreach ( $request as $k => $value ) { $keys = array_keys( $value ); // Prepare column keys & values. foreach ( $keys as $v ) { $column_keys .= sanitize_key( $v ) . ','; $sanitize_value = $value[ $v ]; if ( $sanitize_value && $do_sanitize ) { $sanitize_value = sanitize_text_field( $sanitize_value ); } $column_values .= is_numeric( $sanitize_value ) ? $sanitize_value . ',' : "'$sanitize_value'" . ','; } // Trim trailing comma. $column_keys = rtrim( $column_keys, ',' ); $column_values = rtrim( $column_values, ',' ); if ( $first_key === $k ) { $sql .= "INSERT INTO {$table} ($column_keys) VALUES ($column_values)"; if ( count( $request ) > 1 ) { $sql .= ','; } } elseif ( $last_key == $k ) { $sql .= "($column_values)"; } else { $sql .= "($column_values),"; } // Reset keys & values to avoid duplication. $column_keys = ''; $column_values = ''; } $wpdb->query( $sql );//phpcs:ignore // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } if ( $return_ids ) { $query_ids = $wpdb->get_results( //phpcs:ignore "SELECT ID FROM {$table} WHERE ID >= LAST_INSERT_ID()", 'ARRAY_N' ); return $query_ids; } return true; } /** * Make tge where clause base on its column operator and values. * * If the operator is IN then make the clause like `WHERE column_name IN (value1, value2, ...)` * Otherwise the clause would be `WHERE column_name = 'value'` * * @since 3.0.0 * @since 3.9.7 added prepared statement for value. * * @param array $where The where clause array. e.g. array( 'id', 'IN', array(1, 2, 3) ) or array( 'id', '=', 1 ). * * @return string */ public static function make_clause( array $where ) { list ( $field, $operator, $value ) = $where; $upper_operator = strtoupper( $operator ); if ( in_array( $upper_operator, array( 'IN', 'NOT IN' ), true ) ) { $value = '(' . self::prepare_in_clause( $value ) . ')'; } elseif ( in_array( $upper_operator, array( 'BETWEEN', 'NOT BETWEEN' ), true ) ) { $value = array_map( fn( $val ) => self::prepare_value( $val ), $value ); $value = implode( ' AND ', $value ); } elseif ( strtoupper( $value ) === 'NULL' ) { $value = 'NULL'; } else { $value = self::prepare_value( $value ); } return "{$field} {$upper_operator} {$value}"; } /** * Check operator is supported. * * @since 3.5.0 * * @param string $operator operator like =, !=, > , < etc. * * @return boolean */ public static function is_support_operator( $operator ) { $operator = strtoupper( $operator ); return in_array( $operator, array( '=', '!=', '<>', '>', '<', '>=', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT', 'BETWEEN', 'NOT BETWEEN', 'RAW', ), true ); } /** * Prepare where clause string * * @since 2.0.9 * @since 3.0.0 Null value support added, if need to check with null: [name => 'null'] * @since 3.5.0 All common SQL comparison operators support added. * $where = array( * 'id' => ['BETWEEN', [10, 20]], * 'status' => ['!=', 'draft'], * 'email' => ['LIKE', '%@gmail.com'], * 'type' => ['NOT IN', ['test', 'sample']], * 'age' => ['>=', 18], * 'active' => true, * 'deleted_at' => 'null', * 'role' => 'editor', * ) * @since 3.6.0 Added raw query support. Make sure the query written is not sql injectable. * $where = array( * 'username = %s' => [ 'RAW' , array( 'test' ) ] * ) * @param array $where assoc array with field and value. * * @return string */ public static function prepare_where_clause( array $where ) { $arr = array(); foreach ( $where as $field => $value ) { $operator = null; if ( is_array( $value ) && isset( $value[0] ) && is_string( $value[0] ) && self::is_support_operator( $value[0] ) ) { $operator = strtoupper( $value[0] ); $val = $value[1]; switch ( $operator ) { case 'IN': case 'NOT IN': if ( is_array( $val ) ) { $clause = array( $field, $operator, $val ); } break; case 'BETWEEN': case 'NOT BETWEEN': if ( is_array( $val ) && count( $val ) === 2 ) { $clause = array( $field, $operator, $val ); } break; case 'IS': case 'IS NOT': $val = strtoupper( $val ) === 'NULL' ? 'NULL' : $val; $clause = array( $field, $operator, $val ); break; case 'RAW': $final_query = ''; if ( ! empty( $field ) && is_array( $val ) ) { $final_query = self::prepare_raw_query( $field, $val ); } $clause = $final_query; break; default: // =, !=, <, >, <=, >=, LIKE, NOT LIKE, <> $clause = array( $field, $operator, $val ); break; } } elseif ( is_array( $value ) ) { $clause = array( $field, 'IN', $value ); } elseif ( 'null' === strtolower( $value ) ) { $clause = array( $field, 'IS', 'NULL' ); } else { $clause = array( $field, '=', $value ); } $arr[] = ( 'RAW' === $operator ) ? $clause : self::make_clause( $clause ); } return implode( ' AND ', $arr ); } /** * Prepare raw query for query helper. * * @since 3.6.0 * * @param string $raw_query the query to execute. * @param array $parameters the parameters to pass to the query. * * @return string */ public static function prepare_raw_query( $raw_query, $parameters ) { /** * Not allowed unsafe SQL control characters [;, --, /*] * Allowed safe SQL control characters only. */ $is_safe = preg_match( '/^[a-zA-Z0-9_%\.=\s\'"<>\(\)\-\[\],]+$/', $raw_query ); if ( ! $is_safe ) { return ''; } if ( ! count( $parameters ) ) { return $raw_query; } global $wpdb; $final_query = $wpdb->prepare( $raw_query, $parameters ); //phpcs:ignore return $final_query; } /** * Prepare like clause string with or * * @since 1.0.0 * * @param array $where assoc array with field and value. * @param string $relation default is OR. * * @return string */ public static function prepare_like_clause( array $where, $relation = 'OR' ) { global $wpdb; $like_conditions = array(); foreach ( $where as $column_name => $term ) { //phpcs:ignore $like_conditions[] = $wpdb->prepare( "$column_name LIKE %s", '%' . $wpdb->esc_like( $term ) . '%' ); } $where_clause = implode( ' OR ', $like_conditions ); return $where_clause; } /** * Sanitize assoc array * * @param array $array an assoc array. * @return array * * @since 2.0.9 */ private static function sanitize_assoc_array( array $array ) { return array_map( function ( $value ) { return sanitize_text_field( $value ); }, $array ); } /** * Delete comment with associate meta data * * @param array $where associative array with field and value. * Example: array( 'comment_type' => 'comment', 'comment_id' => 1 ). * @return bool * * @since 2.0.9 */ public static function delete_comment_with_meta( array $where ) { if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) { return false; } $where = self::prepare_where_clause( self::sanitize_assoc_array( $where ) ); global $wpdb; $ids = $wpdb->get_col( "SELECT comment_id FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore if ( is_array( $ids ) && count( $ids ) ) { $in_clause = self::prepare_in_clause( $ids ); // delete comment metas. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN({$in_clause}) " ) );//phpcs:ignore // delete comment. $wpdb->query( "DELETE FROM {$wpdb->comments} WHERE {$where}" );//phpcs:ignore return true; } return false; } /** * Delete post with associate meta data * * @param array $where associative array with field and value. * Example: array( 'post_type' => 'post', 'id' => 1 ). * @return bool * * @since 2.0.9 */ public static function delete_post_with_meta( array $where ) { if ( count( $where ) === 0 || ! tutor_utils()->is_assoc( $where ) ) { return false; } $where = self::prepare_where_clause( self::sanitize_assoc_array( $where ) ); global $wpdb; $ids = $wpdb->get_col( "SELECT id FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore if ( is_array( $ids ) && count( $ids ) ) { $in_clause = self::prepare_in_clause( $ids ); // delete post metas. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN({$in_clause}) " ) );//phpcs:ignore // delete post. $wpdb->query( "DELETE FROM {$wpdb->posts} WHERE {$where}" );//phpcs:ignore return true; } return false; } /** * Prepare SELECT clause. * * @since 3.8.0 * * @param mixed $columns Column name or list of columns. * * @return string */ protected static function prepare_select_clause( $columns = '' ) { if ( empty( $columns ) ) { return '*'; } if ( is_array( $columns ) ) { return implode( ',', $columns ); } return $columns; } /** * Prepare JOIN clause. * * @since 3.8.0 * * @param array $joins Array of joins, each item: * - type: join type (LEFT, INNER, RIGHT etc). * - table: table name. * - on: join condition. * * @return string */ protected static function prepare_join_clause( $joins = array() ) { if ( empty( $joins ) || ! is_array( $joins ) ) { return ''; } $clause = ''; foreach ( $joins as $join ) { $type = strtoupper( $join['type'] ?? 'LEFT' ); $table = self::prepare_table_name( $join['table'] ); $on = $join['on']; if ( $table && $on ) { $clause .= " {$type} JOIN {$table} ON {$on} "; } } return $clause; } /** * Prepare WHERE + SEARCH clause together. * * @since 3.8.0 * * @param array $where Array of key => value pairs. * @param array $search Array of key => search string pairs. * @param string $search_operator Operator for search conditions (AND/OR). * * @return string */ protected static function prepare_where_search_clause( $where = array(), $search = array(), $search_operator = 'OR' ) { $clauses = array(); // Handle WHERE conditions. if ( ! empty( $where ) && is_array( $where ) ) { $clauses[] = self::prepare_where_clause( $where ); } // Handle SEARCH conditions. if ( ! empty( $search ) && is_array( $search ) ) { $clauses[] = self::prepare_like_clause( $search, $search_operator ); } if ( empty( $clauses ) ) { return ''; } return 'WHERE ' . implode( ' AND ', $clauses ); } /** * Prepare order by clause. * * @since 3.8.0 * * @param string $orderby order by column. * @param string $order order ASC|DESC. * * @return string */ protected static function prepare_order_clause( $orderby = '', $order = 'DESC' ) { if ( empty( $orderby ) ) { return ''; } // Allowed: foo, foo_bar, _foo, foo.bar etc. if ( ! preg_match( '/^[A-Za-z_][A-Za-z0-9._]*$/', $orderby ) ) { return ''; } $order = strtoupper( $order ) === 'ASC' ? 'ASC' : 'DESC'; return "ORDER BY {$orderby} {$order}"; } /** * Prepare LIMIT clause. * * @since 3.8.0 * * @param int $limit limit. * @param int $offset offset. * * @return string */ protected static function prepare_limit_clause( $limit = 0, $offset = 0 ) { if ( $limit < 1 || $offset < 0 ) { return ''; } return sprintf( 'LIMIT %d OFFSET %d', $limit, $offset ); } /** * Run a database query with flexible arguments. * * Supports SELECT, JOIN, WHERE, SEARCH, GROUP BY, HAVING, ORDER BY, * LIMIT (pagination), and can return count, single row or full result set. * * @since 3.8.0 * * @param string $table table name. * @param array $args { * Query arguments. * * @type string|array $select Columns to select, defaults to "*". * @type string $alias Table alias. * @type array $where WHERE conditions [ 'col' => 'val', ... ]. * @type array $search LIKE conditions [ 'col' => 'keyword', ... ]. * @type array $joins JOIN clauses [ [ 'type' => 'LEFT', 'table' => '...', 'on' => '...' ], ... ]. * @type string $groupby GROUP BY clause. * @type string $having HAVING clause. * @type string $orderby Column to order by. * @type string $order ASC|DESC, default DESC. * @type int $limit Limit. * @type int $offset Offset. * @type int $per_page Results per page for pagination. * @type int $page Current page number for pagination. * @type bool $count If true, return only total count. * @type bool $single If true, return only single row. * @type string $output OBJECT|ARRAY_A default is OBJECT. * } * * @return mixed Result set, count or single row. */ public static function query( $table, $args = array() ) { // Flags. $count = isset( $args['count'] ) && $args['count']; $single = isset( $args['single'] ) && $args['single']; $pagination = isset( $args['per_page'], $args['page'] ); $output = $args['output'] ?? 'OBJECT'; // Primary table. $table = self::prepare_table_name( $table ); $alias = $args['alias'] ?? 'main'; $table_with_alias = "{$table} AS {$alias}"; // Build clauses. $select_clause = self::prepare_select_clause( $args['select'] ?? '' ); $join_clause = self::prepare_join_clause( $args['joins'] ?? array() ); $where_clause = self::prepare_where_search_clause( $args['where'] ?? array(), $args['search'] ?? array() ); $groupby_clause = empty( $args['groupby'] ) ? '' : 'GROUP BY ' . $args['groupby']; $having_clause = empty( $args['having'] ) ? '' : 'HAVING ' . $args['having']; $order_by_clause = self::prepare_order_clause( $args['orderby'] ?? '', $args['order'] ?? 'DESC' ); global $wpdb; // Count only. if ( $count ) { $sql_query = "SELECT COUNT(*) FROM {$table_with_alias} {$join_clause} {$where_clause} {$groupby_clause} {$having_clause}"; return (int) $wpdb->get_var( $sql_query ); //phpcs:ignore } // Single record. if ( $single ) { $sql_query = "SELECT {$select_clause} FROM {$table_with_alias} {$join_clause} {$where_clause} {$groupby_clause} {$having_clause} {$order_by_clause} LIMIT 1"; return $wpdb->get_row( $sql_query, $output ); //phpcs:ignore } $calc_found_rows = $pagination ? 'SQL_CALC_FOUND_ROWS' : ''; $limit = isset( $args['limit'] ) ? (int) $args['limit'] : 0; $offset = isset( $args['offset'] ) ? (int) $args['offset'] : 0; if ( $pagination ) { $limit = (int) $args['per_page']; $offset = (int) ( $args['page'] - 1 ) * $limit; } $limit_clause = self::prepare_limit_clause( $limit, $offset ); $sql_query = "SELECT {$calc_found_rows} {$select_clause} FROM {$table_with_alias} {$join_clause} {$where_clause} {$groupby_clause} {$having_clause} {$order_by_clause} {$limit_clause}"; $rows = $wpdb->get_results( $sql_query, $output ); //phpcs:ignore if ( $pagination ) { $has_records = is_array( $rows ) && count( $rows ); $page = (int) $args['page']; $per_page = (int) $args['per_page']; $total_record = (int) $has_records ? $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0; $total_page = (int) ceil( $total_record / $per_page ); return array( 'total_record' => (int) $total_record, 'per_page' => $per_page, 'current_page' => $page, 'total_page' => $total_page, 'data' => $rows, ); } return $rows; } /** * Get a single row from any table with where clause * * @param string $table table name with prefix. * * @param array $where assoc_array. For ex: [col_name => value ]. * @param string $order_by order by column name. * @param string $order DESC or ASC, default is DESC. * @param string $output expected output type, default is object. * * @return mixed based on output param, default object */ public static function get_row( string $table, array $where, string $order_by, string $order = 'DESC', string $output = 'OBJECT' ) { global $wpdb; $table = self::prepare_table_name( $table ); $where_clause = self::prepare_where_clause( $where ); //phpcs:disable $query = $wpdb->prepare( "SELECT * FROM {$table} WHERE {$where_clause} ORDER BY {$order_by} {$order} LIMIT %d ", 1 ); return $wpdb->get_row( $query, $output ); //phpcs:enable } /** * Get all row from any table with where clause * * @since 2.2.1 * @since 3.0.0 added support for -1 value in the limit parameter. * * @param string $table table name with prefix. * * @param array $where assoc_array. For ex: [col_name => value ]. * @param string $order_by order by column name. * @param int $limit default is 1000, -1 for no limit. * @param string $order DESC or ASC, default is DESC. * @param string $output expected output type, default is object. * * @return mixed based on output param, default object */ public static function get_all( string $table, array $where, string $order_by, $limit = 1000, string $order = 'DESC', string $output = 'OBJECT' ) { global $wpdb; $table = self::prepare_table_name( $table ); $where_clause = self::prepare_where_clause( $where ); $limit = (int) sanitize_text_field( $limit ); $limit_clause = ( -1 === $limit ) ? '' : 'LIMIT ' . $limit; //phpcs:disable $query = "SELECT * FROM {$table} WHERE {$where_clause} ORDER BY {$order_by} {$order} {$limit_clause}"; return $wpdb->get_results( $query, $output ); //phpcs:enable } /** * Update multiple rows by using where in * clause * * @since v2.1.0 * * @param string $table table name. * @param array $data assoc_array data to update * ex: [id => 2, name => 'john' ]. * @param string $where_in comma separated values, ex: 1,2,3. * @param string $where_col default is ID but could be other. * * @return bool true on success, false on failure */ public static function update_where_in( string $table, array $data, string $where_in, string $where_col = 'ID' ) { global $wpdb; $table = self::prepare_table_name( $table ); if ( empty( $where_in ) || empty( $where_col ) ) { return false; } $set_clause = self::prepare_set_clause( $data ); if ( '' === $set_clause ) { return false; } // @codingStandardsIgnoreStart $query = $wpdb->prepare( "UPDATE {$table} {$set_clause} WHERE $where_col IN ( $where_in ) AND 1 = %d ", 1 ); return $wpdb->query( $query ) ? true : false; } /** * Prepare MySQL SET clause for update query * * @since v2.1.0 * * @param array $data single dimension assoc_array. * * @return string */ public static function prepare_set_clause( array $data ) { $set = ''; foreach ( $data as $key => $value ) { if ( $key === array_key_first ( $data ) ) { $set .= "SET "; } // Multi dimension not allowed. if ( is_array( $value ) ) { continue; } if ( is_null( $value ) ) { $set .= "$key = null"; } else { $value = esc_sql( sanitize_text_field( $value ) ); $set .= is_numeric( $value ) ? "$key = $value" : "$key = '" . $value ."'"; } $set .= ","; } return rtrim( $set, ',' ); } /** * Prepare value before using in query. * * @since 3.9.7 * * @param string|int|float $value the value to prepare. * * @return mixed */ public static function prepare_value( $value ) { global $wpdb; $escaped_value = null; if ( is_int( $value ) ) { $escaped_value = $wpdb->prepare( '%d', $value ); } elseif ( is_float( $value ) ) { list( $whole, $decimal ) = explode( '.', $value ); $expression = '%.'. strlen( $decimal ) . 'f'; $escaped_value = $wpdb->prepare( $expression, $value ); } else { $escaped_value = $wpdb->prepare( '%s', $value ); } return $escaped_value; } /** * Make sanitized SQL IN clause value from an array * * @since 2.1.1 * * @param array $arr a sequential array. * * @return string */ public static function prepare_in_clause( array $arr ) { $escaped = array_map( fn( $value ) => self::prepare_value( $value ), $arr ); return implode( ',', $escaped ); } /** * Check table exist in database. * * @since 2.5.0 * * @param string $table table name. * * @return bool */ public static function table_exists( $table ) { global $wpdb; $table = self::prepare_table_name( $table ); $sql = "SHOW TABLES LIKE '{$table}'"; return $wpdb->get_var( $sql ) === $table; } /** * Check column exist in a table * * @since 3.0.0 * * @param string $table table name. * @param string $column column name. * * @return bool */ public static function column_exist( $table, $column ) { global $wpdb; $table = self::prepare_table_name( $table ); $sql = "SHOW COLUMNS FROM {$table} LIKE '{$column}'"; return $wpdb->get_var( $sql ) === $column; } /** * Get data by joining multiple tables with specified join relations. * * Argument should be SQL escaped. * * @since 3.0.0 * @since 3.8.2 param $get_row added. * * @param string $primary_table The primary table name with prefix. * @param array $joining_tables An array of join relations. Each relation should be an array with keys 'type', 'table', 'on'. * @param array $select_columns An array of columns to select. * @param array $where An associative array for the WHERE clause. For example: [col_name => value]. Without sql esc. * @param array $search An associative array for the search clause. For example: [col_name => value]. Without sql esc. * @param string $order_by Order by column name. * @param int $limit Maximum number of rows to return. * @param int $offset Offset for pagination. * @param string $order DESC or ASC, default is DESC. * @param string $output Expected output type, default is OBJECT. * @param bool $get_row Get a single row. * * @throws \Exception If an error occurred during the query execution. * * @return mixed Based on output param, default OBJECT. */ public static function get_joined_data( string $primary_table, array $joining_tables, array $select_columns, array $where = [], array $search = [], string $order_by = '', $limit = 10, $offset = 0, string $order = 'DESC', string $output = 'OBJECT', bool $get_row = false ) { global $wpdb; $select_clause = implode( ', ', $select_columns ); $from_clause = self::prepare_table_name( $primary_table ); $join_clauses = self::prepare_join_clause( $joining_tables ); $where_clause = self::prepare_where_search_clause( $where, $search ); $order_by_clause = self::prepare_order_clause( $order_by, $order ); $limit_clause = self::prepare_limit_clause( $limit, $offset ); $query = "SELECT SQL_CALC_FOUND_ROWS {$select_clause} FROM {$from_clause} {$join_clauses} {$where_clause} {$order_by_clause} {$limit_clause}"; if ( $get_row ) { return $wpdb->get_row( $query, $output ); } $results = $wpdb->get_results( $query, $output ); $has_records = is_array( $results ) && count( $results ); $total_count = $has_records ? (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0; // Throw exception if error occurred. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } // Prepare response array. $response = array( 'total_count' => $total_count, 'results' => $results, ); return $response; } /** * Get count var * * Argument should be SQL escaped. * * @since 1.0.0 * * @param string $table table name with prefix. * @param array $where array of where condition. * @param array $search array of search conditions for LIKE operator. * @param string $count_column column name to count, default id. * * @return int */ public static function get_count( $table, $where = [], $search = [], $count_column = 'id' ): int { global $wpdb; $table = self::prepare_table_name( $table ); $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' ); $count = $wpdb->get_var( "SELECT COUNT($count_column) FROM $table {$where_clause}" ); // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } return (int) $count; } /** * Get count by joining multiple tables with specified join relations. * * Argument should be SQL escaped. * * @since 3.0.0 * * @param string $primary_table The primary table name with prefix. * @param array $joining_tables An array of join relations. Each relation should be an array with keys 'type', 'table', 'on'. * @param array $where array of where conditions. * @param array $search array of search conditions for LIKE operator. * @param string $count_column column name to count, default id. * * @return int */ public static function get_joined_count(string $primary_table, array $joining_tables, array $where = [], array $search = [], string $count_column = '*'): int { global $wpdb; $from_clause = self::prepare_table_name( $primary_table ); $join_clauses = self::prepare_join_clause( $joining_tables ); $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' ); $count_query = " SELECT COUNT($count_column) as total_count FROM {$from_clause} {$join_clauses} {$where_clause} "; $total_count = $wpdb->get_var( $count_query ); // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } return (int) $total_count; } /** * Get all rows from any table with where and search clauses. * * @since 3.0.0 * * @param string $table Table name with prefix. * @param array $where Assoc array for exact match. For example: [col_name => value]. Without sql esc. * @param array $search Assoc array for LIKE match. For example: [col_name => search_term]. Without sql esc. * @param string $order_by Order by column name. * @param int $limit Maximum number of rows to return, default is 10. * @param int $offset Offset for pagination, default is 0. * @param string $order DESC or ASC, default is DESC. * @param string $output Expected output type, default is OBJECT. * * @throws \Exception Throw exception if error occurred during query execution. * * @return mixed Based on output param, default OBJECT. */ 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 { global $wpdb; $table = self::prepare_table_name( $table ); $where_clause = self::prepare_where_search_clause( $where, $search, 'AND' ); $order_by_clause = self::prepare_order_clause( $order_by, $order ); $limit_clause = self::prepare_limit_clause( $limit, $offset ); // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } $query = "SELECT SQL_CALC_FOUND_ROWS * FROM {$table} {$where_clause} {$order_by_clause} {$limit_clause}"; $results = $wpdb->get_results( $query, $output ); $has_records = is_array( $results ) && count( $results ); $total_count = $has_records ? (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ) : 0; // If error occurred then throw new exception. if ( $wpdb->last_error ) { throw new \Exception( $wpdb->last_error ); } // Prepare response array. $response = array( 'results' => $results, 'total_count' => $total_count, ); return $response; } /** * Get period clause based on the provided period. * * @since 3.0.0 * * @param string $column Table.column name, ex: table.created_at. * @param string $period Period for filter refund data. * * @return string */ public static function get_period_clause( string $column, string $period = '' ) { $period_clause = ''; switch ( $period ) { case 'today': $period_clause = "AND DATE($column) = CURDATE()"; break; case 'monthly': $period_clause = "AND MONTH($column) = MONTH(CURDATE())"; break; case 'yearly': $period_clause = "AND YEAR($column) = YEAR(CURDATE())"; break; case 'last30days': $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 30 DAY) AND CURDATE()"; break; case 'last90days': $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 90 DAY) AND CURDATE()"; break; case 'last365days': $period_clause = "AND DATE($column) BETWEEN DATE_SUB(CURDATE(), INTERVAL 365 DAY) AND CURDATE()"; break; default: break; } return $period_clause; } /** * Get last executed SQL query. * * @since 3.6.0 * * @return string */ public static function get_last_query(){ global $wpdb; return $wpdb->last_query; } /** * Get table prefix. * * @since 3.7.0 * * @return string */ public static function get_table_prefix() { global $wpdb; return $wpdb->prefix; } /** * Prepare table name with prefix. * * @since 3.7.0 * * @param string $table_name table name. * * @return string */ public static function prepare_table_name( string $table_name ) { $table_prefix = self::get_table_prefix(); if ( strpos( $table_name,$table_prefix ) !== 0 ) { $table_name = $table_prefix . $table_name; } return $table_name; } /** * Duplicate a row with modification callback support. * * @since 3.7.0 * * @param string $table_name name of the database table (with prefix if needed). * @param array $where associative array of WHERE conditions. * @param callable|null $modifier optional callback to modify or exclude fields before insertion. * * @return int|WP_Error New row ID on success, or WP_Error on failure. */ public static function duplicate_row( $table_name, array $where, ?callable $modifier = null ) { global $wpdb; $table_name = self::prepare_table_name( $table_name ); if ( empty( $where ) ) { return new \WP_Error( 'missing_where', 'No WHERE condition provided.' ); } $where_clause = self::prepare_where_clause( $where ); $sql = $wpdb->prepare( "SELECT * FROM `$table_name` WHERE {$where_clause} LIMIT %d", 1 ); $row = $wpdb->get_row( $sql, ARRAY_A ); if ( ! $row ) { return new \WP_Error( 'not_found', 'No matching row found to duplicate.' ); } // Apply user-defined modifications (ex: remove ID, change field value) if ( is_callable( $modifier ) ) { $row = call_user_func( $modifier, $row ); if ( ! is_array( $row ) || empty( $row ) ) { return new \WP_Error( 'invalid_modified_row', 'Modified row is invalid or empty.' ); } } // Prepare insert $columns = array_keys( $row ); $placeholders = array_fill( 0, count( $columns ), '%s' ); $values = array_values( $row ); $insert_sql = $wpdb->prepare( "INSERT INTO `$table_name` (`" . implode( '`, `', $columns ) . "`) VALUES (" . implode( ', ', $placeholders ) . ")", ...$values ); $result = $wpdb->query( $insert_sql ); if ( false === $result ) { return new \WP_Error( 'insert_failed', 'Failed to insert duplicate row.' ); } return $wpdb->insert_id; } /** * Get valid sort order. * * @since 3.7.1 * * @param string $order order. * * @return string */ public static function get_valid_sort_order( $order ) { return 'ASC' === strtoupper( $order ) ? 'ASC' : 'DESC'; } /** * Get the schema of a database table. * * @since 3.8.1 * * @param string $table_name The name of the database table. * * @throws \Exception Throws an exception if there is a database error. * * @return array Returns an array of table columns and their details. */ public static function get_table_schema( $table_name) { global $wpdb; $result = $wpdb->get_results( "DESCRIBE {$table_name}", ARRAY_A ); // If error occurred then throw new exception. if ($wpdb->last_error) { throw new \Exception($wpdb->last_error); } return $result; } }