*/ private array $permission_errors = []; /** * Returns the plugin instance. * * @return Plugin */ public static function instance(): Plugin { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Hooks into WordPress. Does nothing when the Abilities API isn't available (WordPress < 6.9). * * @return void */ public function init() { if ( ! function_exists( 'wp_register_ability' ) ) { return; } add_action( 'wp_abilities_api_categories_init', [ $this, 'register_categories' ] ); add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] ); if ( is_admin() ) { ( new Settings( $this ) )->init(); } } /** * Returns the ability groups. Each group is registered as one ability category. * * @return Abilities\Group[] */ public function get_groups(): array { if ( null === $this->groups ) { $this->groups = [ new Abilities\Content(), new Abilities\Taxonomies(), new Abilities\Blocks(), new Abilities\SiteEditor(), new Abilities\Navigation(), new Abilities\Media(), new Abilities\Comments(), new Abilities\Users(), new Abilities\Site(), new Abilities\Audit(), ]; } return $this->groups; } /** * Registers one ability category per group. * * @return void */ public function register_categories() { foreach ( $this->get_groups() as $group ) { wp_register_ability_category( $group->get_category(), [ 'label' => $group->get_label(), 'description' => $group->get_description(), ] ); } } /** * Registers all enabled abilities. * * @return void */ public function register_abilities() { foreach ( $this->get_groups() as $group ) { foreach ( $group->get_abilities() as $slug => $args ) { $name = self::PREFIX . '/' . $slug; if ( ! $this->is_enabled( $name, $group->get_slug() ) ) { continue; } $args['category'] = $group->get_category(); $args['meta'] = array_merge( [ 'public' => true, 'show_in_rest' => true, ], $args['meta'] ?? [] ); /** * Filters the arguments of a Seeb Abilities ability before it is registered. * * @param array $args Ability arguments, as passed to wp_register_ability(). * @param string $name Ability name, e.g. "seeb-abilities/post-create". */ $args = apply_filters( 'seeb_abilities_ability_args', $args, $name ); wp_register_ability( $name, $this->forward_permission_errors( $name, $args ) ); } } } /** * Makes the reason a permission check failed reach the caller. * * WP_Ability::execute() replaces a WP_Error from the permission callback with a generic * "does not have necessary permission" error, so "post not found" or "use the draft status" * would never reach the agent. Instead, the permission callback passes and the execute * callback returns the error without running. * * @param string $name Ability name. * @param array $args Ability arguments. * * @return array */ private function forward_permission_errors( string $name, array $args ): array { $permission_callback = $args['permission_callback'] ?? null; $execute_callback = $args['execute_callback'] ?? null; if ( ! is_callable( $permission_callback ) || ! is_callable( $execute_callback ) ) { return $args; } $args['permission_callback'] = function ( ...$input ) use ( $name, $permission_callback ) { $result = $permission_callback( ...$input ); $this->permission_errors[ $name ] = is_wp_error( $result ) ? $result : null; return is_wp_error( $result ) || (bool) $result; }; $args['execute_callback'] = function ( ...$input ) use ( $name, $execute_callback ) { $error = $this->permission_errors[ $name ] ?? null; unset( $this->permission_errors[ $name ] ); return $error ?? $execute_callback( ...$input ); }; return $args; } /** * Whether an ability is enabled. * * @param string $name Ability name, e.g. "seeb-abilities/post-create". * @param string $group Group slug, e.g. "content". * * @return bool */ public function is_enabled( string $name, string $group ): bool { $settings = Settings::get(); $enabled = ! in_array( $group, $settings['disabled_categories'], true ) && ! in_array( $name, $settings['disabled_abilities'], true ); /** * Filters whether a Seeb Abilities ability is registered. * * @param bool $enabled Whether the ability is enabled in the settings. * @param string $name Ability name, e.g. "seeb-abilities/post-create". * @param string $group Group slug, e.g. "content". */ return (bool) apply_filters( 'seeb_abilities_enabled', $enabled, $name, $group ); } }