Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
63.89% |
46 / 72 |
|
33.33% |
4 / 12 |
CRAP | |
0.00% |
0 / 1 |
| Lcp | |
63.89% |
46 / 72 |
|
33.33% |
4 / 12 |
30.61 | |
0.00% |
0 / 1 |
| setup | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| on_wp_load | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| add_output_filter | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| optimize_lcp_img_tag | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
| activate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_slug | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_always_available_endpoints | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| is_available | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| is_ready | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_change_output_action_names | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| register_data_sync | |
100.00% |
41 / 41 |
|
100.00% |
1 / 1 |
1 | |||
| handle_lcp_invalidated | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace Automattic\Jetpack_Boost\Modules\Optimizations\Lcp; |
| 4 | |
| 5 | use Automattic\Jetpack\Schema\Schema; |
| 6 | use Automattic\Jetpack\WP_JS_Data_Sync\Data_Sync; |
| 7 | use Automattic\Jetpack_Boost\Contracts\Changes_Output_After_Activation; |
| 8 | use Automattic\Jetpack_Boost\Contracts\Feature; |
| 9 | use Automattic\Jetpack_Boost\Contracts\Has_Activate; |
| 10 | use Automattic\Jetpack_Boost\Contracts\Has_Data_Sync; |
| 11 | use Automattic\Jetpack_Boost\Contracts\Needs_To_Be_Ready; |
| 12 | use Automattic\Jetpack_Boost\Contracts\Needs_Website_To_Be_Public; |
| 13 | use Automattic\Jetpack_Boost\Contracts\Optimization; |
| 14 | use Automattic\Jetpack_Boost\Lib\Output_Filter; |
| 15 | use Automattic\Jetpack_Boost\REST_API\Contracts\Has_Always_Available_Endpoints; |
| 16 | use Automattic\Jetpack_Boost\REST_API\Endpoints\Update_LCP; |
| 17 | |
| 18 | class 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 | } |