├── LICENSE ├── README.md ├── composer.json └── src ├── Abstracts └── DataModel.php ├── Lib └── functions.php ├── QueryBuilder.php └── Traits └── DataModelTrait.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 10 Quality Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wordpress Query Builder Library 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/10quality/wp-query-builder/v/stable)](https://packagist.org/packages/10quality/wp-query-builder) 4 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/10quality/wp-query-builder/test.yml) 5 | [![Total Downloads](https://poser.pugx.org/10quality/wp-query-builder/downloads)](https://packagist.org/packages/10quality/wp-query-builder) 6 | [![License](https://poser.pugx.org/10quality/wp-query-builder/license)](https://packagist.org/packages/10quality/wp-query-builder) 7 | 8 | This package provides a SQL query builder class built on top of WordPress core Database accessor. Usability is similar to Laravel's Eloquent. 9 | 10 | The library also provides an abstract class and a trait to be used on data models built for custom tables. The abstract class extends our generic [PHP model](https://github.com/10quality/php-data-model) class. 11 | 12 | This is the perfect package to use within the [WordPress MVC](https://www.wordpress-mvc.com/) framework. 13 | 14 | ## Install 15 | 16 | This package / library requires composer. 17 | 18 | ```bash 19 | composer require 10quality/wp-query-builder 20 | ``` 21 | 22 | ## Usage & Documentation 23 | 24 | Please read the [wiki](https://github.com/10quality/wp-query-builder/wiki) for documentation. 25 | 26 | Quick snippet sample: 27 | ```php 28 | $books = wp_query_builder() 29 | ->select( 'ID' ) 30 | ->select( 'post_name AS name' ) 31 | ->from( 'posts' ) 32 | ->where( ['post_type' => 'book'] ) 33 | ->get(); 34 | 35 | foreach ( $books as $book ) { 36 | echo $book->ID; 37 | echo $book->name; 38 | } 39 | ``` 40 | 41 | ## Coding Guidelines 42 | 43 | PSR-2 coding guidelines. 44 | 45 | ## License 46 | 47 | MIT License (c) 2019 [10 Quality](https://www.10quality.com/). -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "10quality/wp-query-builder", 3 | "description": "Wordpress Query Builder class library for custom models and data querying.", 4 | "license": "MIT", 5 | "keywords": ["wordpress","query builder","database", "models"], 6 | "authors": [ 7 | { 8 | "name": "Alejandro Mostajo", 9 | "email": "amostajo@gmail.com" 10 | }, 11 | { 12 | "name": "Hyper Tribal", 13 | "email": "grow@hypertribal.com" 14 | }, 15 | { 16 | "name": "10 Quality", 17 | "email": "info@10quality.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.4", 22 | "10quality/php-data-model": "^1.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "9.*" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "TenQuality\\WP\\Database\\": "src" 30 | }, 31 | "files": [ 32 | "src/Lib/functions.php" 33 | ] 34 | }, 35 | "scripts": { 36 | "test": [ 37 | "phpunit" 38 | ] 39 | }, 40 | "minimum-stability": "dev" 41 | } 42 | -------------------------------------------------------------------------------- /src/Abstracts/DataModel.php: -------------------------------------------------------------------------------- 1 | 13 | * @license MIT 14 | * @package wp-query-builder 15 | * @version 1.0.12 16 | */ 17 | abstract class DataModel extends Model 18 | { 19 | /** 20 | * Database table name. 21 | * @since 1.0.0 22 | * @var string 23 | */ 24 | protected $table = ''; 25 | /** 26 | * Reference to primary key column name. 27 | * @since 1.0.0 28 | * @var string 29 | */ 30 | protected $primary_key = 'ID'; 31 | /** 32 | * List of properties used for keyword search. 33 | * @since 1.0.0 34 | * @var array 35 | */ 36 | protected static $keywords = []; 37 | /** 38 | * Default model constructor. 39 | * @since 1.0.0 40 | * 41 | * @param array $attributes 42 | * @param mixed $id 43 | */ 44 | public function __construct( $attributes = [], $id = null ) 45 | { 46 | parent::__construct( $attributes ); 47 | if ( ! empty( $id ) ) 48 | $this->attributes[$this->primary_key] = $id; 49 | } 50 | /** 51 | * Returns `tablename` property. 52 | * @since 1.0.0 53 | * 54 | * @global object Wordpress Data base accessor. 55 | * 56 | * @return string 57 | */ 58 | protected function getTablenameAlias() 59 | { 60 | global $wpdb; 61 | return $wpdb->prefix . $this->table; 62 | } 63 | /** 64 | * Returns list of protected/readonly properties for 65 | * when saving or updating. 66 | * @since 1.0.0 67 | * 68 | * @return array 69 | */ 70 | protected function protected_properties() 71 | { 72 | return apply_filters( 73 | 'data_model_' . $this->table . '_excluded_save_fields', 74 | [$this->primary_key, 'created_at', 'updated_at'], 75 | $this->tablename 76 | ); 77 | } 78 | /** 79 | * Saves data attributes in database. 80 | * Returns flag indicating if save process was successful. 81 | * @since 1.0.0 82 | * 83 | * @global object Wordpress Data base accessor. 84 | * 85 | * @param bool $force_insert Flag that indicates if should insert regardless of ID. 86 | * 87 | * @return bool 88 | */ 89 | public function save( $force_insert = false ) 90 | { 91 | global $wpdb; 92 | $protected = $this->protected_properties(); 93 | if ( ! $force_insert && $this->{$this->primary_key} ) { 94 | // Update 95 | $success = $wpdb->update( $this->tablename, array_filter( $this->attributes, function( $key ) use( $protected ) { 96 | return ! in_array( $key , $protected ); 97 | }, ARRAY_FILTER_USE_KEY ), [$this->primary_key => $this->attributes[$this->primary_key]] ); 98 | if ( $success ) 99 | do_action( 'data_model_' . $this->table . '_updated', $this ); 100 | } else { 101 | // Insert 102 | $success = $wpdb->insert( $this->tablename, array_filter( $this->attributes, function( $key ) use( $protected ) { 103 | return ! in_array( $key , $protected ); 104 | }, ARRAY_FILTER_USE_KEY ) ); 105 | $this->{$this->primary_key} = $wpdb->insert_id; 106 | $date = date( 'Y-m-d H:i:s' ); 107 | $this->created_at = $date; 108 | $this->updated_at = $date; 109 | if ( $success ) 110 | do_action( 'data_model_' . $this->table . '_inserted', $this ); 111 | } 112 | if ( $success ) 113 | do_action( 'data_model_' . $this->table . '_save', $this ); 114 | return $success; 115 | } 116 | /** 117 | * Loads attributes from database. 118 | * @since 1.0.0 119 | * 120 | * @global object Wordpress Data base accessor. 121 | * 122 | * @return \TenQuality\WP\Database\Abstracts\DataModel|null 123 | */ 124 | public function load() 125 | { 126 | $builder = new QueryBuilder( $this->table . '_load' ); 127 | $this->attributes = $builder->select( '*' ) 128 | ->from( $this->table ) 129 | ->where( [$this->primary_key => $this->attributes[$this->primary_key]] ) 130 | ->first( ARRAY_A ); 131 | return ! empty( $this->attributes ) 132 | ? apply_filters( 'data_model_' . $this->table, $this ) 133 | : null; 134 | } 135 | /** 136 | * Loads attributes from database based on custome where statements 137 | * 138 | * Samples: 139 | * // Simple query 140 | * $this->load_where( ['slug' => 'this-example-1'] ); 141 | * // Compound query with OR statement 142 | * $this->load_where( ['ID' => 77, 'ID' => ['OR', 546]] ); 143 | * @since 1.0.0 144 | * 145 | * @global object Wordpress Data base accessor. 146 | * 147 | * @param array $args Query arguments. 148 | * 149 | * @return \TenQuality\WP\Database\Abstracts\DataModel|null 150 | */ 151 | public function load_where( $args ) 152 | { 153 | if ( empty( $args ) ) 154 | return null; 155 | if ( ! is_array( $args ) ) 156 | throw new Exception( 'Arguments parameter must be an array.', 10100 ); 157 | $builder = new QueryBuilder( $this->table . '_load_where' ); 158 | $this->attributes = $builder->select( '*' ) 159 | ->from( $this->table ) 160 | ->where( $args ) 161 | ->first( ARRAY_A ); 162 | return ! empty( $this->attributes ) 163 | ? apply_filters( 'data_model_' . $this->table, $this ) 164 | : null; 165 | } 166 | /** 167 | * Deletes record. 168 | * @since 1.0.0 169 | * 170 | * @global object Wordpress Data base accessor. 171 | * 172 | * @return bool 173 | */ 174 | public function delete() 175 | { 176 | global $wpdb; 177 | $deleted = $this->{$this->primary_key} 178 | ? $wpdb->delete( $this->tablename, [$this->primary_key => $this->attributes[$this->primary_key]] ) 179 | : false; 180 | if ( $deleted ) 181 | do_action( 'data_model_' . $this->table . '_deleted', $this ); 182 | return $deleted; 183 | } 184 | /** 185 | * Updates specific columns of the model (not the whole object like save()). 186 | * @since 1.0.12 187 | * 188 | * @param array $data Data to update. 189 | * 190 | * @return bool 191 | */ 192 | public function update( $data = [] ) 193 | { 194 | // If not data, let save() handle this 195 | if ( empty( $data ) || !is_array( $data ) ) { 196 | return $this->save(); 197 | } 198 | global $wpdb; 199 | $success = false; 200 | $protected = $this->protected_properties(); 201 | if ( $this->{$this->primary_key} ) { 202 | // Update 203 | $success = $wpdb->update( $this->tablename, array_filter( $data, function( $key ) use( $protected ) { 204 | return ! in_array( $key , $protected ); 205 | }, ARRAY_FILTER_USE_KEY ), [$this->primary_key => $this->attributes[$this->primary_key]] ); 206 | if ( $success ) { 207 | foreach ( $data as $key => $value ) { 208 | $this->$key = $value; 209 | } 210 | do_action( 'data_model_' . $this->table . '_updated', $this ); 211 | } 212 | } 213 | return $success; 214 | } 215 | /** 216 | * Deletes where query. 217 | * @since 1.0.0 218 | * 219 | * @global object Wordpress Data base accessor. 220 | * 221 | * @param array $args Query arguments. 222 | * 223 | * @return bool 224 | */ 225 | protected function _delete_where( $args ) 226 | { 227 | global $wpdb; 228 | return $wpdb->delete( $this->tablename, $args ); 229 | } 230 | } -------------------------------------------------------------------------------- /src/Lib/functions.php: -------------------------------------------------------------------------------- 1 | 8 | * @license MIT 9 | * @package wp-query-builder 10 | * @version 1.0.9 11 | */ 12 | if ( !function_exists( 'wp_query_builder' ) ) { 13 | /** 14 | * Returns initialized QueryBuilder instance. 15 | * @since 1.0.9 16 | * 17 | * @param string|null $query_id 18 | * 19 | * @return \TenQuality\WP\Database\QueryBuilder 20 | */ 21 | function wp_query_builder( $query_id = null ) 22 | { 23 | return QueryBuilder::create( $query_id ); 24 | } 25 | } -------------------------------------------------------------------------------- /src/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | Hyper Tribal 11 | * @author 10 Quality 12 | * @license MIT 13 | * @package wp-query-builder 14 | * @version 1.0.13 15 | */ 16 | class QueryBuilder 17 | { 18 | /** 19 | * Builder ID for hook references. 20 | * @since 1.0.0 21 | * @var string 22 | */ 23 | protected $id; 24 | /** 25 | * Builder statements. 26 | * @since 1.0.0 27 | * @var array 28 | */ 29 | protected $builder; 30 | /** 31 | * Builder options. 32 | * @since 1.0.11 33 | * @var array 34 | */ 35 | protected $options; 36 | /** 37 | * Builder constructor. 38 | * @since 1.0.0 39 | * 40 | * @param string|null $id 41 | */ 42 | public function __construct( $id = null ) 43 | { 44 | $this->id = ! empty( $id ) ? $id : uniqid(); 45 | $this->builder = [ 46 | 'select' => [], 47 | 'from' => null, 48 | 'join' => [], 49 | 'where' => [], 50 | 'order' => [], 51 | 'group' => [], 52 | 'having' => null, 53 | 'limit' => null, 54 | 'offset' => 0, 55 | 'set' => [], 56 | ]; 57 | $this->options = [ 58 | 'wildcard' => '{%}', 59 | 'default_wildcard' => '{%}', 60 | ]; 61 | } 62 | /** 63 | * Static constructor. 64 | * @since 1.0.0 65 | * 66 | * @param string $id 67 | */ 68 | public static function create( $id = null ) 69 | { 70 | $builder = new self( $id ); 71 | return $builder; 72 | } 73 | /** 74 | * Adds select statement. 75 | * @since 1.0.0 76 | * 77 | * @param array|string $statement 78 | * 79 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 80 | */ 81 | public function select( $statement ) 82 | { 83 | $this->builder['select'][] = $statement; 84 | return $this; 85 | } 86 | /** 87 | * Adds from statement. 88 | * @since 1.0.0 89 | * 90 | * @global object $wpdb 91 | * 92 | * @param string $from 93 | * @param bool $add_prefix Should DB prefix be added. 94 | * 95 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 96 | */ 97 | public function from( $from, $add_prefix = true ) 98 | { 99 | global $wpdb; 100 | $this->builder['from'] = ( $add_prefix ? $wpdb->prefix : '' ) . $from; 101 | return $this; 102 | } 103 | /** 104 | * Adds keywords search statement. 105 | * @since 1.0.0 106 | * 107 | * @global object $wpdb 108 | * 109 | * @param string $keywords Searched keywords. 110 | * @param array $columns Column or fields where to search. 111 | * @param string $separator Keyword separator within keywords string. 112 | * 113 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 114 | */ 115 | public function keywords( $keywords, $columns, $separator = ' ' ) 116 | { 117 | if ( ! empty( $keywords ) ) { 118 | global $wpdb; 119 | foreach ( explode( $separator , $keywords ) as $keyword ) { 120 | $keyword = '%' . $this->sanitize_value( true, $keyword ) . '%'; 121 | $this->builder['where'][] = [ 122 | 'joint' => 'AND', 123 | 'condition' => '(' . implode( ' OR ', array_map( function( $column ) use( &$wpdb, &$keyword ) { 124 | return $wpdb->prepare( $column . ' LIKE %s', $keyword ); 125 | }, $columns ) ) . ')', 126 | ]; 127 | } 128 | } 129 | return $this; 130 | } 131 | /** 132 | * Adds where statement. 133 | * @since 1.0.0 134 | * 135 | * @global object $wpdb 136 | * 137 | * @param array $args Multiple where arguments. 138 | * 139 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 140 | */ 141 | public function where( $args ) 142 | { 143 | global $wpdb; 144 | foreach ( $args as $key => $value ) { 145 | // Options - set 146 | if ( is_array( $value ) && array_key_exists( 'wildcard', $value ) && !empty( $value['wildcard'] ) ) 147 | $this->options['wildcard'] = trim( $value['wildcard'] ); 148 | // Value 149 | $arg_value = is_array( $value ) && array_key_exists( 'value', $value ) ? $value['value'] : $value; 150 | if ( is_array( $value ) && array_key_exists( 'min', $value ) ) 151 | $arg_value = $value['min']; 152 | $sanitize_callback = is_array( $value ) && array_key_exists( 'sanitize_callback', $value ) 153 | ? $value['sanitize_callback'] 154 | : true; 155 | if ( $sanitize_callback 156 | && $key !== 'raw' 157 | && ( !is_array( $value ) || !array_key_exists( 'key', $value ) ) 158 | ) 159 | $arg_value = $this->sanitize_value( $sanitize_callback, $arg_value ); 160 | $statement = $key === 'raw' 161 | ? [$arg_value] 162 | : [ 163 | $key, 164 | is_array( $value ) && isset( $value['operator'] ) ? strtoupper( $value['operator'] ) : ( $arg_value === null ? 'is' : '=' ), 165 | is_array( $value ) && array_key_exists( 'key', $value ) 166 | ? $value['key'] 167 | : ( is_array( $arg_value ) 168 | ? ( '(\'' . implode( '\',\'', $arg_value ) . '\')' ) 169 | : ( $arg_value === null 170 | ? 'null' 171 | : $wpdb->prepare( ( !is_array( $value ) || !array_key_exists( 'force_string', $value ) || !$value['force_string'] ) && is_numeric( $arg_value ) ? '%d' : '%s' , $arg_value ) 172 | ) 173 | ), 174 | ]; 175 | // Between? 176 | if ( is_array( $value ) && isset( $value['operator'] ) ) { 177 | $value['operator'] = strtoupper( $value['operator'] ); 178 | if ( strpos( $value['operator'], 'BETWEEN' ) !== false ) { 179 | if ( array_key_exists( 'max', $value ) || array_key_exists( 'key_b', $value ) ) { 180 | if ( array_key_exists( 'max', $value ) ) 181 | $arg_value = $value['max']; 182 | if ( array_key_exists( 'sanitize_callback2', $value ) ) 183 | $sanitize_callback = $value['sanitize_callback2']; 184 | if ( $sanitize_callback && !array_key_exists( 'key_b', $value ) ) 185 | $arg_value = $this->sanitize_value( $sanitize_callback, $arg_value ); 186 | $statement[] = 'AND'; 187 | $statement[] = array_key_exists( 'key_b', $value ) 188 | ? $value['key_b'] 189 | : ( is_array( $arg_value ) 190 | ? ( '(\'' . implode( '\',\'', $arg_value ) . '\')' ) 191 | : $wpdb->prepare( ( !array_key_exists( 'force_string', $value ) || !$value['force_string'] ) && is_numeric( $arg_value ) ? '%d' : '%s' , $arg_value ) 192 | ); 193 | } else { 194 | throw new Exception( '"max" or "key_b "parameter must be indicated when using the BETWEEN operator.', 10202 ); 195 | } 196 | } 197 | } 198 | $this->builder['where'][] = [ 199 | 'joint' => is_array( $value ) && isset( $value['joint'] ) ? $value['joint'] : 'AND', 200 | 'condition' => implode( ' ', $statement ), 201 | ]; 202 | // Options - reset 203 | if ( is_array( $value ) && array_key_exists( 'wildcard', $value ) && !empty( $value['wildcard'] ) ) 204 | $this->options['wildcard'] = $this->options['default_wildcard']; 205 | } 206 | return $this; 207 | } 208 | /** 209 | * Adds join statement. 210 | * @since 1.0.0 211 | * 212 | * @global object $wpdb 213 | * 214 | * @throws Exception 215 | * 216 | * @param string $table Join table. 217 | * @param array $args Join arguments. 218 | * @param bool|string $type Flag that indicates if it is "LEFT or INNER", also accepts direct join string. 219 | * @param bool $add_prefix Should DB prefix be added. 220 | * 221 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 222 | */ 223 | public function join( $table, $args, $type = false, $add_prefix = true ) 224 | { 225 | $type = is_string( $type ) ? strtoupper( trim( $type ) ) : ( $type ? 'LEFT' : '' ); 226 | if ( !in_array( $type, ['', 'LEFT', 'RIGHT', 'INNER', 'CROSS', 'LEFT OUTER', 'RIGHT OUTER'] ) ) 227 | throw new Exception( 'Invalid join type.', 10201 ); 228 | global $wpdb; 229 | $join = [ 230 | 'table' => ( $add_prefix ? $wpdb->prefix : '' ) . $table, 231 | 'type' => $type, 232 | 'on' => [], 233 | ]; 234 | foreach ( $args as $argument ) { 235 | // Options - set 236 | if ( array_key_exists( 'wildcard', $argument ) && !empty( $argument['wildcard'] ) ) 237 | $this->options['wildcard'] = trim( $argument['wildcard'] ); 238 | // Value 239 | $arg_value = isset( $argument['value'] ) ? $argument['value'] : null; 240 | if ( array_key_exists( 'min', $argument ) ) 241 | $arg_value = $argument['min']; 242 | $sanitize_callback = array_key_exists( 'sanitize_callback', $argument ) ? $argument['sanitize_callback'] : true; 243 | if ( $sanitize_callback 244 | && !array_key_exists( 'raw', $argument ) 245 | && !array_key_exists( 'key_b', $argument ) 246 | ) 247 | $arg_value = $this->sanitize_value( $sanitize_callback, $arg_value ); 248 | $statement = array_key_exists( 'raw', $argument ) 249 | ? [$argument['raw']] 250 | : [ 251 | isset( $argument['key_a'] ) ? $argument['key_a'] : $argument['key'], 252 | isset( $argument['operator'] ) ? strtoupper( $argument['operator'] ) : ( $arg_value === null && ! isset( $argument['key_b'] ) ? 'is' : '=' ), 253 | array_key_exists( 'key_b', $argument ) 254 | ? $argument['key_b'] 255 | : ( is_array( $arg_value ) 256 | ? ( '(\'' . implode( '\',\'', $arg_value ) . '\')' ) 257 | : ( $arg_value === null 258 | ? 'null' 259 | : $wpdb->prepare( ( !array_key_exists( 'force_string', $argument ) || !$argument['force_string'] ) && is_numeric( $arg_value ) ? '%d' : '%s' , $arg_value ) 260 | ) 261 | ), 262 | ]; 263 | // Between? 264 | if ( isset( $argument['operator'] ) ) { 265 | $argument['operator'] = strtoupper( $argument['operator'] ); 266 | if ( strpos( $argument['operator'], 'BETWEEN' ) !== false ) { 267 | if ( array_key_exists( 'max', $argument ) || array_key_exists( 'key_c', $argument ) ) { 268 | if ( array_key_exists( 'max', $argument ) ) 269 | $arg_value = $argument['max']; 270 | if ( array_key_exists( 'sanitize_callback2', $argument ) ) 271 | $sanitize_callback = $argument['sanitize_callback2']; 272 | if ( $sanitize_callback && !array_key_exists( 'key_c', $argument ) ) 273 | $arg_value = $this->sanitize_value( $sanitize_callback, $arg_value ); 274 | $statement[] = 'AND'; 275 | $statement[] = array_key_exists( 'key_c', $argument ) 276 | ? $argument['key_c'] 277 | : ( is_array( $arg_value ) 278 | ? ( '(\'' . implode( '\',\'', $arg_value ) . '\')' ) 279 | : $wpdb->prepare( ( !array_key_exists( 'force_string', $argument ) || !$argument['force_string'] ) && is_numeric( $arg_value ) ? '%d' : '%s' , $arg_value ) 280 | ); 281 | } else { 282 | throw new Exception( '"max" or "key_c" parameter must be indicated when using the BETWEEN operator.', 10203 ); 283 | } 284 | } 285 | } 286 | $join['on'][] = [ 287 | 'joint' => isset( $argument['joint'] ) ? $argument['joint'] : 'AND', 288 | 'condition' => implode( ' ', $statement ), 289 | ]; 290 | // Options - reset 291 | if ( array_key_exists( 'wildcard', $argument ) && !empty( $argument['wildcard'] ) ) 292 | $this->options['wildcard'] = $this->options['default_wildcard']; 293 | } 294 | $this->builder['join'][] = $join; 295 | return $this; 296 | } 297 | /** 298 | * Adds limit statement. 299 | * @since 1.0.0 300 | * 301 | * @param int $limit 302 | * 303 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 304 | */ 305 | public function limit( $limit ) 306 | { 307 | $this->builder['limit'] = $limit; 308 | return $this; 309 | } 310 | /** 311 | * Adds offset statement. 312 | * @since 1.0.0 313 | * 314 | * @param int $offset 315 | * 316 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 317 | */ 318 | public function offset( $offset ) 319 | { 320 | $this->builder['offset'] = $offset; 321 | return $this; 322 | } 323 | /** 324 | * Adds order by statement. 325 | * @since 1.0.0 326 | * 327 | * @param string $key 328 | * @param string $direction 329 | * 330 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 331 | */ 332 | public function order_by( $key, $direction = 'ASC' ) 333 | { 334 | $direction = trim( strtoupper( $direction ) ); 335 | if ( $direction !== 'ASC' && $direction !== 'DESC' ) 336 | throw new Exception( 'Invalid direction value.', 10200 ); 337 | if ( ! empty( $key ) ) 338 | $this->builder['order'][] = $key . ' ' . $direction; 339 | return $this; 340 | } 341 | /** 342 | * Adds group by statement. 343 | * @since 1.0.0 344 | * 345 | * @param string $statement 346 | * 347 | * @return \TenQuality\WP\Database\Utility\QueryBuilder this for chaining. 348 | */ 349 | public function group_by( $statement ) 350 | { 351 | if ( ! empty( $statement ) ) 352 | $this->builder['group'][] = $statement; 353 | return $this; 354 | } 355 | /** 356 | * Adds having statement. 357 | * @since 1.0.0 358 | * 359 | * @param string $statement 360 | * 361 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 362 | */ 363 | public function having( $statement ) 364 | { 365 | if ( ! empty( $statement ) ) 366 | $this->builder['having'] = $statement; 367 | return $this; 368 | } 369 | /** 370 | * Adds set statement (for update). 371 | * @since 1.0.12 372 | * 373 | * @global object $wpdb 374 | * 375 | * @param array $args Multiple where arguments. 376 | * 377 | * @return \TenQuality\WP\Database\QueryBuilder this for chaining. 378 | */ 379 | public function set( $args ) 380 | { 381 | global $wpdb; 382 | foreach ( $args as $key => $value ) { 383 | // Value 384 | $arg_value = is_array( $value ) && array_key_exists( 'value', $value ) ? $value['value'] : $value; 385 | $sanitize_callback = is_array( $value ) && array_key_exists( 'sanitize_callback', $value ) 386 | ? $value['sanitize_callback'] 387 | : true; 388 | if ( $sanitize_callback 389 | && $key !== 'raw' 390 | && ( !is_array( $value ) || !array_key_exists( 'raw', $value ) ) 391 | ) 392 | $arg_value = $this->sanitize_value( $sanitize_callback, $arg_value ); 393 | $statement = $key === 'raw' 394 | ? [$arg_value] 395 | : [ 396 | $key, 397 | '=', 398 | is_array( $value ) && array_key_exists( 'raw', $value ) 399 | ? $value['raw'] 400 | : ( is_array( $arg_value ) 401 | ? ( '\'' . implode( ',', $arg_value ) . '\'' ) 402 | : ( $arg_value === null 403 | ? 'null' 404 | : $wpdb->prepare( ( !is_array( $value ) || !array_key_exists( 'force_string', $value ) || !$value['force_string'] ) && is_numeric( $arg_value ) ? '%d' : '%s' , $arg_value ) 405 | ) 406 | ), 407 | ]; 408 | $this->builder['set'][] = implode( ' ', $statement ); 409 | } 410 | return $this; 411 | } 412 | /** 413 | * Retunrs results from builder statements. 414 | * @since 1.0.0 415 | * 416 | * @global object $wpdb 417 | * 418 | * @param int $output WPDB output type. 419 | * @param callable $callable_mapping Function callable to filter or map results to. 420 | * @param bool $calc_rows Flag that indicates to SQL if rows should be calculated or not. 421 | * 422 | * @return array 423 | */ 424 | public function get( $output = OBJECT, $callable_mapping = null, $calc_rows = false ) 425 | { 426 | global $wpdb; 427 | $this->builder = apply_filters( 'query_builder_get_builder', $this->builder ); 428 | $this->builder = apply_filters( 'query_builder_get_builder_' . $this->id, $this->builder ); 429 | // Build 430 | // Query 431 | $query = ''; 432 | $this->_query_select( $query, $calc_rows ); 433 | $this->_query_from( $query ); 434 | $this->_query_join( $query ); 435 | $this->_query_where( $query ); 436 | $this->_query_group( $query ); 437 | $this->_query_having( $query ); 438 | $this->_query_order( $query ); 439 | $this->_query_limit( $query ); 440 | $this->_query_offset( $query ); 441 | // Process 442 | $query = apply_filters( 'query_builder_get_query', $query ); 443 | $query = apply_filters( 'query_builder_get_query_' . $this->id, $query ); 444 | $results = $wpdb->get_results( $query, $output ); 445 | if ( $callable_mapping ) { 446 | $results = array_map( function( $row ) use( &$callable_mapping ) { 447 | return call_user_func_array( $callable_mapping, [$row] ); 448 | }, $results ); 449 | } 450 | return $results; 451 | } 452 | /** 453 | * Returns first row found. 454 | * @since 1.0.0 455 | * 456 | * @global object $wpdb 457 | * 458 | * @param int $output WPDB output type. 459 | * 460 | * @return object|array 461 | */ 462 | public function first( $output = OBJECT ) 463 | { 464 | global $wpdb; 465 | $this->builder = apply_filters( 'query_builder_first_builder', $this->builder ); 466 | $this->builder = apply_filters( 'query_builder_first_builder_' . $this->id, $this->builder ); 467 | // Build 468 | // Query 469 | $query = ''; 470 | $this->_query_select( $query ); 471 | $this->_query_from( $query ); 472 | $this->_query_join( $query ); 473 | $this->_query_where( $query ); 474 | $this->_query_group( $query ); 475 | $this->_query_having( $query ); 476 | $this->_query_order( $query ); 477 | $query .= ' LIMIT 1'; 478 | $this->_query_offset( $query ); 479 | // Process 480 | $query = apply_filters( 'query_builder_first_query', $query ); 481 | $query = apply_filters( 'query_builder_first_query_' . $this->id, $query ); 482 | return $wpdb->get_row( $query, $output ); 483 | } 484 | /** 485 | * Returns a value. 486 | * @since 1.0.0 487 | * 488 | * @global object $wpdb 489 | * 490 | * @param int $x Column of value to return. Indexed from 0. 491 | * @param int $y Row of value to return. Indexed from 0. 492 | * 493 | * @return mixed 494 | */ 495 | public function value( $x = 0, $y = 0 ) 496 | { 497 | global $wpdb; 498 | $this->builder = apply_filters( 'query_builder_value_builder', $this->builder ); 499 | $this->builder = apply_filters( 'query_builder_value_builder_' . $this->id, $this->builder ); 500 | // Build 501 | // Query 502 | $query = ''; 503 | $this->_query_select( $query ); 504 | $this->_query_from( $query ); 505 | $this->_query_join( $query ); 506 | $this->_query_where( $query ); 507 | $this->_query_group( $query ); 508 | $this->_query_having( $query ); 509 | $this->_query_order( $query ); 510 | $this->_query_limit( $query ); 511 | $this->_query_offset( $query ); 512 | // Process 513 | $query = apply_filters( 'query_builder_value_query', $query ); 514 | $query = apply_filters( 'query_builder_value_query_' . $this->id, $query ); 515 | return $wpdb->get_var( $query, $x, $y ); 516 | } 517 | /** 518 | * Returns the count. 519 | * @since 1.0.0 520 | * 521 | * @global object $wpdb 522 | * 523 | * @param string|int $column Count column. 524 | * @param bool $bypass_limit Flag that indicates if limit + offset should be considered on count. 525 | * 526 | * @return int 527 | */ 528 | public function count( $column = 1, $bypass_limit = true ) 529 | { 530 | global $wpdb; 531 | $this->builder = apply_filters( 'query_builder_count_builder', $this->builder ); 532 | $this->builder = apply_filters( 'query_builder_count_builder_' . $this->id, $this->builder ); 533 | // Build 534 | // Query 535 | $query = 'SELECT count(' . $column . ') as `count`'; 536 | $this->_query_from( $query ); 537 | $this->_query_join( $query ); 538 | $this->_query_where( $query ); 539 | $this->_query_group( $query ); 540 | $this->_query_having( $query ); 541 | if ( ! $bypass_limit ) { 542 | $this->_query_limit( $query ); 543 | $this->_query_offset( $query ); 544 | } 545 | // Process 546 | $query = apply_filters( 'query_builder_count_query', $query ); 547 | $query = apply_filters( 'query_builder_count_query_' . $this->id, $query ); 548 | return intval( $wpdb->get_var( $query ) ); 549 | } 550 | /** 551 | * Returns column results from builder statements. 552 | * @since 1.0.6 553 | * 554 | * @global object $wpdb 555 | * 556 | * @param int $x Column index number. 557 | * @param bool $calc_rows Flag that indicates to SQL if rows should be calculated or not. 558 | * 559 | * @return array 560 | */ 561 | public function col( $x = 0, $calc_rows = false ) 562 | { 563 | global $wpdb; 564 | $this->builder = apply_filters( 'query_builder_col_builder', $this->builder ); 565 | $this->builder = apply_filters( 'query_builder_col_builder_' . $this->id, $this->builder ); 566 | // Build 567 | // Query 568 | $query = ''; 569 | $this->_query_select( $query, $calc_rows ); 570 | $this->_query_from( $query ); 571 | $this->_query_join( $query ); 572 | $this->_query_where( $query ); 573 | $this->_query_group( $query ); 574 | $this->_query_having( $query ); 575 | $this->_query_order( $query ); 576 | $this->_query_limit( $query ); 577 | $this->_query_offset( $query ); 578 | // Process 579 | $query = apply_filters( 'query_builder_col_query', $query ); 580 | $query = apply_filters( 'query_builder_col_query_' . $this->id, $query ); 581 | return $wpdb->get_col( $query, $x ); 582 | } 583 | /** 584 | * Returns flag indicating if query has been executed. 585 | * @since 1.0.8 586 | * 587 | * @global object $wpdb 588 | * 589 | * @param string $sql 590 | * 591 | * @return bool 592 | */ 593 | public function query( $sql = '' ) 594 | { 595 | global $wpdb; 596 | $this->builder = apply_filters( 'query_builder_query_builder', $this->builder ); 597 | $this->builder = apply_filters( 'query_builder_query_builder_' . $this->id, $this->builder ); 598 | // Build 599 | // Query 600 | $query = $sql; 601 | if ( empty( $query ) ) { 602 | $this->_query_select( $query, false ); 603 | $this->_query_from( $query ); 604 | $this->_query_join( $query ); 605 | $this->_query_where( $query ); 606 | $this->_query_group( $query ); 607 | $this->_query_having( $query ); 608 | $this->_query_order( $query ); 609 | $this->_query_limit( $query ); 610 | $this->_query_offset( $query ); 611 | } 612 | // Process 613 | $query = apply_filters( 'query_builder_query_query', $query ); 614 | $query = apply_filters( 'query_builder_query_query_' . $this->id, $query ); 615 | return $wpdb->query( $query ); 616 | } 617 | /** 618 | * Returns flag indicating if query has been executed. 619 | * @since 1.0.8 620 | * 621 | * @see self::query() 622 | * 623 | * @param string $sql 624 | * 625 | * @return bool 626 | */ 627 | public function raw( $sql ) 628 | { 629 | return $this->query( $sql ); 630 | } 631 | /** 632 | * Returns flag indicating if delete query has been executed. 633 | * @since 1.0.8 634 | * 635 | * @global object $wpdb 636 | * 637 | * @return bool 638 | */ 639 | public function delete() 640 | { 641 | global $wpdb; 642 | $this->builder = apply_filters( 'query_builder_delete_builder', $this->builder ); 643 | $this->builder = apply_filters( 'query_builder_delete_builder_' . $this->id, $this->builder ); 644 | // Build 645 | // Query 646 | $query = ''; 647 | $this->_query_delete( $query ); 648 | $this->_query_from( $query ); 649 | $this->_query_join( $query ); 650 | $this->_query_where( $query ); 651 | // Process 652 | $query = apply_filters( 'query_builder_delete_query', $query ); 653 | $query = apply_filters( 'query_builder_delete_query_' . $this->id, $query ); 654 | return $wpdb->query( $query ); 655 | } 656 | /** 657 | * Returns flag indicating if update query has been executed. 658 | * @since 1.0.12 659 | * 660 | * @global object $wpdb 661 | * 662 | * @return bool 663 | */ 664 | public function update() 665 | { 666 | global $wpdb; 667 | $this->builder = apply_filters( 'query_builder_update_builder', $this->builder ); 668 | $this->builder = apply_filters( 'query_builder_update_builder_' . $this->id, $this->builder ); 669 | // Build 670 | // Query 671 | $query = ''; 672 | $this->_query_update( $query ); 673 | $this->_query_join( $query ); 674 | $this->_query_set( $query ); 675 | $this->_query_where( $query ); 676 | // Process 677 | $query = apply_filters( 'query_builder_update_query', $query ); 678 | $query = apply_filters( 'query_builder_update_query_' . $this->id, $query ); 679 | return $wpdb->query( $query ); 680 | } 681 | /** 682 | * Retunrs found rows in last query, if SQL_CALC_FOUND_ROWS is used and is supported. 683 | * @since 1.0.6 684 | * 685 | * @global object $wpdb 686 | * 687 | * @return array 688 | */ 689 | public function rows_found() 690 | { 691 | global $wpdb; 692 | $query = 'SELECT FOUND_ROWS()'; 693 | // Process 694 | $query = apply_filters( 'query_builder_found_rows_query', $query ); 695 | $query = apply_filters( 'query_builder_found_rows_query_' . $this->id, $query ); 696 | return $wpdb->get_var( $query ); 697 | } 698 | /** 699 | * Builds query's select statement. 700 | * @since 1.0.0 701 | * 702 | * @param string &$query 703 | * @param bool $calc_rows 704 | */ 705 | private function _query_select( &$query, $calc_rows = false ) 706 | { 707 | $query = 'SELECT ' . ( $calc_rows ? 'SQL_CALC_FOUND_ROWS ' : '' ) . ( 708 | is_array( $this->builder['select'] ) && count( $this->builder['select'] ) 709 | ? implode( ',' , $this->builder['select'] ) 710 | : '*' 711 | ); 712 | } 713 | /** 714 | * Builds query's from statement. 715 | * @since 1.0.0 716 | * 717 | * @param string &$query 718 | */ 719 | private function _query_from( &$query ) 720 | { 721 | $query .= ' FROM ' . $this->builder['from']; 722 | } 723 | /** 724 | * Builds query's join statement. 725 | * @since 1.0.0 726 | * 727 | * @param string &$query 728 | */ 729 | private function _query_join( &$query ) 730 | { 731 | foreach ( $this->builder['join'] as $join ) { 732 | $query .= ( !empty( $join['type'] ) ? ' ' . $join['type'] . ' JOIN ' : ' JOIN ' ) . $join['table']; 733 | for ( $i = 0; $i < count( $join['on'] ); ++$i ) { 734 | $query .= ( $i === 0 ? ' ON ' : ' ' . $join['on'][$i]['joint'] . ' ' ) 735 | . $join['on'][$i]['condition']; 736 | } 737 | } 738 | } 739 | /** 740 | * Builds query's where statement. 741 | * @since 1.0.0 742 | * 743 | * @param string &$query 744 | */ 745 | private function _query_where( &$query ) 746 | { 747 | for ( $i = 0; $i < count( $this->builder['where'] ); ++$i ) { 748 | $query .= ( $i === 0 ? ' WHERE ' : ' ' . $this->builder['where'][$i]['joint'] . ' ' ) 749 | . $this->builder['where'][$i]['condition']; 750 | } 751 | } 752 | /** 753 | * Builds query's group by statement. 754 | * @since 1.0.0 755 | * 756 | * @param string &$query 757 | */ 758 | private function _query_group( &$query ) 759 | { 760 | if ( count( $this->builder['group'] ) ) 761 | $query .= ' GROUP BY ' . implode( ',', $this->builder['group'] ); 762 | } 763 | /** 764 | * Builds query's having statement. 765 | * @since 1.0.0 766 | * 767 | * @param string &$query 768 | */ 769 | private function _query_having( &$query ) 770 | { 771 | if ( $this->builder['having'] ) 772 | $query .= ' HAVING ' . $this->builder['having']; 773 | } 774 | /** 775 | * Builds query's order by statement. 776 | * @since 1.0.0 777 | * 778 | * @param string &$query 779 | */ 780 | private function _query_order( &$query ) 781 | { 782 | if ( count( $this->builder['order'] ) ) 783 | $query .= ' ORDER BY ' . implode( ',', $this->builder['order'] ); 784 | } 785 | /** 786 | * Builds query's limit statement. 787 | * @since 1.0.0 788 | * 789 | * @global object $wpdb 790 | * 791 | * @param string &$query 792 | */ 793 | private function _query_limit( &$query ) 794 | { 795 | global $wpdb; 796 | if ( $this->builder['limit'] ) 797 | $query .= $wpdb->prepare( ' LIMIT %d', $this->builder['limit'] ); 798 | } 799 | /** 800 | * Builds query's offset statement. 801 | * @since 1.0.0 802 | * 803 | * @global object $wpdb 804 | * 805 | * @param string &$query 806 | */ 807 | private function _query_offset( &$query ) 808 | { 809 | global $wpdb; 810 | if ( $this->builder['offset'] ) 811 | $query .= $wpdb->prepare( ' OFFSET %d', $this->builder['offset'] ); 812 | } 813 | /** 814 | * Builds query's delete statement. 815 | * @since 1.0.8 816 | * 817 | * @param string &$query 818 | */ 819 | private function _query_delete( &$query ) 820 | { 821 | $query .= trim( 'DELETE ' . ( count( $this->builder['join'] ) 822 | ? preg_replace( '/\s[aA][sS][\s\S]+.*?/', '', $this->builder['from'] ) 823 | : '' 824 | ) ); 825 | } 826 | /** 827 | * Builds query's update statement. 828 | * @since 1.0.12 829 | * 830 | * @param string &$query 831 | */ 832 | private function _query_update( &$query ) 833 | { 834 | $query .= trim( 'UPDATE ' . ( count( $this->builder['join'] ) 835 | ? $this->builder['from'] . ',' . implode( ',', array_map( function( $join ) { 836 | return $join['table']; 837 | }, $this->builder['join'] ) ) 838 | : $this->builder['from'] 839 | ) ); 840 | } 841 | /** 842 | * Builds query's set statement. 843 | * @since 1.0.12 844 | * 845 | * @param string &$query 846 | */ 847 | private function _query_set( &$query ) 848 | { 849 | $query .= $this->builder['set'] ? ' SET ' . implode( ',', $this->builder['set'] ) : ''; 850 | } 851 | /** 852 | * Sanitize value. 853 | * @since 1.0.0 854 | * 855 | * @param string|bool $callback Sanitize callback. 856 | * @param mixed $value 857 | * 858 | * @return mixed 859 | */ 860 | private function sanitize_value( $callback, $value ) 861 | { 862 | if ( $callback === true ) 863 | $callback = ( is_numeric( $value ) && strpos( $value, '.' ) !== false ) 864 | ? 'floatval' 865 | : ( is_numeric( $value ) 866 | ? 'intval' 867 | : ( is_string( $value ) 868 | ? 'sanitize_text_field' 869 | : null 870 | ) 871 | ); 872 | if ( $callback && strpos( $callback, '_builder' ) !== false ) 873 | $callback = [&$this, $callback]; 874 | if ( is_array( $value ) ) 875 | for ( $i = count( $value ) -1; $i >= 0; --$i ) { 876 | $value[$i] = $this->sanitize_value( true, $value[$i] ); 877 | } 878 | return $callback && is_callable( $callback ) ? call_user_func_array( $callback, [$value] ) : $value; 879 | } 880 | /** 881 | * Returns value escaped with WPDB `esc_like`, 882 | * @since 1.0.6 883 | * 884 | * @param mixed $value 885 | * 886 | * @return string 887 | */ 888 | private function _builder_esc_like( $value ) 889 | { 890 | global $wpdb; 891 | $wildcard = $this->options['wildcard']; 892 | return implode( '%', array_map( function( $part ) use( &$wpdb, &$wildcard ) { 893 | return $wpdb->esc_like( $part ); 894 | }, explode( $wildcard, $value ) ) ) ; 895 | } 896 | /** 897 | * Returns escaped value for LIKE comparison and appends wild card at the beggining. 898 | * @since 1.0.6 899 | * 900 | * @param mixed $value 901 | * 902 | * @return string 903 | */ 904 | private function _builder_esc_like_wild_value( $value ) 905 | { 906 | return '%' . $this->_builder_esc_like( $value ); 907 | } 908 | /** 909 | * Returns escaped value for LIKE comparison and appends wild card at the end. 910 | * @since 1.0.6 911 | * 912 | * @param mixed $value 913 | * 914 | * @return string 915 | */ 916 | private function _builder_esc_like_value_wild( $value ) 917 | { 918 | return $this->_builder_esc_like( $value ) . '%'; 919 | } 920 | /** 921 | * Returns escaped value for LIKE comparison and appends wild cards at both ends. 922 | * @since 1.0.6 923 | * 924 | * @param mixed $value 925 | * 926 | * @return string 927 | */ 928 | private function _builder_esc_like_wild_wild( $value ) 929 | { 930 | return '%' . $this->_builder_esc_like( $value ) . '%'; 931 | } 932 | } -------------------------------------------------------------------------------- /src/Traits/DataModelTrait.php: -------------------------------------------------------------------------------- 1 | 11 | * @license MIT 12 | * @package wp-query-builder 13 | * @version 1.0.12 14 | */ 15 | trait DataModelTrait 16 | { 17 | /** 18 | * Static constructor that finds recond in database 19 | * and fills model. 20 | * @since 1.0.0 21 | * 22 | * @param mixed $id 23 | * 24 | * @return \TenQuality\WP\Database\Abstracts\DataModel|null 25 | */ 26 | public static function find( $id ) 27 | { 28 | $model = new self( [], $id ); 29 | return $model->load(); 30 | } 31 | /** 32 | * Static constructor that finds recond in database 33 | * and fills model using where statement. 34 | * @since 1.0.0 35 | * 36 | * @param array $args Where query statement arguments. See non-static method. 37 | * 38 | * @return \TenQuality\WP\Database\Abstracts\DataModel 39 | */ 40 | public static function find_where( $args ) 41 | { 42 | $model = new self; 43 | return $model->load_where( $args ); 44 | } 45 | /** 46 | * Static constructor that inserts recond in database and fills model. 47 | * @since 1.0.0 48 | * 49 | * @param array $attributes 50 | * 51 | * @return \TenQuality\WP\Database\Abstracts\DataModel 52 | */ 53 | public static function insert( $attributes ) 54 | { 55 | $model = new self( $attributes ); 56 | return $model->save( true ) ? $model : null; 57 | } 58 | /** 59 | * Static constructor that deletes records 60 | * @since 1.0.0 61 | * 62 | * @param array $args Where query statement arguments. See non-static method. 63 | * 64 | * @return bool 65 | */ 66 | public static function delete_where( $args ) 67 | { 68 | $model = new self; 69 | return $model->_delete_where( $args ); 70 | } 71 | /** 72 | * Returns a collection of models. 73 | * @since 1.0.0 74 | * 75 | * @return array 76 | */ 77 | public static function where( $args = [] ) 78 | { 79 | // Pull specific data from args 80 | $limit = isset( $args['limit'] ) ? $args['limit'] : null; 81 | unset( $args['limit'] ); 82 | $offset = isset( $args['offset'] ) ? $args['offset'] : 0; 83 | unset( $args['offset'] ); 84 | $keywords = isset( $args['keywords'] ) ? $args['keywords'] : null; 85 | unset( $args['keywords'] ); 86 | $keywords_separator = isset( $args['keywords_separator'] ) ? $args['keywords_separator'] : ' '; 87 | unset( $args['keywords_separator'] ); 88 | $order_by = isset( $args['order_by'] ) ? $args['order_by'] : null; 89 | unset( $args['order_by'] ); 90 | $order = isset( $args['order'] ) ? $args['order'] : 'ASC'; 91 | unset( $args['order'] ); 92 | // Build query and retrieve 93 | $builder = new QueryBuilder( self::TABLE . '_where' ); 94 | return array_map( 95 | function( $attributes ) { 96 | return new self( $attributes ); 97 | }, 98 | $builder->select( '*' ) 99 | ->from( self::TABLE . ' as `' . self::TABLE . '`' ) 100 | ->keywords( $keywords, static::$keywords, $keywords_separator ) 101 | ->where( $args ) 102 | ->order_by( $order_by, $order ) 103 | ->limit( $limit ) 104 | ->offset( $offset ) 105 | ->get( ARRAY_A ) 106 | ); 107 | } 108 | /** 109 | * Returns count. 110 | * @since 1.0.0 111 | * 112 | * @return int 113 | */ 114 | public static function count( $args = [] ) 115 | { 116 | // Pull specific data from args 117 | unset( $args['limit'] ); 118 | unset( $args['offset'] ); 119 | $keywords = isset( $args['keywords'] ) ? sanitize_text_field( $args['keywords'] ) : null; 120 | unset( $args['keywords'] ); 121 | // Build query and retrieve 122 | $builder = new QueryBuilder( self::TABLE . '_count' ); 123 | return $builder->from( self::TABLE . ' as `' . self::TABLE . '`' ) 124 | ->keywords( $keywords, static::$keywords ) 125 | ->where( $args ) 126 | ->count(); 127 | } 128 | /** 129 | * Returns initialized builder with model set in from statement. 130 | * @since 1.0.0 131 | * 132 | * @return \TenQuality\WP\Database\Utility\QueryBuilder 133 | */ 134 | public static function builder() 135 | { 136 | $builder = new QueryBuilder( self::TABLE . '_custom' ); 137 | return $builder->from( self::TABLE . ' as `' . self::TABLE . '`' ); 138 | } 139 | /** 140 | * Returns a collection with all models found in the database. 141 | * @since 1.0.7 142 | * 143 | * @return array 144 | */ 145 | public static function all() 146 | { 147 | // Build query and retrieve 148 | $builder = new QueryBuilder( self::TABLE . '_all' ); 149 | return array_map( 150 | function( $attributes ) { 151 | return new self( $attributes ); 152 | }, 153 | $builder->select( '*' ) 154 | ->from( self::TABLE . ' as `' . self::TABLE . '`' ) 155 | ->get( ARRAY_A ) 156 | ); 157 | } 158 | /** 159 | * Returns query results from mass update. 160 | * @since 1.0.12 161 | * 162 | * @param array $set Set of column => data to update. 163 | * @param array $where Where condition. 164 | * 165 | * @return \TenQuality\WP\Database\Abstracts\DataModel|null 166 | */ 167 | public static function update_all( $set, $where = [] ) 168 | { 169 | $builder = new QueryBuilder( self::TABLE . '_static_update' ); 170 | return $builder->from( self::TABLE ) 171 | ->set( $set ) 172 | ->where( $where ) 173 | ->update(); 174 | } 175 | } --------------------------------------------------------------------------------