Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
31.37% covered (danger)
31.37%
16 / 51
63.64% covered (warning)
63.64%
7 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Status
31.37% covered (danger)
31.37%
16 / 51
63.64% covered (warning)
63.64%
7 / 11
362.97
0.00% covered (danger)
0.00%
0 / 1
 get_status
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 is_cache_expired
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 should_use_cache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 get_from_options
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 update_status_option
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 get_cache_end_date_by_status
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delete_option
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 has_threats
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_total_threats
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_all_threats
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_wordpress_threats
n/a
0 / 0
n/a
0 / 0
1
 get_themes_threats
n/a
0 / 0
n/a
0 / 0
1
 get_plugins_threats
n/a
0 / 0
n/a
0 / 0
1
 get_files_threats
n/a
0 / 0
n/a
0 / 0
1
 get_database_threats
n/a
0 / 0
n/a
0 / 0
1
 get_threats
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 sort_threats
n/a
0 / 0
n/a
0 / 0
2
1<?php
2/**
3 * Class to handle the Status of Jetpack Protect
4 *
5 * @package automattic/jetpack-protect-status
6 */
7
8namespace Automattic\Jetpack\Protect_Status;
9
10use Automattic\Jetpack\Protect_Models\Status_Model;
11
12/**
13 * Class that handles fetching and caching the Status of vulnerabilities check from the WPCOM servers
14 */
15class Status {
16
17    const PACKAGE_VERSION = '0.7.8';
18    /**
19     * Name of the option where status is stored
20     *
21     * @var string
22     */
23    const OPTION_NAME = '';
24
25    /**
26     * Name of the option where the timestamp of the status is stored
27     *
28     * @var string
29     */
30    const OPTION_TIMESTAMP_NAME = '';
31
32    /**
33     * Time in seconds that the cache should last
34     *
35     * @var int
36     */
37    const OPTION_EXPIRES_AFTER = 3600; // 1 hour.
38
39    /**
40     * Time in seconds that the cache for the initial empty response should last
41     *
42     * @var int
43     */
44    const INITIAL_OPTION_EXPIRES_AFTER = 1 * MINUTE_IN_SECONDS;
45
46    /**
47     * Memoization for the current status
48     *
49     * @var null|Status_Model
50     */
51    public static $status = null;
52
53    /**
54     * Gets the current status of the Jetpack Protect checks
55     *
56     * @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
57     * @return Status_Model
58     */
59    public static function get_status( $refresh_from_wpcom = false ) {
60        $use_scan_status = Plan::has_required_plan( $refresh_from_wpcom );
61
62        if ( defined( 'JETPACK_PROTECT_DEV__DATA_SOURCE' ) ) {
63            if ( 'scan_api' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
64                $use_scan_status = true;
65            }
66
67            if ( 'protect_report' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
68                $use_scan_status = false;
69            }
70        }
71
72        self::$status = $use_scan_status ? Scan_Status::get_status( $refresh_from_wpcom ) : Protect_Status::get_status( $refresh_from_wpcom );
73        return self::$status;
74    }
75
76    /**
77     * Checks if the current cached status is expired and should be renewed
78     *
79     * @return boolean
80     */
81    public static function is_cache_expired() {
82        $option_timestamp = get_option( static::OPTION_TIMESTAMP_NAME );
83
84        if ( ! $option_timestamp ) {
85            return true;
86        }
87
88        return time() > (int) $option_timestamp;
89    }
90
91    /**
92     * Checks if we should consider the stored cache or bypass it
93     *
94     * @return boolean
95     */
96    public static function should_use_cache() {
97        return defined( 'JETPACK_PROTECT_DEV__BYPASS_CACHE' ) && JETPACK_PROTECT_DEV__BYPASS_CACHE ? false : true;
98    }
99
100    /**
101     * Gets the current cached status
102     *
103     * @return bool|array False if value is not found. Array with values if cache is found.
104     */
105    public static function get_from_options() {
106        return maybe_unserialize( get_option( static::OPTION_NAME ) );
107    }
108
109    /**
110     * Updated the cached status and its timestamp
111     *
112     * @param array $status The new status to be cached.
113     * @return void
114     */
115    public static function update_status_option( $status ) {
116        // TODO: Sanitize $status.
117        update_option( static::OPTION_NAME, maybe_serialize( $status ) );
118        $end_date = self::get_cache_end_date_by_status( $status );
119        update_option( static::OPTION_TIMESTAMP_NAME, $end_date );
120    }
121
122    /**
123     * Returns the timestamp the cache should expire depending on the current status
124     *
125     * Initial empty status, which are returned before the first check was performed, should be cache for less time
126     *
127     * @param object $status The response from the server being cached.
128     * @return int The timestamp when the cache should expire.
129     */
130    public static function get_cache_end_date_by_status( $status ) {
131        if ( ! is_object( $status ) || empty( $status->last_checked ) ) {
132            return time() + static::INITIAL_OPTION_EXPIRES_AFTER;
133        }
134        return time() + static::OPTION_EXPIRES_AFTER;
135    }
136
137    /**
138     * Delete the cached status and its timestamp
139     *
140     * @return bool Whether all related status options were successfully deleted.
141     */
142    public static function delete_option() {
143        $option_deleted           = delete_option( static::OPTION_NAME );
144        $option_timestamp_deleted = delete_option( static::OPTION_TIMESTAMP_NAME );
145
146        return $option_deleted && $option_timestamp_deleted;
147    }
148
149    /**
150     * Checks the current status to see if there are any threats found
151     *
152     * @return boolean
153     */
154    public static function has_threats() {
155        return 0 < self::get_total_threats();
156    }
157
158    /**
159     * Gets the total number of threats found
160     *
161     * @return integer
162     */
163    public static function get_total_threats() {
164        $status = static::get_status();
165        return count( $status->threats );
166    }
167
168    /**
169     * Get all threats combined
170     *
171     * @return array
172     */
173    public static function get_all_threats() {
174        $status = static::get_status();
175        return $status->threats;
176    }
177
178    /**
179     * Get threats found for WordPress core
180     *
181     * @deprecated 0.3.0
182     *
183     * @return array
184     */
185    public static function get_wordpress_threats() {
186        return self::get_threats( 'core' );
187    }
188
189    /**
190     * Get threats found for themes
191     *
192     * @deprecated 0.3.0
193     *
194     * @return array
195     */
196    public static function get_themes_threats() {
197        return self::get_threats( 'themes' );
198    }
199
200    /**
201     * Get threats found for plugins
202     *
203     * @deprecated 0.3.0
204     *
205     * @return array
206     */
207    public static function get_plugins_threats() {
208        return self::get_threats( 'plugins' );
209    }
210
211    /**
212     * Get threats found for files
213     *
214     * @deprecated 0.3.0
215     *
216     * @return array
217     */
218    public static function get_files_threats() {
219        return self::get_threats( 'files' );
220    }
221
222    /**
223     * Get threats found for plugins
224     *
225     * @deprecated 0.3.0
226     *
227     * @return array
228     */
229    public static function get_database_threats() {
230        return self::get_threats( 'database' );
231    }
232
233    /**
234     * Get the threats for one type of extension or core
235     *
236     * @param string $type What threats you want to get. Possible values are 'core', 'themes' and 'plugins'.
237     *
238     * @return array
239     */
240    public static function get_threats( $type ) {
241        $status = static::get_status();
242
243        if ( in_array( $type, array( 'plugin', 'theme', 'core' ), true ) ) {
244            return array_filter(
245                $status->threats,
246                function ( $threat ) use ( $type ) {
247                    return isset( $threat->extension ) && $type === $threat->extension->type;
248                }
249            );
250        }
251
252        if ( 'files' === $type ) {
253            return array_filter(
254                $status->threats,
255                function ( $threat ) {
256                    return ! empty( $threat->filename );
257                }
258            );
259        }
260
261        if ( 'database' === $type ) {
262            return array_filter(
263                $status->threats,
264                function ( $threat ) {
265                    return ! empty( $threat->table );
266                }
267            );
268        }
269
270        return $status->threats;
271    }
272
273    /**
274     * Sort By Threats
275     *
276     * @deprecated 0.3.0
277     *
278     * @param array<object> $threats Array of threats to sort.
279     *
280     * @return array<object> The sorted $threats array.
281     */
282    protected static function sort_threats( $threats ) {
283        usort(
284            $threats,
285            function ( $a, $b ) {
286                // sort primarily based on the presence of threats
287                $ret = empty( $a->threats ) <=> empty( $b->threats );
288
289                // sort secondarily on whether the item has been checked
290                if ( ! $ret ) {
291                    $ret = $a->checked <=> $b->checked;
292                }
293
294                return $ret;
295            }
296        );
297
298        return $threats;
299    }
300}