get_site_relations_table(); $mlp2_content_table = $mlp2->get_content_relations_table(); $mlp5_site_table = $mlp5->get_site_relations_table(); $mlp5_content_table = $mlp5->get_content_relations_table(); $mlp5_rel_table = $mlp5->get_relationships_table(); // --- Counts --- $site_count = ''; if ( '' !== $mlp2_site_table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $site_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$mlp2_site_table}`" ); } $post_count = 0; $term_count = 0; if ( '' !== $mlp2_content_table ) { $col = self::source_blog_col( $mlp2, $mlp2_content_table ); $id = self::source_element_col( $mlp2, $mlp2_content_table ); if ( '' !== $col && '' !== $id ) { // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $post_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT {$col}, {$id}) FROM `{$mlp2_content_table}` WHERE ml_type = 'post'" ); $term_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT {$col}, {$id}) FROM `{$mlp2_content_table}` WHERE ml_type = 'term'" ); // phpcs:enable } } // --- Orphaned sites --- $orphaned_sites = self::detect_orphaned_sites( $mlp2_site_table ); // --- Orphaned posts/terms (sampled) --- $orphaned_posts = self::detect_orphaned_content( $mlp2_content_table, 'post', 100 ); $orphaned_terms = self::detect_orphaned_content( $mlp2_content_table, 'term', 100 ); // --- Duplicates --- $duplicates = self::count_duplicate_relations( $mlp2_content_table ); // --- MLP5 schema validation --- list( $schema_ok, $schema_error ) = self::validate_mlp5_schema( $mlp5_site_table, $mlp5_content_table, $mlp5_rel_table, $mlp2, $mlp2_site_table, $mlp2_content_table ); return array( 'site_relations_count' => (int) $site_count, 'post_groups_count' => $post_count, 'term_groups_count' => $term_count, 'orphaned_site_ids' => $orphaned_sites, 'orphaned_posts_count' => $orphaned_posts, 'orphaned_terms_count' => $orphaned_terms, 'duplicate_relations' => $duplicates, 'mlp5_schema_ok' => $schema_ok, 'mlp5_schema_error' => $schema_error, 'mlp2_content_table' => $mlp2_content_table, 'mlp2_site_table' => $mlp2_site_table, 'mlp5_content_table' => $mlp5_content_table, 'mlp5_site_table' => $mlp5_site_table, 'mlp5_relationships_table' => $mlp5_rel_table, ); } /** * Render the Step 2 UI. */ public static function render(): void { $data = Robotstxt_Mlp2_Mlp5_Wizard_State::get_step_data( 2 ); $ran = ! empty( $data ); $ran_ok = $ran && ! empty( $data['mlp5_schema_ok'] ); ?>
get_results( "SELECT site_1, site_2 FROM `{$table}`", ARRAY_A ); if ( empty( $rows ) ) { return array(); } $ids = array(); foreach ( $rows as $row ) { $ids[] = (int) $row['site_1']; $ids[] = (int) $row['site_2']; } $ids = array_unique( $ids ); $existing = get_sites( array( 'site__in' => $ids, 'fields' => 'ids', 'number' => count( $ids ), ) ); $existing = array_map( 'intval', $existing ); return array_values( array_diff( $ids, $existing ) ); } /** * Sample up to $sample content relation rows and count orphaned elements. * * @param string $table MLP2 backup content relations table name. * @param string $type 'post' or 'term'. * @param int $sample Maximum rows to inspect. * @return int Count of orphaned references found in the sample. */ private static function detect_orphaned_content( string $table, string $type, int $sample ): int { if ( '' === $table ) { return 0; } global $wpdb; $type_safe = ( 'term' === $type ) ? 'term' : 'post'; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $rows = $wpdb->get_results( $wpdb->prepare( "SELECT ml_blogid, ml_elementid FROM `{$table}` WHERE ml_type = %s LIMIT %d", $type_safe, $sample ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared if ( empty( $rows ) ) { return 0; } $orphans = 0; foreach ( $rows as $row ) { $site_id = (int) $row['ml_blogid']; $element_id = (int) $row['ml_elementid']; if ( ! get_site( $site_id ) ) { ++$orphans; continue; } switch_to_blog( $site_id ); if ( 'term' === $type_safe ) { $exists = (bool) get_term( $element_id ); } else { $exists = (bool) get_post( $element_id ); } restore_current_blog(); if ( ! $exists ) { ++$orphans; } } return $orphans; } /** * Count duplicate rows in the content relations table. * * @param string $table MLP2 backup content relations table name. * @return int Count of duplicate rows. */ private static function count_duplicate_relations( string $table ): int { if ( '' === $table ) { return 0; } global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $distinct = (int) $wpdb->get_var( "SELECT COUNT(*) FROM (SELECT DISTINCT ml_source_blogid, ml_source_elementid, ml_blogid, ml_elementid, ml_type FROM `{$table}`) AS d" ); return max( 0, $total - $distinct ); } /** * Validate that MLP5 tables have the expected columns. * * @param string $mlp5_site MLP5 site relations table. * @param string $mlp5_content MLP5 content relations table. * @param string $mlp5_rel MLP5 relationships table. * @param Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2 MLP2 detector. * @param string $mlp2_site MLP2 backup site relations table. * @param string $mlp2_content MLP2 backup content relations table. * @return array [ bool $ok, string $error_message ] */ private static function validate_mlp5_schema( string $mlp5_site, string $mlp5_content, string $mlp5_rel, Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2, string $mlp2_site, string $mlp2_content ): array { if ( '' === $mlp5_site || '' === $mlp5_content ) { return array( false, __( 'MLP5 tables not found. Ensure MultilingualPress 5 is active and has been initialized.', 'robotstxt-mlp2-mlp5' ) ); } // Site relations: expect site_1 and site_2 columns. $site_cols = $mlp2->describe_table( $mlp5_site ); if ( ! isset( $site_cols['site_1'], $site_cols['site_2'] ) ) { return array( false, sprintf( /* translators: %s: table name */ __( 'Table %s does not have expected columns (site_1, site_2).', 'robotstxt-mlp2-mlp5' ), $mlp5_site ) ); } // Content relations: expect relationship_id, site_id, content_id. $content_cols = $mlp2->describe_table( $mlp5_content ); if ( ! isset( $content_cols['relationship_id'], $content_cols['site_id'], $content_cols['content_id'] ) ) { return array( false, sprintf( /* translators: %s: table name */ __( 'Table %s does not have expected columns (relationship_id, site_id, content_id).', 'robotstxt-mlp2-mlp5' ), $mlp5_content ) ); } // Relationships group table: expect id and type columns. if ( '' !== $mlp5_rel ) { $rel_cols = $mlp2->describe_table( $mlp5_rel ); if ( ! isset( $rel_cols['id'], $rel_cols['type'] ) ) { return array( false, sprintf( /* translators: %s: table name */ __( 'Table %s does not have expected columns (id, type).', 'robotstxt-mlp2-mlp5' ), $mlp5_rel ) ); } } // MLP2 backup: check the content table has the known columns. if ( '' !== $mlp2_content ) { $mlp2_cols = $mlp2->describe_table( $mlp2_content ); $required = array( 'ml_blogid', 'ml_elementid', 'ml_type' ); foreach ( $required as $col ) { if ( ! isset( $mlp2_cols[ $col ] ) ) { return array( false, sprintf( /* translators: 1: column name, 2: table name */ __( 'MLP2 backup table %2$s is missing expected column "%1$s". Schema may differ from expected.', 'robotstxt-mlp2-mlp5' ), $col, $mlp2_content ) ); } } } return array( true, '' ); } /** * Detect the source-blog column name for the MLP2 content relations table. * * @param Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2 MLP2 detector. * @param string $table Content relations table name. * @return string Column name or empty string. */ private static function source_blog_col( Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2, string $table ): string { $cols = $mlp2->describe_table( $table ); foreach ( array( 'ml_source_blogid', 'ml_blogid' ) as $candidate ) { if ( isset( $cols[ $candidate ] ) ) { return $candidate; } } return ''; } /** * Detect the source-element column name for the MLP2 content relations table. * * @param Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2 MLP2 detector. * @param string $table Content relations table name. * @return string Column name or empty string. */ private static function source_element_col( Robotstxt_Mlp2_Mlp5_Detector_Mlp2 $mlp2, string $table ): string { $cols = $mlp2->describe_table( $table ); foreach ( array( 'ml_source_elementid', 'ml_elementid' ) as $candidate ) { if ( isset( $cols[ $candidate ] ) ) { return $candidate; } } return ''; } }