PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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 2.0.10 2.0.11 All 82 releases
yatra / app / Managers / SEOManager.php

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

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