PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 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 All 85 releases
← All changes | app/Providers/AdminAssetsProvider.php +144 -1 3.0.7 → 3.0.17 View file →
@@ -63,8 +63,11 @@
63 63 'currentUserAvatar' => get_avatar($current_user->ID, 96),
64 64 'siteUrl' => home_url(),
65 65 'adminUrl' => admin_url('admin.php'),
66 66 'pluginUrl' => YATRA_PLUGIN_URL,
67 + // Public URL of the Yatra sitemap (handles plain vs pretty
68 + // permalinks), shown in the SEO settings tab.
69 + 'sitemapUrl' => \Yatra\Sitemap\SitemapRouter::sitemapUrl(),
67 70 // Brand-name and brand-logo helpers are filter-backed (defaults
68 71 // wired in includes/helpers.php). Pro's WhiteLabel module
69 72 // overrides the filters when Agency white-label is active.
70 73 'brandLogoUrl' => function_exists('yatra_get_brand_icon_url') ? yatra_get_brand_icon_url() : '',
@@ -405,9 +408,17 @@
405 408 // Use built assets in production
406 409 $appJs = YATRA_PLUGIN_PATH . 'assets/admin/dist/js/app.js';
407 410
408 411 if (file_exists($appJs)) {
409 - $jsVersion = YATRA_VERSION . '.' . filemtime($appJs) . '.view-icon-fix.' . time() . '.' . microtime(true);
412 + // Version on the plugin version + the bundle's own mtime. That
413 + // already changes on every update or rebuild, which is exactly
414 + // when the cache must be busted.
415 + //
416 + // This previously appended time() . microtime(true), making the
417 + // URL unique on every single request — so the ~3 MB admin bundle
418 + // was re-downloaded on every admin page view and could never be
419 + // cached by the browser.
420 + $jsVersion = YATRA_VERSION . '.' . filemtime($appJs);
410 421
411 422 $localized_data = $this->buildAdminLocalizedData();
412 423
413 424 // Enqueue our script with media library as dependency
@@ -428,11 +439,32 @@
428 439 $jsVersion,
429 440 true
430 441 );
431 442
443 + // The bundle calls the global wp.i18n.__() (it never ships its
444 + // own copy), and scripts/extract-js-pot.mjs writes every admin
445 + // string's `#:` reference as this bundle's path precisely so
446 + // WordPress's md5(handle src) JSON lookup matches. This call is
447 + // the missing last link: it tells WordPress to load
448 + // i18n/languages/yatra-{locale}-{md5}.json (or the copy under
449 + // WP_LANG_DIR/plugins) for the admin UI. Without it, translated
450 + // admin strings never reach the SPA. Mirrors FrontendAssetsProvider.
451 + if (function_exists('wp_set_script_translations')) {
452 + wp_set_script_translations('yatra-admin', 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
453 + $this->backfillScriptTranslations('yatra-admin');
454 + }
455 +
432 456 // Localize script data
433 457 wp_localize_script('yatra-admin', 'yatraAdmin', $localized_data);
434 458
459 + // Phone dataset for admin displays (flag + dial-code detection of
460 + // stored "+<code><number>" values in booking details).
461 + wp_localize_script('yatra-admin', 'yatraPhoneData', [
462 + 'countries' => \Yatra\Helpers\FormatHelper::getPhoneCountries(),
463 + 'priority' => \Yatra\Helpers\FormatHelper::getPhonePriority(),
464 + 'flagBase' => YATRA_PLUGIN_URL . 'assets/img/flags/',
465 + ]);
466 +
435 467 // Start fetching the ES module as early as possible (helps shorten white/splash time before React runs)
436 468 $app_js_url = YATRA_PLUGIN_URL . 'assets/admin/dist/js/app.js';
437 469 add_action('admin_head', static function () use ($app_js_url, $jsVersion): void {
438 470 $href = esc_url(add_query_arg('ver', rawurlencode((string) $jsVersion), $app_js_url));
@@ -516,8 +548,119 @@
516 548 * Enqueue setup wizard assets
517 549 *
518 550 * @return void
519 551 */
552 + /**
553 + * Feed the admin bundle its translations when no JSON file exists.
554 + *
555 + * wp_set_script_translations() can only read a `yatra-{locale}-{md5}.json`
556 + * file. WordPress.org language packs ship one, so translations from
557 + * translate.wordpress.org simply work — but a site translated by hand, with
558 + * Loco Translate or a .po/.mo dropped into wp-content/languages, has only
559 + * the PHP catalogue. For those sites every string in the React admin stayed
560 + * in English no matter how complete the translation was, which looked like
561 + * the plugin ignoring the translation altogether.
562 + *
563 + * The strings are the same ones the PHP catalogue already holds, so they are
564 + * handed to wp.i18n directly. Core is asked first and left in charge
565 + * whenever it can find a JSON file: that path is cached by the browser as a
566 + * separate request, and this one is not.
567 + *
568 + * Nothing is emitted on an untranslated site — the map comes back empty and
569 + * English sites carry no extra weight.
570 + */
571 + private function backfillScriptTranslations(string $handle): void
572 + {
573 + if (!function_exists('load_script_textdomain')) {
574 + return;
575 + }
576 +
577 + // A real JSON file beats this: let WordPress load it as it normally would.
578 + if (load_script_textdomain($handle, 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages')) {
579 + return;
580 + }
581 +
582 + $localeData = $this->localeDataFromTextdomain('yatra');
583 + if ($localeData === []) {
584 + return;
585 + }
586 +
587 + wp_add_inline_script(
588 + $handle,
589 + 'wp.i18n.setLocaleData(' . wp_json_encode($localeData) . ', "yatra");',
590 + 'before'
591 + );
592 + }
593 +
594 + /**
595 + * The loaded PHP catalogue, in the shape wp.i18n.setLocaleData() expects.
596 + *
597 + * Entries that were never translated are left out: they would only restate
598 + * the English the bundle already carries. Contexts use the same NUL
599 + * separator Jed and gettext use, so _x() resolves too.
600 + *
601 + * @return array<string, mixed>
602 + */
603 + private function localeDataFromTextdomain(string $domain): array
604 + {
605 + $translations = get_translations_for_domain($domain);
606 + if (!is_object($translations)) {
607 + return [];
608 + }
609 + // Deliberately duck-typed, and deliberately not isset()/??. WordPress
610 + // returns Translations, NOOP_Translations or — since the performant
611 + // translations work — WP_Translations, which neither extends
612 + // Translations nor declares `entries`: it serves that property through
613 + // __get() and defines no __isset(), so both instanceof and isset()
614 + // report nothing is there and quietly disable this fallback. Only a
615 + // direct read reaches the magic getter.
616 + if (!property_exists($translations, 'entries') && !method_exists($translations, '__get')) {
617 + return [];
618 + }
619 +
620 + $entries = $translations->entries;
621 + if (!is_array($entries) || $entries === []) {
622 + return [];
623 + }
624 +
625 + $data = [];
626 + foreach ($entries as $entry) {
627 + if (!is_object($entry) || (string) $entry->singular === '') {
628 + continue;
629 + }
630 +
631 + $forms = array_values(array_filter(
632 + (array) $entry->translations,
633 + static fn($t) => is_string($t) && $t !== ''
634 + ));
635 + if ($forms === []) {
636 + continue;
637 + }
638 + // Untranslated entries come back as the original string.
639 + if (count($forms) === 1 && $forms[0] === $entry->singular) {
640 + continue;
641 + }
642 +
643 + $key = ($entry->context !== null && $entry->context !== '')
644 + ? $entry->context . "\u{0004}" . $entry->singular
645 + : $entry->singular;
646 +
647 + $data[$key] = $forms;
648 + }
649 +
650 + if ($data === []) {
651 + return [];
652 + }
653 +
654 + $data[''] = [
655 + 'domain' => $domain,
656 + 'lang' => determine_locale(),
657 + 'plural-forms' => 'nplurals=2; plural=(n != 1);',
658 + ];
659 +
660 + return $data;
661 + }
662 +
520 663 public function enqueueSetupWizardAssets(): void
521 664 {
522 665 // Enqueue setup wizard CSS
523 666 $cssPath = YATRA_PLUGIN_PATH . 'assets/admin/css/setup-wizard.css';