installed plugin Easy Digital Downloads version 3.1.0.3

This commit is contained in:
2022-11-27 15:03:07 +00:00
committed by Gitium
parent 555673545b
commit c5dce2cec6
1200 changed files with 238970 additions and 0 deletions

View File

@ -0,0 +1,337 @@
<?php
/**
* Base Custom Database Class.
*
* @package Database
* @subpackage Base
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* The base class that all other database base classes extend.
*
* This class attempts to provide some universal immutability to all other
* classes that extend it, starting with a magic getter, but likely expanding
* into a magic call handler and others.
*
* @since 1.0.0
*/
class Base {
/**
* The name of the PHP global that contains the primary database interface.
*
* For example, WordPress traditionally uses 'wpdb', but other applications
* may use something else, or you may be doing something really cool that
* requires a custom interface.
*
* A future version of this utility may abstract this out entirely, so
* custom calls to the get_db() should be avoided if at all possible.
*
* @since 1.0.0
* @var string
*/
protected $db_global = 'wpdb';
/** Global Properties *****************************************************/
/**
* Global prefix used for tables/hooks/cache-groups/etc...
*
* @since 1.0.0
* @var string
*/
protected $prefix = 'edd';
/**
* The last database error, if any.
*
* @since 1.0.0
* @var mixed
*/
protected $last_error = false;
/** Public ****************************************************************/
/**
* Magic isset'ter for immutability.
*
* @since 1.0.0
*
* @param string $key
* @return mixed
*/
public function __isset( $key = '' ) {
// No more uppercase ID properties ever
if ( 'ID' === $key ) {
$key = 'id';
}
// Class method to try and call
$method = "get_{$key}";
// Return property if exists
if ( method_exists( $this, $method ) ) {
return true;
// Return get method results if exists
} elseif ( property_exists( $this, $key ) ) {
return true;
}
// Return false if not exists
return false;
}
/**
* Magic getter for immutability.
*
* @since 1.0.0
*
* @param string $key
* @return mixed
*/
public function __get( $key = '' ) {
// No more uppercase ID properties ever
if ( 'ID' === $key ) {
$key = 'id';
}
// Class method to try and call
$method = "get_{$key}";
// Return property if exists
if ( method_exists( $this, $method ) ) {
return call_user_func( array( $this, $method ) );
// Return get method results if exists
} elseif ( property_exists( $this, $key ) ) {
return $this->{$key};
}
// Return null if not exists
return null;
}
/**
* Converts the given object to an array.
*
* @since 1.0.0
*
* @return array Array version of the given object.
*/
public function to_array() {
return get_object_vars( $this );
}
/** Protected *************************************************************/
/**
* Maybe append the prefix to string.
*
* @since 1.0.0
*
* @param string $string
* @param string $sep
* @return string
*/
protected function apply_prefix( $string = '', $sep = '_' ) {
return ! empty( $this->prefix )
? "{$this->prefix}{$sep}{$string}"
: $string;
}
/**
* Return the first letters of a string of words with a separator.
*
* Used primarily to guess at table aliases when none is manually set.
*
* Applies the following formatting to a string:
* - Trim whitespace
* - No accents
* - No trailing underscores
*
* @since 1.0.0
*
* @param string $string
* @param string $sep
* @return string
*/
protected function first_letters( $string = '', $sep = '_' ) {
// Set empty default return value
$retval = '';
// Bail if empty or not a string
if ( empty( $string ) || ! is_string( $string ) ) {
return $retval;
}
// Trim spaces off the ends
$unspace = trim( $string );
$accents = remove_accents( $unspace );
$lower = strtolower( $accents );
$parts = explode( $sep, $lower );
// Loop through parts and concatenate the first letters together
foreach ( $parts as $part ) {
$retval .= substr( $part, 0, 1 );
}
// Return the result
return $retval;
}
/**
* Sanitize a table name string.
*
* Used to make sure that a table name value meets MySQL expectations.
*
* Applies the following formatting to a string:
* - Trim whitespace
* - No accents
* - No special characters
* - No hyphens
* - No double underscores
* - No trailing underscores
*
* @since 1.0.0
*
* @param string $name The name of the database table
*
* @return string Sanitized database table name
*/
protected function sanitize_table_name( $name = '' ) {
// Bail if empty or not a string
if ( empty( $name ) || ! is_string( $name ) ) {
return false;
}
// Trim spaces off the ends
$unspace = trim( $name );
// Only non-accented table names (avoid truncation)
$accents = remove_accents( $unspace );
// Only lowercase characters, hyphens, and dashes (avoid index corruption)
$lower = sanitize_key( $accents );
// Replace hyphens with single underscores
$under = str_replace( '-', '_', $lower );
// Single underscores only
$single = str_replace( '__', '_', $under );
// Remove trailing underscores
$clean = trim( $single, '_' );
// Bail if table name was garbaged
if ( empty( $clean ) ) {
return false;
}
// Return the cleaned table name
return $clean;
}
/**
* Set class variables from arguments.
*
* @since 1.0.0
* @param array $args
*/
protected function set_vars( $args = array() ) {
// Bail if empty or not an array
if ( empty( $args ) ) {
return;
}
// Cast to an array
if ( ! is_array( $args ) ) {
$args = (array) $args;
}
// Set all properties
foreach ( $args as $key => $value ) {
$this->{$key} = $value;
}
}
/**
* Return the global database interface.
*
* See: https://core.trac.wordpress.org/ticket/31556
*
* @since 1.0.0
*
* @return \wpdb Database interface, or False if not set
*/
protected function get_db() {
// Default database return value (might change)
$retval = false;
// Look for a commonly used global database interface
if ( isset( $GLOBALS[ $this->db_global ] ) ) {
$retval = $GLOBALS[ $this->db_global ];
}
/*
* Developer note:
*
* It should be impossible for a database table to be interacted with
* before the primary database interface it is setup.
*
* However, because applications are complicated, it is unsafe to assume
* anything, so this silently returns false instead of halting everything.
*
* If you are here because this method is returning false for you, that
* means the database table is being invoked too early in the lifecycle
* of the application.
*
* In WordPress, that means before the $wpdb global is created; in other
* environments, you will need to adjust accordingly.
*/
// Return the database interface
return $retval;
}
/**
* Check if an operation succeeded.
*
* @since 1.0.0
*
* @param mixed $result
* @return bool
*/
protected function is_success( $result = false ) {
// Bail if no row exists
if ( empty( $result ) ) {
$retval = false;
// Bail if an error occurred
} elseif ( is_wp_error( $result ) ) {
$this->last_error = $result;
$retval = false;
// No errors
} else {
$retval = true;
}
// Return the result
return (bool) $retval;
}
}

View File

@ -0,0 +1,965 @@
<?php
/**
* Base Custom Database Table Column Class.
*
* @package Database
* @subpackage Column
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Base class used for each column for a custom table.
*
* @since 1.0.0
*
* @see Column::__construct() for accepted arguments.
*/
class Column extends Base {
/** Table Attributes ******************************************************/
/**
* Name for the database column.
*
* Required. Must contain lowercase alphabetical characters only. Use of any
* other character (number, ascii, unicode, emoji, etc...) will result in
* fatal application errors.
*
* @since 1.0.0
* @var string
*/
public $name = '';
/**
* Type of database column.
*
* See: https://dev.mysql.com/doc/en/data-types.html
*
* @since 1.0.0
* @var string
*/
public $type = '';
/**
* Length of database column.
*
* See: https://dev.mysql.com/doc/en/storage-requirements.html
*
* @since 1.0.0
* @var string
*/
public $length = false;
/**
* Is integer unsigned?
*
* See: https://dev.mysql.com/doc/en/numeric-type-overview.html
*
* @since 1.0.0
* @var bool
*/
public $unsigned = true;
/**
* Is integer filled with zeroes?
*
* See: https://dev.mysql.com/doc/en/numeric-type-overview.html
*
* @since 1.0.0
* @var bool
*/
public $zerofill = false;
/**
* Is data in a binary format?
*
* See: https://dev.mysql.com/doc/en/binary-varbinary.html
*
* @since 1.0.0
* @var bool
*/
public $binary = false;
/**
* Is null an allowed value?
*
* See: https://dev.mysql.com/doc/en/data-type-defaults.html
*
* @since 1.0.0
* @var bool
*/
public $allow_null = false;
/**
* Typically empty/null, or date value.
*
* See: https://dev.mysql.com/doc/en/data-type-defaults.html
*
* @since 1.0.0
* @var string
*/
public $default = '';
/**
* auto_increment, etc...
*
* See: https://dev.mysql.com/doc/en/data-type-defaults.html
*
* @since 1.0.0
* @var string
*/
public $extra = '';
/**
* Typically inherited from the database interface (wpdb).
*
* By default, this will use the globally available database encoding. You
* most likely do not want to change this; if you do, you already know what
* to do.
*
* See: https://dev.mysql.com/doc/mysql/en/charset-column.html
*
* @since 1.0.0
* @var string
*/
public $encoding = '';
/**
* Typically inherited from the database interface (wpdb).
*
* By default, this will use the globally available database collation. You
* most likely do not want to change this; if you do, you already know what
* to do.
*
* See: https://dev.mysql.com/doc/mysql/en/charset-column.html
*
* @since 1.0.0
* @var string
*/
public $collation = '';
/**
* Typically empty; probably ignore.
*
* By default, columns do not have comments. This is unused by any other
* relative code, but you can include less than 1024 characters here.
*
* @since 1.0.0
* @var string
*/
public $comment = '';
/** Special Attributes ****************************************************/
/**
* Is this the primary column?
*
* By default, columns are not the primary column. This is used by the Query
* class for several critical functions, including (but not limited to) the
* cache key, meta-key relationships, auto-incrementing, etc...
*
* @since 1.0.0
* @var bool
*/
public $primary = false;
/**
* Is this the column used as a created date?
*
* By default, columns do not represent the date a value was first entered.
* This is used by the Query class to set its value automatically to the
* current datetime value immediately before insert.
*
* @since 1.0.0
* @var bool
*/
public $created = false;
/**
* Is this the column used as a modified date?
*
* By default, columns do not represent the date a value was last changed.
* This is used by the Query class to update its value automatically to the
* current datetime value immediately before insert|update.
*
* @since 1.0.0
* @var bool
*/
public $modified = false;
/**
* Is this the column used as a unique universal identifier?
*
* By default, columns are not UUIDs. This is used by the Query class to
* generate a unique string that can be used to identify a row in a database
* table, typically in such a way that is unrelated to the row data itself.
*
* @since 1.0.0
* @var bool
*/
public $uuid = false;
/** Query Attributes ******************************************************/
/**
* What is the string-replace pattern?
*
* By default, column patterns will be guessed based on their type. Set this
* manually to `%s|%d|%f` only if you are doing something weird, or are
* explicitly storing numeric values in text-based column types.
*
* @since 1.0.0
* @var string
*/
public $pattern = '';
/**
* Is this column searchable?
*
* By default, columns are not searchable. When `true`, the Query class will
* add this column to the results of search queries.
*
* Avoid setting to `true` on large blobs of text, unless you've optimized
* your database server to accommodate these kinds of queries.
*
* @since 1.0.0
* @var bool
*/
public $searchable = false;
/**
* Is this column a date?
*
* By default, columns do not support date queries. When `true`, the Query
* class will accept complex statements to help narrow results down to
* specific periods of time for values in this column.
*
* @since 1.0.0
* @var bool
*/
public $date_query = false;
/**
* Is this column used in orderby?
*
* By default, columns are not sortable. This ensures that the database
* table does not perform costly operations on unindexed columns or columns
* of an inefficient type.
*
* You can safely turn this on for most numeric columns, indexed columns,
* and text columns with intentionally limited lengths.
*
* @since 1.0.0
* @var bool
*/
public $sortable = false;
/**
* Is __in supported?
*
* By default, columns support being queried using an `IN` statement. This
* allows the Query class to retrieve rows that match your array of values.
*
* Consider setting this to `false` for longer text columns.
*
* @since 1.0.0
* @var bool
*/
public $in = true;
/**
* Is __not_in supported?
*
* By default, columns support being queried using a `NOT IN` statement.
* This allows the Query class to retrieve rows that do not match your array
* of values.
*
* Consider setting this to `false` for longer text columns.
*
* @since 1.0.0
* @var bool
*/
public $not_in = true;
/** Cache Attributes ******************************************************/
/**
* Does this column have its own cache key?
*
* By default, only primary columns are used as cache keys. If this column
* is unique, or is frequently used to get database results, you may want to
* consider setting this to true.
*
* Use in conjunction with a database index for speedy queries.
*
* @since 1.0.0
* @var string
*/
public $cache_key = false;
/** Action Attributes *****************************************************/
/**
* Does this column fire a transition action when it's value changes?
*
* By default, columns do not fire transition actions. In some cases, it may
* be desirable to know when a database value changes, and what the old and
* new values are when that happens.
*
* The Query class is responsible for triggering the event action.
*
* @since 1.0.0
* @var bool
*/
public $transition = false;
/** Callback Attributes ***************************************************/
/**
* Maybe validate this data before it is written to the database.
*
* By default, column data is validated based on the type of column that it
* is. You can set this to a callback function of your choice to override
* the default validation behavior.
*
* @since 1.0.0
* @var string
*/
public $validate = '';
/**
* Array of capabilities used to interface with this column.
*
* These are used by the Query class to allow and disallow CRUD access to
* column data, typically based on roles or capabilities.
*
* @since 1.0.0
* @var array
*/
public $caps = array();
/**
* Array of possible aliases this column can be referred to as.
*
* These are used by the Query class to allow for columns to be renamed
* without requiring complex architectural backwards compatibility support.
*
* @since 1.0.0
* @var array
*/
public $aliases = array();
/**
* Array of possible relationships this column has with columns in other
* database tables.
*
* These are typically unenforced foreign keys, and are used by the Query
* class to help prime related items.
*
* @since 1.0.0
* @var array
*/
public $relationships = array();
/** Methods ***************************************************************/
/**
* Sets up the order query, based on the query vars passed.
*
* @since 1.0.0
*
* @param string|array $args {
* Optional. Array or query string of order query parameters. Default empty.
*
* @type string $name Name of database column
* @type string $type Type of database column
* @type int $length Length of database column
* @type bool $unsigned Is integer unsigned?
* @type bool $zerofill Is integer filled with zeroes?
* @type bool $binary Is data in a binary format?
* @type bool $allow_null Is null an allowed value?
* @type mixed $default Typically empty/null, or date value
* @type string $extra auto_increment, etc...
* @type string $encoding Typically inherited from wpdb
* @type string $collation Typically inherited from wpdb
* @type string $comment Typically empty
* @type bool $pattern What is the string-replace pattern?
* @type bool $primary Is this the primary column?
* @type bool $created Is this the column used as a created date?
* @type bool $modified Is this the column used as a modified date?
* @type bool $uuid Is this the column used as a universally unique identifier?
* @type bool $searchable Is this column searchable?
* @type bool $sortable Is this column used in orderby?
* @type bool $date_query Is this column a datetime?
* @type bool $in Is __in supported?
* @type bool $not_in Is __not_in supported?
* @type bool $cache_key Is this column queried independently?
* @type bool $transition Does this column transition between changes?
* @type string $validate A callback function used to validate on save.
* @type array $caps Array of capabilities to check.
* @type array $aliases Array of possible column name aliases.
* @type array $relationships Array of columns in other tables this column relates to.
* }
*/
public function __construct( $args = array() ) {
// Parse arguments
$r = $this->parse_args( $args );
// Maybe set variables from arguments
if ( ! empty( $r ) ) {
$this->set_vars( $r );
}
}
/** Argument Handlers *****************************************************/
/**
* Parse column arguments
*
* @since 1.0.0
* @param array $args Default empty array.
* @return array
*/
private function parse_args( $args = array() ) {
// Parse arguments
$r = wp_parse_args( $args, array(
// Table
'name' => '',
'type' => '',
'length' => '',
'unsigned' => false,
'zerofill' => false,
'binary' => false,
'allow_null' => false,
'default' => '',
'extra' => '',
'encoding' => $this->get_db()->charset,
'collation' => $this->get_db()->collate,
'comment' => '',
// Query
'pattern' => false,
'searchable' => false,
'sortable' => false,
'date_query' => false,
'transition' => false,
'in' => true,
'not_in' => true,
// Special
'primary' => false,
'created' => false,
'modified' => false,
'uuid' => false,
// Cache
'cache_key' => false,
// Validation
'validate' => '',
// Capabilities
'caps' => array(),
// Backwards Compatibility
'aliases' => array(),
// Column Relationships
'relationships' => array()
) );
// Force some arguments for special column types
$r = $this->special_args( $r );
// Set the args before they are sanitized
$this->set_vars( $r );
// Return array
return $this->validate_args( $r );
}
/**
* Validate arguments after they are parsed.
*
* @since 1.0.0
* @param array $args Default empty array.
* @return array
*/
private function validate_args( $args = array() ) {
// Sanitization callbacks
$callbacks = array(
'name' => 'sanitize_key',
'type' => 'strtoupper',
'length' => 'intval',
'unsigned' => 'wp_validate_boolean',
'zerofill' => 'wp_validate_boolean',
'binary' => 'wp_validate_boolean',
'allow_null' => 'wp_validate_boolean',
'default' => array( $this, 'sanitize_default' ),
'extra' => 'wp_kses_data',
'encoding' => 'wp_kses_data',
'collation' => 'wp_kses_data',
'comment' => 'wp_kses_data',
'primary' => 'wp_validate_boolean',
'created' => 'wp_validate_boolean',
'modified' => 'wp_validate_boolean',
'uuid' => 'wp_validate_boolean',
'searchable' => 'wp_validate_boolean',
'sortable' => 'wp_validate_boolean',
'date_query' => 'wp_validate_boolean',
'transition' => 'wp_validate_boolean',
'in' => 'wp_validate_boolean',
'not_in' => 'wp_validate_boolean',
'cache_key' => 'wp_validate_boolean',
'pattern' => array( $this, 'sanitize_pattern' ),
'validate' => array( $this, 'sanitize_validation' ),
'caps' => array( $this, 'sanitize_capabilities' ),
'aliases' => array( $this, 'sanitize_aliases' ),
'relationships' => array( $this, 'sanitize_relationships' )
);
// Default args array
$r = array();
// Loop through and try to execute callbacks
foreach ( $args as $key => $value ) {
// Callback is callable
if ( isset( $callbacks[ $key ] ) && is_callable( $callbacks[ $key ] ) ) {
$r[ $key ] = call_user_func( $callbacks[ $key ], $value );
// Callback is malformed so just let it through to avoid breakage
} else {
$r[ $key ] = $value;
}
}
// Return sanitized arguments
return $r;
}
/**
* Force column arguments for special column types
*
* @since 1.0.0
* @param array $args Default empty array.
* @return array
*/
private function special_args( $args = array() ) {
// Primary key columns are always used as cache keys
if ( ! empty( $args['primary'] ) ) {
$args['cache_key'] = true;
// All UUID columns need to follow a very specific pattern
} elseif ( ! empty( $args['uuid'] ) ) {
$args['name'] = 'uuid';
$args['type'] = 'varchar';
$args['length'] = '100';
$args['in'] = false;
$args['not_in'] = false;
$args['searchable'] = false;
$args['sortable'] = false;
}
// Return args
return (array) $args;
}
/** Public Helpers ********************************************************/
/**
* Return if a column type is numeric or not.
*
* @since 1.0.0
* @return bool
*/
public function is_numeric() {
return $this->is_type( array(
'tinyint',
'int',
'mediumint',
'bigint'
) );
}
/** Private Helpers *******************************************************/
/**
* Return if this column is of a certain type.
*
* @since 1.0.0
* @param mixed $type Default empty string. The type to check. Also accepts an array.
* @return bool True if of type, False if not
*/
private function is_type( $type = '' ) {
// If string, cast to array
if ( is_string( $type ) ) {
$type = (array) $type;
}
// Make them lowercase
$types = array_map( 'strtolower', $type );
// Return if match or not
return (bool) in_array( strtolower( $this->type ), $types, true );
}
/** Private Sanitizers ****************************************************/
/**
* Sanitize capabilities array
*
* @since 1.0.0
* @param array $caps Default empty array.
* @return array
*/
private function sanitize_capabilities( $caps = array() ) {
return wp_parse_args( $caps, array(
'select' => 'exist',
'insert' => 'exist',
'update' => 'exist',
'delete' => 'exist'
) );
}
/**
* Sanitize aliases array using `sanitize_key()`
*
* @since 1.0.0
* @param array $aliases Default empty array.
* @return array
*/
private function sanitize_aliases( $aliases = array() ) {
return array_map( 'sanitize_key', $aliases );
}
/**
* Sanitize relationships array
*
* @todo
* @since 1.0.0
* @param array $relationships Default empty array.
* @return array
*/
private function sanitize_relationships( $relationships = array() ) {
return array_filter( $relationships );
}
/**
* Sanitize the default value
*
* @since 1.0.0
* @param string $default
* @return string|null
*/
private function sanitize_default( $default = '' ) {
// Null
if ( ( true === $this->allow_null ) && is_null( $default ) ) {
return null;
// String
} elseif ( is_string( $default ) ) {
return wp_kses_data( $default );
// Integer
} elseif ( $this->is_numeric( $default ) ) {
return (int) $default;
}
// @todo datetime, decimal, and other column types
// Unknown, so return the default's default
return '';
}
/**
* Sanitize the pattern
*
* @since 1.0.0
* @param mixed $pattern
* @return string
*/
private function sanitize_pattern( $pattern = false ) {
// Allowed patterns
$allowed_patterns = array( '%s', '%d', '%f' );
// Return pattern if allowed
if ( in_array( $pattern, $allowed_patterns, true ) ) {
return $pattern;
}
// Fallback to digit or string
return $this->is_numeric()
? '%d'
: '%s';
}
/**
* Sanitize the validation callback
*
* @since 1.0.0
* @param string $callback Default empty string. A callable PHP function name or method
* @return string The most appropriate callback function for the value
*/
private function sanitize_validation( $callback = '' ) {
// Return callback if it's callable
if ( is_callable( $callback ) ) {
return $callback;
}
// UUID special column
if ( true === $this->uuid ) {
$callback = array( $this, 'validate_uuid' );
// Datetime fallback
} elseif ( $this->is_type( 'datetime' ) ) {
$callback = array( $this, 'validate_datetime' );
// Decimal fallback
} elseif ( $this->is_type( 'decimal' ) ) {
$callback = array( $this, 'validate_decimal' );
// Intval fallback
} elseif ( $this->is_numeric() ) {
$callback = 'intval';
}
// Return the callback
return $callback;
}
/** Public Validators *****************************************************/
/**
* Fallback to validate a datetime value if no other is set.
*
* This assumes NO_ZERO_DATES is off or overridden.
*
* If MySQL drops support for zero dates, this method will need to be
* updated to support different default values based on the environment.
*
* @since 1.0.0
* @param string $value Default ''. A datetime value that needs validating
* @return string A valid datetime value
*/
public function validate_datetime( $value = '' ) {
// Handle "empty" values
if ( empty( $value ) || ( '0000-00-00 00:00:00' === $value ) ) {
$value = ! empty( $this->default )
? $this->default
: '';
// Convert to MySQL datetime format via date() && strtotime
} elseif ( function_exists( 'date' ) ) {
$value = date( 'Y-m-d H:i:s', strtotime( $value ) );
}
// Return the validated value
return $value;
}
/**
* Validate a decimal
*
* (Recommended decimal column length is '18,9'.)
*
* This is used to validate a mixed value before it is saved into a decimal
* column in a database table.
*
* Uses number_format() which does rounding to the last decimal if your
* value is longer than specified.
*
* @since 1.0.0
* @param mixed $value Default empty string. The decimal value to validate
* @param int $decimals Default 9. The number of decimal points to accept
* @return float
*/
public function validate_decimal( $value = 0, $decimals = 9 ) {
// Protect against non-numeric values
if ( ! is_numeric( $value ) ) {
$value = 0;
}
// Protect against non-numeric decimals
if ( ! is_numeric( $decimals ) ) {
$decimals = 9;
}
// Is the value negative?
$negative_exponent = ( $value < 0 )
? -1
: 1;
// Only numbers and period
$value = preg_replace( '/[^0-9\.]/', '', (string) $value );
// Format to number of decimals, and cast as float
$formatted = number_format( $value, $decimals, '.', '' );
// Adjust for negative values
$retval = $formatted * $negative_exponent;
// Return
return $retval;
}
/**
* Validate a UUID.
*
* This uses the v4 algorithm to generate a UUID that is used to uniquely
* and universally identify a given database row without any direct
* connection or correlation to the data in that row.
*
* From http://php.net/manual/en/function.uniqid.php#94959
*
* @since 1.0.0
* @param string $value The UUID value (empty on insert, string on update)
* @return string Generated UUID.
*/
public function validate_uuid( $value = '' ) {
// Default URN UUID prefix
$prefix = 'urn:uuid:';
// Bail if not empty and correctly prefixed
// (UUIDs should _never_ change once they are set)
if ( ! empty( $value ) && ( 0 === strpos( $value, $prefix ) ) ) {
return $value;
}
// Put the pieces together
$value = sprintf( "{$prefix}%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
// Return the new UUID
return $value;
}
/** Table Helpers *********************************************************/
/**
* Return a string representation of what this column's properties look like
* in a MySQL.
*
* @todo
* @since 1.0.0
* @return string
*/
public function get_create_string() {
// Default return val
$retval = '';
// Bail if no name
if ( ! empty( $this->name ) ) {
$retval .= $this->name;
}
// Type
if ( ! empty( $this->type ) ) {
$retval .= " {$this->type}";
}
// Length
if ( ! empty( $this->length ) ) {
$retval .= '(' . $this->length . ')';
}
// Unsigned
if ( ! empty( $this->unsigned ) ) {
$retval .= " unsigned";
}
// Zerofill
if ( ! empty( $this->zerofill ) ) {
// TBD
}
// Binary
if ( ! empty( $this->binary ) ) {
// TBD
}
// Allow null
if ( ! empty( $this->allow_null ) ) {
$retval .= " NOT NULL ";
}
// Default
if ( ! empty( $this->default ) ) {
$retval .= " default '{$this->default}'";
// A literal false means no default value
} elseif ( false !== $this->default ) {
// Numeric
if ( $this->is_numeric() ) {
$retval .= " default '0'";
} elseif ( $this->is_type( 'datetime' ) ) {
$retval .= " default '0000-00-00 00:00:00'";
} else {
$retval .= " default ''";
}
}
// Extra
if ( ! empty( $this->extra ) ) {
$retval .= " {$this->extra}";
}
// Encoding
if ( ! empty( $this->encoding ) ) {
} else {
}
// Collation
if ( ! empty( $this->collation ) ) {
} else {
}
// Return the create string
return $retval;
}
}

View File

@ -0,0 +1,157 @@
<?php
/**
* Base Custom Database Table Compare Query Class.
*
* @package Database
* @subpackage Compare
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database\Queries;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Class used for generating SQL for compare clauses.
*
* This class is used to generate the SQL when a `compare` argument is passed to
* the `Base` query class. It extends `Meta` so the `compare` key accepts
* the same parameters as the ones passed to `Meta`.
*
* @since 1.0.0
*/
class Compare extends Meta {
/**
* Generate SQL WHERE clauses for a first-order query clause.
*
* "First-order" means that it's an array with a 'key' or 'value'.
*
* @since 1.0.0
*
* @param array $clause Query clause (passed by reference).
* @param array $parent_query Parent query array.
* @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query`
* parameters. If not provided, a key will be generated automatically.
* @return array {
* Array containing WHERE SQL clauses to append to a first-order query.
*
* @type string $where SQL fragment to append to the main WHERE clause.
* }
*/
public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
global $wpdb;
$sql_chunks = array(
'where' => array(),
'join' => array(),
);
if ( isset( $clause['compare'] ) ) {
$clause['compare'] = strtoupper( $clause['compare'] );
} else {
$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
}
if ( ! in_array(
$clause['compare'], array(
'=',
'!=',
'>',
'>=',
'<',
'<=',
'LIKE',
'NOT LIKE',
'IN',
'NOT IN',
'BETWEEN',
'NOT BETWEEN',
'EXISTS',
'NOT EXISTS',
'REGEXP',
'NOT REGEXP',
'RLIKE',
), true
) ) {
$clause['compare'] = '=';
}
if ( isset( $clause['compare_key'] ) && 'LIKE' === strtoupper( $clause['compare_key'] ) ) {
$clause['compare_key'] = strtoupper( $clause['compare_key'] );
} else {
$clause['compare_key'] = '=';
}
$compare = $clause['compare'];
$compare_key = $clause['compare_key'];
// Build the WHERE clause.
// Column name and value.
if ( array_key_exists( 'key', $clause ) && array_key_exists( 'value', $clause ) ) {
$column = sanitize_key( $clause['key'] );
$value = $clause['value'];
if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) {
if ( ! is_array( $value ) ) {
$value = preg_split( '/[,\s]+/', $value );
}
} else {
$value = trim( $value );
}
switch ( $compare ) {
case 'IN':
case 'NOT IN':
$compare_string = '(' . substr( str_repeat( ',%s', count( $value ) ), 1 ) . ')';
$where = $wpdb->prepare( $compare_string, $value );
break;
case 'BETWEEN':
case 'NOT BETWEEN':
$value = array_slice( $value, 0, 2 );
$where = $wpdb->prepare( '%s AND %s', $value );
break;
case 'LIKE':
case 'NOT LIKE':
$value = '%' . $wpdb->esc_like( $value ) . '%';
$where = $wpdb->prepare( '%s', $value );
break;
// EXISTS with a value is interpreted as '='.
case 'EXISTS':
$compare = '=';
$where = $wpdb->prepare( '%s', $value );
break;
// 'value' is ignored for NOT EXISTS.
case 'NOT EXISTS':
$where = '';
break;
default:
$where = $wpdb->prepare( '%s', $value );
break;
}
if ( $where ) {
$sql_chunks['where'][] = "{$column} {$compare} {$where}";
}
}
/*
* Multiple WHERE clauses (for meta_key and meta_value) should
* be joined in parentheses.
*/
if ( 1 < count( $sql_chunks['where'] ) ) {
$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
}
return $sql_chunks;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
<?php
/**
* Base Custom Database Table Meta Query Class.
*
* @package Database
* @subpackage Meta
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.1.0
*/
namespace EDD\Database\Queries;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
// @todo Remove the need for this dependency
use \WP_Meta_Query;
/**
* Class for generating SQL clauses that filter a primary query according to meta.
*
* It currently extends the WP_Meta_Query class in WordPress, but in the future
* will be derived completely from other registered tables.
*
* @since 1.1.0
*/
class Meta extends WP_Meta_Query {
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Base Custom Database Table Row Class.
*
* @package Database
* @subpackage Row
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Base database row class.
*
* This class exists solely for other classes to extend (and to encapsulate
* database schema changes for those objects) to help separate the needs of the
* application layer from the requirements of the database layer.
*
* For example, if a database column is renamed or a return value needs to be
* formatted differently, this class will make sure old values are still
* supported and new values do not conflict.
*
* @since 1.0.0
*/
class Row extends Base {
/**
* Construct a database object.
*
* @since 1.0.0
*
* @param mixed Null by default, Array/Object if not
*/
public function __construct( $item = null ) {
if ( ! empty( $item ) ) {
$this->init( $item );
}
}
/**
* Initialize class properties based on data array.
*
* @since 1.0.0
*
* @param array $data
*/
private function init( $data = array() ) {
$this->set_vars( $data );
}
/**
* Determines whether the current row exists.
*
* @since 1.0.0
*
* @return bool
*/
public function exists() {
return ! empty( $this->id );
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* Base Custom Database Table Schema Class.
*
* @package Database
* @subpackage Schema
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* A base database table schema class, which houses the collection of columns
* that a table is made out of.
*
* This class is intended to be extended for each unique database table,
* including global tables for multisite, and users tables.
*
* @since 1.0.0
*/
class Schema extends Base {
/**
* Array of database column objects to turn into Column.
*
* @since 1.0.0
* @var array
*/
protected $columns = array();
/**
* Invoke new column objects based on array of column data.
*
* @since 1.0.0
*/
public function __construct() {
// Bail if no columns
if ( empty( $this->columns ) || ! is_array( $this->columns ) ) {
return;
}
// Juggle original columns array
$columns = $this->columns;
$this->columns = array();
// Loop through columns and create objects from them
foreach ( $columns as $column ) {
if ( is_array( $column ) ) {
$this->columns[] = new Column( $column );
} elseif ( $column instanceof Column ) {
$this->columns[] = $column;
}
}
}
/**
* Return the schema in string form.
*
* @since 1.0.0
*
* @return string Calls get_create_string() on every column.
*/
protected function to_string() {
// Default return value
$retval = '';
// Bail if no columns to convert
if ( empty( $this->columns ) ) {
return $retval;
}
// Loop through columns...
foreach ( $this->columns as $column_info ) {
if ( method_exists( $column_info, 'get_create_string' ) ) {
$retval .= '\n' . $column_info->get_create_string() . ', ';
}
}
// Return the string
return $retval;
}
}

View File

@ -0,0 +1,963 @@
<?php
/**
* Base Custom Database Table Class.
*
* @package Database
* @subpackage Table
* @copyright Copyright (c) 2020
* @license https://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0.0
*/
namespace EDD\Database;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* A base database table class, which facilitates the creation of (and schema
* changes to) individual database tables.
*
* This class is intended to be extended for each unique database table,
* including global tables for multisite, and users tables.
*
* It exists to make managing database tables as easy as possible.
*
* Extending this class comes with several automatic benefits:
* - Activation hook makes it great for plugins
* - Tables store their versions in the database independently
* - Tables upgrade via independent upgrade abstract methods
* - Multisite friendly - site tables switch on "switch_blog" action
*
* @since 1.0.0
*/
abstract class Table extends Base {
/**
* Table name, without the global table prefix.
*
* @since 1.0.0
* @var string
*/
protected $name = '';
/**
* Optional description.
*
* @since 1.0.0
* @var string
*/
protected $description = '';
/**
* Database version.
*
* @since 1.0.0
* @var mixed
*/
protected $version = '';
/**
* Is this table for a site, or global.
*
* @since 1.0.0
* @var bool
*/
protected $global = false;
/**
* Database version key (saved in _options or _sitemeta)
*
* @since 1.0.0
* @var string
*/
protected $db_version_key = '';
/**
* Current database version.
*
* @since 1.0.0
* @var mixed
*/
protected $db_version = 0;
/**
* Table prefix, including the site prefix.
*
* @since 1.0.0
* @var string
*/
protected $table_prefix = '';
/**
* Table name.
*
* @since 1.0.0
* @var string
*/
protected $table_name = '';
/**
* Table name, prefixed from the base.
*
* @since 1.0.0
* @var string
*/
protected $prefixed_name = '';
/**
* Table schema.
*
* @since 1.0.0
* @var string
*/
protected $schema = '';
/**
* Database character-set & collation for table.
*
* @since 1.0.0
* @var string
*/
protected $charset_collation = '';
/**
* Key => value array of versions => methods.
*
* @since 1.0.0
* @var array
*/
protected $upgrades = array();
/** Methods ***************************************************************/
/**
* Hook into queries, admin screens, and more!
*
* @since 1.0.0
*/
public function __construct() {
// Setup the database table
$this->setup();
// Bail if setup failed
if ( empty( $this->name ) || empty( $this->db_version_key ) ) {
return;
}
// Add the table to the database interface
$this->set_db_interface();
// Set the database schema
$this->set_schema();
// Add hooks
$this->add_hooks();
// Maybe force upgrade if testing
if ( $this->is_testing() ) {
$this->maybe_upgrade();
}
}
/** Abstract **************************************************************/
/**
* Setup this database table.
*
* @since 1.0.0
*/
protected abstract function set_schema();
/** Multisite *************************************************************/
/**
* Update table version & references.
*
* Hooked to the "switch_blog" action.
*
* @since 1.0.0
*
* @param int $site_id The site being switched to
*/
public function switch_blog( $site_id = 0 ) {
// Update DB version based on the current site
if ( ! $this->is_global() ) {
$this->db_version = get_blog_option( $site_id, $this->db_version_key, false );
}
// Update interface for switched site
$this->set_db_interface();
}
/** Public Helpers ********************************************************/
/**
* Maybe upgrade the database table. Handles creation & schema changes.
*
* Hooked to the `admin_init` action.
*
* @since 1.0.0
*/
public function maybe_upgrade() {
// Bail if not upgradeable
if ( ! $this->is_upgradeable() ) {
return;
}
// Bail if upgrade not needed
if ( ! $this->needs_upgrade() ) {
return;
}
// Upgrade
if ( $this->exists() ) {
$this->upgrade();
// Install
} else {
$this->install();
}
}
/**
* Return whether this table needs an upgrade.
*
* @since 1.0.0
*
* @param mixed $version Database version to check if upgrade is needed
*
* @return bool True if table needs upgrading. False if not.
*/
public function needs_upgrade( $version = false ) {
// Use the current table version if none was passed
if ( empty( $version ) ) {
$version = $this->version;
}
// Get the current database version
$this->get_db_version();
// Is the database table up to date?
$is_current = version_compare( $this->db_version, $version, '>=' );
// Return false if current, true if out of date
return ( true === $is_current )
? false
: true;
}
/**
* Return whether this table can be upgraded.
*
* @since 1.0.0
*
* @return bool True if table can be upgraded. False if not.
*/
public function is_upgradeable() {
// Bail if global and upgrading global tables is not allowed
if ( $this->is_global() && ! wp_should_upgrade_global_tables() ) {
return false;
}
// Kinda weird, but assume it is
return true;
}
/**
* Return the current table version from the database.
*
* This is public method for accessing a private variable so that it cannot
* be externally modified.
*
* @since 1.0.0
*
* @return string
*/
public function get_version() {
$this->get_db_version();
return $this->db_version;
}
/**
* Install a database table by creating the table and setting the version.
*
* @since 1.0.0
*/
public function install() {
$created = $this->create();
// Set the DB version if create was successful
if ( true === $created ) {
$this->set_db_version();
}
}
/**
* Destroy a database table by dropping the table and deleting the version.
*
* @since 1.0.0
*/
public function uninstall() {
$dropped = $this->drop();
// Delete the DB version if drop was successful
if ( true === $dropped ) {
$this->delete_db_version();
}
}
/** Public Management *****************************************************/
/**
* Check if table already exists.
*
* @since 1.0.0
*
* @return bool
*/
public function exists() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "SHOW TABLES LIKE %s";
$like = $db->esc_like( $this->table_name );
$prepared = $db->prepare( $query, $like );
$result = $db->get_var( $prepared );
// Does the table exist?
return $this->is_success( $result );
}
/**
* Create the table.
*
* @since 1.0.0
*
* @return bool
*/
public function create() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "CREATE TABLE {$this->table_name} ( {$this->schema} ) {$this->charset_collation}";
$result = $db->query( $query );
// Was the table created?
return $this->is_success( $result );
}
/**
* Drop the database table.
*
* @since 1.0.0
*
* @return bool
*/
public function drop() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "DROP TABLE {$this->table_name}";
$result = $db->query( $query );
// Did the table get dropped?
return $this->is_success( $result );
}
/**
* Truncate the database table.
*
* @since 1.0.0
*
* @return bool
*/
public function truncate() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "TRUNCATE TABLE {$this->table_name}";
$result = $db->query( $query );
// Did the table get truncated?
return $this->is_success( $result );
}
/**
* Delete all items from the database table.
*
* @since 1.0.0
*
* @return bool
*/
public function delete_all() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "DELETE FROM {$this->table_name}";
$deleted = $db->query( $query );
// Did the table get emptied?
return $deleted;
}
/**
* Clone this database table.
*
* Pair with copy().
*
* @since 1.1.0
*
* @param string $new_table_name The name of the new table, without prefix
*
* @return bool
*/
public function _clone( $new_table_name = '' ) {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Sanitize the new table name
$table_name = $this->sanitize_table_name( $new_table_name );
// Bail if new table name is invalid
if ( empty( $table_name ) ) {
return false;
}
// Query statement
$table = $this->apply_prefix( $table_name );
$query = "CREATE TABLE {$table} LIKE {$this->table_name}";
$result = $db->query( $query );
// Did the table get cloned?
return $this->is_success( $result );
}
/**
* Copy the contents of this table to a new table.
*
* Pair with clone().
*
* @since 1.1.0
*
* @param string $new_table_name The name of the new table, without prefix
*
* @return bool
*/
public function copy( $new_table_name = '' ) {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Sanitize the new table name
$table_name = $this->sanitize_table_name( $new_table_name );
// Bail if new table name is invalid
if ( empty( $table_name ) ) {
return false;
}
// Query statement
$table = $this->apply_prefix( $table_name );
$query = "INSERT INTO {$table} SELECT * FROM {$this->table_name}";
$result = $db->query( $query );
// Did the table get copied?
return $this->is_success( $result );
}
/**
* Count the number of items in the database table.
*
* @since 1.0.0
*
* @return int
*/
public function count() {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return 0;
}
// Query statement
$query = "SELECT COUNT(*) FROM {$this->table_name}";
$count = $db->get_var( $query );
// Query success/fail
return intval( $count );
}
/**
* Check if column already exists.
*
* @since 1.0.0
*
* @param string $name Value
*
* @return bool
*/
public function column_exists( $name = '' ) {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
// Query statement
$query = "SHOW COLUMNS FROM {$this->table_name} LIKE %s";
$like = $db->esc_like( $name );
$prepared = $db->prepare( $query, $like );
$result = $db->query( $prepared );
// Does the column exist?
return $this->is_success( $result );
}
/**
* Check if index already exists.
*
* @since 1.0.0
*
* @param string $name Value
* @param string $column Column name
*
* @return bool
*/
public function index_exists( $name = '', $column = 'Key_name' ) {
// Get the database interface
$db = $this->get_db();
// Bail if no database interface is available
if ( empty( $db ) ) {
return false;
}
$column = esc_sql( $column );
// Query statement
$query = "SHOW INDEXES FROM {$this->table_name} WHERE {$column} LIKE %s";
$like = $db->esc_like( $name );
$prepared = $db->prepare( $query, $like );
$result = $db->query( $prepared );
// Does the index exist?
return $this->is_success( $result );
}
/** Upgrades **************************************************************/
/**
* Upgrade this database table.
*
* @since 1.0.0
*
* @return bool
*/
public function upgrade() {
// Get pending upgrades
$upgrades = $this->get_pending_upgrades();
// Bail if no upgrades
if ( empty( $upgrades ) ) {
$this->set_db_version();
// Return, without failure
return true;
}
// Default result
$result = false;
// Try to do the upgrades
foreach ( $upgrades as $version => $callback ) {
// Do the upgrade
$result = $this->upgrade_to( $version, $callback );
// Bail if an error occurs, to avoid skipping upgrades
if ( ! $this->is_success( $result ) ) {
return false;
}
}
// Success/fail
return $this->is_success( $result );
}
/**
* Return array of upgrades that still need to run.
*
* @since 1.1.0
*
* @return array Array of upgrade callbacks, keyed by their db version.
*/
public function get_pending_upgrades() {
// Default return value
$upgrades = array();
// Bail if no upgrades, or no database version to compare to
if ( empty( $this->upgrades ) || empty( $this->db_version ) ) {
return $upgrades;
}
// Loop through all upgrades, and pick out the ones that need doing
foreach ( $this->upgrades as $version => $callback ) {
if ( true === version_compare( $version, $this->db_version, '>' ) ) {
$upgrades[ $version ] = $callback;
}
}
// Return
return $upgrades;
}
/**
* Upgrade to a specific database version.
*
* @since 1.0.0
*
* @param mixed $version Database version to check if upgrade is needed
* @param string $callback Callback function or class method to call
*
* @return bool
*/
public function upgrade_to( $version = '', $callback = '' ) {
// Bail if no upgrade is needed
if ( ! $this->needs_upgrade( $version ) ) {
return false;
}
// Allow self-named upgrade callbacks
if ( empty( $callback ) ) {
$callback = $version;
}
// Is the callback... callable?
$callable = $this->get_callable( $callback );
// Bail if no callable upgrade was found
if ( empty( $callable ) ) {
return false;
}
// Do the upgrade
$result = call_user_func( $callable );
$success = $this->is_success( $result );
// Bail if upgrade failed
if ( true !== $success ) {
return false;
}
// Set the database version to this successful version
$this->set_db_version( $version );
// Return success
return true;
}
/** Private ***************************************************************/
/**
* Setup the necessary table variables.
*
* @since 1.0.0
*/
private function setup() {
// Bail if no database interface is available
if ( ! $this->get_db() ) {
return;
}
// Sanitize the database table name
$this->name = $this->sanitize_table_name( $this->name );
// Bail if database table name was garbage
if ( false === $this->name ) {
return;
}
// Separator
$glue = '_';
// Setup the prefixed name
$this->prefixed_name = $this->apply_prefix( $this->name, $glue );
// Maybe create database key
if ( empty( $this->db_version_key ) ) {
$this->db_version_key = implode(
$glue,
array(
sanitize_key( $this->db_global ),
$this->prefixed_name,
'version'
)
);
}
}
/**
* Set this table up in the database interface.
*
* This must be done directly because the database interface does not
* have a common mechanism for manipulating them safely.
*
* @since 1.0.0
*/
private function set_db_interface() {
// Get the database once, to avoid duplicate function calls
$db = $this->get_db();
// Bail if no database
if ( empty( $db ) ) {
return;
}
// Set variables for global tables
if ( $this->is_global() ) {
$site_id = 0;
$tables = 'ms_global_tables';
// Set variables for per-site tables
} else {
$site_id = null;
$tables = 'tables';
}
// Set the table prefix and prefix the table name
$this->table_prefix = $db->get_blog_prefix( $site_id );
// Get the prefixed table name
$prefixed_table_name = "{$this->table_prefix}{$this->prefixed_name}";
// Set the database interface
$db->{$this->prefixed_name} = $this->table_name = $prefixed_table_name;
// Create the array if it does not exist
if ( ! isset( $db->{$tables} ) ) {
$db->{$tables} = array();
}
// Add the table to the global table array
$db->{$tables}[] = $this->prefixed_name;
// Charset
if ( ! empty( $db->charset ) ) {
$this->charset_collation = "DEFAULT CHARACTER SET {$db->charset}";
}
// Collation
if ( ! empty( $db->collate ) ) {
$this->charset_collation .= " COLLATE {$db->collate}";
}
}
/**
* Set the database version for the table.
*
* @since 1.0.0
*
* @param mixed $version Database version to set when upgrading/creating
*/
private function set_db_version( $version = '' ) {
// If no version is passed during an upgrade, use the current version
if ( empty( $version ) ) {
$version = $this->version;
}
// Update the DB version
$this->is_global()
? update_network_option( get_main_network_id(), $this->db_version_key, $version )
: update_option( $this->db_version_key, $version );
// Set the DB version
$this->db_version = $version;
}
/**
* Get the table version from the database.
*
* @since 1.0.0
*/
private function get_db_version() {
$this->db_version = $this->is_global()
? get_network_option( get_main_network_id(), $this->db_version_key, false )
: get_option( $this->db_version_key, false );
/**
* If the DB version is higher than the stated version and is 12 digits
* long, we need to update it to our new, shorter format of 9 digits.
*
* This is only for 3.0 beta testers, and can be removed in 3.0.1 or above.
*
* @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/7579
*/
if ( version_compare( $this->db_version, $this->version, '<=' ) || ( 12 !== strlen( $this->db_version ) ) ) {
return;
}
// Parse the new version number from the existing. Converting from
// {YYYY}{mm}{dd}{xxxx} to {YYYY}{mm}{dd}{x}
$date = substr( $this->db_version, 0, 8 );
$increment = substr( $this->db_version, 8, 4 );
// Trims off the three prefixed zeros.
$this->db_version = intval( $date . intval( $increment ) );
$this->set_db_version( $this->db_version );
}
/**
* Delete the table version from the database.
*
* @since 1.0.0
*/
private function delete_db_version() {
$this->db_version = $this->is_global()
? delete_network_option( get_main_network_id(), $this->db_version_key )
: delete_option( $this->db_version_key );
}
/**
* Add class hooks to the parent application actions.
*
* @since 1.0.0
*/
private function add_hooks() {
// Add table to the global database object
add_action( 'switch_blog', array( $this, 'switch_blog' ) );
add_action( 'admin_init', array( $this, 'maybe_upgrade' ) );
}
/**
* Check if the current request is from some kind of test.
*
* This is primarily used to skip 'admin_init' and force-install tables.
*
* @since 1.0.0
*
* @return bool
*/
private function is_testing() {
return (bool)
// Tests constant is being used
( defined( 'WP_TESTS_DIR' ) && WP_TESTS_DIR )
||
// Scaffolded (https://make.wordpress.org/cli/handbook/plugin-unit-tests/)
function_exists( '_manually_load_plugin' );
}
/**
* Check if table is global.
*
* @since 1.0.0
*
* @return bool
*/
private function is_global() {
return ( true === $this->global );
}
/**
* Try to get a callable upgrade, with some magic to avoid needing to
* do this dance repeatedly inside subclasses.
*
* @since 1.0.0
*
* @param string $callback
*
* @return mixed Callable string, or false if not callable
*/
private function get_callable( $callback = '' ) {
// Default return value
$callable = $callback;
// Look for global function
if ( ! is_callable( $callable ) ) {
// Fallback to local class method
$callable = array( $this, $callback );
if ( ! is_callable( $callable ) ) {
// Fallback to class method prefixed with "__"
$callable = array( $this, "__{$callback}" );
if ( ! is_callable( $callable ) ) {
$callable = false;
}
}
}
// Return callable string, or false if not callable
return $callable;
}
}