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