Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.52% covered (warning)
69.52%
130 / 187
47.37% covered (danger)
47.37%
9 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Protect
70.27% covered (warning)
70.27%
130 / 185
47.37% covered (danger)
47.37%
9 / 19
43.76
0.00% covered (danger)
0.00%
0 / 1
 register_endpoints
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 permissions_callback
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_title
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_long_description
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_features
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 get_tiers
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 get_features_by_tier
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
1 / 1
1
 get_pricing_for_ui
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 does_module_need_attention
30.43% covered (danger)
30.43%
7 / 23
0.00% covered (danger)
0.00%
0 / 1
13.42
 get_paid_plan_product_slugs
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 is_upgradable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_post_checkout_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_post_checkout_urls_by_feature
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get_manage_url
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 get_manage_urls_by_feature
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 is_upgradable_by_bundle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_site_protect_data
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Protect product
4 *
5 * @package my-jetpack
6 */
7
8namespace Automattic\Jetpack\My_Jetpack\Products;
9
10use Automattic\Jetpack\My_Jetpack\Hybrid_Product;
11use Automattic\Jetpack\My_Jetpack\Wpcom_Products;
12use Automattic\Jetpack\Protect_Status\Status as Protect_Status;
13use Automattic\Jetpack\Redirect;
14use Automattic\Jetpack\Waf\Waf_Runner;
15use WP_Error;
16use WP_REST_Response;
17
18if ( ! defined( 'ABSPATH' ) ) {
19    exit( 0 );
20}
21
22/**
23 * Class responsible for handling the Protect product
24 */
25class Protect extends Hybrid_Product {
26
27    const FREE_TIER_SLUG             = 'free';
28    const UPGRADED_TIER_SLUG         = 'upgraded';
29    const UPGRADED_TIER_PRODUCT_SLUG = 'jetpack_scan';
30
31    const SCAN_FEATURE_SLUG     = 'scan';
32    const FIREWALL_FEATURE_SLUG = 'firewall';
33
34    /**
35     * The product slug
36     *
37     * @var string
38     */
39    public static $slug = 'protect';
40
41    /**
42     * The Jetpack module name
43     *
44     * @var string
45     */
46    public static $module_name = 'protect';
47
48    /**
49     * The filename (id) of the plugin associated with this product.
50     *
51     * @var string
52     */
53    public static $plugin_filename = array(
54        'jetpack-protect/jetpack-protect.php',
55        'protect/jetpack-protect.php',
56        'jetpack-protect-dev/jetpack-protect.php',
57    );
58
59    /**
60     * The slug of the plugin associated with this product.
61     *
62     * @var string
63     */
64    public static $plugin_slug = 'jetpack-protect';
65
66    /**
67     * The category of the product
68     *
69     * @var string
70     */
71    public static $category = 'security';
72
73    /**
74     * Defines whether or not to show a product interstitial as tiered pricing or not
75     *
76     * @var bool
77     */
78    public static $is_tiered_pricing = true;
79
80    /**
81     * Whether this product requires a user connection
82     *
83     * @var string
84     */
85    public static $requires_user_connection = false;
86
87    /**
88     * Whether this product has a free offering
89     *
90     * @var bool
91     */
92    public static $has_free_offering = true;
93
94    /**
95     * Protect has a standalone plugin
96     *
97     * @var bool
98     */
99    public static $has_standalone_plugin = true;
100
101    /**
102     * The feature slug that identifies the paid plan
103     *
104     * @var string
105     */
106    public static $feature_identifying_paid_plan = 'scan';
107
108    /**
109     * Setup Protect REST API endpoints
110     *
111     * @return void
112     */
113    public static function register_endpoints(): void {
114        parent::register_endpoints();
115        // Get Jetpack Protect data.
116        register_rest_route(
117            'my-jetpack/v1',
118            '/site/protect/data',
119            array(
120                'methods'             => \WP_REST_Server::READABLE,
121                'callback'            => __CLASS__ . '::get_site_protect_data',
122                'permission_callback' => __CLASS__ . '::permissions_callback',
123            )
124        );
125    }
126
127    /**
128     * Checks if the user has the correct permissions
129     */
130    public static function permissions_callback() {
131        return current_user_can( 'edit_posts' );
132    }
133
134    /**
135     * Get the product name
136     *
137     * @return string
138     */
139    public static function get_name() {
140        return 'Protect';
141    }
142
143    /**
144     * Get the product title
145     *
146     * @return string
147     */
148    public static function get_title() {
149        return 'Jetpack Protect';
150    }
151
152    /**
153     * Get the internationalized product description
154     *
155     * @return string
156     */
157    public static function get_description() {
158        return __( 'Guard against malware and bad actors 24/7', 'jetpack-my-jetpack' );
159    }
160
161    /**
162     * Get the internationalized product long description
163     *
164     * @return string
165     */
166    public static function get_long_description() {
167        return __( 'Protect your site from bad actors and malware 24/7. Clean up security vulnerabilities with one click.', 'jetpack-my-jetpack' );
168    }
169
170    /**
171     * Get the internationalized features list
172     *
173     * @return array Protect features list
174     */
175    public static function get_features() {
176        return array(
177            __( 'Over 20,000 listed vulnerabilities', 'jetpack-my-jetpack' ),
178            __( 'Daily automatic scans', 'jetpack-my-jetpack' ),
179            __( 'Check plugin and theme version status', 'jetpack-my-jetpack' ),
180            __( 'Easy to navigate and use', 'jetpack-my-jetpack' ),
181        );
182    }
183
184    /**
185     * Get the product's available tiers
186     *
187     * @return string[] Slugs of the available tiers
188     */
189    public static function get_tiers() {
190        return array(
191            self::UPGRADED_TIER_SLUG,
192            self::FREE_TIER_SLUG,
193        );
194    }
195
196    /**
197     * Get the internationalized comparison of free vs upgraded features
198     *
199     * @return array[] Protect features comparison
200     */
201    public static function get_features_by_tier() {
202        return array(
203            array(
204                'name'  => __( 'Scan for threats and vulnerabilities', 'jetpack-my-jetpack' ),
205                'tiers' => array(
206                    self::FREE_TIER_SLUG     => array(
207                        'included'    => true,
208                        'description' => __( 'Check items against database', 'jetpack-my-jetpack' ),
209                    ),
210                    self::UPGRADED_TIER_SLUG => array(
211                        'included'    => true,
212                        'description' => __( 'Line by line malware scanning', 'jetpack-my-jetpack' ),
213                    ),
214                ),
215            ),
216            array(
217                'name'  => __( 'Daily automated scans', 'jetpack-my-jetpack' ),
218                'tiers' => array(
219                    self::FREE_TIER_SLUG     => array( 'included' => true ),
220                    self::UPGRADED_TIER_SLUG => array(
221                        'included'    => true,
222                        'description' => __( 'Plus on-demand manual scans', 'jetpack-my-jetpack' ),
223                    ),
224                ),
225            ),
226            array(
227                'name'  => __( 'Web Application Firewall', 'jetpack-my-jetpack' ),
228                'tiers' => array(
229                    self::FREE_TIER_SLUG     => array(
230                        'included'    => false,
231                        'description' => __( 'Manual rules only', 'jetpack-my-jetpack' ),
232                    ),
233                    self::UPGRADED_TIER_SLUG => array(
234                        'included'    => true,
235                        'description' => __( 'Automatic protection and rule updates', 'jetpack-my-jetpack' ),
236                    ),
237                ),
238            ),
239            array(
240                'name'  => __( 'Brute force protection', 'jetpack-my-jetpack' ),
241                'tiers' => array(
242                    self::FREE_TIER_SLUG     => array( 'included' => true ),
243                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
244                ),
245            ),
246            array(
247                'name'  => __( 'Account protection', 'jetpack-my-jetpack' ),
248                'tiers' => array(
249                    self::FREE_TIER_SLUG     => array( 'included' => true ),
250                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
251                ),
252            ),
253            array(
254                'name'  => __( 'Access to scan on Cloud', 'jetpack-my-jetpack' ),
255                'tiers' => array(
256                    self::FREE_TIER_SLUG     => array( 'included' => false ),
257                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
258                ),
259            ),
260            array(
261                'name'  => __( 'One-click auto fixes', 'jetpack-my-jetpack' ),
262                'tiers' => array(
263                    self::FREE_TIER_SLUG     => array( 'included' => false ),
264                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
265                ),
266            ),
267            array(
268                'name'  => __( 'Notifications', 'jetpack-my-jetpack' ),
269                'tiers' => array(
270                    self::FREE_TIER_SLUG     => array( 'included' => false ),
271                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
272                ),
273            ),
274            array(
275                'name'  => __( 'Severity labels', 'jetpack-my-jetpack' ),
276                'tiers' => array(
277                    self::FREE_TIER_SLUG     => array( 'included' => false ),
278                    self::UPGRADED_TIER_SLUG => array( 'included' => true ),
279                ),
280            ),
281        );
282    }
283
284    /**
285     * Get the product pricing details
286     *
287     * @return array Pricing details
288     */
289    public static function get_pricing_for_ui() {
290        return array(
291            'tiers' => array(
292                self::FREE_TIER_SLUG     => array(
293                    'available' => true,
294                    'is_free'   => true,
295                ),
296                self::UPGRADED_TIER_SLUG => array_merge(
297                    array(
298                        'available'          => true,
299                        'wpcom_product_slug' => self::UPGRADED_TIER_PRODUCT_SLUG,
300                    ),
301                    Wpcom_Products::get_product_pricing( self::UPGRADED_TIER_PRODUCT_SLUG )
302                ),
303            ),
304        );
305    }
306
307    /**
308     * Determines whether the module/plugin/product needs the users attention.
309     * Typically due to some sort of error where user troubleshooting is needed.
310     *
311     * @return boolean|array
312     */
313    public static function does_module_need_attention() {
314        $protect_threat_status = false;
315        $scan_data             = Protect_Status::get_status();
316
317        // Check if there are scan threats.
318        $protect_data = $scan_data;
319        if ( is_wp_error( $protect_data ) ) {
320            return $protect_threat_status; // false
321        }
322        $critical_threat_count = false;
323        if ( ! empty( $protect_data->threats ) ) {
324            $critical_threat_count = array_reduce(
325                $protect_data->threats,
326                function ( $accum, $threat ) {
327                    return $threat->severity >= 5 ? ++$accum : $accum;
328                },
329                0
330            );
331
332            $protect_threat_status = array(
333                'type' => $critical_threat_count ? 'error' : 'warning',
334                'data' => array(
335                    'threat_count'          => count( $protect_data->threats ),
336                    'critical_threat_count' => $critical_threat_count,
337                    'fixable_threat_ids'    => $protect_data->fixable_threat_ids,
338                ),
339            );
340        }
341
342        return $protect_threat_status;
343    }
344
345    /**
346     * Get the product-slugs of the paid plans for this product.
347     * (Do not include bundle plans, unless it's a bundle plan itself).
348     *
349     * @return array
350     */
351    public static function get_paid_plan_product_slugs() {
352        return array(
353            'jetpack_scan',
354            'jetpack_scan_monthly',
355            'jetpack_scan_bi_yearly',
356        );
357    }
358
359    /**
360     * Checks whether the product can be upgraded - i.e. this shows the /#add-protect interstitial
361     *
362     * @return boolean
363     */
364    public static function is_upgradable() {
365        return ! self::has_paid_plan_for_product();
366    }
367
368    /**
369     * Get the URL the user is taken after purchasing the product through the checkout
370     *
371     * @return ?string
372     */
373    public static function get_post_checkout_url() {
374        return self::get_manage_url();
375    }
376
377    /**
378     * Get the URL the user is taken after purchasing the product through the checkout for each product feature
379     *
380     * @return ?array
381     */
382    public static function get_post_checkout_urls_by_feature() {
383        return array(
384            self::SCAN_FEATURE_SLUG     => self::get_post_checkout_url(),
385            self::FIREWALL_FEATURE_SLUG => admin_url( 'admin.php?page=jetpack-protect#/firewall' ),
386        );
387    }
388
389    /**
390     * Get the URL where the user manages the product
391     *
392     * @return ?string
393     */
394    public static function get_manage_url() {
395        if ( static::is_standalone_plugin_active() ) {
396            // Protect admin dashboard.
397            return admin_url( 'admin.php?page=jetpack-protect' );
398        }
399
400        if ( static::has_paid_plan_for_product() ) {
401            // Paid users without standalone plugin go to Jetpack Cloud Scan dashboard.
402            return Redirect::get_url( 'my-jetpack-manage-scan' );
403        }
404
405        // Free users without standalone plugin go to the Protect details page.
406        return admin_url( 'admin.php?page=my-jetpack#/protect-details' );
407    }
408
409    /**
410     * Get the URL where the user manages the product for each product feature
411     *
412     * @return ?array
413     */
414    public static function get_manage_urls_by_feature() {
415        return array(
416            self::SCAN_FEATURE_SLUG     => self::get_manage_url(),
417            self::FIREWALL_FEATURE_SLUG => admin_url( 'admin.php?page=jetpack-protect#/firewall' ),
418        );
419    }
420
421    /**
422     * Return product bundles list
423     * that supports the product.
424     *
425     * @return array Products bundle list.
426     */
427    public static function is_upgradable_by_bundle() {
428        return array( 'security', 'complete' );
429    }
430
431    /**
432     * Return site Jetpack Protect data for the REST API.
433     *
434     * @return WP_Rest_Response|WP_Error
435     */
436    public static function get_site_protect_data() {
437        $scan_data = Protect_Status::get_status();
438
439        $waf_config     = array();
440        $waf_supported  = false;
441        $is_waf_enabled = false;
442
443        if ( class_exists( 'Automattic\Jetpack\Waf\Waf_Runner' ) ) {
444            // @phan-suppress-next-line PhanUndeclaredClassMethod
445            $waf_config = Waf_Runner::get_config();
446            // @phan-suppress-next-line PhanUndeclaredClassMethod
447            $is_waf_enabled = Waf_Runner::is_enabled();
448            // @phan-suppress-next-line PhanUndeclaredClassMethod
449            $waf_supported = Waf_Runner::is_supported_environment();
450        }
451
452        return rest_ensure_response(
453            array(
454                'scanData'  => $scan_data,
455                'wafConfig' => array_merge(
456                    $waf_config,
457                    array(
458                        'waf_supported' => $waf_supported,
459                        'waf_enabled'   => $is_waf_enabled,
460                    ),
461                    array( 'blocked_logins' => (int) get_site_option( 'jetpack_protect_blocked_attempts', 0 ) )
462                ),
463            )
464        );
465    }
466}