*/ private const CONTEXT_MAP = array( 'discover' => 'discover', 'info' => 'get_info', 'execute' => 'execute', ); /** * Singleton accessor. * * @since 0.1.1 * @return self */ public static function instance(): self { if ( null === self::$_instance ) { self::$_instance = new self(); } return self::$_instance; } /** * `acrossai_toolset_member_visible` callback. * * Narrows only. An ability the sibling has already decided to hide stays * hidden — this is a second gate on the same catalogue, not a chance to * re-admit something, which keeps deny-precedence the same here as in * `ToolExposureGate`. * * Outside an MCP request there is no server whose policy could apply, so * the decision is left alone: WP-CLI, cron and a direct * `wp_get_ability( 'toolset/users' )->execute()` behave as they did. * * **Exposure is not authorization.** Returning true does not bypass the * target ability's own `permission_callback`; the Toolset still runs it * before dispatch and again inside `WP_Ability::execute()`. * * @since 0.1.1 * * @param bool $visible Sibling's decision so far. * @param \WP_Ability $ability The member being considered. * @param string $group Toolset group being listed (unused). * @param string $context One of 'discover' | 'info' | 'execute'. * @return bool */ public function filter_member_visible( $visible, $ability, $group, $context ): bool { unset( $group ); if ( ! $visible ) { return false; } if ( ! $ability instanceof WP_Ability ) { return (bool) $visible; } if ( null === CurrentServerHolder::instance()->get_server_id() ) { return (bool) $visible; } return self::apply_exposure_filter( $ability, self::CONTEXT_MAP[ $context ] ?? 'discover' ); } }