Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.01% covered (danger)
1.01%
1 / 99
5.56% covered (danger)
5.56%
1 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
Critical_CSS_State
1.01% covered (danger)
1.01%
1 / 99
5.56% covered (danger)
5.56%
1 / 18
1438.68
0.00% covered (danger)
0.00%
0 / 1
 __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 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 set_provider_error_dismissed
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 update_provider_state
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 set_provider_errors
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 set_provider_success
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_set_generated
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 has_errors
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 get_error_message
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_generated
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_requesting
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prepare_request
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 set_pending_providers
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 prepare_for_generation
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 has_pending_provider
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace Automattic\Jetpack_Boost\Lib\Critical_CSS;
4
5use WP_Error;
6
7class Critical_CSS_State {
8
9    const GENERATION_STATES = array(
10        'not_generated' => 'not_generated',
11        'pending'       => 'pending',
12        'generated'     => 'generated',
13        'error'         => 'error',
14    );
15
16    const PROVIDER_STATES = array(
17        'pending' => 'pending',
18        'success' => 'success',
19        'error'   => 'error',
20    );
21    public $state;
22
23    public function __construct() {
24        $this->state = jetpack_boost_ds_get( 'critical_css_state' );
25    }
26
27    public function clear() {
28        jetpack_boost_ds_delete( 'critical_css_state' );
29    }
30
31    public function save() {
32        $this->state['updated'] = microtime( true );
33        jetpack_boost_ds_set( 'critical_css_state', $this->state );
34
35        if ( $this->is_generated() ) {
36            /**
37             * Fires when critical CSS has successfully been generated.
38             */
39            do_action( 'jetpack_boost_critical_css_generated' );
40        }
41    }
42
43    public function set_error( $message ) {
44        if ( empty( $message ) ) {
45            error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
46                'Critical CSS: set_error() called with empty message'
47            );
48            return $this;
49        }
50
51        $this->state['status_error'] = $message;
52        $this->state['status']       = self::GENERATION_STATES['error'];
53
54        return $this;
55    }
56
57    public function set_provider_error_dismissed( $provider_key, $error_type, $dismissed ) {
58        if ( empty( $this->state['providers'] ) ) {
59            return new WP_Error( 'invalid_provider_key', 'No providers exist' );
60        }
61
62        $provider_index = array_search( $provider_key, array_column( $this->state['providers'], 'key' ), true );
63        if ( $provider_index === false ) {
64            return new WP_Error( 'invalid_provider_key', 'Invalid provider key' );
65        }
66
67        if ( ! isset( $this->state['providers'][ $provider_index ]['dismissed_errors'] ) ) {
68            $this->state['providers'][ $provider_index ]['dismissed_errors'] = array();
69        }
70
71        if ( $dismissed ) {
72            if ( ! in_array( $error_type, $this->state['providers'][ $provider_index ]['dismissed_errors'], true ) ) {
73                $this->state['providers'][ $provider_index ]['dismissed_errors'][] = $error_type;
74            }
75        } else {
76            $key = array_search( $error_type, $this->state['providers'][ $provider_index ]['dismissed_errors'], true );
77            if ( $key !== false ) {
78                unset( $this->state['providers'][ $provider_index ]['dismissed_errors'][ $key ] );
79                $this->state['providers'][ $provider_index ]['dismissed_errors'] = array_values( $this->state['providers'][ $provider_index ]['dismissed_errors'] );
80            }
81        }
82
83        return true;
84    }
85
86    /**
87     * Update a provider's state. The provider must already exist in the state to be updated.
88     *
89     * @param string $provider_key The provider key.
90     * @param array  $state        An array to overlay over the current state.
91     * @return bool|WP_Error True on success, WP_Error on failure.
92     */
93    private function update_provider_state( $provider_key, $state ) {
94        if ( empty( $this->state['providers'] ) ) {
95            return new WP_Error( 'invalid_provider_key', 'No providers exist' );
96        }
97
98        $provider_index = array_search( $provider_key, array_column( $this->state['providers'], 'key' ), true );
99        if ( $provider_index === false ) {
100            return new WP_Error( 'invalid_provider_key', 'Invalid provider key' );
101        }
102
103        $this->state['providers'][ $provider_index ] = array_merge(
104            $this->state['providers'][ $provider_index ],
105            $state
106        );
107
108        $this->maybe_set_generated();
109
110        return true;
111    }
112
113    /**
114     * Set a provider's state to error.
115     *
116     * @param string $provider_key The provider key.
117     * @param array  $errors        A list of errors to store with this provider.
118     * @return bool|WP_Error True on success, WP_Error on failure.
119     */
120    public function set_provider_errors( $provider_key, $errors ) {
121        return $this->update_provider_state(
122            $provider_key,
123            array(
124                'status' => self::PROVIDER_STATES['error'],
125                'errors' => $errors,
126            )
127        );
128    }
129
130    /**
131     * Set a provider's state to success.
132     *
133     * @param string $provider_key The provider key.
134     * @return bool|WP_Error True on success, WP_Error on failure.
135     */
136    public function set_provider_success( $provider_key ) {
137        return $this->update_provider_state(
138            $provider_key,
139            array(
140                'status' => self::PROVIDER_STATES['success'],
141            )
142        );
143    }
144
145    /**
146     * Set the state to generated if all providers are done. Should be called wherever
147     * a provider's state is updated.
148     */
149    private function maybe_set_generated() {
150        if ( empty( $this->state['providers'] ) ) {
151            return;
152        }
153
154        $provider_states = array_column( $this->state['providers'], 'status' );
155        $is_done         = ! in_array( self::GENERATION_STATES['pending'], $provider_states, true );
156
157        if ( $is_done ) {
158            $this->state['status'] = self::GENERATION_STATES['generated'];
159        }
160    }
161
162    public function has_errors() {
163        // Check if any of the providers have errors as well.
164        $any_provider_has_error = in_array(
165            'error',
166            array_unique(
167                wp_list_pluck(
168                    $this->state['providers'],
169                    'status'
170                )
171            ),
172            true
173        );
174
175        return self::GENERATION_STATES['error'] === $this->state['status'] || $any_provider_has_error;
176    }
177
178    public function get_error_message() {
179        return $this->state['status_error'] ?? null;
180    }
181
182    public function is_generated() {
183        return self::GENERATION_STATES['generated'] === $this->state['status'];
184    }
185
186    public function is_requesting() {
187        return self::GENERATION_STATES['pending'] === $this->state['status'];
188    }
189
190    public function prepare_request() {
191        $this->state = array(
192            'status'    => self::GENERATION_STATES['pending'],
193            'providers' => array(),
194            'created'   => microtime( true ),
195            'updated'   => microtime( true ),
196        );
197
198        return $this;
199    }
200
201    public function set_pending_providers( $providers ) {
202        foreach ( $providers as $key => $provider ) {
203            $providers[ $key ]['status'] = self::PROVIDER_STATES['pending'];
204        }
205        $this->state['providers'] = $providers;
206        return $this;
207    }
208
209    /**
210     * Add providers to the state, sets their status to pending
211     * and sets the generation status to pending.
212     *
213     * @param array $providers The providers to include in the state and set as pending.
214     * @return $this
215     */
216    public function prepare_for_generation( $providers ) {
217        $this->set_pending_providers( $providers );
218        $this->state['status'] = self::GENERATION_STATES['pending'];
219        return $this;
220    }
221
222    /**
223     * Get fresh state
224     */
225    public function get() {
226        $this->state = jetpack_boost_ds_get( 'critical_css_state' );
227        return $this->state;
228    }
229
230    public function has_pending_provider( $needles = array() ) {
231        if ( empty( $this->state['providers'] ) ) {
232            return false;
233        }
234
235        $providers = $this->state['providers'];
236        foreach ( $providers as $provider ) {
237            if (
238                ! empty( $provider['key'] )
239                && ! empty( $provider['status'] )
240                && self::PROVIDER_STATES['pending'] === $provider['status']
241                && in_array( $provider['key'], $needles, true )
242            ) {
243                return true;
244            }
245        }
246        return false;
247    }
248}