| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Nov 4 08:20:38 2026 | Cross-referenced by PHPXref |