← All changes
|
includes/admin/importers/class-rankmath-exporter.php
+134
-12
2.2.0
→
2.13.0
View file →
| @@ -182,8 +182,17 @@ | ||
| 182 | 182 | if ($log_count > 0) { |
| 183 | 183 | $types['404_logs'] = $log_count; |
| 184 | 184 | } |
| 185 | 185 | |
| 186 | + // FAQ / HowTo blocks live in post_content, not in any meta table, so | |
| 187 | + // they are invisible to every count above. Without this type they were | |
| 188 | + // never migrated at all and simply broke when Rank Math went away | |
| 189 | + // (#777). | |
| 190 | + $block_count = Block_Converter::count_posts(); | |
| 191 | + if ($block_count > 0) { | |
| 192 | + $types[Block_Converter::TYPE] = $block_count; | |
| 193 | + } | |
| 194 | + | |
| 186 | 195 | foreach ($this->option_keys as $key) { |
| 187 | 196 | if (get_option($key, null) !== null) { |
| 188 | 197 | $types['settings'] = 1; |
| 189 | 198 | break; |
| @@ -253,10 +262,10 @@ | ||
| 253 | 262 | 'object_id' => $post_id, |
| 254 | 263 | 'object_type' => 'post', |
| 255 | 264 | 'source_plugin' => $this->plugin_slug, |
| 256 | 265 | 'data' => [ |
| 257 | - 'seo_title' => $this->convert_template_variables($meta['rank_math_title'] ?? '', $post_id), | |
| 258 | - 'meta_description' => $this->convert_template_variables($meta['rank_math_description'] ?? '', $post_id), | |
| 266 | + 'seo_title' => $this->convert_post_value($meta['rank_math_title'] ?? '', $post_id), | |
| 267 | + 'meta_description' => $this->convert_post_value($meta['rank_math_description'] ?? '', $post_id), | |
| 259 | 268 | 'focus_keyword' => $primary_keyword, |
| 260 | 269 | // Full keyword list; the migrator dedupes, drops empties and |
| 261 | 270 | // caps at the ThinkRank maximum via Focus_Keywords. |
| 262 | 271 | 'focus_keywords' => $focus_keywords, |
| @@ -268,16 +277,22 @@ | ||
| 268 | 277 | 'nosnippet' => isset($robots_flags['nosnippet']) ? 1 : 0, |
| 269 | 278 | 'max_snippet' => $this->advanced_robot_int($advanced_robots, 'max-snippet'), |
| 270 | 279 | 'max_video_preview' => $this->advanced_robot_int($advanced_robots, 'max-video-preview'), |
| 271 | 280 | 'max_image_preview' => $this->advanced_robot_string($advanced_robots, 'max-image-preview'), |
| 272 | - 'og_title' => $this->convert_template_variables($meta['rank_math_facebook_title'] ?? '', $post_id), | |
| 273 | - 'og_description' => $this->convert_template_variables($meta['rank_math_facebook_description'] ?? '', $post_id), | |
| 281 | + 'og_title' => $this->convert_post_value($meta['rank_math_facebook_title'] ?? '', $post_id), | |
| 282 | + 'og_description' => $this->convert_post_value($meta['rank_math_facebook_description'] ?? '', $post_id), | |
| 274 | 283 | 'og_image' => $meta['rank_math_facebook_image'] ?? '', |
| 275 | - 'twitter_title' => $this->convert_template_variables($meta['rank_math_twitter_title'] ?? '', $post_id), | |
| 276 | - 'twitter_description' => $this->convert_template_variables($meta['rank_math_twitter_description'] ?? '', $post_id), | |
| 284 | + 'twitter_title' => $this->convert_post_value($meta['rank_math_twitter_title'] ?? '', $post_id), | |
| 285 | + 'twitter_description' => $this->convert_post_value($meta['rank_math_twitter_description'] ?? '', $post_id), | |
| 277 | 286 | 'twitter_image' => $meta['rank_math_twitter_image'] ?? '', |
| 278 | 287 | 'primary_category' => (int) ($meta['rank_math_primary_category'] ?? 0), |
| 279 | 288 | 'schema_type' => $this->resolve_schema_type($meta), |
| 289 | + // WooCommerce product identifier. Rank Math keeps it in its | |
| 290 | + // own per-post meta; ThinkRank Pro reads the canonical field | |
| 291 | + // this maps to, so a migrating store keeps the identifier | |
| 292 | + // Google asks for instead of collecting a "missing | |
| 293 | + // identifier" warning on every product (#715). | |
| 294 | + 'product_identifier' => (string) ($meta['rank_math_gtin_code'] ?? ''), | |
| 280 | 295 | // Rank Math pillar content maps directly to ThinkRank pillar content. |
| 281 | 296 | 'pillar_content' => $this->normalize_pillar_content($meta['rank_math_pillar_content'] ?? ''), |
| 282 | 297 | ], |
| 283 | 298 | 'extended' => [ |
| @@ -334,16 +349,16 @@ | ||
| 334 | 349 | 'object_id' => $term_id, |
| 335 | 350 | 'object_type' => 'term', |
| 336 | 351 | 'source_plugin' => $this->plugin_slug, |
| 337 | 352 | 'data' => [ |
| 338 | - 'seo_title' => $this->convert_term_template_variables($meta['rank_math_title'] ?? '', $term_id), | |
| 339 | - 'meta_description' => $this->convert_term_template_variables($meta['rank_math_description'] ?? '', $term_id), | |
| 353 | + 'seo_title' => $this->convert_term_value($meta['rank_math_title'] ?? '', $term_id), | |
| 354 | + 'meta_description' => $this->convert_term_value($meta['rank_math_description'] ?? '', $term_id), | |
| 340 | 355 | 'focus_keyword' => $primary_keyword, |
| 341 | 356 | 'canonical_url' => $meta['rank_math_canonical_url'] ?? '', |
| 342 | 357 | 'noindex' => $robots['noindex'], |
| 343 | 358 | 'nofollow' => $robots['nofollow'], |
| 344 | - 'og_title' => $this->convert_term_template_variables($meta['rank_math_facebook_title'] ?? '', $term_id), | |
| 345 | - 'og_description' => $this->convert_term_template_variables($meta['rank_math_facebook_description'] ?? '', $term_id), | |
| 359 | + 'og_title' => $this->convert_term_value($meta['rank_math_facebook_title'] ?? '', $term_id), | |
| 360 | + 'og_description' => $this->convert_term_value($meta['rank_math_facebook_description'] ?? '', $term_id), | |
| 346 | 361 | ], |
| 347 | 362 | 'extended' => [ |
| 348 | 363 | 'og_image' => $meta['rank_math_facebook_image'] ?? '', |
| 349 | 364 | 'twitter_title' => $meta['rank_math_twitter_title'] ?? '', |
| @@ -750,8 +765,40 @@ | ||
| 750 | 765 | |
| 751 | 766 | /** |
| 752 | 767 | * {@inheritDoc} |
| 753 | 768 | * |
| 769 | + * `content_blocks` records carry post ids only. The blocks themselves stay | |
| 770 | + * in `post_content` and are rewritten in place at migrate time — copying | |
| 771 | + * the content into the snapshot would mean holding a second copy of every | |
| 772 | + * FAQ page in `wp_options`, which is where snapshots live. | |
| 773 | + */ | |
| 774 | + protected function export_custom_type_page(string $type, int $page): array { | |
| 775 | + if (Block_Converter::TYPE !== $type) { | |
| 776 | + return parent::export_custom_type_page($type, $page); | |
| 777 | + } | |
| 778 | + | |
| 779 | + $post_ids = Block_Converter::get_post_ids($page, $this->chunk_size); | |
| 780 | + | |
| 781 | + // export_chunk() decides whether another page follows from the number | |
| 782 | + // of rows the query returned, so report it even when nothing is emitted. | |
| 783 | + $this->last_page_row_count = count($post_ids); | |
| 784 | + | |
| 785 | + $records = []; | |
| 786 | + foreach ($post_ids as $post_id) { | |
| 787 | + $records[] = [ | |
| 788 | + 'object_id' => $post_id, | |
| 789 | + 'object_type' => 'post', | |
| 790 | + 'source_plugin' => $this->plugin_slug, | |
| 791 | + 'data' => ['post_id' => $post_id], | |
| 792 | + ]; | |
| 793 | + } | |
| 794 | + | |
| 795 | + return $records; | |
| 796 | + } | |
| 797 | + | |
| 798 | + /** | |
| 799 | + * {@inheritDoc} | |
| 800 | + * | |
| 754 | 801 | * Rank Math's 404 monitor keeps one row per URI in `rank_math_404_logs`, |
| 755 | 802 | * which lines up with ThinkRank Pro's `thinkrank_404_logs`. |
| 756 | 803 | */ |
| 757 | 804 | protected function export_404_logs_page(int $page): array { |
| @@ -949,8 +996,68 @@ | ||
| 949 | 996 | return self::MODERN_SCHEMA_TYPE_MAP[$key] ?? ''; |
| 950 | 997 | } |
| 951 | 998 | |
| 952 | 999 | /** |
| 1000 | + * Rank Math per-post tokens ThinkRank resolves per request (#886): kept | |
| 1001 | + * as tags rather than frozen into text. Same vocabulary as | |
| 1002 | + * convert_template_tokens(). | |
| 1003 | + */ | |
| 1004 | + private const POST_TOKENS = [ | |
| 1005 | + '%title%' => '%title%', | |
| 1006 | + '%sitename%' => '%sitename%', | |
| 1007 | + '%sep%' => '%sep%', | |
| 1008 | + '%excerpt%' => '%excerpt%', | |
| 1009 | + '%date%' => '%date%', | |
| 1010 | + '%modified%' => '%modified%', | |
| 1011 | + '%name%' => '%author%', | |
| 1012 | + '%category%' => '%category%', | |
| 1013 | + '%wc_price%' => '%price%', | |
| 1014 | + '%wc_sku%' => '%sku%', | |
| 1015 | + '%wc_shortdesc%' => '%short_description%', | |
| 1016 | + '%wc_brand%' => '%brand%', | |
| 1017 | + ]; | |
| 1018 | + | |
| 1019 | + /** | |
| 1020 | + * Rank Math per-term tokens ThinkRank resolves on a term archive. | |
| 1021 | + */ | |
| 1022 | + private const TERM_TOKENS = [ | |
| 1023 | + '%term%' => '%term%', | |
| 1024 | + '%term_description%' => '%excerpt%', | |
| 1025 | + '%sitename%' => '%sitename%', | |
| 1026 | + '%sep%' => '%sep%', | |
| 1027 | + ]; | |
| 1028 | + | |
| 1029 | + /** | |
| 1030 | + * A per-post Rank Math value: mapped tokens as ThinkRank tags, the rest literal. | |
| 1031 | + * | |
| 1032 | + * @param mixed $value Raw Rank Math value | |
| 1033 | + * @param int $post_id Post ID | |
| 1034 | + * @return string | |
| 1035 | + */ | |
| 1036 | + private function convert_post_value($value, int $post_id): string { | |
| 1037 | + return $this->tokenize_object_template( | |
| 1038 | + $this->stringify_template_value($value), | |
| 1039 | + self::literal_token_patterns(self::POST_TOKENS), | |
| 1040 | + fn(string $rest): string => $this->convert_template_variables($rest, $post_id) | |
| 1041 | + ); | |
| 1042 | + } | |
| 1043 | + | |
| 1044 | + /** | |
| 1045 | + * A per-term Rank Math value: mapped tokens as ThinkRank tags, the rest literal. | |
| 1046 | + * | |
| 1047 | + * @param mixed $value Raw Rank Math value | |
| 1048 | + * @param int $term_id Term ID | |
| 1049 | + * @return string | |
| 1050 | + */ | |
| 1051 | + private function convert_term_value($value, int $term_id): string { | |
| 1052 | + return $this->tokenize_object_template( | |
| 1053 | + $this->stringify_template_value($value), | |
| 1054 | + self::literal_token_patterns(self::TERM_TOKENS), | |
| 1055 | + fn(string $rest): string => $this->convert_term_template_variables($rest, $term_id) | |
| 1056 | + ); | |
| 1057 | + } | |
| 1058 | + | |
| 1059 | + /** | |
| 953 | 1060 | * Resolve a Rank Math TERM title/description value, replacing the |
| 954 | 1061 | * term-context tokens (%term%, %term_description%) Rank Math uses for term |
| 955 | 1062 | * archives before delegating to the shared variable resolver. Without this |
| 956 | 1063 | * the term name is stripped and titles render as "Archives - Site". |
| @@ -1002,9 +1109,12 @@ | ||
| 1002 | 1109 | if ($post_id) { |
| 1003 | 1110 | $post = get_post($post_id); |
| 1004 | 1111 | if ($post) { |
| 1005 | 1112 | $replacements['%title%'] = $post->post_title; |
| 1006 | - $replacements['%excerpt%'] = wp_trim_words($post->post_excerpt ?: wp_trim_words(wp_strip_all_tags($post->post_content), 55), 55); | |
| 1113 | + $replacements['%excerpt%'] = \ThinkRank\Core\Seo_Text::trim_words( | |
| 1114 | + $post->post_excerpt ?: \ThinkRank\Core\Seo_Text::trim_words(wp_strip_all_tags($post->post_content), 55), | |
| 1115 | + 55 | |
| 1116 | + ); | |
| 1007 | 1117 | $replacements['%date%'] = get_the_date('', $post); |
| 1008 | 1118 | $replacements['%modified%'] = get_the_modified_date('', $post); |
| 1009 | 1119 | $replacements['%id%'] = (string) $post_id; |
| 1010 | 1120 | $replacements['%name%'] = get_the_author_meta('display_name', (int) $post->post_author); |
| @@ -1057,13 +1167,25 @@ | ||
| 1057 | 1167 | |
| 1058 | 1168 | // Rank Math token => ThinkRank Global SEO token (structure preserved). |
| 1059 | 1169 | $token_map = [ |
| 1060 | 1170 | '%name%' => '%author%', // Rank Math author display name token |
| 1171 | + // WooCommerce product variables. Rank Math lets a product title or | |
| 1172 | + // description carry the price, SKU and stock status; every one of | |
| 1173 | + // them used to be stripped by the pass below, so "Buy %title% for | |
| 1174 | + // %wc_price%" imported as "Buy %title% for" (#715). | |
| 1175 | + '%wc_price%' => '%price%', | |
| 1176 | + '%wc_sku%' => '%sku%', | |
| 1177 | + '%wc_shortdesc%' => '%short_description%', | |
| 1178 | + '%wc_brand%' => '%brand%', | |
| 1061 | 1179 | ]; |
| 1062 | 1180 | $template = str_replace(array_keys($token_map), array_values($token_map), $template); |
| 1063 | 1181 | |
| 1064 | 1182 | // Tokens ThinkRank's Global SEO engine resolves natively — keep as-is. |
| 1065 | - $supported = ['%title%', '%sitename%', '%sep%', '%excerpt%', '%date%', '%modified%', '%author%', '%category%']; | |
| 1183 | + $supported = [ | |
| 1184 | + '%title%', '%sitename%', '%sep%', '%excerpt%', '%date%', '%modified%', '%author%', '%category%', | |
| 1185 | + // Resolved by Pattern_Resolver on a product, empty elsewhere. | |
| 1186 | + '%price%', '%sale_price%', '%sku%', '%stock_status%', '%short_description%', '%brand%', | |
| 1187 | + ]; | |
| 1066 | 1188 | |
| 1067 | 1189 | // Strip any token ThinkRank cannot resolve so it does not render literally. |
| 1068 | 1190 | $template = preg_replace_callback( |
| 1069 | 1191 | '/%[a-z0-9_]+%/i', |