is can be one of the following: ARRAY_A | ARRAY_N | OBJECT | OBJECT_K. * @return Database Returns the Database class which can be method chained for more query building. */ public function output( $output ) { $this->output = $output; return $this; } /** * Reset the cache so we make sure the query gets to the DB. * * @since 1.0.0 * * @return Database Returns the Database class which can be method chained for more query building. */ public function resetCache() { $this->shouldResetCache = true; return $this; } /** * Run this query. * * @since 1.0.0 * * @param boolean $reset Whether to reset the results/query. * @param string $return Determine which method to call on the $wpdb object * @param array $params Optional extra parameters to pass to the db method call * @return Database Database query results. */ public function run( $reset = true, $return = 'results', $params = [] ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( ! in_array( $return, [ 'results', 'col', 'var' ], true ) ) { $return = 'results'; } // Cache query string to avoid generating it twice. $queryString = $this->query(); $prepare = $this->db->prepare( $queryString, 1, 1 ); $queryHash = md5( $queryString ); $cacheTableName = $this->getCacheTableName(); // Pull the result from the in-memory cache if everything checks out. if ( ! $this->shouldResetCache && isset( $this->cache[ $cacheTableName ][ $queryHash ][ $return ] ) && empty( $this->join ) ) { $this->result = $this->cache[ $cacheTableName ][ $queryHash ][ $return ]; return $this; } switch ( $return ) { case 'col': $this->result = $this->db->get_col( $prepare ); break; case 'var': $this->result = $this->db->get_var( $prepare ); break; default: $this->result = $this->db->get_results( $prepare, $this->output ); } if ( $reset ) { $this->reset(); } // Only cache SELECT queries for performance. if ( in_array( $this->statement, [ 'SELECT', 'SELECT DISTINCT' ], true ) ) { $this->cache[ $cacheTableName ][ $queryHash ][ $return ] = $this->result; } // Reset the cache trigger for the next run. $this->shouldResetCache = false; return $this; } /** * Inject a count select statement and return the result. * * @since 1.0.0 * * @param string $countColumn The column to count with. Defaults to '*' all. * @return int The count total. */ public function count( $countColumn = '*' ) { $usingGroup = ! empty( $this->group ); $results = $this->select( 'count(' . $countColumn . ') as count' ) ->run() ->result(); return 1 === $this->numRows() && ! $usingGroup ? (int) $results[0]->count : $this->numRows(); } /** * Returns the query results based on the output. * * @since 1.0.0 * * @return mixed This could be an array or an object based on the original output method. */ public function result() { return $this->result; } /** * Return a model model from a row. * * @since 1.0.0 * * @param string $class The class to call. * @return object The class object. */ public function model( $class ) { $result = $this->result(); return ! empty( $result ) ? ( is_array( $result ) ? new $class( (array) current( $result ) ) : $result ) : new $class(); } /** * Return an array of model models from the result * * @since 1.0.0 * * @param string $class The class to call. * @param string $id The id of the index to use. * @param bool $toJson Whether to convert to json. * @return array An array of class models. */ public function models( $class, $id = null, $toJson = false ) { if ( empty( $this->models ) ) { $i = 0; $models = []; foreach ( $this->result() as $row ) { $var = ( null === $id ) ? $row : $row[ $id ]; $class = new $class( $var ); // Lets add the class to the array using the class ID. $models[ $class->id ] = $toJson ? $class->jsonSerialize() : $class; ++$i; } $this->models = $models; } return $this->models; } /** * Returns the last error reported by MySQL. * * @since 1.0.0 * * @return string The last error. */ public function lastError() { return $this->db->last_error; } /** * Return the $wpdb insert_id from the last query. * * @since 1.0.0 * * @return integer The id of the most recent INSERT query. */ public function insertId() { return $this->db->insert_id; } /** * Return the $wpdb rows_affected from the last query. * * @since 1.0.0 * * @return integer The number of rows affected. */ public function rowsAffected() { return $this->db->rows_affected; } /** * Return the $wpdb num_rows from the last query. * * @since 1.0.0 * * @return integer The count for the number of rows in the last query. */ public function numRows() { return $this->db->num_rows; } /** * Check if the last query had any rows. * * @since 1.0.0 * * @return bool Whether there were any rows retrived by the last query. */ public function nullSet() { return ( $this->numRows() < 1 ); } /** * This will start a MySQL transaction. Be sure to commit or rollback! * * @since 1.0.0 */ public function startTransaction() { $this->db->query( 'START TRANSACTION' ); } /** * This will commit a MySQL transaction. Used in conjunction with startTransaction. * * @since 1.0.0 */ public function commit() { $this->db->query( 'COMMIT' ); } /** * This will rollback a MySQL transaction. Used in conjunction with startTransaction. * * @since 1.0.0 */ public function rollback() { $this->db->query( 'ROLLBACK' ); } /** * Fast way to execute queries. * * @since 1.0.0 * * @param string $sql The sql query to execute. * @return mixed Could be an array or object depending on the result set. */ public function execute( $sql, $results = false ) { $this->lastQuery = $sql; if ( $results ) { $this->result = $this->db->get_results( $sql ); return $this; } return $this->db->query( $sql ); } /** * Escape a value for safe use in SQL queries. * * @param string $value The value to be escaped. * @param boolean $options Escape options. * @return string The escaped SQL value. */ public function escape( $value, $options = null ) { if ( is_array( $value ) ) { foreach ( $value as &$val ) { $val = $this->escape( $val, $options ); } return $value; } $options = ( is_null( $options ) ) ? $this->getEscapeOptions() : $options; if ( ( $options & self::ESCAPE_STRIP_HTML ) !== 0 && isset( $this->stripTags ) && true === $this->stripTags ) { $value = wp_strip_all_tags( $value ); } // Cache php_sapi_name() result for performance. if ( null === self::$sapiName ) { self::$sapiName = php_sapi_name(); } // Check if we need to escape and quote the value. $needsEscaping = ( ( $options & self::ESCAPE_FORCE ) !== 0 || 'cli' === self::$sapiName ) || ( ( $options & self::ESCAPE_QUOTE ) !== 0 && ! is_int( $value ) && ! is_float( $value ) ); if ( $needsEscaping ) { $value = esc_sql( $value ); $value = "'$value'"; } return $value; } /** * Get the current escape options. * * @since 1.0.0 * * @return integer The current escape options. */ public function getEscapeOptions() { return $this->escapeOptions; } /** * Set the current escape options. * * @since 1.0.0 * * @param integer $options */ public function setEscapeOptions( $options ) { $this->escapeOptions = $options; } /** * Backtick-escapes an array of column and/or table names. * * @since 1.0.0 * * @param array $cols An array of column names to be escaped. * @return array An array of escaped column names. */ private function escapeColNames( $cols ) { if ( ! is_array( $cols ) ) { $cols = [ $cols ]; } foreach ( $cols as &$col ) { if ( false === stripos( $col, '(' ) && false === stripos( $col, ' ' ) && false === stripos( $col, '*' ) ) { if ( stripos( $col, '.' ) ) { list( $table, $c ) = explode( '.', $col ); $col = "`$table`.`$c`"; } else { $col = "`$col`"; } } } return $cols; } /** * Gets a variable list of function arguments and reformats them as needed for many of the functions of this class. * * @since 1.0.0 * * @param mixed $values This could be anything, but if used properly its usually a string or an array. * @return array If the preparation is correct it will return an array of arguments. */ private function prepArgs( $values ) { $values = (array) $values; if ( ! is_array( $values[0] ) && count( $values ) === 2 ) { $values = [ $values[0] => $values[1] ]; } elseif ( is_array( $values[0] ) && count( $values ) === 1 ) { $values = $values[0]; } return $values; } /** * Resets all the variables that make up the query. * * @since 1.0.0 * * @param array $what Set which items you want to reset, all are selected by default. * @return Database Returns the Database object. */ public function reset( $what = [ 'table', 'statement', 'limit', 'group', 'order', 'select', 'set', 'onDuplicate', 'ignore', 'where', 'union', 'distinct', 'orderDirection', 'query', 'output', 'stripTags', 'models', 'join' ] ) { // If we are not running a select query, let's bust the cache for this table. $selectStatements = [ 'SELECT', 'SELECT DISTINCT' ]; if ( ! empty( $this->statement ) && ! in_array( $this->statement, $selectStatements, true ) ) { $this->bustCache( $this->getCacheTableName() ); } foreach ( (array) $what as $var ) { switch ( $var ) { case 'group': case 'order': case 'select': case 'set': case 'onDuplicate': case 'where': case 'union': case 'join': $this->$var = []; break; case 'orderDirection': $this->$var = 'ASC'; break; case 'ignore': case 'stripTags': $this->$var = false; break; case 'output': $this->$var = 'OBJECT'; break; default: if ( isset( $this->$var ) ) { $this->$var = null; } break; } } return $this; } /** * Get the current value of one or more query properties. If only one property is specified, returns the value; * if an array of values is specified, then returns an array of values. * * @since 1.0.0 * * @param string|array $what You can pass in an array of options to retrieve. By default it selects all if them. * @return string|array Returns the value of whichever variables are passed in. */ public function getQueryProperty( $what = [ 'table', 'statement', 'limit', 'group', 'order', 'select', 'set', 'onDuplicate', 'where', 'union', 'distinct', 'orderDirection', 'query', 'output', 'result' ] ) { if ( is_array( $what ) ) { $return = []; foreach ( (array) $what as $which ) { $return[ $which ] = $this->$which; } return $return; } else { return $this->$what; } } /** * Get a table name for the cache key. * * @since 1.0.0 * * @param string $cacheTableName The table name to check against. * @return string The cache key table name. */ private function getCacheTableName( $cacheTableName = null ) { $cacheTableName = empty( $cacheTableName ) ? $this->table : $cacheTableName; foreach ( $this->customTables as $tableName ) { if ( false !== stripos( $cacheTableName, $this->prefix . $tableName ) ) { $cacheTableName = $tableName; break; } } return $cacheTableName; } /** * Busts the cache for the given table name. * * @since 1.0.0 * * @param string|null $tableName The table name. * @return void */ public function bustCache( $tableName = null ) { if ( ! $tableName ) { // Bust all the cache. $this->cache = []; return; } unset( $this->cache[ $tableName ] ); } /** * In order to not have a conflict, we need to return a clone. * * @since 1.0.0 * * @return Database The cloned Database object. */ public function noConflict() { return clone $this; } /** * Acquires a database lock with the given name. * * @since 4.0.4 * * @param string $lockName The name of the lock to acquire. * @param int $timeout Timeout in seconds. 0 returns immediately when the lock is taken. * @return bool Whether the lock was acquired. */ public function acquireLock( $lockName, $timeout = 0 ) { $lockResult = $this->db->get_var( $this->db->prepare( 'SELECT GET_LOCK(%s, %d)', $lockName, $timeout ) ); $acquired = '1' === $lockResult; if ( $acquired ) { // Always release the lock, even if a fatal error takes the request down. register_shutdown_function( function () use ( $lockName ) { $this->releaseLock( $lockName ); } ); } return $acquired; } /** * Releases a database lock with the given name. * * @since 4.0.4 * * @param string $lockName The name of the lock to release. * @return bool Whether the lock was released. */ public function releaseLock( $lockName ) { $releaseResult = $this->db->query( $this->db->prepare( 'SELECT RELEASE_LOCK(%s)', $lockName ) ); return false !== $releaseResult; } }