first commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use ElementorPro\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Base_Database_Updater {
|
||||
/**
|
||||
* Run all the 'up' method of the migrations classes if needed, and update the db version.
|
||||
*
|
||||
* @param bool $force When passing true, it ignores the current version and run all the up migrations.
|
||||
*/
|
||||
public function up( $force = false ) {
|
||||
$installed_version = $this->get_installed_version();
|
||||
|
||||
// Up to date. Nothing to do.
|
||||
if ( ! $force && $this->get_db_version() <= $installed_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$migrations = $this->get_collected_migrations();
|
||||
|
||||
if ( ! $force ) {
|
||||
$migrations = $migrations->filter( function ( $_, $version ) use ( $installed_version ) {
|
||||
// Filter all the migrations that already done.
|
||||
return $version > $installed_version;
|
||||
} );
|
||||
}
|
||||
|
||||
$migrations->map( function ( Base_Migration $migration, $version ) {
|
||||
$migration->up();
|
||||
|
||||
// In case some migration failed it updates version every migration.
|
||||
$this->update_db_version_option( $version );
|
||||
} );
|
||||
|
||||
$this->update_db_version_option( $this->get_db_version() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all the 'down' method of the migrations classes if can, and update the db version.
|
||||
*
|
||||
* @param bool $force When passing true, it ignores the current version and run all the down migrations.
|
||||
*/
|
||||
public function down( $force = false ) {
|
||||
$installed_version = $this->get_installed_version();
|
||||
|
||||
$migrations = $this->get_collected_migrations();
|
||||
|
||||
if ( ! $force ) {
|
||||
$migrations = $migrations->filter( function ( $_, $version ) use ( $installed_version ) {
|
||||
// Filter all the migrations that was not installed.
|
||||
return $version <= $installed_version;
|
||||
} );
|
||||
}
|
||||
|
||||
$migrations->reverse( true )
|
||||
->map( function ( Base_Migration $migration, $version ) {
|
||||
$migration->down();
|
||||
|
||||
// In case some migration failed it updates version every migration.
|
||||
$this->update_db_version_option( $version );
|
||||
} );
|
||||
|
||||
$this->update_db_version_option( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register hooks to activate the migrations.
|
||||
*/
|
||||
public function register() {
|
||||
add_action( 'admin_init', function () {
|
||||
$this->up();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the version in the users DB.
|
||||
*
|
||||
* @param $version
|
||||
*/
|
||||
protected function update_db_version_option( $version ) {
|
||||
update_option( $this->get_db_version_option_name(), $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version that already installed.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_installed_version() {
|
||||
return intval( get_option( $this->get_db_version_option_name() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all migrations inside a Collection.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function get_collected_migrations() {
|
||||
return new Collection( $this->get_migrations() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The most updated version of the DB.
|
||||
*
|
||||
* @return numeric
|
||||
*/
|
||||
abstract protected function get_db_version();
|
||||
|
||||
/**
|
||||
* The name of the option that saves the current user DB version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_db_version_option_name();
|
||||
|
||||
/**
|
||||
* Array of migration classes.
|
||||
*
|
||||
* @return Base_Migration[]
|
||||
*/
|
||||
abstract protected function get_migrations();
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Base_Migration {
|
||||
/*
|
||||
* @see https://github.com/WordPress/WordPress/blob/d2694aa46647af48d1bcaff48a4f6cac7f5cf470/wp-admin/includes/schema.php#L49
|
||||
*/
|
||||
const MAX_INDEX_LENGTH = 191;
|
||||
|
||||
/**
|
||||
* @var \wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* @param \wpdb|null $wpdb_instance
|
||||
*/
|
||||
public function __construct( \wpdb $wpdb_instance = null ) {
|
||||
if ( ! $wpdb_instance ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
} else {
|
||||
$this->wpdb = $wpdb_instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when upgrading the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function up();
|
||||
|
||||
/**
|
||||
* Runs when downgrading the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function down();
|
||||
|
||||
/**
|
||||
* A util to run SQL for creating tables.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function create_table( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$columns_sql = ( new Collection( $columns ) )
|
||||
->map( function( $definition, $col_name ) {
|
||||
return "`{$col_name}` {$definition}";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$query = "CREATE TABLE `{$table_name}` ({$columns_sql}) {$this->wpdb->get_charset_collate()};";
|
||||
|
||||
$this->run_db_delta( $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add columns.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function add_columns( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$add_columns_sql = ( new Collection( $columns ) )
|
||||
->map( function ( $definition, $column_name ) {
|
||||
return "ADD COLUMN `{$column_name}` {$definition}";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$add_columns_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop columns
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $columns
|
||||
*/
|
||||
protected function drop_columns( $table_name, array $columns ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$drop_columns_sql = ( new Collection( $columns ) )
|
||||
->map( function ( $column_name ) {
|
||||
return "DROP COLUMN `{$column_name}`";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$drop_columns_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* A util to run SQL for dropping tables.
|
||||
*
|
||||
* @param $table_name
|
||||
*/
|
||||
protected function drop_table( $table_name ) {
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
$query = "DROP TABLE IF EXISTS `{$table_name}`;";
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$this->wpdb->query( $query ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* A util to run SQL for creating indexes.
|
||||
*
|
||||
* @param $table_name
|
||||
* @param array $column_names
|
||||
*/
|
||||
protected function create_indexes( $table_name, array $column_names ) {
|
||||
$max_index_length = static::MAX_INDEX_LENGTH;
|
||||
$table_name = "{$this->wpdb->prefix}{$table_name}";
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$column_definition = $this->get_column_definition( $table_name );
|
||||
|
||||
if ( ! $column_definition ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$should_set_max_length_index = ( new Collection( $column_definition ) )
|
||||
->filter( function ( $value ) {
|
||||
preg_match( '/\((\d+)\)/', $value['Type'], $match );
|
||||
|
||||
return ( isset( $match[1] ) && intval( $match[1] ) > Base_Migration::MAX_INDEX_LENGTH )
|
||||
|| in_array( strtolower( $value['Type'] ), [ 'text', 'longtext' ], true );
|
||||
} )
|
||||
->pluck( 'Field' )
|
||||
->values();
|
||||
|
||||
$indexes_sql = ( new Collection( $column_names ) )
|
||||
->map( function( $col_name ) use ( $should_set_max_length_index, $max_index_length ) {
|
||||
$max_index_length_sql = '';
|
||||
|
||||
if ( in_array( $col_name, $should_set_max_length_index, true ) ) {
|
||||
$max_index_length_sql = " ({$max_index_length})";
|
||||
}
|
||||
|
||||
return "ADD INDEX `{$col_name}_index` (`{$col_name}`{$max_index_length_sql})";
|
||||
} )
|
||||
->implode( ', ' );
|
||||
|
||||
// Safe query that shouldn't be escaped.
|
||||
$this->wpdb->query( "ALTER TABLE `{$table_name}` {$indexes_sql};" ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table_name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_column_definition( $table_name ) {
|
||||
return $this->wpdb->get_results( "SHOW COLUMNS FROM `{$table_name}`;", ARRAY_A ); // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs global dbDelta function (wrapped into method to allowing mock for testing).
|
||||
*
|
||||
* @param $query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function run_db_delta( $query ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
return dbDelta( $query );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* JOIN clause builder.
|
||||
*
|
||||
* Essentially, it uses the regular Builder's capabilities while wrapping some method
|
||||
* for syntactic sugar and better readability.
|
||||
*/
|
||||
class Join_Clause extends Query_Builder {
|
||||
|
||||
// JOIN types.
|
||||
const TYPE_INNER = 'inner';
|
||||
const TYPE_LEFT = 'left';
|
||||
const TYPE_RIGHT = 'right';
|
||||
|
||||
/**
|
||||
* JOIN type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Join_Clause constructor.
|
||||
*
|
||||
* @param string $type - JOIN type.
|
||||
* @param \wpdb|null $connection - MySQL connection to use.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $type, \wpdb $connection = null ) {
|
||||
parent::__construct( $connection );
|
||||
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses `$this->where()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function on( $column, $operator, $value, $and_or = self::RELATION_AND ) {
|
||||
return $this->where( $column, $operator, $value, $and_or );
|
||||
}
|
||||
|
||||
/**
|
||||
* @shortcut `$this->on()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function or_on( $first, $operator, $second ) {
|
||||
return $this->on( $first, $operator, $second, self::RELATION_OR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses `$this->where_column()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function on_column( $first, $operator, $second, $and_or = self::RELATION_AND ) {
|
||||
return $this->where_column( $first, $operator, $second, $and_or );
|
||||
}
|
||||
|
||||
/**
|
||||
* @shortcut `$this->on_column()`.
|
||||
*
|
||||
* @return Join_Clause
|
||||
*/
|
||||
public function or_on_column( $first, $operator, $second ) {
|
||||
return $this->on_column( $first, $operator, $second, self::RELATION_OR );
|
||||
}
|
||||
}
|
||||
160
wp-content/plugins/elementor-pro/core/database/model-base.php
Normal file
160
wp-content/plugins/elementor-pro/core/database/model-base.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
use ElementorPro\Core\Utils\Collection;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Model_Base implements \JsonSerializable {
|
||||
|
||||
// Casting types.
|
||||
const TYPE_BOOLEAN = 'boolean';
|
||||
const TYPE_COLLECTION = 'collection';
|
||||
const TYPE_INTEGER = 'integer';
|
||||
const TYPE_STRING = 'string';
|
||||
const TYPE_JSON = 'json';
|
||||
const TYPE_DATETIME = 'datetime';
|
||||
const TYPE_DATETIME_GMT = 'datetime_gmt';
|
||||
|
||||
/**
|
||||
* Casts array.
|
||||
* Used to automatically cast values from DB to the appropriate property type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $casts = [];
|
||||
|
||||
/**
|
||||
* Model_Base constructor.
|
||||
*
|
||||
* @param array $fields - Fields from the DB to fill.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( array $fields ) {
|
||||
foreach ( $fields as $key => $value ) {
|
||||
if ( ! property_exists( $this, $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->{$key} = ( empty( static::$casts[ $key ] ) )
|
||||
? $value
|
||||
: static::cast( $value, static::$casts[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's table name.
|
||||
* Throws an exception by default in order to require implementation,
|
||||
* since abstract static functions are not allowed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_table() {
|
||||
throw new \Exception( 'You must implement `get_table()` inside ' . static::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Query Builder for the model's table.
|
||||
*
|
||||
* @param \wpdb|null $connection - MySQL connection to use.
|
||||
*
|
||||
* @return Query_Builder
|
||||
*/
|
||||
public static function query( \wpdb $connection = null ) {
|
||||
$builder = new Model_Query_Builder( static::class, $connection );
|
||||
|
||||
return $builder->from( static::get_table() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast value into specific type.
|
||||
*
|
||||
* @param $value - Value to cast.
|
||||
* @param $type - Type to cast into.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function cast( $value, $type ) {
|
||||
if ( null === $value ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ( $type ) {
|
||||
case self::TYPE_BOOLEAN:
|
||||
return boolval( $value );
|
||||
|
||||
case self::TYPE_COLLECTION:
|
||||
return new Collection( $value );
|
||||
|
||||
case self::TYPE_INTEGER:
|
||||
return intval( $value );
|
||||
|
||||
case self::TYPE_STRING:
|
||||
return strval( $value );
|
||||
|
||||
case self::TYPE_JSON:
|
||||
return json_decode( $value, true );
|
||||
|
||||
case self::TYPE_DATETIME:
|
||||
return new \DateTime( $value );
|
||||
|
||||
case self::TYPE_DATETIME_GMT:
|
||||
return new \DateTime( $value, new \DateTimeZone( 'GMT' ) );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a model property value into a JSON compatible data type.
|
||||
*
|
||||
* @param $value - Value to cast.
|
||||
* @param $type - Type to cast into.
|
||||
* @param $property_name - The model property name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function json_serialize_property( $value, $type, $property_name ) {
|
||||
switch ( $type ) {
|
||||
case self::TYPE_DATETIME:
|
||||
case self::TYPE_DATETIME_GMT:
|
||||
/** @var \DateTime $value */
|
||||
return $value->format( 'c' );
|
||||
}
|
||||
|
||||
/** @var mixed $value */
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return ( new Collection( (array) $this ) )
|
||||
->map( function ( $_, $key ) {
|
||||
$value = $this->{$key};
|
||||
|
||||
$type = array_key_exists( $key, static::$casts )
|
||||
? static::$casts[ $key ]
|
||||
: null;
|
||||
|
||||
if ( null === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Can be overridden by child model.
|
||||
$value = static::json_serialize_property( $value, $type, $key );
|
||||
|
||||
if ( $value instanceof \JsonSerializable ) {
|
||||
return $value->jsonSerialize();
|
||||
}
|
||||
|
||||
return $value;
|
||||
} )
|
||||
->all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace ElementorPro\Core\Database;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Model_Query_Builder extends Query_Builder {
|
||||
/**
|
||||
* The Query Builder associated model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Whether the returned value should be hydrated into a model.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $return_as_model = true;
|
||||
|
||||
/**
|
||||
* Model_Query_Builder constructor.
|
||||
*
|
||||
* @param string $model_classname - Model to use inside the builder.
|
||||
* @param \wpdb|null $connection - MySQL connection.
|
||||
*/
|
||||
public function __construct( $model_classname, \wpdb $connection = null ) {
|
||||
$this->set_model( $model_classname );
|
||||
|
||||
parent::__construct( $connection );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model the generated from the query builder.
|
||||
*
|
||||
* @param $model_classname
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function set_model( $model_classname ) {
|
||||
$this->model = $model_classname;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable model hydration.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disable_model_initiation() {
|
||||
$this->return_as_model = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable hydration before calling the original count.
|
||||
*
|
||||
* @param string $column
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count( $column = '*' ) {
|
||||
$this->disable_model_initiation();
|
||||
|
||||
return parent::count( $column );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable hydration before calling the original pluck.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pluck( $column = null ) {
|
||||
$this->disable_model_initiation();
|
||||
|
||||
return parent::pluck( $column );
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the parent `get()` and make Models from the results.
|
||||
*
|
||||
* @return \ElementorPro\Core\Utils\Collection
|
||||
*/
|
||||
public function get() {
|
||||
$items = parent::get();
|
||||
|
||||
if ( ! $this->return_as_model ) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Convert the SQL results to Model instances.
|
||||
return $items->map( function ( $comment ) {
|
||||
return new $this->model( $comment );
|
||||
} );
|
||||
}
|
||||
}
|
||||
1342
wp-content/plugins/elementor-pro/core/database/query-builder.php
Normal file
1342
wp-content/plugins/elementor-pro/core/database/query-builder.php
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user