PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 All 54 releases
← 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 }