Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Admin_Sidebar_Link
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 14
1560
0.00% covered (danger)
0.00%
0 / 1
 instance
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 init_hooks
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_add_admin_link
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
30
 get_link_offset
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 refresh_state_cache
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 should_show_link
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 should_show_scan
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 should_show_scan_history_only
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 should_show_backup
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 has_scan
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 has_protect_plugin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 has_backup
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 has_backup_plugin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_refresh_transient_cache
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * A class that adds a scan and backup 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\My_Jetpack\Products\Backup;
12use Automattic\Jetpack\Redirect;
13use Automattic\Jetpack\Status\Host;
14use Jetpack_Core_Json_Api_Endpoints;
15
16/**
17 * Class Main
18 *
19 * Responsible for showing the link if available.
20 *
21 * @package Automattic\Jetpack\Scan
22 */
23class Admin_Sidebar_Link {
24
25    const SCHEDULE_ACTION_HOOK = 'jetpack_scan_refresh_states_event';
26
27    /**
28     * The singleton instance of this class.
29     *
30     * @var Admin_Sidebar_Link
31     */
32    protected static $instance;
33
34    /**
35     * Used to check if we need to schedule the refresh or we need to do it.
36     *
37     * @var boolean | null
38     */
39    private $schedule_refresh_checked;
40
41    /**
42     * Get the singleton instance of the class.
43     *
44     * @return Admin_Sidebar_Link
45     */
46    public static function instance() {
47        if ( ! isset( self::$instance ) ) {
48            self::$instance = new Admin_Sidebar_Link();
49            self::$instance->init_hooks();
50        }
51
52        return self::$instance;
53    }
54
55    /**
56     * Adds action hooks.
57     */
58    public function init_hooks() {
59        add_action( 'jetpack_admin_menu', array( $this, 'maybe_add_admin_link' ), 99 );
60        add_action( self::SCHEDULE_ACTION_HOOK, array( $this, 'refresh_state_cache' ) );
61    }
62
63    /**
64     * Adds a link to the Scan and Backup page.
65     */
66    public function maybe_add_admin_link() {
67        if ( ! $this->should_show_link() ) {
68            return;
69        }
70
71        if ( $this->should_show_scan() ) {
72            Admin_Menu::add_menu(
73                /** "Scan" is a product name, do not translate. */
74                'Scan',
75                'Scan <span class="dashicons dashicons-external"></span>',
76                'manage_options',
77                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
78                null,
79                $this->get_link_offset()
80            );
81        }
82
83        // Add scan item which shows history page only. This is mutally exclusive from the scan item above and is only shown for Atomic sitse.
84        if ( $this->should_show_scan_history_only() ) {
85            Admin_Menu::add_menu(
86                /** "Scan" is a product name, do not translate. */
87                'Scan',
88                'Scan <span class="dashicons dashicons-external"></span>',
89                'manage_options',
90                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
91                null,
92                $this->get_link_offset()
93            );
94        }
95
96        if ( $this->should_show_backup() ) {
97            Admin_Menu::add_menu(
98                /** "VaultPress Backup" is a product name, do not translate. */
99                'VaultPress Backup',
100                'VaultPress Backup <span class="dashicons dashicons-external"></span>',
101                'manage_options',
102                esc_url( Redirect::get_url( 'calypso-backups' ) ),
103                null,
104                $this->get_link_offset()
105            );
106        }
107    }
108
109    /**
110     * We create a menu offset by counting all the pages that have a jetpack_admin_page set as the capability.
111     *
112     * This makes it so that the highlight of the pages works as expected. When you click on the Setting or Dashboard.
113     *
114     * @return int Menu offset.
115     */
116    private function get_link_offset() {
117        global $submenu;
118        $offset = 9;
119
120        if ( ! array_key_exists( 'jetpack', $submenu ) ) {
121            return $offset;
122        }
123
124        foreach ( $submenu['jetpack'] as $link ) {
125            if ( 'jetpack_admin_page' !== $link[1] ) {
126                break;
127            }
128            ++$offset;
129        }
130
131        return $offset;
132    }
133
134    /**
135     * Refreshes the state cache via API call. Called via cron.
136     */
137    public function refresh_state_cache() {
138        Jetpack_Core_Json_Api_Endpoints::get_scan_state();
139        Jetpack_Core_Json_Api_Endpoints::get_rewind_data();
140    }
141
142    /**
143     * Returns true if the link should appear.
144     *
145     * @return boolean
146     */
147    private function should_show_link() {
148        // Jetpack Scan/Backup is currently not supported on multisite.
149        if ( is_multisite() ) {
150            return false;
151        }
152
153        // Check if VaultPress is active, the assumption there is that VaultPress is working.
154        // It has its link the adminbar.
155        if ( class_exists( 'VaultPress' ) ) {
156            return false;
157        }
158
159        return $this->should_show_scan() || $this->should_show_backup() || $this->should_show_scan_history_only();
160    }
161
162    /**
163     * Check if we should display the Scan menu item.
164     *
165     * 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.
166     *
167     * @return boolean
168     */
169    private function should_show_scan() {
170        return $this->has_scan() && ! $this->has_protect_plugin() && ! ( new Host() )->is_woa_site();
171    }
172
173    /**
174     * Check if we should display the Scan menu item history.
175     *
176     * It will only be displayed if site has Scan enabled, is an Atomic site.
177     *
178     * @return boolean
179     */
180    private function should_show_scan_history_only() {
181        return $this->has_scan() && ( new Host() )->is_woa_site() && get_option( 'wpcom_admin_interface' ) === 'wp-admin';
182    }
183
184    /**
185     * Check if we should display the Backup menu item.
186     *
187     * It will only be displayed if site has Backup enabled and the stand-alone Backup plugin is not active, because it will have a menu item of its own.
188     *
189     * @return boolean
190     */
191    private function should_show_backup() {
192        return $this->has_backup() && ! $this->has_backup_plugin();
193    }
194
195    /**
196     * Detects if Scan is enabled.
197     *
198     * @return boolean
199     */
200    private function has_scan() {
201        $this->maybe_refresh_transient_cache();
202        $scan_state = get_transient( 'jetpack_scan_state' );
203        if ( ! $scan_state ) {
204            return false;
205        }
206
207        return isset( $scan_state->state ) && 'unavailable' !== $scan_state->state;
208    }
209
210    /**
211     * Detects if Protect plugin is active.
212     *
213     * @return boolean
214     */
215    private function has_protect_plugin() {
216        return class_exists( 'Jetpack_Protect' );
217    }
218
219    /**
220     * Detects if Backup is enabled.
221     *
222     * @return boolean
223     */
224    private function has_backup() {
225        $this->maybe_refresh_transient_cache();
226        $rewind_state = get_transient( 'jetpack_rewind_state' );
227        if ( ! $rewind_state ) {
228            return false;
229        }
230
231        return isset( $rewind_state->state ) && 'unavailable' !== $rewind_state->state;
232    }
233
234    /**
235     * Detects if Backup plugin is active.
236     *
237     * @return boolean
238     */
239    private function has_backup_plugin() {
240        return Backup::is_standalone_plugin_active();
241    }
242
243    /**
244     * Triggers a cron job to refresh the Scan and Rewind state cache.
245     */
246    private function maybe_refresh_transient_cache() {
247        if ( $this->schedule_refresh_checked ) {
248            return;
249        }
250
251        // Do we have a jetpack_scan and jetpack_rewind state set?
252        if ( get_transient( 'jetpack_scan_state' ) && get_transient( 'jetpack_rewind_state' ) ) {
253            return;
254        }
255
256        if ( false === wp_next_scheduled( self::SCHEDULE_ACTION_HOOK ) ) {
257            wp_schedule_single_event( time(), self::SCHEDULE_ACTION_HOOK );
258        }
259
260        $this->schedule_refresh_checked = true;
261    }
262}