Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
36.28% |
78 / 215 |
|
32.14% |
9 / 28 |
CRAP | |
0.00% |
0 / 1 |
| Jetpack_Protect | |
36.62% |
78 / 213 |
|
32.14% |
9 / 28 |
1175.05 | |
0.00% |
0 / 1 |
| __construct | |
33.33% |
18 / 54 |
|
0.00% |
0 / 1 |
8.74 | |||
| filter_opt_in_modules | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| drop_leftover_opt_in_module | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
7 | |||
| record_opt_in | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
4 | |||
| forget_opt_in | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| get_opted_in_modules | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| init | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
| admin_page_init | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
3 | |||
| enqueue_admin_styles | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| enqueue_admin_scripts | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
| render_initial_state | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| initial_state | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
2 | |||
| plugin_settings_page | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| plugin_activation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| do_plugin_activation_activities | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
| activate_modules | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| plugin_deactivation | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
| should_notify_threats | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| admin_bar | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
| is_protect_page | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
| protect_filter_available_modules | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| jetpack_check_user_licenses | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
56 | |||
| get_waf_upgrade_seen_status | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| set_waf_upgrade_seen_status | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
| get_waf_upgrade_badge_timestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| set_waf_upgrade_badge_timestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_waf_upgrade_badge_display_status | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| get_waf_stats | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Primary class file for the Jetpack Protect plugin. |
| 4 | * |
| 5 | * @package automattic/jetpack-protect-plugin |
| 6 | */ |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) { |
| 9 | exit( 0 ); |
| 10 | } |
| 11 | |
| 12 | use Automattic\Jetpack\Account_Protection\Settings as Account_Protection_Settings; |
| 13 | use Automattic\Jetpack\Activity_Log\Jetpack_Activity_Log; |
| 14 | use Automattic\Jetpack\Admin_UI\Admin_Menu; |
| 15 | use Automattic\Jetpack\Assets; |
| 16 | use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; |
| 17 | use Automattic\Jetpack\Connection\Manager as Connection_Manager; |
| 18 | use Automattic\Jetpack\Connection\SSO; |
| 19 | use Automattic\Jetpack\IP\Utils as IP_Utils; |
| 20 | use Automattic\Jetpack\JITMS\JITM; |
| 21 | use Automattic\Jetpack\Modules; |
| 22 | use Automattic\Jetpack\Monitor\Monitor; |
| 23 | use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer; |
| 24 | use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products; |
| 25 | use Automattic\Jetpack\Plugins_Installer; |
| 26 | use Automattic\Jetpack\Protect\Credentials; |
| 27 | use Automattic\Jetpack\Protect\Dashboard; |
| 28 | use Automattic\Jetpack\Protect\Onboarding; |
| 29 | use Automattic\Jetpack\Protect\REST_Controller; |
| 30 | use Automattic\Jetpack\Protect\Scan_History; |
| 31 | use Automattic\Jetpack\Protect\Site_Health; |
| 32 | use Automattic\Jetpack\Protect\Threats; |
| 33 | use Automattic\Jetpack\Protect_Status\Plan; |
| 34 | use Automattic\Jetpack\Protect_Status\Protect_Status; |
| 35 | use Automattic\Jetpack\Protect_Status\Scan_Status; |
| 36 | use Automattic\Jetpack\Protect_Status\Status; |
| 37 | use Automattic\Jetpack\Status as Jetpack_Status; |
| 38 | use Automattic\Jetpack\Sync\Functions as Sync_Functions; |
| 39 | use Automattic\Jetpack\Sync\Sender; |
| 40 | use Automattic\Jetpack\Waf\Waf_Runner; |
| 41 | use Automattic\Jetpack\Waf\Waf_Stats; |
| 42 | |
| 43 | /** |
| 44 | * Class Jetpack_Protect |
| 45 | * |
| 46 | * @phan-constructor-used-for-side-effects |
| 47 | */ |
| 48 | class Jetpack_Protect { |
| 49 | |
| 50 | const JETPACK_SCAN_PRODUCT_IDS = array( |
| 51 | 2010, // JETPACK_SECURITY_DAILY. |
| 52 | 2011, // JETPACK_SECURITY_DAILY_MOTNHLY. |
| 53 | 2012, // JETPACK_SECURITY_REALTIME. |
| 54 | 2013, // JETPACK_SECURITY_REALTIME_MONTHLY. |
| 55 | 2014, // JETPACK_COMPLETE. |
| 56 | 2015, // JETPACK_COMPLETE_MONTHLY. |
| 57 | 2025, // JETPACK_PRO_YEARLY. |
| 58 | 2026, // JETPACK_PRO_BI_YEARLY. |
| 59 | 2016, // JETPACK_SECURITY_TIER_1_YEARLY. |
| 60 | 2017, // JETPACK_SECURITY_TIER_1_MONTHLY. |
| 61 | 2019, // JETPACK_SECURITY_TIER_2_YEARLY. |
| 62 | 2020, // JETPACK_SECURITY_TIER_2_MONTHLY. |
| 63 | 2106, // JETPACK_SCAN. |
| 64 | 2107, // JETPACK_SCAN_MONTHLY. |
| 65 | 2108, // JETPACK_SCAN_REALTIME. |
| 66 | 2109, // JETPACK_SCAN_REALTIME_MONTHLY. |
| 67 | ); |
| 68 | const JETPACK_WAF_MODULE_SLUG = 'waf'; |
| 69 | const JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG = 'protect'; |
| 70 | const JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG = 'account-protection'; |
| 71 | const JETPACK_PROTECT_ACTIVATION_OPTION = JETPACK_PROTECT_SLUG . '_activated'; |
| 72 | |
| 73 | /** |
| 74 | * Modules a site owner turns on from the Protect page, rather than keeping them from the Jetpack plugin. |
| 75 | * |
| 76 | * @var string[] |
| 77 | */ |
| 78 | const OPT_IN_MODULES = array( 'sso', 'monitor' ); |
| 79 | |
| 80 | /** |
| 81 | * The opt-in modules turned on without the Jetpack plugin. |
| 82 | * |
| 83 | * @var string |
| 84 | */ |
| 85 | const OPTED_IN_MODULES_OPTION = JETPACK_PROTECT_SLUG . '_opted_in_modules'; |
| 86 | |
| 87 | /** |
| 88 | * Constructor. |
| 89 | */ |
| 90 | public function __construct() { |
| 91 | add_action( 'init', array( $this, 'init' ) ); |
| 92 | |
| 93 | Monitor::init(); |
| 94 | |
| 95 | /* |
| 96 | * The shared dashboard has no site connection flow, so unconnected sites keep the plugin's setup app. |
| 97 | * It also covers an older jetpack-protect package, loaded by another plugin, that lacks the dashboard. |
| 98 | */ |
| 99 | if ( class_exists( Dashboard::class ) && ( new Connection_Manager() )->is_connected() ) { |
| 100 | Dashboard::init( array( 'product' => 'protect' ) ); |
| 101 | } else { |
| 102 | add_action( '_admin_menu', array( $this, 'admin_page_init' ) ); |
| 103 | } |
| 104 | |
| 105 | // Activate the module as the plugin is activated |
| 106 | add_action( 'admin_init', array( $this, 'do_plugin_activation_activities' ) ); |
| 107 | |
| 108 | // Init Jetpack packages |
| 109 | add_action( |
| 110 | 'plugins_loaded', |
| 111 | function () { |
| 112 | $config = new Automattic\Jetpack\Config(); |
| 113 | // Connection package. |
| 114 | $config->ensure( |
| 115 | 'connection', |
| 116 | array( |
| 117 | 'slug' => JETPACK_PROTECT_SLUG, |
| 118 | 'name' => JETPACK_PROTECT_NAME, |
| 119 | 'url_info' => JETPACK_PROTECT_URI, |
| 120 | ) |
| 121 | ); |
| 122 | // Sync package. |
| 123 | $config->ensure( |
| 124 | 'sync', |
| 125 | array( |
| 126 | 'jetpack_sync_modules' => array( |
| 127 | 'Automattic\\Jetpack\\Sync\\Modules\\Options', |
| 128 | 'Automattic\\Jetpack\\Sync\\Modules\\Callables', |
| 129 | 'Automattic\\Jetpack\\Sync\\Modules\\Users', |
| 130 | ), |
| 131 | 'jetpack_sync_callable_whitelist' => array( |
| 132 | 'main_network_site' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'main_network_site_url' ), |
| 133 | 'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ), |
| 134 | 'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ), |
| 135 | 'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ), |
| 136 | // Tells WordPress.com which modules, such as Monitor, are on. |
| 137 | 'active_modules' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_active_modules' ), |
| 138 | ), |
| 139 | 'jetpack_sync_options_contentless' => array(), |
| 140 | 'jetpack_sync_options_whitelist' => array( |
| 141 | 'active_plugins', |
| 142 | 'stylesheet', |
| 143 | ), |
| 144 | ) |
| 145 | ); |
| 146 | |
| 147 | // Identity crisis package. |
| 148 | $config->ensure( 'identity_crisis' ); |
| 149 | |
| 150 | // Web application firewall package. |
| 151 | $config->ensure( 'waf' ); |
| 152 | |
| 153 | // Account protection package. |
| 154 | $config->ensure( 'account_protection' ); |
| 155 | |
| 156 | // Fallback: an older connection package loaded by another plugin may predate init_standalone(). |
| 157 | if ( method_exists( SSO::class, 'init_standalone' ) ) { |
| 158 | SSO::init_standalone(); |
| 159 | } |
| 160 | }, |
| 161 | 1 |
| 162 | ); |
| 163 | |
| 164 | add_filter( 'jetpack_connection_user_has_license', array( $this, 'jetpack_check_user_licenses' ), 10, 3 ); |
| 165 | |
| 166 | add_filter( 'jetpack_get_available_standalone_modules', array( $this, 'protect_filter_available_modules' ), 10, 1 ); |
| 167 | |
| 168 | add_filter( 'jetpack_active_modules', array( __CLASS__, 'filter_opt_in_modules' ) ); |
| 169 | add_action( 'jetpack_pre_activate_module', array( __CLASS__, 'drop_leftover_opt_in_module' ) ); |
| 170 | add_action( 'jetpack_activate_module', array( __CLASS__, 'record_opt_in' ) ); |
| 171 | add_action( 'jetpack_deactivate_module', array( __CLASS__, 'forget_opt_in' ) ); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Without the Jetpack plugin, count an opt-in module as on only if it was turned on without the Jetpack plugin. |
| 176 | * |
| 177 | * The saved list is left alone, so the Jetpack plugin gets its own state back if it returns. |
| 178 | * |
| 179 | * @param string[] $active Active module slugs. |
| 180 | * @return string[] |
| 181 | */ |
| 182 | public static function filter_opt_in_modules( $active ) { |
| 183 | if ( class_exists( 'Jetpack', false ) ) { |
| 184 | return $active; |
| 185 | } |
| 186 | $stale = array_diff( self::OPT_IN_MODULES, self::get_opted_in_modules() ); |
| 187 | return array_values( array_diff( (array) $active, $stale ) ); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Without the Jetpack plugin, drop a leftover saved entry before an opt-in module is turned on, so activation runs its hooks. |
| 192 | * |
| 193 | * @param string $module Module slug. |
| 194 | * @return void |
| 195 | */ |
| 196 | public static function drop_leftover_opt_in_module( $module ) { |
| 197 | if ( class_exists( 'Jetpack', false ) || ! in_array( $module, self::OPT_IN_MODULES, true ) || in_array( $module, self::get_opted_in_modules(), true ) || ! ( new Modules() )->is_module( $module ) ) { |
| 198 | return; |
| 199 | } |
| 200 | $saved = Jetpack_Options::get_option( 'active_modules', array() ); |
| 201 | if ( is_array( $saved ) && in_array( $module, $saved, true ) ) { |
| 202 | Jetpack_Options::update_option( 'active_modules', array_values( array_diff( $saved, array( $module ) ) ) ); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * Remember that an opt-in module was turned on without the Jetpack plugin. |
| 208 | * |
| 209 | * @param string $module Module slug. |
| 210 | * @return void |
| 211 | */ |
| 212 | public static function record_opt_in( $module ) { |
| 213 | if ( class_exists( 'Jetpack', false ) || ! in_array( $module, self::OPT_IN_MODULES, true ) || in_array( $module, self::get_opted_in_modules(), true ) ) { |
| 214 | return; |
| 215 | } |
| 216 | update_option( self::OPTED_IN_MODULES_OPTION, array_merge( self::get_opted_in_modules(), array( $module ) ) ); |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Forget an opt-in module once it is turned off; before that, the filter would hide it from the deactivation. |
| 221 | * |
| 222 | * @param string $module Module slug. |
| 223 | * @return void |
| 224 | */ |
| 225 | public static function forget_opt_in( $module ) { |
| 226 | $opted_in = self::get_opted_in_modules(); |
| 227 | if ( in_array( $module, $opted_in, true ) ) { |
| 228 | update_option( self::OPTED_IN_MODULES_OPTION, array_values( array_diff( $opted_in, array( $module ) ) ) ); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * The opt-in modules turned on without the Jetpack plugin. |
| 234 | * |
| 235 | * @return string[] |
| 236 | */ |
| 237 | private static function get_opted_in_modules() { |
| 238 | $opted_in = get_option( self::OPTED_IN_MODULES_OPTION, array() ); |
| 239 | return is_array( $opted_in ) ? $opted_in : array(); |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Initialize the plugin |
| 244 | * |
| 245 | * @return void |
| 246 | */ |
| 247 | public function init() { |
| 248 | add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 65 ); |
| 249 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) ); |
| 250 | |
| 251 | REST_Controller::init(); |
| 252 | My_Jetpack_Initializer::init(); |
| 253 | // Activity Log. No-ops while the `activity-log` module is off. |
| 254 | Jetpack_Activity_Log::initialize(); |
| 255 | Site_Health::init(); |
| 256 | |
| 257 | // Sets up JITMS. |
| 258 | JITM::configure(); |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Initialize the admin page resources. |
| 263 | */ |
| 264 | public function admin_page_init() { |
| 265 | // Only report the threat count to users who can actually reach the Protect |
| 266 | // menu (added below with the 'manage_options' cap). Otherwise the central |
| 267 | // menu-badges total would include threats the current user can't see. |
| 268 | if ( current_user_can( 'manage_options' ) && self::should_notify_threats() ) { |
| 269 | \Automattic\Jetpack\Menu_Badges\Menu_Badges::init(); // idempotent; wires the renderer. |
| 270 | \Automattic\Jetpack\Menu_Badges\Notification_Counts::register( |
| 271 | 'jetpack-protect', |
| 272 | array( |
| 273 | 'menu_slug' => 'jetpack-protect', |
| 274 | 'count' => Status::get_total_threats(), |
| 275 | 'type' => 'count', |
| 276 | ) |
| 277 | ); |
| 278 | } |
| 279 | |
| 280 | $page_suffix = Admin_Menu::add_menu( |
| 281 | 'Jetpack Protect', // "Jetpack Protect" is a product name, do not translate. |
| 282 | 'Protect', // "Protect" is a product name, do not translate. |
| 283 | 'manage_options', |
| 284 | 'jetpack-protect', |
| 285 | array( $this, 'plugin_settings_page' ), |
| 286 | null, |
| 287 | array( |
| 288 | 'product' => 'protect', |
| 289 | 'key' => 'jetpack-protect', |
| 290 | ) |
| 291 | ); |
| 292 | |
| 293 | add_action( 'load-' . $page_suffix, array( $this, 'enqueue_admin_scripts' ) ); |
| 294 | } |
| 295 | |
| 296 | /** |
| 297 | * Enqueues the wp-admin styles (used outside the React app) |
| 298 | */ |
| 299 | public function enqueue_admin_styles() { |
| 300 | wp_enqueue_style( 'jetpack-protect-wpadmin', JETPACK_PROTECT_BASE_PLUGIN_URL . '/assets/jetpack-protect.css', array(), JETPACK_PROTECT_VERSION ); |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * Enqueue plugin admin scripts and styles. |
| 305 | */ |
| 306 | public function enqueue_admin_scripts() { |
| 307 | |
| 308 | Assets::register_script( |
| 309 | 'jetpack-protect', |
| 310 | 'build/index.js', |
| 311 | JETPACK_PROTECT_ROOT_FILE, |
| 312 | array( |
| 313 | 'in_footer' => true, |
| 314 | 'textdomain' => 'jetpack-protect', |
| 315 | ) |
| 316 | ); |
| 317 | Assets::enqueue_script( 'jetpack-protect' ); |
| 318 | // Required for Analytics. |
| 319 | wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true ); |
| 320 | // Initial JS state including JP Connection data. |
| 321 | Connection_Initial_State::render_script( 'jetpack-protect' ); |
| 322 | wp_add_inline_script( 'jetpack-protect', $this->render_initial_state(), 'before' ); |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * Render the initial state into a JavaScript variable. |
| 327 | * |
| 328 | * @return string |
| 329 | */ |
| 330 | public function render_initial_state() { |
| 331 | return 'var jetpackProtectInitialState=' . wp_json_encode( $this->initial_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';'; |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * Get the initial state data for hydrating the React UI. |
| 336 | * |
| 337 | * @return array |
| 338 | */ |
| 339 | public function initial_state() { |
| 340 | global $wp_version; |
| 341 | |
| 342 | // Always fetch the latest plan status from WPCOM. |
| 343 | $has_plan = Plan::has_required_plan( true ); |
| 344 | |
| 345 | $status = Status::get_status(); |
| 346 | |
| 347 | $initial_state = array( |
| 348 | 'apiRoot' => esc_url_raw( rest_url() ), |
| 349 | 'apiNonce' => wp_create_nonce( 'wp_rest' ), |
| 350 | 'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ), |
| 351 | 'credentials' => Credentials::get_credential_array(), |
| 352 | 'status' => $status, |
| 353 | 'fixerStatus' => Threats::fix_threats_status( $status->fixable_threat_ids ), |
| 354 | 'scanHistory' => Scan_History::get_scan_history(), |
| 355 | 'installedPlugins' => Plugins_Installer::get_plugins(), |
| 356 | 'installedThemes' => Sync_Functions::get_themes(), |
| 357 | 'wpVersion' => $wp_version, |
| 358 | 'adminUrl' => 'admin.php?page=jetpack-protect', |
| 359 | 'siteSuffix' => ( new Jetpack_Status() )->get_site_suffix(), |
| 360 | 'blogID' => Connection_Manager::get_site_id( true ), |
| 361 | 'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ), |
| 362 | 'hasPlan' => $has_plan, |
| 363 | 'onboardingProgress' => Onboarding::get_current_user_progress(), |
| 364 | 'accountProtection' => ( new Account_Protection_Settings() )->get(), |
| 365 | 'waf' => array( |
| 366 | 'wafSupported' => Waf_Runner::is_supported_environment(), |
| 367 | 'currentIp' => IP_Utils::get_ip(), |
| 368 | 'upgradeIsSeen' => self::get_waf_upgrade_seen_status(), |
| 369 | 'displayUpgradeBadge' => self::get_waf_upgrade_badge_display_status(), |
| 370 | 'isEnabled' => Waf_Runner::is_enabled(), |
| 371 | 'config' => Waf_Runner::get_config(), |
| 372 | 'stats' => self::get_waf_stats(), |
| 373 | 'globalStats' => Waf_Stats::get_global_stats(), |
| 374 | ), |
| 375 | ); |
| 376 | |
| 377 | $initial_state['jetpackScan']['pricingForUi'] = Plan::get_product( 'jetpack_scan' ); |
| 378 | |
| 379 | return $initial_state; |
| 380 | } |
| 381 | /** |
| 382 | * Main plugin settings page. |
| 383 | */ |
| 384 | public function plugin_settings_page() { |
| 385 | ?> |
| 386 | <div id="jetpack-protect-root"></div> |
| 387 | <?php |
| 388 | } |
| 389 | |
| 390 | /** |
| 391 | * Activate the WAF module on plugin activation. |
| 392 | * |
| 393 | * @static |
| 394 | */ |
| 395 | public static function plugin_activation() { |
| 396 | add_option( self::JETPACK_PROTECT_ACTIVATION_OPTION, true ); |
| 397 | } |
| 398 | |
| 399 | /** |
| 400 | * Runs on admin_init, and does actions required on plugin activation, based on |
| 401 | * the activation option. |
| 402 | * |
| 403 | * This needs to be run after the activation hook, as that results in a redirect, |
| 404 | * and we need the sync module's actions and filters to be registered. |
| 405 | */ |
| 406 | public static function do_plugin_activation_activities() { |
| 407 | if ( get_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ) && ( new Connection_Manager() )->is_connected() ) { |
| 408 | self::activate_modules(); |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | /** |
| 413 | * Activates the waf and brute force protection modules and disables the activation option |
| 414 | */ |
| 415 | public static function activate_modules() { |
| 416 | delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ); |
| 417 | ( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false ); |
| 418 | ( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false ); |
| 419 | } |
| 420 | |
| 421 | /** |
| 422 | * Removes plugin from the connection manager |
| 423 | * If it's the last plugin using the connection, the site will be disconnected. |
| 424 | * |
| 425 | * @access public |
| 426 | * @static |
| 427 | */ |
| 428 | public static function plugin_deactivation() { |
| 429 | |
| 430 | // Clear Sync data. |
| 431 | Sender::get_instance()->uninstall(); |
| 432 | |
| 433 | $manager = new Connection_Manager( 'jetpack-protect' ); |
| 434 | $manager->remove_connection(); |
| 435 | |
| 436 | Protect_Status::delete_option(); |
| 437 | Scan_Status::delete_option(); |
| 438 | Scan_History::delete_option(); |
| 439 | } |
| 440 | |
| 441 | /** |
| 442 | * Whether the host lets threats be announced in the menu and the admin bar. |
| 443 | * |
| 444 | * @return bool |
| 445 | */ |
| 446 | private static function should_notify_threats() { |
| 447 | return ! method_exists( Status::class, 'should_notify_threats' ) || Status::should_notify_threats(); |
| 448 | } |
| 449 | |
| 450 | /** |
| 451 | * Create a shortcut on Admin Bar to show the total of threats found. |
| 452 | * |
| 453 | * @param object $wp_admin_bar The Admin Bar object. |
| 454 | * @return void |
| 455 | */ |
| 456 | public function admin_bar( $wp_admin_bar ) { |
| 457 | if ( ! current_user_can( 'manage_options' ) || ! self::should_notify_threats() ) { |
| 458 | return; |
| 459 | } |
| 460 | |
| 461 | $total = Status::get_total_threats(); |
| 462 | |
| 463 | // On the Protect page the app updates the count, so a hidden node is kept for a later scan to reveal. |
| 464 | if ( $total > 0 || self::is_protect_page() ) { |
| 465 | $args = array( |
| 466 | 'id' => 'jetpack-protect', |
| 467 | 'title' => '<span class="ab-icon jp-protect-icon"></span><span class="ab-label">' . $total . '</span>', |
| 468 | 'href' => admin_url( 'admin.php?page=jetpack-protect' ), |
| 469 | 'meta' => array( |
| 470 | 'class' => $total > 0 ? '' : 'hidden', |
| 471 | // translators: %d is the number of threats found. |
| 472 | 'title' => sprintf( _n( '%d threat found by Jetpack Protect', '%d threats found by Jetpack Protect', $total, 'jetpack-protect' ), $total ), |
| 473 | ), |
| 474 | ); |
| 475 | |
| 476 | $wp_admin_bar->add_node( $args ); |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /** |
| 481 | * Whether the request is for the Protect page in wp-admin. |
| 482 | * |
| 483 | * @return bool |
| 484 | */ |
| 485 | private static function is_protect_page() { |
| 486 | if ( ! is_admin() ) { |
| 487 | return false; |
| 488 | } |
| 489 | if ( method_exists( Status::class, 'is_protect_page' ) ) { |
| 490 | return Status::is_protect_page(); |
| 491 | } |
| 492 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only compared against the page slug. |
| 493 | return isset( $_GET['page'] ) && 'jetpack-protect' === $_GET['page']; |
| 494 | } |
| 495 | |
| 496 | /** |
| 497 | * Adds modules to the list of available modules |
| 498 | * |
| 499 | * @param array $modules The available modules. |
| 500 | * @return array |
| 501 | */ |
| 502 | public function protect_filter_available_modules( $modules ) { |
| 503 | return array_merge( array( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, self::JETPACK_WAF_MODULE_SLUG, self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG ), $modules ); |
| 504 | } |
| 505 | |
| 506 | /** |
| 507 | * Check if the user has an available license that includes Jetpack Scan. |
| 508 | * |
| 509 | * @param boolean $has_license Whether a license was already found. |
| 510 | * @param object[] $licenses Unattached licenses belonging to the user. |
| 511 | * @param string $plugin_slug Slug of the plugin that initiated the flow. |
| 512 | * |
| 513 | * @return boolean |
| 514 | */ |
| 515 | public static function jetpack_check_user_licenses( $has_license, $licenses, $plugin_slug ) { |
| 516 | if ( $plugin_slug !== JETPACK_PROTECT_SLUG || $has_license ) { |
| 517 | return $has_license; |
| 518 | } |
| 519 | |
| 520 | $license_found = false; |
| 521 | |
| 522 | foreach ( $licenses as $license ) { |
| 523 | if ( $license->attached_at || $license->revoked_at ) { |
| 524 | continue; |
| 525 | } |
| 526 | |
| 527 | if ( in_array( $license->product_id, self::JETPACK_SCAN_PRODUCT_IDS, true ) ) { |
| 528 | $license_found = true; |
| 529 | break; |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | return $license_found; |
| 534 | } |
| 535 | |
| 536 | /** |
| 537 | * Get WAF Upgrade "Seen" Status |
| 538 | * |
| 539 | * @return bool Whether the current user has dismissed the upgrade popover or enabled the automatic rules feature. |
| 540 | */ |
| 541 | public static function get_waf_upgrade_seen_status() { |
| 542 | return (bool) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true ); |
| 543 | } |
| 544 | |
| 545 | /** |
| 546 | * Set WAF Upgrade "Seen" Status |
| 547 | * |
| 548 | * @return bool True if upgrade seen status updated to true, false on failure. |
| 549 | */ |
| 550 | public static function set_waf_upgrade_seen_status() { |
| 551 | self::set_waf_upgrade_badge_timestamp(); |
| 552 | return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true ); |
| 553 | } |
| 554 | |
| 555 | /** |
| 556 | * Get WAF Upgrade Badge Timestamp |
| 557 | * |
| 558 | * @return integer The timestamp for the when the upgrade seen status was first set to true. |
| 559 | */ |
| 560 | public static function get_waf_upgrade_badge_timestamp() { |
| 561 | return (int) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', true ); |
| 562 | } |
| 563 | |
| 564 | /** |
| 565 | * Set WAF Upgrade Badge Timestamp |
| 566 | * |
| 567 | * @return bool True if upgrade badge timestamp to set to the current time, false on failure. |
| 568 | */ |
| 569 | public static function set_waf_upgrade_badge_timestamp() { |
| 570 | return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', time() ); |
| 571 | } |
| 572 | |
| 573 | /** |
| 574 | * Get WAF Upgrade Badge Display Status |
| 575 | * |
| 576 | * @return bool True if upgrade badge timestamp is set and less than 7 days ago, otherwise false. |
| 577 | */ |
| 578 | public static function get_waf_upgrade_badge_display_status() { |
| 579 | $badge_timestamp_exists = metadata_exists( 'user', get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp' ); |
| 580 | if ( ! $badge_timestamp_exists ) { |
| 581 | return true; |
| 582 | } |
| 583 | |
| 584 | $badge_timestamp = self::get_waf_upgrade_badge_timestamp(); |
| 585 | $seven_days = strtotime( '-7 days' ); |
| 586 | if ( $badge_timestamp > $seven_days ) { |
| 587 | return true; |
| 588 | } |
| 589 | |
| 590 | return false; |
| 591 | } |
| 592 | |
| 593 | /** |
| 594 | * Get WAF stats |
| 595 | * |
| 596 | * @return bool|array False if WAF is not enabled, otherwise an array of stats. |
| 597 | */ |
| 598 | public static function get_waf_stats() { |
| 599 | if ( ! Waf_Runner::is_enabled() ) { |
| 600 | return false; |
| 601 | } |
| 602 | |
| 603 | return array( |
| 604 | 'blockedRequests' => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false, |
| 605 | 'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(), |
| 606 | ); |
| 607 | } |
| 608 | } |