332 lines
9.4 KiB
PHP
332 lines
9.4 KiB
PHP
<?php
|
|
/**
|
|
* Table preparation service.
|
|
*
|
|
* MultilingualPress 5 refuses to network-activate when it detects tables
|
|
* with the original MLP2 prefix ({base_prefix}mlp_*). This class renames
|
|
* those tables to a backup prefix ({base_prefix}mlp2bak_*) so that MLP5
|
|
* can activate cleanly. The migration wizard then reads from the renamed
|
|
* backup tables.
|
|
*
|
|
* Original name pattern : {base_prefix}mlp_{suffix}
|
|
* Backup name pattern : {base_prefix}mlp2bak_{suffix}
|
|
*
|
|
* @package Robotstxt_Mlp2_Mlp5
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class Robotstxt_Mlp2_Mlp5_Table_Preparer
|
|
*
|
|
* All methods are static. No instantiation required.
|
|
*/
|
|
class Robotstxt_Mlp2_Mlp5_Table_Preparer {
|
|
|
|
/**
|
|
* Table name fragment (after base_prefix) that identifies original MLP2 tables.
|
|
*
|
|
* @var string
|
|
*/
|
|
const ORIGINAL_FRAGMENT = 'mlp_';
|
|
|
|
/**
|
|
* Table name fragment (after base_prefix) used for renamed backup copies.
|
|
*
|
|
* @var string
|
|
*/
|
|
const BACKUP_FRAGMENT = 'mlp2bak_';
|
|
|
|
/**
|
|
* Table suffixes (after base_prefix + ORIGINAL_FRAGMENT) that belong to
|
|
* this plugin and must never be renamed to backup.
|
|
*
|
|
* @var string[]
|
|
*/
|
|
const PLUGIN_TABLE_SUFFIXES = array( 'migrator_log' );
|
|
|
|
/**
|
|
* Return the backup table name for a given original MLP2 table.
|
|
*
|
|
* @param string $original_table Full original table name (including base_prefix).
|
|
* @return string Backup table name, or empty string if not a valid MLP2 table.
|
|
*/
|
|
public static function backup_name( string $original_table ): string {
|
|
global $wpdb;
|
|
|
|
$orig_prefix = $wpdb->base_prefix . self::ORIGINAL_FRAGMENT;
|
|
|
|
if ( ! str_starts_with( $original_table, $orig_prefix ) ) {
|
|
return '';
|
|
}
|
|
|
|
return $wpdb->base_prefix . self::BACKUP_FRAGMENT . substr( $original_table, strlen( $orig_prefix ) );
|
|
}
|
|
|
|
/**
|
|
* Return the original table name for a given backup table.
|
|
*
|
|
* @param string $backup_table Full backup table name (including base_prefix).
|
|
* @return string Original table name, or empty string if not a backup table.
|
|
*/
|
|
public static function original_name( string $backup_table ): string {
|
|
global $wpdb;
|
|
|
|
$bak_prefix = $wpdb->base_prefix . self::BACKUP_FRAGMENT;
|
|
|
|
if ( ! str_starts_with( $backup_table, $bak_prefix ) ) {
|
|
return '';
|
|
}
|
|
|
|
return $wpdb->base_prefix . self::ORIGINAL_FRAGMENT . substr( $backup_table, strlen( $bak_prefix ) );
|
|
}
|
|
|
|
/**
|
|
* Return all tables in the database with the original MLP2 prefix,
|
|
* excluding tables that belong to this plugin.
|
|
*
|
|
* @return string[] Array of full table names.
|
|
*/
|
|
public static function get_original_tables(): array {
|
|
global $wpdb;
|
|
|
|
$like = $wpdb->esc_like( $wpdb->base_prefix . self::ORIGINAL_FRAGMENT ) . '%';
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $like ) );
|
|
|
|
if ( ! is_array( $tables ) ) {
|
|
return array();
|
|
}
|
|
|
|
$prefix = $wpdb->base_prefix . self::ORIGINAL_FRAGMENT;
|
|
|
|
return array_values(
|
|
array_filter(
|
|
$tables,
|
|
static function ( string $table ) use ( $prefix ): bool {
|
|
$suffix = substr( $table, strlen( $prefix ) );
|
|
return ! in_array( $suffix, self::PLUGIN_TABLE_SUFFIXES, true );
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return all tables in the database with the backup prefix.
|
|
*
|
|
* @return string[] Array of full table names.
|
|
*/
|
|
public static function get_backup_tables(): array {
|
|
global $wpdb;
|
|
|
|
$like = $wpdb->esc_like( $wpdb->base_prefix . self::BACKUP_FRAGMENT ) . '%';
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $like ) );
|
|
|
|
return is_array( $tables ) ? $tables : array();
|
|
}
|
|
|
|
/**
|
|
* Sitemeta key prefix used by MLP2 that blocks MLP5 activation.
|
|
*
|
|
* @var string
|
|
*/
|
|
const SITEMETA_ORIGINAL_PREFIX = 'inpsyde_multilingual';
|
|
|
|
/**
|
|
* Prefix prepended to MLP2 sitemeta keys when backing them up.
|
|
*
|
|
* @var string
|
|
*/
|
|
const SITEMETA_BACKUP_PREFIX = 'mlp2bak_inpsyde_multilingual';
|
|
|
|
/**
|
|
* Rename MLP2 blocking sitemeta keys to backup names.
|
|
*
|
|
* MLP5's SystemChecker detects any sitemeta key starting with
|
|
* `inpsyde_multilingual` and refuses to activate. This method renames
|
|
* those keys to `mlp2bak_inpsyde_multilingual*` so MLP5 can activate
|
|
* cleanly while the original data is preserved for restore or cleanup.
|
|
*
|
|
* @return int Number of sitemeta rows renamed.
|
|
*/
|
|
public static function rename_mlp2_sitemeta(): int {
|
|
global $wpdb;
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$renamed = $wpdb->query(
|
|
$wpdb->prepare(
|
|
"UPDATE {$wpdb->sitemeta} SET meta_key = CONCAT( %s, SUBSTR( meta_key, %d ) ) WHERE meta_key LIKE %s AND site_id = %d",
|
|
'mlp2bak_',
|
|
1,
|
|
$wpdb->esc_like( self::SITEMETA_ORIGINAL_PREFIX ) . '%',
|
|
get_current_network_id()
|
|
)
|
|
);
|
|
|
|
return is_int( $renamed ) ? $renamed : 0;
|
|
}
|
|
|
|
/**
|
|
* Restore backup sitemeta keys to their original MLP2 names.
|
|
*
|
|
* Reverses rename_mlp2_sitemeta(). Only use this before MLP5 has been
|
|
* activated, as restoring these keys will block MLP5 activation again.
|
|
*
|
|
* @return int Number of sitemeta rows restored.
|
|
*/
|
|
public static function restore_mlp2_sitemeta(): int {
|
|
global $wpdb;
|
|
|
|
$prefix_length = strlen( 'mlp2bak_' ) + 1; // SUBSTR is 1-indexed.
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
$restored = $wpdb->query(
|
|
$wpdb->prepare(
|
|
"UPDATE {$wpdb->sitemeta} SET meta_key = SUBSTR( meta_key, %d ) WHERE meta_key LIKE %s AND site_id = %d",
|
|
$prefix_length,
|
|
$wpdb->esc_like( self::SITEMETA_BACKUP_PREFIX ) . '%',
|
|
get_current_network_id()
|
|
)
|
|
);
|
|
|
|
return is_int( $restored ) ? $restored : 0;
|
|
}
|
|
|
|
/**
|
|
* Whether any original MLP2 tables exist in the database.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function has_original_tables(): bool {
|
|
return count( self::get_original_tables() ) > 0;
|
|
}
|
|
|
|
/**
|
|
* Whether any backup (renamed) MLP2 tables exist in the database.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function has_backup_tables(): bool {
|
|
return count( self::get_backup_tables() ) > 0;
|
|
}
|
|
|
|
/**
|
|
* Rename all original MLP2 tables to their backup equivalents.
|
|
*
|
|
* Tables that already have a backup copy are skipped. Tables that cannot
|
|
* be converted to a valid backup name are recorded as errors.
|
|
*
|
|
* @return array {
|
|
* @type string[] $renamed Tables successfully renamed.
|
|
* @type string[] $skipped Tables skipped (backup name already exists).
|
|
* @type string[] $errors Tables that could not be renamed.
|
|
* }
|
|
*/
|
|
public static function rename_to_backup(): array {
|
|
global $wpdb;
|
|
|
|
$result = array(
|
|
'renamed' => array(),
|
|
'skipped' => array(),
|
|
'errors' => array(),
|
|
);
|
|
|
|
foreach ( self::get_original_tables() as $table ) {
|
|
$backup = self::backup_name( $table );
|
|
|
|
if ( '' === $backup ) {
|
|
$result['errors'][] = $table;
|
|
continue;
|
|
}
|
|
|
|
// Skip tables that belong to this plugin.
|
|
$suffix = substr( $table, strlen( $wpdb->base_prefix . self::ORIGINAL_FRAGMENT ) );
|
|
if ( in_array( $suffix, self::PLUGIN_TABLE_SUFFIXES, true ) ) {
|
|
$result['skipped'][] = $table;
|
|
continue;
|
|
}
|
|
|
|
if ( self::table_exists( $backup ) ) {
|
|
$result['skipped'][] = $table;
|
|
continue;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$renamed = $wpdb->query( "RENAME TABLE `{$table}` TO `{$backup}`" );
|
|
|
|
if ( false === $renamed ) {
|
|
$result['errors'][] = $table;
|
|
} else {
|
|
$result['renamed'][] = $table;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Restore backup tables to their original names.
|
|
*
|
|
* Intended as a recovery mechanism if the rename needs to be undone before
|
|
* MLP5 has been activated. Tables whose original name already exists in the
|
|
* database are skipped.
|
|
*
|
|
* @return array {
|
|
* @type string[] $restored Tables successfully restored.
|
|
* @type string[] $skipped Tables skipped (original name already exists).
|
|
* @type string[] $errors Tables that could not be restored.
|
|
* }
|
|
*/
|
|
public static function restore_from_backup(): array {
|
|
global $wpdb;
|
|
|
|
$result = array(
|
|
'restored' => array(),
|
|
'skipped' => array(),
|
|
'errors' => array(),
|
|
);
|
|
|
|
foreach ( self::get_backup_tables() as $table ) {
|
|
$original = self::original_name( $table );
|
|
|
|
if ( '' === $original ) {
|
|
$result['errors'][] = $table;
|
|
continue;
|
|
}
|
|
|
|
if ( self::table_exists( $original ) ) {
|
|
$result['skipped'][] = $table;
|
|
continue;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$restored = $wpdb->query( "RENAME TABLE `{$table}` TO `{$original}`" );
|
|
|
|
if ( false === $restored ) {
|
|
$result['errors'][] = $table;
|
|
} else {
|
|
$result['restored'][] = $table;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check whether a table exists in the database.
|
|
*
|
|
* @param string $table Full table name.
|
|
* @return bool
|
|
*/
|
|
private static function table_exists( string $table ): bool {
|
|
global $wpdb;
|
|
|
|
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
|
return (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
|
|
}
|
|
}
|