initial
This commit is contained in:
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @phpstan-type DatabaseStatus array{
|
||||
* status: string|false,
|
||||
* inProgress: bool,
|
||||
* current?: string,
|
||||
* next?: string,
|
||||
* time?: float,
|
||||
* manual?: array<int, string>,
|
||||
* result?: 'ok'|'error',
|
||||
* reason?: string|false,
|
||||
* debug?: array<int, string>,
|
||||
* complete?: int|float
|
||||
* }
|
||||
*/
|
||||
class Red_Database_Status {
|
||||
// Used in < 3.7 versions of Redirection, but since migrated to general settings
|
||||
const OLD_DB_VERSION = 'redirection_version';
|
||||
const DB_UPGRADE_STAGE = 'database_stage';
|
||||
|
||||
const RESULT_OK = 'ok';
|
||||
const RESULT_ERROR = 'error';
|
||||
|
||||
const STATUS_OK = 'ok';
|
||||
const STATUS_NEED_INSTALL = 'need-install';
|
||||
const STATUS_NEED_UPDATING = 'need-update';
|
||||
const STATUS_FINISHED_INSTALL = 'finish-install';
|
||||
const STATUS_FINISHED_UPDATING = 'finish-update';
|
||||
|
||||
/**
|
||||
* Current upgrade stage
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
private $stage = false;
|
||||
|
||||
/**
|
||||
* List of all upgrade stages
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private $stages = [];
|
||||
|
||||
/**
|
||||
* Current database status
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
private $status = false;
|
||||
|
||||
/**
|
||||
* Result of last operation
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
private $result = false;
|
||||
|
||||
/**
|
||||
* Reason for current status
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
private $reason = false;
|
||||
|
||||
/**
|
||||
* Debug information
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private $debug = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->status = self::STATUS_OK;
|
||||
|
||||
if ( $this->needs_installing() ) {
|
||||
$this->status = self::STATUS_NEED_INSTALL;
|
||||
}
|
||||
|
||||
$this->load_stage();
|
||||
|
||||
if ( $this->needs_updating() ) {
|
||||
$this->status = self::STATUS_NEED_UPDATING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load current upgrade stage from options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function load_stage(): void {
|
||||
$settings = Red_Options::get();
|
||||
|
||||
if ( isset( $settings[ self::DB_UPGRADE_STAGE ] ) ) {
|
||||
$stage_data = $settings[ self::DB_UPGRADE_STAGE ];
|
||||
|
||||
// Database stage can be set to false to clear it - only process if it's an array
|
||||
// phpcs:ignore function.alreadyNarrowedType
|
||||
// @phpstan-ignore function.alreadyNarrowedType
|
||||
if ( ! is_array( $stage_data ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->stage = isset( $stage_data['stage'] ) ? $stage_data['stage'] : false;
|
||||
$this->stages = isset( $stage_data['stages'] ) ? $stage_data['stages'] : [];
|
||||
|
||||
// Only override status if we have a saved upgrade in progress
|
||||
if ( isset( $stage_data['status'] ) && $stage_data['status'] !== false ) {
|
||||
$this->status = $stage_data['status'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the database need install
|
||||
*
|
||||
* @return bool true if needs installing, false otherwise
|
||||
*/
|
||||
public function needs_installing(): bool {
|
||||
$settings = Red_Options::get();
|
||||
|
||||
if ( $settings['database'] === '' && $this->get_old_version() === false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current database need updating to the target
|
||||
*
|
||||
* @return bool true if needs updating, false otherwise
|
||||
*/
|
||||
public function needs_updating(): bool {
|
||||
// We need updating if we don't need to install, and the current version is less than target version
|
||||
if ( $this->needs_installing() === false && version_compare( $this->get_current_version(), REDIRECTION_DB_VERSION, '<' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also if we're still in the process of upgrading
|
||||
if ( $this->get_current_stage() !== false && $this->status !== self::STATUS_NEED_INSTALL ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current database version
|
||||
*
|
||||
* @return string Current database version
|
||||
*/
|
||||
public function get_current_version(): string {
|
||||
$settings = Red_Options::get();
|
||||
|
||||
if ( $settings['database'] !== '' ) {
|
||||
if ( $settings['database'] === '+OK' ) {
|
||||
return REDIRECTION_DB_VERSION;
|
||||
}
|
||||
|
||||
return $settings['database'];
|
||||
}
|
||||
|
||||
$version = $this->get_old_version();
|
||||
if ( $version !== false && $version !== '' && $version !== '0' && $version !== 0 ) {
|
||||
// Upgrade the old value
|
||||
$version = (string) $version;
|
||||
red_set_options( array( 'database' => $version ) );
|
||||
delete_option( self::OLD_DB_VERSION );
|
||||
$this->clear_cache();
|
||||
return $version;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get old database version from legacy option
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function get_old_version() {
|
||||
return get_option( self::OLD_DB_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if required database tables exist
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check_tables_exist(): void {
|
||||
$latest = Red_Database::get_latest_database();
|
||||
$missing = $latest->get_missing_tables();
|
||||
|
||||
// No tables installed - do a fresh install
|
||||
if ( count( $missing ) === count( $latest->get_all_tables() ) ) {
|
||||
delete_option( self::OLD_DB_VERSION );
|
||||
red_set_options( [ 'database' => '' ] );
|
||||
$this->clear_cache();
|
||||
|
||||
$this->status = self::STATUS_NEED_INSTALL;
|
||||
$this->stop_update();
|
||||
} elseif ( count( $missing ) > 0 && version_compare( $this->get_current_version(), '2.3.3', 'ge' ) ) {
|
||||
// Some tables are missing - try and fill them in
|
||||
$latest->install();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current database support a particular version
|
||||
*
|
||||
* @param string $version Target version
|
||||
* @return bool true if supported, false otherwise
|
||||
*/
|
||||
public function does_support( string $version ): bool {
|
||||
return version_compare( $this->get_current_version(), $version, 'ge' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if last operation resulted in error
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_error(): bool {
|
||||
return $this->result === self::RESULT_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error status
|
||||
*
|
||||
* @param string $error Error message.
|
||||
* @return void
|
||||
*/
|
||||
public function set_error( string $error ): void {
|
||||
global $wpdb;
|
||||
|
||||
$this->result = self::RESULT_ERROR;
|
||||
$this->reason = str_replace( "\t", ' ', $error );
|
||||
|
||||
if ( $wpdb->last_error ) {
|
||||
$this->debug[] = $wpdb->last_error;
|
||||
|
||||
if ( strpos( $wpdb->last_error, 'command denied to user' ) !== false ) {
|
||||
$this->reason .= ' - ' . __( 'Insufficient database permissions detected. Please give your database user appropriate permissions.', 'redirection' );
|
||||
}
|
||||
}
|
||||
|
||||
$latest = Red_Database::get_latest_database();
|
||||
$this->debug = array_merge( $this->debug, $latest->get_table_schema() );
|
||||
$this->debug[] = 'Stage: ' . $this->get_current_stage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set success status
|
||||
*
|
||||
* @param string $reason Success message.
|
||||
* @return void
|
||||
*/
|
||||
public function set_ok( string $reason ): void {
|
||||
$this->reason = $reason;
|
||||
$this->result = self::RESULT_OK;
|
||||
$this->debug = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop current upgrade
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function stop_update(): void {
|
||||
$this->stage = false;
|
||||
$this->stages = [];
|
||||
$this->debug = [];
|
||||
|
||||
red_set_options( [ self::DB_UPGRADE_STAGE => false ] );
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish upgrade process
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function finish(): void {
|
||||
$this->stop_update();
|
||||
|
||||
if ( $this->status === self::STATUS_NEED_INSTALL ) {
|
||||
$this->status = self::STATUS_FINISHED_INSTALL;
|
||||
} elseif ( $this->status === self::STATUS_NEED_UPDATING ) {
|
||||
$this->status = self::STATUS_FINISHED_UPDATING;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current upgrade stage
|
||||
*
|
||||
* @return string|false Current stage name, or false if not upgrading
|
||||
*/
|
||||
public function get_current_stage() {
|
||||
return $this->stage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move current stage on to the next
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_next_stage(): void {
|
||||
$this->debug = [];
|
||||
$stage = $this->get_current_stage();
|
||||
|
||||
if ( $stage !== false ) {
|
||||
$stage = $this->get_next_stage( $stage );
|
||||
|
||||
// Save next position
|
||||
if ( $stage !== false ) {
|
||||
$this->set_stage( $stage );
|
||||
} else {
|
||||
$this->finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current upgrade status
|
||||
*
|
||||
* @return DatabaseStatus Database status array
|
||||
*/
|
||||
public function get_json() {
|
||||
// Base information
|
||||
$result = [
|
||||
'status' => $this->status,
|
||||
'inProgress' => $this->stage !== false,
|
||||
];
|
||||
|
||||
// Add on version status
|
||||
if ( $this->status === self::STATUS_NEED_INSTALL || $this->status === self::STATUS_NEED_UPDATING ) {
|
||||
$result = array_merge(
|
||||
$result,
|
||||
$this->get_version_upgrade(),
|
||||
[ 'manual' => $this->get_manual_upgrade() ]
|
||||
);
|
||||
}
|
||||
|
||||
// Add on upgrade status
|
||||
if ( $this->is_error() ) {
|
||||
$result = array_merge( $result, $this->get_version_upgrade(), $this->get_progress_status(), $this->get_error_status() );
|
||||
} elseif ( $result['inProgress'] ) {
|
||||
$result = array_merge( $result, $this->get_progress_status() );
|
||||
} elseif ( $this->status === self::STATUS_FINISHED_INSTALL || $this->status === self::STATUS_FINISHED_UPDATING ) {
|
||||
$result['complete'] = 100;
|
||||
$result['reason'] = $this->reason;
|
||||
} elseif ( $this->status === self::STATUS_NEED_INSTALL || $this->status === self::STATUS_NEED_UPDATING ) {
|
||||
// For fresh install/update that hasn't started yet, set initial progress state
|
||||
$result['complete'] = 0;
|
||||
$result['result'] = self::RESULT_OK;
|
||||
$result['reason'] = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error status information
|
||||
*
|
||||
* @phpstan-return array{reason: string|false, result: 'error', debug: array<int, string>}
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function get_error_status(): array {
|
||||
return [
|
||||
'reason' => $this->reason,
|
||||
'result' => self::RESULT_ERROR,
|
||||
'debug' => $this->debug,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress status information
|
||||
*
|
||||
* @return array{complete: int|float, result: 'ok', reason: string|false}
|
||||
*/
|
||||
private function get_progress_status() {
|
||||
$complete = 0;
|
||||
|
||||
if ( $this->stage !== false ) {
|
||||
$total = count( $this->stages );
|
||||
$pos = array_search( $this->stage, $this->stages, true );
|
||||
|
||||
if ( $pos !== false && $total > 0 ) {
|
||||
$complete = round( ( $pos / $total ) * 100, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'complete' => $complete,
|
||||
'result' => self::RESULT_OK,
|
||||
'reason' => $this->reason,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get version upgrade information
|
||||
*
|
||||
* @phpstan-return array{current: string, next: string, time: float}
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function get_version_upgrade(): array {
|
||||
return [
|
||||
'current' => $this->get_current_version() ? $this->get_current_version() : '-',
|
||||
'next' => REDIRECTION_DB_VERSION,
|
||||
'time' => microtime( true ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status information for a database upgrade
|
||||
*
|
||||
* @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
|
||||
* @return void
|
||||
*/
|
||||
public function start_install( array $upgrades ) {
|
||||
$this->set_stages( $upgrades );
|
||||
$this->status = self::STATUS_NEED_INSTALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start database upgrade process
|
||||
*
|
||||
* @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
|
||||
* @return void
|
||||
*/
|
||||
public function start_upgrade( array $upgrades ) {
|
||||
$this->set_stages( $upgrades );
|
||||
$this->status = self::STATUS_NEED_UPDATING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set upgrade stages
|
||||
*
|
||||
* @param Red_Database_Upgrade[] $upgrades List of upgrade versions.
|
||||
* @return void
|
||||
*/
|
||||
private function set_stages( array $upgrades ) {
|
||||
$this->stages = [];
|
||||
|
||||
foreach ( $upgrades as $upgrade ) {
|
||||
$upgrader = Red_Database_Upgrader::get( $upgrade );
|
||||
$this->stages = array_merge( $this->stages, array_keys( $upgrader->get_stages() ) );
|
||||
}
|
||||
|
||||
if ( count( $this->stages ) > 0 ) {
|
||||
$this->set_stage( $this->stages[0] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|false $stage
|
||||
* @return void
|
||||
*/
|
||||
public function set_stage( $stage ): void {
|
||||
$this->stage = $stage;
|
||||
$this->save_details();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function save_details(): void {
|
||||
$stages = [
|
||||
self::DB_UPGRADE_STAGE => [
|
||||
'stage' => $this->stage,
|
||||
'stages' => $this->stages,
|
||||
'status' => $this->status,
|
||||
],
|
||||
];
|
||||
|
||||
red_set_options( $stages );
|
||||
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function get_manual_upgrade(): array {
|
||||
$queries = [];
|
||||
$database = new Red_Database();
|
||||
$upgraders = $database->get_upgrades_for_version( $this->get_current_version(), false );
|
||||
|
||||
foreach ( $upgraders as $upgrade ) {
|
||||
$upgrade = Red_Database_Upgrader::get( $upgrade );
|
||||
|
||||
$stages = $upgrade->get_stages();
|
||||
foreach ( array_keys( $stages ) as $stage ) {
|
||||
$queries = array_merge( $queries, $upgrade->get_queries_for_stage( $stage ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @return string|false
|
||||
*/
|
||||
private function get_next_stage( string $stage ) {
|
||||
$database = new Red_Database();
|
||||
$upgraders = $database->get_upgrades_for_version( $this->get_current_version(), $this->get_current_stage() );
|
||||
|
||||
if ( count( $upgraders ) === 0 ) {
|
||||
$upgraders = $database->get_upgrades_for_version( $this->get_current_version(), false );
|
||||
}
|
||||
|
||||
if ( count( $upgraders ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upgrader = Red_Database_Upgrader::get( $upgraders[0] );
|
||||
|
||||
// Where are we in this?
|
||||
$pos = array_search( $stage, $this->stages, true );
|
||||
|
||||
if ( $pos === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $pos === count( $this->stages ) - 1 ) {
|
||||
$this->save_db_version( REDIRECTION_DB_VERSION );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set current DB version
|
||||
$current_stages = array_keys( $upgrader->get_stages() );
|
||||
|
||||
$current_position = array_search( $stage, $current_stages, true );
|
||||
|
||||
if ( $current_position !== false && $current_position === count( $current_stages ) - 1 && isset( $upgraders[1] ) ) {
|
||||
$this->save_db_version( $upgraders[1]->get_version() );
|
||||
}
|
||||
|
||||
// Move on to next in current version
|
||||
return $this->stages[ $pos + 1 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
* @return void
|
||||
*/
|
||||
public function save_db_version( string $version ): void {
|
||||
red_set_options( array( 'database' => $version ) );
|
||||
delete_option( self::OLD_DB_VERSION );
|
||||
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function clear_cache(): void {
|
||||
// Clear Red_Options in-memory cache
|
||||
Red_Options::reset();
|
||||
|
||||
// Clear WordPress object cache if available
|
||||
if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) && function_exists( 'wp_cache_flush' ) ) {
|
||||
wp_cache_flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class Red_Database_Upgrade {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $file;
|
||||
|
||||
/**
|
||||
* @var class-string<Red_Database_Upgrader>
|
||||
*/
|
||||
private $class;
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
* @param string $file
|
||||
* @param class-string<Red_Database_Upgrader> $class
|
||||
*/
|
||||
public function __construct( string $version, string $file, string $class ) {
|
||||
$this->version = $version;
|
||||
$this->file = $file;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
public function get_version(): string {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function get_file(): string {
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Red_Database_Upgrader>
|
||||
*/
|
||||
public function get_class(): string {
|
||||
return $this->class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/database-upgrade.php';
|
||||
|
||||
abstract class Red_Database_Upgrader {
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $queries = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $live = true;
|
||||
|
||||
/**
|
||||
* Return an array of all the stages for an upgrade
|
||||
*
|
||||
* @return array<string, string> stage name => reason
|
||||
*/
|
||||
abstract public function get_stages();
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @return string
|
||||
*/
|
||||
public function get_reason( string $stage ): string {
|
||||
$stages = $this->get_stages();
|
||||
|
||||
if ( isset( $stages[ $stage ] ) ) {
|
||||
return $stages[ $stage ];
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a particular stage on the current upgrader
|
||||
*
|
||||
* @param Red_Database_Status $status
|
||||
* @return void
|
||||
*/
|
||||
public function perform_stage( Red_Database_Status $status ): void {
|
||||
global $wpdb;
|
||||
|
||||
$stage = $status->get_current_stage();
|
||||
if ( is_string( $stage ) && $this->has_stage( $stage ) && method_exists( $this, $stage ) ) {
|
||||
try {
|
||||
$this->invoke_stage( $stage, $wpdb, true );
|
||||
$status->set_ok( $this->get_reason( $stage ) );
|
||||
} catch ( Exception $e ) {
|
||||
$status->set_error( $e->getMessage() );
|
||||
}
|
||||
} else {
|
||||
$status->set_error( 'No stage found for upgrade ' . $stage );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @return list<string>
|
||||
*/
|
||||
public function get_queries_for_stage( string $stage ): array {
|
||||
global $wpdb;
|
||||
|
||||
$this->queries = [];
|
||||
$this->live = false;
|
||||
$this->invoke_stage( $stage, $wpdb, false );
|
||||
$this->live = true;
|
||||
|
||||
return $this->queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current database charset
|
||||
*
|
||||
* @return string Database charset
|
||||
*/
|
||||
public function get_charset(): string {
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = '';
|
||||
if ( ! empty( $wpdb->charset ) ) {
|
||||
// Fix some common invalid charset values
|
||||
$fixes = [
|
||||
'utf-8',
|
||||
'utf',
|
||||
];
|
||||
|
||||
$charset = $wpdb->charset;
|
||||
if ( in_array( strtolower( $charset ), $fixes, true ) ) {
|
||||
$charset = 'utf8';
|
||||
}
|
||||
|
||||
$charset_collate = "DEFAULT CHARACTER SET $charset";
|
||||
}
|
||||
|
||||
if ( ! empty( $wpdb->collate ) ) {
|
||||
$charset_collate .= " COLLATE=$wpdb->collate";
|
||||
}
|
||||
|
||||
return $charset_collate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a $wpdb->query, and throws an exception if an error occurs
|
||||
*
|
||||
* @param wpdb $wpdb WordPress database instance.
|
||||
* @param string $sql SQL query.
|
||||
* @return bool true if query is performed ok, otherwise an exception is thrown
|
||||
*/
|
||||
protected function do_query( wpdb $wpdb, string $sql ): bool {
|
||||
if ( ! $this->live ) {
|
||||
$this->queries[] = $sql;
|
||||
return true;
|
||||
}
|
||||
|
||||
// These are known queries without user input
|
||||
// phpcs:ignore
|
||||
$result = $wpdb->query( $sql );
|
||||
|
||||
if ( $result === false ) {
|
||||
/* translators: 1: SQL string */
|
||||
throw new Exception( sprintf( 'Failed to perform query "%s"', $sql ) ); // phpcs:ignore
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a database upgrader class
|
||||
*
|
||||
* @param Red_Database_Upgrade $version
|
||||
* @return Red_Database_Upgrader Database upgrader
|
||||
*/
|
||||
public static function get( Red_Database_Upgrade $version ): Red_Database_Upgrader {
|
||||
include_once __DIR__ . '/schema/' . str_replace( [ '..', '/' ], '', $version->get_file() );
|
||||
|
||||
$class = $version->get_class();
|
||||
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @return bool
|
||||
*/
|
||||
private function has_stage( string $stage ): bool {
|
||||
return in_array( $stage, array_keys( $this->get_stages() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @param wpdb $wpdb
|
||||
* @param bool $live
|
||||
* @return void
|
||||
*/
|
||||
private function invoke_stage( string $stage, wpdb $wpdb, bool $live ): void {
|
||||
if ( ! method_exists( $this, $stage ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Methods that accept both $wpdb and $live parameters
|
||||
// Most stage methods only accept $wpdb, but some (like create_groups) accept both
|
||||
$two_param_methods = [ 'create_groups' ];
|
||||
|
||||
if ( in_array( $stage, $two_param_methods, true ) ) {
|
||||
/** @var callable(wpdb, bool): void $callable */
|
||||
$callable = [ $this, $stage ];
|
||||
call_user_func( $callable, $wpdb, $live );
|
||||
} else {
|
||||
/** @var callable(wpdb): void $callable */
|
||||
$callable = [ $this, $stage ];
|
||||
call_user_func( $callable, $wpdb );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/database-status.php';
|
||||
require_once __DIR__ . '/database-upgrade.php';
|
||||
require_once __DIR__ . '/database-upgrader.php';
|
||||
|
||||
class Red_Database {
|
||||
/**
|
||||
* Get all upgrades for a database version
|
||||
*
|
||||
* @param string $current_version
|
||||
* @param string|false $current_stage
|
||||
* @return list<Red_Database_Upgrade> Array of versions from self::get_upgrades()
|
||||
*/
|
||||
public function get_upgrades_for_version( $current_version, $current_stage ) {
|
||||
if ( empty( $current_version ) ) {
|
||||
return [
|
||||
new Red_Database_Upgrade( REDIRECTION_DB_VERSION, 'latest.php', 'Red_Latest_Database' ),
|
||||
];
|
||||
}
|
||||
|
||||
$upgraders = [];
|
||||
$found = false;
|
||||
|
||||
foreach ( $this->get_upgrades() as $upgrade ) {
|
||||
if ( ! $found ) {
|
||||
$upgrader = Red_Database_Upgrader::get( $upgrade );
|
||||
|
||||
$stage_present = is_string( $current_stage ) && in_array( $current_stage, array_keys( $upgrader->get_stages() ), true );
|
||||
$same_version = $current_stage === false && version_compare( $upgrade->get_version(), $current_version, 'gt' );
|
||||
|
||||
if ( $stage_present || $same_version ) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $found ) {
|
||||
$upgraders[] = $upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
return $upgraders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a particular upgrade stage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function apply_upgrade( Red_Database_Status $status ) {
|
||||
$upgraders = $this->get_upgrades_for_version( $status->get_current_version(), $status->get_current_stage() );
|
||||
|
||||
if ( count( $upgraders ) === 0 ) {
|
||||
$status->set_error( 'No upgrades found for version ' . $status->get_current_version() );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $status->get_current_stage() === false ) {
|
||||
if ( $status->needs_installing() ) {
|
||||
$status->start_install( $upgraders );
|
||||
} else {
|
||||
$status->start_upgrade( $upgraders );
|
||||
}
|
||||
}
|
||||
|
||||
// Look at first upgrade
|
||||
$upgrader = Red_Database_Upgrader::get( $upgraders[0] );
|
||||
|
||||
// Perform the upgrade
|
||||
$upgrader->perform_stage( $status );
|
||||
|
||||
if ( ! $status->is_error() ) {
|
||||
$status->set_next_stage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a callback to all sites in a multisite network, or to the current site if not multisite.
|
||||
*
|
||||
* @param callable $callback Callback function to apply to each site.
|
||||
* @return void
|
||||
*/
|
||||
public static function apply_to_sites( $callback ) {
|
||||
if ( is_multisite() && ( is_network_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) ) {
|
||||
$total = get_sites( [ 'count' => true ] );
|
||||
$per_page = 100;
|
||||
|
||||
// Paginate through all sites and apply the callback
|
||||
for ( $offset = 0; $offset < $total; $offset += $per_page ) {
|
||||
array_map(
|
||||
function ( $site ) use ( $callback ) {
|
||||
switch_to_blog( (int) $site->blog_id );
|
||||
|
||||
$callback();
|
||||
|
||||
restore_current_blog();
|
||||
},
|
||||
get_sites( [ 'number' => $per_page, 'offset' => $offset ] )
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest database installer
|
||||
*
|
||||
* @return Red_Latest_Database Red_Latest_Database
|
||||
*/
|
||||
public static function get_latest_database() {
|
||||
include_once __DIR__ . '/schema/latest.php';
|
||||
|
||||
return new Red_Latest_Database();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all upgrades and their associated file
|
||||
*
|
||||
* @return list<Red_Database_Upgrade> Database upgrade array
|
||||
*/
|
||||
public function get_upgrades() {
|
||||
return [
|
||||
new Red_Database_Upgrade( '2.0.1', '201.php', 'Red_Database_201' ),
|
||||
new Red_Database_Upgrade( '2.1.16', '216.php', 'Red_Database_216' ),
|
||||
new Red_Database_Upgrade( '2.2', '220.php', 'Red_Database_220' ),
|
||||
new Red_Database_Upgrade( '2.3.1', '231.php', 'Red_Database_231' ),
|
||||
new Red_Database_Upgrade( '2.3.2', '232.php', 'Red_Database_232' ),
|
||||
new Red_Database_Upgrade( '2.3.3', '233.php', 'Red_Database_233' ),
|
||||
new Red_Database_Upgrade( '2.4', '240.php', 'Red_Database_240' ),
|
||||
new Red_Database_Upgrade( '4.0', '400.php', 'Red_Database_400' ),
|
||||
new Red_Database_Upgrade( '4.1', '410.php', 'Red_Database_410' ),
|
||||
new Red_Database_Upgrade( '4.2', '420.php', 'Red_Database_420' ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_201 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'add_title_201' => 'Add titles to redirects',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_title_201( $wpdb ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `title` varchar(50) NULL" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_216 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'add_group_indices_216' => 'Add indices to groups',
|
||||
'add_redirect_indices_216' => 'Add indices to redirects',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_group_indices_216( $wpdb ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_groups` ADD INDEX(module_id)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_groups` ADD INDEX(status)" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_redirect_indices_216( $wpdb ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(url(191))" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(status)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX(regex)" );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_220 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'add_group_indices_220' => 'Add group indices to redirects',
|
||||
'add_log_indices_220' => 'Add indices to logs',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_group_indices_220( $wpdb ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `group_idpos` (`group_id`,`position`)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `group` (`group_id`)" );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_log_indices_220( $wpdb ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `created` (`created`)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `redirection_id` (`redirection_id`)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `ip` (`ip`)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `group_id` (`group_id`)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD INDEX `module_id` (`module_id`)" );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_231 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'remove_404_module_231' => 'Remove 404 module',
|
||||
'create_404_table_231' => 'Create 404 table',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function remove_404_module_231( $wpdb ) {
|
||||
return $this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_groups SET module_id=1 WHERE module_id=3" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function create_404_table_231( $wpdb ) {
|
||||
$this->do_query( $wpdb, $this->get_404_table( $wpdb ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return string
|
||||
*/
|
||||
private function get_404_table( $wpdb ) {
|
||||
$charset_collate = $this->get_charset();
|
||||
|
||||
return "CREATE TABLE `{$wpdb->prefix}redirection_404` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`created` datetime NOT NULL,
|
||||
`url` varchar(255) NOT NULL DEFAULT '',
|
||||
`agent` varchar(255) DEFAULT NULL,
|
||||
`referrer` varchar(255) DEFAULT NULL,
|
||||
`ip` int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created` (`created`),
|
||||
KEY `url` (`url`(191)),
|
||||
KEY `ip` (`ip`),
|
||||
KEY `referrer` (`referrer`(191))
|
||||
) $charset_collate";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_232 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'remove_modules_232' => 'Remove module table',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function remove_modules_232( $wpdb ) {
|
||||
$this->do_query( $wpdb, "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_modules" );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
// Note: not localised as the messages aren't important enough
|
||||
class Red_Database_233 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'fix_invalid_groups_233' => 'Migrate any groups with invalid module ID',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function fix_invalid_groups_233( $wpdb ) {
|
||||
$this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_groups SET module_id=1 WHERE module_id > 2" );
|
||||
|
||||
$latest = Red_Database::get_latest_database();
|
||||
return $latest->create_groups( $wpdb );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* There are several problems with 2.3.3 => 2.4 that this attempts to cope with:
|
||||
* - some sites have a misconfigured IP column
|
||||
* - some sites don't have any IP column
|
||||
*/
|
||||
class Red_Database_240 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'convert_int_ip_to_varchar_240' => 'Convert integer IP values to support IPv6',
|
||||
'expand_log_ip_column_240' => 'Expand IP size in logs to support IPv6',
|
||||
'convert_title_to_text_240' => 'Expand size of redirect titles',
|
||||
'add_missing_index_240' => 'Add missing IP index to 404 logs',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_ip_index( $wpdb ) {
|
||||
$wpdb->hide_errors();
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'key `ip` (' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function has_varchar_ip( $wpdb ) {
|
||||
$wpdb->hide_errors();
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), '`ip` varchar(45)' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function has_int_ip( $wpdb ) {
|
||||
$wpdb->hide_errors();
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), '`ip` int' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function convert_int_ip_to_varchar_240( $wpdb ) {
|
||||
if ( $this->has_int_ip( $wpdb ) ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `ipaddress` VARCHAR(45) DEFAULT NULL AFTER `ip`" );
|
||||
$this->do_query( $wpdb, "UPDATE {$wpdb->prefix}redirection_404 SET ipaddress=INET_NTOA(ip)" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP `ip`" );
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` CHANGE `ipaddress` `ip` VARCHAR(45) DEFAULT NULL" );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function expand_log_ip_column_240( $wpdb ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` CHANGE `ip` `ip` VARCHAR(45) DEFAULT NULL" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_missing_index_240( $wpdb ) {
|
||||
if ( $this->has_ip_index( $wpdb ) ) {
|
||||
// Remove index
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP INDEX ip" );
|
||||
}
|
||||
|
||||
// Ensure we have an IP column
|
||||
$this->convert_int_ip_to_varchar_240( $wpdb );
|
||||
if ( ! $this->has_varchar_ip( $wpdb ) ) {
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `ip` VARCHAR(45) DEFAULT NULL" );
|
||||
}
|
||||
|
||||
// Finally add the index
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD INDEX `ip` (`ip`)" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function convert_title_to_text_240( $wpdb ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` CHANGE `title` `title` text" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
class Red_Database_400 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'add_match_url_400' => 'Add a matched URL column',
|
||||
'add_match_url_index' => 'Add match URL index',
|
||||
'add_redirect_data_400' => 'Add column to store new flags',
|
||||
'convert_existing_urls_400' => 'Convert existing URLs to new format',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @param string $column
|
||||
* @return bool
|
||||
*/
|
||||
private function has_column( $wpdb, $column ) {
|
||||
$wpdb->hide_errors();
|
||||
$wpdb->suppress_errors( true );
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_items`", ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
$wpdb->suppress_errors( false );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), strtolower( $column ) ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_match_index( $wpdb ) {
|
||||
$wpdb->hide_errors();
|
||||
$wpdb->suppress_errors( true );
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_items`", ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
$wpdb->suppress_errors( false );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'key `match_url' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_match_url_400( $wpdb ) {
|
||||
if ( ! $this->has_column( $wpdb, '`match_url` varchar(2000)' ) ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `match_url` VARCHAR(2000) NULL DEFAULT NULL AFTER `url`" );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool|void
|
||||
*/
|
||||
protected function add_match_url_index( $wpdb ) {
|
||||
if ( ! $this->has_match_index( $wpdb ) ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD INDEX `match_url` (`match_url`(191))" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_redirect_data_400( $wpdb ) {
|
||||
if ( ! $this->has_column( $wpdb, '`match_data` TEXT' ) ) {
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_items` ADD `match_data` TEXT NULL DEFAULT NULL AFTER `match_url`" );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function convert_existing_urls_400( $wpdb ) {
|
||||
// All regex get match_url=regex
|
||||
$this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='regex' WHERE regex=1" );
|
||||
|
||||
// Remove query part from all URLs and lowercase
|
||||
$this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LOWER(url) WHERE regex=0" );
|
||||
|
||||
// Set exact match if query param present
|
||||
|
||||
$table_name = $wpdb->prefix . 'redirection_items';
|
||||
// phpstan requires literal-string but table prefix must be dynamic for WordPress multisite
|
||||
/** @phpstan-ignore argument.type */
|
||||
$prepared = $wpdb->prepare( "UPDATE `{$table_name}` SET match_data=%s WHERE regex=0 AND match_url LIKE %s", '{"source":{"flag_query":"exactorder"}}', '%?%' ); // phpcs:ignore
|
||||
if ( $prepared !== null ) {
|
||||
$this->do_query( $wpdb, $prepared );
|
||||
}
|
||||
|
||||
// Trim the last / from a URL
|
||||
$this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LEFT(match_url,LENGTH(match_url)-1) WHERE regex=0 AND match_url != '/' AND RIGHT(match_url, 1) = '/'" );
|
||||
$this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=REPLACE(match_url, '/?', '?') WHERE regex=0" );
|
||||
|
||||
// Any URL that is now empty becomes /
|
||||
return $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='/' WHERE match_url=''" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class Red_Database_410 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'handle_double_slash' => 'Support double-slash URLs',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function handle_double_slash( $wpdb ) {
|
||||
// Update any URL with a double slash at the end
|
||||
$this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url=LOWER(LEFT(SUBSTRING_INDEX(url, '?', 1),LENGTH(SUBSTRING_INDEX(url, '?', 1)) - 1)) WHERE RIGHT(SUBSTRING_INDEX(url, '?', 1), 2) = '//' AND regex=0" );
|
||||
|
||||
// Any URL that is now empty becomes /
|
||||
return $this->do_query( $wpdb, "UPDATE `{$wpdb->prefix}redirection_items` SET match_url='/' WHERE match_url=''" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
class Red_Database_420 extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
'add_extra_logging' => 'Add extra logging support',
|
||||
'remove_module_id' => 'Remove module ID from logs',
|
||||
'remove_group_id' => 'Remove group ID from logs',
|
||||
'add_extra_404' => 'Add extra 404 logging support',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function remove_module_id( $wpdb ) {
|
||||
if ( ! $this->has_module_id( $wpdb ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` DROP `module_id`" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function remove_group_id( $wpdb ) {
|
||||
if ( ! $this->has_group_id( $wpdb ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` DROP `group_id`" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_module_id( $wpdb ) {
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'module_id' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_group_id( $wpdb ) {
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'group_id' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_log_domain( $wpdb ) {
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_logs`", ARRAY_N );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'domain` varchar' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
private function has_404_domain( $wpdb ) {
|
||||
$existing = $wpdb->get_row( "SHOW CREATE TABLE `{$wpdb->prefix}redirection_404`", ARRAY_N );
|
||||
|
||||
if ( isset( $existing[1] ) && strpos( strtolower( $existing[1] ), 'domain` varchar' ) !== false ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_extra_logging( $wpdb ) {
|
||||
if ( $this->has_log_domain( $wpdb ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update any URL with a double slash at the end
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `domain` VARCHAR(255) NULL DEFAULT NULL AFTER `url`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `http_code` INT(11) unsigned NOT NULL DEFAULT 0 AFTER `referrer`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `request_method` VARCHAR(10) NULL DEFAULT NULL AFTER `http_code`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `redirect_by` VARCHAR(50) NULL DEFAULT NULL AFTER `request_method`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` ADD `request_data` MEDIUMTEXT NULL DEFAULT NULL AFTER `request_method`" );
|
||||
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_logs` CHANGE COLUMN `agent` `agent` MEDIUMTEXT NULL" );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
protected function add_extra_404( $wpdb ) {
|
||||
if ( $this->has_404_domain( $wpdb ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update any URL with a double slash at the end
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `domain` VARCHAR(255) NULL DEFAULT NULL AFTER `url`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `http_code` INT(11) unsigned NOT NULL DEFAULT 0 AFTER `referrer`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `request_method` VARCHAR(10) NULL DEFAULT NULL AFTER `http_code`" );
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` ADD `request_data` MEDIUMTEXT NULL DEFAULT NULL AFTER `request_method`" );
|
||||
|
||||
// Same as log table
|
||||
$this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` DROP INDEX `url`" );
|
||||
return $this->do_query( $wpdb, "ALTER TABLE `{$wpdb->prefix}redirection_404` CHANGE COLUMN `url` `url` MEDIUMTEXT NOT NULL" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Latest database schema
|
||||
*/
|
||||
class Red_Latest_Database extends Red_Database_Upgrader {
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_stages() {
|
||||
return [
|
||||
/* translators: displayed when installing the plugin */
|
||||
'create_tables' => __( 'Install Redirection tables', 'redirection' ),
|
||||
/* translators: displayed when installing the plugin */
|
||||
'create_groups' => __( 'Create basic data', 'redirection' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the latest database
|
||||
*
|
||||
* @return bool|WP_Error true if installed, WP_Error otherwise
|
||||
*/
|
||||
/**
|
||||
* @return bool|\WP_Error
|
||||
*/
|
||||
public function install() {
|
||||
global $wpdb;
|
||||
|
||||
foreach ( $this->get_stages() as $stage => $info ) {
|
||||
// @phpstan-ignore method.dynamicName
|
||||
$result = $this->$stage( $wpdb );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
if ( $wpdb->last_error ) {
|
||||
$result->add_data( $wpdb->last_error );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
red_set_options( array( 'database' => REDIRECTION_DB_VERSION ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the database and any options (including unused ones)
|
||||
*/
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function remove() {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_items" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_logs" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_groups" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_modules" );
|
||||
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}redirection_404" );
|
||||
|
||||
delete_option( 'redirection_lookup' );
|
||||
delete_option( 'redirection_post' );
|
||||
delete_option( 'redirection_root' );
|
||||
delete_option( 'redirection_index' );
|
||||
delete_option( 'redirection_options' );
|
||||
delete_option( Red_Database_Status::OLD_DB_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any tables that are missing from the database
|
||||
*
|
||||
* @return array Array of missing table names
|
||||
*/
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function get_missing_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$tables = array_keys( $this->get_all_tables() );
|
||||
$missing = [];
|
||||
|
||||
foreach ( $tables as $table ) {
|
||||
$result = $wpdb->query( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
|
||||
|
||||
if ( intval( $result, 10 ) !== 1 ) {
|
||||
$missing[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table schema for latest database tables
|
||||
*
|
||||
* @return array Database schema array
|
||||
*/
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function get_table_schema() {
|
||||
global $wpdb;
|
||||
|
||||
$tables = array_keys( $this->get_all_tables() );
|
||||
$show = array();
|
||||
|
||||
foreach ( $tables as $table ) {
|
||||
// Suppress errors when checking for table existence
|
||||
$wpdb->hide_errors();
|
||||
$wpdb->suppress_errors( true );
|
||||
// These are known queries without user input
|
||||
// phpcs:ignore
|
||||
$row = $wpdb->get_row( 'SHOW CREATE TABLE ' . $table, ARRAY_N );
|
||||
$wpdb->show_errors();
|
||||
$wpdb->suppress_errors( false );
|
||||
|
||||
if ( $row ) {
|
||||
$show = array_merge( $show, explode( "\n", $row[1] ) );
|
||||
$show[] = '';
|
||||
} else {
|
||||
/* translators: 1: table name */
|
||||
$show[] = sprintf( __( 'Table "%s" is missing', 'redirection' ), $table );
|
||||
}
|
||||
}
|
||||
|
||||
return $show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of table names and table schema
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function get_all_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$charset_collate = $this->get_charset();
|
||||
|
||||
return array(
|
||||
"{$wpdb->prefix}redirection_items" => $this->create_items_sql( $wpdb->prefix, $charset_collate ),
|
||||
"{$wpdb->prefix}redirection_groups" => $this->create_groups_sql( $wpdb->prefix, $charset_collate ),
|
||||
"{$wpdb->prefix}redirection_logs" => $this->create_log_sql( $wpdb->prefix, $charset_collate ),
|
||||
"{$wpdb->prefix}redirection_404" => $this->create_404_sql( $wpdb->prefix, $charset_collate ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates default group information
|
||||
*/
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @param bool $is_live
|
||||
* @return bool
|
||||
*/
|
||||
public function create_groups( $wpdb, $is_live = true ) {
|
||||
if ( ! $is_live ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
[
|
||||
'name' => __( 'Redirections', 'redirection' ),
|
||||
'module_id' => 1,
|
||||
'position' => 0,
|
||||
],
|
||||
[
|
||||
'name' => __( 'Modified Posts', 'redirection' ),
|
||||
'module_id' => 1,
|
||||
'position' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$existing_groups = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_groups" );
|
||||
|
||||
// Default groups
|
||||
if ( intval( $existing_groups, 10 ) === 0 ) {
|
||||
$wpdb->insert( $wpdb->prefix . 'redirection_groups', $defaults[0] );
|
||||
$wpdb->insert( $wpdb->prefix . 'redirection_groups', $defaults[1] );
|
||||
}
|
||||
|
||||
$group = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}redirection_groups LIMIT 1" );
|
||||
if ( $group !== null ) {
|
||||
red_set_options( array( 'last_group_id' => $group->id ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the tables
|
||||
*/
|
||||
/**
|
||||
* @param \wpdb $wpdb
|
||||
* @return bool
|
||||
*/
|
||||
public function create_tables( $wpdb ) {
|
||||
global $wpdb;
|
||||
|
||||
foreach ( $this->get_all_tables() as $table => $sql ) {
|
||||
$sql = (string) preg_replace( '/[ \t]{2,}/', '', $sql );
|
||||
$this->do_query( $wpdb, $sql );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @param string $charset_collate
|
||||
* @return string
|
||||
*/
|
||||
private function create_items_sql( $prefix, $charset_collate ) {
|
||||
return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_items` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`url` mediumtext NOT NULL,
|
||||
`match_url` VARCHAR(2000) DEFAULT NULL,
|
||||
`match_data` TEXT DEFAULT NULL,
|
||||
`regex` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
`position` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
`last_count` INT(10) unsigned NOT NULL DEFAULT 0,
|
||||
`last_access` datetime NOT NULL DEFAULT '1970-01-01 00:00:00',
|
||||
`group_id` INT(11) NOT NULL DEFAULT 0,
|
||||
`status` enum('enabled','disabled') NOT NULL DEFAULT 'enabled',
|
||||
`action_type` VARCHAR(20) NOT NULL,
|
||||
`action_code` INT(11) unsigned NOT NULL,
|
||||
`action_data` MEDIUMTEXT DEFAULT NULL,
|
||||
`match_type` VARCHAR(20) NOT NULL,
|
||||
`title` TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `url` (`url`(191)),
|
||||
KEY `status` (`status`),
|
||||
KEY `regex` (`regex`),
|
||||
KEY `group_idpos` (`group_id`,`position`),
|
||||
KEY `group` (`group_id`),
|
||||
KEY `match_url` (`match_url`(191))
|
||||
) $charset_collate";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @param string $charset_collate
|
||||
* @return string
|
||||
*/
|
||||
private function create_groups_sql( $prefix, $charset_collate ) {
|
||||
return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_groups` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`tracking` INT(11) NOT NULL DEFAULT 1,
|
||||
`module_id` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
`status` enum('enabled','disabled') NOT NULL DEFAULT 'enabled',
|
||||
`position` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `module_id` (`module_id`),
|
||||
KEY `status` (`status`)
|
||||
) $charset_collate";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @param string $charset_collate
|
||||
* @return string
|
||||
*/
|
||||
private function create_log_sql( $prefix, $charset_collate ) {
|
||||
return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_logs` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`created` datetime NOT NULL,
|
||||
`url` MEDIUMTEXT NOT NULL,
|
||||
`domain` VARCHAR(255) DEFAULT NULL,
|
||||
`sent_to` MEDIUMTEXT DEFAULT NULL,
|
||||
`agent` MEDIUMTEXT DEFAULT NULL,
|
||||
`referrer` MEDIUMTEXT DEFAULT NULL,
|
||||
`http_code` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
`request_method` VARCHAR(10) DEFAULT NULL,
|
||||
`request_data` MEDIUMTEXT DEFAULT NULL,
|
||||
`redirect_by` VARCHAR(50) DEFAULT NULL,
|
||||
`redirection_id` INT(11) unsigned DEFAULT NULL,
|
||||
`ip` VARCHAR(45) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created` (`created`),
|
||||
KEY `redirection_id` (`redirection_id`),
|
||||
KEY `ip` (`ip`)
|
||||
) $charset_collate";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @param string $charset_collate
|
||||
* @return string
|
||||
*/
|
||||
private function create_404_sql( $prefix, $charset_collate ) {
|
||||
return "CREATE TABLE IF NOT EXISTS `{$prefix}redirection_404` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`created` datetime NOT NULL,
|
||||
`url` MEDIUMTEXT NOT NULL,
|
||||
`domain` VARCHAR(255) DEFAULT NULL,
|
||||
`agent` VARCHAR(255) DEFAULT NULL,
|
||||
`referrer` VARCHAR(255) DEFAULT NULL,
|
||||
`http_code` INT(11) unsigned NOT NULL DEFAULT 0,
|
||||
`request_method` VARCHAR(10) DEFAULT NULL,
|
||||
`request_data` MEDIUMTEXT DEFAULT NULL,
|
||||
`ip` VARCHAR(45) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `created` (`created`),
|
||||
KEY `referrer` (`referrer`(191)),
|
||||
KEY `ip` (`ip`)
|
||||
) $charset_collate";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user