PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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 / Compatibility / Wanderland / Header.php

Header.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Compatibility/Wanderland/Header.php

265 lines 9.9 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\Compatibility\Wanderland;
6
7 /**
8 * Wanderland Theme Header Compatibility
9 *
10 * Solves two interrelated problems that occur on Yatra custom-routed pages:
11 *
12 * Problem 1 – Wrong page context at the `wp` hook:
13 * Yatra routes produce queried_object_id = 0 (no real WP page).
14 * Wanderland's wanderland_mikado_check_is_header_type_enabled() has a branch for
15 * page_id = 0 (PHP empty(0) = true) that looks up ALL per-page header-type meta
16 * values across the entire site. If any page uses e.g. `header-bottom`, the check
17 * returns true even when the global setting is `header-standard`, causing the header
18 * action to be remapped to a hook Yatra templates never call.
19 *
20 * Problem 2 – Remapped action never fired by Yatra templates:
21 * Header types like `header-bottom`, `header-bottom-centered`, and
22 * `header-bottom-minimal` move `wanderland_mikado_get_header` from
23 * `wanderland_mikado_action_after_wrapper_inner` to
24 * `wanderland_mikado_action_before_main_content`. Yatra's templates never call the
25 * latter, so the header simply disappears.
26 *
27 * Fixes:
28 * 1. At `wp` priority 0, supply a real WordPress page ID as the queried object so
29 * Wanderland's header-type checks evaluate correctly (only against that specific
30 * page, not all pages on the site).
31 * 2. Hook into `wanderland_mikado_action_after_wrapper_inner` and fire
32 * `wanderland_mikado_action_before_main_content` from there once per request,
33 * ensuring bottom-style headers still render inside `wanderland/header.php`.
34 *
35 * Safety guarantees:
36 * - Executes only when the Wanderland theme (or a child theme of it) is active, verified
37 * via get_template() === 'wanderland'.
38 * - Has zero interaction with Elementor compatibility (Elementor\Assets uses separate
39 * hooks: wp_enqueue_scripts, wp_head, body_class – none of which this class touches).
40 * - `maybeFireBeforeMainContent` is guarded by a per-request static flag so
41 * `wanderland_mikado_get_header` can never be rendered more than once per request,
42 * regardless of how many times the outer action fires.
43 *
44 * Registration timing note:
45 * Yatra's Compatibility loader calls register() at `plugins_loaded` priority 20.
46 * The Wanderland theme loads its functions.php AFTER `plugins_loaded`, so we must
47 * NOT gate registration on theme function existence here; the callbacks check
48 * isWanderlandActive() lazily when they actually fire (after `after_setup_theme`).
49 */
50 final class Header
51 {
52 /** Prevents register() from wiring hooks more than once. */
53 private static bool $registered = false;
54
55 /**
56 * Prevents maybeFireBeforeMainContent from firing more than once per request.
57 * This is the hard guard against double-header rendering.
58 */
59 private static bool $beforeMainContentFired = false;
60
61 // -------------------------------------------------------------------------
62 // Registration
63 // -------------------------------------------------------------------------
64
65 /**
66 * Register all compatibility hooks.
67 * Called by Yatra\Compatibility\Compatibility at plugins_loaded priority 20.
68 */
69 public static function register(): void
70 {
71 if (self::$registered) {
72 return;
73 }
74
75 self::$registered = true;
76
77 /*
78 * Priority 0: run before wanderland_mikado_set_header_object (priority 1)
79 * AND before wanderland_mikado_include_header_types_after_load (priority 11).
80 * Both read wanderland_mikado_get_page_id() → get_queried_object_id(), so we
81 * must supply a valid queried object ID before they execute.
82 */
83 add_action('wp', [self::class, 'applyRequestContext'], 0);
84
85 /*
86 * Fires inside wanderland/header.php → do_action('wanderland_mikado_action_after_wrapper_inner').
87 * Priority 5 runs BEFORE the default header hook (priority 10). For bottom-style
88 * header types that moved wanderland_mikado_get_header to
89 * wanderland_mikado_action_before_main_content, we fire that action here so the
90 * header renders inside the header.php template that Yatra's get_header() calls.
91 * The static $beforeMainContentFired flag ensures this fires at most once.
92 */
93 add_action('wanderland_mikado_action_after_wrapper_inner', [self::class, 'maybeFireBeforeMainContent'], 5);
94 }
95
96 // -------------------------------------------------------------------------
97 // Hook callbacks
98 // -------------------------------------------------------------------------
99
100 /**
101 * Set a real WordPress page ID as the queried object on Yatra-routed requests.
102 *
103 * This prevents the page_id = 0 path in wanderland_mikado_check_is_header_type_enabled(),
104 * which can incorrectly enable non-global header types (because PHP's empty(0) = true
105 * triggers a fallback that checks ALL per-page meta across the whole site).
106 *
107 * Hooked: wp, priority 0
108 */
109 public static function applyRequestContext(): void
110 {
111 if (!self::isWanderlandActive()) {
112 return;
113 }
114
115 if (!self::isYatraRoutedRequest()) {
116 return;
117 }
118
119 $contextId = self::resolveContextPageId();
120 if ($contextId <= 0) {
121 return;
122 }
123
124 $contextPost = get_post($contextId);
125 if (!($contextPost instanceof \WP_Post)) {
126 return;
127 }
128
129 global $wp_query;
130 if (!($wp_query instanceof \WP_Query)) {
131 return;
132 }
133
134 // Clear any stale 404 state so wanderland_mikado_is_default_wp_template()
135 // returns false (otherwise get_page_id() returns -1 which skips per-page meta
136 // entirely and forces global-only resolution).
137 $wp_query->is_404 = false;
138 $wp_query->queried_object = $contextPost;
139 $wp_query->queried_object_id = $contextId;
140 }
141
142 /**
143 * On Yatra pages, fire wanderland_mikado_action_before_main_content from inside
144 * wanderland_mikado_action_after_wrapper_inner.
145 *
146 * Header types that remap the header hook (header-bottom, header-bottom-centered,
147 * header-bottom-minimal) attach wanderland_mikado_get_header to
148 * wanderland_mikado_action_before_main_content. Yatra templates only call
149 * get_header() → wanderland/header.php → wanderland_mikado_action_after_wrapper_inner,
150 * never the before_main_content action. This bridge ensures those header types
151 * still render their output.
152 *
153 * The static flag guarantees this bridge fires at most once per request, so the
154 * header is never duplicated even if the action fires more than once.
155 *
156 * Hooked: wanderland_mikado_action_after_wrapper_inner, priority 5
157 */
158 public static function maybeFireBeforeMainContent(): void
159 {
160 // Hard guard: fire at most once per request.
161 if (self::$beforeMainContentFired) {
162 return;
163 }
164
165 if (!self::isWanderlandActive()) {
166 return;
167 }
168
169 if (!self::isYatraRoutedRequest()) {
170 return;
171 }
172
173 self::$beforeMainContentFired = true;
174
175 do_action('wanderland_mikado_action_before_main_content');
176 }
177
178 // -------------------------------------------------------------------------
179 // Internal helpers
180 // -------------------------------------------------------------------------
181
182 /**
183 * Returns true when the Wanderland theme (or a child theme of it) is active.
184 *
185 * Uses get_template() which returns the PARENT theme slug even for child themes.
186 * This is checked lazily (inside callbacks) so it works correctly regardless of
187 * whether the theme was loaded before or after register() ran.
188 */
189 private static function isWanderlandActive(): bool
190 {
191 return get_template() === 'wanderland';
192 }
193
194 /**
195 * Detect whether the current request is a Yatra-routed page.
196 * Relies only on registered WP query vars, which are available from `wp` onwards.
197 */
198 private static function isYatraRoutedRequest(): bool
199 {
200 $yatraQueryVars = [
201 'yatra_page',
202 'yatra_booking_confirmation',
203 'yatra_remaining_checkout',
204 'yatra_verify_email',
205 ];
206
207 foreach ($yatraQueryVars as $var) {
208 if ((string) get_query_var($var, '') !== '') {
209 return true;
210 }
211 }
212
213 return false;
214 }
215
216 /**
217 * Find the best WordPress page ID to use as the request context.
218 *
219 * The chosen page must NOT have a per-page header-type override so Wanderland
220 * falls back to the global header option — which is what should apply to Yatra pages.
221 *
222 * Selection order (first non-zero result wins):
223 * 1. Static front page (page_on_front) — present on most sites, rarely has a
224 * per-page header override.
225 * 2. Posts page (page_for_posts).
226 * 3. The first published WP page (by ID ascending) without mkdf_header_type_meta set.
227 */
228 private static function resolveContextPageId(): int
229 {
230 // 1. Static front page
231 $id = (int) get_option('page_on_front', 0);
232 if ($id > 0) {
233 return $id;
234 }
235
236 // 2. Posts page
237 $id = (int) get_option('page_for_posts', 0);
238 if ($id > 0) {
239 return $id;
240 }
241
242 // 3. Any published page without a per-page header override
243 $pages = get_posts([
244 'post_type' => 'page',
245 'post_status' => 'publish',
246 'posts_per_page' => 1,
247 'orderby' => 'ID',
248 'order' => 'ASC',
249 'meta_query' => [
250 [
251 'key' => 'mkdf_header_type_meta',
252 'compare' => 'NOT EXISTS',
253 ],
254 ],
255 'no_found_rows' => true,
256 ]);
257
258 if (!empty($pages) && $pages[0] instanceof \WP_Post) {
259 return $pages[0]->ID;
260 }
261
262 return 0;
263 }
264 }
265