PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.14.2
Yatra – Travel Booking & Tour Operator Software v3.0.14.2
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 2.0.10 All 83 releases
yatra / app / Core / Routing / PlainPageMatcher.php

PlainPageMatcher.php in Yatra – Travel Booking & Tour Operator Software 3.0.14.2, at app/Core/Routing/PlainPageMatcher.php

314 lines 10.7 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\Core\Routing;
6
7 use Yatra\Services\SettingsService;
8
9 /**
10 * Resolves storefront routes when WordPress uses plain permalinks (?p=…).
11 * Primary discriminator: {@see query_var `yatra_page`} — value matches saved URL bases from settings
12 * (trip_base, booking_base, destination_base, activity_base, trip_category_base).
13 *
14 * Single trip (plain) may also be requested as ?{trip_base}=slug (same segment as the trip URL base)
15 * without yatra_page. Legacy: ?yatra_trip= / ?yatra_trip_slug=.
16 *
17 * Taxonomy singles use the same pattern: ?{destination_base}=slug, ?{activity_base}=slug,
18 * ?{trip_category_base}=slug. Legacy: ?yatra_page={base}&yatra_*_slug=slug still supported.
19 */
20 final class PlainPageMatcher
21 {
22 /**
23 * @return array<string, mixed>|null Route data compatible with {@see Router::handleRouteData()}
24 */
25 public static function match(): ?array
26 {
27 /**
28 * Override plain / query-arg routing entirely (runs before core matching).
29 * Return a handler route array compatible with {@see Router::handleRouteData()}.
30 */
31 $plain_override = apply_filters('yatra_plain_route_match', null);
32 if (is_array($plain_override) && isset($plain_override['type']) && is_string($plain_override['type']) && $plain_override['type'] !== '') {
33 return $plain_override;
34 }
35
36 // Plain-style query routing (?yatra_page=activity&paged=2) must run even when the site uses
37 // pretty permalinks — pagination links use home_url('/') + query args. Without this, the router
38 // never sees those URLs and WordPress may 404 on paged front-page requests.
39
40 $trip_base = self::sanitizeBaseSegment(SettingsService::getTripBase());
41 $booking_base = self::sanitizeBaseSegment(SettingsService::getBookingBase());
42 $dest_base = self::permalinkBase('destination_base', 'destination');
43 $act_base = self::permalinkBase('activity_base', 'activity');
44 $cat_base = self::permalinkBase('trip_category_base', 'trip-category');
45
46 $raw = $_GET['yatra_page'] ?? '';
47 $raw = is_string($raw) ? trim(wp_unslash($raw)) : '';
48
49 // Plain email verification / remaining checkout: must run before trip/taxonomy heuristics
50 // so ?yatra_verify_email=… and ?yatra_remaining_checkout=… always win (same as pretty paths).
51 if ($raw === '') {
52 $checkoutToken = isset($_GET['yatra_remaining_checkout']) ? (string) wp_unslash($_GET['yatra_remaining_checkout']) : '';
53 $checkoutToken = preg_replace('/[^a-zA-Z0-9_-]/', '', $checkoutToken) ?? '';
54 if ($checkoutToken !== '') {
55 return [
56 'type' => 'checkout',
57 'token' => $checkoutToken,
58 ];
59 }
60
61 $verifyToken = isset($_GET['yatra_verify_email']) ? (string) wp_unslash($_GET['yatra_verify_email']) : '';
62 $verifyToken = preg_replace('/[^a-zA-Z0-9_-]/', '', $verifyToken) ?? '';
63 if ($verifyToken !== '') {
64 return [
65 'type' => 'email_verification',
66 'token' => $verifyToken,
67 ];
68 }
69 }
70
71 // Plain single trip: ?{trip_base}=slug (legacy ?yatra_trip= / ?yatra_trip_slug=)
72 $trip_slug_only = self::getTripSlugFromRequest();
73 if ($trip_slug_only !== '' && $raw === '') {
74 return [
75 'type' => 'trip',
76 'slug' => $trip_slug_only,
77 'base' => $trip_base,
78 ];
79 }
80
81 if ($raw === '') {
82 // On pretty permalinks, trip listing is /{trip_base}/?destination=…&activity=… — those keys
83 // match taxonomy plain routing below and must NOT be treated as /{destination_base}/slug/ singles.
84 $tripSeg = self::sanitizeBaseSegment($trip_base);
85 $cleanPath = trim((string) UrlParser::getCleanRequestPath(), '/');
86 $taxPlain = null;
87 if (!self::isTripArchiveRequestPath($cleanPath, $tripSeg)) {
88 $taxPlain = self::matchTaxonomyPlainBaseSlugs(
89 $tripSeg,
90 $dest_base,
91 $act_base,
92 $cat_base
93 );
94 }
95 if ($taxPlain !== null) {
96 return $taxPlain;
97 }
98 }
99
100 if ($raw === '') {
101 return null;
102 }
103
104 $page = self::sanitizeBaseSegment($raw);
105 if ($page === '') {
106 return null;
107 }
108
109 if ($page === $booking_base && !SettingsService::useCustomBookingPage()) {
110 return [
111 'type' => 'booking',
112 'page' => 'main',
113 'base' => $booking_base,
114 ];
115 }
116
117 $account_base = self::sanitizeBaseSegment(SettingsService::getAccountBase());
118 if ($page === $account_base) {
119 $tab = isset($_GET['tab']) ? sanitize_key((string) $_GET['tab']) : '';
120
121 return [
122 'type' => 'account',
123 'page' => self::accountTabToPage($tab),
124 'base' => SettingsService::getAccountBase(),
125 ];
126 }
127
128 if ($page === $trip_base) {
129 $slug = self::getTripSlugFromRequest();
130 if ($slug !== '') {
131 return [
132 'type' => 'trip',
133 'slug' => $slug,
134 'base' => $trip_base,
135 ];
136 }
137
138 $paged = self::currentPaged();
139
140 return [
141 'type' => 'listing',
142 'listing_type' => 'trip',
143 'base' => $trip_base,
144 'paged' => $paged,
145 ];
146 }
147
148 if ($page === $dest_base) {
149 return self::matchTaxonomy('destination', $dest_base, 'yatra_destination_slug');
150 }
151
152 if ($page === $act_base) {
153 return self::matchTaxonomy('activity', $act_base, 'yatra_activity_slug');
154 }
155
156 if ($page === $cat_base) {
157 return self::matchTaxonomy('category', $cat_base, 'yatra_category_slug');
158 }
159
160 return null;
161 }
162
163 private static function matchTaxonomy(string $taxonomyType, string $base, string $legacySlugKey): ?array
164 {
165 $baseKey = self::sanitizeBaseSegment($base);
166 $slug = '';
167 if ($baseKey !== '') {
168 $fromBase = $_GET[$baseKey] ?? null;
169 if (is_string($fromBase)) {
170 $slug = \Yatra\Helpers\SlugHelper::generate(wp_unslash($fromBase));
171 }
172 }
173 if ($slug === '') {
174 $legacy = $_GET[$legacySlugKey] ?? '';
175 $slug = is_string($legacy) ? \Yatra\Helpers\SlugHelper::generate(wp_unslash($legacy)) : '';
176 }
177 if ($slug !== '') {
178 return [
179 'type' => 'taxonomy',
180 'taxonomy_type' => $taxonomyType,
181 'slug' => $slug,
182 'base' => $base,
183 'paged' => self::currentPaged(),
184 ];
185 }
186
187 return [
188 'type' => 'listing',
189 'listing_type' => $taxonomyType,
190 'base' => $base,
191 'paged' => self::currentPaged(),
192 ];
193 }
194
195 /**
196 * Plain taxonomy singles: ?{destination_base}=slug (no yatra_page), same idea as trip.
197 * Skips keys identical to trip_base so ?trip= only resolves as a trip.
198 *
199 * @return array<string, mixed>|null
200 */
201 /**
202 * Trip archive (or /{trip_base}/page/N/) — query params like ?destination= are filters, not taxonomy singles.
203 */
204 private static function isTripArchiveRequestPath(string $cleanPath, string $tripBaseSanitized): bool
205 {
206 if ($tripBaseSanitized === '') {
207 return false;
208 }
209 if ($cleanPath === $tripBaseSanitized) {
210 return true;
211 }
212
213 return (bool) preg_match('/^' . preg_quote($tripBaseSanitized, '/') . '\/page\/\d+$/', $cleanPath);
214 }
215
216 private static function matchTaxonomyPlainBaseSlugs(
217 string $trip_base_sanitized,
218 string $dest_base,
219 string $act_base,
220 string $cat_base
221 ): ?array {
222 $pairs = [
223 [self::sanitizeBaseSegment($dest_base), 'destination'],
224 [self::sanitizeBaseSegment($act_base), 'activity'],
225 [self::sanitizeBaseSegment($cat_base), 'category'],
226 ];
227 foreach ($pairs as [$b, $taxType]) {
228 if ($b === '' || $b === $trip_base_sanitized) {
229 continue;
230 }
231 if (!isset($_GET[$b])) {
232 continue;
233 }
234 $raw = $_GET[$b];
235 $slug = is_string($raw) ? \Yatra\Helpers\SlugHelper::generate(wp_unslash($raw)) : '';
236 if ($slug === '') {
237 continue;
238 }
239
240 return [
241 'type' => 'taxonomy',
242 'taxonomy_type' => $taxType,
243 'slug' => $slug,
244 'base' => $b,
245 'paged' => self::currentPaged(),
246 ];
247 }
248
249 return null;
250 }
251
252 private static function accountTabToPage(string $tab): string
253 {
254 $allowed = ['dashboard', 'bookings', 'payments', 'documents', 'profile', 'saved-trips'];
255 if ($tab === '' || !in_array($tab, $allowed, true)) {
256 return 'dashboard';
257 }
258
259 return $tab;
260 }
261
262 private static function getTripSlugFromRequest(): string
263 {
264 $key = self::tripSlugQueryKey();
265 $slug = $_GET[$key] ?? $_GET['yatra_trip'] ?? $_GET['yatra_trip_slug'] ?? '';
266 $slug = is_string($slug) ? \Yatra\Helpers\SlugHelper::generate(wp_unslash($slug)) : '';
267
268 return $slug;
269 }
270
271 /**
272 * Query parameter name for single-trip slug in plain mode — matches {@see SettingsService::getTripBase()}.
273 */
274 private static function tripSlugQueryKey(): string
275 {
276 $b = self::sanitizeBaseSegment(SettingsService::getTripBase());
277
278 return $b !== '' ? $b : 'trip';
279 }
280
281 private static function currentPaged(): int
282 {
283 $p = get_query_var('paged');
284 if ($p !== '' && $p !== null) {
285 return max(1, (int) $p);
286 }
287
288 if (isset($_GET['paged'])) {
289 return max(1, (int) wp_unslash($_GET['paged']));
290 }
291
292 if (isset($_GET['page'])) {
293 return max(1, (int) wp_unslash($_GET['page']));
294 }
295
296 return 1;
297 }
298
299 private static function sanitizeBaseSegment(string $value): string
300 {
301 $value = preg_replace('/[^a-z0-9_-]/i', '', $value) ?? '';
302
303 return $value;
304 }
305
306 private static function permalinkBase(string $optionKey, string $default): string
307 {
308 $raw = SettingsService::getString($optionKey, $default);
309 $sanitized = preg_replace('/[^a-z0-9_-]/i', '', $raw) ?? '';
310
311 return $sanitized !== '' ? $sanitized : $default;
312 }
313 }
314