Make WordPress Core

Changeset 62255


Ignore:
Timestamp:
04/22/2026 06:13:47 PM (8 weeks ago)
Author:
westonruter
Message:

AI: Validate filtered default request timeout in WP_AI_Client_Prompt_Builder.

This checks that the return value of the wp_ai_client_default_request_timeout filter is a non-negative number before passing it to RequestOptions. If the filtered value is invalid, it is discarded in favor of the original default of 30.0 and a _doing_it_wrong() notice is issued. Without this check, a fatal error would ensue from the exception thrown in \WordPress\AiClient\Providers\Http\DTO\RequestOptions::validateTimeout().

The following static analysis issues are addressed:

  • Use float instead of int for the wp_ai_client_default_request_timeout filter parameter.
  • Add missing PHP imports for Message and MessagePart in the PHPDoc for wp_ai_client_prompt().
  • Add PHP return type hints for wp_ai_client_prompt() and WP_AI_Client_Cache::getMultiple().
  • Use native property type hints in WP_AI_Client_HTTP_Client.

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

Props westonruter, justlevine, flixos90, khushdoms, darshitrajyaguru97, adrmf25, jarodortegaaraya, tusharaddweb, gaurangsondagar.
Fixes #65094.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/ai-client.php

    r62239 r62255  
    99
    1010use WordPress\AiClient\AiClient;
     11use WordPress\AiClient\Messages\DTO\Message;
     12use WordPress\AiClient\Messages\DTO\MessagePart;
    1113
    1214/**
     
    5658 * @return WP_AI_Client_Prompt_Builder The prompt builder instance.
    5759 */
    58 function wp_ai_client_prompt( $prompt = null ) {
     60function wp_ai_client_prompt( $prompt = null ): WP_AI_Client_Prompt_Builder {
    5961    return new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(), $prompt );
    6062}
  • trunk/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php

    r61700 r62255  
    105105     * @return array<string, mixed> A list of key => value pairs.
    106106     */
    107     public function getMultiple( $keys, $default_value = null ) {
     107    public function getMultiple( $keys, $default_value = null ): array {
    108108        /**
    109109         * Keys array.
  • trunk/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php

    r61700 r62255  
    3333     *
    3434     * @since 7.0.0
    35      * @var ResponseFactoryInterface
    36      */
    37     private $response_factory;
     35     */
     36    private ResponseFactoryInterface $response_factory;
    3837
    3938    /**
     
    4140     *
    4241     * @since 7.0.0
    43      * @var StreamFactoryInterface
    44      */
    45     private $stream_factory;
     42     */
     43    private StreamFactoryInterface $stream_factory;
    4644
    4745    /**
  • trunk/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php

    r62101 r62255  
    191191        }
    192192
     193        $default_timeout = 30.0;
     194
    193195        /**
    194196         * Filters the default request timeout in seconds for AI Client HTTP requests.
     
    196198         * @since 7.0.0
    197199         *
    198          * @param int $default_timeout The default timeout in seconds.
     200         * @param float $default_timeout The default timeout in seconds.
    199201         */
    200         $default_timeout = (int) apply_filters( 'wp_ai_client_default_request_timeout', 30 );
     202        $filtered_default_timeout = apply_filters( 'wp_ai_client_default_request_timeout', $default_timeout );
     203        if ( is_numeric( $filtered_default_timeout ) && (float) $filtered_default_timeout >= 0.0 ) {
     204            $default_timeout = (float) $filtered_default_timeout;
     205        } else {
     206            _doing_it_wrong(
     207                __METHOD__,
     208                sprintf(
     209                    /* translators: %s: wp_ai_client_default_request_timeout */
     210                    __( 'The %s filter must return a non-negative number.' ),
     211                    '<code>wp_ai_client_default_request_timeout</code>'
     212                ),
     213                '7.0.0'
     214            );
     215        }
    201216
    202217        $this->builder->usingRequestOptions(
  • trunk/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php

    r62248 r62255  
    193193
    194194    /**
    195      * Test that the constructor allows overriding the default request timeout.
    196      *
    197      * @ticket 64591
    198      */
    199     public function test_constructor_allows_overriding_request_timeout() {
     195     * Test that the constructor allows overriding the default request timeout with a valid value.
     196     *
     197     * @ticket 64591
     198     * @ticket 65094
     199     *
     200     * @dataProvider data_valid_request_timeout_overrides
     201     *
     202     * @param mixed $input    The timeout value returned by the filter.
     203     * @param float $expected The expected timeout stored on the request options.
     204     */
     205    public function test_constructor_allows_overriding_request_timeout_with_valid_timeout( $input, float $expected ) {
    200206        add_filter(
    201207            'wp_ai_client_default_request_timeout',
    202             static function () {
    203                 return 45;
     208            static function () use ( $input ) {
     209                return $input;
    204210            }
    205211        );
     
    211217
    212218        $this->assertInstanceOf( RequestOptions::class, $request_options );
    213         $this->assertSame( 45.0, $request_options->getTimeout() );
     219        $this->assertSame( $expected, $request_options->getTimeout() );
     220    }
     221
     222    /**
     223     * Data provider for {@see self::test_constructor_allows_overriding_request_timeout_with_valid_timeout()}.
     224     *
     225     * @return array<string, array{0: mixed, 1: float}>
     226     */
     227    public function data_valid_request_timeout_overrides(): array {
     228        return array(
     229            'float'    => array( 45.5, 45.5 ),
     230            'integer'  => array( 67, 67.0 ),
     231            'string'   => array( '20', 20.0 ),
     232            'infinity' => array( INF, INF ),
     233            'zero'     => array( 0.0, 0.0 ),
     234        );
     235    }
     236
     237    /**
     238     * Test that the constructor disallows overriding the default request timeout with an invalid value.
     239     *
     240     * @ticket 65094
     241     *
     242     * @dataProvider data_invalid_request_timeouts
     243     *
     244     * @expectedIncorrectUsage WP_AI_Client_Prompt_Builder::__construct
     245     *
     246     * @param mixed $timeout The invalid timeout value returned by the filter.
     247     */
     248    public function test_constructor_disallows_overriding_with_invalid_request_timeout( $timeout ) {
     249        add_filter(
     250            'wp_ai_client_default_request_timeout',
     251            static function () use ( $timeout ) {
     252                return $timeout;
     253            }
     254        );
     255
     256        $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry() );
     257
     258        /** @var RequestOptions $request_options */
     259        $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' );
     260
     261        $this->assertInstanceOf( RequestOptions::class, $request_options );
     262        $this->assertSame( 30.0, $request_options->getTimeout() );
     263    }
     264
     265    /**
     266     * Data provider for {@see self::test_constructor_disallows_overriding_with_invalid_request_timeout()}.
     267     *
     268     * @return array<string, array{0: mixed}>
     269     */
     270    public function data_invalid_request_timeouts(): array {
     271        return array(
     272            'negative number' => array( -1 ),
     273            'array'           => array( array() ),
     274            'null'            => array( null ),
     275        );
    214276    }
    215277
Note: See TracChangeset for help on using the changeset viewer.