> */ public static function get_competitor_maps(): array { return array( 'yoast' => array( 'label' => 'Yoast SEO Metadata (_yoast_wpseo_*)', 'title' => '_yoast_wpseo_title', 'description' => '_yoast_wpseo_metadesc', 'focus_keyword' => '_yoast_wpseo_focuskw', 'canonical_url' => '_yoast_wpseo_canonical', 'noindex' => '_yoast_wpseo_meta-robots-noindex', 'nofollow' => '_yoast_wpseo_meta-robots-nofollow', 'og_title' => '_yoast_wpseo_opengraph-title', 'og_desc' => '_yoast_wpseo_opengraph-description', 'og_image' => '_yoast_wpseo_opengraph-image', ), 'rankmath' => array( 'label' => 'Rank Math Metadata (rank_math_*)', 'title' => 'rank_math_title', 'description' => 'rank_math_description', 'focus_keyword' => 'rank_math_focus_keyword', 'canonical_url' => 'rank_math_canonical_url', 'robots' => 'rank_math_robots', 'og_title' => 'rank_math_facebook_title', 'og_desc' => 'rank_math_facebook_description', 'og_image' => 'rank_math_facebook_image', ), 'aioseo' => array( 'label' => 'AIOSEO Metadata (_aioseo_*)', 'title' => '_aioseo_title', 'description' => '_aioseo_description', 'focus_keyword' => '_aioseo_keywords', 'canonical_url' => '_aioseo_canonical_url', 'og_title' => '_aioseo_og_title', 'og_desc' => '_aioseo_og_description', ), 'seopress' => array( 'label' => 'SEOPress Metadata (_seopress_*)', 'title' => '_seopress_titles_title', 'description' => '_seopress_titles_desc', 'focus_keyword' => '_seopress_analysis_target_kw', 'canonical_url' => '_seopress_robots_canonical', 'noindex' => '_seopress_robots_index', 'nofollow' => '_seopress_robots_follow', 'og_title' => '_seopress_social_fb_title', 'og_desc' => '_seopress_social_fb_desc', 'og_image' => '_seopress_social_fb_img', ), ); } /** * Processes a batch of posts to migrate existing SEO metadata into `wp_modulustools_meta`. * * @param string $source 'yoast', 'rankmath', 'aioseo', or 'seopress'. * @param int $offset Batch offset. * @param int $limit Batch size. * @return array */ public static function process_batch( string $source, int $offset = 0, int $limit = 50 ): array { global $wpdb; $maps = self::get_competitor_maps(); if ( ! isset( $maps[ $source ] ) ) { return array( 'success' => false, 'message' => __( 'Unsupported metadata source selected.', 'modulustools-seo-suite' ), ); } $map = $maps[ $source ]; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $post_ids = $wpdb->get_col( $wpdb->prepare( 'SELECT ID FROM %i WHERE post_status IN (%s, %s, %s, %s) AND post_type NOT IN (%s, %s, %s) ORDER BY ID ASC LIMIT %d OFFSET %d', $wpdb->posts, 'publish', 'draft', 'private', 'future', 'revision', 'nav_menu_item', 'attachment', $limit, $offset ) ); $migrated_count = 0; if ( is_array( $post_ids ) ) { foreach ( $post_ids as $post_id ) { $post_id = (int) $post_id; $title = (string) get_post_meta( $post_id, $map['title'], true ); $desc = (string) get_post_meta( $post_id, $map['description'], true ); $kw = (string) get_post_meta( $post_id, $map['focus_keyword'], true ); $canon = isset( $map['canonical_url'] ) ? (string) get_post_meta( $post_id, $map['canonical_url'], true ) : ''; $title = self::normalize_variables( $title ); $desc = self::normalize_variables( $desc ); $focus_kw = $kw; $secondary_kw = ''; if ( false !== strpos( $kw, ',' ) ) { $parts = array_map( 'trim', explode( ',', $kw ) ); $focus_kw = (string) array_shift( $parts ); $secondary_kw = implode( ', ', $parts ); } $robots_index = 'index'; $robots_follow = 'follow'; if ( 'rankmath' === $source && isset( $map['robots'] ) ) { $rm_robots = get_post_meta( $post_id, $map['robots'], true ); if ( is_array( $rm_robots ) ) { if ( in_array( 'noindex', $rm_robots, true ) ) { $robots_index = 'noindex'; } if ( in_array( 'nofollow', $rm_robots, true ) ) { $robots_follow = 'nofollow'; } } } else { $raw_noindex = isset( $map['noindex'] ) ? (string) get_post_meta( $post_id, $map['noindex'], true ) : ''; $raw_nofollow = isset( $map['nofollow'] ) ? (string) get_post_meta( $post_id, $map['nofollow'], true ) : ''; if ( in_array( $raw_noindex, array( '1', 'yes', 'noindex' ), true ) ) { $robots_index = 'noindex'; } if ( in_array( $raw_nofollow, array( '1', 'yes', 'nofollow' ), true ) ) { $robots_follow = 'nofollow'; } } $og_title = isset( $map['og_title'] ) ? (string) get_post_meta( $post_id, $map['og_title'], true ) : ''; $og_desc = isset( $map['og_desc'] ) ? (string) get_post_meta( $post_id, $map['og_desc'], true ) : ''; $og_image = isset( $map['og_image'] ) ? (string) get_post_meta( $post_id, $map['og_image'], true ) : ''; if ( '' !== $title || '' !== $desc || '' !== $focus_kw || '' !== $canon || 'noindex' === $robots_index ) { Core::save_meta( $post_id, 'post', array( 'title' => $title, 'description' => $desc, 'focus_keyword' => $focus_kw, 'secondary_keywords' => $secondary_kw, 'canonical_url' => $canon, 'robots_index' => $robots_index, 'robots_follow' => $robots_follow, 'og_title' => $og_title, 'og_description' => $og_desc, 'og_image' => $og_image, ) ); ++$migrated_count; } } } $has_more = is_array( $post_ids ) && count( $post_ids ) === $limit; return array( 'success' => true, 'source' => $source, 'processed' => is_array( $post_ids ) ? count( $post_ids ) : 0, 'migrated_count' => $migrated_count, 'next_offset' => $offset + $limit, 'has_more' => $has_more, ); } /** * Normalizes template variables to `%post_title%`, `%site_name%`, and `%sep%`. * * @param string $raw Raw template string. * @return string */ private static function normalize_variables( string $raw ): string { if ( '' === $raw ) { return ''; } $search = array( '%%title%%', '%title%', '#post_title', '%%sitename%%', '%sitename%', '#site_title', '%%sep%%', '%sep%', '#separator_sa' ); $replace = array( '%post_title%', '%post_title%', '%post_title%', '%site_name%', '%site_name%', '%site_name%', '%sep%', '%sep%', '%sep%' ); return str_replace( $search, $replace, $raw ); } /** * Renders the Metadata Importer Wizard in the Admin Dashboard. * * @return void */ public static function render_migration_wizard(): void { $maps = self::get_competitor_maps(); ?>