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,197 @@
<?php
/**
* MLP2 content relations reader.
*
* Reads post and term relation data from the MLP2 backup content_relations
* table. Relations are grouped by (ml_source_blogid, ml_source_elementid)
* so that each group can be written as a single MLP5 relationship.
*
* MLP2 backup table schema (mlp2bak_content_relations or mlp2bak_relationships):
* ml_source_blogid INT -- source site ID
* ml_source_elementid INT -- source element (post/term) ID
* ml_blogid INT -- target site ID
* ml_elementid INT -- target element ID
* ml_type VARCHAR -- 'post' or 'term'
*
* @package Robotstxt_Mlp2_Mlp5
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Robotstxt_Mlp2_Mlp5_Reader_Content_Relations
*/
class Robotstxt_Mlp2_Mlp5_Reader_Content_Relations {
/**
* Count distinct source groups for a given type.
*
* @param string $type 'post' or 'term'.
* @return int
*/
public static function count_groups( string $type ): int {
global $wpdb;
$mlp2 = new Robotstxt_Mlp2_Mlp5_Detector_Mlp2();
$table = $mlp2->get_content_relations_table();
if ( '' === $table ) {
return 0;
}
list( $src_blog, $src_elem ) = self::source_columns( $mlp2, $table );
if ( '' === $src_blog ) {
return 0;
}
$type_safe = ( 'term' === $type ) ? 'term' : 'post';
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT {$src_blog}, {$src_elem}) FROM `{$table}` WHERE ml_type = %s",
$type_safe
)
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
* Return a batch of distinct source groups, with all their translation members.
*
* Each returned element is:
* [
* 'type' => string, // 'post' or 'term'
* 'members' => [
* ['site_id' => int, 'element_id' => int],
* ...
* ]
* ]
*
* The source element itself is always included as a member.
*
* @param string $type 'post' or 'term'.
* @param int $offset Offset into the distinct source list.
* @param int $limit Number of groups to return.
* @return array[]
*/
public static function get_groups_batch( string $type, int $offset, int $limit ): array {
global $wpdb;
$mlp2 = new Robotstxt_Mlp2_Mlp5_Detector_Mlp2();
$table = $mlp2->get_content_relations_table();
if ( '' === $table ) {
return array();
}
list( $src_blog, $src_elem ) = self::source_columns( $mlp2, $table );
if ( '' === $src_blog ) {
return array();
}
$type_safe = ( 'term' === $type ) ? 'term' : 'post';
// Fetch a page of distinct sources.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$sources = $wpdb->get_results(
$wpdb->prepare(
"SELECT DISTINCT {$src_blog} AS source_blog, {$src_elem} AS source_elem FROM `{$table}` WHERE ml_type = %s ORDER BY {$src_blog}, {$src_elem} LIMIT %d OFFSET %d",
$type_safe,
$limit,
$offset
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( empty( $sources ) ) {
return array();
}
$groups = array();
foreach ( $sources as $source ) {
$src_blog_id = (int) $source['source_blog'];
$src_elem_id = (int) $source['source_elem'];
// Fetch all translations for this source.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$translations = $wpdb->get_results(
$wpdb->prepare(
"SELECT ml_blogid, ml_elementid FROM `{$table}` WHERE ml_type = %s AND {$src_blog} = %d AND {$src_elem} = %d",
$type_safe,
$src_blog_id,
$src_elem_id
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// Build members: source + all translations (deduplicated).
$seen = array();
$members = array();
$source_key = $src_blog_id . ':' . $src_elem_id;
$seen[ $source_key ] = true;
$members[] = array(
'site_id' => $src_blog_id,
'element_id' => $src_elem_id,
);
foreach ( (array) $translations as $t ) {
$key = ( (int) $t['ml_blogid'] ) . ':' . ( (int) $t['ml_elementid'] );
if ( isset( $seen[ $key ] ) ) {
continue;
}
$seen[ $key ] = true;
$members[] = array(
'site_id' => (int) $t['ml_blogid'],
'element_id' => (int) $t['ml_elementid'],
);
}
if ( count( $members ) >= 2 ) {
$groups[] = array(
'type' => $type_safe,
'members' => $members,
);
}
}
return $groups;
}
/**
* Detect the source-blog and source-element column names.
*
* Returns ['', ''] when columns cannot be determined.
*
* @param Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2 MLP2 detector.
* @param string $table Content relations table name.
* @return string[] [ $source_blog_col, $source_element_col ]
*/
private static function source_columns( Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2, string $table ): array {
$cols = $mlp2->describe_table( $table );
$blog_col = '';
foreach ( array( 'ml_source_blogid', 'ml_blogid' ) as $c ) {
if ( isset( $cols[ $c ] ) ) {
$blog_col = $c;
break;
}
}
$elem_col = '';
foreach ( array( 'ml_source_elementid', 'ml_elementid' ) as $c ) {
if ( isset( $cols[ $c ] ) ) {
$elem_col = $c;
break;
}
}
return array( $blog_col, $elem_col );
}
}