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/Providers/AdminServiceProvider.php +201 -24 3.0.2.7 → 3.0.16 View file →
@@ -16,8 +16,18 @@
16 16 {
17 17 private const UPGRADE_TO_PRO_URL = 'https://wpyatra.com/pricing';
18 18
19 19 /**
20 + * Guard so bootstrapMenuCapability() is idempotent across the two
21 + * call sites (AppServiceProvider::register() — always-loaded path —
22 + * and registerAdminMenu() — admin-only). add_filter() with anonymous
23 + * closures does NOT dedupe, because each call creates a new closure
24 + * with a distinct object identity, so without this guard the filters
25 + * would run twice per cap check on admin pageviews.
26 + */
27 + private static bool $capabilityFiltersInstalled = false;
28 +
29 + /**
20 30 * Register services
21 31 */
22 32 public function register(): void
23 33 {
@@ -27,8 +37,15 @@
27 37 add_action('admin_menu', [$this, 'registerUpgradeProExternalSubmenu'], 100);
28 38
29 39 add_filter('plugin_action_links_' . YATRA_PLUGIN_BASENAME, [$this, 'addPluginUpgradeLink']);
30 40 add_filter('plugin_row_meta', [$this, 'filterPluginRowMeta'], 10, 4);
41 + // White-label-specific hooks (all_plugins rebrand, dependency name
42 + // rewrite, brand-color CSS injection) live in Pro's WhiteLabel
43 + // module — see yatra-pro/app/Modules/WhiteLabel/Hooks/AdminHooks.php.
44 + // The plugin_row_meta filter above stays here because it serves a
45 + // dual purpose (links + version row); white-label conditional logic
46 + // inside it reads filter-backed brand helpers, so Pro overrides it
47 + // transparently without owning the hook.
31 48
32 49 // Enqueue admin assets - use priority 20 to run after WordPress core
33 50 add_action('admin_enqueue_scripts', [$this, 'enqueueAdminAssets'], 20);
34 51
@@ -295,12 +312,17 @@
295 312 if ($plugin_file !== YATRA_PLUGIN_BASENAME) {
296 313 return $plugin_meta;
297 314 }
298 315
299 - $home = 'https://wpyatra.com/';
316 + $is_white_label = function_exists('yatra_is_white_label_active') && yatra_is_white_label_active();
317 + $brand_company = function_exists('yatra_get_brand_company') ? yatra_get_brand_company() : 'MantraBrain';
318 + $brand_home = function_exists('yatra_get_brand_website_url') ? yatra_get_brand_website_url() : 'https://wpyatra.com/';
319 + $brand_support = function_exists('yatra_get_brand_support_url') ? yatra_get_brand_support_url() : 'https://wordpress.org/support/plugin/yatra/reviews/?filter=5';
320 +
321 + $home = $brand_home;
300 322 $org_plugin_page = 'https://wordpress.org/plugins/yatra/';
301 - $support_url = 'https://wordpress.org/support/plugin/yatra/reviews/?filter=5';
302 - $contact_url = 'https://mantrabrain.com/contact';
323 + $support_url = $brand_support;
324 + $contact_url = $brand_home;
303 325 $rate_url = 'https://wordpress.org/support/plugin/yatra/reviews/?filter=5';
304 326
305 327 $row = [];
306 328
@@ -314,16 +336,20 @@
314 336
315 337 $row[] = sprintf(
316 338 /* translators: %s: linked author name (HTML). */
317 339 __('By %s', 'yatra'),
318 - '<a href="' . esc_url($home) . '" target="_blank" rel="noopener noreferrer">MantraBrain</a>'
340 + '<a href="' . esc_url($home) . '" target="_blank" rel="noopener noreferrer">' . esc_html($brand_company) . '</a>'
319 341 );
320 342
321 - $row[] = sprintf(
322 - '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
323 - esc_url($org_plugin_page),
324 - esc_html__('View details', 'yatra')
325 - );
343 + // White-label sites should not link clients off to wp.org / Yatra rating
344 + // pages. Show only the agency's own support + homepage links.
345 + if (!$is_white_label) {
346 + $row[] = sprintf(
347 + '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
348 + esc_url($org_plugin_page),
349 + esc_html__('View details', 'yatra')
350 + );
351 + }
326 352
327 353 $row[] = sprintf(
328 354 '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
329 355 esc_url($support_url),
@@ -335,23 +361,26 @@
335 361 esc_url($home),
336 362 esc_html__('Plugin Homepage', 'yatra')
337 363 );
338 364
339 - $row[] = sprintf(
340 - '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
341 - esc_url($contact_url),
342 - esc_html__('Contact', 'yatra')
343 - );
365 + if (!$is_white_label) {
366 + $row[] = sprintf(
367 + '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
368 + esc_url($contact_url),
369 + esc_html__('Contact', 'yatra')
370 + );
344 371
345 - $row[] = sprintf(
346 - '<a href="%s" target="_blank" rel="noopener noreferrer">%s <span aria-hidden="true">&#9733;&#9733;&#9733;&#9733;&#9733;</span></a>',
347 - esc_url($rate_url),
348 - esc_html__('Rate the plugin', 'yatra')
349 - );
372 + $row[] = sprintf(
373 + '<a href="%s" target="_blank" rel="noopener noreferrer">%s <span aria-hidden="true">&#9733;&#9733;&#9733;&#9733;&#9733;</span></a>',
374 + esc_url($rate_url),
375 + esc_html__('Rate the plugin', 'yatra')
376 + );
377 + }
350 378
351 379 return $row;
352 380 }
353 381
382 +
354 383 /**
355 384 * Amber styling for Upgrade to Pro (admin menu + plugins list).
356 385 */
357 386 public function printUpgradeProAdminStyles(): void
@@ -449,20 +478,167 @@
449 478 }
450 479 }
451 480
452 481 /**
482 + * Map the Yatra menu visibility cap to `manage_options` for users
483 + * who don't otherwise have it, so WP admins keep seeing the menu
484 + * even when the Team & Access module (which formally defines the
485 + * cap on roles) is not installed.
486 + *
487 + * Mechanism: hook `user_has_cap` to grant `yatra_access_admin`
488 + * whenever the user already has `manage_options`. Pro/Team's
489 + * Capabilities filter does the same thing via the admin-fallback
490 + * path; this is a free-plugin safety-net so the cap is always
491 + * truthy for site owners regardless of Pro install state.
492 + *
493 + * Idempotent — adding the same filter callback twice is a no-op in WP.
494 + */
495 + public static function bootstrapMenuCapability(): void
496 + {
497 + if (self::$capabilityFiltersInstalled) {
498 + return;
499 + }
500 + self::$capabilityFiltersInstalled = true;
501 +
502 + add_filter('user_has_cap', static function (array $allcaps, array $caps, array $args, \WP_User $user): array {
503 + if (empty($allcaps['manage_options'])) return $allcaps;
504 + // Only set if the cap was asked-about (avoids polluting
505 + // unrelated cap checks with a key we don't need to answer).
506 + foreach ($caps as $cap) {
507 + if ($cap === 'yatra_access_admin') {
508 + $allcaps['yatra_access_admin'] = true;
509 + break;
510 + }
511 + }
512 + return $allcaps;
513 + }, 8, 4);
514 +
515 + // Module-state cap gate. ALWAYS installed by the free plugin
516 + // so it runs regardless of whether the Pro plugin / Team
517 + // module are active. Strips every yatra_* cap from non-admin
518 + // users UNLESS something returns true from the
519 + // `yatra_team_role_enforcement_active` filter.
520 + //
521 + // Signal semantics (set by the Pro Team module):
522 + // - Module ENABLED: signal returns TRUE → no strip, defer
523 + // to Team's Capabilities filter (priority 8) for layered
524 + // logic (admin fallback, expiry, revoke, role, grant).
525 + // - Module DISABLED + "keep access" setting OFF (default):
526 + // signal returns FALSE → strip yatra_* caps for non-admins.
527 + // - Module DISABLED + "keep access" setting ON: signal
528 + // returns TRUE → no strip; WP-native role machinery
529 + // resolves caps from the stored role records.
530 + //
531 + // Why this lives in the free plugin: when Pro is deactivated,
532 + // the Team module's own filter doesn't load. Without this
533 + // free-side gate, users with a stored yatra_* role assignment
534 + // (e.g. yatra_sales_agent) would still get their caps via
535 + // WP-native role resolution — meaning deactivating Pro would
536 + // NOT actually disable Yatra role-based access. This filter
537 + // ensures the gate is honored regardless of Pro state.
538 + //
539 + // Priority 7 — runs BEFORE Team's Capabilities::filterUserHasCap
540 + // (priority 8). Admin users (`manage_options`) are always
541 + // exempt — they pass yatra_* caps via the admin fallback above
542 + // plus per-controller manage_options short-circuits.
543 + add_filter('user_has_cap', static function (array $allcaps, array $caps, array $args, \WP_User $user): array {
544 + // Admin fallback FIRST — site owners always pass every
545 + // yatra_* cap regardless of enforcement state OR whether
546 + // the Pro Team module is installed. This is the contract
547 + // that makes the free plugin usable on any site: an
548 + // administrator who installs Yatra and visits Bookings /
549 + // Trips / Settings must always have access without any
550 + // role configuration step.
551 + //
552 + // We grant the SPECIFIC yatra_* caps being asked about
553 + // (not a blanket pollution of every key) — keeps the
554 + // $allcaps array tidy for downstream filters.
555 + //
556 + // CRITICAL: this admin grant must run BEFORE the
557 + // enforcement-signal check below. Earlier code returned
558 + // $allcaps unchanged for admins, which silently broke
559 + // every controller that gates on a granular cap (e.g.
560 + // `current_user_can('yatra_view_bookings')`) — admins
561 + // don't have those caps by default because they're
562 + // custom-registered. The old controllers worked because
563 + // each one OR-ed `manage_options` into its own check; new
564 + // controllers gate on the granular cap only and rely on
565 + // this filter to make the admin path work uniformly.
566 + if (!empty($allcaps['manage_options'])) {
567 + foreach ($caps as $cap) {
568 + if (\is_string($cap) && strpos($cap, 'yatra_') === 0) {
569 + $allcaps[$cap] = true;
570 + }
571 + }
572 + return $allcaps;
573 + }
574 +
575 + /**
576 + * Access signal. The Pro Team module hooks this to
577 + * return true when:
578 + * - The Team module is enabled, OR
579 + * - The module is disabled but the operator has flipped
580 + * the "keep access on module disable" setting to ON.
581 + *
582 + * Defaults to false when no hook is installed (e.g. Pro
583 + * deactivated) → we strip yatra_* caps for non-admins.
584 + *
585 + * @param bool $active
586 + */
587 + $active = (bool) apply_filters('yatra_team_role_enforcement_active', false);
588 + if ($active) {
589 + return $allcaps; // defer to Team module's filter
590 + }
591 +
592 + // Strip every yatra_* cap on this user. `yatra_access_admin`
593 + // is stripped too — non-admin users shouldn't see the menu
594 + // when the module isn't actively enforcing.
595 + foreach ($allcaps as $cap => $on) {
596 + if ($on && \is_string($cap) && strpos($cap, 'yatra_') === 0) {
597 + $allcaps[$cap] = false;
598 + }
599 + }
600 + return $allcaps;
601 + }, 7, 4);
602 +
603 + // NOTE: actual removal of Yatra team roles when the Team &
604 + // Access module is disabled lives INSIDE the Pro Team module
605 + // (see TeamModule::registerAlwaysOn → 'yatra_module_deactive'
606 + // hook). That keeps role lifecycle owned by the module that
607 + // creates the roles.
608 + }
609 +
610 + /**
453 611 * Register admin menu
454 612 */
455 613 public function registerAdminMenu(): void
456 614 {
615 + self::bootstrapMenuCapability();
616 +
457 617 $menu_icon = (function_exists('yatra_get_brand_icon_url') && yatra_get_brand_icon_url() !== '')
458 618 ? yatra_get_brand_icon_url()
459 619 : 'dashicons-palmtree';
460 620
621 + $brand_name = function_exists('yatra_get_brand_name') ? yatra_get_brand_name() : 'Yatra';
622 +
623 + // Menu visibility cap. By default WP requires `manage_options`
624 + // which only WP administrators have — invisible to every
625 + // Yatra-only role (Accountant, Sales Agent, Guide, etc.). The
626 + // Team & Access module's RoleProvisioner adds `yatra_access_admin`
627 + // to every system role + grants it to admins via the user_has_cap
628 + // filter. If Pro/Team isn't installed on this site, the cap
629 + // doesn't exist anywhere → no role qualifies → behavior is
630 + // identical to the old `manage_options` gate, and admins still
631 + // pass because they have `manage_options`. So this is a strict
632 + // expansion: same admins see it as before, plus team roles when
633 + // Team & Access is on. We pass an OR-style check by using a
634 + // filter so non-Pro sites can override.
635 + $menu_cap = (string) apply_filters('yatra_admin_menu_cap', 'yatra_access_admin');
636 +
461 637 add_menu_page(
462 - __('Yatra', 'yatra'),
463 - __('Yatra', 'yatra'),
464 - 'manage_options',
638 + $brand_name,
639 + $brand_name,
640 + $menu_cap,
465 641 'yatra',
466 642 [$this, 'renderAdminPage'],
467 643 $menu_icon,
468 644 30
@@ -471,11 +647,12 @@
471 647 // WordPress would otherwise add a first submenu also titled "Yatra" (duplicate of the parent).
472 648 // A submenu with the same slug as the parent replaces that entry with a distinct label.
473 649 add_submenu_page(
474 650 'yatra',
475 - __('Yatra Dashboard', 'yatra'),
651 + /* translators: %s: branded plugin name. */
652 + sprintf(__('%s Dashboard', 'yatra'), $brand_name),
476 653 __('Dashboard', 'yatra'),
477 - 'manage_options',
654 + $menu_cap,
478 655 'yatra',
479 656 [$this, 'renderAdminPage']
480 657 );
481 658