Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.53% covered (warning)
73.53%
50 / 68
53.85% covered (warning)
53.85%
7 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Admin_Sidebar_Link
73.53% covered (warning)
73.53%
50 / 68
53.85% covered (warning)
53.85%
7 / 13
60.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
72.73% covered (warning)
72.73%
24 / 33
0.00% covered (danger)
0.00%
0 / 1
6.73
 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
5.20
 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
 should_show_backup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 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
 has_backup
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 has_backup_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 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        $position = defined( Admin_Menu::class . '::POSITION_EXTERNAL' ) ? Admin_Menu::POSITION_EXTERNAL : 100;
72
73        if ( $this->should_show_scan() ) {
74            Admin_Menu::add_menu(
75                /** "Scan" is a product name, do not translate. */
76                'Scan',
77                'Scan <span aria-hidden="true">↗</span>',
78                'manage_options',
79                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
80                null,
81                $position,
82                array( 'key' => 'jetpack-scan-cloud' )
83            );
84        }
85
86        // Add scan item which shows history page only. This is mutally exclusive from the scan item above and is only shown for Atomic sitse.
87        if ( $this->should_show_scan_history_only() ) {
88            Admin_Menu::add_menu(
89                /** "Scan" is a product name, do not translate. */
90                'Scan',
91                'Scan <span aria-hidden="true">↗</span>',
92                'manage_options',
93                esc_url( Redirect::get_url( 'cloud-scan-history-wp-menu' ) ),
94                null,
95                $position,
96                array( 'key' => 'jetpack-scan-cloud' )
97            );
98        }
99
100        if ( $this->should_show_backup() ) {
101            Admin_Menu::add_menu(
102                /** "VaultPress Backup" is a product name, do not translate. */
103                'VaultPress Backup',
104                'VaultPress Backup <span aria-hidden="true">↗</span>',
105                'manage_options',
106                esc_url( Redirect::get_url( 'calypso-backups' ) ),
107                null,
108                $position,
109                array( 'key' => 'jetpack-backup-cloud' )
110            );
111        }
112    }
113
114    /**
115     * Refreshes the state cache via API call. Called via cron.
116     */
117    public function refresh_state_cache() {
118        Jetpack_Core_Json_Api_Endpoints::get_scan_state();
119        Jetpack_Core_Json_Api_Endpoints::get_rewind_data();
120    }
121
122    /**
123     * Returns true if the link should appear.
124     *
125     * @return boolean
126     */
127    private function should_show_link() {
128        // Jetpack Scan/Backup is currently not supported on multisite.
129        if ( is_multisite() ) {
130            return false;
131        }
132
133        // Check if VaultPress is active, the assumption there is that VaultPress is working.
134        // It has its link the adminbar.
135        if ( class_exists( 'VaultPress' ) ) {
136            return false;
137        }
138
139        return $this->should_show_scan() || $this->should_show_backup() || $this->should_show_scan_history_only();
140    }
141
142    /**
143     * Check if we should display the Scan menu item.
144     *
145     * 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.
146     *
147     * @return boolean
148     */
149    private function should_show_scan() {
150        return $this->has_scan() && ! $this->has_protect_plugin() && ! ( new Host() )->is_woa_site();
151    }
152
153    /**
154     * Check if we should display the Scan menu item history.
155     *
156     * It will only be displayed if site has Scan enabled, is an Atomic site.
157     *
158     * @return boolean
159     */
160    private function should_show_scan_history_only() {
161        return $this->has_scan() && ( new Host() )->is_woa_site() && get_option( 'wpcom_admin_interface' ) === 'wp-admin';
162    }
163
164    /**
165     * Check if we should display the Backup menu item.
166     *
167     * 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.
168     *
169     * @return boolean
170     */
171    private function should_show_backup() {
172        return $this->has_backup() && ! $this->has_backup_plugin();
173    }
174
175    /**
176     * Detects if Scan is enabled.
177     *
178     * @return boolean
179     */
180    private function has_scan() {
181        $this->maybe_refresh_transient_cache();
182        $scan_state = get_transient( 'jetpack_scan_state' );
183        if ( ! $scan_state ) {
184            return false;
185        }
186
187        return isset( $scan_state->state ) && 'unavailable' !== $scan_state->state;
188    }
189
190    /**
191     * Detects if Protect plugin is active.
192     *
193     * @return boolean
194     */
195    private function has_protect_plugin() {
196        return class_exists( 'Jetpack_Protect' );
197    }
198
199    /**
200     * Detects if Backup is enabled.
201     *
202     * @return boolean
203     */
204    private function has_backup() {
205        $this->maybe_refresh_transient_cache();
206        $rewind_state = get_transient( 'jetpack_rewind_state' );
207        if ( ! $rewind_state ) {
208            return false;
209        }
210
211        return isset( $rewind_state->state ) && 'unavailable' !== $rewind_state->state;
212    }
213
214    /**
215     * Detects if Backup plugin is active.
216     *
217     * @return boolean
218     */
219    private function has_backup_plugin() {
220        return Backup::is_standalone_plugin_active();
221    }
222
223    /**
224     * Triggers a cron job to refresh the Scan and Rewind state cache.
225     */
226    private function maybe_refresh_transient_cache() {
227        if ( $this->schedule_refresh_checked ) {
228            return;
229        }
230
231        // Do we have a jetpack_scan and jetpack_rewind state set?
232        if ( get_transient( 'jetpack_scan_state' ) && get_transient( 'jetpack_rewind_state' ) ) {
233            return;
234        }
235
236        if ( false === wp_next_scheduled( self::SCHEDULE_ACTION_HOOK ) ) {
237            wp_schedule_single_event( time(), self::SCHEDULE_ACTION_HOOK );
238        }
239
240        $this->schedule_refresh_checked = true;
241    }
242}