PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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
← All changes | app/Core/TemplateLoader.php +182 -118 3.0.2.9 → 3.0.16 View file →
@@ -5,11 +5,13 @@
5 5 namespace Yatra\Core;
6 6
7 7 use Yatra\Core\Routing\Router;
8 8 use Yatra\Core\Routing\PermalinkCanonical;
9 +use Yatra\Core\Routing\UrlParser;
10 +use Yatra\Core\Routing\PageContext;
11 +use Yatra\Core\Template\FseTemplates;
9 12 use Yatra\Services\SettingsService;
10 13 use Yatra\Core\Handlers\BookingConfirmationPageHandler;
11 -use Yatra\Repositories\BookingRepository;
12 14
13 15 /**
14 16 * Template Loader
15 17 *
@@ -34,73 +36,156 @@
34 36 // Initialize rewrite rules and query vars first
35 37 add_action('init', [self::class, 'addTripRewriteRules'], 10);
36 38 add_filter('query_vars', [self::class, 'addCustomQueryVars']);
37 39
38 - // Early template include for booking confirmation (plain permalinks safety net)
39 - add_filter('template_include', [self::class, 'maybeLoadBookingConfirmationTemplate'], 0);
40 + // FSE Site Editor integration — virtual block templates + page-content block.
41 + // No-ops on classic themes.
42 + FseTemplates::init();
40 43
44 + // Prevent WP::handle_404() from marking Yatra URLs as 404 in the first place.
45 + // This is the proper WordPress way — fires before status_header(404) is sent and
46 + // before FSE locate_block_template() reads is_404() to pick 404.html.
47 + add_filter('pre_handle_404', [self::class, 'preventCore404'], 10, 2);
48 +
49 + // Strip the `error404` body class for Yatra requests so FSE themes don't apply
50 + // 404-specific styling to plugin pages.
51 + add_filter('body_class', [self::class, 'filterBodyClass'], 20);
52 +
41 53 add_action('template_redirect', [PermalinkCanonical::class, 'enforce'], 0);
42 54
43 - // Initialize the main router for template handling
55 + // Initialize the main router for template handling. Handlers configure
56 + // $wp_query + a virtual WP_Post here and stash the chosen template in
57 + // PageContext; they do NOT include or exit, so the full template-loader
58 + // pipeline (template hierarchy resolution, wp_head, wp_footer, plugin
59 + // template_include hooks) continues to run normally.
44 60 add_action('template_redirect', [self::class, 'handleTemplateRedirect'], 1);
61 +
62 + // Hand off to WordPress's template-loader: if a Yatra handler queued a
63 + // template via PageContext, swap it in here. Priority 99 ensures we run
64 + // after FSE's locate_block_template() (default priority 10) so we win
65 + // over the theme's block-template choice without disabling FSE for the
66 + // rest of the site.
67 + add_filter('template_include', [self::class, 'filterTemplateInclude'], 99);
45 68 }
46 69
47 70 /**
48 - * Early template include for booking confirmation (plain permalinks)
71 + * Short-circuit WP::handle_404() for Yatra-owned URLs.
72 + *
73 + * Returning true tells WordPress core to skip setting is_404 / status_header(404).
74 + * This keeps FSE/block themes from resolving to 404.html and pulling the wrong
75 + * header template part. The Router still runs on template_redirect to actually
76 + * render the page; this filter only prevents the premature 404 marking.
77 + *
78 + * @param bool $preempt Whether to short-circuit default 404 handling.
79 + * @param \WP_Query $wp_query The main WP_Query instance (unused — detection uses globals).
80 + * @return bool
49 81 */
50 - public static function maybeLoadBookingConfirmationTemplate(string $template): string
82 + public static function preventCore404($preempt, /** @noinspection PhpUnusedParameterInspection */ $wp_query)
51 83 {
52 - global $wp_query;
53 - if (!empty($wp_query->is_404)) {
54 - return $template;
84 + if ($preempt) {
85 + return $preempt;
55 86 }
87 + if (!self::isYatraRequest()) {
88 + return $preempt;
89 + }
90 + // Mirror what WP::handle_404() does on success: do NOT set is_404, but ensure
91 + // status header is 200 so downstream caches / CDNs behave correctly.
92 + status_header(200);
93 + nocache_headers();
94 + return true;
95 + }
56 96
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;
97 + /**
98 + * Remove the `error404` body class for Yatra pages so FSE themes render normal page chrome.
99 + *
100 + * @param array $classes
101 + * @return array
102 + */
103 + public static function filterBodyClass(array $classes): array
104 + {
105 + $ctx = PageContext::instance();
106 + $isYatra = self::isYatraRequest() || $ctx->isHandled();
107 + if (!$isYatra) {
108 + return $classes;
61 109 }
62 110
63 - if (defined('WP_DEBUG') && WP_DEBUG) {
111 + $classes = array_values(array_filter($classes, static function ($c) {
112 + return $c !== 'error404' && $c !== 'error-404';
113 + }));
114 +
115 + if (!in_array('yatra-page', $classes, true)) {
116 + $classes[] = 'yatra-page';
117 + }
118 +
119 + foreach ($ctx->getBodyClasses() as $extra) {
120 + if (!in_array($extra, $classes, true)) {
121 + $classes[] = $extra;
64 122 }
123 + }
65 124
66 - $bookingRepo = new BookingRepository();
67 - $booking = $bookingRepo->findByConfirmationSegment((string) $confirmationId);
68 - if (!$booking) {
69 - if (defined('WP_DEBUG') && WP_DEBUG) {
70 - }
125 + return $classes;
126 + }
127 +
128 + /**
129 + * Public detection helper — true when the current request belongs to a Yatra route.
130 + * Centralises the logic previously in shouldClear404ForYatraRouting() so it can be
131 + * reused by pre_handle_404 and body_class filters.
132 + */
133 + public static function isYatraRequest(): bool
134 + {
135 + return self::shouldClear404ForYatraRouting();
136 + }
137 +
138 + /**
139 + * `template_include` filter — choose between PHP template and FSE block template.
140 + *
141 + * Decision flow when a Yatra handler has queued a template:
142 + * 1. If the admin has customised the matching virtual block template in the
143 + * Site Editor (wp_template post with source = 'custom'), defer to FSE —
144 + * WP renders the customised block template, which embeds Yatra content
145 + * via the `yatra/page-content` server block. Their edits take effect.
146 + * 2. Otherwise return Yatra's PHP template. It's faster, cache-friendly,
147 + * and the path the plugin tested most.
148 + *
149 + * For non-Yatra requests, return $template unchanged so the theme/FSE keep control.
150 + */
151 + public static function filterTemplateInclude(string $template): string
152 + {
153 + $ctx = PageContext::instance();
154 + if (!$ctx->hasTemplate()) {
71 155 return $template;
72 156 }
73 157
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;
158 + // If the admin has saved a Site-Editor customisation for this page
159 + // type, render via WordPress's block-template canvas with the saved
160 + // content. loadCustomisedCanvas() returns null if no customisation
161 + // exists (the common case) or returns template-canvas.php with the
162 + // canvas globals primed.
163 + $pageType = $ctx->getPageType();
164 + if ($pageType !== '') {
165 + $canvas = FseTemplates::loadCustomisedCanvas($pageType);
166 + if ($canvas !== null) {
167 + return $canvas;
168 + }
87 169 }
88 170
89 - if (defined('WP_DEBUG') && WP_DEBUG) {
90 - }
91 - return $template;
171 + $selected = $ctx->getTemplate();
172 + return $selected !== null ? $selected : $template;
92 173 }
93 174
94 175 /**
95 - * Handle template redirect using the new routing system
176 + * Handle template_redirect:
177 + * 1. Clear residual is_404 (paged-home quirk; pre_handle_404 catches the rest).
178 + * 2. Run the Router — handlers configure $wp_query and queue a template.
179 + * 3. Fall through to WordPress's template-loader. Our template_include
180 + * filter at priority 99 swaps in the Yatra template if one was queued.
181 + *
182 + * No include + exit here — that was the source of the FSE breakage.
96 183 */
97 184 public static function handleTemplateRedirect(): void
98 185 {
99 186 global $wp_query;
100 187
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 188 if (!empty($wp_query->is_404) && self::shouldClear404ForYatraRouting()) {
104 189 $wp_query->is_404 = false;
105 190 status_header(200);
106 191 }
@@ -108,61 +193,29 @@
108 193 if (!empty($wp_query->is_404)) {
109 194 return;
110 195 }
111 196
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 197 if (!self::$router) {
134 198 self::$router = new Router();
135 199 }
136 200
137 - // Let the router handle the request
138 201 $handled = self::$router->route();
139 202
140 - // If router didn't handle it, let WordPress continue normally
203 + // Plain-permalink fallback: ?yatra_booking_confirmation=... still needs
204 + // an explicit dispatch because PlainPageMatcher doesn't know about it.
141 205 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 - }
206 + $confirmationId = get_query_var('yatra_booking_confirmation')
207 + ?: ($_GET['yatra_booking_confirmation'] ?? ($_GET['reference'] ?? ($_GET['booking_id'] ?? '')));
208 + if (!empty($confirmationId)) {
209 + $handler = new BookingConfirmationPageHandler();
210 + $handler->handle([
211 + 'confirmation_id' => sanitize_text_field((string) $confirmationId),
212 + ]);
153 213 }
154 -
155 - // Plain permalink fallback: login endpoint removed (use [yatra_login] shortcode instead)
156 214 }
157 215
158 - // If still not handled, continue normally
159 - if (!$handled) {
160 - return;
161 - }
162 -
163 - // If router handled it, exit to prevent further processing
164 - exit;
216 + // No exit. The selected Yatra template (if any) is in PageContext;
217 + // filterTemplateInclude() will return it from the template_include filter.
165 218 }
166 219
167 220 /**
168 221 * True when this request should be routed by Yatra even if WP marked it 404 (paged home quirk).
@@ -190,11 +243,11 @@
190 243 }
191 244
192 245 foreach (
193 246 [
194 - SettingsService::getString('destination_base', 'destination'),
195 - SettingsService::getString('activity_base', 'activity'),
196 - SettingsService::getString('trip_category_base', 'trip-category'),
247 + SettingsService::getDestinationBase(),
248 + SettingsService::getActivityBase(),
249 + SettingsService::getTripCategoryBase(),
197 250 ] as $base
198 251 ) {
199 252 $bk = preg_replace('/[^a-zA-Z0-9_-]/', '', $base) ?: '';
200 253 if ($bk === '' || $bk === $tripKey) {
@@ -204,9 +257,9 @@
204 257 return true;
205 258 }
206 259 }
207 260
208 - foreach (['yatra_trip', 'yatra_trip_slug', 'yatra_destination_slug', 'yatra_activity_slug', 'yatra_category_slug', 'yatra_booking_confirmation'] as $key) {
261 + foreach (['yatra_trip', 'yatra_trip_slug', 'yatra_destination_slug', 'yatra_activity_slug', 'yatra_category_slug', 'yatra_booking_confirmation', 'yatra_verify_email'] as $key) {
209 262 if (!isset($_GET[$key])) {
210 263 continue;
211 264 }
212 265 $v = wp_unslash($_GET[$key]);
@@ -214,8 +267,18 @@
214 267 return true;
215 268 }
216 269 }
217 270
271 + if ((string) get_query_var('yatra_verify_email') !== '') {
272 + return true;
273 + }
274 +
275 + $verifyPath = trim(UrlParser::getCleanRequestPath(), '/');
276 + $verifyPrefix = SettingsService::getPermalinkBases()['email_verification_prefix'];
277 + if ($verifyPath !== '' && strpos($verifyPath, $verifyPrefix . '/') === 0) {
278 + return true;
279 + }
280 +
218 281 return (bool) apply_filters('yatra_clear_404_for_routing', false);
219 282 }
220 283
221 284 /**
@@ -222,24 +285,20 @@
222 285 * Add rewrite rules for trip permalinks and listing pages
223 286 */
224 287 public static function addTripRewriteRules(): void
225 288 {
226 - // Use centralized SettingsService for all settings
227 - $trip_base = SettingsService::getTripBase();
228 - $booking_base = SettingsService::getBookingBase();
229 - $account_base = SettingsService::getAccountBase();
230 - $account_base = preg_replace('/[^a-z0-9_-]/i', '', $account_base) ?: 'account';
289 + $bases = SettingsService::getPermalinkBases();
290 + $trip_base = $bases['trip_base'];
291 + $booking_base = $bases['booking_base'];
292 + $account_base = $bases['account_base'];
293 + $destination_base = $bases['destination_base'];
294 + $activity_base = $bases['activity_base'];
295 + $trip_category_base = $bases['trip_category_base'];
296 + $bookingConfirmSeg = $bases['booking_flow_confirmation_segment'];
297 + $legacyBookingConfirmation = $bases['legacy_booking_confirmation_prefix'];
298 + $remainingCheckout = $bases['remaining_checkout_prefix'];
299 + $emailVerifyPrefix = $bases['email_verification_prefix'];
231 300
232 - // Get other bases with sanitization
233 - $destination_base = SettingsService::getString('destination_base', 'destination');
234 - $destination_base = preg_replace('/[^a-z0-9_-]/i', '', $destination_base) ?: 'destination';
235 -
236 - $activity_base = SettingsService::getString('activity_base', 'activity');
237 - $activity_base = preg_replace('/[^a-z0-9_-]/i', '', $activity_base) ?: 'activity';
238 -
239 - $trip_category_base = SettingsService::getString('trip_category_base', 'trip-category');
240 - $trip_category_base = preg_replace('/[^a-z0-9_-]/i', '', $trip_category_base) ?: 'trip-category';
241 -
242 301 // Add query vars first (must be registered before rewrite rules)
243 302 // Single-trip slug query var matches trip URL base (e.g. trip=, tours=)
244 303 add_rewrite_tag('%' . $trip_base . '%', '([^&]+)');
245 304 add_rewrite_tag('%yatra_booking_confirmation%', '([^&]+)');
@@ -251,11 +310,11 @@
251 310 add_rewrite_tag('%yatra_category_slug%', '([^&]+)');
252 311 add_rewrite_tag('%yatra_page%', '([a-zA-Z0-9_-]+)');
253 312 add_rewrite_tag('%paged%', '([0-9]+)');
254 313
255 - // Add rewrite rule for email verification: /yatra-verify-email/{token}/
314 + // Add rewrite rule for email verification: /{email_verification_prefix}/{token}/
256 315 add_rewrite_rule(
257 - '^yatra-verify-email/([a-zA-Z0-9_-]+)/?$',
316 + '^' . $emailVerifyPrefix . '/([a-zA-Z0-9_-]+)/?$',
258 317 'index.php?yatra_verify_email=$matches[1]',
259 318 'top'
260 319 );
261 320
@@ -348,11 +407,11 @@
348 407 'index.php?yatra_page=' . $trip_category_base,
349 408 'top'
350 409 );
351 410
352 - // Pageless booking confirmation: /{booking_base}/confirmation/{reference}/ (before trip slug rule)
411 + // Pageless booking confirmation: /{booking_base}/{confirmation_segment}/{reference}/ (before trip slug rule)
353 412 add_rewrite_rule(
354 - '^' . $booking_base . '/confirmation/([a-zA-Z0-9_-]+)/?$',
413 + '^' . $booking_base . '/' . $bookingConfirmSeg . '/([a-zA-Z0-9_-]+)/?$',
355 414 'index.php?yatra_booking_confirmation=$matches[1]',
356 415 'top'
357 416 );
358 417
@@ -369,25 +428,34 @@
369 428 'index.php?yatra_page=' . $booking_base,
370 429 'top'
371 430 );
372 431
373 - // Add rewrite rule for booking confirmation page slug: /booking-confirmation/{reference}
432 + // Legacy booking confirmation: /{legacy_booking_confirmation_prefix}/{reference}
374 433 add_rewrite_rule(
375 - '^booking-confirmation/([a-zA-Z0-9_-]+)/?$',
434 + '^' . $legacyBookingConfirmation . '/([a-zA-Z0-9_-]+)/?$',
376 435 'index.php?yatra_booking_confirmation=$matches[1]',
377 436 'top'
378 437 );
379 438
380 - // Add rewrite rule for remaining checkout: /remaining-checkout/{token}/
439 + // Remaining checkout: /{remaining_checkout_prefix}/{token}/
381 440 add_rewrite_rule(
382 - '^remaining-checkout/([a-zA-Z0-9_-]+)/?$',
441 + '^' . $remainingCheckout . '/([a-zA-Z0-9_-]+)/?$',
383 442 'index.php?yatra_remaining_checkout=$matches[1]',
384 443 'top'
385 444 );
445 +
446 + /**
447 + * Fires after Yatra registers its core rewrite tags/rules.
448 + *
449 + * Use {@see \Yatra\Services\SettingsService::getPermalinkBases()} for the same slugs/helpers use.
450 + *
451 + * @param array<string, string> $bases
452 + */
453 + do_action('yatra_register_rewrite_rules', $bases);
386 454
387 455 // Check if rewrite rules need flushing (only flush once after plugin update/activation)
388 456 $rewrite_version = get_option('yatra_rewrite_rules_version', '0');
389 - $current_version = '1.0.8'; // Increment this when rewrite rules change
457 + $current_version = '1.0.9'; // Increment this when rewrite rules change
390 458 if ($rewrite_version !== $current_version) {
391 459 flush_rewrite_rules(false);
392 460 update_option('yatra_rewrite_rules_version', $current_version);
393 461 }
@@ -411,17 +479,13 @@
411 479 'paged',
412 480 ];
413 481
414 482 // Add dynamic base names for plain permalink support
415 - $trip_base = SettingsService::getTripBase();
416 - $destination_base = SettingsService::getString('destination_base', 'destination');
417 - $activity_base = SettingsService::getString('activity_base', 'activity');
418 - $category_base = SettingsService::getString('trip_category_base', 'trip-category');
419 -
420 - $yatra_vars[] = $trip_base;
421 - $yatra_vars[] = $destination_base;
422 - $yatra_vars[] = $activity_base;
423 - $yatra_vars[] = $category_base;
483 + $pb = SettingsService::getPermalinkBases();
484 + $yatra_vars[] = $pb['trip_base'];
485 + $yatra_vars[] = $pb['destination_base'];
486 + $yatra_vars[] = $pb['activity_base'];
487 + $yatra_vars[] = $pb['trip_category_base'];
424 488
425 489 return array_merge($vars, $yatra_vars);
426 490 }
427 491