PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.10
Yatra – Travel Booking & Tour Operator Software v3.0.10
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
yatra / app / Providers / FrontendAssetsProvider.php

FrontendAssetsProvider.php in Yatra – Travel Booking & Tour Operator Software 3.0.10, at app/Providers/FrontendAssetsProvider.php

1,275 lines 51.4 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\Providers;
6
7 use Yatra\Core\Modules\ModuleManager;
8 use Yatra\Utils\Logger;
9
10 /**
11 * Frontend Assets Provider
12 *
13 * Handles enqueuing of all frontend-related CSS and JavaScript assets
14 * Centralizes frontend asset management for better organization and maintainability
15 *
16 * @package Yatra\Providers
17 * @since 3.0.0
18 */
19 class FrontendAssetsProvider
20 {
21 /**
22 * Register the provider
23 *
24 * @return void
25 */
26 public function register(): void
27 {
28 add_action('init', [self::class, 'registerCoreFrontendStylesheets'], 5);
29 // Hook into WordPress to enqueue assets
30 add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
31 }
32
33 /**
34 * Register Font Awesome (optional) and common.css so block editor + shortcode styles can
35 * depend on `yatra-common` (shared @keyframes: yatra-spin, yatra-shimmer, etc.).
36 */
37 /**
38 * @return list<string>
39 */
40 public static function shortcodeStyleDependencies(): array
41 {
42 self::registerCoreFrontendStylesheets();
43
44 return wp_style_is('yatra-common', 'registered') ? ['yatra-common'] : [];
45 }
46
47 public static function registerCoreFrontendStylesheets(): void
48 {
49 $faPath = YATRA_PLUGIN_PATH . 'assets/vendor/fontawesome/css/all.min.css';
50 if (file_exists($faPath) && !wp_style_is('yatra-fontawesome-6', 'registered')) {
51 wp_register_style(
52 'yatra-fontawesome-6',
53 YATRA_PLUGIN_URL . 'assets/vendor/fontawesome/css/all.min.css',
54 [],
55 '6.7.2.' . filemtime($faPath)
56 );
57 }
58
59 if (wp_style_is('yatra-common', 'registered')) {
60 return;
61 }
62 $path = YATRA_PLUGIN_PATH . 'assets/css/common.css';
63 if (!is_readable($path)) {
64 return;
65 }
66 $commonDeps = wp_style_is('yatra-fontawesome-6', 'registered') ? ['yatra-fontawesome-6'] : [];
67 wp_register_style(
68 'yatra-common',
69 YATRA_PLUGIN_URL . 'assets/css/common.css',
70 $commonDeps,
71 YATRA_VERSION . '.' . filemtime($path)
72 );
73 }
74
75 /**
76 * Enqueue frontend assets based on context
77 *
78 * @return void
79 */
80 public function enqueueAssets(): void
81 {
82 // Only run on frontend
83 if (is_admin()) {
84 return;
85 }
86
87 // Always enqueue common frontend assets
88 $this->enqueueCommonAssets();
89
90 // Enqueue page-specific assets
91 $this->enqueuePageSpecificAssets();
92 }
93
94 /**
95 * Enqueue common frontend assets
96 *
97 * @return void
98 */
99 private function enqueueCommonAssets(): void
100 {
101 // Enqueue common CSS
102 $this->enqueueCommonCss();
103
104 // Enqueue common JavaScript
105 $this->enqueueCommonJs();
106 }
107
108 /**
109 * Enqueue common CSS files
110 *
111 * @return void
112 */
113 private function enqueueCommonCss(): void
114 {
115 $cssFiles = [
116 'common' => 'common.css',
117 'listing' => 'listing.css',
118 'stripe' => 'stripe.css',
119 'trip' => 'trip.css',
120 'activity' => 'activity.css',
121 'destination' => 'destination.css',
122 'yatra-capacity' => 'yatra-capacity.css',
123 'video-player' => 'video-player.css',
124 'tour-viewer' => 'tour-viewer.css',
125 ];
126
127 self::registerCoreFrontendStylesheets();
128
129 if (wp_style_is('yatra-fontawesome-6', 'registered')) {
130 wp_enqueue_style('yatra-fontawesome-6');
131 }
132
133 foreach ($cssFiles as $handle => $filename) {
134 $filePath = YATRA_PLUGIN_PATH . "assets/css/{$filename}";
135 if (!file_exists($filePath)) {
136 continue;
137 }
138 $styleHandle = 'yatra-' . $handle;
139 $ver = YATRA_VERSION . '.' . filemtime($filePath);
140
141 if ($handle === 'common') {
142 if (wp_style_is('yatra-common', 'registered')) {
143 wp_enqueue_style('yatra-common');
144 } else {
145 $deps = wp_style_is('yatra-fontawesome-6', 'registered') ? ['yatra-fontawesome-6'] : [];
146 wp_enqueue_style($styleHandle, YATRA_PLUGIN_URL . "assets/css/{$filename}", $deps, $ver);
147 }
148 continue;
149 }
150
151 $deps = [];
152 if ($handle !== 'common' && wp_style_is('yatra-common', 'registered')) {
153 $deps[] = 'yatra-common';
154 }
155
156 wp_enqueue_style(
157 $styleHandle,
158 YATRA_PLUGIN_URL . "assets/css/{$filename}",
159 $deps,
160 $ver
161 );
162 }
163
164 $this->enqueueFrontendThemeVariables();
165 $this->enqueueFrontendLayoutVariables();
166 }
167
168 /**
169 * Override design tokens from Settings (single primary color → related shades).
170 */
171 private function enqueueFrontendThemeVariables(): void
172 {
173 if (!wp_style_is('yatra-common', 'enqueued')) {
174 return;
175 }
176 $primary = \Yatra\Services\SettingsService::getString(
177 'frontend_primary_color',
178 \Yatra\Utils\FrontendThemeCss::DEFAULT_PRIMARY
179 );
180 $primary = \Yatra\Utils\FrontendThemeCss::sanitizePrimaryColor($primary);
181 if (strtolower($primary) === strtolower(\Yatra\Utils\FrontendThemeCss::DEFAULT_PRIMARY)) {
182 return;
183 }
184 $css = \Yatra\Utils\FrontendThemeCss::buildInlineRootCss($primary);
185 if ($css !== '') {
186 wp_add_inline_style('yatra-common', $css);
187 }
188 }
189
190 /**
191 * Align --yatra-container-max-width with the active theme (theme.json wide/content size or $content_width).
192 */
193 private function enqueueFrontendLayoutVariables(): void
194 {
195 if (!wp_style_is('yatra-common', 'enqueued')) {
196 return;
197 }
198 $fromSetting = \Yatra\Utils\FrontendThemeCss::sanitizeContainerMaxWidthSetting(
199 \Yatra\Services\SettingsService::getString('frontend_container_max_width', '')
200 );
201 $max = $fromSetting !== ''
202 ? $fromSetting
203 : \Yatra\Utils\FrontendThemeCss::resolveThemeContainerMaxWidth();
204 if ($max === null || $max === '') {
205 return;
206 }
207 $maxEsc = esc_attr($max);
208 wp_add_inline_style('yatra-common', ':root{--yatra-container-max-width:' . $maxEsc . ';}');
209 }
210
211 /**
212 * Enqueue common JavaScript files
213 *
214 * @return void
215 */
216 private function enqueueCommonJs(): void
217 {
218 $jsFiles = [
219 'api-helper' => 'api-helper.js',
220 'video-player' => 'video-player.js',
221 'tour-viewer' => 'tour-viewer.js',
222 'listing' => 'listing.js',
223 'listing-filters' => 'listing-filters.js',
224 'stripe' => 'stripe.js',
225 'trip' => 'trip.js',
226 ];
227
228 foreach ($jsFiles as $handle => $filename) {
229 $filePath = YATRA_PLUGIN_PATH . "assets/js/{$filename}";
230 if (file_exists($filePath)) {
231 // Set dependencies
232 $dependencies = ['jquery'];
233 if ($handle === 'trip') {
234 $dependencies[] = 'yatra-api-helper';
235 }
236 // Scripts that call `wp.i18n.__()` for user-facing
237 // strings need wp-i18n as a dependency so the global
238 // exists before they run AND a `wp_set_script_translations`
239 // call below so WordPress loads each one's Jed JSON
240 // catalog (each handle has its own md5-named JSON
241 // because the .po references their respective source
242 // file paths). Add a handle here whenever you wrap a
243 // new string in __() inside its file.
244 $i18nHandles = ['trip', 'listing', 'stripe', 'tour-viewer', 'video-player'];
245 if (in_array($handle, $i18nHandles, true)) {
246 $dependencies[] = 'wp-i18n';
247 }
248
249 wp_enqueue_script(
250 "yatra-{$handle}",
251 YATRA_PLUGIN_URL . "assets/js/{$filename}",
252 $dependencies,
253 YATRA_VERSION . '.' . filemtime($filePath),
254 true
255 );
256
257 // Mirror the wp-i18n dep list above. wp_set_script_translations
258 // tells WordPress where to look for this script's Jed JSON
259 // catalog (path = plugin's i18n/languages/) and the loader
260 // hashes md5(handle src) to find the right file.
261 if (in_array($handle, $i18nHandles, true)
262 && function_exists('wp_set_script_translations')
263 ) {
264 wp_set_script_translations(
265 "yatra-{$handle}",
266 'yatra',
267 YATRA_PLUGIN_PATH . 'i18n/languages'
268 );
269 }
270 }
271 }
272
273 if (\Yatra\Services\SettingsService::wishlistEnabled()) {
274 $wishPath = YATRA_PLUGIN_PATH . 'assets/js/listing-wishlist.js';
275 if (file_exists($wishPath)) {
276 wp_enqueue_script(
277 'yatra-listing-wishlist',
278 YATRA_PLUGIN_URL . 'assets/js/listing-wishlist.js',
279 ['jquery', 'wp-i18n'],
280 YATRA_VERSION . '.' . filemtime($wishPath),
281 true
282 );
283 if (function_exists('wp_set_script_translations')) {
284 wp_set_script_translations(
285 'yatra-listing-wishlist',
286 'yatra',
287 YATRA_PLUGIN_PATH . 'i18n/languages'
288 );
289 }
290 // Wishlist "Login" should send guests to the configured
291 // My Account page (Settings → Permalink slug), not the raw
292 // wp-login.php screen. Fall back to wp_login_url() only when
293 // no account base is configured so the button never dead-ends.
294 $yatraAccountBase = \Yatra\Services\SettingsService::getAccountBase();
295 $yatraAccountLoginUrl = $yatraAccountBase !== ''
296 ? home_url('/' . trim($yatraAccountBase, '/') . '/')
297 : wp_login_url();
298 wp_localize_script('yatra-listing-wishlist', 'yatraWishlistConfig', [
299 'enabled' => true,
300 'restUrl' => rest_url('yatra/v1'),
301 'nonce' => wp_create_nonce('wp_rest'),
302 'isLoggedIn' => is_user_logged_in(),
303 'loginUrl' => $yatraAccountLoginUrl,
304 'i18n' => [
305 'loginRequired' => __('Login Required', 'yatra'),
306 'loginPrompt' => __('Please login to save trips to your wishlist.', 'yatra'),
307 'login' => __('Login', 'yatra'),
308 'cancel' => __('Cancel', 'yatra'),
309 'genericError' => __('An error occurred. Please try again.', 'yatra'),
310 'saved' => __('Trip saved to wishlist', 'yatra'),
311 'removed' => __('Trip removed from wishlist', 'yatra'),
312 'saveFailed' => __('Failed to save trip', 'yatra'),
313 'removeFailed' => __('Failed to remove trip', 'yatra'),
314 'addAria' => __('Add to favorites', 'yatra'),
315 'removeAria' => __('Remove from favorites', 'yatra'),
316 ],
317 ]);
318 }
319 }
320 }
321
322 /**
323 * Enqueue page-specific assets
324 *
325 * @return void
326 */
327 private function enqueuePageSpecificAssets(): void
328 {
329 if (yatra_is_account_page()) {
330 $this->enqueueAccountAssets();
331
332 return;
333 }
334
335 // Check current page context and enqueue specific assets using helper functions
336 if (yatra_is_trip_listing()) {
337 $this->enqueueTripListingAssets();
338 }
339
340 if (yatra_is_single_trip()) {
341 $this->enqueueTripDetailAssets();
342 }
343
344 if (yatra_is_activity_listing()) {
345 $this->enqueueActivityListingAssets();
346 }
347
348 if (yatra_is_destination_listing()) {
349 $this->enqueueDestinationListingAssets();
350 }
351
352 if (yatra_is_booking_page()) {
353 $this->enqueueBookingAssets();
354 }
355
356 if (yatra_is_taxonomy_page()) {
357 // Taxonomy pages use the same assets as trip listing
358 $this->enqueueTripListingAssets();
359 }
360 }
361
362 /**
363 * Enqueue trip listing specific assets
364 *
365 * @return void
366 */
367 private function enqueueTripListingAssets(): void
368 {
369 $this->enqueueListingFiltersJs();
370 }
371
372 /**
373 * Enqueue trip detail specific assets
374 *
375 * @return void
376 */
377 private function enqueueTripDetailAssets(): void
378 {
379 // Enqueue booking assets for trip detail pages (booking forms)
380 $bookingJs = YATRA_PLUGIN_PATH . 'assets/js/booking.js';
381 if (file_exists($bookingJs)) {
382 wp_enqueue_script(
383 'yatra-booking',
384 YATRA_PLUGIN_URL . 'assets/js/booking.js',
385 ['jquery', 'wp-i18n'],
386 YATRA_VERSION . '.' . filemtime($bookingJs),
387 true
388 );
389 if (function_exists('wp_set_script_translations')) {
390 wp_set_script_translations(
391 'yatra-booking',
392 'yatra',
393 YATRA_PLUGIN_PATH . 'i18n/languages'
394 );
395 }
396 }
397
398 // International phone-number widget (country flag + dial code) used by
399 // the booking form's tel fields.
400 $this->enqueuePhoneInputAssets();
401
402 // Mobile sticky-sidebar + flatpickr init for the single-trip page. Lives in a
403 // dedicated file rather than as inline <script> in the partial because
404 // WordPress core's `convert_chars` filter (hooked to the_content) rewrites the
405 // `&&` operators inside inline scripts as `&#038;&#038;` — JS parsers don't
406 // decode HTML entities inside <script>, producing a SyntaxError. As a properly
407 // enqueued external file, the source is delivered verbatim.
408 $sidebarJs = YATRA_PLUGIN_PATH . 'assets/js/single-trip-sidebar.js';
409 if (file_exists($sidebarJs)) {
410 wp_enqueue_script(
411 'yatra-single-trip-sidebar',
412 YATRA_PLUGIN_URL . 'assets/js/single-trip-sidebar.js',
413 ['yatra-trip', 'wp-i18n'], // depends on window.yatraTripData from yatra-trip
414 YATRA_VERSION . '.' . filemtime($sidebarJs),
415 true
416 );
417 if (function_exists('wp_set_script_translations')) {
418 wp_set_script_translations(
419 'yatra-single-trip-sidebar',
420 'yatra',
421 YATRA_PLUGIN_PATH . 'i18n/languages'
422 );
423 }
424 }
425
426 // Localize trip page data for JS (trip.js, booking.js)
427 global $trip;
428
429 $permalink_structure = get_option('permalink_structure') ?: '';
430 $is_plain = empty($permalink_structure);
431
432 $trip_id = null;
433 $trip_slug = null;
434 $has_trip = isset($trip) && is_object($trip) && isset($trip->id);
435 if ($has_trip) {
436 $trip_id = (int) $trip->id;
437 }
438 if ($has_trip && isset($trip->slug)) {
439 $trip_slug = $trip->slug;
440 }
441
442 $tripData = [
443 'apiUrl' => rest_url('yatra/v1'),
444 'restUrl' => rest_url(),
445 'siteUrl' => site_url(),
446 'bookingBase' => \Yatra\Services\SettingsService::getBookingBase(),
447 'permalinkStructure' => $is_plain ? 'plain' : $permalink_structure,
448 'nonce' => wp_create_nonce('wp_rest'),
449 'tripId' => $trip_id,
450 'tripSlug' => $trip_slug,
451 'wishlistEnabled' => \Yatra\Services\SettingsService::wishlistEnabled(),
452 'isLoggedIn' => is_user_logged_in(),
453 // Wishlist "Login" (guest) → the configured My Account page
454 // (Settings → Permalink slug), NOT wp-login.php. Without this key
455 // trip.js falls back to a hardcoded '/wp-login.php'. Falls back to
456 // wp_login_url() only when no account base slug is configured.
457 'loginUrl' => (function () {
458 $base = \Yatra\Services\SettingsService::getAccountBase();
459 return $base !== '' ? home_url('/' . trim($base, '/') . '/') : wp_login_url();
460 })(),
461 // Regional settings
462 'timezone' => \Yatra\Services\SettingsService::getString('timezone', 'UTC'),
463 'dateFormat' => \Yatra\Services\SettingsService::getString('date_format', 'Y-m-d'),
464 'timeFormat' => \Yatra\Services\SettingsService::getString('time_format', 'H:i'),
465 // Currency/settings
466 'currency' => \Yatra\Services\SettingsService::getCurrency(),
467 'currencyPosition' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
468 'currency_position' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
469 'decimalPlaces' => \Yatra\Services\SettingsService::getPriceDecimals(),
470 'thousandSeparator' => \Yatra\Services\SettingsService::getString('thousand_separator', ','),
471 'decimalSeparator' => \Yatra\Services\SettingsService::getString('decimal_separator', '.'),
472 'basePrice' => 0.0,
473 'currencySymbol' => function_exists('yatra_get_currency_symbol')
474 ? yatra_get_currency_symbol(\Yatra\Services\SettingsService::getCurrency())
475 : '$',
476 'availabilityDates' => [],
477 'groupDiscountsUrl' => rest_url('yatra/v1/discounts/group-discounts'),
478 'dynamicPricingDisplay' => apply_filters('yatra_get_dynamic_pricing_display_settings', [
479 'show_original_price' => true,
480 'show_savings_badge' => true,
481 'show_urgency_messages' => false,
482 ]),
483 'pricingType' => 'regular',
484 'sidebarAvailability' => [],
485 'sidebarGroupDiscounts' => [],
486 'flatpickrLocale' => $this->buildFlatpickrLocalePayload(),
487 ];
488
489 if ($has_trip) {
490 if (function_exists('yatra_single_trip_calculate_base_price')) {
491 $pricing_data = yatra_single_trip_calculate_base_price($trip);
492 $tripData['basePrice'] = (float) ($pricing_data['base_price'] ?? 0);
493 }
494 if (method_exists($trip, 'getAvailabilityDates')) {
495 $tripData['availabilityDates'] = array_values(array_filter(array_map(static function ($avail) {
496 if (is_object($avail)) {
497 return $avail->departure_date ?? $avail->date ?? null;
498 }
499 if (is_array($avail)) {
500 return $avail['departure_date'] ?? $avail['date'] ?? null;
501 }
502
503 return null;
504 }, $trip->getAvailabilityDates())));
505 }
506 if (function_exists('yatra_single_trip_get_client_booking_payload')) {
507 $bookingPayload = yatra_single_trip_get_client_booking_payload($trip);
508 $tripData['pricingType'] = $bookingPayload['pricingType'];
509 $tripData['sidebarAvailability'] = $bookingPayload['sidebarAvailability'];
510 $tripData['sidebarGroupDiscounts'] = $bookingPayload['sidebarGroupDiscounts'];
511 }
512 }
513
514 wp_localize_script('yatra-trip', 'yatraTripData', $tripData);
515 $tripTitle = '';
516 if ($has_trip && isset($trip->title)) {
517 $tripTitle = is_string($trip->title) ? $trip->title : '';
518 }
519 wp_localize_script('yatra-booking', 'yatraBookingData', array_merge(
520 $tripData,
521 $this->getStripeFrontendBookingPayload(),
522 [
523 'tripTitle' => $tripTitle !== '' ? $tripTitle : ($tripData['tripTitle'] ?? 'Trip Booking'),
524 // booking page expects these keys; leave placeholders if not set on trip view
525 'isRemainingPayment' => false,
526 'remainingAmount' => 0,
527 'totalAmount' => 0,
528 'amountPaid' => 0,
529 // Booking-scoped CSRF nonce — covers BOTH logged-in and
530 // guest checkouts. The REST endpoint's public
531 // permission_callback intentionally bypasses the WP REST
532 // cookie/nonce check (so guests can hit it at all);
533 // this token is what gates the actual write. The JS
534 // forwards it in the `X-Yatra-Booking-Nonce` header on
535 // every booking-create / booking-update POST.
536 'bookingNonce' => wp_create_nonce('yatra_booking_action'),
537 ]
538 ));
539 }
540
541 /**
542 * Enqueue the international phone-number widget (assets/js/phone-input.js +
543 * assets/css/phone-input.css) and localize its country + dial-code dataset.
544 *
545 * Self-contained (reads its own `yatraPhoneData` global) and idempotent, so
546 * it can be called from every path that renders the booking form. Country
547 * data is the single source of truth in {@see FormatHelper}.
548 *
549 * @return void
550 */
551 private function enqueuePhoneInputAssets(): void
552 {
553 if (wp_script_is('yatra-phone-input', 'enqueued')) {
554 return;
555 }
556
557 $css = YATRA_PLUGIN_PATH . 'assets/css/phone-input.css';
558 if (file_exists($css)) {
559 wp_enqueue_style(
560 'yatra-phone-input',
561 YATRA_PLUGIN_URL . 'assets/css/phone-input.css',
562 [],
563 YATRA_VERSION . '.' . filemtime($css)
564 );
565 }
566
567 $js = YATRA_PLUGIN_PATH . 'assets/js/phone-input.js';
568 if (!file_exists($js)) {
569 return;
570 }
571 wp_enqueue_script(
572 'yatra-phone-input',
573 YATRA_PLUGIN_URL . 'assets/js/phone-input.js',
574 [],
575 YATRA_VERSION . '.' . filemtime($js),
576 true
577 );
578 wp_localize_script('yatra-phone-input', 'yatraPhoneData', [
579 'countries' => \Yatra\Helpers\FormatHelper::getPhoneCountries(),
580 'priority' => \Yatra\Helpers\FormatHelper::getPhonePriority(),
581 'i18n' => [
582 'search' => __('Search country', 'yatra'),
583 'noResults' => __('No matches', 'yatra'),
584 ],
585 ]);
586 }
587
588 /**
589 * Enqueue activity listing specific assets
590 *
591 * @return void
592 */
593 private function enqueueActivityListingAssets(): void
594 {
595 // Activity listing specific assets
596 $filePath = YATRA_PLUGIN_PATH . 'assets/css/activity.css';
597 if (file_exists($filePath)) {
598 wp_enqueue_style(
599 'yatra-activity-listing',
600 YATRA_PLUGIN_URL . 'assets/css/activity.css',
601 [],
602 YATRA_VERSION . '.' . filemtime($filePath)
603 );
604 }
605 }
606
607 /**
608 * Enqueue destination listing specific assets
609 *
610 * @return void
611 */
612 private function enqueueDestinationListingAssets(): void
613 {
614 // Destination listing specific assets
615 $filePath = YATRA_PLUGIN_PATH . 'assets/css/destination.css';
616 if (file_exists($filePath)) {
617 wp_enqueue_style(
618 'yatra-destination-listing',
619 YATRA_PLUGIN_URL . 'assets/css/destination.css',
620 [],
621 YATRA_VERSION . '.' . filemtime($filePath)
622 );
623 }
624 }
625
626 /**
627 * Enqueue booking specific assets
628 *
629 * @return void
630 */
631 private function enqueueBookingAssets(): void
632 {
633 // Enqueue booking-specific CSS
634 $bookingCss = YATRA_PLUGIN_PATH . 'assets/css/booking.css';
635 if (file_exists($bookingCss)) {
636 wp_enqueue_style(
637 'yatra-booking',
638 YATRA_PLUGIN_URL . 'assets/css/booking.css',
639 ['yatra-common'],
640 YATRA_VERSION . '.' . filemtime($bookingCss)
641 );
642 }
643
644 // Flatpickr — used by booking.js to upgrade Date-of-Birth (and other
645 // date) inputs to a picker with fast, typeable year navigation. The
646 // single-trip page already ships flatpickr (see single-trip.php); the
647 // dedicated booking page did not, so enqueue it here. booking.js
648 // self-guards on `typeof flatpickr`, so this is safe either way.
649 wp_enqueue_style(
650 'yatra-flatpickr',
651 'https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css',
652 [],
653 YATRA_VERSION
654 );
655 wp_enqueue_script(
656 'yatra-flatpickr',
657 'https://cdn.jsdelivr.net/npm/flatpickr',
658 [],
659 YATRA_VERSION,
660 true
661 );
662
663 // Enqueue booking-specific JavaScript
664 $bookingJs = YATRA_PLUGIN_PATH . 'assets/js/booking.js';
665 if (file_exists($bookingJs)) {
666 // booking.js renders user-facing strings via wp.i18n.__() (the
667 // "Processing..." button label and the per-gateway info messages
668 // shown when a payment method is selected). It therefore needs
669 // wp-i18n as a dependency AND wp_set_script_translations so its
670 // Jed catalog loads — mirroring the trip-detail enqueue above.
671 // Without these, those strings stay English on the standalone
672 // booking page regardless of site locale.
673 wp_enqueue_script(
674 'yatra-booking',
675 YATRA_PLUGIN_URL . 'assets/js/booking.js',
676 ['jquery', 'yatra-flatpickr', 'wp-i18n'],
677 YATRA_VERSION . '.' . filemtime($bookingJs),
678 true
679 );
680 if (function_exists('wp_set_script_translations')) {
681 wp_set_script_translations(
682 'yatra-booking',
683 'yatra',
684 YATRA_PLUGIN_PATH . 'i18n/languages'
685 );
686 }
687 }
688
689 // International phone-number widget (country flag + dial code).
690 $this->enqueuePhoneInputAssets();
691
692 // Load each available gateway's own client scripts on the checkout page
693 // (e.g. Square Web Payments SDK + square.js, Authorize.Net Accept.js +
694 // its handler, Razorpay SDK + its handler). Every gateway's
695 // enqueueScripts() self-guards on isAvailable(), so only enabled +
696 // configured gateways load anything. This call was previously missing,
697 // so Pro gateways that render an inline card form shipped no JS to
698 // checkout and clicking "Pay" just span the button forever. It is
699 // additive and safe for the others: Stripe's enqueueScripts() is a
700 // no-op (Stripe is loaded via enqueueCommonJs), and PayPal/Pay Later
701 // have no client scripts.
702 if (class_exists(\Yatra\PaymentGateways\PaymentGatewayRegistry::class)) {
703 \Yatra\PaymentGateways\PaymentGatewayRegistry::getInstance()->enqueueScripts();
704 }
705
706 // Localize booking data for booking.js
707 $permalink_structure = get_option('permalink_structure') ?: '';
708 $is_plain = empty($permalink_structure);
709
710 $deposit_pct_store = (int) \Yatra\Services\SettingsService::get('deposit_percentage', 20);
711 $partial_pct_store = (int) \Yatra\Services\SettingsService::get('partial_payment_percentage', 30);
712 $deposit_pct_resolved = (int) apply_filters('yatra_deposit_percentage', $deposit_pct_store);
713 $partial_pct_resolved = (int) apply_filters('yatra_partial_payment_percentage', $partial_pct_store);
714 $flexible_payments_enabled = (bool) apply_filters('yatra_flexible_payments_enabled', false);
715 $flexible_module_on = class_exists(ModuleManager::class)
716 ? ModuleManager::isModuleEnabled('flexible_payments')
717 : false;
718
719 Logger::debug('Yatra booking localize: flexible payment snapshot', [
720 'context' => 'booking_localize',
721 'flexible_payments_enabled' => $flexible_payments_enabled,
722 'flexible_payments_module' => $flexible_module_on,
723 'partial_payment_setting' => (bool) \Yatra\Services\SettingsService::get('partial_payment', false),
724 'deposit_required_setting' => (bool) \Yatra\Services\SettingsService::get('deposit_required', false),
725 'deposit_percentage_store' => $deposit_pct_store,
726 'partial_percentage_store' => $partial_pct_store,
727 'deposit_percentage_resolved' => $deposit_pct_resolved,
728 'partial_percentage_resolved' => $partial_pct_resolved,
729 ]);
730
731 $bookingData = [
732 'apiUrl' => rest_url('yatra/v1'),
733 'restUrl' => rest_url(),
734 'siteUrl' => site_url(),
735 'bookingBase' => \Yatra\Services\SettingsService::getBookingBase(),
736 'permalinkStructure' => $is_plain ? 'plain' : $permalink_structure,
737 'nonce' => wp_create_nonce('wp_rest'),
738 // Booking-scoped CSRF nonce. See enqueueTripDetailAssets()
739 // for the rationale: the booking REST endpoint bypasses
740 // the WP REST cookie/nonce check (so guests can use it),
741 // and this token is what gates the actual booking write.
742 'bookingNonce' => wp_create_nonce('yatra_booking_action'),
743 'currency' => \Yatra\Services\SettingsService::getCurrency(),
744 'currencyPosition' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
745 'currency_position' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
746 'decimalPlaces' => \Yatra\Services\SettingsService::getPriceDecimals(),
747 'thousandSeparator' => \Yatra\Services\SettingsService::getString('thousand_separator', ','),
748 'decimalSeparator' => \Yatra\Services\SettingsService::getString('decimal_separator', '.'),
749 // Payment gateways data
750 'paymentGateways' => $this->sanitizeGatewayConfigsForFrontend(apply_filters('yatra_payment_gateways', \Yatra\Services\SettingsService::get('payment_gateways', []))),
751 'paymentMethods' => \Yatra\Services\SettingsService::get('payment_methods', []),
752 'paymentTestMode' => \Yatra\Services\SettingsService::get('payment_test_mode', false),
753 'partialPayment' => \Yatra\Services\SettingsService::get('partial_payment', false),
754 'partialPaymentPercentage' => \Yatra\Services\SettingsService::get('partial_payment_percentage', 0),
755 // Flexible payments (Pro) — keep these keys stable for frontend booking.js.
756 // Values are resolved via generic filters so Pro can override without hard-coding premium logic here.
757 'depositRequired' => (bool) \Yatra\Services\SettingsService::get('deposit_required', false),
758 'depositPercentage' => $deposit_pct_resolved,
759 'partialPercentage' => $partial_pct_resolved,
760 'gatewayOrder' => \Yatra\Services\SettingsService::get('gateway_order', []),
761 'autoConfirmPayLater' => \Yatra\Services\SettingsService::get('auto_confirm_pay_later', true),
762 'allowWaitlist' => \Yatra\Services\SettingsService::isEnabled('allow_waitlist'),
763 'waitlistAutoConfirm' => \Yatra\Services\SettingsService::isEnabled('waitlist_auto_confirm'),
764 'gateways' => $this->getGatewayFrontendConfigs(),
765 'enabledGateways' => $this->sanitizeGatewayConfigsForFrontend(\Yatra\Services\SettingsService::get('payment_gateways', [])),
766 // Server-side translated UI strings for booking.js. PHP __() resolves via .mo
767 // (reliable), so these stay translatable even when the JS-translation JSON
768 // chain (wp_set_script_translations) doesn't load on a given setup.
769 'i18n' => [
770 'complete_booking' => __('Complete Booking', 'yatra'),
771 'pay_now' => __('Pay Now', 'yatra'),
772 ],
773 ];
774
775 $bookingData = array_merge($bookingData, $this->getStripeFrontendBookingPayload());
776
777 wp_localize_script('yatra-booking', 'yatraBookingData', $bookingData);
778 }
779
780 /**
781 * Enqueue account page specific assets
782 *
783 * @return void
784 */
785 private function enqueueAccountAssets(): void
786 {
787 // Account bundle shares admin Vite chunks; CSS is extracted to admin/dist/css (ES modules do not auto-load it).
788 $reactVendorCss = YATRA_PLUGIN_PATH . 'assets/admin/dist/css/react-vendor.css';
789 if (file_exists($reactVendorCss)) {
790 wp_enqueue_style(
791 'yatra-account-react-vendor',
792 YATRA_PLUGIN_URL . 'assets/admin/dist/css/react-vendor.css',
793 [],
794 YATRA_VERSION . '.' . filemtime($reactVendorCss)
795 );
796 }
797
798 $accountUiCss = YATRA_PLUGIN_PATH . 'assets/admin/dist/css/index.css';
799 $accountUiDeps = file_exists($reactVendorCss) ? ['yatra-account-react-vendor'] : [];
800 if (file_exists($accountUiCss)) {
801 wp_enqueue_style(
802 'yatra-account-ui',
803 YATRA_PLUGIN_URL . 'assets/admin/dist/css/index.css',
804 $accountUiDeps,
805 YATRA_VERSION . '.' . filemtime($accountUiCss)
806 );
807 }
808
809 // Vite build outputs to assets/dist/js/account-page.js (ES module + shared chunks).
810 $accountJs = YATRA_PLUGIN_PATH . 'assets/dist/js/account-page.js';
811 if (!file_exists($accountJs)) {
812 return;
813 }
814
815 wp_enqueue_script(
816 'yatra-account-page',
817 YATRA_PLUGIN_URL . 'assets/dist/js/account-page.js',
818 [],
819 YATRA_VERSION . '.' . filemtime($accountJs),
820 true
821 );
822
823 wp_script_add_data('yatra-account-page', 'type', 'module');
824
825 wp_localize_script('yatra-account-page', 'yatraAccountPage', [
826 'apiUrl' => rest_url('yatra/v1'),
827 'nonce' => wp_create_nonce('wp_rest'),
828 'userId' => get_current_user_id(),
829 'siteUrl' => site_url(),
830 'logoutUrl' => wp_logout_url(home_url('/')),
831 'companyPhone' => \Yatra\Services\SettingsService::getString('company_phone', ''),
832 'companyName' => \Yatra\Services\SettingsService::getString('company_name', ''),
833 'companyEmail' => \Yatra\Services\SettingsService::getString('company_email', ''),
834 'currency' => \Yatra\Services\SettingsService::getCurrency(),
835 'currencyPosition' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
836 'currency_position' => \Yatra\Services\SettingsService::getString('currency_position', 'left'),
837 'decimalPlaces' => \Yatra\Services\SettingsService::getPriceDecimals(),
838 'thousandSeparator' => \Yatra\Services\SettingsService::getString('thousand_separator', ','),
839 'decimalSeparator' => \Yatra\Services\SettingsService::getString('decimal_separator', '.'),
840 'locale' => get_locale(),
841 // Global date/time format so the customer account pages render dates
842 // in the operator's configured format (Settings → General), not a
843 // hardcoded browser style. Keys mirror what the admin app receives.
844 'date_format' => \Yatra\Services\SettingsService::getString('date_format', 'Y-m-d'),
845 'time_format' => \Yatra\Services\SettingsService::getString('time_format', 'H:i'),
846 'timezone' => \Yatra\Services\SettingsService::getString('timezone', 'UTC'),
847 // Full ISO country map (code => name) so the account profile can show
848 // full country names and render the country dropdown. Mirrors the
849 // admin (`yatraAdmin.countries`); honours the `yatra_countries_list` filter.
850 'countries' => class_exists('\\Yatra\\Helpers\\FormatHelper')
851 ? \Yatra\Helpers\FormatHelper::getCountries()
852 : [],
853 'translations' => $this->getFrontendTranslations(),
854 'wishlistEnabled' => \Yatra\Services\SettingsService::wishlistEnabled(),
855 ]);
856
857 // Match admin React (`yatra-admin`): register Jed translations for this handle so `wp.i18n` resolves
858 // strings from PHP/Loco JSON catalogs. Without this, only keys in `translations` above work; the rest
859 // stay English because the account bundle is not in `yatra-admin`'s Jed file (different script hash).
860 if (function_exists('wp_set_script_translations')) {
861 wp_set_script_translations('yatra-account-page', 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
862 }
863 }
864
865 /**
866 * Enqueue listing filters JavaScript
867 *
868 * @return void
869 */
870 private function enqueueListingFiltersJs(): void
871 {
872 $filtersJs = YATRA_PLUGIN_PATH . 'assets/js/listing-filters.js';
873 if (file_exists($filtersJs)) {
874 wp_enqueue_script(
875 'yatra-listing-filters',
876 YATRA_PLUGIN_URL . 'assets/js/listing-filters.js',
877 ['jquery'],
878 YATRA_VERSION . '.' . filemtime($filtersJs),
879 true
880 );
881
882 // Add currency formatting function
883 wp_add_inline_script('yatra-listing-filters', "
884 window.yatra_format_price = function(amount) {
885 if (!amount || amount == 0) return '" . esc_js(__('Contact for pricing', 'yatra')) . "';
886 const currency = window.yatraSettings?.currency || 'USD';
887 const symbol = window.yatraSettings?.currencySymbol || '$';
888 return symbol + amount.toLocaleString();
889 };
890 ");
891 }
892 }
893
894 /**
895 * Month and weekday labels for Flatpickr from {@see \WP_Locale} (site language).
896 *
897 * @return array<string, mixed>
898 */
899 private function buildFlatpickrLocalePayload(): array
900 {
901 global $wp_locale;
902
903 $first_day = (int) get_option('start_of_week', 1);
904 $first_day = max(0, min(6, $first_day));
905
906 if (!($wp_locale instanceof \WP_Locale)) {
907 return [
908 'firstDayOfWeek' => $first_day,
909 ];
910 }
911
912 // IMPORTANT — `month_abbrev` and `weekday_abbrev` are keyed by the
913 // TRANSLATED LONG NAME, not by a numeric index:
914 //
915 // $wp_locale->month['01'] = 'January' (or 'जनवरी', 'enero'…)
916 // $wp_locale->month_abbrev['January'] = 'Jan' (or 'जन', 'ene'…)
917 //
918 // An earlier version of this code mistakenly indexed
919 // month_abbrev by '01'..'12' / weekday_abbrev by 0..6, which
920 // ALWAYS returned null → flatpickr's locale.months.shorthand
921 // shipped as an array of empty strings → the `M` token in any
922 // altFormat rendered as nothing. Net effect: a date set to
923 // "19 May 2026" displayed as "19 2026" (no month) under any
924 // non-en_US locale that exposed the bug.
925 //
926 // WP_Locale exposes get_month_abbrev() / get_weekday_abbrev()
927 // which take the long name and do the right lookup. We use
928 // those so the indexing rule lives inside core, not here.
929 $months_long = [];
930 $months_short = [];
931 for ($m = 1; $m <= 12; ++$m) {
932 $key = sprintf('%02d', $m);
933 $long = $wp_locale->month[$key] ?? '';
934 $short = $long !== '' ? (string) $wp_locale->get_month_abbrev($long) : '';
935 $months_long[] = $long;
936 // Final fallback to the long name if the locale has no
937 // abbreviated form — better than shipping an empty string
938 // that flatpickr would render as blank.
939 $months_short[] = $short !== '' ? $short : $long;
940 }
941
942 $weekdays_long = [];
943 $weekdays_short = [];
944 for ($d = 0; $d <= 6; ++$d) {
945 $long = $wp_locale->weekday[$d] ?? '';
946 $short = $long !== '' ? (string) $wp_locale->get_weekday_abbrev($long) : '';
947 $weekdays_long[] = $long;
948 $weekdays_short[] = $short !== '' ? $short : $long;
949 }
950
951 $payload = [
952 'weekdays' => [
953 'shorthand' => $weekdays_short,
954 'longhand' => $weekdays_long,
955 ],
956 'months' => [
957 'shorthand' => $months_short,
958 'longhand' => $months_long,
959 ],
960 'firstDayOfWeek' => $first_day,
961 ];
962
963 return apply_filters('yatra_flatpickr_locale', $payload);
964 }
965
966 /**
967 * Get frontend translations
968 *
969 * @return array
970 */
971 private function getFrontendTranslations(): array
972 {
973 return [
974 // Account page
975 'My Account' => __('My Account', 'yatra'),
976 'My Bookings' => __('My Bookings', 'yatra'),
977 'Account Settings' => __('Account Settings', 'yatra'),
978 'Logout' => __('Logout', 'yatra'),
979 'Login' => __('Login', 'yatra'),
980 'Register' => __('Register', 'yatra'),
981
982 // Booking related
983 'Booking Details' => __('Booking Details', 'yatra'),
984 'Booking Status' => __('Booking Status', 'yatra'),
985 'Total Amount' => __('Total Amount', 'yatra'),
986 'Payment Status' => __('Payment Status', 'yatra'),
987 'View Details' => __('View Details', 'yatra'),
988
989 // Traveler / contact / emergency field labels on the account page.
990 // Keep in sync with the `fieldLabel()` map in account/BookingDetails.tsx.
991 'First Name' => __('First Name', 'yatra'),
992 'Last Name' => __('Last Name', 'yatra'),
993 'Full Name' => __('Full Name', 'yatra'),
994 'Name' => __('Name', 'yatra'),
995 'Email' => __('Email', 'yatra'),
996 'Phone' => __('Phone', 'yatra'),
997 'Mobile' => __('Mobile', 'yatra'),
998 'Date of Birth' => __('Date of Birth', 'yatra'),
999 'Gender' => __('Gender', 'yatra'),
1000 'Nationality' => __('Nationality', 'yatra'),
1001 'Country' => __('Country', 'yatra'),
1002 'Address' => __('Address', 'yatra'),
1003 'City' => __('City', 'yatra'),
1004 'State' => __('State', 'yatra'),
1005 'Postal Code' => __('Postal Code', 'yatra'),
1006 'Zip Code' => __('Zip Code', 'yatra'),
1007 'Passport' => __('Passport', 'yatra'),
1008 'Passport Number' => __('Passport Number', 'yatra'),
1009 'Passport Expiry' => __('Passport Expiry', 'yatra'),
1010 'Dietary Requirements' => __('Dietary Requirements', 'yatra'),
1011 'Special Requirements' => __('Special Requirements', 'yatra'),
1012 'Relationship' => __('Relationship', 'yatra'),
1013 'Company' => __('Company', 'yatra'),
1014
1015 // Common
1016 'Loading...' => __('Loading...', 'yatra'),
1017 'No data available' => __('No data available', 'yatra'),
1018 'Error loading data' => __('Error loading data', 'yatra'),
1019 'Please try again' => __('Please try again', 'yatra'),
1020
1021 // Currency and pricing
1022 'Contact for pricing' => __('Contact for pricing', 'yatra'),
1023 'Free' => __('Free', 'yatra'),
1024 'Price' => __('Price', 'yatra'),
1025
1026 // Trip related
1027 'Trip Details' => __('Trip Details', 'yatra'),
1028 'Duration' => __('Duration', 'yatra'),
1029 'Difficulty' => __('Difficulty', 'yatra'),
1030 'Group Size' => __('Group Size', 'yatra'),
1031
1032 // Navigation
1033 'Home' => __('Home', 'yatra'),
1034 'Trips' => __('Trips', 'yatra'),
1035 'Destinations' => __('Destinations', 'yatra'),
1036 'Activities' => __('Activities', 'yatra'),
1037 'About Us' => __('About Us', 'yatra'),
1038 'Contact' => __('Contact', 'yatra'),
1039 ];
1040 }
1041
1042 /**
1043 * Enqueue assets for specific shortcodes
1044 *
1045 * @param array $shortcodes Array of shortcodes that need assets
1046 * @return void
1047 */
1048 public function enqueueShortcodeAssets(array $shortcodes): void
1049 {
1050 global $post;
1051
1052 if (!$post || !has_shortcode($post->post_content, $shortcodes)) {
1053 return;
1054 }
1055
1056 // Enqueue common frontend assets for shortcodes
1057 $this->enqueueCommonAssets();
1058
1059 // Shortcode-specific assets can be added here based on $shortcodes array
1060 foreach ($shortcodes as $shortcode) {
1061 switch ($shortcode) {
1062 case 'yatra_trip_listing':
1063 $this->enqueueTripListingAssets();
1064 break;
1065 case 'yatra_cart':
1066 case 'yatra_checkout':
1067 $this->enqueueBookingAssets();
1068 break;
1069 case 'yatra_my_account':
1070 $this->enqueueAccountAssets();
1071 break;
1072 }
1073 }
1074 }
1075
1076 /**
1077 * Enqueue assets conditionally based on custom conditions
1078 *
1079 * @param callable $condition Function that returns true if assets should be enqueued
1080 * @return void
1081 */
1082 public function enqueueConditionalAssets(callable $condition): void
1083 {
1084 if ($condition()) {
1085 $this->enqueueAssets();
1086 }
1087 }
1088
1089 /**
1090 * Get asset URL with versioning
1091 *
1092 * @param string $path Relative path to asset
1093 * @param string $type 'css' or 'js'
1094 * @return string Asset URL or empty string if file doesn't exist
1095 */
1096 public function getAssetUrl(string $path, string $type = 'css'): string
1097 {
1098 $basePath = $type === 'css' ? 'assets/css/' : 'assets/js/';
1099 $fullPath = YATRA_PLUGIN_PATH . $basePath . $path;
1100
1101 if (!file_exists($fullPath)) {
1102 return '';
1103 }
1104
1105 $version = YATRA_VERSION . '.' . filemtime($fullPath);
1106 return YATRA_PLUGIN_URL . $basePath . $path . '?ver=' . $version;
1107 }
1108
1109 /**
1110 * Check if asset file exists
1111 *
1112 * @param string $path Relative path to asset
1113 * @param string $type 'css' or 'js'
1114 * @return bool
1115 */
1116 public function assetExists(string $path, string $type = 'css'): bool
1117 {
1118 $basePath = $type === 'css' ? 'assets/css/' : 'assets/js/';
1119 $fullPath = YATRA_PLUGIN_PATH . $basePath . $path;
1120 return file_exists($fullPath);
1121 }
1122
1123 /**
1124 * Strip secret credentials from per-gateway config before it is localized
1125 * into the page (yatraBookingData). The stored payment_gateways option
1126 * holds private keys / access tokens that must NEVER reach the browser; the
1127 * checkout scripts only ever read public values (publishable keys, Square
1128 * application/location IDs, Authorize.Net public client key, the enabled
1129 * flag, etc.). This removes the known secret keys while preserving the
1130 * structure and every public field, so existing gateways/consumers are
1131 * unaffected — only secrets are dropped.
1132 *
1133 * @param mixed $gateways
1134 * @return array<string, mixed>
1135 */
1136 /**
1137 * Per-gateway PUBLIC config for the booking page, keyed by gateway id
1138 * (window.yatraBookingData.gateways.<id>). Checkout scripts read their public
1139 * settings from here — e.g. square.js → gateways.square.application_id /
1140 * location_id, authorizenet.js → gateways.authorize_net.public_client_key /
1141 * api_login_id.
1142 *
1143 * Source of truth is each ENABLED gateway's own getFrontendData(), i.e. an
1144 * allowlist the gateway itself declares. This is deliberately NOT a denylist
1145 * over the raw stored config: a denylist would leak any secret whose key we
1146 * forgot (e.g. Stripe live_secret_key / test_secret_key, Bank Transfer
1147 * account_number / routing_code). Gateways without a getFrontendData()
1148 * (Bank Transfer, PayPal, Pay Later, …) contribute nothing, so their stored
1149 * details never reach the browser. Disabled gateways are excluded.
1150 *
1151 * @return array<string, array<string, mixed>>
1152 */
1153 private function getGatewayFrontendConfigs(): array
1154 {
1155 if (!class_exists(\Yatra\PaymentGateways\PaymentGatewayRegistry::class)) {
1156 return [];
1157 }
1158
1159 $out = [];
1160 try {
1161 $registry = \Yatra\PaymentGateways\PaymentGatewayRegistry::getInstance();
1162 foreach ($registry->getEnabledGateways() as $id => $gateway) {
1163 if (!is_object($gateway) || !method_exists($gateway, 'getFrontendData')) {
1164 continue;
1165 }
1166 $data = $gateway->getFrontendData();
1167 if (is_array($data) && $data !== []) {
1168 $data['enabled'] = true;
1169 $out[(string) $id] = $data;
1170 }
1171 }
1172 } catch (\Throwable $e) {
1173 return [];
1174 }
1175
1176 return $out;
1177 }
1178
1179 private function sanitizeGatewayConfigsForFrontend($gateways): array
1180 {
1181 if (!is_array($gateways)) {
1182 return [];
1183 }
1184
1185 // Credential fields that are private to the server.
1186 $secretKeys = [
1187 'access_token',
1188 'api_key',
1189 'api_secret',
1190 'secret_key',
1191 'key_secret',
1192 'client_secret',
1193 'transaction_key',
1194 'webhook_secret',
1195 'webhook_signing_secret',
1196 'signing_secret',
1197 'private_key',
1198 'password',
1199 'secret',
1200 ];
1201
1202 $clean = [];
1203 foreach ($gateways as $id => $config) {
1204 if (is_array($config)) {
1205 foreach ($secretKeys as $secret) {
1206 unset($config[$secret]);
1207 }
1208 }
1209 $clean[$id] = $config;
1210 }
1211
1212 return $clean;
1213 }
1214
1215 /**
1216 * Stripe Elements (assets/js/stripe.js) expects publishableKey under yatraBookingData.stripe.
1217 * Mirror YatraPro StripeGateway::loadConfig() live/test selection; never expose secret keys.
1218 *
1219 * @return array<string, mixed>
1220 */
1221 private function getStripeFrontendBookingPayload(): array
1222 {
1223 $allConfigs = get_option('yatra_gateway_configs', []);
1224 if (is_string($allConfigs)) {
1225 $maybe = maybe_unserialize($allConfigs);
1226 $allConfigs = is_array($maybe) ? $maybe : [];
1227 }
1228 if (!is_array($allConfigs)) {
1229 $allConfigs = [];
1230 }
1231
1232 $stripe = isset($allConfigs['stripe']) && is_array($allConfigs['stripe'])
1233 ? $allConfigs['stripe']
1234 : [];
1235
1236 $test = filter_var(\Yatra\Services\SettingsService::get('payment_test_mode', true), FILTER_VALIDATE_BOOLEAN);
1237
1238 $livePub = trim((string) ($stripe['live_publishable_key'] ?? ''));
1239 $testPub = trim((string) ($stripe['test_publishable_key'] ?? ''));
1240
1241 $publishableKey = trim((string) ($stripe['api_key'] ?? ''));
1242 if ($test) {
1243 if ($testPub !== '') {
1244 $publishableKey = $testPub;
1245 }
1246 } elseif ($livePub !== '') {
1247 $publishableKey = $livePub;
1248 }
1249
1250 // Match StripeGateway default + admin multi-select when unset (was dropped by old sanitizer).
1251 $defaultMethods = 'card,google_pay,apple_pay';
1252 $enabledMethods = $stripe['enabled_methods'] ?? $defaultMethods;
1253 if (is_array($enabledMethods)) {
1254 // stripe.js accepts an array of method ids
1255 } elseif (!is_string($enabledMethods) || trim($enabledMethods) === '') {
1256 $enabledMethods = $defaultMethods;
1257 }
1258
1259 $companyCountry = trim((string) \Yatra\Services\SettingsService::get('company_country', ''));
1260 if ($companyCountry === '') {
1261 $companyCountry = 'US';
1262 }
1263
1264 $payload = [
1265 'stripe' => [
1266 'publishableKey' => $publishableKey,
1267 'enabledMethods' => $enabledMethods,
1268 ],
1269 'companyCountry' => $companyCountry,
1270 ];
1271
1272 return apply_filters('yatra_booking_stripe_frontend_data', $payload, $stripe, $test);
1273 }
1274 }
1275