Make WordPress Core

Changeset 62240


Ignore:
Timestamp:
04/16/2026 08:16:54 AM (5 months ago)
Author:
gziolo
Message:

Abilities API: Catch exceptions thrown by ability callbacks and return WP_Error.

Wraps invoke_callback() in a try/catch so that exceptions thrown by execute or permission callbacks are converted to a WP_Error with the ability_callback_exception code instead of propagating as uncaught throwables.

Developed in: https://github.com/WordPress/wordpress-develop/pull/11544

Reviewed by adamsilverstein, justlevine, jorbin.
Merges [62238] to the 7.0 branch.

Props priyankagusani, jamesgiroux, jeffpaul, dkotter, adamsilverstein, justlevine, jorbin, pavanpatil1.
Fixes #65058.

Location:
branches/7.0
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/7.0

  • branches/7.0/src/wp-includes/abilities-api/class-wp-ability.php

    r62094 r62240  
    503503         * @param callable $callback The callable to invoke.
    504504         * @param mixed    $input    Optional. The input data for the ability. Default `null`.
    505          * @return mixed The result of the callable execution.
     505         * @return mixed The result of the callable execution, or a `WP_Error` if the callback threw.
    506506         */
    507507        protected function invoke_callback( callable $callback, $input = null ) {
     
    511511                }
    512512
    513                 return $callback( ...$args );
     513                try {
     514                        return $callback( ...$args );
     515                } catch ( Throwable $e ) {
     516                        return new WP_Error(
     517                                'ability_callback_exception',
     518                                sprintf(
     519                                        /* translators: 1: Ability name, 2: Exception message. */
     520                                        __( 'Ability "%1$s" callback threw an exception: %2$s' ),
     521                                        esc_html( $this->name ),
     522                                        esc_html( $e->getMessage() )
     523                                )
     524                        );
     525                }
    514526        }
    515527
  • branches/7.0/tests/phpunit/tests/abilities-api/wpAbility.php

    r61130 r62240  
    499499
    500500        /**
     501         * Tests that an exception thrown by the execute callback is converted to a WP_Error
     502         * instead of being propagated as an uncaught throwable.
     503         *
     504         * @ticket 65058
     505         */
     506        public function test_execute_catches_callback_exception() {
     507                $args = array_merge(
     508                        self::$test_ability_properties,
     509                        array(
     510                                'execute_callback' => static function (): int {
     511                                        throw new RuntimeException( 'boom' );
     512                                },
     513                        )
     514                );
     515
     516                $ability = new WP_Ability( self::$test_ability_name, $args );
     517                $result  = $ability->execute();
     518
     519                $this->assertWPError( $result, 'Ability::execute() should return WP_Error when the callback throws.' );
     520                $this->assertSame( 'ability_callback_exception', $result->get_error_code() );
     521                $this->assertStringContainsString( 'boom', $result->get_error_message() );
     522        }
     523
     524        /**
     525         * Tests that an exception thrown by the permission callback is converted to a WP_Error
     526         * instead of being propagated as an uncaught throwable.
     527         *
     528         * @ticket 65058
     529         */
     530        public function test_check_permissions_catches_callback_exception() {
     531                $args = array_merge(
     532                        self::$test_ability_properties,
     533                        array(
     534                                'permission_callback' => static function (): bool {
     535                                        throw new RuntimeException( 'permission exploded' );
     536                                },
     537                        )
     538                );
     539
     540                $ability = new WP_Ability( self::$test_ability_name, $args );
     541                $result  = $ability->check_permissions();
     542
     543                $this->assertWPError( $result, 'Ability::check_permissions() should return WP_Error when the callback throws.' );
     544                $this->assertSame( 'ability_callback_exception', $result->get_error_code() );
     545                $this->assertStringContainsString( 'permission exploded', $result->get_error_message() );
     546        }
     547
     548        /**
    501549         * Tests that before_execute_ability action is fired with correct parameters.
    502550         *
Note: See TracChangeset for help on using the changeset viewer.