Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.76% covered (danger)
6.76%
5 / 74
6.25% covered (danger)
6.25%
1 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
LCP_State
6.76% covered (danger)
6.76%
5 / 74
6.25% covered (danger)
6.25%
1 / 16
862.14
0.00% covered (danger)
0.00%
0 / 1
 get_state
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clear
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 save
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 set_error
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 update_page_state
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 set_page_success
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 set_page_errors
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_set_analyzed
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 is_analyzed
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 is_pending
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 prepare_request
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 get_pages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_pages
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 set_pending_pages
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 get
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 WP_Error;
6
7class LCP_State {
8    const ANALYSIS_STATES = array(
9        'not_analyzed' => 'not_analyzed',
10        'pending'      => 'pending',
11        'analyzed'     => 'analyzed',
12        'error'        => 'error',
13    );
14
15    const PAGE_STATES = array(
16        'pending' => 'pending',
17        'success' => 'success',
18        'error'   => 'error',
19    );
20
21    /**
22     * LCP analysis state data
23     *
24     * @var array
25     */
26    public $state = array();
27
28    /**
29     * Retrieves and validates the LCP state
30     *
31     * @param bool $refresh Whether to refresh the state from storage.
32     * @return array The validated state
33     * @since 3.13.1
34     */
35    private function get_state( $refresh = false ) {
36        if ( $refresh ) {
37            $stored_state = jetpack_boost_ds_get( 'lcp_state' );
38            $this->state  = is_array( $stored_state ) ? $stored_state : array();
39        } elseif ( ! is_array( $this->state ) ) {
40            $this->state = array();
41        }
42
43        return $this->state;
44    }
45
46    public function __construct() {
47        $this->get_state( true );
48    }
49
50    public function clear() {
51        jetpack_boost_ds_delete( 'lcp_state' );
52    }
53
54    public function save() {
55        $this->state['updated'] = microtime( true );
56        jetpack_boost_ds_set( 'lcp_state', $this->state );
57
58        if ( $this->is_analyzed() ) {
59            /**
60             * Fires when LCP analysis has successfully completed.
61             */
62            do_action( 'jetpack_boost_lcp_analyzed' );
63        }
64    }
65
66    public function set_error( $message ) {
67        if ( empty( $message ) ) {
68            return $this;
69        }
70
71        $this->state['status_error'] = $message;
72        $this->state['status']       = self::ANALYSIS_STATES['error'];
73
74        return $this;
75    }
76
77    /**
78     * Update a page's state. The page must already exist in the state to be updated.
79     *
80     * @param string $page_key The page key.
81     * @param array  $state    An array to overlay over the current state.
82     * @return bool|\WP_Error True on success, WP_Error on failure.
83     */
84    public function update_page_state( $page_key, $state ) {
85        if ( empty( $this->state['pages'] ) ) {
86            return new WP_Error( 'invalid_page_key', 'No pages exist' );
87        }
88
89        $page_index = array_search( $page_key, array_column( $this->state['pages'], 'key' ), true );
90        if ( $page_index === false ) {
91            return new WP_Error( 'invalid_page_key', 'Invalid page key' );
92        }
93
94        $this->state['pages'][ $page_index ] = array_merge(
95            $this->state['pages'][ $page_index ],
96            $state
97        );
98
99        $this->maybe_set_analyzed();
100
101        return true;
102    }
103
104    /**
105     * Set a page's state to success.
106     *
107     * @param string $page_key The page key.
108     * @return bool|\WP_Error True on success, WP_Error on failure.
109     */
110    public function set_page_success( $page_key ) {
111        return $this->update_page_state(
112            $page_key,
113            array(
114                'status' => self::PAGE_STATES['success'],
115                'errors' => null,
116            )
117        );
118    }
119
120    /**
121     * Signifies that the page was not optimized for reason(s) in $errors.
122     *
123     * @param string $page_key The page key.
124     * @param array  $errors   The errors to set for the page.
125     * @return bool|\WP_Error True on success, WP_Error on failure.
126     */
127    public function set_page_errors( $page_key, $errors ) {
128        return $this->update_page_state(
129            $page_key,
130            array(
131                'status' => self::PAGE_STATES['error'],
132                'errors' => $errors,
133            )
134        );
135    }
136
137    /**
138     * Set the state to analyzed if all pages are done. Should be called wherever
139     * a page's state is updated.
140     */
141    private function maybe_set_analyzed() {
142        if ( empty( $this->state['pages'] ) ) {
143            return;
144        }
145
146        $page_states = array_column( $this->state['pages'], 'status' );
147        $is_done     = ! in_array( self::PAGE_STATES['pending'], $page_states, true );
148
149        if ( $is_done ) {
150            $this->state['status'] = self::ANALYSIS_STATES['analyzed'];
151        }
152    }
153
154    public function is_analyzed() {
155        return ! empty( $this->state )
156            && isset( $this->state['status'] )
157            && self::ANALYSIS_STATES['analyzed'] === $this->state['status'];
158    }
159
160    public function is_pending() {
161        return ! empty( $this->state )
162            && isset( $this->state['status'] )
163            && self::ANALYSIS_STATES['pending'] === $this->state['status'];
164    }
165
166    public function prepare_request() {
167        $this->state = array(
168            'status'  => self::ANALYSIS_STATES['pending'],
169            'pages'   => array(),
170            'created' => microtime( true ),
171            'updated' => microtime( true ),
172        );
173
174        return $this;
175    }
176
177    /**
178     * Get the pages from the state.
179     *
180     * @return array The pages from the state.
181     *
182     * @since 4.0.0
183     */
184    public function get_pages() {
185        return $this->state['pages'];
186    }
187
188    /**
189     * Set the pages in the state.
190     *
191     * @param array $pages The pages to set in the state.
192     * @return $this
193     *
194     * @since 4.0.0
195     */
196    public function set_pages( $pages ) {
197        $this->state['pages'] = $pages;
198        return $this;
199    }
200
201    /**
202     * Set the status to pending for the pages that are in the $pages array.
203     * Pages that are not in the $pages array will not be touched.
204     *
205     * @param array $pages The pages to set to pending.
206     * @return $this
207     *
208     * @since 4.0.0
209     */
210    public function set_pending_pages( $pages ) {
211        $current_pages = $this->state['pages'];
212
213        foreach ( $pages as $page ) {
214            $page_key = $page['key'];
215            foreach ( $current_pages as $index => $current_page ) {
216                if ( $current_page['key'] === $page_key ) {
217                    $current_pages[ $index ]['status'] = self::PAGE_STATES['pending'];
218                    break;
219                }
220            }
221        }
222
223        $this->state['pages'] = $current_pages;
224        return $this;
225    }
226
227    /**
228     * Get fresh state
229     *
230     * @return array Current LCP state
231     * @since 3.13.1
232     */
233    public function get() {
234        return $this->get_state( true );
235    }
236}