PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
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.2.6, at app/Core/TemplateLoader.php

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