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