Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
34 / 51
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Admin_Sidebar_Link
66.67% covered (warning)
66.67%
34 / 51
50.00% covered (danger)
50.00%
5 / 10
57.04
0.00% covered (danger)
0.00%
0 / 1
 instance
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 init_hooks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 maybe_add_admin_link
60.87% covered (warning)
60.87%
14 / 23
0.00% covered (danger)
0.00%
0 / 1
6.50
 refresh_state_cache
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 should_show_link
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 should_show_scan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 should_show_scan_history_only
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 has_scan
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 has_protect_plugin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 maybe_refresh_transient_cache
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
9.66
1<?php
2/**
3 * A class that adds a scan link to the admin sidebar.
4 *
5 * @package automattic/jetpack
6 */
7
8namespace Automattic\Jetpack\Scan;
9
10use Automattic\Jetpack\Admin_UI\Admin_Menu;
11use Automattic\Jetpack\Redirect;
12use Automattic\Jetpack\Status\Host;
13use Jetpack_Core_Json_Api_Endpoints;
14
15/**
16 * Class Main
17 *
18 * Responsible for showing the link if available.
19 *
20 * @package Automattic\Jetpack\Scan
21 */
22class Admin_Sidebar_Link {
23
24    const SCHEDULE_ACTION_HOOK = 'jetpack_scan_refresh_states_event';
25
26    /**
27     * The singleton instance of this class.
28     *
29     * @var Admin_Sidebar_Link
30     */
31    protected static $instance;
32
33    /**
34     * Used to check if we need to schedule the refresh or we need to do it.
35     *
36     * @var boolean | null
37     */
38    private $schedule_refresh_checked;
39
40    /**
41     * Get the singleton instance of the class.
42     *
43     * @return Admin_Sidebar_Link
44     */
45    public static function instance() {
46        if ( ! isset( self::$instance ) ) {
47            self::$instance = new Admin_Sidebar_Link();
48            self::$instance->init_hooks();
49        }
50
51        return self::$instance;
52    }
53
54    /**
55     * Adds action hooks.
56     */
57    public function init_hooks() {
58        add_action( 'jetpack_admin_menu', array( $this, 'maybe_add_admin_link' ), 99 );
59        add_action( self::SCHEDULE_ACTION_HOOK, array( $this, 'refresh_state_cache' ) );
60    }
61
62    /**
63     * Adds a link to the Scan page.
64     */
65    public function maybe_add_admin_link() {
66        if ( ! $this->should_show_link() ) {
67            return;
68        }
69
70        $position = defined( Admin_Menu::class . '::POSITION_EXTERNAL' ) ? Admin_Menu::POSITION_EXTERNAL : 100;
71
72        if ( $this->should_show_scan() ) {
73            Admin_Menu::add_menu(
74                /** "Scan" is a product name, do not translate. */
75                'Scan',
76                'Scan <span aria-hidden="true">↗</span>',
77                'manage_options',
78                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
79                null,
80                $position,
81                array( 'key' => 'jetpack-scan-cloud' )
82            );
83        }
84
85        // Add scan item which shows history page only. This is mutally exclusive from the scan item above and is only shown for Atomic sitse.
86        if ( $this->should_show_scan_history_only() ) {
87            Admin_Menu::add_menu(
88                /** "Scan" is a product name, do not translate. */
89                'Scan',
90                'Scan <span aria-hidden="true">↗</span>',
91                'manage_options',
92                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
93                null,
94                $position,
95                array( 'key' => 'jetpack-scan-cloud' )
96            );
97        }
98    }
99
100    /**
101     * Refreshes the state cache via API call. Called via cron.
102     *
103     * Still warms the rewind state, which this class no longer reads itself:
104     * Jetpack_Core_Json_Api_Endpoints::rewind_data() serves it from that cache.
105     */
106    public function refresh_state_cache() {
107        Jetpack_Core_Json_Api_Endpoints::get_scan_state();
108        Jetpack_Core_Json_Api_Endpoints::get_rewind_data();
109    }
110
111    /**
112     * Returns true if the link should appear.
113     *
114     * @return boolean
115     */
116    private function should_show_link() {
117        // Jetpack Scan is currently not supported on multisite.
118        if ( is_multisite() ) {
119            return false;
120        }
121
122        // Check if VaultPress is active, the assumption there is that VaultPress is working.
123        // It has its link the adminbar.
124        if ( class_exists( 'VaultPress' ) ) {
125            return false;
126        }
127
128        return $this->should_show_scan() || $this->should_show_scan_history_only();
129    }
130
131    /**
132     * Check if we should display the Scan menu item.
133     *
134     * It will only be displayed if site has Scan enabled, is not an Atomic site, and the stand-alone Protect plugin is not active, because it will have a menu item of its own.
135     *
136     * @return boolean
137     */
138    private function should_show_scan() {
139        return $this->has_scan() && ! $this->has_protect_plugin() && ! ( new Host() )->is_woa_site();
140    }
141
142    /**
143     * Check if we should display the Scan menu item history.
144     *
145     * It will only be displayed if site has Scan enabled, is an Atomic site.
146     *
147     * @return boolean
148     */
149    private function should_show_scan_history_only() {
150        return $this->has_scan() && ( new Host() )->is_woa_site() && get_option( 'wpcom_admin_interface' ) === 'wp-admin';
151    }
152
153    /**
154     * Detects if Scan is enabled.
155     *
156     * @return boolean
157     */
158    private function has_scan() {
159        $this->maybe_refresh_transient_cache();
160        $scan_state = get_transient( 'jetpack_scan_state' );
161        if ( ! $scan_state ) {
162            return false;
163        }
164
165        return isset( $scan_state->state ) && 'unavailable' !== $scan_state->state;
166    }
167
168    /**
169     * Detects if Protect plugin is active.
170     *
171     * @return boolean
172     */
173    private function has_protect_plugin() {
174        return class_exists( 'Jetpack_Protect' );
175    }
176
177    /**
178     * Triggers a cron job to refresh the Scan and Rewind state cache.
179     */
180    private function maybe_refresh_transient_cache() {
181        if ( $this->schedule_refresh_checked ) {
182            return;
183        }
184
185        // Do we have a jetpack_scan and jetpack_rewind state set?
186        if ( get_transient( 'jetpack_scan_state' ) && get_transient( 'jetpack_rewind_state' ) ) {
187            return;
188        }
189
190        if ( false === wp_next_scheduled( self::SCHEDULE_ACTION_HOOK ) ) {
191            wp_schedule_single_event( time(), self::SCHEDULE_ACTION_HOOK );
192        }
193
194        $this->schedule_refresh_checked = true;
195    }
196}