Make WordPress Core

Changeset 62238


Ignore:
Timestamp:
04/16/2026 07:22:23 AM (4 weeks 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

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

Location:
trunk
Files:
2 edited

Legend:

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

    r62094 r62238  
    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
  • trunk/tests/phpunit/tests/abilities-api/wpAbility.php

    r61130 r62238  
    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.