PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.11.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.11.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 trunk 1.0.0 All 52 releases
← All changes | includes/admin/importers/class-yoast-exporter.php +64 -1 2.2.0 → 2.11.0 View file →
@@ -155,8 +155,9 @@
155 155 'twitter_title' => $this->convert_template_variables($meta['_yoast_wpseo_twitter-title'] ?? '', $post_id),
156 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),
159 160 'schema_type' => $meta['_yoast_wpseo_schema_page_type'] ?? '',
160 161 // Yoast "cornerstone content" maps to ThinkRank pillar content.
161 162 'pillar_content' => (int) ($meta['_yoast_wpseo_is_cornerstone'] ?? 0),
162 163 ],
@@ -479,9 +480,12 @@
479 480 if ($post_id) {
480 481 $post = get_post($post_id);
481 482 if ($post) {
482 483 $replacements['%%title%%'] = $post->post_title;
483 - $replacements['%%excerpt%%'] = wp_trim_words($post->post_excerpt ?: wp_trim_words(wp_strip_all_tags($post->post_content), 55), 55);
484 + $replacements['%%excerpt%%'] = \ThinkRank\Core\Seo_Text::trim_words(
485 + $post->post_excerpt ?: \ThinkRank\Core\Seo_Text::trim_words(wp_strip_all_tags($post->post_content), 55),
486 + 55
487 + );
484 488 $replacements['%%date%%'] = get_the_date('', $post);
485 489 $replacements['%%modified%%'] = get_the_modified_date('', $post);
486 490 $replacements['%%id%%'] = (string) $post_id;
487 491 $replacements['%%name%%'] = get_the_author_meta('display_name', (int) $post->post_author);
@@ -546,8 +550,15 @@
546 550 '%%modified%%' => '%modified%',
547 551 '%%name%%' => '%author%',
548 552 '%%category%%' => '%category%',
549 553 '%%primary_category%%' => '%category%',
554 + // WooCommerce SEO product variables. Stripped rather than mapped
555 + // until #715, so a product description template imported as "Buy
556 + // %title% for" with the price and stock gone.
557 + '%%wc_price%%' => '%price%',
558 + '%%wc_sku%%' => '%sku%',
559 + '%%wc_shortdesc%%' => '%short_description%',
560 + '%%wc_brand%%' => '%brand%',
550 561 ];
551 562
552 563 $template = str_replace(array_keys($map), array_values($map), $template);
553 564
@@ -785,8 +796,22 @@
785 796 // Math tokens wrapped in Yoast's double-percent syntax — tokens Yoast
786 797 // itself cannot resolve either. Map them rather than stripping them,
787 798 // or the format silently loses its search term / term name.
788 799 '%%search_query%%' => '%search_term%',
800 + // NOT the WooCommerce product variables, deliberately. This
801 + // converter writes Site Identity title formats, and those are
802 + // resolved by SEO_Manager::process_title_template() against its own
803 + // vocabulary — %site_title%, %separator%, %search_term% and the
804 + // rest — which has no %price%/%sku%/%brand% and, unlike
805 + // Pattern_Resolver, does not strip what it cannot resolve. Mapping
806 + // them here published "Search pennant - %price% - Media Wipe" in a
807 + // live <title>: a silent loss traded for the literal leak #715 is
808 + // about. The formats this feeds are the homepage, search, archive,
809 + // author, category and tag titles, where there is no one product
810 + // for a price to belong to either way, so the strip below is the
811 + // right answer. The per-post converter keeps them, because
812 + // Pattern_Resolver does resolve them: see
813 + // convert_template_pattern().
789 814 ];
790 815 if ($context_token !== '') {
791 816 $map['%%title%%'] = $context_token;
792 817 $map['%%term_title%%'] = $context_token;
@@ -989,6 +1014,44 @@
989 1014 }
990 1015 }
991 1016
992 1017 return $settings;
1018 + }
1019 +
1020 + /**
1021 + * A product's identifier from Yoast WooCommerce SEO.
1022 + *
1023 + * Yoast stores every identifier type in one serialised map keyed by type
1024 + * (`gtin8`, `gtin12`, `gtin13`, `gtin14`, `isbn`, `mpn`) rather than in a
1025 + * field per type. ThinkRank Pro holds a single identifier, so the first
1026 + * non-empty one wins, in the order Google prefers: a GTIN identifies the
1027 + * product itself, an MPN only identifies it within one manufacturer, and
1028 + * an ISBN applies to books alone.
1029 + *
1030 + * @since 2.10.1
1031 + *
1032 + * @param array $meta Post meta for the product.
1033 + * @return string Identifier, or '' when the product has none.
1034 + */
1035 + private function extract_product_identifier(array $meta): string {
1036 + $raw = $meta['wpseo_global_identifier_values'] ?? '';
1037 +
1038 + if (is_string($raw) && '' !== $raw) {
1039 + $decoded = maybe_unserialize($raw);
1040 + $raw = is_array($decoded) ? $decoded : [];
1041 + }
1042 +
1043 + if (!is_array($raw) || empty($raw)) {
1044 + return '';
1045 + }
1046 +
1047 + foreach (['gtin13', 'gtin14', 'gtin12', 'gtin8', 'mpn', 'isbn'] as $type) {
1048 + $value = trim((string) ($raw[$type] ?? ''));
1049 +
1050 + if ('' !== $value) {
1051 + return $value;
1052 + }
1053 + }
1054 +
1055 + return '';
993 1056 }
994 1057 }