| @@ -36,47 +36,92 @@ | ||
| 36 | 36 | return window.yatraAdmin?.isPro || false; |
| 37 | 37 | }, []); |
| 38 | 38 | |
| 39 | 39 | /** |
| 40 | - * Check if user has a specific capability | |
| 40 | + * Check if the current user has a specific capability. | |
| 41 | + * | |
| 42 | + * Sources, in order: | |
| 43 | + * 1. `window.yatraAdmin.capabilities` — server-built map of every | |
| 44 | + * `yatra_*` cap the user passes via WP-native `current_user_can`. | |
| 45 | + * Built by AdminAssetsProvider from `$current_user->allcaps`, | |
| 46 | + * filtered to the `yatra_` prefix. | |
| 47 | + * 2. `window.yatraAdmin.userCaps` — Team module's enriched list, | |
| 48 | + * includes the same caps + anything added via the user_has_cap | |
| 49 | + * filter (admin fallback, per-user grants, derived caps). | |
| 50 | + * 3. `window.yatraAdmin.isWpAdmin` — last-resort fallback for site | |
| 51 | + * owners so they're never locked out of anything. | |
| 52 | + * | |
| 53 | + * Default-deny when none match. The previous implementation had a | |
| 54 | + * hardcoded "every user gets yatra_view_trips / yatra_edit_trips / | |
| 55 | + * yatra_view_bookings" map that broke the Team & Access module — an | |
| 56 | + * Accountant calling `can('yatra_edit_trips')` returned true via that | |
| 57 | + * defaults map, leaking Tools / Modules / etc. into their UI even | |
| 58 | + * though their role doesn't grant edit_trips. | |
| 59 | + * | |
| 41 | 60 | * @param capability - Capability to check |
| 42 | 61 | * @returns True if user has capability |
| 43 | 62 | */ |
| 44 | 63 | const can = useCallback( |
| 45 | 64 | (capability: string): boolean => { |
| 46 | - // Check direct capabilities | |
| 65 | + // 1) Server-built per-user cap map (yatra_* only). | |
| 47 | 66 | if (capabilities[capability] === true) { |
| 48 | 67 | return true; |
| 49 | 68 | } |
| 50 | 69 | |
| 51 | - // Check permissions array | |
| 70 | + // 2) Team module's enriched userCaps list. | |
| 71 | + const userCaps = ( | |
| 72 | + window.yatraAdmin as { userCaps?: string[] } | undefined | |
| 73 | + )?.userCaps; | |
| 74 | + if (Array.isArray(userCaps) && userCaps.includes(capability)) { | |
| 75 | + return true; | |
| 76 | + } | |
| 77 | + | |
| 78 | + // 3) Legacy permissions array (kept for back-compat with any | |
| 79 | + // code path that pre-dates the capabilities map). | |
| 52 | 80 | if (permissions.includes(capability)) { |
| 53 | 81 | return true; |
| 54 | 82 | } |
| 55 | 83 | |
| 56 | - // Check for Pro-only features | |
| 84 | + // 4) WP admin fallback — site owners pass everything. | |
| 85 | + // | |
| 86 | + // Belt-and-suspenders: we check THREE signals in case one of | |
| 87 | + // them goes missing on a particular install. All three are | |
| 88 | + // injected by AdminAssetsProvider, but defense in depth is | |
| 89 | + // cheap and protects against: | |
| 90 | + // - server-side filters stripping isWpAdmin from localized data | |
| 91 | + // - JSON-encoding edge cases where booleans get coerced | |
| 92 | + // - roles arrays that contain admin even when capabilities map | |
| 93 | + // was built from a stale $current_user (page-cache + role | |
| 94 | + // changes) | |
| 95 | + const adm = window.yatraAdmin as | |
| 96 | + | { | |
| 97 | + isWpAdmin?: unknown; | |
| 98 | + roles?: unknown; | |
| 99 | + capabilities?: Record<string, unknown>; | |
| 100 | + } | |
| 101 | + | undefined; | |
| 102 | + if (adm) { | |
| 103 | + if ( | |
| 104 | + adm.isWpAdmin === true || | |
| 105 | + adm.isWpAdmin === "1" || | |
| 106 | + adm.isWpAdmin === 1 | |
| 107 | + ) { | |
| 108 | + return true; | |
| 109 | + } | |
| 110 | + if (Array.isArray(adm.roles) && adm.roles.includes("administrator")) { | |
| 111 | + return true; | |
| 112 | + } | |
| 113 | + if (adm.capabilities && adm.capabilities["manage_options"] === true) { | |
| 114 | + return true; | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + // 5) Pro-only check stays for completeness. | |
| 57 | 119 | if (capability.startsWith("yatra_pro_") && !isPro) { |
| 58 | 120 | return false; |
| 59 | 121 | } |
| 60 | 122 | |
| 61 | - // Default capabilities | |
| 62 | - const defaultCapabilities: Record<string, boolean> = { | |
| 63 | - manage_yatra: true, // Default admin capability | |
| 64 | - yatra_view_trips: true, | |
| 65 | - yatra_edit_trips: true, | |
| 66 | - yatra_delete_trips: true, | |
| 67 | - yatra_view_bookings: true, | |
| 68 | - yatra_edit_bookings: true, | |
| 69 | - yatra_delete_bookings: true, | |
| 70 | - yatra_view_customers: true, | |
| 71 | - yatra_edit_customers: true, | |
| 72 | - yatra_delete_customers: true, | |
| 73 | - yatra_view_reviews: true, | |
| 74 | - yatra_edit_reviews: true, | |
| 75 | - yatra_delete_reviews: true, | |
| 76 | - }; | |
| 77 | - | |
| 78 | - return defaultCapabilities[capability] || false; | |
| 123 | + return false; | |
| 79 | 124 | }, |
| 80 | 125 | [capabilities, permissions, isPro], |
| 81 | 126 | ); |
| 82 | 127 | |