PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.3
Yatra – Travel Booking & Tour Operator Software v3.0.3
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 / Core / TemplateLoader.php

TemplateLoader.php in Yatra – Travel Booking & Tour Operator Software 3.0.3, at app/Core/TemplateLoader.php

458 lines 16.4 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;
6
7 use Yatra\Core\Routing\Router;
8 use Yatra\Core\Routing\PermalinkCanonical;
9 use Yatra\Core\Routing\UrlParser;
10 use Yatra\Services\SettingsService;
11 use Yatra\Core\Handlers\BookingConfirmationPageHandler;
12 use Yatra\Repositories\BookingRepository;
13
14 /**
15 * Template Loader
16 *
17 * Handles all frontend template loading and routing for Yatra pages.
18 * Uses a modern, modular architecture with dedicated routing and asset management.
19 *
20 * @package Yatra\Core
21 * @since 3.0.0
22 */
23 class TemplateLoader
24 {
25 /**
26 * Router instance
27 */
28 private static ?Router $router = null;
29
30 /**
31 * Initialize template loading hooks
32 */
33 public static function init(): void
34 {
35 // Initialize rewrite rules and query vars first
36 add_action('init', [self::class, 'addTripRewriteRules'], 10);
37 add_filter('query_vars', [self::class, 'addCustomQueryVars']);
38
39 // Early template include for booking confirmation (plain permalinks safety net)
40 add_filter('template_include', [self::class, 'maybeLoadBookingConfirmationTemplate'], 0);
41
42 add_action('template_redirect', [PermalinkCanonical::class, 'enforce'], 0);
43
44 // Initialize the main router for template handling
45 add_action('template_redirect', [self::class, 'handleTemplateRedirect'], 1);
46 }
47
48 /**
49 * Early template include for booking confirmation (plain permalinks)
50 */
51 public static function maybeLoadBookingConfirmationTemplate(string $template): string
52 {
53 global $wp_query;
54 if (!empty($wp_query->is_404)) {
55 return $template;
56 }
57
58 $confirmationId = get_query_var('yatra_booking_confirmation')
59 ?: ($_GET['yatra_booking_confirmation'] ?? ($_GET['reference'] ?? ($_GET['booking_id'] ?? '')));
60 if (empty($confirmationId)) {
61 return $template;
62 }
63
64 if (defined('WP_DEBUG') && WP_DEBUG) {
65 }
66
67 $bookingRepo = new BookingRepository();
68 $booking = $bookingRepo->findByConfirmationSegment((string) $confirmationId);
69 if (!$booking) {
70 if (defined('WP_DEBUG') && WP_DEBUG) {
71 }
72 return $template;
73 }
74
75 // Prevent 404 and set globals
76 global $wp_query;
77 $wp_query->is_404 = false;
78 status_header(200);
79 $GLOBALS['yatra_booking'] = $booking;
80 $wp_query->set('yatra_booking_confirmation', $confirmationId);
81 $wp_query->set('yatra_booking', $booking);
82
83 $template_path = YATRA_PLUGIN_PATH . 'templates/booking-confirmation.php';
84 if (file_exists($template_path)) {
85 if (defined('WP_DEBUG') && WP_DEBUG) {
86 }
87 return $template_path;
88 }
89
90 if (defined('WP_DEBUG') && WP_DEBUG) {
91 }
92 return $template;
93 }
94
95 /**
96 * Handle template redirect using the new routing system
97 */
98 public static function handleTemplateRedirect(): void
99 {
100 global $wp_query;
101
102 // WordPress often sets 404 for ?paged=N on the front page when the main blog query has no Nth page.
103 // Yatra listings use the same query vars (?yatra_page=…&paged=2); clear 404 so routing can run.
104 if (!empty($wp_query->is_404) && self::shouldClear404ForYatraRouting()) {
105 $wp_query->is_404 = false;
106 status_header(200);
107 }
108
109 if (!empty($wp_query->is_404)) {
110 return;
111 }
112
113 // Early plain-permalink handling for booking confirmation via query var
114 $confirmationId = get_query_var('yatra_booking_confirmation')
115 ?: ($_GET['yatra_booking_confirmation'] ?? ($_GET['reference'] ?? ($_GET['booking_id'] ?? '')));
116 if (!empty($confirmationId)) {
117 $bookingRepo = new BookingRepository();
118 $booking = $bookingRepo->findByConfirmationSegment((string) $confirmationId);
119 if ($booking) {
120 global $wp_query;
121 $wp_query->is_404 = false;
122 status_header(200);
123 $GLOBALS['yatra_booking'] = $booking;
124 $wp_query->set('yatra_booking_confirmation', $confirmationId);
125 $wp_query->set('yatra_booking', $booking);
126 $template_path = YATRA_PLUGIN_PATH . 'templates/booking-confirmation.php';
127 if (file_exists($template_path)) {
128 include $template_path;
129 exit;
130 }
131 }
132 }
133
134 if (!self::$router) {
135 self::$router = new Router();
136 }
137
138 // Let the router handle the request
139 $handled = self::$router->route();
140
141 // If router didn't handle it, let WordPress continue normally
142 if (!$handled) {
143 // Plain permalinks: routing uses ?yatra_page={base from settings} (see PlainPageMatcher).
144
145 // Plain permalink fallback: handle ?yatra_booking_confirmation=
146 if (!$handled) {
147 $confirmationId = get_query_var('yatra_booking_confirmation') ?: ($_GET['yatra_booking_confirmation'] ?? '');
148 if (!empty($confirmationId)) {
149 $handler = new BookingConfirmationPageHandler();
150 $handled = $handler->handle([
151 'confirmation_id' => sanitize_text_field($confirmationId),
152 ]);
153 }
154 }
155
156 // Plain permalink fallback: login endpoint removed (use [yatra_login] shortcode instead)
157 }
158
159 // If still not handled, continue normally
160 if (!$handled) {
161 return;
162 }
163
164 // If router handled it, exit to prevent further processing
165 exit;
166 }
167
168 /**
169 * True when this request should be routed by Yatra even if WP marked it 404 (paged home quirk).
170 */
171 private static function shouldClear404ForYatraRouting(): bool
172 {
173 if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
174 return false;
175 }
176
177 $yatraPage = isset($_GET['yatra_page']) ? trim((string) wp_unslash($_GET['yatra_page'])) : '';
178 if ($yatraPage !== '') {
179 return true;
180 }
181
182 $qv = (string) get_query_var('yatra_page');
183 if ($qv !== '') {
184 return true;
185 }
186
187 // Plain trip / taxonomy slug keys (same idea as {@see PermalinkCanonical::requestHasPlainYatraRoutingQuery})
188 $tripKey = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) SettingsService::getTripBase()) ?: 'trip';
189 if (isset($_GET[$tripKey]) && is_string($_GET[$tripKey]) && trim(wp_unslash($_GET[$tripKey])) !== '') {
190 return true;
191 }
192
193 foreach (
194 [
195 SettingsService::getString('destination_base', 'destination'),
196 SettingsService::getString('activity_base', 'activity'),
197 SettingsService::getString('trip_category_base', 'trip-category'),
198 ] as $base
199 ) {
200 $bk = preg_replace('/[^a-zA-Z0-9_-]/', '', $base) ?: '';
201 if ($bk === '' || $bk === $tripKey) {
202 continue;
203 }
204 if (isset($_GET[$bk]) && is_string($_GET[$bk]) && trim(wp_unslash($_GET[$bk])) !== '') {
205 return true;
206 }
207 }
208
209 foreach (['yatra_trip', 'yatra_trip_slug', 'yatra_destination_slug', 'yatra_activity_slug', 'yatra_category_slug', 'yatra_booking_confirmation', 'yatra_verify_email'] as $key) {
210 if (!isset($_GET[$key])) {
211 continue;
212 }
213 $v = wp_unslash($_GET[$key]);
214 if (is_string($v) && trim($v) !== '') {
215 return true;
216 }
217 }
218
219 if ((string) get_query_var('yatra_verify_email') !== '') {
220 return true;
221 }
222
223 $verifyPath = trim(UrlParser::getCleanRequestPath(), '/');
224 if ($verifyPath !== '' && strpos($verifyPath, 'yatra-verify-email/') === 0) {
225 return true;
226 }
227
228 return (bool) apply_filters('yatra_clear_404_for_routing', false);
229 }
230
231 /**
232 * Add rewrite rules for trip permalinks and listing pages
233 */
234 public static function addTripRewriteRules(): void
235 {
236 // Use centralized SettingsService for all settings
237 $trip_base = SettingsService::getTripBase();
238 $booking_base = SettingsService::getBookingBase();
239 $account_base = SettingsService::getAccountBase();
240 $account_base = preg_replace('/[^a-z0-9_-]/i', '', $account_base) ?: 'account';
241
242 // Get other bases with sanitization
243 $destination_base = SettingsService::getString('destination_base', 'destination');
244 $destination_base = preg_replace('/[^a-z0-9_-]/i', '', $destination_base) ?: 'destination';
245
246 $activity_base = SettingsService::getString('activity_base', 'activity');
247 $activity_base = preg_replace('/[^a-z0-9_-]/i', '', $activity_base) ?: 'activity';
248
249 $trip_category_base = SettingsService::getString('trip_category_base', 'trip-category');
250 $trip_category_base = preg_replace('/[^a-z0-9_-]/i', '', $trip_category_base) ?: 'trip-category';
251
252 // Add query vars first (must be registered before rewrite rules)
253 // Single-trip slug query var matches trip URL base (e.g. trip=, tours=)
254 add_rewrite_tag('%' . $trip_base . '%', '([^&]+)');
255 add_rewrite_tag('%yatra_booking_confirmation%', '([^&]+)');
256 add_rewrite_tag('%yatra_remaining_checkout%', '([^&]+)');
257 add_rewrite_tag('%yatra_verify_email%', '([^&]+)');
258 // Single taxonomy page tags
259 add_rewrite_tag('%yatra_destination_slug%', '([^&]+)');
260 add_rewrite_tag('%yatra_activity_slug%', '([^&]+)');
261 add_rewrite_tag('%yatra_category_slug%', '([^&]+)');
262 add_rewrite_tag('%yatra_page%', '([a-zA-Z0-9_-]+)');
263 add_rewrite_tag('%paged%', '([0-9]+)');
264
265 // Add rewrite rule for email verification: /yatra-verify-email/{token}/
266 add_rewrite_rule(
267 '^yatra-verify-email/([a-zA-Z0-9_-]+)/?$',
268 'index.php?yatra_verify_email=$matches[1]',
269 'top'
270 );
271
272 // Trip listing pagination: {trip_base}/page/{n}/
273 add_rewrite_rule(
274 '^' . $trip_base . '/page/([0-9]+)/?$',
275 'index.php?yatra_page=' . $trip_base . '&paged=$matches[1]',
276 'top'
277 );
278
279 // Trip listing (page 1): {trip_base}/
280 add_rewrite_rule(
281 '^' . $trip_base . '/?$',
282 'index.php?yatra_page=' . $trip_base,
283 'top'
284 );
285
286 // Customer account (pageless): /{account_base}/ and /{account_base}/{tab}/
287 // Must be real rewrite rules so WordPress doesn't 404 before Yatra Router runs.
288 add_rewrite_rule(
289 '^' . $account_base . '/?$',
290 'index.php?yatra_page=' . $account_base . '&yatra_account_page=dashboard',
291 'top'
292 );
293
294 add_rewrite_rule(
295 '^' . $account_base . '/([^/]+)/?$',
296 'index.php?yatra_page=' . $account_base . '&yatra_account_page=$matches[1]',
297 'top'
298 );
299
300 // Single trip: {trip_base}/{trip_slug}/
301 add_rewrite_rule(
302 '^' . $trip_base . '/([^/]+)/?$',
303 'index.php?yatra_page=' . $trip_base . '&' . $trip_base . '=$matches[1]',
304 'top'
305 );
306
307 // Taxonomy: single + pagination (yatra_page = base from settings; paged = WP pagination)
308 add_rewrite_rule(
309 '^' . $destination_base . '/([^/]+)/page/([0-9]+)/?$',
310 'index.php?yatra_page=' . $destination_base . '&yatra_destination_slug=$matches[1]&paged=$matches[2]',
311 'top'
312 );
313
314 add_rewrite_rule(
315 '^' . $destination_base . '/([^/]+)/?$',
316 'index.php?yatra_page=' . $destination_base . '&yatra_destination_slug=$matches[1]',
317 'top'
318 );
319
320 add_rewrite_rule(
321 '^' . $destination_base . '/?$',
322 'index.php?yatra_page=' . $destination_base,
323 'top'
324 );
325
326 add_rewrite_rule(
327 '^' . $activity_base . '/([^/]+)/page/([0-9]+)/?$',
328 'index.php?yatra_page=' . $activity_base . '&yatra_activity_slug=$matches[1]&paged=$matches[2]',
329 'top'
330 );
331
332 add_rewrite_rule(
333 '^' . $activity_base . '/([^/]+)/?$',
334 'index.php?yatra_page=' . $activity_base . '&yatra_activity_slug=$matches[1]',
335 'top'
336 );
337
338 add_rewrite_rule(
339 '^' . $activity_base . '/?$',
340 'index.php?yatra_page=' . $activity_base,
341 'top'
342 );
343
344 add_rewrite_rule(
345 '^' . $trip_category_base . '/([^/]+)/page/([0-9]+)/?$',
346 'index.php?yatra_page=' . $trip_category_base . '&yatra_category_slug=$matches[1]&paged=$matches[2]',
347 'top'
348 );
349
350 add_rewrite_rule(
351 '^' . $trip_category_base . '/([^/]+)/?$',
352 'index.php?yatra_page=' . $trip_category_base . '&yatra_category_slug=$matches[1]',
353 'top'
354 );
355
356 add_rewrite_rule(
357 '^' . $trip_category_base . '/?$',
358 'index.php?yatra_page=' . $trip_category_base,
359 'top'
360 );
361
362 // Pageless booking confirmation: /{booking_base}/confirmation/{reference}/ (before trip slug rule)
363 add_rewrite_rule(
364 '^' . $booking_base . '/confirmation/([a-zA-Z0-9_-]+)/?$',
365 'index.php?yatra_booking_confirmation=$matches[1]',
366 'top'
367 );
368
369 // Booking with trip slug: /{booking_base}/{trip}/
370 add_rewrite_rule(
371 '^' . $booking_base . '/([^/]+)/?$',
372 'index.php?yatra_page=' . $booking_base . '&trip=$matches[1]',
373 'top'
374 );
375
376 // Booking hub (Settings → booking base, e.g. /book/)
377 add_rewrite_rule(
378 '^' . $booking_base . '/?$',
379 'index.php?yatra_page=' . $booking_base,
380 'top'
381 );
382
383 // Add rewrite rule for booking confirmation page slug: /booking-confirmation/{reference}
384 add_rewrite_rule(
385 '^booking-confirmation/([a-zA-Z0-9_-]+)/?$',
386 'index.php?yatra_booking_confirmation=$matches[1]',
387 'top'
388 );
389
390 // Add rewrite rule for remaining checkout: /remaining-checkout/{token}/
391 add_rewrite_rule(
392 '^remaining-checkout/([a-zA-Z0-9_-]+)/?$',
393 'index.php?yatra_remaining_checkout=$matches[1]',
394 'top'
395 );
396
397 // Check if rewrite rules need flushing (only flush once after plugin update/activation)
398 $rewrite_version = get_option('yatra_rewrite_rules_version', '0');
399 $current_version = '1.0.8'; // Increment this when rewrite rules change
400 if ($rewrite_version !== $current_version) {
401 flush_rewrite_rules(false);
402 update_option('yatra_rewrite_rules_version', $current_version);
403 }
404 }
405
406 /**
407 * Add custom query vars for Yatra functionality
408 */
409 public static function addCustomQueryVars(array $vars): array
410 {
411 $yatra_vars = [
412 'yatra_trip', // legacy plain/pretty query var for trip slug
413 'yatra_booking_confirmation',
414 'yatra_remaining_checkout',
415 'yatra_verify_email',
416 'yatra_account_page',
417 'yatra_destination_slug',
418 'yatra_activity_slug',
419 'yatra_category_slug',
420 'yatra_page',
421 'paged',
422 ];
423
424 // Add dynamic base names for plain permalink support
425 $trip_base = SettingsService::getTripBase();
426 $destination_base = SettingsService::getString('destination_base', 'destination');
427 $activity_base = SettingsService::getString('activity_base', 'activity');
428 $category_base = SettingsService::getString('trip_category_base', 'trip-category');
429
430 $yatra_vars[] = $trip_base;
431 $yatra_vars[] = $destination_base;
432 $yatra_vars[] = $activity_base;
433 $yatra_vars[] = $category_base;
434
435 return array_merge($vars, $yatra_vars);
436 }
437
438 /**
439 * Get client IP address
440 */
441 private static function getClientIp(): string
442 {
443 $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
444
445 foreach ($ip_keys as $key) {
446 if (!empty($_SERVER[$key])) {
447 $ips = explode(',', $_SERVER[$key]);
448 $ip = trim($ips[0]);
449 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
450 return $ip;
451 }
452 }
453 }
454
455 return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
456 }
457 }
458