diff --git a/src/wp-includes/abilities-api/class-wp-ability.php b/src/wp-includes/abilities-api/class-wp-ability.php index d116080c1ccdc..3af7f7fc9844e 100644 --- a/src/wp-includes/abilities-api/class-wp-ability.php +++ b/src/wp-includes/abilities-api/class-wp-ability.php @@ -277,13 +277,15 @@ protected function prepare_properties( array $args ): array { ); } - if ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) { + // If we are not overriding `ability_class` parameter during instantiation, then we need to validate the execute_callback. + if ( get_class( $this ) === self::class && ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) ) { throw new InvalidArgumentException( __( 'The ability properties must contain a valid `execute_callback` function.' ) ); } - if ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) { + // If we are not overriding `ability_class` parameter during instantiation, then we need to validate the permission_callback. + if ( get_class( $this ) === self::class && ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) ) { throw new InvalidArgumentException( __( 'The ability properties must provide a valid `permission_callback` function.' ) ); diff --git a/tests/phpunit/includes/class-tests-custom-ability-class.php b/tests/phpunit/includes/class-tests-custom-ability-class.php new file mode 100644 index 0000000000000..6e600e5c013e1 --- /dev/null +++ b/tests/phpunit/includes/class-tests-custom-ability-class.php @@ -0,0 +1,29 @@ +registry = new WP_Abilities_Registry(); @@ -257,6 +259,36 @@ public function test_register_incorrect_execute_callback_type() { $this->assertNull( $result ); } + /** + * Should allow ability registration with custom ability_class that overrides do_execute. + * + * @ticket 64407 + * + * @covers WP_Abilities_Registry::register + * @covers WP_Ability::prepare_properties + */ + public function test_register_with_custom_ability_class_without_execute_callback() { + // Remove execute_callback and permission_callback since the custom class provides its own implementation. + unset( self::$test_ability_args['execute_callback'] ); + unset( self::$test_ability_args['permission_callback'] ); + + self::$test_ability_args['ability_class'] = 'Tests_Custom_Ability_Class'; + + $result = $this->registry->register( self::$test_ability_name, self::$test_ability_args ); + + $this->assertInstanceOf( WP_Ability::class, $result, 'Should return a WP_Ability instance.' ); + $this->assertInstanceOf( Tests_Custom_Ability_Class::class, $result, 'Should return an instance of the custom class.' ); + + // Verify the custom execute method works. + $execute_result = $result->execute( + array( + 'a' => 5, + 'b' => 3, + ) + ); + $this->assertSame( 15, $execute_result, 'Custom do_execute should multiply instead of add.' ); + } + /** * Should reject ability registration without an execute callback. *