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 +172 -118 3.0.3 → 3.0.16 View file →
@@ -6,11 +6,12 @@
6 6
7 7 use Yatra\Core\Routing\Router;
8 8 use Yatra\Core\Routing\PermalinkCanonical;
9 9 use Yatra\Core\Routing\UrlParser;
10 +use Yatra\Core\Routing\PageContext;
11 +use Yatra\Core\Template\FseTemplates;
10 12 use Yatra\Services\SettingsService;
11 13 use Yatra\Core\Handlers\BookingConfirmationPageHandler;
12 -use Yatra\Repositories\BookingRepository;
13 14
14 15 /**
15 16 * Template Loader
16 17 *
@@ -35,73 +36,156 @@
35 36 // Initialize rewrite rules and query vars first
36 37 add_action('init', [self::class, 'addTripRewriteRules'], 10);
37 38 add_filter('query_vars', [self::class, 'addCustomQueryVars']);
38 39
39 - // Early template include for booking confirmation (plain permalinks safety net)
40 - 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();
41 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 +
42 53 add_action('template_redirect', [PermalinkCanonical::class, 'enforce'], 0);
43 54
44 - // 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.
45 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);
46 68 }
47 69
48 70 /**
49 - * 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
50 81 */
51 - public static function maybeLoadBookingConfirmationTemplate(string $template): string
82 + public static function preventCore404($preempt, /** @noinspection PhpUnusedParameterInspection */ $wp_query)
52 83 {
53 - global $wp_query;
54 - if (!empty($wp_query->is_404)) {
55 - return $template;
84 + if ($preempt) {
85 + return $preempt;
56 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 + }
57 96
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;
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;
62 109 }
63 110
64 - 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;
65 122 }
123 + }
66 124
67 - $bookingRepo = new BookingRepository();
68 - $booking = $bookingRepo->findByConfirmationSegment((string) $confirmationId);
69 - if (!$booking) {
70 - if (defined('WP_DEBUG') && WP_DEBUG) {
71 - }
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()) {
72 155 return $template;
73 156 }
74 157
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;
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 + }
88 169 }
89 170
90 - if (defined('WP_DEBUG') && WP_DEBUG) {
91 - }
92 - return $template;
171 + $selected = $ctx->getTemplate();
172 + return $selected !== null ? $selected : $template;
93 173 }
94 174
95 175 /**
96 - * 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.
97 183 */
98 184 public static function handleTemplateRedirect(): void
99 185 {
100 186 global $wp_query;
101 187
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 188 if (!empty($wp_query->is_404) && self::shouldClear404ForYatraRouting()) {
105 189 $wp_query->is_404 = false;
106 190 status_header(200);
107 191 }
@@ -109,61 +193,29 @@
109 193 if (!empty($wp_query->is_404)) {
110 194 return;
111 195 }
112 196
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 197 if (!self::$router) {
135 198 self::$router = new Router();
136 199 }
137 200
138 - // Let the router handle the request
139 201 $handled = self::$router->route();
140 202
141 - // 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.
142 205 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 - }
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 + ]);
154 213 }
155 -
156 - // Plain permalink fallback: login endpoint removed (use [yatra_login] shortcode instead)
157 214 }
158 215
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;
216 + // No exit. The selected Yatra template (if any) is in PageContext;
217 + // filterTemplateInclude() will return it from the template_include filter.
166 218 }
167 219
168 220 /**
169 221 * True when this request should be routed by Yatra even if WP marked it 404 (paged home quirk).
@@ -191,11 +243,11 @@
191 243 }
192 244
193 245 foreach (
194 246 [
195 - SettingsService::getString('destination_base', 'destination'),
196 - SettingsService::getString('activity_base', 'activity'),
197 - SettingsService::getString('trip_category_base', 'trip-category'),
247 + SettingsService::getDestinationBase(),
248 + SettingsService::getActivityBase(),
249 + SettingsService::getTripCategoryBase(),
198 250 ] as $base
199 251 ) {
200 252 $bk = preg_replace('/[^a-zA-Z0-9_-]/', '', $base) ?: '';
201 253 if ($bk === '' || $bk === $tripKey) {
@@ -220,9 +272,10 @@
220 272 return true;
221 273 }
222 274
223 275 $verifyPath = trim(UrlParser::getCleanRequestPath(), '/');
224 - if ($verifyPath !== '' && strpos($verifyPath, 'yatra-verify-email/') === 0) {
276 + $verifyPrefix = SettingsService::getPermalinkBases()['email_verification_prefix'];
277 + if ($verifyPath !== '' && strpos($verifyPath, $verifyPrefix . '/') === 0) {
225 278 return true;
226 279 }
227 280
228 281 return (bool) apply_filters('yatra_clear_404_for_routing', false);
@@ -232,24 +285,20 @@
232 285 * Add rewrite rules for trip permalinks and listing pages
233 286 */
234 287 public static function addTripRewriteRules(): void
235 288 {
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';
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'];
241 300
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 301 // Add query vars first (must be registered before rewrite rules)
253 302 // Single-trip slug query var matches trip URL base (e.g. trip=, tours=)
254 303 add_rewrite_tag('%' . $trip_base . '%', '([^&]+)');
255 304 add_rewrite_tag('%yatra_booking_confirmation%', '([^&]+)');
@@ -261,11 +310,11 @@
261 310 add_rewrite_tag('%yatra_category_slug%', '([^&]+)');
262 311 add_rewrite_tag('%yatra_page%', '([a-zA-Z0-9_-]+)');
263 312 add_rewrite_tag('%paged%', '([0-9]+)');
264 313
265 - // Add rewrite rule for email verification: /yatra-verify-email/{token}/
314 + // Add rewrite rule for email verification: /{email_verification_prefix}/{token}/
266 315 add_rewrite_rule(
267 - '^yatra-verify-email/([a-zA-Z0-9_-]+)/?$',
316 + '^' . $emailVerifyPrefix . '/([a-zA-Z0-9_-]+)/?$',
268 317 'index.php?yatra_verify_email=$matches[1]',
269 318 'top'
270 319 );
271 320
@@ -358,11 +407,11 @@
358 407 'index.php?yatra_page=' . $trip_category_base,
359 408 'top'
360 409 );
361 410
362 - // Pageless booking confirmation: /{booking_base}/confirmation/{reference}/ (before trip slug rule)
411 + // Pageless booking confirmation: /{booking_base}/{confirmation_segment}/{reference}/ (before trip slug rule)
363 412 add_rewrite_rule(
364 - '^' . $booking_base . '/confirmation/([a-zA-Z0-9_-]+)/?$',
413 + '^' . $booking_base . '/' . $bookingConfirmSeg . '/([a-zA-Z0-9_-]+)/?$',
365 414 'index.php?yatra_booking_confirmation=$matches[1]',
366 415 'top'
367 416 );
368 417
@@ -379,25 +428,34 @@
379 428 'index.php?yatra_page=' . $booking_base,
380 429 'top'
381 430 );
382 431
383 - // Add rewrite rule for booking confirmation page slug: /booking-confirmation/{reference}
432 + // Legacy booking confirmation: /{legacy_booking_confirmation_prefix}/{reference}
384 433 add_rewrite_rule(
385 - '^booking-confirmation/([a-zA-Z0-9_-]+)/?$',
434 + '^' . $legacyBookingConfirmation . '/([a-zA-Z0-9_-]+)/?$',
386 435 'index.php?yatra_booking_confirmation=$matches[1]',
387 436 'top'
388 437 );
389 438
390 - // Add rewrite rule for remaining checkout: /remaining-checkout/{token}/
439 + // Remaining checkout: /{remaining_checkout_prefix}/{token}/
391 440 add_rewrite_rule(
392 - '^remaining-checkout/([a-zA-Z0-9_-]+)/?$',
441 + '^' . $remainingCheckout . '/([a-zA-Z0-9_-]+)/?$',
393 442 'index.php?yatra_remaining_checkout=$matches[1]',
394 443 'top'
395 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);
396 454
397 455 // Check if rewrite rules need flushing (only flush once after plugin update/activation)
398 456 $rewrite_version = get_option('yatra_rewrite_rules_version', '0');
399 - $current_version = '1.0.8'; // Increment this when rewrite rules change
457 + $current_version = '1.0.9'; // Increment this when rewrite rules change
400 458 if ($rewrite_version !== $current_version) {
401 459 flush_rewrite_rules(false);
402 460 update_option('yatra_rewrite_rules_version', $current_version);
403 461 }
@@ -421,17 +479,13 @@
421 479 'paged',
422 480 ];
423 481
424 482 // 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;
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'];
434 488
435 489 return array_merge($vars, $yatra_vars);
436 490 }
437 491