v1.0.0
This commit is contained in:
parent
8caa140991
commit
5cdf7f48e5
29 changed files with 6526 additions and 0 deletions
282
includes/class-robotstxt-mlp2-mlp5-detector-mlp2.php
Normal file
282
includes/class-robotstxt-mlp2-mlp5-detector-mlp2.php
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
/**
|
||||
* MultilingualPress 2 detection service.
|
||||
*
|
||||
* Detects whether MultilingualPress 2 is installed (active or inactive),
|
||||
* reads its plugin metadata, and identifies its database tables and schema.
|
||||
*
|
||||
* Note: MLP2 does not need to be active for migration. Only its database
|
||||
* tables need to exist. See docs/ROADMAP.md — Background section.
|
||||
*
|
||||
* @package Robotstxt_Mlp2_Mlp5
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Robotstxt_Mlp2_Mlp5_Detector_Mlp2
|
||||
*
|
||||
* Instantiate once to detect MLP2; all detection results are cached in
|
||||
* instance properties to avoid repeated file or database reads.
|
||||
*
|
||||
* Usage:
|
||||
* $mlp2 = new Robotstxt_Mlp2_Mlp5_Detector_Mlp2();
|
||||
* if ( $mlp2->get_site_relations_table() !== '' ) { ... }
|
||||
*/
|
||||
class Robotstxt_Mlp2_Mlp5_Detector_Mlp2 {
|
||||
|
||||
/**
|
||||
* Known plugin basename for MultilingualPress 2 (WordPress.org slug).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_BASENAME = 'multilingual-press/multilingual-press.php';
|
||||
|
||||
/**
|
||||
* Resolved MLP2 plugin basename, or empty string if not found on disk.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_file;
|
||||
|
||||
/**
|
||||
* Plugin header data from get_plugin_data(), or empty array if unavailable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $plugin_data;
|
||||
|
||||
/**
|
||||
* Constructor. Locates the MLP2 plugin file and loads its header data.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->plugin_file = $this->find_plugin_file();
|
||||
$this->plugin_data = $this->plugin_file ? $this->load_plugin_data() : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether MultilingualPress 2 is currently network-active.
|
||||
*
|
||||
* Informational only. MLP2 may be deactivated or uninstalled while its
|
||||
* database tables still exist. Table presence is the migration gate, not
|
||||
* plugin activity.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active(): bool {
|
||||
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
return '' !== $this->plugin_file && is_plugin_active_for_network( $this->plugin_file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MLP2 version string, or empty string if unavailable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_version(): string {
|
||||
return isset( $this->plugin_data['Version'] ) ? (string) $this->plugin_data['Version'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved MLP2 plugin basename, or empty string if not on disk.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_file(): string {
|
||||
return $this->plugin_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given table exists in the database.
|
||||
*
|
||||
* @param string $table Full table name (including prefix).
|
||||
* @return bool
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full MLP2 site relations table name, or empty string if absent.
|
||||
*
|
||||
* Checks the original name first. If the original has been renamed by the
|
||||
* Table_Preparer, returns the backup name instead so that later phases can
|
||||
* read the data regardless of whether preparation has run.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_site_relations_table(): string {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->base_prefix . 'mlp_site_relations';
|
||||
$backup = Robotstxt_Mlp2_Mlp5_Table_Preparer::backup_name( $table );
|
||||
|
||||
// Check backup first: after preparation the original name belongs to MLP5.
|
||||
if ( '' !== $backup && $this->table_exists( $backup ) ) {
|
||||
return $backup;
|
||||
}
|
||||
|
||||
// Fall back to original only when no backup tables exist (preparation not done yet).
|
||||
if ( ! Robotstxt_Mlp2_Mlp5_Table_Preparer::has_backup_tables() && $this->table_exists( $table ) ) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full MLP2 content relations table name, or empty string if absent.
|
||||
*
|
||||
* MLP2 uses `mlp_relationships` in older minor versions,
|
||||
* `mlp_content_relations` in newer ones, and `multilingual_linked` in some
|
||||
* installations (table name lacks the mlp_ prefix and is never renamed by
|
||||
* the Table_Preparer). All three are checked. If the original table has been
|
||||
* renamed by the Table_Preparer, its backup name is returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_relations_table(): string {
|
||||
global $wpdb;
|
||||
|
||||
// After preparation, original names belong to MLP5; only look at backups.
|
||||
$preparation_done = Robotstxt_Mlp2_Mlp5_Table_Preparer::has_backup_tables();
|
||||
|
||||
foreach ( array( 'mlp_relationships', 'mlp_content_relations' ) as $candidate ) {
|
||||
$table = $wpdb->base_prefix . $candidate;
|
||||
$backup = Robotstxt_Mlp2_Mlp5_Table_Preparer::backup_name( $table );
|
||||
|
||||
if ( '' !== $backup && $this->table_exists( $backup ) ) {
|
||||
return $backup;
|
||||
}
|
||||
|
||||
// Fall back to original only when preparation has not run yet.
|
||||
if ( ! $preparation_done && $this->table_exists( $table ) ) {
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
|
||||
// Some MLP2 installations store content relations in `multilingual_linked`
|
||||
// (no mlp_ prefix). This table is never touched by Table_Preparer, so it
|
||||
// remains accessible regardless of whether preparation has run.
|
||||
$linked = $wpdb->base_prefix . 'multilingual_linked';
|
||||
if ( $this->table_exists( $linked ) ) {
|
||||
return $linked;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all tables in the database that match the MLP table prefix pattern.
|
||||
*
|
||||
* This may include tables belonging to MLP5 if it uses the same prefix.
|
||||
* Use get_site_relations_table() and get_content_relations_table() for
|
||||
* precise MLP2-specific detection.
|
||||
*
|
||||
* @return string[] Array of full table names.
|
||||
*/
|
||||
public function get_all_tables(): array {
|
||||
global $wpdb;
|
||||
|
||||
$like = $wpdb->esc_like( $wpdb->base_prefix . 'mlp_' ) . '%';
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column definitions for a table.
|
||||
*
|
||||
* @param string $table Full table name (must start with the DB base prefix).
|
||||
* @return array Associative array keyed by column name (Field), or empty array
|
||||
* if the table does not exist or the name fails validation.
|
||||
*/
|
||||
public function describe_table( string $table ): array {
|
||||
global $wpdb;
|
||||
|
||||
// Validate: table name must start with the base prefix.
|
||||
if ( ! str_starts_with( $table, $wpdb->base_prefix ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( ! $this->table_exists( $table ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$columns = $wpdb->get_results( "SHOW COLUMNS FROM {$table}", ARRAY_A );
|
||||
|
||||
if ( empty( $columns ) || ! is_array( $columns ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ( $columns as $column ) {
|
||||
if ( isset( $column['Field'] ) ) {
|
||||
$result[ $column['Field'] ] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the MLP2 plugin file on disk.
|
||||
*
|
||||
* Checks the known canonical basename first. If not found, scans all
|
||||
* installed plugins for a plugin named "MultilingualPress" with a
|
||||
* version below 3.0.0 (which marks the MLP3+ series).
|
||||
*
|
||||
* @return string Plugin basename, or empty string if not found.
|
||||
*/
|
||||
private function find_plugin_file(): string {
|
||||
if ( file_exists( WP_PLUGIN_DIR . '/' . self::PLUGIN_BASENAME ) ) {
|
||||
return self::PLUGIN_BASENAME;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
foreach ( get_plugins() as $basename => $data ) {
|
||||
if (
|
||||
isset( $data['Name'], $data['Version'] ) &&
|
||||
'MultilingualPress' === $data['Name'] &&
|
||||
version_compare( (string) $data['Version'], '3.0.0', '<' )
|
||||
) {
|
||||
return $basename;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin header data from the MLP2 main file.
|
||||
*
|
||||
* @return array Plugin data from get_plugin_data(), or empty array on failure.
|
||||
*/
|
||||
private function load_plugin_data(): array {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$path = WP_PLUGIN_DIR . '/' . $this->plugin_file;
|
||||
|
||||
if ( ! file_exists( $path ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return get_plugin_data( $path, false, false );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue