Make WordPress Core


Ignore:
Timestamp:
10/30/2025 12:46:29 AM (12 months ago)
Author:
westonruter
Message:

General: Add wp_send_late_headers action which fires right before the template enhancement output buffer is flushed.

This adds a (missing) wp_send_late_headers action which fires right after the wp_template_enhancement_output_buffer filters have applied and right before the output buffer is flushed. The filtered output buffer is passed as an argument to the action so that plugins may do things like send an ETag header which is calculated from the content. This action eliminates the need for plugins to hack the wp_template_enhancement_output_buffer filter with a high priority to send a late response header. This action compliments the send_headers action which is commonly used to send HTTP headers before the template is rendered. Furthermore:

  • The template enhancement output buffer is now enabled by default if there is a callback added to either the wp_template_enhancement_output_buffer filter or the wp_send_late_headers action.
  • The wp_start_template_enhancement_output_buffer() callback for the wp_before_include_template action is increased from the default of 10 to 1000. This goes with the previous point, so that plugins can add those filters and actions during the wp_before_include_template action without having to worry about adding them too late, that is, after wp_start_template_enhancement_output_buffer() has run.
  • The wp_send_late_headers action fires regardless of whether the buffered response is HTML.

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

Follow-up to [60936].

Props westonruter, peterwilsoncc, johnbillion.
See #43258.
Fixes #64126.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/template.php

    r61076 r61088  
    604604         *
    605605         * @ticket 43258
     606         *
    606607         * @covers ::wp_should_output_buffer_template_for_enhancement
    607608         * @covers ::wp_start_template_enhancement_output_buffer
    … …  
    627628         *
    628629         * @ticket 43258
     630         * @ticket 64126
     631         *
    629632         * @covers ::wp_start_template_enhancement_output_buffer
    630633         * @covers ::wp_finalize_template_enhancement_output_buffer
    … …  
    634637                ob_start();
    635638
    636                 $filter_args = null;
     639                $mock_filter_callback = new MockAction();
    637640                add_filter(
    638641                        'wp_template_enhancement_output_buffer',
    639                         static function ( string $buffer ) use ( &$filter_args ): string {
    640                                 $filter_args = func_get_args();
    641 
     642                        array( $mock_filter_callback, 'filter' ),
     643                        10,
     644                        PHP_INT_MAX
     645                );
     646
     647                $mock_action_callback = new MockAction();
     648                add_filter(
     649                        'wp_send_late_headers',
     650                        array( $mock_action_callback, 'action' ),
     651                        10,
     652                        PHP_INT_MAX
     653                );
     654
     655                add_filter(
     656                        'wp_template_enhancement_output_buffer',
     657                        static function ( string $buffer ): string {
    642658                                $p = WP_HTML_Processor::create_full_parser( $buffer );
    643659                                while ( $p->next_tag() ) {
    … …  
    657673                                }
    658674                                return $p->get_updated_html();
    659                         },
    660                         10,
    661                         PHP_INT_MAX
     675                        }
    662676                );
    663677
    … …  
    696710                $this->assertSame( $initial_ob_level, ob_get_level(), 'Expected the output buffer to be back at the initial level.' );
    697711
     712                $this->assertSame( 1, $mock_filter_callback->get_call_count(), 'Expected the wp_template_enhancement_output_buffer filter to have applied.' );
     713                $filter_args = $mock_filter_callback->get_args()[0];
    698714                $this->assertIsArray( $filter_args, 'Expected the wp_template_enhancement_output_buffer filter to have applied.' );
    699715                $this->assertCount( 2, $filter_args, 'Expected two args to be supplied to the wp_template_enhancement_output_buffer filter.' );
    … …  
    717733                $this->assertStringContainsString( '<h1>¡Hola, mundo!</h1>', $processed_output, 'Expected processed output to contain string.' );
    718734                $this->assertStringContainsString( '</html>', $processed_output, 'Expected processed output to contain string.' );
     735
     736                $this->assertSame( 1, did_action( 'wp_send_late_headers' ), 'Expected the wp_send_late_headers action to have fired.' );
     737                $this->assertSame( 1, $mock_action_callback->get_call_count(), 'Expected wp_send_late_headers action callback to have been called once.' );
     738                $action_args = $mock_action_callback->get_args()[0];
     739                $this->assertCount( 1, $action_args, 'Expected the wp_send_late_headers action to have been passed only one argument.' );
     740                $this->assertSame( $processed_output, $action_args[0], 'Expected the arg passed to wp_send_late_headers to be the same as the processed output buffer.' );
    719741        }
    720742
    … …  
    723745         *
    724746         * @ticket 43258
     747         * @ticket 64126
     748         *
    725749         * @covers ::wp_start_template_enhancement_output_buffer
    726750         * @covers ::wp_finalize_template_enhancement_output_buffer
    … …  
    730754                ob_start();
    731755
    732                 $applied_filter = false;
     756                $mock_filter_callback = new MockAction();
    733757                add_filter(
    734758                        'wp_template_enhancement_output_buffer',
    735                         static function ( string $buffer ) use ( &$applied_filter ): string {
    736                                 $applied_filter = true;
    737 
     759                        array( $mock_filter_callback, 'filter' )
     760                );
     761
     762                add_filter(
     763                        'wp_template_enhancement_output_buffer',
     764                        static function ( string $buffer ): string {
    738765                                $p = WP_HTML_Processor::create_full_parser( $buffer );
    739766                                if ( $p->next_tag( array( 'tag_name' => 'TITLE' ) ) ) {
    … …  
    742769                                return $p->get_updated_html();
    743770                        }
     771                );
     772
     773                $mock_action_callback = new MockAction();
     774                add_filter(
     775                        'wp_send_late_headers',
     776                        array( $mock_action_callback, 'action' ),
     777                        10,
     778                        PHP_INT_MAX
    744779                );
    745780
    … …  
    775810                $this->assertSame( $initial_ob_level, ob_get_level(), 'Expected the output buffer to be back at the initial level.' );
    776811
    777                 $this->assertFalse( $applied_filter, 'Expected the wp_template_enhancement_output_buffer filter to not have applied.' );
    778                 $this->assertSame( 0, did_action( 'wp_final_template_output_buffer' ), 'Expected the wp_final_template_output_buffer action to not have fired.' );
     812                $this->assertSame( 0, $mock_filter_callback->get_call_count(), 'Expected the wp_template_enhancement_output_buffer filter to not have applied.' );
    779813
    780814                // Obtain the output via the wrapper output buffer.
    … …  
    784818                $this->assertStringNotContainsString( '<title>Processed</title>', $output, 'Expected output buffer to not have string since the filter did not apply.' );
    785819                $this->assertStringContainsString( '<title>Output Buffer Not Processed</title>', $output, 'Expected output buffer to have string since the output buffer was ended with cleaning.' );
     820
     821                $this->assertSame( 0, did_action( 'wp_send_late_headers' ), 'Expected the wp_send_late_headers action to not have fired.' );
     822                $this->assertSame( 0, $mock_action_callback->get_call_count(), 'Expected wp_send_late_headers action callback to have been called once.' );
    786823        }
    787824
    … …  
    790827         *
    791828         * @ticket 43258
     829         * @ticket 64126
     830         *
    792831         * @covers ::wp_start_template_enhancement_output_buffer
    793832         * @covers ::wp_finalize_template_enhancement_output_buffer
    … …  
    797836                ob_start();
    798837
    799                 $called_filter = false;
     838                $mock_filter_callback = new MockAction();
    800839                add_filter(
    801840                        'wp_template_enhancement_output_buffer',
    802                         static function ( string $buffer ) use ( &$called_filter ): string {
    803                                 $called_filter = true;
    804 
     841                        array( $mock_filter_callback, 'filter' )
     842                );
     843
     844                add_filter(
     845                        'wp_template_enhancement_output_buffer',
     846                        static function ( string $buffer ): string {
    805847                                $p = WP_HTML_Processor::create_full_parser( $buffer );
    806848                                if ( $p->next_tag( array( 'tag_name' => 'TITLE' ) ) ) {
    … …  
    809851                                return $p->get_updated_html();
    810852                        }
     853                );
     854
     855                $mock_action_callback = new MockAction();
     856                add_filter(
     857                        'wp_send_late_headers',
     858                        array( $mock_action_callback, 'action' ),
     859                        10,
     860                        PHP_INT_MAX
    811861                );
    812862
    … …  
    847897                $this->assertSame( $initial_ob_level, ob_get_level(), 'Expected the output buffer to be back at the initial level.' );
    848898
    849                 $this->assertTrue( $called_filter, 'Expected the wp_template_enhancement_output_buffer filter to have applied.' );
     899                $this->assertSame( 1, $mock_filter_callback->get_call_count(), 'Expected the wp_template_enhancement_output_buffer filter to have applied.' );
    850900
    851901                // Obtain the output via the wrapper output buffer.
    … …  
    855905                $this->assertStringContainsString( '<title>Processed</title>', $output, 'Expected output buffer to have string due to filtering.' );
    856906                $this->assertStringContainsString( '<h1>Template Replaced</h1>', $output, 'Expected output buffer to have string due to replaced template.' );
     907
     908                $this->assertSame( 1, did_action( 'wp_send_late_headers' ), 'Expected the wp_send_late_headers action to have fired.' );
     909                $this->assertSame( 1, $mock_action_callback->get_call_count(), 'Expected wp_send_late_headers action callback to have been called once.' );
     910                $action_args = $mock_action_callback->get_args()[0];
     911                $this->assertCount( 1, $action_args, 'Expected the wp_send_late_headers action to have been passed only one argument.' );
     912                $this->assertSame( $output, $action_args[0], 'Expected the arg passed to wp_send_late_headers to be the same as the processed output buffer.' );
    857913        }
    858914
    … …  
    861917         *
    862918         * @ticket 43258
     919         * @ticket 64126
     920         *
    863921         * @covers ::wp_start_template_enhancement_output_buffer
    864922         * @covers ::wp_finalize_template_enhancement_output_buffer
    … …  
    870928                $mock_filter_callback = new MockAction();
    871929                add_filter( 'wp_template_enhancement_output_buffer', array( $mock_filter_callback, 'filter' ) );
     930
     931                $mock_action_callback = new MockAction();
     932                add_filter(
     933                        'wp_send_late_headers',
     934                        array( $mock_action_callback, 'action' ),
     935                        10,
     936                        PHP_INT_MAX
     937                );
    872938
    873939                $initial_ob_level = ob_get_level();
    … …  
    904970                $this->assertIsString( $output, 'Expected ob_get_clean() to return a string.' );
    905971                $this->assertSame( $json, $output, 'Expected output to not be processed.' );
     972
     973                $this->assertSame( 1, did_action( 'wp_send_late_headers' ), 'Expected the wp_send_late_headers action to have fired even though the wp_template_enhancement_output_buffer filter did not apply.' );
     974                $this->assertSame( 1, $mock_action_callback->get_call_count(), 'Expected wp_send_late_headers action callback to have been called once.' );
     975                $action_args = $mock_action_callback->get_args()[0];
     976                $this->assertCount( 1, $action_args, 'Expected the wp_send_late_headers action to have been passed only one argument.' );
     977                $this->assertSame( $output, $action_args[0], 'Expected the arg passed to wp_send_late_headers to be the same as the processed output buffer.' );
    906978        }
    907979
Note: See TracChangeset for help on using the changeset viewer.