// phpcs:disable WPForms.Comments.PHPDocHooks.RequiredHookDocumentation, WPForms.PHP.ValidateHooks.InvalidHookName
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
/** @noinspection PhpIllegalPsrClassPathInspection */
// phpcs:disable Generic.Commenting.DocComment.MissingShort
* This handy class originated from Pippin's Easy Digital Downloads.
* See https://github.com/easydigitaldownloads/easy-digital-downloads/blob/master/includes/class-edd-db.php
* Subclasses should define $table_name, $version, and $primary_key in __construct() method.
abstract class WPForms_DB {
* Maximum length of index key.
* Indexes have a maximum size of 767 bytes. Historically, we haven't needed to be concerned about that.
* As of WP 4.2, however, WP moved to utf8mb4, which uses 4 bytes per character. This means that an index, which
* used to have room for floor(767/3) = 255 characters, now only has room for floor(767/4) = 191 characters.
const MAX_INDEX_LENGTH = 191;
* The dedicated cache key to store the All Keys array.
const ALL_KEYS = '_all_keys';
* Primary key (unique field) for the database table.
* Database type identifier.
* WPForms_DB constructor.
public function __construct() {
$this->cache_group = static::class . '_cache';
$this->cache_disabled = defined( 'WPFORMS_DISABLE_DB_CACHE' ) && WPFORMS_DISABLE_DB_CACHE;
private function hooks() {
add_filter( 'query', [ $this, 'query_filter' ] );
* Retrieve the list of columns for the database table.
* Subclasses should define an array of columns here.
* @return array List of columns.
public function get_columns() {
* Retrieve column defaults.
* Subclasses can define default for any/all columns defined in the get_columns() method.
* @return array All defined column defaults.
public function get_column_defaults() {
* @param string|mixed $query Query.
public function query_filter( $query ): string {
$query = (string) $query;
if ( strpos( $query, $this->table_name ) === false ) {
// Not a query for our table, bail out.
if ( ! $this->is_select( $query ) ) {
// Flush cache on non-SELECT queries.
$this->cache_flush_group();
* Retrieve a row from the database based on a given row ID.
* @param int $row_id Row ID.
public function get( $row_id ) {
$key = md5( __METHOD__ . $row_id );
$row = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
"SELECT * FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->cache_set( $key, $row );
* Retrieve a row based on column and row ID.
* @param string $column Column name.
* @param int|string $value Column value.
* @return object|null Database query result, object or null on failure.
public function get_by( $column, $value ) {
! array_key_exists( $column, $this->get_columns() )
$key = md5( __METHOD__ . $column . $value );
$row = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
"SELECT * FROM $this->table_name WHERE $column = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->cache_set( $key, $row );
* Retrieve a value based on column name and row ID.
* @param string $column Column name.
* @param int|string $row_id Row ID.
* @return string|null Database query result (as string), or null on failure.
* @noinspection PhpUnused
public function get_column( $column, $row_id ) {
if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) {
$key = md5( __METHOD__ . $column . $row_id );
$var = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
"SELECT $column FROM $this->table_name WHERE $this->primary_key = %d LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->cache_set( $key, $var );
* Retrieve one column value based on another given column and matching value.
* @param string $column Column name.
* @param string $column_where Column to match against in the WHERE clause.
* @param string $column_value Value to match to the column in the WHERE clause.
* @return string|null Database query result (as string), or null on failure.
* @noinspection PhpUnused
public function get_column_by( $column, $column_where, $column_value ) {
empty( $column_where ) ||
empty( $column_value ) ||
! array_key_exists( $column_where, $this->get_columns() ) ||
! array_key_exists( $column, $this->get_columns() )
$key = md5( __METHOD__ . $column . $column_where . $column_value );
$var = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
"SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->cache_set( $key, $var );
* Clone of $wpdb->query() with caching.
* @param string $query Database query.
* @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
* affected/selected for all other queries. Boolean false on error.
* @noinspection PhpMissingParamTypeInspection
public function query( $query ) {
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->query( $query );
$key = md5( __METHOD__ . $query );
$results = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->query( $query );
$this->cache_set( $key, $results );
* Clone of $wpdb->get_results() with caching.
* @param string|null $query SQL query.
* @param string $output Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
* @return array|object|null Database query results.
* @noinspection PhpMissingParamTypeInspection
public function get_results( $query = null, $output = OBJECT ) {
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( $query, $output );
$key = md5( __METHOD__ . $query . $output );
$results = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results( $query, $output );
$this->cache_set( $key, $results );
* Clone of $wpdb->get_col() with caching.
* @param string|null $query SQL query.
* @param int $x Column to return. Indexed from 0.
* @return array Database query results.
* @noinspection PhpMissingParamTypeInspection
public function get_col( $query = null, $x = 0 ) {
if ( ! $this->is_select( $query ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_col( $query );
$key = md5( __METHOD__ . $query . $x );
$col = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$col = $wpdb->get_col( $query, $x );
$this->cache_set( $key, $col );
* Clone of $wpdb->get_row() with caching.
* @param string|null $query SQL query.
* @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
* correspond to an stdClass object, an associative array, or a numeric array,
* respectively. Default OBJECT.
* @param int $y Optional. Row to return. Indexed from 0. Default 0.
* @return array|int|object|stdClass|null Database query result in format specified by $output or null on failure.
* @noinspection PhpMissingParamTypeInspection
public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_row( $query, $output, $y );
$key = md5( __METHOD__ . $query . $output . $y );
$row = $this->cache_get( $key, $found );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
$row = $wpdb->get_row( $query, $output, $y );
$this->cache_set( $key, $row );
* Clone of $wpdb->get_var() with caching.
* @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
* @param int $x Optional. Column of value to return. Indexed from 0. Default 0.
* @param int $y Optional. Row of value to return. Indexed from 0. Default 0.
* @return string|null Database query result (as string), or null on failure.
* @noinspection PhpMissingParamTypeInspection
public function get_var( $query = null, $x = 0, $y = 0 ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_var( $query, $x, $y );
$key = md5( __METHOD__ . $query . $x . $y );
$var = $this->cache_get( $key, $found );