[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/abilities-api/ -> class-wp-abilities-registry.php (source)

   1  <?php
   2  /**
   3   * Abilities API
   4   *
   5   * Defines WP_Abilities_Registry class.
   6   *
   7   * @package WordPress
   8   * @subpackage Abilities API
   9   * @since 6.9.0
  10   */
  11  
  12  declare( strict_types = 1 );
  13  
  14  /**
  15   * Manages the registration and lookup of abilities.
  16   *
  17   * @since 6.9.0
  18   * @access private
  19   */
  20  final class WP_Abilities_Registry {
  21      /**
  22       * The singleton instance of the registry.
  23       *
  24       * @since 6.9.0
  25       * @var self|null
  26       */
  27      private static $instance = null;
  28  
  29      /**
  30       * Holds the registered abilities.
  31       *
  32       * @since 6.9.0
  33       * @var WP_Ability[]
  34       */
  35      private $registered_abilities = array();
  36  
  37      /**
  38       * Registers a new ability.
  39       *
  40       * Do not use this method directly. Instead, use the `wp_register_ability()` function.
  41       *
  42       * @since 6.9.0
  43       * @since 7.1.0 Added the `public` meta argument.
  44       * @since 7.2.0 The `category` argument is now optional and defaults to `uncategorized`.
  45       * @since 7.2.0 Added the `required_capabilities` argument.
  46       *
  47       * @see wp_register_ability()
  48       *
  49       * @param string               $name The name of the ability. The name must be a string containing a namespace
  50       *                                   prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase
  51       *                                   alphanumeric characters, dashes and the forward slash.
  52       * @param array<string, mixed> $args {
  53       *     An associative array of arguments for the ability.
  54       *
  55       *     @type string               $label                 The human-readable label for the ability.
  56       *     @type string               $description           A detailed description of what the ability does.
  57       *     @type string               $category              Optional. The ability category slug this ability belongs to.
  58       *                                                       Defaults to `uncategorized`.
  59       *     @type callable             $execute_callback      A callback function to execute when the ability is invoked.
  60       *                                                       Receives optional mixed input and returns mixed result or WP_Error.
  61       *     @type callable             $permission_callback   A callback function to check permissions before execution.
  62       *                                                       Receives optional mixed input and returns bool or WP_Error.
  63       *                                                       Required unless `$required_capabilities` lists at least one capability.
  64       *     @type list<string>         $required_capabilities Optional. Minimum capabilities required for every execution of the
  65       *                                                       ability, regardless of input. They are checked before
  66       *                                                       `$permission_callback`, which handles input-dependent requirements.
  67       *     @type array<string, mixed> $input_schema          Optional. JSON Schema definition for the ability's input.
  68       *     @type array<string, mixed> $output_schema         Optional. JSON Schema definition for the ability's output.
  69       *     @type array<string, mixed> $meta                  {
  70       *         Optional. Additional metadata for the ability.
  71       *
  72       *         @type array<string, bool|null> $annotations  {
  73       *             Optional. Semantic annotations describing the ability's behavioral characteristics.
  74       *             These annotations are hints for tooling and documentation.
  75       *
  76       *             @type bool|null $readonly    Optional. If true, the ability does not modify its environment.
  77       *             @type bool|null $destructive Optional. If true, the ability may perform destructive updates to its environment.
  78       *                                          If false, the ability performs only additive updates.
  79       *             @type bool|null $idempotent  Optional. If true, calling the ability repeatedly with the same arguments
  80       *                                          will have no additional effect on its environment.
  81       *         }
  82       *         @type bool                     $public       Optional. Whether the ability is meant to be available
  83       *                                                      to clients such as the REST API, MCP, or AI agents.
  84       *                                                      Seeds the default for per-channel flags like
  85       *                                                      `$show_in_rest`. Defaults to false.
  86       *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API.
  87       *                                                      Default is the value of `$public` when set, false otherwise.
  88       *     }
  89       *     @type string               $ability_class         Optional. Custom class to instantiate instead of WP_Ability.
  90       * }
  91       * @return WP_Ability|null The registered ability instance on success, null on failure.
  92       *
  93       * @phpstan-param lowercase-string&non-falsy-string $name
  94       */
  95  	public function register( string $name, array $args ): ?WP_Ability {
  96          if ( ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $name ) ) {
  97              _doing_it_wrong(
  98                  __METHOD__,
  99                  __(
 100                      'Ability name must be a string containing a namespace prefix, i.e. "my-plugin/my-ability". It can only contain lowercase alphanumeric characters, dashes and the forward slash.'
 101                  ),
 102                  '6.9.0'
 103              );
 104              return null;
 105          }
 106  
 107          if ( $this->is_registered( $name ) ) {
 108              _doing_it_wrong(
 109                  __METHOD__,
 110                  /* translators: %s: Ability name. */
 111                  sprintf( __( 'Ability "%s" is already registered.' ), esc_html( $name ) ),
 112                  '6.9.0'
 113              );
 114              return null;
 115          }
 116  
 117          /**
 118           * Filters the ability arguments before they are validated and used to instantiate the ability.
 119           *
 120           * @since 6.9.0
 121           * @since 7.1.0 Added the `public` meta argument.
 122           * @since 7.2.0 The `category` argument is now optional and defaults to `uncategorized`.
 123           * @since 7.2.0 Added the `required_capabilities` argument.
 124           *
 125           * @param array<string, mixed> $args {
 126           *     An associative array of arguments for the ability.
 127           *
 128           *     @type string               $label                 The human-readable label for the ability.
 129           *     @type string               $description           A detailed description of what the ability does.
 130           *     @type string               $category              Optional. The ability category slug this ability belongs to.
 131           *                                                       Defaults to `uncategorized`.
 132           *     @type callable             $execute_callback      A callback function to execute when the ability is invoked.
 133           *                                                       Receives optional mixed input and returns mixed result or WP_Error.
 134           *     @type callable             $permission_callback   A callback function to check permissions before execution.
 135           *                                                       Receives optional mixed input and returns bool or WP_Error.
 136           *                                                       Required unless `$required_capabilities` lists at least one capability.
 137           *     @type list<string>         $required_capabilities Optional. Minimum capabilities required for every execution of the
 138           *                                                       ability, regardless of input. They are checked before
 139           *                                                       `$permission_callback`, which handles input-dependent requirements.
 140           *     @type array<string, mixed> $input_schema          Optional. JSON Schema definition for the ability's input.
 141           *     @type array<string, mixed> $output_schema         Optional. JSON Schema definition for the ability's output.
 142           *     @type array<string, mixed> $meta                  {
 143           *         Optional. Additional metadata for the ability.
 144           *
 145           *         @type array<string, bool|string> $annotations  Optional. Annotation metadata for the ability.
 146           *         @type bool                       $public       Optional. Whether the ability is meant to be
 147           *                                                        available to clients such as the REST API, MCP, or AI
 148           *                                                        agents. Seeds the default for per-channel flags like
 149           *                                                        `$show_in_rest`. Defaults to false.
 150           *         @type bool                       $show_in_rest Optional. Whether to expose this ability in the REST API.
 151           *                                                        Default is the value of `$public` when set, false otherwise.
 152           *     }
 153           *     @type string               $ability_class         Optional. Custom class to instantiate instead of WP_Ability.
 154           * }
 155           * @param string               $name The name of the ability, with its namespace.
 156           */
 157          $args = apply_filters( 'wp_register_ability_args', $args, $name );
 158  
 159          if ( ! array_key_exists( 'category', $args ) ) {
 160              $args['category'] = 'uncategorized';
 161          }
 162  
 163          if ( ! is_string( $args['category'] ) ) {
 164              _doing_it_wrong(
 165                  __METHOD__,
 166                  __( 'Ability category must be a string.' ),
 167                  '6.9.0'
 168              );
 169              return null;
 170          }
 171  
 172          if ( ! wp_has_ability_category( $args['category'] ) ) {
 173              _doing_it_wrong(
 174                  __METHOD__,
 175                  sprintf(
 176                      /* translators: %1$s: ability category slug, %2$s: ability name */
 177                      __( 'Ability category "%1$s" is not registered. Please register the ability category before assigning it to ability "%2$s".' ),
 178                      esc_html( $args['category'] ),
 179                      esc_html( $name )
 180                  ),
 181                  '6.9.0'
 182              );
 183              return null;
 184          }
 185  
 186          // The class is only used to instantiate the ability, and is not a property of the ability itself.
 187          if ( isset( $args['ability_class'] ) && ! is_a( $args['ability_class'], WP_Ability::class, true ) ) {
 188              _doing_it_wrong(
 189                  __METHOD__,
 190                  __( 'The ability args should provide a valid `ability_class` that extends WP_Ability.' ),
 191                  '6.9.0'
 192              );
 193              return null;
 194          }
 195  
 196          /** @var class-string<WP_Ability> */
 197          $ability_class = $args['ability_class'] ?? WP_Ability::class;
 198          unset( $args['ability_class'] );
 199  
 200          try {
 201              // WP_Ability::prepare_properties() will throw an exception if the properties are invalid.
 202              $ability = new $ability_class( $name, $args );
 203          } catch ( InvalidArgumentException $e ) {
 204              _doing_it_wrong(
 205                  __METHOD__,
 206                  $e->getMessage(),
 207                  '6.9.0'
 208              );
 209              return null;
 210          }
 211  
 212          $this->registered_abilities[ $name ] = $ability;
 213          return $ability;
 214      }
 215  
 216      /**
 217       * Unregisters an ability.
 218       *
 219       * Do not use this method directly. Instead, use the `wp_unregister_ability()` function.
 220       *
 221       * @since 6.9.0
 222       *
 223       * @see wp_unregister_ability()
 224       *
 225       * @param string $name The name of the registered ability, with its namespace.
 226       * @return WP_Ability|null The unregistered ability instance on success, null on failure.
 227       */
 228  	public function unregister( string $name ): ?WP_Ability {
 229          if ( ! $this->is_registered( $name ) ) {
 230              _doing_it_wrong(
 231                  __METHOD__,
 232                  /* translators: %s: Ability name. */
 233                  sprintf( __( 'Ability "%s" not found.' ), esc_html( $name ) ),
 234                  '6.9.0'
 235              );
 236              return null;
 237          }
 238  
 239          $unregistered_ability = $this->registered_abilities[ $name ];
 240          unset( $this->registered_abilities[ $name ] );
 241  
 242          return $unregistered_ability;
 243      }
 244  
 245      /**
 246       * Retrieves the list of all registered abilities.
 247       *
 248       * Do not use this method directly. Instead, use the `wp_get_abilities()` function.
 249       *
 250       * @since 6.9.0
 251       *
 252       * @see wp_get_abilities()
 253       *
 254       * @return WP_Ability[] The array of registered abilities.
 255       */
 256  	public function get_all_registered(): array {
 257          return $this->registered_abilities;
 258      }
 259  
 260      /**
 261       * Checks if an ability is registered.
 262       *
 263       * Do not use this method directly. Instead, use the `wp_has_ability()` function.
 264       *
 265       * @since 6.9.0
 266       *
 267       * @see wp_has_ability()
 268       *
 269       * @param string $name The name of the registered ability, with its namespace.
 270       * @return bool True if the ability is registered, false otherwise.
 271       */
 272  	public function is_registered( string $name ): bool {
 273          return isset( $this->registered_abilities[ $name ] );
 274      }
 275  
 276      /**
 277       * Retrieves a registered ability.
 278       *
 279       * Do not use this method directly. Instead, use the `wp_get_ability()` function.
 280       *
 281       * @since 6.9.0
 282       *
 283       * @see wp_get_ability()
 284       *
 285       * @param string $name The name of the registered ability, with its namespace.
 286       * @return WP_Ability|null The registered ability instance, or null if it is not registered.
 287       */
 288  	public function get_registered( string $name ): ?WP_Ability {
 289          if ( ! $this->is_registered( $name ) ) {
 290              _doing_it_wrong(
 291                  __METHOD__,
 292                  /* translators: %s: Ability name. */
 293                  sprintf( __( 'Ability "%s" not found.' ), esc_html( $name ) ),
 294                  '6.9.0'
 295              );
 296              return null;
 297          }
 298          return $this->registered_abilities[ $name ];
 299      }
 300  
 301      /**
 302       * Utility method to retrieve the main instance of the registry class.
 303       *
 304       * The instance will be created if it does not exist yet.
 305       *
 306       * @since 6.9.0
 307       *
 308       * @return WP_Abilities_Registry|null The main registry instance, or null when `init` action has not fired.
 309       */
 310  	public static function get_instance(): ?self {
 311          if ( ! did_action( 'init' ) ) {
 312              _doing_it_wrong(
 313                  __METHOD__,
 314                  sprintf(
 315                      // translators: %s: init action.
 316                      __( 'Ability API should not be initialized before the %s action has fired.' ),
 317                      '<code>init</code>'
 318                  ),
 319                  '6.9.0'
 320              );
 321              return null;
 322          }
 323  
 324          if ( null === self::$instance ) {
 325              self::$instance = new self();
 326  
 327              // Ensure ability category registry is initialized first to allow categories to be registered
 328              // before abilities that depend on them.
 329              WP_Ability_Categories_Registry::get_instance();
 330  
 331              /**
 332               * Fires when preparing abilities registry.
 333               *
 334               * Abilities should be created and register their hooks on this action rather
 335               * than another action to ensure they're only loaded when needed.
 336               *
 337               * @since 6.9.0
 338               *
 339               * @param WP_Abilities_Registry $instance Abilities registry object.
 340               */
 341              do_action( 'wp_abilities_api_init', self::$instance );
 342          }
 343  
 344          return self::$instance;
 345      }
 346  
 347      /**
 348       * Wakeup magic method.
 349       *
 350       * @since 6.9.0
 351       * @throws LogicException If the registry object is unserialized.
 352       *                        This is a security hardening measure to prevent unserialization of the registry.
 353       */
 354  	public function __wakeup(): void {
 355          throw new LogicException( __CLASS__ . ' should never be unserialized.' );
 356      }
 357  
 358      /**
 359       * Sleep magic method.
 360       *
 361       * @since 6.9.0
 362       * @throws LogicException If the registry object is serialized.
 363       *                        This is a security hardening measure to prevent serialization of the registry.
 364       */
 365  	public function __sleep(): array {
 366          throw new LogicException( __CLASS__ . ' should never be serialized.' );
 367      }
 368  }


Generated : Wed Nov 4 08:20:38 2026 Cross-referenced by PHPXref