PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
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
yatra / app / Managers / SEOManager.php

SEOManager.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Managers/SEOManager.php

292 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Managers;
6
7 use Yatra\Services\SEOService;
8 use Yatra\Services\SettingsService;
9
10 /**
11 * SEO Manager
12 *
13 * Production-ready main entry point for SEO functionality
14 * Routes SEO requests to appropriate services
15 * Handles WordPress hooks and filters with error handling
16 *
17 * @package Yatra\Managers
18 */
19 class SEOManager
20 {
21 /**
22 * Initialize SEO functionality with error handling
23 */
24 public static function init(): void
25 {
26 try {
27 // Only initialize on front-end
28 if (!is_admin()) {
29 add_action('wp_head', [self::class, 'outputSEOMetaTags'], 1);
30 add_filter('document_title', [self::class, 'filterDocumentTitle'], 10);
31 }
32 } catch (\Exception $e) {
33 error_log('Yatra SEO Manager init Error: ' . $e->getMessage());
34 }
35 }
36
37 /**
38 * Output SEO meta tags based on current page with error handling
39 */
40 public static function outputSEOMetaTags(): void
41 {
42 try {
43 $pageType = self::getCurrentPageType();
44 $pageObject = self::getCurrentPageObject();
45
46 // For archive pages, we don't need a page object
47 if ($pageType) {
48 $seoService = SEOService::forPage($pageType, $pageObject);
49 $seoService->generateMetaTags();
50 }
51 } catch (\Exception $e) {
52 error_log('Yatra SEO Manager outputSEOMetaTags Error: ' . $e->getMessage());
53
54 // Fail silently to avoid breaking page rendering
55 }
56 }
57
58 /**
59 * Filter document title with error handling and validation
60 *
61 * @param string $title The original title
62 * @return string The filtered title
63 */
64 public static function filterDocumentTitle(string $title): string
65 {
66 try {
67 $pageType = self::getCurrentPageType();
68 $pageObject = self::getCurrentPageObject();
69
70 if ($pageType === '') {
71 return $title;
72 }
73
74 $allowsNullObject = \in_array(
75 $pageType,
76 [
77 SEOService::PAGE_TYPE_TRIP_ARCHIVE,
78 SEOService::PAGE_TYPE_DESTINATION_LISTING,
79 SEOService::PAGE_TYPE_ACTIVITY_LISTING,
80 SEOService::PAGE_TYPE_CATEGORY_LISTING,
81 ],
82 true
83 );
84 if (!$allowsNullObject && !$pageObject) {
85 return $title;
86 }
87
88 $seoService = SEOService::forPage($pageType, $pageObject);
89 $seoTitle = $seoService->getTitle();
90
91 if (!empty($seoTitle) && is_string($seoTitle)) {
92 return wp_strip_all_tags($seoTitle);
93 }
94
95 return $title;
96 } catch (\Exception $e) {
97 error_log('Yatra SEO Manager filterDocumentTitle Error: ' . $e->getMessage());
98
99 // Return original title on error
100 return $title;
101 }
102 }
103
104 /**
105 * Get current page type with validation
106 *
107 * @return string The current page type
108 */
109 private static function getCurrentPageType(): string
110 {
111 try {
112 global $yatra_taxonomy_data;
113
114 // Check if we're on trip archive page
115 if (self::isTripArchivePage()) {
116 return SEOService::PAGE_TYPE_TRIP_ARCHIVE;
117 }
118
119 $listingType = self::getTaxonomyListingPageType();
120 if ($listingType !== '') {
121 return $listingType;
122 }
123
124 // Check taxonomy pages with validation
125 if (isset($yatra_taxonomy_data) && !empty($yatra_taxonomy_data) && is_object($yatra_taxonomy_data)) {
126 $type = $yatra_taxonomy_data->type ?? '';
127
128 switch ($type) {
129 case 'destination':
130 return SEOService::PAGE_TYPE_DESTINATION;
131 case 'activity':
132 return SEOService::PAGE_TYPE_ACTIVITY;
133 case 'category':
134 return SEOService::PAGE_TYPE_CATEGORY;
135 }
136 }
137
138 // Check single trip page
139 if (self::isSingleTripPage()) {
140 return SEOService::PAGE_TYPE_TRIP;
141 }
142
143 return '';
144 } catch (\Exception $e) {
145 error_log('Yatra SEO Manager getCurrentPageType Error: ' . $e->getMessage());
146 return '';
147 }
148 }
149
150 /**
151 * Get current page object with validation
152 *
153 * @return mixed The current page object or null
154 */
155 private static function getCurrentPageObject()
156 {
157 try {
158 global $yatra_taxonomy_data, $trip;
159
160 // Return taxonomy data for taxonomy pages with validation
161 if (isset($yatra_taxonomy_data) && !empty($yatra_taxonomy_data) && is_object($yatra_taxonomy_data)) {
162 return $yatra_taxonomy_data;
163 }
164
165 // Return trip object for single trip pages with validation
166 if (isset($trip) && !empty($trip) && is_object($trip)) {
167 return $trip;
168 }
169
170 return null;
171 } catch (\Exception $e) {
172 error_log('Yatra SEO Manager getCurrentPageObject Error: ' . $e->getMessage());
173 return null;
174 }
175 }
176
177 /**
178 * Check if current page is trip archive with validation
179 *
180 * @return bool True if trip archive page
181 */
182 private static function isTripArchivePage(): bool
183 {
184 try {
185 $current_url = $_SERVER['REQUEST_URI'] ?? '';
186 $current_path = parse_url($current_url, PHP_URL_PATH) ?? '';
187 $trip_base = SettingsService::getTripBase();
188
189 // Validate inputs
190 if (empty($current_path) || empty($trip_base)) {
191 return false;
192 }
193
194 // Check for both /trip/ and /trip patterns
195 $pattern1 = '/' . trim($trip_base, '/') . '/';
196 $pattern2 = '/' . trim($trip_base, '/');
197
198 $isArchivePage = (strpos($current_path, $pattern1) !== false || $current_path === $pattern2);
199
200 return $isArchivePage && !self::isSingleTripPage();
201 } catch (\Exception $e) {
202 error_log('Yatra SEO Manager isTripArchivePage Error: ' . $e->getMessage());
203 return false;
204 }
205 }
206
207 /**
208 * Check if current page is single trip
209 */
210 private static function isSingleTripPage(): bool
211 {
212 global $trip;
213 return !empty($trip) && !empty($trip->id);
214 }
215
216 /**
217 * Browse-all taxonomy listing (/destination/, /activity/, …) including paged and plain ?yatra_page=.
218 */
219 private static function getTaxonomyListingPageType(): string
220 {
221 try {
222 if (self::matchesTaxonomyListingRoot('destination')) {
223 return SEOService::PAGE_TYPE_DESTINATION_LISTING;
224 }
225 if (self::matchesTaxonomyListingRoot('activity')) {
226 return SEOService::PAGE_TYPE_ACTIVITY_LISTING;
227 }
228 if (self::matchesTaxonomyListingRoot('category')) {
229 return SEOService::PAGE_TYPE_CATEGORY_LISTING;
230 }
231 } catch (\Exception $e) {
232 error_log('Yatra SEOManager getTaxonomyListingPageType Error: ' . $e->getMessage());
233 }
234
235 return '';
236 }
237
238 /**
239 * True when the request is the index listing for a taxonomy (not a single term).
240 */
241 private static function matchesTaxonomyListingRoot(string $type): bool
242 {
243 $bases = [
244 'destination' => SettingsService::getString('destination_base', 'destination'),
245 'activity' => SettingsService::getString('activity_base', 'activity'),
246 'category' => SettingsService::getString('trip_category_base', 'trip-category'),
247 ];
248 $legacyKeys = [
249 'destination' => 'yatra_destination_slug',
250 'activity' => 'yatra_activity_slug',
251 'category' => 'yatra_category_slug',
252 ];
253 if (!isset($bases[$type], $legacyKeys[$type])) {
254 return false;
255 }
256
257 $base = trim((string) $bases[$type], '/');
258 if ($base === '') {
259 return false;
260 }
261
262 $path = (string) (parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH) ?? '');
263 $path = trim($path, '/');
264 if ($path === $base) {
265 return true;
266 }
267 if (preg_match('/^' . preg_quote($base, '/') . '\/page\/[0-9]+\/?$/', $path)) {
268 return true;
269 }
270
271 $yp = isset($_GET['yatra_page']) ? sanitize_text_field(wp_unslash((string) $_GET['yatra_page'])) : '';
272 if ($yp !== '' && $yp === $base) {
273 $baseKey = preg_replace('/[^a-z0-9_-]/i', '', $base) ?: '';
274 $slug = '';
275 if ($baseKey !== '') {
276 $fromBase = $_GET[$baseKey] ?? null;
277 if (is_string($fromBase)) {
278 $slug = sanitize_title(wp_unslash($fromBase));
279 }
280 }
281 if ($slug === '') {
282 $legacy = $_GET[$legacyKeys[$type]] ?? '';
283 $slug = is_string($legacy) ? sanitize_title(wp_unslash($legacy)) : '';
284 }
285
286 return $slug === '';
287 }
288
289 return false;
290 }
291 }
292