PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
← All changes | includes/integrations/class-multilingual-manager.php +48 -0 1.30.02.7.0 View file →
@@ -80,8 +80,9 @@
80 80 // Keep sitemap queries covering every language. Registered for admin
81 81 // and front end alike: the sitemap can be generated from either.
82 82 add_filter('thinkrank_sitemap_query_args', [$this, 'filter_sitemap_query_args']);
83 83 add_filter('thinkrank_sitemap_term_query_args', [$this, 'filter_sitemap_term_query_args']);
84 + add_filter('thinkrank_sitemap_post_permalink', [$this, 'localize_sitemap_permalink'], 10, 2);
84 85
85 86 if (!is_admin()) {
86 87 // WPML prints at wp_head priority 1 and Polylang at 10, so run
87 88 // after both: by then we know whether anything was printed.
@@ -598,8 +599,54 @@
598 599 *
599 600 * @param array<string, mixed> $args Query args.
600 601 * @return array<string, mixed>
601 602 */
603 + /**
604 + * Resolve a sitemap entry's permalink in the post's own language.
605 + *
606 + * The sitemap query runs with suppress_filters pinned (see
607 + * filter_sitemap_query_args) so every language's rows are fetched — but
608 + * that also strips WPML's chance to contextualise the permalink, and the
609 + * debounced cron rebuild runs with no language context at all. Each
610 + * translation therefore resolved to the default-language URL: N sitemap
611 + * entries with different lastmod and images sharing one identical <loc>
612 + * (#409).
613 + *
614 + * WPML's stateless conversion API fixes it per row: look up the post's
615 + * own language, then ask wpml_permalink for the URL in that language.
616 + * Both are documented WPML hooks and no-op safely when absent. Polylang
617 + * needs none of this — its permalink filtering rides post_link, which
618 + * get_permalink() applies regardless of suppress_filters — and
619 + * TranslatePress translates rendered output without duplicating posts.
620 + *
621 + * @since 2.0.1
622 + * @param string $url Permalink as WordPress resolved it.
623 + * @param \WP_Post $post Post the entry describes.
624 + * @return string
625 + */
626 + public function localize_sitemap_permalink(string $url, \WP_Post $post): string {
627 + if ($this->provider !== 'wpml') {
628 + return $url;
629 + }
630 +
631 + // WPML's own documented filters; the prefix rule does not apply to a
632 + // third-party hook we are consuming rather than declaring.
633 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
634 + $lang = apply_filters('wpml_element_language_code', null, [
635 + 'element_id' => $post->ID,
636 + 'element_type' => 'post_' . $post->post_type,
637 + ]);
638 +
639 + if (!is_string($lang) || $lang === '') {
640 + return $url;
641 + }
642 +
643 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
644 + $localized = apply_filters('wpml_permalink', $url, $lang, true);
645 +
646 + return is_string($localized) && $localized !== '' ? $localized : $url;
647 + }
648 +
602 649 public function filter_sitemap_query_args(array $args): array {
603 650 if ($this->provider === 'polylang') {
604 651 // Polylang filters through parse_query, which suppress_filters does
605 652 // not bypass. An empty language disables its language clause.
@@ -623,8 +670,9 @@
623 670 //
624 671 // Measured against WPML 4.9.5: setting suppress_filters => false cut the
625 672 // sitemap down to the active language, and a 'lang' => 'all' argument
626 673 // was ignored outright, so neither is used here.
674 + // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- Dropping it narrows the sitemap to the active language under WPML; see the note above.
627 675 $args['suppress_filters'] = true;
628 676
629 677 return $args;
630 678 }