This commit is contained in:
Javier Casares 2026-03-02 11:22:59 +00:00
commit 5cdf7f48e5
29 changed files with 6526 additions and 0 deletions

View file

@ -0,0 +1,218 @@
<?php
/**
* MultilingualPress 5 detection service.
*
* Detects whether MultilingualPress 5 is installed (active or inactive),
* reads its plugin metadata, and identifies its database tables.
*
* MLP5 must be network-active for migration to proceed. Unlike MLP2,
* the plugin itself needs to be running so its API is available.
*
* @package Robotstxt_Mlp2_Mlp5
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Robotstxt_Mlp2_Mlp5_Detector_Mlp5
*
* Instantiate once to detect MLP5; all detection results are cached in
* instance properties to avoid repeated file or database reads.
*
* Usage:
* $mlp5 = new Robotstxt_Mlp2_Mlp5_Detector_Mlp5();
* if ( $mlp5->is_active() ) { ... }
*/
class Robotstxt_Mlp2_Mlp5_Detector_Mlp5 {
/**
* Known plugin basename for MultilingualPress 5 (commercial, Inpsyde).
*
* @var string
*/
const PLUGIN_BASENAME = 'multilingualpress/multilingualpress.php';
/**
* Resolved MLP5 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 MLP5 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 5 is currently network-active.
*
* MLP5 must be active for migration to proceed; its API tables and
* internal state are only available when the plugin is running.
*
* @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 MLP5 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 MLP5 plugin basename, or empty string if not on disk.
*
* @return string
*/
public function get_plugin_file(): string {
return $this->plugin_file;
}
/**
* Return the MLP5 site relations table name, or empty string if absent.
*
* MLP5 uses the same mlp_site_relations table name as MLP2.
*
* @return string
*/
public function get_site_relations_table(): string {
global $wpdb;
$table = $wpdb->base_prefix . 'mlp_site_relations';
return $this->table_exists( $table ) ? $table : '';
}
/**
* Return the MLP5 relationship groups table name, or empty string if absent.
*
* MLP5 stores one row per group of related content here (id, type).
* The content_relations table references this table via relationship_id.
*
* @return string
*/
public function get_relationships_table(): string {
global $wpdb;
$table = $wpdb->base_prefix . 'mlp_relationships';
return $this->table_exists( $table ) ? $table : '';
}
/**
* Return the MLP5 content relations table name, or empty string if absent.
*
* MLP5 uses mlp_content_relations (the newer name introduced in MLP2
* late versions and carried forward).
*
* @return string
*/
public function get_content_relations_table(): string {
global $wpdb;
$table = $wpdb->base_prefix . 'mlp_content_relations';
return $this->table_exists( $table ) ? $table : '';
}
/**
* Return all tables in the database that match the MLP table prefix pattern.
*
* @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();
}
/**
* Check whether a given table exists in the database.
*
* @param string $table Full table name (including prefix).
* @return bool
*/
private 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;
}
/**
* Locate the MLP5 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 of 3.0.0 or higher (MLP3+ series includes MLP5).
*
* @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 MLP5 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 );
}
}