← All changes
|
includes/admin/importers/class-yoast-exporter.php
+10
-133
trunk
→
2.10.0
View file →
| @@ -128,10 +128,10 @@ | ||
| 128 | 128 | 'object_id' => $post_id, |
| 129 | 129 | 'object_type' => 'post', |
| 130 | 130 | 'source_plugin' => $this->plugin_slug, |
| 131 | 131 | 'data' => [ |
| 132 | - 'seo_title' => $this->convert_post_value($meta['_yoast_wpseo_title'] ?? '', $post_id), | |
| 133 | - 'meta_description' => $this->convert_post_value($meta['_yoast_wpseo_metadesc'] ?? '', $post_id), | |
| 132 | + 'seo_title' => $this->convert_template_variables($meta['_yoast_wpseo_title'] ?? '', $post_id), | |
| 133 | + 'meta_description' => $this->convert_template_variables($meta['_yoast_wpseo_metadesc'] ?? '', $post_id), | |
| 134 | 134 | 'focus_keyword' => $meta['_yoast_wpseo_focuskw'] ?? '', |
| 135 | 135 | // Full keyword list (primary + Yoast Premium's additional |
| 136 | 136 | // keyphrases) for ThinkRank's keyword array — without this |
| 137 | 137 | // the migrator falls back to the single focus_keyword and |
| @@ -148,16 +148,15 @@ | ||
| 148 | 148 | 'nofollow' => $robots['nofollow'], |
| 149 | 149 | 'noarchive' => $advanced_robots['noarchive'], |
| 150 | 150 | 'noimageindex' => $advanced_robots['noimageindex'], |
| 151 | 151 | 'nosnippet' => $advanced_robots['nosnippet'], |
| 152 | - 'og_title' => $this->convert_post_value($meta['_yoast_wpseo_opengraph-title'] ?? '', $post_id), | |
| 153 | - 'og_description' => $this->convert_post_value($meta['_yoast_wpseo_opengraph-description'] ?? '', $post_id), | |
| 152 | + 'og_title' => $this->convert_template_variables($meta['_yoast_wpseo_opengraph-title'] ?? '', $post_id), | |
| 153 | + 'og_description' => $this->convert_template_variables($meta['_yoast_wpseo_opengraph-description'] ?? '', $post_id), | |
| 154 | 154 | 'og_image' => $meta['_yoast_wpseo_opengraph-image'] ?? '', |
| 155 | - 'twitter_title' => $this->convert_post_value($meta['_yoast_wpseo_twitter-title'] ?? '', $post_id), | |
| 156 | - 'twitter_description' => $this->convert_post_value($meta['_yoast_wpseo_twitter-description'] ?? '', $post_id), | |
| 155 | + 'twitter_title' => $this->convert_template_variables($meta['_yoast_wpseo_twitter-title'] ?? '', $post_id), | |
| 156 | + 'twitter_description' => $this->convert_template_variables($meta['_yoast_wpseo_twitter-description'] ?? '', $post_id), | |
| 157 | 157 | 'twitter_image' => $meta['_yoast_wpseo_twitter-image'] ?? '', |
| 158 | 158 | 'primary_category' => (int) ($meta['_yoast_wpseo_primary_category'] ?? 0), |
| 159 | - 'product_identifier' => $this->extract_product_identifier($meta), | |
| 160 | 159 | 'schema_type' => $meta['_yoast_wpseo_schema_page_type'] ?? '', |
| 161 | 160 | // Yoast "cornerstone content" maps to ThinkRank pillar content. |
| 162 | 161 | 'pillar_content' => (int) ($meta['_yoast_wpseo_is_cornerstone'] ?? 0), |
| 163 | 162 | ], |
| @@ -209,16 +208,16 @@ | ||
| 209 | 208 | 'object_id' => $term_id, |
| 210 | 209 | 'object_type' => 'term', |
| 211 | 210 | 'source_plugin' => $this->plugin_slug, |
| 212 | 211 | 'data' => [ |
| 213 | - 'seo_title' => $this->convert_term_value($meta['wpseo_title'] ?? ''), | |
| 214 | - 'meta_description' => $this->convert_term_value($meta['wpseo_desc'] ?? ''), | |
| 212 | + 'seo_title' => $this->convert_template_variables($meta['wpseo_title'] ?? ''), | |
| 213 | + 'meta_description' => $this->convert_template_variables($meta['wpseo_desc'] ?? ''), | |
| 215 | 214 | 'focus_keyword' => $meta['wpseo_focuskw'] ?? '', |
| 216 | 215 | 'canonical_url' => $meta['wpseo_canonical'] ?? '', |
| 217 | 216 | 'noindex' => $noindex, |
| 218 | 217 | 'nofollow' => 0, |
| 219 | - 'og_title' => $this->convert_term_value($meta['wpseo_opengraph-title'] ?? ''), | |
| 220 | - 'og_description' => $this->convert_term_value($meta['wpseo_opengraph-description'] ?? ''), | |
| 218 | + 'og_title' => $this->convert_template_variables($meta['wpseo_opengraph-title'] ?? ''), | |
| 219 | + 'og_description' => $this->convert_template_variables($meta['wpseo_opengraph-description'] ?? ''), | |
| 221 | 220 | ], |
| 222 | 221 | 'extended' => [ |
| 223 | 222 | 'breadcrumb_title' => $meta['wpseo_bctitle'] ?? '', |
| 224 | 223 | 'og_image' => $meta['wpseo_opengraph-image'] ?? '', |
| @@ -514,71 +513,8 @@ | ||
| 514 | 513 | return trim($value); |
| 515 | 514 | } |
| 516 | 515 | |
| 517 | 516 | /** |
| 518 | - * Yoast per-post tokens ThinkRank resolves per request (#886): kept as | |
| 519 | - * tags rather than frozen into text. Mirrors convert_template_pattern(). | |
| 520 | - */ | |
| 521 | - private const POST_TOKENS = [ | |
| 522 | - '%%title%%' => '%title%', | |
| 523 | - '%%sitename%%' => '%sitename%', | |
| 524 | - '%%sep%%' => '%sep%', | |
| 525 | - '%%excerpt%%' => '%excerpt%', | |
| 526 | - '%%date%%' => '%date%', | |
| 527 | - '%%modified%%' => '%modified%', | |
| 528 | - '%%name%%' => '%author%', | |
| 529 | - '%%category%%' => '%category%', | |
| 530 | - '%%primary_category%%' => '%category%', | |
| 531 | - '%%wc_price%%' => '%price%', | |
| 532 | - '%%wc_sku%%' => '%sku%', | |
| 533 | - '%%wc_shortdesc%%' => '%short_description%', | |
| 534 | - '%%wc_brand%%' => '%brand%', | |
| 535 | - ]; | |
| 536 | - | |
| 537 | - /** | |
| 538 | - * Yoast per-term tokens ThinkRank resolves on a term archive. The term's | |
| 539 | - * own name and description used to be stripped outright, since the | |
| 540 | - * literal resolver only knows post context. | |
| 541 | - */ | |
| 542 | - private const TERM_TOKENS = [ | |
| 543 | - '%%term_title%%' => '%term%', | |
| 544 | - '%%term_description%%' => '%excerpt%', | |
| 545 | - '%%category_description%%' => '%excerpt%', | |
| 546 | - '%%tag_description%%' => '%excerpt%', | |
| 547 | - '%%sitename%%' => '%sitename%', | |
| 548 | - '%%sep%%' => '%sep%', | |
| 549 | - ]; | |
| 550 | - | |
| 551 | - /** | |
| 552 | - * A per-post Yoast value: mapped tokens as ThinkRank tags, the rest literal. | |
| 553 | - * | |
| 554 | - * @param mixed $value Raw Yoast value | |
| 555 | - * @param int $post_id Post ID | |
| 556 | - * @return string | |
| 557 | - */ | |
| 558 | - private function convert_post_value($value, int $post_id): string { | |
| 559 | - return $this->tokenize_object_template( | |
| 560 | - $this->stringify_template_value($value), | |
| 561 | - self::literal_token_patterns(self::POST_TOKENS), | |
| 562 | - fn(string $rest): string => $this->convert_template_variables($rest, $post_id) | |
| 563 | - ); | |
| 564 | - } | |
| 565 | - | |
| 566 | - /** | |
| 567 | - * A per-term Yoast value: mapped tokens as ThinkRank tags, the rest literal. | |
| 568 | - * | |
| 569 | - * @param mixed $value Raw Yoast value | |
| 570 | - * @return string | |
| 571 | - */ | |
| 572 | - private function convert_term_value($value): string { | |
| 573 | - return $this->tokenize_object_template( | |
| 574 | - $this->stringify_template_value($value), | |
| 575 | - self::literal_token_patterns(self::TERM_TOKENS), | |
| 576 | - fn(string $rest): string => $this->convert_template_variables($rest) | |
| 577 | - ); | |
| 578 | - } | |
| 579 | - | |
| 580 | - /** | |
| 581 | 517 | * Translate a Yoast title/description *template* into ThinkRank pattern syntax. |
| 582 | 518 | * |
| 583 | 519 | * Unlike convert_template_variables() — which resolves a per-post value to a |
| 584 | 520 | * literal string — post-type and taxonomy templates must stay templates so the |
| @@ -613,15 +549,8 @@ | ||
| 613 | 549 | '%%modified%%' => '%modified%', |
| 614 | 550 | '%%name%%' => '%author%', |
| 615 | 551 | '%%category%%' => '%category%', |
| 616 | 552 | '%%primary_category%%' => '%category%', |
| 617 | - // WooCommerce SEO product variables. Stripped rather than mapped | |
| 618 | - // until #715, so a product description template imported as "Buy | |
| 619 | - // %title% for" with the price and stock gone. | |
| 620 | - '%%wc_price%%' => '%price%', | |
| 621 | - '%%wc_sku%%' => '%sku%', | |
| 622 | - '%%wc_shortdesc%%' => '%short_description%', | |
| 623 | - '%%wc_brand%%' => '%brand%', | |
| 624 | 553 | ]; |
| 625 | 554 | |
| 626 | 555 | $template = str_replace(array_keys($map), array_values($map), $template); |
| 627 | 556 | |
| @@ -859,22 +788,8 @@ | ||
| 859 | 788 | // Math tokens wrapped in Yoast's double-percent syntax — tokens Yoast |
| 860 | 789 | // itself cannot resolve either. Map them rather than stripping them, |
| 861 | 790 | // or the format silently loses its search term / term name. |
| 862 | 791 | '%%search_query%%' => '%search_term%', |
| 863 | - // NOT the WooCommerce product variables, deliberately. This | |
| 864 | - // converter writes Site Identity title formats, and those are | |
| 865 | - // resolved by SEO_Manager::process_title_template() against its own | |
| 866 | - // vocabulary — %site_title%, %separator%, %search_term% and the | |
| 867 | - // rest — which has no %price%/%sku%/%brand% and, unlike | |
| 868 | - // Pattern_Resolver, does not strip what it cannot resolve. Mapping | |
| 869 | - // them here published "Search pennant - %price% - Media Wipe" in a | |
| 870 | - // live <title>: a silent loss traded for the literal leak #715 is | |
| 871 | - // about. The formats this feeds are the homepage, search, archive, | |
| 872 | - // author, category and tag titles, where there is no one product | |
| 873 | - // for a price to belong to either way, so the strip below is the | |
| 874 | - // right answer. The per-post converter keeps them, because | |
| 875 | - // Pattern_Resolver does resolve them: see | |
| 876 | - // convert_template_pattern(). | |
| 877 | 792 | ]; |
| 878 | 793 | if ($context_token !== '') { |
| 879 | 794 | $map['%%title%%'] = $context_token; |
| 880 | 795 | $map['%%term_title%%'] = $context_token; |
| @@ -1077,44 +992,6 @@ | ||
| 1077 | 992 | } |
| 1078 | 993 | } |
| 1079 | 994 | |
| 1080 | 995 | return $settings; |
| 1081 | - } | |
| 1082 | - | |
| 1083 | - /** | |
| 1084 | - * A product's identifier from Yoast WooCommerce SEO. | |
| 1085 | - * | |
| 1086 | - * Yoast stores every identifier type in one serialised map keyed by type | |
| 1087 | - * (`gtin8`, `gtin12`, `gtin13`, `gtin14`, `isbn`, `mpn`) rather than in a | |
| 1088 | - * field per type. ThinkRank Pro holds a single identifier, so the first | |
| 1089 | - * non-empty one wins, in the order Google prefers: a GTIN identifies the | |
| 1090 | - * product itself, an MPN only identifies it within one manufacturer, and | |
| 1091 | - * an ISBN applies to books alone. | |
| 1092 | - * | |
| 1093 | - * @since 2.10.1 | |
| 1094 | - * | |
| 1095 | - * @param array $meta Post meta for the product. | |
| 1096 | - * @return string Identifier, or '' when the product has none. | |
| 1097 | - */ | |
| 1098 | - private function extract_product_identifier(array $meta): string { | |
| 1099 | - $raw = $meta['wpseo_global_identifier_values'] ?? ''; | |
| 1100 | - | |
| 1101 | - if (is_string($raw) && '' !== $raw) { | |
| 1102 | - $decoded = maybe_unserialize($raw); | |
| 1103 | - $raw = is_array($decoded) ? $decoded : []; | |
| 1104 | - } | |
| 1105 | - | |
| 1106 | - if (!is_array($raw) || empty($raw)) { | |
| 1107 | - return ''; | |
| 1108 | - } | |
| 1109 | - | |
| 1110 | - foreach (['gtin13', 'gtin14', 'gtin12', 'gtin8', 'mpn', 'isbn'] as $type) { | |
| 1111 | - $value = trim((string) ($raw[$type] ?? '')); | |
| 1112 | - | |
| 1113 | - if ('' !== $value) { | |
| 1114 | - return $value; | |
| 1115 | - } | |
| 1116 | - } | |
| 1117 | - | |
| 1118 | - return ''; | |
| 1119 | 996 | } |
| 1120 | 997 | } |