PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Services/SEOService.php +105 -4 3.0.11 → 3.0.16 View file →
@@ -630,8 +630,46 @@
630 630 return $raw !== '' ? $this->validateUrl($raw) : '';
631 631 }
632 632
633 633 /**
634 + * The page's language as WordPress reports it, reduced to what hreflang
635 + * and Open Graph accept: language + optional region (`de-DE`, `en-US`,
636 + * `ca`). Read at render time through get_locale()/the `locale` filter, so
637 + * a German install, WPML and Polylang per-page languages all resolve
638 + * correctly. WordPress variant locales such as `de_DE_formal` ("Deutsch
639 + * (Sie)") or `pt_PT_ao90` carry a third segment that is not a region —
640 + * Google ignores an hreflang like `de-DE-formal` and Facebook rejects
641 + * `de_DE_formal` — so only the first two segments are kept. Falls back to
642 + * `en-US` when WP has no locale, which keeps existing English sites
643 + * byte-identical.
644 + */
645 + private function languageTag(): string
646 + {
647 + $locale = (string) get_locale();
648 + if ($locale === '') {
649 + $locale = str_replace('-', '_', (string) get_bloginfo('language'));
650 + }
651 +
652 + $parts = preg_split('/[_-]/', $locale) ?: [];
653 + $language = strtolower((string) ($parts[0] ?? ''));
654 + if (!preg_match('/^[a-z]{2,3}$/', $language)) {
655 + return 'en-US';
656 + }
657 +
658 + $region = strtoupper((string) ($parts[1] ?? ''));
659 +
660 + return preg_match('/^[A-Z]{2}$/', $region) ? $language . '-' . $region : $language;
661 + }
662 +
663 + /**
664 + * Same language in Open Graph form (`de_DE`, `en_US`).
665 + */
666 + private function ogLocale(): string
667 + {
668 + return str_replace('-', '_', $this->languageTag());
669 + }
670 +
671 + /**
634 672 * Output basic meta tags
635 673 */
636 674 private function outputBasicMetaTags(): void
637 675 {
@@ -648,9 +686,9 @@
648 686 * Output Open Graph meta tags
649 687 */
650 688 private function outputOpenGraphTags(): void
651 689 {
652 - echo '<meta property="og:locale" content="en_US">' . "\n";
690 + echo '<meta property="og:locale" content="' . esc_attr($this->ogLocale()) . '">' . "\n";
653 691 echo '<meta property="og:site_name" content="' . esc_attr(get_bloginfo('name')) . '">' . "\n";
654 692 echo '<meta property="og:title" content="' . esc_attr($this->seoData['title']) . '">' . "\n";
655 693 echo '<meta property="og:description" content="' . esc_attr($this->truncateText($this->seoData['description'], 160)) . '">' . "\n";
656 694 echo '<meta property="og:type" content="' . esc_attr($this->seoData['type']) . '">' . "\n";
@@ -686,18 +724,81 @@
686 724 }
687 725 }
688 726
689 727 /**
728 + * The robots directive for this page.
729 + *
730 + * Normally "index, follow" with the usual snippet hints, exactly as before.
731 + * When the operator has both dropped this content type from the sitemap and
732 + * opted in to noindexing excluded types, it becomes "noindex, follow" —
733 + * because removing a URL from a sitemap does not stop Google indexing it,
734 + * and this page was previously asserting the opposite.
735 + *
736 + * "follow" is kept deliberately: the page should stop being indexed, but
737 + * links out of it (to trips that ARE published) should still be crawled.
738 + */
739 + private function robotsDirective(): string
740 + {
741 + $indexable = 'index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1';
742 +
743 + if (!SettingsService::isEnabled('sitemap_noindex_excluded')) {
744 + return $indexable;
745 + }
746 +
747 + $sitemapType = $this->sitemapTypeForPage();
748 + if ($sitemapType === '') {
749 + return $indexable;
750 + }
751 +
752 + if (!class_exists(\Yatra\Sitemap\SitemapService::class)) {
753 + return $indexable;
754 + }
755 +
756 + $sitemap = new \Yatra\Sitemap\SitemapService();
757 +
758 + return $sitemap->isTypeEnabled($sitemapType) ? $indexable : 'noindex, follow';
759 + }
760 +
761 + /**
762 + * Map this page onto the sitemap content type that governs it.
763 + *
764 + * The taxonomy listing pages are published by the sitemap's `archive` type
765 + * (the trip listing plus each taxonomy index), so they follow it.
766 + *
767 + * @return string '' when no sitemap type owns this page.
768 + */
769 + private function sitemapTypeForPage(): string
770 + {
771 + switch ($this->pageType) {
772 + case self::PAGE_TYPE_TRIP:
773 + return \Yatra\Sitemap\SitemapService::TYPE_TRIP;
774 + case self::PAGE_TYPE_DESTINATION:
775 + return \Yatra\Sitemap\SitemapService::TYPE_DESTINATION;
776 + case self::PAGE_TYPE_ACTIVITY:
777 + return \Yatra\Sitemap\SitemapService::TYPE_ACTIVITY;
778 + case self::PAGE_TYPE_CATEGORY:
779 + return \Yatra\Sitemap\SitemapService::TYPE_CATEGORY;
780 + case self::PAGE_TYPE_TRIP_ARCHIVE:
781 + case self::PAGE_TYPE_DESTINATION_LISTING:
782 + case self::PAGE_TYPE_ACTIVITY_LISTING:
783 + case self::PAGE_TYPE_CATEGORY_LISTING:
784 + return \Yatra\Sitemap\SitemapService::TYPE_ARCHIVE;
785 + default:
786 + return '';
787 + }
788 + }
789 +
790 + /**
690 791 * Output advanced meta tags
691 792 */
692 793 private function outputAdvancedMetaTags(): void
693 794 {
694 - echo '<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">' . "\n";
795 + echo '<meta name="robots" content="' . esc_attr($this->robotsDirective()) . '">' . "\n";
695 796 echo '<link rel="canonical" href="' . esc_url($this->seoData['url']) . '">' . "\n";
696 797 echo '<meta name="author" content="' . esc_attr($this->seoData['author']) . '">' . "\n";
697 798 echo '<meta name="publisher" content="' . esc_attr($this->seoData['publisher']) . '">' . "\n";
698 799 echo '<meta name="lastmod" content="' . esc_attr($this->seoData['modified_time']) . '">' . "\n";
699 - echo '<link rel="alternate" hreflang="en-US" href="' . esc_url($this->seoData['url']) . '">' . "\n";
800 + echo '<link rel="alternate" hreflang="' . esc_attr($this->languageTag()) . '" href="' . esc_url($this->seoData['url']) . '">' . "\n";
700 801 echo '<meta name="revisit-after" content="7 days">' . "\n";
701 802 echo '<meta name="distribution" content="global">' . "\n";
702 803 echo '<meta name="rating" content="general">' . "\n";
703 804 }
@@ -819,9 +920,9 @@
819 920 'url' => home_url()
820 921 ],
821 922 'dateModified' => $this->seoData['modified_time'],
822 923 'datePublished' => $this->seoData['published_time'],
823 - 'inLanguage' => 'en-US',
924 + 'inLanguage' => $this->languageTag(),
824 925 'isPartOf' => [
825 926 '@type' => 'WebSite',
826 927 'name' => get_bloginfo('name'),
827 928 'url' => home_url()