Skip to content

Commit 9906821

Browse files
committed
Sync changes from the v0.3.0 release
1 parent fd7412b commit 9906821

15 files changed

+2238
-100
lines changed

src/wp-includes/abilities-api.php

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,23 @@
2424
* prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase
2525
* alphanumeric characters, dashes and the forward slash.
2626
* @param array<string,mixed> $args An associative array of arguments for the ability. This should include
27-
* `label`, `description`, `input_schema`, `output_schema`, `execute_callback`,
27+
* `label`, `description`, `category`, `input_schema`, `output_schema`, `execute_callback`,
2828
* `permission_callback`, `meta`, and `ability_class`.
2929
* @return ?\WP_Ability An instance of registered ability on success, null on failure.
3030
*
3131
* @phpstan-param array{
3232
* label?: string,
3333
* description?: string,
34+
* category?: string,
3435
* execute_callback?: callable( mixed $input= ): (mixed|\WP_Error),
3536
* permission_callback?: callable( mixed $input= ): (bool|\WP_Error),
3637
* input_schema?: array<string,mixed>,
3738
* output_schema?: array<string,mixed>,
38-
* meta?: array<string,mixed>,
39+
* meta?: array{
40+
* annotations?: array<string,(bool|string)>,
41+
* show_in_rest?: bool,
42+
* ...<string,mixed>,
43+
* },
3944
* ability_class?: class-string<\WP_Ability>,
4045
* ...<string, mixed>
4146
* } $args
@@ -98,3 +103,68 @@ function wp_get_ability( string $name ): ?WP_Ability {
98103
function wp_get_abilities(): array {
99104
return WP_Abilities_Registry::get_instance()->get_all_registered();
100105
}
106+
107+
/**
108+
* Registers a new ability category.
109+
*
110+
* @since 0.3.0
111+
*
112+
* @see WP_Abilities_Category_Registry::register()
113+
*
114+
* @param string $slug The unique slug for the category. Must contain only lowercase
115+
* alphanumeric characters and dashes.
116+
* @param array<string,mixed> $args An associative array of arguments for the category. This should
117+
* include `label`, `description`, and optionally `meta`.
118+
* @return ?\WP_Ability_Category The registered category instance on success, null on failure.
119+
*
120+
* @phpstan-param array{
121+
* label: string,
122+
* description: string,
123+
* meta?: array<string,mixed>,
124+
* ...<string, mixed>
125+
* } $args
126+
*/
127+
function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category {
128+
return WP_Abilities_Category_Registry::get_instance()->register( $slug, $args );
129+
}
130+
131+
/**
132+
* Unregisters an ability category.
133+
*
134+
* @since 0.3.0
135+
*
136+
* @see WP_Abilities_Category_Registry::unregister()
137+
*
138+
* @param string $slug The slug of the registered category.
139+
* @return ?\WP_Ability_Category The unregistered category instance on success, null on failure.
140+
*/
141+
function wp_unregister_ability_category( string $slug ): ?WP_Ability_Category {
142+
return WP_Abilities_Category_Registry::get_instance()->unregister( $slug );
143+
}
144+
145+
/**
146+
* Retrieves a registered ability category.
147+
*
148+
* @since 0.3.0
149+
*
150+
* @see WP_Abilities_Category_Registry::get_registered()
151+
*
152+
* @param string $slug The slug of the registered category.
153+
* @return ?\WP_Ability_Category The registered category instance, or null if it is not registered.
154+
*/
155+
function wp_get_ability_category( string $slug ): ?WP_Ability_Category {
156+
return WP_Abilities_Category_Registry::get_instance()->get_registered( $slug );
157+
}
158+
159+
/**
160+
* Retrieves all registered ability categories.
161+
*
162+
* @since 0.3.0
163+
*
164+
* @see WP_Abilities_Category_Registry::get_all_registered()
165+
*
166+
* @return \WP_Ability_Category[] The array of registered categories.
167+
*/
168+
function wp_get_ability_categories(): array {
169+
return WP_Abilities_Category_Registry::get_instance()->get_all_registered();
170+
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<?php
2+
/**
3+
* Abilities API
4+
*
5+
* Defines WP_Abilities_Category_Registry class.
6+
*
7+
* @package WordPress
8+
* @subpackage Abilities API
9+
* @since 0.3.0
10+
*/
11+
12+
declare( strict_types = 1 );
13+
14+
/**
15+
* Manages the registration and lookup of ability categories.
16+
*
17+
* @since 0.3.0
18+
* @access private
19+
*/
20+
final class WP_Abilities_Category_Registry {
21+
/**
22+
* The singleton instance of the registry.
23+
*
24+
* @since 0.3.0
25+
* @var ?self
26+
*/
27+
private static $instance = null;
28+
29+
/**
30+
* Holds the registered categories.
31+
*
32+
* @since 0.3.0
33+
* @var \WP_Ability_Category[]
34+
*/
35+
private $registered_categories = array();
36+
37+
/**
38+
* Registers a new category.
39+
*
40+
* Do not use this method directly. Instead, use the `wp_register_ability_category()` function.
41+
*
42+
* @since 0.3.0
43+
*
44+
* @see wp_register_ability_category()
45+
*
46+
* @param string $slug The unique slug for the category. Must contain only lowercase
47+
* alphanumeric characters and dashes.
48+
* @param array<string,mixed> $args An associative array of arguments for the category. See wp_register_ability_category() for
49+
* details.
50+
* @return ?\WP_Ability_Category The registered category instance on success, null on failure.
51+
*
52+
* @phpstan-param array{
53+
* label: string,
54+
* description: string,
55+
* meta?: array<string,mixed>,
56+
* ...<string, mixed>
57+
* } $args
58+
*/
59+
public function register( string $slug, array $args ): ?WP_Ability_Category {
60+
if ( ! doing_action( 'abilities_api_categories_init' ) ) {
61+
_doing_it_wrong(
62+
__METHOD__,
63+
sprintf(
64+
/* translators: 1: abilities_api_categories_init, 2: category slug. */
65+
esc_html__( 'Categories must be registered during the %1$s action. The category %2$s was not registered.' ),
66+
'<code>abilities_api_categories_init</code>',
67+
'<code>' . esc_html( $slug ) . '</code>'
68+
),
69+
'0.3.0'
70+
);
71+
return null;
72+
}
73+
74+
if ( $this->is_registered( $slug ) ) {
75+
_doing_it_wrong(
76+
__METHOD__,
77+
/* translators: %s: Category slug. */
78+
esc_html( sprintf( __( 'Category "%s" is already registered.' ), $slug ) ),
79+
'0.3.0'
80+
);
81+
return null;
82+
}
83+
84+
if ( ! preg_match( '/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $slug ) ) {
85+
_doing_it_wrong(
86+
__METHOD__,
87+
esc_html__( 'Category slug must contain only lowercase alphanumeric characters and dashes.' ),
88+
'0.3.0'
89+
);
90+
return null;
91+
}
92+
93+
/**
94+
* Filters the category arguments before they are validated and used to instantiate the category.
95+
*
96+
* @since 0.3.0
97+
*
98+
* @param array<string,mixed> $args The arguments used to instantiate the category.
99+
* @param string $slug The slug of the category.
100+
*/
101+
$args = apply_filters( 'register_ability_category_args', $args, $slug );
102+
103+
try {
104+
// WP_Ability_Category::prepare_properties() will throw an exception if the properties are invalid.
105+
$category = new WP_Ability_Category( $slug, $args );
106+
} catch ( \InvalidArgumentException $e ) {
107+
_doing_it_wrong(
108+
__METHOD__,
109+
esc_html( $e->getMessage() ),
110+
'0.3.0'
111+
);
112+
return null;
113+
}
114+
115+
$this->registered_categories[ $slug ] = $category;
116+
return $category;
117+
}
118+
119+
/**
120+
* Unregisters a category.
121+
*
122+
* Do not use this method directly. Instead, use the `wp_unregister_ability_category()` function.
123+
*
124+
* @since 0.3.0
125+
*
126+
* @see wp_unregister_ability_category()
127+
*
128+
* @param string $slug The slug of the registered category.
129+
* @return ?\WP_Ability_Category The unregistered category instance on success, null on failure.
130+
*/
131+
public function unregister( string $slug ): ?WP_Ability_Category {
132+
if ( ! $this->is_registered( $slug ) ) {
133+
_doing_it_wrong(
134+
__METHOD__,
135+
/* translators: %s: Ability category slug. */
136+
sprintf( esc_html__( 'Ability category "%s" not found.' ), esc_attr( $slug ) ),
137+
'0.3.0'
138+
);
139+
return null;
140+
}
141+
142+
$unregistered_category = $this->registered_categories[ $slug ];
143+
unset( $this->registered_categories[ $slug ] );
144+
145+
return $unregistered_category;
146+
}
147+
148+
/**
149+
* Retrieves the list of all registered categories.
150+
*
151+
* Do not use this method directly. Instead, use the `wp_get_ability_categories()` function.
152+
*
153+
* @since 0.3.0
154+
*
155+
* @see wp_get_ability_categories()
156+
*
157+
* @return array<string,\WP_Ability_Category> The array of registered categories.
158+
*/
159+
public function get_all_registered(): array {
160+
return $this->registered_categories;
161+
}
162+
163+
/**
164+
* Checks if a category is registered.
165+
*
166+
* @since 0.3.0
167+
*
168+
* @param string $slug The slug of the category.
169+
* @return bool True if the category is registered, false otherwise.
170+
*/
171+
public function is_registered( string $slug ): bool {
172+
return isset( $this->registered_categories[ $slug ] );
173+
}
174+
175+
/**
176+
* Retrieves a registered category.
177+
*
178+
* Do not use this method directly. Instead, use the `wp_get_ability_category()` function.
179+
*
180+
* @since 0.3.0
181+
*
182+
* @see wp_get_ability_category()
183+
*
184+
* @param string $slug The slug of the registered category.
185+
* @return ?\WP_Ability_Category The registered category instance, or null if it is not registered.
186+
*/
187+
public function get_registered( string $slug ): ?WP_Ability_Category {
188+
if ( ! $this->is_registered( $slug ) ) {
189+
_doing_it_wrong(
190+
__METHOD__,
191+
/* translators: %s: Ability category slug. */
192+
sprintf( esc_html__( 'Ability category "%s" not found.' ), esc_attr( $slug ) ),
193+
'0.3.0'
194+
);
195+
return null;
196+
}
197+
return $this->registered_categories[ $slug ];
198+
}
199+
200+
/**
201+
* Utility method to retrieve the main instance of the registry class.
202+
*
203+
* The instance will be created if it does not exist yet.
204+
*
205+
* @since 0.3.0
206+
*
207+
* @return \WP_Abilities_Category_Registry The main registry instance.
208+
*/
209+
public static function get_instance(): self {
210+
if ( null === self::$instance ) {
211+
self::$instance = new self();
212+
213+
/**
214+
* Fires when preparing ability categories registry.
215+
*
216+
* Categories should be registered on this action to ensure they're available when needed.
217+
*
218+
* @since 0.3.0
219+
*
220+
* @param \WP_Abilities_Category_Registry $instance Categories registry object.
221+
*/
222+
do_action( 'abilities_api_categories_init', self::$instance );
223+
}
224+
225+
return self::$instance;
226+
}
227+
228+
/**
229+
* Wakeup magic method.
230+
*
231+
* @since 0.3.0
232+
* @throws \LogicException If the registry is unserialized. This is a security hardening measure to prevent unserialization of the registry.
233+
*/
234+
public function __wakeup(): void {
235+
throw new \LogicException( self::class . ' must not be unserialized.' );
236+
}
237+
238+
/**
239+
* Serialization magic method.
240+
*
241+
* @since 0.3.0
242+
* @throws \LogicException If the registry is serialized. This is a security hardening measure to prevent serialization of the registry.
243+
*/
244+
public function __sleep(): array {
245+
throw new \LogicException( self::class . ' must not be serialized.' );
246+
}
247+
}

src/wp-includes/abilities-api/class-wp-abilities-registry.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,16 @@ final class WP_Abilities_Registry {
5353
* @phpstan-param array{
5454
* label?: string,
5555
* description?: string,
56+
* category?: string,
5657
* execute_callback?: callable( mixed $input= ): (mixed|\WP_Error),
5758
* permission_callback?: callable( mixed $input= ): (bool|\WP_Error),
5859
* input_schema?: array<string,mixed>,
5960
* output_schema?: array<string,mixed>,
60-
* meta?: array<string,mixed>,
61+
* meta?: array{
62+
* annotations?: array<string,(bool|string)>,
63+
* show_in_rest?: bool,
64+
* ...<string, mixed>
65+
* },
6166
* ability_class?: class-string<\WP_Ability>,
6267
* ...<string, mixed>
6368
* } $args
@@ -94,6 +99,24 @@ public function register( string $name, array $args ): ?WP_Ability {
9499
*/
95100
$args = apply_filters( 'register_ability_args', $args, $name );
96101

102+
// Validate category exists if provided (will be validated as required in WP_Ability).
103+
if ( isset( $args['category'] ) ) {
104+
$category_registry = WP_Abilities_Category_Registry::get_instance();
105+
if ( ! $category_registry->is_registered( $args['category'] ) ) {
106+
_doing_it_wrong(
107+
__METHOD__,
108+
sprintf(
109+
/* translators: %1$s: ability category slug, %2$s: ability name */
110+
esc_html__( 'Ability category "%1$s" is not registered. Please register the category before assigning it to ability "%2$s".' ),
111+
esc_attr( $args['category'] ),
112+
esc_attr( $name )
113+
),
114+
'0.3.0'
115+
);
116+
return null;
117+
}
118+
}
119+
97120
// The class is only used to instantiate the ability, and is not a property of the ability itself.
98121
if ( isset( $args['ability_class'] ) && ! is_a( $args['ability_class'], WP_Ability::class, true ) ) {
99122
_doing_it_wrong(
@@ -218,6 +241,10 @@ public static function get_instance(): self {
218241
if ( null === self::$instance ) {
219242
self::$instance = new self();
220243

244+
// Ensure category registry is initialized first to allow categories to be registered
245+
// before abilities that depend on them.
246+
WP_Abilities_Category_Registry::get_instance();
247+
221248
/**
222249
* Fires when preparing abilities registry.
223250
*

0 commit comments

Comments
 (0)