Skip to content

Commit 5df7861

Browse files
Abilities API: Enhance WP_Ability validation for execute_callback and permission_callback.
Abilities API allows for extending WP_Ability by providing ability_class during the ability registration. This is meant to unlock complex abilities holding some sort of state or logic that requires multiple helper methods. In all of those scenarios you would ovewrite execute or do_execute method. However, because the check for execute_callback is in constructor, then in order to register an ability with ability_class overwrite, you have to BOTH: provide do_execute and provide a dummy execute_callback. The same need happens for permission_callback. This commit fixes the issue execute_callback and permission_callback are now optional when a class is provided. Props artpi, swissspidy, jorgefilipecosta, mindctrl. Fixes #64407. git-svn-id: https://develop.svn.wordpress.org/trunk@61390 602fd350-edb4-49c9-b593-d223f7449a82
1 parent bbc6029 commit 5df7861

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,15 @@ protected function prepare_properties( array $args ): array {
277277
);
278278
}
279279

280-
if ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) {
280+
// If we are not overriding `ability_class` parameter during instantiation, then we need to validate the execute_callback.
281+
if ( get_class( $this ) === self::class && ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) ) {
281282
throw new InvalidArgumentException(
282283
__( 'The ability properties must contain a valid `execute_callback` function.' )
283284
);
284285
}
285286

286-
if ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) {
287+
// If we are not overriding `ability_class` parameter during instantiation, then we need to validate the permission_callback.
288+
if ( get_class( $this ) === self::class && ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) ) {
287289
throw new InvalidArgumentException(
288290
__( 'The ability properties must provide a valid `permission_callback` function.' )
289291
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* Test custom ability class that extends WP_Ability.
4+
*
5+
* This class overrides do_execute() and check_permissions() directly,
6+
* allowing registration without execute_callback or permission_callback.
7+
*/
8+
class Tests_Custom_Ability_Class extends WP_Ability {
9+
10+
/**
11+
* Custom execute implementation that multiplies instead of adds.
12+
*
13+
* @param mixed $input The input data.
14+
* @return int The result of multiplying a and b.
15+
*/
16+
protected function do_execute( $input = null ) {
17+
return $input['a'] * $input['b'];
18+
}
19+
20+
/**
21+
* Custom permission check that always returns true.
22+
*
23+
* @param mixed $input The input data.
24+
* @return bool Always true.
25+
*/
26+
public function check_permissions( $input = null ) {
27+
return true;
28+
}
29+
}

tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class Tests_Abilities_API_WpAbilitiesRegistry extends WP_UnitTestCase {
2323
* Set up each test method.
2424
*/
2525
public function set_up(): void {
26+
require_once DIR_TESTDATA . '/../includes/class-tests-custom-ability-class.php';
27+
2628
parent::set_up();
2729

2830
$this->registry = new WP_Abilities_Registry();
@@ -257,6 +259,36 @@ public function test_register_incorrect_execute_callback_type() {
257259
$this->assertNull( $result );
258260
}
259261

262+
/**
263+
* Should allow ability registration with custom ability_class that overrides do_execute.
264+
*
265+
* @ticket 64407
266+
*
267+
* @covers WP_Abilities_Registry::register
268+
* @covers WP_Ability::prepare_properties
269+
*/
270+
public function test_register_with_custom_ability_class_without_execute_callback() {
271+
// Remove execute_callback and permission_callback since the custom class provides its own implementation.
272+
unset( self::$test_ability_args['execute_callback'] );
273+
unset( self::$test_ability_args['permission_callback'] );
274+
275+
self::$test_ability_args['ability_class'] = 'Tests_Custom_Ability_Class';
276+
277+
$result = $this->registry->register( self::$test_ability_name, self::$test_ability_args );
278+
279+
$this->assertInstanceOf( WP_Ability::class, $result, 'Should return a WP_Ability instance.' );
280+
$this->assertInstanceOf( Tests_Custom_Ability_Class::class, $result, 'Should return an instance of the custom class.' );
281+
282+
// Verify the custom execute method works.
283+
$execute_result = $result->execute(
284+
array(
285+
'a' => 5,
286+
'b' => 3,
287+
)
288+
);
289+
$this->assertSame( 15, $execute_result, 'Custom do_execute should multiply instead of add.' );
290+
}
291+
260292
/**
261293
* Should reject ability registration without an execute callback.
262294
*

0 commit comments

Comments
 (0)