| @@ -34,9 +34,57 @@ | ||
| 34 | 34 | * @return void |
| 35 | 35 | */ |
| 36 | 36 | public function init(): void { |
| 37 | 37 | add_action('init', [Capability_Manager::class, 'ensure']); |
| 38 | + add_filter('user_has_cap', [$this, 'grant_admin_caps'], 10, 2); | |
| 38 | 39 | add_filter('rest_pre_dispatch', [$this, 'gate_rest'], 10, 3); |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Give every ThinkRank capability to anyone who can `manage_options`. | |
| 44 | + * | |
| 45 | + * The bypass already existed, but only where the plugin could see it. | |
| 46 | + * Capability_Manager::current_user_can() is a plugin static, invisible to | |
| 47 | + * WordPress core — and add_menu_page() evaluates its capability argument | |
| 48 | + * with core's current_user_can(). Meanwhile Capability_Manager::ensure() | |
| 49 | + * grants the capabilities to the `administrator` role by name. | |
| 50 | + * | |
| 51 | + * So a user holding manage_options through some other role — a multisite | |
| 52 | + * super admin, or a custom admin role from Members / User Role Editor — | |
| 53 | + * passed every REST check and saw no ThinkRank menu at all: the plugin was | |
| 54 | + * invisible in wp-admin while fully reachable over the API (#575). | |
| 55 | + * Expressing the bypass as a core filter closes that split and makes the | |
| 56 | + * explicit check in Capability_Manager belt-and-braces rather than the only | |
| 57 | + * path. | |
| 58 | + * | |
| 59 | + * Uses `+` rather than array_merge: an entry already present keeps its | |
| 60 | + * value, so an explicit denial elsewhere is not resurrected here. | |
| 61 | + * | |
| 62 | + * @since 2.1.3 | |
| 63 | + * | |
| 64 | + * @param array $allcaps Capabilities the user holds. | |
| 65 | + * @param array $caps Capabilities being checked (unused). | |
| 66 | + * @return array | |
| 67 | + */ | |
| 68 | + public function grant_admin_caps(array $allcaps, array $caps): array { | |
| 69 | + if (empty($allcaps['manage_options'])) { | |
| 70 | + return $allcaps; | |
| 71 | + } | |
| 72 | + | |
| 73 | + // Built once per request. Uses slugs() rather than capabilities(): | |
| 74 | + // core fires this filter from wp_set_current_user() during | |
| 75 | + // wp-settings.php, before `init`, so the __() calls behind the labelled | |
| 76 | + // map are both wasted (only the keys are used) and early enough to earn | |
| 77 | + // a _load_textdomain_just_in_time notice on every request (#580). | |
| 78 | + static $thinkrank_caps = null; | |
| 79 | + if (null === $thinkrank_caps) { | |
| 80 | + $thinkrank_caps = array_fill_keys( | |
| 81 | + Capability_Manager::slugs(), | |
| 82 | + true | |
| 83 | + ); | |
| 84 | + } | |
| 85 | + | |
| 86 | + return $allcaps + $thinkrank_caps; | |
| 39 | 87 | } |
| 40 | 88 | |
| 41 | 89 | /** |
| 42 | 90 | * Central capability gate for all ThinkRank REST routes. |