PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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 2.0.10 All 83 releases
yatra / app / Compatibility / Elementor / Assets.php

Assets.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at app/Compatibility/Elementor/Assets.php

462 lines 17.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\Elementor;
6
7 /**
8 * Elementor compatibility – Assets & Body Class
9 *
10 * Ensures Elementor Theme Builder header/footer styling (kit typography, spacing,
11 * Google Fonts) applies on all Yatra routed pages (trip, booking, destination,
12 * activity, category and listing pages).
13 *
14 * Boot order: registered on `plugins_loaded` (priority 20) via Bootstrap so that
15 * Elementor classes are guaranteed to be loaded before we check for them.
16 *
17 * === Font loading architecture ===
18 *
19 * Elementor loads Google Fonts in two stages:
20 * 1. CSS enqueue : `\Elementor\Core\Files\CSS\Post::create($id)->enqueue()` enqueues the
21 * pre-generated CSS file AND queues each font in
22 * `$frontend->fonts_to_enqueue` via `$frontend->enqueue_font()`.
23 * 2. wp_head print : `$frontend->print_fonts_links()` (hooked by `Frontend::init()` at
24 * priority 7) reads `fonts_to_enqueue` and outputs `<link>` tags.
25 *
26 * On Yatra pages (custom routing), Elementor does NOT detect the page as its own,
27 * so neither step runs automatically. We trigger both:
28 * – Step 1: call `Post::create($id)->enqueue()` for the active Kit and every
29 * active Theme Builder header/footer template.
30 * – Step 2: hook an explicit `wp_head` callback at priority 9 as a safety-net
31 * in case `Frontend::init()` did not register its own priority-7 hook
32 * (edge-case: some caching/proxy setups skip template_redirect).
33 */
34 final class Assets
35 {
36 /**
37 * Per-request cache (avoid duplicate DB/API calls within the same request).
38 *
39 * @var array<string,mixed>
40 */
41 private static array $cache = [];
42
43 // -------------------------------------------------------------------------
44 // Registration
45 // -------------------------------------------------------------------------
46
47 public static function register(): void
48 {
49 if (!class_exists('\Elementor\Plugin')) {
50 return;
51 }
52
53 // Primary: late priority so kit typography wins over theme base styles.
54 add_action('wp_enqueue_scripts', [self::class, 'enqueue'], 100);
55
56 // Safety-net: wp_enqueue_scripts sometimes fires before Yatra sets its
57 // routing query-vars (especially with caching plugins). Re-run on wp_head
58 // priority 1 as a second chance; WP deduplicates enqueued handles.
59 add_action('wp_head', [self::class, 'enqueue'], 1);
60
61 // Safety-net for Google Fonts: in edge-cases where Frontend::init() did not
62 // register its own print_fonts_links hook, call it ourselves at priority 9
63 // (just after Elementor's priority 7). Calling it when fonts_to_enqueue is
64 // already empty is a no-op, so this is always safe.
65 add_action('wp_head', [self::class, 'printFonts'], 9);
66
67 // Inject the active Elementor kit body-class.
68 add_filter('body_class', [self::class, 'bodyClass'], 20, 1);
69 }
70
71 // -------------------------------------------------------------------------
72 // Yatra page detection
73 // -------------------------------------------------------------------------
74
75 /**
76 * Returns true when the current request is a Yatra-routed frontend page.
77 *
78 * Uses routing query-vars (available early, before TemplateLoader sets globals)
79 * and falls back to REQUEST_URI path matching + late globals-based helpers.
80 */
81 private static function isYatraRoutedPage(): bool
82 {
83 // 1. WordPress query-var set by Yatra rewrite rules (pretty permalinks).
84 if ((string) get_query_var('yatra_page') !== '') {
85 return true;
86 }
87
88 // 2. Plain-permalink GET param fallback.
89 $rawYatraPage = $_GET['yatra_page'] ?? '';
90 if (is_string($rawYatraPage) && trim(wp_unslash($rawYatraPage)) !== '') {
91 return true;
92 }
93
94 // 3. Individual taxonomy / special-page query-vars.
95 foreach ([
96 'yatra_booking_confirmation',
97 'yatra_remaining_checkout',
98 'yatra_verify_email',
99 'yatra_login_page',
100 'yatra_destination_slug',
101 'yatra_activity_slug',
102 'yatra_category_slug',
103 ] as $qv) {
104 $v = get_query_var($qv);
105 if (is_string($v) && trim($v) !== '') {
106 return true;
107 }
108 }
109
110 // 4. Dynamic single-trip query-var (key = trip base, e.g. "trip" or "tours").
111 if (class_exists('\Yatra\Services\SettingsService')) {
112 $tripBase = preg_replace(
113 '/[^a-zA-Z0-9_-]/',
114 '',
115 (string) \Yatra\Services\SettingsService::getTripBase()
116 ) ?: 'trip';
117
118 if (trim((string) get_query_var($tripBase)) !== '') {
119 return true;
120 }
121
122 // Plain-permalink version: ?{tripBase}=slug
123 $rawTrip = $_GET[$tripBase] ?? '';
124 if (is_string($rawTrip) && trim(wp_unslash($rawTrip)) !== '') {
125 return true;
126 }
127
128 // 5. Last resort: match REQUEST_URI path against Yatra URL bases.
129 // Handles edge-cases where caching/proxies strip query-vars.
130 $requestUri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
131 $path = $requestUri !== ''
132 ? trim((string) wp_parse_url($requestUri, PHP_URL_PATH), '/')
133 : '';
134
135 if ($path !== '') {
136 $bases = [
137 $tripBase,
138 preg_replace('/[^a-zA-Z0-9_-]/', '', (string) \Yatra\Services\SettingsService::getString('destination_base', 'destination')),
139 preg_replace('/[^a-zA-Z0-9_-]/', '', (string) \Yatra\Services\SettingsService::getString('activity_base', 'activity')),
140 preg_replace('/[^a-zA-Z0-9_-]/', '', (string) \Yatra\Services\SettingsService::getString('trip_category_base', 'trip-category')),
141 preg_replace('/[^a-zA-Z0-9_-]/', '', (string) \Yatra\Services\SettingsService::getBookingBase()),
142 preg_replace('/[^a-zA-Z0-9_-]/', '', (string) \Yatra\Services\SettingsService::getAccountBase()),
143 ];
144 foreach (array_filter($bases) as $base) {
145 if ($path === $base || str_starts_with($path, $base . '/')) {
146 return true;
147 }
148 }
149 }
150 }
151
152 // 6. Fallback: globals-based helpers (available after TemplateLoader runs).
153 if (function_exists('yatra_is_yatra_page')) {
154 return yatra_is_yatra_page()
155 || (function_exists('yatra_is_booking_page') && yatra_is_booking_page())
156 || (function_exists('yatra_is_trip_listing') && yatra_is_trip_listing())
157 || (function_exists('yatra_is_single_trip') && yatra_is_single_trip());
158 }
159
160 return false;
161 }
162
163 // -------------------------------------------------------------------------
164 // Asset enqueue
165 // -------------------------------------------------------------------------
166
167 public static function enqueue(): void
168 {
169 if (!self::isYatraRoutedPage()) {
170 return;
171 }
172
173 // Guard: only run once per request.
174 if (!empty(self::$cache['enqueue_done'])) {
175 return;
176 }
177 self::$cache['enqueue_done'] = true;
178
179 try {
180 $plugin = \Elementor\Plugin::$instance;
181
182 // -- Core Elementor frontend handles -----------------------------------
183 if ($plugin && isset($plugin->frontend)) {
184 if (method_exists($plugin->frontend, 'enqueue_styles')) {
185 $plugin->frontend->enqueue_styles();
186 }
187 if (method_exists($plugin->frontend, 'enqueue_scripts')) {
188 $plugin->frontend->enqueue_scripts();
189 }
190 }
191
192 // Explicit handles some themes depend on directly.
193 foreach ([
194 'elementor-frontend',
195 'elementor-icons',
196 'elementor-animations',
197 'elementor-frontend-google-fonts',
198 ] as $handle) {
199 if (wp_style_is($handle, 'registered') || wp_style_is($handle, 'queued')) {
200 wp_enqueue_style($handle);
201 }
202 }
203
204 // -- Active Kit (Site Settings) CSS ------------------------------------
205 // The kit CSS defines all CSS custom properties (colors, typography variables)
206 // that the header/footer templates inherit.
207 self::enqueuePostCss($plugin, (int) self::getKitId($plugin));
208
209 // -- Theme Builder Header / Footer template CSS ------------------------
210 if (class_exists('\ElementorPro\Plugin')) {
211 self::enqueueThemeBuilderTemplates($plugin);
212
213 // Pro frontend styles & fonts.
214 foreach ([
215 'elementor-pro',
216 'elementor-pro-frontend',
217 'elementor-pro-frontend-google-fonts',
218 ] as $handle) {
219 if (wp_style_is($handle, 'registered') || wp_style_is($handle, 'queued')) {
220 wp_enqueue_style($handle);
221 }
222 }
223
224 $pro = \ElementorPro\Plugin::instance();
225 if ($pro && method_exists($pro, 'get_frontend')) {
226 $fe = $pro->get_frontend();
227 if ($fe) {
228 if (method_exists($fe, 'enqueue_styles')) {
229 $fe->enqueue_styles();
230 }
231 if (method_exists($fe, 'enqueue_scripts')) {
232 $fe->enqueue_scripts();
233 }
234 }
235 }
236 }
237 } catch (\Throwable $e) {
238 // Never break frontend rendering due to optional integration.
239 }
240 }
241
242 // -------------------------------------------------------------------------
243 // Google Fonts safety-net
244 // -------------------------------------------------------------------------
245
246 /**
247 * Explicitly flush `$frontend->fonts_to_enqueue` → Google Fonts <link> tags.
248 *
249 * Elementor normally does this on wp_head priority 7 inside Frontend::init().
250 * init() is hooked to template_redirect, which fires on all pages, so under
251 * normal circumstances this is a no-op (fonts_to_enqueue is already empty).
252 * It protects against edge-cases where template_redirect was skipped (e.g.
253 * via a caching layer) and init() was therefore never called.
254 */
255 public static function printFonts(): void
256 {
257 if (!self::isYatraRoutedPage()) {
258 return;
259 }
260
261 try {
262 $frontend = \Elementor\Plugin::$instance->frontend ?? null;
263 if ($frontend && method_exists($frontend, 'print_fonts_links')) {
264 $frontend->print_fonts_links();
265 }
266 } catch (\Throwable $e) {
267 // Silently skip.
268 }
269 }
270
271 // -------------------------------------------------------------------------
272 // Body class
273 // -------------------------------------------------------------------------
274
275 /**
276 * @param string[] $classes
277 * @return string[]
278 */
279 public static function bodyClass(array $classes): array
280 {
281 if (!self::isYatraRoutedPage()) {
282 return $classes;
283 }
284
285 try {
286 $kitId = (int) self::getKitId(\Elementor\Plugin::$instance);
287 if ($kitId > 0) {
288 $kitClass = 'elementor-kit-' . $kitId;
289 if (!in_array($kitClass, $classes, true)) {
290 $classes[] = $kitClass;
291 }
292 }
293 } catch (\Throwable $e) {
294 // Silently skip.
295 }
296
297 return $classes;
298 }
299
300 // -------------------------------------------------------------------------
301 // Internal helpers
302 // -------------------------------------------------------------------------
303
304 /**
305 * Return the active Elementor kit ID (cached per request).
306 */
307 private static function getKitId(object $plugin): int
308 {
309 if (isset(self::$cache['kit_id'])) {
310 return (int) self::$cache['kit_id'];
311 }
312
313 $kitId = 0;
314 $kits = $plugin->kits_manager ?? null;
315 if ($kits && method_exists($kits, 'get_active_id')) {
316 $kitId = (int) $kits->get_active_id();
317 }
318
319 self::$cache['kit_id'] = $kitId;
320
321 return $kitId;
322 }
323
324 /**
325 * Enqueue an Elementor post CSS file.
326 *
327 * Uses Elementor's internal `Post::create($id)->enqueue()` API which:
328 * a) Enqueues the pre-generated `post-{id}.css` file (or inlines it).
329 * b) Calls `$frontend->enqueue_font($font)` for every font stored in the
330 * post's CSS meta, populating `$frontend->fonts_to_enqueue` so that
331 * Elementor's `print_fonts_links()` (wp_head priority 7) can output
332 * the correct Google Fonts <link> tags.
333 *
334 * Falls back to directly linking the file from uploads if the internal API
335 * doesn't produce a registered handle (belt-and-suspenders for caching setups).
336 *
337 * IMPORTANT: Do NOT call `\Elementor\Core\Files\CSS\Post::enqueue($id)` —
338 * `enqueue()` is an INSTANCE method; calling it statically is a PHP error
339 * that is silently swallowed and leaves fonts_to_enqueue unpopulated.
340 */
341 private static function enqueuePostCss(object $plugin, int $postId): void
342 {
343 if ($postId <= 0) {
344 return;
345 }
346
347 // Ask Elementor to enqueue via its internal system.
348 // Post::create() uses the files_manager for per-request caching.
349 if (class_exists('\Elementor\Core\Files\CSS\Post')) {
350 try {
351 /** @var \Elementor\Core\Files\CSS\Post $cssFile */
352 $cssFile = \Elementor\Core\Files\CSS\Post::create($postId);
353 if ($cssFile && method_exists($cssFile, 'enqueue')) {
354 $cssFile->enqueue();
355 }
356 } catch (\Throwable $e) {
357 // Continue to fallback.
358 }
359 }
360
361 // Ensure Elementor's own registered handle is queued (in case create()
362 // registered but did not enqueue it for some reason).
363 $handle = 'elementor-post-' . $postId;
364 if (wp_style_is($handle, 'registered') || wp_style_is($handle, 'queued')) {
365 wp_enqueue_style($handle);
366 }
367
368 // Absolute fallback: link the generated CSS file from uploads directly.
369 // This fires even when Elementor's internal enqueue silently does nothing
370 // (e.g. the document has no Elementor data flag set). It does NOT populate
371 // fonts_to_enqueue — that path relies on the Post::create() call above.
372 $upload = wp_upload_dir();
373 if (is_array($upload) && !empty($upload['basedir']) && !empty($upload['baseurl'])) {
374 $dir = rtrim((string) $upload['basedir'], '/\\') . '/elementor/css/';
375 $url = rtrim((string) $upload['baseurl'], '/\\') . '/elementor/css/';
376 $file = $dir . 'post-' . $postId . '.css';
377 if (file_exists($file)) {
378 wp_enqueue_style(
379 'yatra-elementor-post-' . $postId,
380 $url . 'post-' . $postId . '.css',
381 [],
382 (string) @filemtime($file)
383 );
384 }
385 }
386 }
387
388 /**
389 * Enqueue CSS for all active Elementor Pro Theme Builder header/footer templates.
390 * Prefers the Theme Builder conditions API; falls back to a DB query.
391 */
392 private static function enqueueThemeBuilderTemplates(object $plugin): void
393 {
394 if (!class_exists('\ElementorPro\Modules\ThemeBuilder\Module')) {
395 return;
396 }
397
398 foreach (['header', 'footer'] as $location) {
399 $ids = self::getThemeBuilderTemplateIds($location);
400 foreach ($ids as $id) {
401 self::enqueuePostCss($plugin, $id);
402 }
403 }
404 }
405
406 /**
407 * Resolve template IDs for a Theme Builder location (cached per request).
408 *
409 * @return int[]
410 */
411 private static function getThemeBuilderTemplateIds(string $location): array
412 {
413 $cacheKey = 'tb_ids_' . $location;
414 if (isset(self::$cache[$cacheKey])) {
415 return (array) self::$cache[$cacheKey];
416 }
417
418 $ids = [];
419
420 // Preferred: Theme Builder conditions manager (only currently active templates).
421 try {
422 $tb = \ElementorPro\Modules\ThemeBuilder\Module::instance();
423 if ($tb && method_exists($tb, 'get_conditions_manager')) {
424 $cm = $tb->get_conditions_manager();
425 if ($cm && method_exists($cm, 'get_documents_for_location')) {
426 foreach ((array) $cm->get_documents_for_location($location) as $doc) {
427 if (is_object($doc) && method_exists($doc, 'get_main_id')) {
428 $ids[] = (int) $doc->get_main_id();
429 } elseif (is_numeric($doc)) {
430 $ids[] = (int) $doc;
431 }
432 }
433 }
434 }
435 } catch (\Throwable $e) {
436 $ids = [];
437 }
438
439 // Fallback: query published Elementor library items for this template type.
440 if (empty($ids)) {
441 $rows = get_posts([
442 'post_type' => 'elementor_library',
443 'post_status' => 'publish',
444 'fields' => 'ids',
445 'numberposts' => -1,
446 'no_found_rows' => true,
447 'meta_query' => [[
448 'key' => '_elementor_template_type',
449 'value' => $location,
450 'compare' => '=',
451 ]],
452 ]);
453 $ids = array_map('intval', (array) $rows);
454 }
455
456 $ids = array_values(array_filter(array_unique($ids)));
457 self::$cache[$cacheKey] = $ids;
458
459 return $ids;
460 }
461 }
462