Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
25.00% covered (danger)
25.00%
8 / 32
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Plan
25.00% covered (danger)
25.00%
8 / 32
25.00% covered (danger)
25.00%
1 / 4
52.19
0.00% covered (danger)
0.00%
0 / 1
 is_cache_old
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_product_from_cache
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_product
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 has_required_plan
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * Class to handle the Protect plan
4 *
5 * @package automattic/jetpack-protect-status
6 */
7
8namespace Automattic\Jetpack\Protect_Status;
9
10use Automattic\Jetpack\Current_Plan;
11
12/**
13 * The Plan class.
14 */
15class Plan {
16    /**
17     * The meta name used to store the cache date
18     *
19     * @var string
20     */
21    const CACHE_DATE_META_NAME = 'protect-cache-date';
22
23    /**
24     * Valid pediord for the cache: One week.
25     */
26    const CACHE_VALIDITY_PERIOD = 7 * DAY_IN_SECONDS;
27
28    /**
29     * The meta name used to store the cache
30     *
31     * @var string
32     */
33    const CACHE_META_NAME = 'protect-cache';
34
35    /**
36     * Every Scan product's slug starts with this: monthly, bi-yearly and the legacy real-time ones.
37     * Automattic for Agencies licenses add an `a4a_` prefix, as in `a4a_jetpack_scan_yearly`.
38     *
39     * @since 0.10.2
40     *
41     * @var string
42     */
43    const SCAN_PRODUCT_PREFIX = 'jetpack_scan';
44
45    /**
46     * Checks if the cache is old, meaning we need to fetch new data from WPCOM
47     */
48    private static function is_cache_old() {
49        if ( empty( self::get_product_from_cache() ) ) {
50            return true;
51        }
52
53        $cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
54        return time() - (int) $cache_date > ( self::CACHE_VALIDITY_PERIOD );
55    }
56
57    /**
58     * Gets the product list from the user cache
59     */
60    private static function get_product_from_cache() {
61        return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true );
62    }
63
64    /**
65     * Gets the product data
66     *
67     * @param string $wpcom_product The product slug.
68     * @return array
69     */
70    public static function get_product( $wpcom_product = 'jetpack_scan' ) {
71        if ( ! self::is_cache_old() ) {
72            return self::get_product_from_cache();
73        }
74
75        $request_url   = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
76        $wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
77        $response_code = wp_remote_retrieve_response_code( $wpcom_request );
78
79        if ( 200 === $response_code ) {
80            $products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
81
82            // Pick the desired product...
83            $product = $products->{$wpcom_product};
84
85            // ... and store it into the cache.
86            update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
87            update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $product );
88
89            return $product;
90        }
91
92        return new \WP_Error(
93            'failed_to_fetch_data',
94            esc_html__( 'Unable to fetch the requested data.', 'jetpack-protect-status' ),
95            array(
96                'status'  => $response_code,
97                'request' => $wpcom_request,
98            )
99        );
100    }
101
102    /**
103     * Has Required Plan
104     *
105     * @param bool $force_refresh Refresh the local plan cache from wpcom.
106     * @return bool True when the site has a plan or product that supports the paid Protect tier.
107     */
108    public static function has_required_plan( $force_refresh = false ) {
109        // WordPress.com's Personal and Premium include `scan` without Scan's UI,
110        // so gate on `scan-self-serve` there, as Jetpack::show_scan_ui() does.
111        $feature = function_exists( 'wpcom_feature_exists' ) && wpcom_feature_exists( 'scan-self-serve' ) ? 'scan-self-serve' : 'scan';
112
113        // Before the products: a refresh updates them too.
114        if ( Current_Plan::supports( $feature, $force_refresh ) ) {
115            return true;
116        }
117
118        $products = array_column( Current_Plan::get_products(), 'product_slug' );
119
120        return array() !== array_filter(
121            $products,
122            fn( $slug ) => 0 === strpos( (string) preg_replace( '/^a4a_/', '', (string) $slug ), self::SCAN_PRODUCT_PREFIX )
123        );
124    }
125}