Make WordPress Core

Changeset 62418


Ignore:
Timestamp:
05/26/2026 08:14:49 AM (3 months ago)
Author:
gziolo
Message:

Abilities API: Add wp_ability_invoked action

Introduces a new wp_ability_invoked action that fires at the start of WP_Ability::execute(), before input normalization, validation, or permission checks. This gives observers a reliable entry point for every invocation regardless of outcome (short-circuit, validation failure, permission denial, or successful execution).

Also extends the existing wp_before_execute_ability and wp_after_execute_ability actions with a new $ability parameter exposing the WP_Ability instance.

Follow-up for #64989.

Props sukhendu2002, peterwilsoncc, gziolo.
Fixes #65248.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/abilities-api/class-wp-ability.php

    r62401 r62418  
    726726         *
    727727         * @since 6.9.0
     728         * @since 7.1.0 Added the `wp_ability_invoked` action.
    728729         * @since 7.1.0 Added the `wp_pre_execute_ability` filter.
    729730         *
     
    732733         */
    733734        public function execute( $input = null ) {
     735                /**
     736                 * Fires when an ability is invoked, before any processing takes place.
     737                 *
     738                 * This action fires for every call regardless of outcome (validation failure,
     739                 * permission denial, short-circuit, or successful execution), and before input
     740                 * normalization so the raw input is captured as-is.
     741                 *
     742                 * @since 7.1.0
     743                 *
     744                 * @param string     $ability_name The name of the ability.
     745                 * @param mixed      $input        The raw input data for the ability, before normalization.
     746                 * @param WP_Ability $ability      The ability instance.
     747                 */
     748                do_action( 'wp_ability_invoked', $this->name, $input, $this );
     749
    734750                /**
    735751                 * Filters whether to short-circuit ability execution.
     
    792808                 *
    793809                 * @since 6.9.0
    794                  *
    795                  * @param string $ability_name The name of the ability.
    796                  * @param mixed  $input        The input data for the ability.
    797                  */
    798                 do_action( 'wp_before_execute_ability', $this->name, $input );
     810                 * @since 7.1.0 Added the `$ability` parameter.
     811                 *
     812                 * @param string     $ability_name The name of the ability.
     813                 * @param mixed      $input        The input data for the ability.
     814                 * @param WP_Ability $ability      The ability instance.
     815                 */
     816                do_action( 'wp_before_execute_ability', $this->name, $input, $this );
    799817
    800818                $result = $this->do_execute( $input );
     
    812830                 *
    813831                 * @since 6.9.0
    814                  *
    815                  * @param string $ability_name The name of the ability.
    816                  * @param mixed  $input        The input data for the ability.
    817                  * @param mixed  $result       The result of the ability execution.
    818                  */
    819                 do_action( 'wp_after_execute_ability', $this->name, $input, $result );
     832                 * @since 7.1.0 Added the `$ability` parameter.
     833                 *
     834                 * @param string     $ability_name The name of the ability.
     835                 * @param mixed      $input        The input data for the ability.
     836                 * @param mixed      $result       The result of the ability execution.
     837                 * @param WP_Ability $ability      The ability instance.
     838                 */
     839                do_action( 'wp_after_execute_ability', $this->name, $input, $result, $this );
    820840
    821841                return $result;
  • trunk/tests/phpunit/tests/abilities-api/wpAbility.php

    r62405 r62418  
    554554                $action_ability_name = null;
    555555                $action_input        = null;
     556                $action_ability      = null;
    556557
    557558                $args = array_merge(
     
    571572                add_action(
    572573                        'wp_before_execute_ability',
    573                         static function ( $ability_name, $input ) use ( &$action_ability_name, &$action_input ) {
     574                        static function ( $ability_name, $input, $ability ) use ( &$action_ability_name, &$action_input, &$action_ability ) {
    574575                                $action_ability_name = $ability_name;
    575576                                $action_input        = $input;
     577                                $action_ability      = $ability;
    576578                        },
    577579                        10,
    578                         2
     580                        3
    579581                );
    580582
     
    584586                $this->assertSame( self::$test_ability_name, $action_ability_name, 'Action should receive correct ability name' );
    585587                $this->assertSame( 5, $action_input, 'Action should receive correct input' );
     588                $this->assertSame( $ability, $action_ability, 'Action should receive the ability instance' );
    586589                $this->assertSame( 10, $result, 'Ability should execute correctly' );
    587590        }
     
    595598                $action_ability_name = null;
    596599                $action_input        = null;
     600                $action_ability      = null;
    597601
    598602                $args = array_merge(
     
    607611                add_action(
    608612                        'wp_before_execute_ability',
    609                         static function ( $ability_name, $input ) use ( &$action_ability_name, &$action_input ) {
     613                        static function ( $ability_name, $input, $ability ) use ( &$action_ability_name, &$action_input, &$action_ability ) {
    610614                                $action_ability_name = $ability_name;
    611615                                $action_input        = $input;
     616                                $action_ability      = $ability;
    612617                        },
    613618                        10,
    614                         2
     619                        3
    615620                );
    616621
     
    620625                $this->assertSame( self::$test_ability_name, $action_ability_name, 'Action should receive correct ability name' );
    621626                $this->assertNull( $action_input, 'Action should receive null input when no input provided' );
     627                $this->assertSame( $ability, $action_ability, 'Action should receive the ability instance' );
    622628                $this->assertSame( 42, $result, 'Ability should execute correctly' );
    623629        }
     
    632638                $action_input        = null;
    633639                $action_result       = null;
     640                $action_ability      = null;
    634641
    635642                $args = array_merge(
     
    649656                add_action(
    650657                        'wp_after_execute_ability',
    651                         static function ( $ability_name, $input, $result ) use ( &$action_ability_name, &$action_input, &$action_result ) {
     658                        static function ( $ability_name, $input, $result, $ability ) use ( &$action_ability_name, &$action_input, &$action_result, &$action_ability ) {
    652659                                $action_ability_name = $ability_name;
    653660                                $action_input        = $input;
    654661                                $action_result       = $result;
     662                                $action_ability      = $ability;
    655663                        },
    656664                        10,
    657                         3
     665                        4
    658666                );
    659667
     
    664672                $this->assertSame( 7, $action_input, 'Action should receive correct input' );
    665673                $this->assertSame( 21, $action_result, 'Action should receive correct result' );
     674                $this->assertSame( $ability, $action_ability, 'Action should receive the ability instance' );
    666675                $this->assertSame( 21, $result, 'Ability should execute correctly' );
    667676        }
     
    676685                $action_input        = null;
    677686                $action_result       = null;
     687                $action_ability      = null;
    678688
    679689                $args = array_merge(
     
    689699                add_action(
    690700                        'wp_after_execute_ability',
    691                         static function ( $ability_name, $input, $result ) use ( &$action_ability_name, &$action_input, &$action_result ) {
     701                        static function ( $ability_name, $input, $result, $ability ) use ( &$action_ability_name, &$action_input, &$action_result, &$action_ability ) {
    692702                                $action_ability_name = $ability_name;
    693703                                $action_input        = $input;
    694704                                $action_result       = $result;
     705                                $action_ability      = $ability;
    695706                        },
    696707                        10,
    697                         3
     708                        4
    698709                );
    699710
     
    704715                $this->assertNull( $action_input, 'Action should receive null input when no input provided' );
    705716                $this->assertSame( 'test-result', $action_result, 'Action should receive correct result' );
     717                $this->assertSame( $ability, $action_ability, 'Action should receive the ability instance' );
    706718                $this->assertSame( 'test-result', $result, 'Ability should execute correctly' );
    707719        }
     
    16971709                $this->assertSame( 'custom_output_error', $result->get_error_code() );
    16981710        }
     1711
     1712        /**
     1713         * Tests that wp_ability_invoked action fires with correct parameters and raw input before normalization.
     1714         *
     1715         * @ticket 65248
     1716         */
     1717        public function test_ability_invoked_action_fires_with_correct_params() {
     1718                $args = array_merge(
     1719                        self::$test_ability_properties,
     1720                        array(
     1721                                'input_schema'     => array(
     1722                                        'type'        => 'integer',
     1723                                        'description' => 'Test input parameter.',
     1724                                        'default'     => 42,
     1725                                ),
     1726                                'execute_callback' => static function ( int $input ): int {
     1727                                        return $input;
     1728                                },
     1729                        )
     1730                );
     1731
     1732                $action = new MockAction();
     1733                add_action( 'wp_ability_invoked', array( $action, 'action' ), 10, 3 );
     1734
     1735                $ability = new WP_Ability( self::$test_ability_name, $args );
     1736                $ability->execute();
     1737
     1738                $action_args = $action->get_args();
     1739                $this->assertSame( self::$test_ability_name, $action_args[0][0], 'Action should receive correct ability name.' );
     1740                $this->assertNull( $action_args[0][1], 'Action should receive raw null input, not the schema default.' );
     1741                $this->assertSame( $ability, $action_args[0][2], 'Action should receive the ability instance.' );
     1742        }
     1743
     1744        /**
     1745         * Tests that wp_ability_invoked action fires when execution is short-circuited.
     1746         *
     1747         * @ticket 65248
     1748         */
     1749        public function test_ability_invoked_action_fires_on_pre_execute_short_circuit() {
     1750                $action = new MockAction();
     1751                add_action( 'wp_ability_invoked', array( $action, 'action' ) );
     1752
     1753                add_filter(
     1754                        'wp_pre_execute_ability',
     1755                        static function () {
     1756                                return 'short-circuited';
     1757                        }
     1758                );
     1759
     1760                $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties );
     1761                $ability->execute();
     1762
     1763                $this->assertSame( 1, $action->get_call_count(), 'wp_ability_invoked should fire before a pre-execute short-circuit.' );
     1764        }
     1765
     1766        /**
     1767         * Tests that wp_ability_invoked action fires on permission failure.
     1768         *
     1769         * @ticket 65248
     1770         */
     1771        public function test_ability_invoked_action_fires_on_permission_failure() {
     1772                $action = new MockAction();
     1773                add_action( 'wp_ability_invoked', array( $action, 'action' ) );
     1774
     1775                $ability = new WP_Ability(
     1776                        self::$test_ability_name,
     1777                        array_merge(
     1778                                self::$test_ability_properties,
     1779                                array(
     1780                                        'permission_callback' => static function (): bool {
     1781                                                return false;
     1782                                        },
     1783                                )
     1784                        )
     1785                );
     1786                $ability->execute();
     1787
     1788                $this->assertSame( 1, $action->get_call_count(), 'wp_ability_invoked should fire before permission failure.' );
     1789        }
     1790
     1791        /**
     1792         * Tests that wp_ability_invoked action fires on input validation failure.
     1793         *
     1794         * @ticket 65248
     1795         */
     1796        public function test_ability_invoked_action_fires_on_validation_failure() {
     1797                $action = new MockAction();
     1798                add_action( 'wp_ability_invoked', array( $action, 'action' ) );
     1799
     1800                $ability = new WP_Ability(
     1801                        self::$test_ability_name,
     1802                        array_merge(
     1803                                self::$test_ability_properties,
     1804                                array(
     1805                                        'input_schema'     => array(
     1806                                                'type'        => 'integer',
     1807                                                'description' => 'Int input.',
     1808                                                'required'    => true,
     1809                                        ),
     1810                                        'execute_callback' => static function ( int $input ): int {
     1811                                                return $input;
     1812                                        },
     1813                                )
     1814                        )
     1815                );
     1816                $ability->execute( 'not_an_integer' );
     1817
     1818                $this->assertSame( 1, $action->get_call_count(), 'wp_ability_invoked should fire before input validation failure.' );
     1819        }
    16991820}
Note: See TracChangeset for help on using the changeset viewer.