Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.89% covered (warning)
63.89%
46 / 72
33.33% covered (danger)
33.33%
4 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Lcp
63.89% covered (warning)
63.89%
46 / 72
33.33% covered (danger)
33.33%
4 / 12
30.61
0.00% covered (danger)
0.00%
0 / 1
 setup
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 on_wp_load
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 add_output_filter
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 optimize_lcp_img_tag
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 activate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_slug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_always_available_endpoints
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 is_available
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_ready
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_change_output_action_names
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_data_sync
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
1
 handle_lcp_invalidated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Automattic\Jetpack_Boost\Modules\Optimizations\Lcp;
4
5use Automattic\Jetpack\Schema\Schema;
6use Automattic\Jetpack\WP_JS_Data_Sync\Data_Sync;
7use Automattic\Jetpack_Boost\Contracts\Changes_Output_After_Activation;
8use Automattic\Jetpack_Boost\Contracts\Feature;
9use Automattic\Jetpack_Boost\Contracts\Has_Activate;
10use Automattic\Jetpack_Boost\Contracts\Has_Data_Sync;
11use Automattic\Jetpack_Boost\Contracts\Needs_To_Be_Ready;
12use Automattic\Jetpack_Boost\Contracts\Needs_Website_To_Be_Public;
13use Automattic\Jetpack_Boost\Contracts\Optimization;
14use Automattic\Jetpack_Boost\Lib\Output_Filter;
15use Automattic\Jetpack_Boost\REST_API\Contracts\Has_Always_Available_Endpoints;
16use Automattic\Jetpack_Boost\REST_API\Endpoints\Update_LCP;
17
18class Lcp implements Feature, Changes_Output_After_Activation, Optimization, Has_Activate, Needs_To_Be_Ready, Has_Data_Sync, Has_Always_Available_Endpoints, Needs_Website_To_Be_Public {
19    /** LCP type for background images. */
20    const TYPE_BACKGROUND_IMAGE = 'background-image';
21
22    /** LCP type for standard images. */
23    const TYPE_IMAGE = 'img';
24
25    /**
26     * The LCP data of the current request.
27     *
28     * @var array|false
29     */
30    private $lcp_data;
31
32    public function setup() {
33        add_action( 'wp', array( $this, 'on_wp_load' ), 1 );
34        add_action( 'template_redirect', array( $this, 'add_output_filter' ), -999999 );
35
36        add_action( 'jetpack_boost_lcp_invalidated', array( $this, 'handle_lcp_invalidated' ) );
37
38        LCP_Invalidator::init();
39    }
40
41    public function on_wp_load() {
42        $this->lcp_data = ( new LCP_Storage() )->get_current_request_lcp();
43
44        LCP_Optimize_Bg_Image::init( $this->lcp_data );
45    }
46
47    public function add_output_filter() {
48        if ( LCP_Optimization_Util::should_skip_optimization() ) {
49            return;
50        }
51
52        $output_filter = new Output_Filter();
53        $output_filter->add_callback( array( $this, 'optimize_lcp_img_tag' ) );
54    }
55
56    /**
57     * Optimize the HTML content by finding the LCP image and adding required attributes.
58     *
59     * @param string $buffer_start First part of the buffer.
60     * @param string $buffer_end   Second part of the buffer.
61     *
62     * @return array Parts of the buffer.
63     *
64     * @since 3.13.1
65     */
66    public function optimize_lcp_img_tag( $buffer_start, $buffer_end ) {
67        if ( empty( $this->lcp_data ) ) {
68            return array( $buffer_start, $buffer_end );
69        }
70
71        // Combine the buffers for processing
72        $combined_buffer = $buffer_start . $buffer_end;
73
74        foreach ( $this->lcp_data as $lcp_element ) {
75            $optimizer = new LCP_Optimize_Img_Tag( $lcp_element );
76
77            $combined_buffer = $optimizer->optimize_buffer( $combined_buffer );
78        }
79
80        // Split the modified buffer back into two parts
81        $buffer_start_length = strlen( $buffer_start );
82        $new_buffer_start    = substr( $combined_buffer, 0, $buffer_start_length );
83        $new_buffer_end      = substr( $combined_buffer, $buffer_start_length );
84
85        // Check for successful split
86        if ( false === $new_buffer_start || false === $new_buffer_end ) {
87            // If splitting failed, return the original buffers
88            return array( $buffer_start, $buffer_end );
89        }
90
91        return array( $new_buffer_start, $new_buffer_end );
92    }
93
94    /**
95     * @since 3.13.1
96     */
97    public static function activate() {
98        ( new LCP_Analyzer() )->start();
99    }
100
101    /**
102     * @since 3.13.1
103     */
104    public static function get_slug() {
105        return 'lcp';
106    }
107
108    public function get_always_available_endpoints() {
109        return array(
110            new Update_LCP(),
111        );
112    }
113
114    /**
115     * @since 3.13.1
116     */
117    public static function is_available() {
118        return true;
119    }
120
121    /**
122     * Check if the module is ready and already serving optimized pages.
123     *
124     * @return bool
125     */
126    public function is_ready() {
127        return ( new LCP_State() )->is_analyzed();
128    }
129
130    /**
131     * Get the action names that will be triggered when the module is ready.
132     *
133     * @return string[]
134     */
135    public static function get_change_output_action_names() {
136        return array( 'jetpack_boost_lcp_invalidated', 'jetpack_boost_lcp_analyzed' );
137    }
138
139    /**
140     * Register data sync actions.
141     *
142     * @param Data_Sync $instance The Data_Sync object.
143     */
144    public function register_data_sync( $instance ) {
145        $instance->register(
146            'lcp_state',
147            Schema::as_assoc_array(
148                array(
149                    'pages'        => Schema::as_array(
150                        Schema::as_assoc_array(
151                            array(
152                                'key'    => Schema::as_string(),
153                                'url'    => Schema::as_string(),
154                                'status' => Schema::as_string(),
155                                'errors' => Schema::as_array(
156                                    Schema::as_assoc_array(
157                                        array(
158                                            'type' => Schema::as_string(),
159                                            'meta' => Schema::as_assoc_array(
160                                                array(
161                                                    'code' => Schema::as_number()->nullable(),
162                                                    'selector' => Schema::as_string()->nullable(),
163                                                )
164                                            )->nullable(),
165                                        )
166                                    )
167                                )->nullable(),
168                            )
169                        )
170                    ),
171                    'status'       => Schema::enum( array( 'not_analyzed', 'analyzed', 'pending', 'error' ) )->fallback( 'not_analyzed' ),
172                    'created'      => Schema::as_float()->nullable(),
173                    'updated'      => Schema::as_float()->nullable(),
174                    'status_error' => Schema::as_string()->nullable(),
175                )
176            )->fallback(
177                array(
178                    'pages'   => array(),
179                    'status'  => 'not_analyzed',
180                    'created' => null,
181                    'updated' => null,
182                )
183            )
184        );
185
186        $instance->register_action( 'lcp_state', 'request-analyze', Schema::as_void(), new Optimize_LCP_Endpoint() );
187    }
188
189    /**
190     * Handle the LCP invalidated action.
191     */
192    public function handle_lcp_invalidated() {
193        ( new LCP_Analyzer() )->start();
194    }
195}