Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions includes/abilities-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
*
* @since 0.1.0
*
* @param string|\WP_Ability $name The name of the ability, or WP_Ability instance.
* The 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.
* @param array<string,mixed> $properties Optional. An associative array of properties for the ability. This should
* include `label`, `description`, `input_schema`, `output_schema`,
* `execute_callback`, `permission_callback`, and `meta`.
* @param string $name The name of the ability. The 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.
* @param array<string,mixed> $properties An associative array of properties for the ability. This should include
* `label`, `description`, `input_schema`, `output_schema`, `execute_callback`,
* `permission_callback`, `meta`, and `ability_class`.
* @return ?\WP_Ability An instance of registered ability on success, null on failure.
*
* @phpstan-param array{
Expand All @@ -36,18 +36,19 @@
* execute_callback?: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: callable( ?array<string,mixed> $input ): bool,
* meta?: array<string,mixed>,
* ability_class?: class-string<\WP_Ability>,
* ...<string, mixed>
* } $properties
*/
function wp_register_ability( $name, array $properties = array() ): ?WP_Ability {
function wp_register_ability( string $name, array $properties = array() ): ?WP_Ability {
if ( ! did_action( 'abilities_api_init' ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: abilities_api_init, 2: string value of the ability name. */
esc_html__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ),
'<code>abilities_api_init</code>',
'<code>' . esc_html( $name instanceof WP_Ability ? $name->get_name() : $name ) . '</code>'
'<code>' . esc_html( $name ) . '</code>'
),
'0.1.0'
);
Expand Down
52 changes: 23 additions & 29 deletions includes/abilities-api/class-wp-abilities-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ final class WP_Abilities_Registry {
*
* @since 0.1.0
*
* @param string|\WP_Ability $name The name of the ability, or WP_Ability instance. The 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.
* @param array<string,mixed> $properties Optional. An associative array of properties for the ability. This should
* include `label`, `description`, `input_schema`, `output_schema`,
* `execute_callback`, `permission_callback`, and `meta`.
* @param string $name The name of the ability. The 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.
* @param array<string,mixed> $properties An associative array of properties for the ability. This should include
* `label`, `description`, `input_schema`, `output_schema`,
* `execute_callback`, `permission_callback`, `meta`, and ability_class.
* @return ?\WP_Ability The registered ability instance on success, null on failure.
*
* @phpstan-param array{
Expand All @@ -59,16 +59,11 @@ final class WP_Abilities_Registry {
* execute_callback?: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: ?callable( ?array<string,mixed> $input ): bool,
* meta?: array<string,mixed>,
* ability_class?: class-string<\WP_Ability>,
* ...<string, mixed>
* } $properties
*/
public function register( $name, array $properties = array() ): ?WP_Ability {
$ability = null;
if ( $name instanceof WP_Ability ) {
$ability = $name;
$name = $ability->get_name();
}

public function register( string $name, array $properties = array() ): ?WP_Ability {
if ( ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $name ) ) {
_doing_it_wrong(
__METHOD__,
Expand All @@ -90,12 +85,6 @@ public function register( $name, array $properties = array() ): ?WP_Ability {
return null;
}

// If the ability is already an instance, we can skip the rest of the validation.
if ( null !== $ability ) {
$this->registered_abilities[ $name ] = $ability;
return $ability;
}

if ( empty( $properties['label'] ) || ! is_string( $properties['label'] ) ) {
_doing_it_wrong(
__METHOD__,
Expand Down Expand Up @@ -159,17 +148,22 @@ public function register( $name, array $properties = array() ): ?WP_Ability {
return null;
}

$ability = new WP_Ability(
if ( isset( $properties['ability_class'] ) && ! is_a( $properties['ability_class'], WP_Ability::class, true ) ) {
_doing_it_wrong(
__METHOD__,
esc_html__( 'The ability properties should provide a valid `ability_class` that extends WP_Ability.' ),
'0.1.0'
);
return null;
}

// The class is only used to instantiate the ability, and is not a property of the ability itself.
$ability_class = $properties['ability_class'] ?? WP_Ability::class;
unset( $properties['ability_class'] );

$ability = new $ability_class(
$name,
array(
'label' => $properties['label'],
'description' => $properties['description'],
'input_schema' => $properties['input_schema'] ?? array(),
'output_schema' => $properties['output_schema'] ?? array(),
'execute_callback' => $properties['execute_callback'],
'permission_callback' => $properties['permission_callback'] ?? null,
'meta' => $properties['meta'] ?? array(),
)
$properties
);

$this->registered_abilities[ $name ] = $ability;
Expand Down
20 changes: 19 additions & 1 deletion includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* Encapsulates the properties and methods related to a specific ability in the registry.
*
* @since 0.1.0
* @access private
*
* @see WP_Abilities_Registry
*/
Expand Down Expand Up @@ -91,6 +90,8 @@ class WP_Ability {
*
* Do not use this constructor directly. Instead, use the `wp_register_ability()` function.
*
* @access private
*
* @see wp_register_ability()
*
* @since 0.1.0
Expand All @@ -108,11 +109,28 @@ class WP_Ability {
* execute_callback: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: ?callable( ?array<string,mixed> $input ): bool,
* meta?: array<string,mixed>,
* ...<string, mixed>,
* } $properties
*/
public function __construct( string $name, array $properties ) {
$this->name = $name;

foreach ( $properties as $property_name => $property_value ) {
if ( ! property_exists( $this, $property_name ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %s: Property name. */
esc_html__( 'Property "%1$s" is not a valid property for ability "%2$s". Please check the %3$s class for allowed properties.' ),
'<code>' . esc_html( $property_name ) . '</code>',
'<code>' . esc_html( $this->name ) . '</code>',
'<code>' . esc_html( self::class ) . '</code>'
),
'0.1.0'
);
continue;
}

$this->$property_name = $property_value;
}
}
Expand Down
41 changes: 0 additions & 41 deletions tests/unit/abilities-api/wpAbilitiesRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,6 @@ public function test_register_invalid_uppercase_characters_in_name() {
$this->assertNull( $result );
}

/**
* Should reject ability instance with invalid name.
*
* @covers WP_Abilities_Registry::register
*
* @expectedIncorrectUsage WP_Abilities_Registry::register
*/
public function test_register_invalid_name_using_instance() {
$ability = new WP_Ability( 'invalid_name', array() );
$result = $this->registry->register( $ability );
$this->assertNull( $result );
}

/**
* Should reject ability registration without a label.
*
Expand Down Expand Up @@ -277,21 +264,6 @@ public function test_register_incorrect_already_registered_ability() {
$this->assertNull( $result );
}

/**
* Should reject registration for already registered ability when passing an ability instance.
*
* @covers WP_Abilities_Registry::register
*
* @expectedIncorrectUsage WP_Abilities_Registry::register
*/
public function test_register_incorrect_already_registered_ability_using_instance() {
$ability = $this->registry->register( self::$test_ability_name, self::$test_ability_properties );

$result = $this->registry->register( $ability );

$this->assertNull( $result );
}

/**
* Should successfully register a new ability.
*
Expand All @@ -306,19 +278,6 @@ public function test_register_new_ability() {
);
}

/**
* Should successfully register a new ability using an instance.
*
* @covers WP_Abilities_Registry::register
* @covers WP_Ability::construct
*/
public function test_register_new_ability_using_instance() {
$ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties );
$result = $this->registry->register( $ability );

$this->assertSame( $ability, $result );
}

/**
* Should return false for ability that's not registered.
*
Expand Down
50 changes: 50 additions & 0 deletions tests/unit/abilities-api/wpRegisterAbility.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<?php declare( strict_types=1 );

/**
* Mock used to test a custom ability class.
*/
class Mock_Custom_Ability extends WP_Ability {
protected function do_execute( array $input ) {
return 9999;
}
}

/**
* @covers wp_register_ability
* @covers wp_unregister_ability
Expand Down Expand Up @@ -174,6 +183,47 @@ public function test_register_ability_no_permissions(): void {
$this->assertEquals( 'ability_invalid_permissions', $actual->get_error_code() );
}

/**
* Tests registering an ability with a custom ability class.
*/
public function test_register_ability_custom_ability_class(): void {
do_action( 'abilities_api_init' );

$result = wp_register_ability(
self::$test_ability_name,
array_merge(
self::$test_ability_properties,
array(
'ability_class' => Mock_Custom_Ability::class,
)
)
);

$this->assertInstanceOf( Mock_Custom_Ability::class, $result );
$this->assertSame(
9999,
$result->execute(
array(
'a' => 2,
'b' => 3,
)
)
);

// Try again with an invalid class throws a doing it wrong.
$this->setExpectedIncorrectUsage( WP_Abilities_Registry::class . '::register' );
wp_register_ability(
self::$test_ability_name,
array_merge(
self::$test_ability_properties,
array(
'ability_class' => 'Non_Existent_Class',
)
)
);
}


/**
* Tests executing an ability with input not matching schema.
*/
Expand Down