PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.89
WindPress – Tailwind CSS integration for WordPress v3.2.89
3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 All 143 releases
windpress / vendor / wordpress / abilities-api / includes / abilities-api.php

abilities-api.php in WindPress – Tailwind CSS integration for WordPress 3.2.89, at vendor/wordpress/abilities-api/includes/abilities-api.php

608 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abilities API: core functions for registering and managing abilities.
4 *
5 * The Abilities API provides a unified, extensible framework for registering
6 * and executing discrete capabilities within WordPress. An "ability" is a
7 * self-contained unit of functionality with defined inputs, outputs, permissions,
8 * and execution logic.
9 *
10 * ## Overview
11 *
12 * The Abilities API enables developers to:
13 *
14 * - Register custom abilities with standardized interfaces.
15 * - Define permission checks and execution callbacks.
16 * - Organize abilities into logical categories.
17 * - Validate inputs and outputs using JSON Schema.
18 * - Expose abilities through the REST API.
19 *
20 * ## Working with Abilities
21 *
22 * Abilities must be registered on the `wp_abilities_api_init` action hook.
23 * Attempting to register an ability outside of this hook will fail and
24 * trigger a `_doing_it_wrong()` notice.
25
26 * Example:
27 *
28 * function my_plugin_register_abilities(): void {
29 * wp_register_ability(
30 * 'my-plugin/export-users',
31 * array(
32 * 'label' => __( 'Export Users', 'my-plugin' ),
33 * 'description' => __( 'Exports user data to CSV format.', 'my-plugin' ),
34 * 'category' => 'data-export',
35 * 'execute_callback' => 'my_plugin_export_users',
36 * 'permission_callback' => function(): bool {
37 * return current_user_can( 'export' );
38 * },
39 * 'input_schema' => array(
40 * 'type' => 'string',
41 * 'enum' => array( 'subscriber', 'contributor', 'author', 'editor', 'administrator' ),
42 * 'description' => __( 'Limits the export to users with this role.', 'my-plugin' ),
43 * 'required' => false,
44 * ),
45 * 'output_schema' => array(
46 * 'type' => 'string',
47 * 'description' => __( 'User data in CSV format.', 'my-plugin' ),
48 * 'required' => true,
49 * ),
50 * 'meta' => array(
51 * 'show_in_rest' => true,
52 * ),
53 * )
54 * );
55 * }
56 * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' );
57 *
58 * Once registered, abilities can be checked, retrieved, and managed:
59 *
60 * // Checks if an ability is registered, and prints its label.
61 * if ( wp_has_ability( 'my-plugin/export-users' ) ) {
62 * $ability = wp_get_ability( 'my-plugin/export-users' );
63 *
64 * echo $ability->get_label();
65 * }
66 *
67 * // Gets all registered abilities.
68 * $all_abilities = wp_get_abilities();
69 *
70 * // Unregisters when no longer needed.
71 * wp_unregister_ability( 'my-plugin/export-users' );
72 *
73 * ## Best Practices
74 *
75 * - Always register abilities on the `wp_abilities_api_init` hook.
76 * - Use namespaced ability names to prevent conflicts.
77 * - Implement robust permission checks in permission callbacks.
78 * - Provide an `input_schema` to ensure data integrity and document expected inputs.
79 * - Define an `output_schema` to describe return values and validate responses.
80 * - Return `WP_Error` objects for failures rather than throwing exceptions.
81 * - Use internationalization functions for all user-facing strings.
82 *
83 * @package WordPress
84 * @subpackage Abilities_API
85 * @since 6.9.0
86 */
87
88 declare( strict_types = 1 );
89
90 /**
91 * Registers a new ability using the Abilities API. It requires three steps:
92 *
93 * 1. Hook into the `wp_abilities_api_init` action.
94 * 2. Call `wp_register_ability()` with a namespaced name and configuration.
95 * 3. Provide execute and permission callbacks.
96 *
97 * Example:
98 *
99 * function my_plugin_register_abilities(): void {
100 * wp_register_ability(
101 * 'my-plugin/analyze-text',
102 * array(
103 * 'label' => __( 'Analyze Text', 'my-plugin' ),
104 * 'description' => __( 'Performs sentiment analysis on provided text.', 'my-plugin' ),
105 * 'category' => 'text-processing',
106 * 'input_schema' => array(
107 * 'type' => 'string',
108 * 'description' => __( 'The text to be analyzed.', 'my-plugin' ),
109 * 'minLength' => 10,
110 * 'required' => true,
111 * ),
112 * 'output_schema' => array(
113 * 'type' => 'string',
114 * 'enum' => array( 'positive', 'negative', 'neutral' ),
115 * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ),
116 * 'required' => true,
117 * ),
118 * 'execute_callback' => 'my_plugin_analyze_text',
119 * 'permission_callback' => 'my_plugin_can_analyze_text',
120 * 'meta' => array(
121 * 'annotations' => array(
122 * 'readonly' => true,
123 * ),
124 * 'show_in_rest' => true,
125 * ),
126 * )
127 * );
128 * }
129 * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' );
130 *
131 * ### Naming Conventions
132 *
133 * Ability names must follow these rules:
134 *
135 * - Include a namespace prefix (e.g., `my-plugin/my-ability`).
136 * - Use only lowercase alphanumeric characters, dashes, and forward slashes.
137 * - Use descriptive, action-oriented names (e.g., `process-payment`, `generate-report`).
138 *
139 * ### Categories
140 *
141 * Abilities must be organized into categories. Ability categories provide better
142 * discoverability and must be registered before the abilities that reference them:
143 *
144 * function my_plugin_register_categories(): void {
145 * wp_register_ability_category(
146 * 'text-processing',
147 * array(
148 * 'label' => __( 'Text Processing', 'my-plugin' ),
149 * 'description' => __( 'Abilities for analyzing and transforming text.', 'my-plugin' ),
150 * )
151 * );
152 * }
153 * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
154 *
155 * ### Input and Output Schemas
156 *
157 * Schemas define the expected structure, type, and constraints for ability inputs
158 * and outputs using JSON Schema syntax. They serve two critical purposes: automatic
159 * validation of data passed to and returned from abilities, and self-documenting
160 * API contracts for developers.
161 *
162 * WordPress implements a validator based on a subset of the JSON Schema Version 4
163 * specification (https://json-schema.org/specification-links.html#draft-4).
164 * For details on supported JSON Schema properties and syntax, see the
165 * related WordPress REST API Schema documentation:
166 * https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#json-schema-basics
167 *
168 * Defining schemas is mandatory when there is a value to pass or return.
169 * They ensure data integrity, improve developer experience, and enable
170 * better documentation:
171 *
172 * 'input_schema' => array(
173 * 'type' => 'string',
174 * 'description' => __( 'The text to be analyzed.', 'my-plugin' ),
175 * 'minLength' => 10,
176 * 'required' => true,
177 * ),
178 * 'output_schema' => array(
179 * 'type' => 'string',
180 * 'enum' => array( 'positive', 'negative', 'neutral' ),
181 * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ),
182 * 'required' => true,
183 * ),
184 *
185 * ### Callbacks
186 *
187 * #### Execute Callback
188 *
189 * The execute callback performs the ability's core functionality. It receives
190 * optional input data and returns either a result or `WP_Error` on failure.
191 *
192 * function my_plugin_analyze_text( string $input ): string|WP_Error {
193 * $score = My_Plugin::perform_sentiment_analysis( $input );
194 * if ( is_wp_error( $score ) ) {
195 * return $score;
196 * }
197 * return My_Plugin::interpret_sentiment_score( $score );
198 * }
199 *
200 * #### Permission Callback
201 *
202 * The permission callback determines whether the ability can be executed.
203 * It receives the same input as the execute callback and must return a
204 * boolean or `WP_Error`. Common use cases include checking user capabilities,
205 * validating API keys, or verifying system state:
206 *
207 * function my_plugin_can_analyze_text( string $input ): bool|WP_Error {
208 * return current_user_can( 'edit_posts' );
209 * }
210 *
211 * ### REST API Integration
212 *
213 * Abilities can be exposed through the REST API by setting `show_in_rest`
214 * to `true` in the meta configuration:
215 *
216 * 'meta' => array(
217 * 'show_in_rest' => true,
218 * ),
219 *
220 * This allows abilities to be invoked via HTTP requests to the WordPress REST API.
221 *
222 * @since 6.9.0
223 *
224 * @see WP_Abilities_Registry::register()
225 * @see wp_register_ability_category()
226 * @see wp_unregister_ability()
227 *
228 * @param string $name The name of the ability. Must be a namespaced string containing
229 * a prefix, e.g., `my-plugin/my-ability`. Can only contain lowercase
230 * alphanumeric characters, dashes, and forward slashes.
231 * @param array<string, mixed> $args {
232 * An associative array of arguments for configuring the ability.
233 *
234 * @type string $label Required. The human-readable label for the ability.
235 * @type string $description Required. A detailed description of what the ability does
236 * and when it should be used.
237 * @type string $category Required. The ability category slug this ability belongs to.
238 * The ability category must be registered via `wp_register_ability_category()`
239 * before registering the ability.
240 * @type callable $execute_callback Required. A callback function to execute when the ability is invoked.
241 * Receives optional mixed input data and must return either a result
242 * value (any type) or a `WP_Error` object on failure.
243 * @type callable $permission_callback Required. A callback function to check permissions before execution.
244 * Receives optional mixed input data (same as `execute_callback`) and
245 * must return `true`/`false` for simple checks, or `WP_Error` for
246 * detailed error responses.
247 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for validating the ability's input.
248 * Must be a valid JSON Schema object defining the structure and
249 * constraints for input data. Used for automatic validation and
250 * API documentation.
251 * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
252 * Describes the structure of successful return values from
253 * `execute_callback`. Used for documentation and validation.
254 * @type array<string, mixed> $meta {
255 * Optional. Additional metadata for the ability.
256 *
257 * @type array<string, bool|null> $annotations Optional. Annotation metadata for the ability. Provides
258 * additional semantic information about the ability's
259 * characteristics and behavior.
260 * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
261 * When true, the ability can be invoked via HTTP requests.
262 * Default false.
263 * }
264 * @type string $ability_class Optional. Fully-qualified custom class name to instantiate
265 * instead of the default `WP_Ability` class. The custom class
266 * must extend `WP_Ability`. Useful for advanced customization
267 * of ability behavior.
268 * }
269 * @return WP_Ability|null The registered ability instance on success, `null` on failure.
270 */
271 function wp_register_ability( string $name, array $args ): ?WP_Ability {
272 if ( ! did_action( 'wp_abilities_api_init' ) ) {
273 _doing_it_wrong(
274 __FUNCTION__,
275 sprintf(
276 /* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */
277 __( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
278 '<code>wp_abilities_api_init</code>',
279 '<code>' . esc_html( $name ) . '</code>'
280 ),
281 '6.9.0'
282 );
283 return null;
284 }
285
286 $registry = WP_Abilities_Registry::get_instance();
287 if ( null === $registry ) {
288 return null;
289 }
290
291 return $registry->register( $name, $args );
292 }
293
294 /**
295 * Unregisters an ability from the Abilities API.
296 *
297 * Removes a previously registered ability from the global registry. Use this to
298 * disable abilities provided by other plugins or when an ability is no longer needed.
299 *
300 * Can be called at any time after the ability has been registered.
301 *
302 * Example:
303 *
304 * if ( wp_has_ability( 'other-plugin/some-ability' ) ) {
305 * wp_unregister_ability( 'other-plugin/some-ability' );
306 * }
307 *
308 * @since 6.9.0
309 *
310 * @see WP_Abilities_Registry::unregister()
311 * @see wp_register_ability()
312 *
313 * @param string $name The name of the ability to unregister, including namespace prefix
314 * (e.g., 'my-plugin/my-ability').
315 * @return WP_Ability|null The unregistered ability instance on success, `null` on failure.
316 */
317 function wp_unregister_ability( string $name ): ?WP_Ability {
318 $registry = WP_Abilities_Registry::get_instance();
319 if ( null === $registry ) {
320 return null;
321 }
322
323 return $registry->unregister( $name );
324 }
325
326 /**
327 * Checks if an ability is registered.
328 *
329 * Use this for conditional logic and feature detection before attempting to
330 * retrieve or use an ability.
331 *
332 * Example:
333 *
334 * // Displays different UI based on available abilities.
335 * if ( wp_has_ability( 'premium-plugin/advanced-export' ) ) {
336 * echo 'Export with Premium Features';
337 * } else {
338 * echo 'Basic Export';
339 * }
340 *
341 * @since 6.9.0
342 *
343 * @see WP_Abilities_Registry::is_registered()
344 * @see wp_get_ability()
345 *
346 * @param string $name The name of the ability to check, including namespace prefix
347 * (e.g., 'my-plugin/my-ability').
348 * @return bool `true` if the ability is registered, `false` otherwise.
349 */
350 function wp_has_ability( string $name ): bool {
351 $registry = WP_Abilities_Registry::get_instance();
352 if ( null === $registry ) {
353 return false;
354 }
355
356 return $registry->is_registered( $name );
357 }
358
359 /**
360 * Retrieves a registered ability.
361 *
362 * Returns the ability instance for inspection or use. The instance provides access
363 * to the ability's configuration, metadata, and execution methods.
364 *
365 * Example:
366 *
367 * // Prints information about a registered ability.
368 * $ability = wp_get_ability( 'my-plugin/export-data' );
369 * if ( $ability ) {
370 * echo $ability->get_label() . ': ' . $ability->get_description();
371 * }
372 *
373 * @since 6.9.0
374 *
375 * @see WP_Abilities_Registry::get_registered()
376 * @see wp_has_ability()
377 *
378 * @param string $name The name of the ability, including namespace prefix
379 * (e.g., 'my-plugin/my-ability').
380 * @return WP_Ability|null The registered ability instance, or `null` if not registered.
381 */
382 function wp_get_ability( string $name ): ?WP_Ability {
383 $registry = WP_Abilities_Registry::get_instance();
384 if ( null === $registry ) {
385 return null;
386 }
387
388 return $registry->get_registered( $name );
389 }
390
391 /**
392 * Retrieves all registered abilities.
393 *
394 * Returns an array of all ability instances currently registered in the system.
395 * Use this for discovery, debugging, or building administrative interfaces.
396 *
397 * Example:
398 *
399 * // Prints information about all available abilities.
400 * $abilities = wp_get_abilities();
401 * foreach ( $abilities as $ability ) {
402 * echo $ability->get_label() . ': ' . $ability->get_description() . "\n";
403 * }
404 *
405 * @since 6.9.0
406 *
407 * @see WP_Abilities_Registry::get_all_registered()
408 *
409 * @return WP_Ability[] An array of registered WP_Ability instances. Returns an empty
410 * array if no abilities are registered or if the registry is unavailable.
411 */
412 function wp_get_abilities(): array {
413 $registry = WP_Abilities_Registry::get_instance();
414 if ( null === $registry ) {
415 return array();
416 }
417
418 return $registry->get_all_registered();
419 }
420
421 /**
422 * Registers a new ability category.
423 *
424 * Ability categories provide a way to organize and group related abilities for better
425 * discoverability and management. Ability categories must be registered before abilities
426 * that reference them.
427 *
428 * Ability categories must be registered on the `wp_abilities_api_categories_init` action hook.
429 *
430 * Example:
431 *
432 * function my_plugin_register_categories() {
433 * wp_register_ability_category(
434 * 'content-management',
435 * array(
436 * 'label' => __( 'Content Management', 'my-plugin' ),
437 * 'description' => __( 'Abilities for managing and organizing content.', 'my-plugin' ),
438 * )
439 * );
440 * }
441 * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' );
442 *
443 * @since 6.9.0
444 *
445 * @see WP_Ability_Categories_Registry::register()
446 * @see wp_register_ability()
447 * @see wp_unregister_ability_category()
448 *
449 * @param string $slug The unique slug for the ability category. Must contain only lowercase
450 * alphanumeric characters and dashes (e.g., 'data-export').
451 * @param array<string, mixed> $args {
452 * An associative array of arguments for the ability category.
453 *
454 * @type string $label Required. The human-readable label for the ability category.
455 * @type string $description Required. A description of what abilities in this category do.
456 * @type array<string, mixed> $meta Optional. Additional metadata for the ability category.
457 * }
458 * @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure.
459 */
460 function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
461 if ( ! did_action( 'wp_abilities_api_categories_init' ) ) {
462 _doing_it_wrong(
463 __FUNCTION__,
464 sprintf(
465 /* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */
466 __( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ),
467 '<code>wp_abilities_api_categories_init</code>',
468 '<code>' . esc_html( $slug ) . '</code>'
469 ),
470 '6.9.0'
471 );
472 return null;
473 }
474
475 $registry = WP_Ability_Categories_Registry::get_instance();
476 if ( null === $registry ) {
477 return null;
478 }
479
480 return $registry->register( $slug, $args );
481 }
482
483 /**
484 * Unregisters an ability category.
485 *
486 * Removes a previously registered ability category from the global registry. Use this to
487 * disable ability categories that are no longer needed.
488 *
489 * Can be called at any time after the ability category has been registered.
490 *
491 * Example:
492 *
493 * if ( wp_has_ability_category( 'deprecated-category' ) ) {
494 * wp_unregister_ability_category( 'deprecated-category' );
495 * }
496 *
497 * @since 6.9.0
498 *
499 * @see WP_Ability_Categories_Registry::unregister()
500 * @see wp_register_ability_category()
501 *
502 * @param string $slug The slug of the ability category to unregister.
503 * @return WP_Ability_Category|null The unregistered ability category instance on success, `null` on failure.
504 */
505 function wp_unregister_ability_category( string $slug ): ?WP_Ability_Category {
506 $registry = WP_Ability_Categories_Registry::get_instance();
507 if ( null === $registry ) {
508 return null;
509 }
510
511 return $registry->unregister( $slug );
512 }
513
514 /**
515 * Checks if an ability category is registered.
516 *
517 * Use this for conditional logic and feature detection before attempting to
518 * retrieve or use an ability category.
519 *
520 * Example:
521 *
522 * // Displays different UI based on available ability categories.
523 * if ( wp_has_ability_category( 'premium-features' ) ) {
524 * echo 'Premium Features Available';
525 * } else {
526 * echo 'Standard Features';
527 * }
528 *
529 * @since 6.9.0
530 *
531 * @see WP_Ability_Categories_Registry::is_registered()
532 * @see wp_get_ability_category()
533 *
534 * @param string $slug The slug of the ability category to check.
535 * @return bool `true` if the ability category is registered, `false` otherwise.
536 */
537 function wp_has_ability_category( string $slug ): bool {
538 $registry = WP_Ability_Categories_Registry::get_instance();
539 if ( null === $registry ) {
540 return false;
541 }
542
543 return $registry->is_registered( $slug );
544 }
545
546 /**
547 * Retrieves a registered ability category.
548 *
549 * Returns the ability category instance for inspection or use. The instance provides access
550 * to the ability category's configuration and metadata.
551 *
552 * Example:
553 *
554 * // Prints information about a registered ability category.
555 * $ability_category = wp_get_ability_category( 'content-management' );
556 * if ( $ability_category ) {
557 * echo $ability_category->get_label() . ': ' . $ability_category->get_description();
558 * }
559 *
560 * @since 6.9.0
561 *
562 * @see WP_Ability_Categories_Registry::get_registered()
563 * @see wp_has_ability_category()
564 * @see wp_get_ability_categories()
565 *
566 * @param string $slug The slug of the ability category.
567 * @return WP_Ability_Category|null The ability category instance, or `null` if not registered.
568 */
569 function wp_get_ability_category( string $slug ): ?WP_Ability_Category {
570 $registry = WP_Ability_Categories_Registry::get_instance();
571 if ( null === $registry ) {
572 return null;
573 }
574
575 return $registry->get_registered( $slug );
576 }
577
578 /**
579 * Retrieves all registered ability categories.
580 *
581 * Returns an array of all ability category instances currently registered in the system.
582 * Use this for discovery, debugging, or building administrative interfaces.
583 *
584 * Example:
585 *
586 * // Prints information about all available ability categories.
587 * $ability_categories = wp_get_ability_categories();
588 * foreach ( $ability_categories as $ability_category ) {
589 * echo $ability_category->get_label() . ': ' . $ability_category->get_description() . "\n";
590 * }
591 *
592 * @since 6.9.0
593 *
594 * @see WP_Ability_Categories_Registry::get_all_registered()
595 * @see wp_get_ability_category()
596 *
597 * @return WP_Ability_Category[] An array of registered ability category instances. Returns an empty array
598 * if no ability categories are registered or if the registry is unavailable.
599 */
600 function wp_get_ability_categories(): array {
601 $registry = WP_Ability_Categories_Registry::get_instance();
602 if ( null === $registry ) {
603 return array();
604 }
605
606 return $registry->get_all_registered();
607 }
608