}> */ private static array $stack = []; /** * Runs $callback as the credential's user under the ceiling, then restores the previous user and filters. * * @template T * @param callable(): T $callback * @return T */ public static function run(array $auth, callable $callback): mixed { $userId = (int) ($auth['user_id'] ?? 0); $originalUser = get_current_user_id(); self::$stack[] = ['user_id' => $userId, 'denied' => array_fill_keys(self::denied($auth), true)]; if (count(self::$stack) === 1) { add_filter('map_meta_cap', [self::class, 'filterMetaCaps'], PHP_INT_MAX, 4); add_filter('user_has_cap', [self::class, 'filterUserCaps'], PHP_INT_MAX, 4); } wp_set_current_user($userId); // kses decides its filters from the current user; set_current_user does not fire when the user is the same. kses_init(); try { return $callback(); } finally { array_pop(self::$stack); if (!self::$stack) { remove_filter('map_meta_cap', [self::class, 'filterMetaCaps'], PHP_INT_MAX); remove_filter('user_has_cap', [self::class, 'filterUserCaps'], PHP_INT_MAX); } wp_set_current_user($originalUser); kses_init(); } } /** @return list The capabilities denied to this credential (the full list unless an add-on relaxed it). */ public static function denied(array $auth): array { $denied = apply_filters('llamly_mcp_capability_ceiling', self::DENIED, $auth); if (!is_array($denied)) { return self::DENIED; } return array_values(array_unique(array_filter(array_map('strval', $denied), static fn(string $cap): bool => $cap !== ''))); } /** * What the user's role allows, ignoring the ceiling for this one check. Only for read visibility that must stay as it * was before the ceiling (which users' profiles and e-mails a connection may read); never for a write or a tool gate. */ public static function roleCan(int $userId, string $capability, mixed ...$args): bool { self::$stack[] = ['user_id' => 0, 'denied' => []]; try { return user_can($userId, $capability, ...$args); } finally { array_pop(self::$stack); } } public static function active(): bool { return self::$stack !== []; } /** * Primary gate: a capability check of the credential's user that is, or maps to, a denied capability resolves to * do_not_allow. This also covers multisite super admins, for whom WordPress skips user_has_cap. * * @param list $caps * @return list */ public static function filterMetaCaps($caps, $cap, $userId, $args): array { $caps = is_array($caps) ? $caps : []; $denied = self::deniedFor((int) $userId); if ($denied !== null && (isset($denied[(string) $cap]) || array_intersect_key(array_flip(array_map('strval', $caps)), $denied))) { return ['do_not_allow']; } return $caps; } /** * Second gate for code that checks the primitive capability list directly. * * @param array $allcaps * @return array */ public static function filterUserCaps($allcaps, $caps, $args, $user): array { $allcaps = is_array($allcaps) ? $allcaps : []; $denied = $user instanceof \WP_User ? self::deniedFor((int) $user->ID) : null; if ($denied !== null) { foreach ($denied as $cap => $unused) { $allcaps[$cap] = false; } } return $allcaps; } /** @return array|null */ private static function deniedFor(int $userId): ?array { $top = end(self::$stack); return $top !== false && $userId > 0 && $top['user_id'] === $userId ? $top['denied'] : null; } }