PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.1.0
Defender Security – Malware Scanner, Login Security & Firewall v6.1.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / traits / defender-hub-client.php

defender-hub-client.php in Defender Security – Malware Scanner, Login Security & Firewall 6.1.0, at src/traits/defender-hub-client.php

772 lines 24.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle HUB based functionalities of WPMUDEV class.
4 *
5 * @package WP_Defender\Traits
6 */
7
8 namespace WP_Defender\Traits;
9
10 use WP_Error;
11 use Exception;
12 use WP_User_Query;
13 use WPMUDEV_Dashboard;
14 use WP_Defender\Model\Scan;
15 use WPMUDEV\Hub\Connector\Data;
16 use WP_Defender\Model\Notification;
17 use WP_Defender\Controller\Firewall;
18 use WP_Defender\Model\Setting\Two_Fa;
19 use WP_Defender\Component\IP\Antibot_Global_Firewall;
20 use WP_Defender\Model\Setting\Captcha;
21 use WP_Defender\Model\Setting\Mask_Login;
22 use WP_Defender\Controller\Security_Tweaks;
23 use WP_Defender\Controller\Security_Headers;
24 use WP_Defender\Model\Setting\Audit_Logging;
25 use WP_Defender\Model\Setting\Login_Lockout;
26 use WP_Defender\Model\Setting\Notfound_Lockout;
27 use WP_Defender\Model\Notification\Audit_Report;
28 use WP_Defender\Model\Setting\Global_Ip_Lockout;
29 use WP_Defender\Model\Setting\User_Agent_Lockout;
30 use WP_Defender\Model\Notification\Malware_Report;
31 use WP_Defender\Model\Setting\Password_Protection;
32 use WP_Defender\Model\Notification\Firewall_Report;
33 use WP_Defender\Model\Setting\Scan as Scan_Settings;
34 use WP_Defender\Model\Notification\Firewall_Notification;
35 use WP_Defender\Model\Setting\Security_Tweaks as Model_Security_Tweaks;
36
37 trait Defender_Hub_Client {
38
39 use IP;
40
41 /**
42 * Get API base URL.
43 *
44 * @return string
45 * @since 3.4.0
46 */
47 public function get_api_base_url(): string {
48 return defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
49 ? trailingslashit( WPMUDEV_CUSTOM_API_SERVER )
50 : 'https://wpmudev.com/';
51 }
52
53 /**
54 * Retrieves the endpoint URL based on the given scenario.
55 *
56 * @param string $scenario The scenario identifier.
57 *
58 * @return string The endpoint URL.
59 */
60 public function get_endpoint( $scenario ): string {
61 $base = $this->get_api_base_url();
62 switch ( $scenario ) {
63 case self::API_SCAN_KNOWN_VULN:
64 return $base . 'api/defender/v1/vulnerabilities';
65 case self::API_SCAN_SIGNATURE:
66 return $base . 'api/defender/v1/yara-signatures';
67 case self::API_BLACKLIST:
68 return $base . 'api/defender/v1/blacklist-monitoring?domain=' . network_site_url();
69 case self::API_GLOBAL_IP_LIST:
70 return $base . 'api/hub/v1/global-ip-list';
71 case self::API_GLOBAL_IP_LIST_HOSTING:
72 $site_id = $this->get_site_id();
73
74 return $base . "api/hub/v1/sites/$site_id/modules/hosting/global-ip-list";
75 case self::API_PACKAGE_CONFIGS:
76 return $base . 'api/hub/v1/package-configs';
77 case self::API_IP_BLOCKLIST_SUBMIT_LOGS:
78 return $base . 'api/blocklist/v1/logs';
79 case self::API_ANTIBOT_GLOBAL_FIREWALL:
80 $site_id = $this->get_site_id();
81
82 return $base . "api/hub/v1/sites/$site_id/modules/hosting/antibot";
83 case self::API_HUB_SYNC:
84 default:
85 return $base . 'api/defender/v1/scan-results';
86 }
87 }
88
89 /**
90 * Get WPMUDEV site id.
91 *
92 * @return int|bool
93 */
94 public function get_site_id() {
95 if ( 'dashboard' === $this->resolve_connection_mode() ) {
96 return (int) WPMUDEV_Dashboard::$api->get_site_id();
97 }
98
99 // Ignore stale HCM site IDs after disconnect.
100 if ( self::get_hcm_status() && class_exists( 'WPMUDEV\Hub\Connector\Data' ) ) {
101 $site_id = Data::get()->hub_site_id();
102 if ( $site_id > 0 ) {
103 return $site_id;
104 }
105 }
106
107 return false;
108 }
109
110 /**
111 * HUB API remote request method.
112 *
113 * @param string $scenario The scenario for the API request.
114 * @param array $body The body of the API request. Default is an empty array.
115 * @param array $args The arguments for the API request. Default is an empty array.
116 * @param bool $recheck Whether to recheck the API request. Default is false.
117 *
118 * @return array|WP_Error The response body of the API request or a WP_Error object.
119 * @throws Exception If the Dash plugin authentication API key is missing.
120 */
121 private function hub_api_request(
122 string $scenario,
123 array $body = array(),
124 array $args = array(),
125 bool $recheck = false
126 ) {
127 $api_key = $this->get_api_key();
128 $body['domain'] ??= network_site_url();
129
130 $headers = array(
131 'Authorization' => 'Basic ' . $api_key,
132 'apikey' => $api_key,
133 );
134
135 $timeout = isset( $args['timeout'] ) ? $args['timeout'] : 30;
136
137 $args = array_merge(
138 $args,
139 array(
140 'body' => $body,
141 'headers' => $headers,
142 'timeout' => $timeout,
143 'sslverify' => apply_filters( 'https_ssl_verify', true ),
144 )
145 );
146
147 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
148
149 if ( is_wp_error( $request ) ) {
150 if ( ! $recheck ) {
151 return $request;
152 }
153 // Sometimes a response comes with a curl error #52 so should delete Authorization header.
154 $args['headers'] = array( 'apikey' => $api_key );
155 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
156 if ( is_wp_error( $request ) ) {
157 return $request;
158 }
159 }
160
161 $response_code = wp_remote_retrieve_response_code( $request );
162 $result = wp_remote_retrieve_body( $request );
163 $result = json_decode( $result, true );
164
165 if ( $response_code < 200 || $response_code >= 300 ) {
166 return new WP_Error(
167 $response_code,
168 isset( $result['message'] )
169 ? $result['message']
170 : wp_remote_retrieve_response_message( $request )
171 );
172 }
173
174 return $result;
175 }
176
177 /**
178 * Makes a request to the WPMU API.
179 *
180 * @param string $scenario The scenario for the request.
181 * @param array $body The body of the request. Default is an empty array.
182 * @param array $args Additional arguments for the request. Default is an empty array.
183 * @param bool $recheck Whether to recheck the request. Default is false.
184 *
185 * @return array|WP_Error The response from the API request.
186 */
187 public function make_wpmu_request(
188 string $scenario,
189 array $body = array(),
190 array $args = array(),
191 bool $recheck = false
192 ) {
193 $api_key = $this->get_api_key();
194
195 if ( '' === $api_key ) {
196 $link_text = sprintf(
197 '<a target="_blank" href="%s">%s</a>',
198 'https://wpmudev.com/project/wpmu-dev-dashboard/',
199 esc_html__( 'here', 'defender-security' )
200 );
201
202 return new WP_Error(
203 'dashboard_required',
204 sprintf(
205 /* translators: %s - wpmudev link */
206 esc_html__(
207 'WPMU DEV Dashboard will be required for this action. Please visit %s and install the WPMU DEV Dashboard.',
208 'defender-security'
209 ),
210 $link_text
211 )
212 );
213 }
214
215 return $this->hub_api_request( $scenario, $body, $args, $recheck );
216 }
217
218 /**
219 * Makes a request to the WPMU API for free and higher account holding users.
220 *
221 * @param string $scenario The scenario for the request.
222 * @param array $body The body of the request. Default is an empty array.
223 * @param array $args Additional arguments for the request. Default is an empty array.
224 * @param bool $recheck Whether to recheck the request. Default is false.
225 *
226 * @return array|WP_Error The response from the API request.
227 * @throws Exception If the Dash plugin authentication API key is missing.
228 * @throws Exception If permission is denied for the API call.
229 */
230 public function make_wpmu_free_request(
231 string $scenario,
232 array $body = array(),
233 array $args = array(),
234 bool $recheck = false
235 ) {
236 if ( ! $this->hub_connector_connected() ) {
237 throw new Exception( esc_html__( 'API key missing.', 'defender-security' ) );
238 }
239 if ( $this->can_wpmu_free_request() === false ) {
240 throw new Exception( esc_html__( 'Permission denied API call.', 'defender-security' ) );
241 }
242 return $this->hub_api_request( $scenario, $body, $args, $recheck );
243 }
244
245 /**
246 * Check if the current request can be made as a free API request.
247 *
248 * @return bool True if the request is allowed, false otherwise.
249 * @throws Exception If the WPMU DEV Dashboard plugin is missing.
250 */
251 public function can_wpmu_free_request() {
252 return in_array( $this->get_membership_type(), array( 'free', 'full', 'unit' ), true );
253 }
254
255 /**
256 * This will build data relate to scan module, so we can push to hub.
257 *
258 * @return array
259 * @since 2.4.7 add 'plugin_integrity' args
260 */
261 protected function build_scan_hub_data(): array {
262 $scan = Scan::get_last();
263 $scan_result = array(
264 'core_integrity' => 0,
265 'plugin_integrity' => 0,
266 'vulnerability_db' => 0,
267 'file_suspicious' => 0,
268 'outdated_plugin' => 0,
269 'closed_plugin' => 0,
270 'last_completed' => false,
271 'scan_items' => array(),
272 'num_issues' => 0,
273 'num_ignored_issues' => 0,
274 );
275 $total_issues = 0;
276 if ( is_object( $scan ) ) {
277 $data = $scan->prepare_issues( 10, 1 );
278
279 $scan_result['core_integrity'] = $data['count_core'];
280 $scan_result['plugin_integrity'] = $data['count_plugin'];
281 $scan_result['vulnerability_db'] = $data['count_vuln'];
282 $scan_result['file_suspicious'] = $data['count_malware'];
283 $scan_result['outdated_plugin'] = $data['count_outdated_plugin'];
284 $scan_result['closed_plugin'] = $data['count_closed_plugin'];
285 $scan_result['last_completed'] = $scan->date_end;
286 $scan_result['num_ignored_issues'] = $data['count_ignored'];
287
288 if ( isset( $data['issues'] ) && is_array( $data['issues'] ) && array() !== $data['issues'] ) {
289 $total_issues = $data['count_issues'];
290 foreach ( $data['issues'] as $issue ) {
291 $scan_result['scan_items'][] = array(
292 'file' => $issue['full_path'] ?? $issue['file_name'],
293 'detail' => $issue['short_desc'],
294 );
295 }
296 }
297 $scan_result['num_issues'] = $total_issues + $data['count_ignored'];
298 }
299
300 $settings = new Scan_Settings();
301
302 return array(
303 'timestamp' => is_object( $scan ) ? strtotime( $scan->date_start ) : '',
304 'warning' => $total_issues,
305 'scan_result' => $scan_result,
306 'scan_schedule' => array(
307 // @since 2.7.0 change scheduled scan logic.
308 'is_activated' => $settings->is_enabled_scheduled_scanning(),
309 // Example of frequency, day, time in build_notification_hub_data() method.
310 'time' => $settings->time,
311 'day' => $this->get_notification_day( $settings ),
312 'frequency' => $this->backward_frequency_compatibility( $settings->frequency ),
313 ),
314 );
315 }
316
317 /**
318 * Converts a string frequency to an integer value for backward compatibility.
319 *
320 * @param string $frequency The frequency to convert. Must be one of 'daily', 'weekly', or 'monthly'.
321 *
322 * @return int The converted integer value. Returns 1 for 'daily', 7 for 'weekly', and 30 for 'monthly' (default).
323 */
324 public function backward_frequency_compatibility( string $frequency ): int {
325 switch ( $frequency ) {
326 case 'daily':
327 return 1;
328 case 'weekly':
329 return 7;
330 case 'monthly':
331 default:
332 return 30;
333 }
334 }
335
336 /**
337 * Build data for security tweaks.
338 *
339 * @return array
340 */
341 protected function build_security_tweaks_hub_data(): array {
342 // Exclude direct call of data_frontend().
343 $tweak_arr = wd_di()->get( Model_Security_Tweaks::class )->get_tweak_types();
344
345 $data = array(
346 'cautions' => $tweak_arr['count_issues'],
347 'issues' => array(),
348 'ignore' => array(),
349 'fixed' => array(),
350 );
351 $types = array(
352 Security_Tweaks::STATUS_ISSUES,
353 Security_Tweaks::STATUS_IGNORE,
354 Security_Tweaks::STATUS_RESOLVE,
355 );
356 $view = '';
357 foreach ( $types as $type ) {
358 if ( 'ignore' === $type ) {
359 $view = '&view=ignored';
360 } elseif ( 'fixed' === $type ) {
361 $view = '&view=resolved';
362 }
363 foreach ( wd_di()->get( Security_Tweaks::class )->init_tweaks( $type ) as $slug => $tweak ) {
364 $data[ $type ][] = array(
365 'label' => $tweak->get_label(),
366 'url' => network_admin_url( 'admin.php?page=wdf-hardener' . $view . '#' . $slug ),
367 );
368 }
369 }
370
371 return $data;
372 }
373
374 /**
375 * Builds an array of audit data to be sent to the hub.
376 * Audit log is Pro-only; returns empty stub by default.
377 *
378 * @return array
379 */
380 public function build_audit_hub_data(): array {
381 return array(
382 'month' => 0,
383 'last_event' => 'n/a',
384 'enabled' => false,
385 );
386 }
387
388 /**
389 * Builds an array of lockout data to be sent to the hub.
390 *
391 * @return array
392 */
393 public function build_lockout_hub_data(): array {
394 $firewall = wd_di()->get( Firewall::class )->data_frontend();
395 $antibot = wd_di()->get( Antibot_Global_Firewall::class );
396
397 return array(
398 'last_lockout' => $firewall['last_lockout'],
399 'lp' => wd_di()->get( Login_Lockout::class )->enabled,
400 'lp_week' => $firewall['login']['week'],
401 'nf' => wd_di()->get( Notfound_Lockout::class )->enabled,
402 'nf_week' => $firewall['nf']['week'],
403 'ua' => wd_di()->get( User_Agent_Lockout::class )->enabled,
404 'ua_week' => $firewall['ua']['week'],
405 'global_ip_list_enabled' => wd_di()->get( Global_Ip_Lockout::class )->enabled,
406 'antibot_enabled' => $antibot->frontend_is_enabled(),
407 'antibot_mode' => $antibot->frontend_mode(),
408 );
409 }
410
411 /**
412 * Builds an array of 2fa data to be sent to the hub.
413 *
414 * @return array
415 */
416 public function build_2fa_hub_data(): array {
417 $settings = new Two_Fa();
418 $service = wd_di()->get( \WP_Defender\Component\Two_Fa::class );
419 $query = new WP_User_Query(
420 array(
421 // Look over the network.
422 'blog_id' => 0,
423 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
424 array(
425 'key' => $service::DEFAULT_PROVIDER_USER_KEY,
426 'value' => array_keys( $service->get_providers() ),
427 'compare' => 'IN',
428 ),
429 ),
430 )
431 );
432 $active_users = array();
433 if ( $query->get_total() > 0 ) {
434 foreach ( $query->get_results() as $obj_user ) {
435 $active_users[] = array(
436 'display_name' => $obj_user->data->display_name,
437 );
438 }
439 }
440
441 return array(
442 'active' => $settings->enabled && array() !== $settings->user_roles,
443 'enabled' => $settings->enabled,
444 'active_users' => $active_users,
445 );
446 }
447
448 /**
449 * Builds an array of mask login data to be sent to the hub.
450 *
451 * @return array
452 */
453 public function build_mask_login_hub_data(): array {
454 $settings = new Mask_Login();
455
456 return array(
457 'active' => $settings->is_active(),
458 'masked_url' => $settings->mask_url,
459 );
460 }
461
462 /**
463 * Builds an array of Captcha data to be sent to the hub.
464 *
465 * @return array
466 */
467 public function build_captcha_hub_data(): array {
468 $settings = new Captcha();
469
470 return array(
471 'active' => $settings->is_active(),
472 );
473 }
474
475 /**
476 * Builds an array of password protection data to be sent to the hub.
477 *
478 * @return array
479 */
480 public function build_password_protection_hub_data(): array {
481 $settings = new Password_Protection();
482
483 return array(
484 'active' => $settings->is_active(),
485 );
486 }
487
488 /**
489 * Get the notification day.
490 *
491 * @param object $module_report The report object.
492 *
493 * @return string
494 */
495 private function get_notification_day( $module_report ): string {
496 if ( ! ( isset( $module_report->frequency ) && isset( $module_report->day ) && isset( $module_report->day_n ) ) ) {
497 return '';
498 }
499
500 if ( 'daily' === $module_report->frequency ) {
501 $day = '1';
502 } elseif ( 'weekly' === $module_report->frequency ) {
503 $day = $module_report->day;
504 } else {
505 // For "monthly" frequency.
506 $day = $module_report->day_n;
507 }
508
509 return $day;
510 }
511
512 /**
513 * Frequency format:
514 * if frequency is day, e.g.: 'frequency' => 1, 'day' => '1', 'time' => '20:30'
515 * if frequency is week, e.g.: 'frequency' => 7, 'day' => 'wednesday', 'time' => '14:00'
516 * if frequency is month, e.g.: 'frequency' => 30, 'day' => '4', 'time' => '4:30'
517 *
518 * @return array
519 */
520 public function build_notification_hub_data(): array {
521 $audit_settings = new Audit_Logging();
522 $audit_report = new Audit_Report();
523 $firewall_report = new Firewall_Report();
524 $malware_report = new Malware_Report();
525 $scan_settings = new Scan_Settings();
526
527 return array(
528 'file_scanning' => array(
529 'active' => true,
530 // @since 2.7.0 move scheduled options to Scan settings, but we get status of Malware Scanning - Reporting here.
531 'enabled' => Notification::STATUS_ACTIVE === $malware_report->status,
532 // Report enabled bool value.
533 'frequency' => array(
534 'frequency' => $this->backward_frequency_compatibility( $scan_settings->frequency ),
535 'day' => $this->get_notification_day( $scan_settings ),
536 'time' => $scan_settings->time,
537 ),
538 ),
539 'audit_logging' => array(
540 'active' => $audit_settings->is_active(),
541 'enabled' => Notification::STATUS_ACTIVE === $audit_report->status,
542 'frequency' => array(
543 'frequency' => $this->backward_frequency_compatibility( $audit_report->frequency ),
544 'day' => $this->get_notification_day( $audit_report ),
545 'time' => $audit_report->time,
546 ),
547 ),
548 'ip_lockouts' => array(
549 // Always true as we have blacklist listening.
550 'active' => true,
551 'enabled' => Notification::STATUS_ACTIVE === $firewall_report->status,
552 // Report enabled bool value.
553 'frequency' => array(
554 'frequency' => $this->backward_frequency_compatibility( $firewall_report->frequency ),
555 'day' => $this->get_notification_day( $firewall_report ),
556 'time' => $firewall_report->time,
557 ),
558 ),
559 );
560 }
561
562 /**
563 * Build data for firewall notification.
564 *
565 * @return array
566 */
567 public function build_firewall_notification_hub_data(): array {
568 return array( 'firewall' => ( new Firewall_Notification() )->get_hub_data() );
569 }
570
571 /**
572 * Build security headers hub data.
573 *
574 * @return array
575 */
576 public function build_security_headers_hub_data(): array {
577 $security_headers = wd_di()->get( Security_Headers::class )->get_type_headers();
578
579 return array(
580 'active' => $security_headers['active'],
581 'inactive' => $security_headers['inactive'],
582 );
583 }
584
585 /**
586 * Build data to be sent to the hub.
587 *
588 * @return array
589 */
590 public function build_stats_to_hub(): array {
591 $scan_data = $this->build_scan_hub_data();
592 $tweaks_data = $this->build_security_tweaks_hub_data();
593 $audit_data = $this->build_audit_hub_data();
594 $firewall_data = $this->build_lockout_hub_data();
595 $two_fa = $this->build_2fa_hub_data();
596 $mask_login = $this->build_mask_login_hub_data();
597 $sec_headers = $this->build_security_headers_hub_data();
598 $captcha = $this->build_captcha_hub_data();
599 $pwned_password = $this->build_password_protection_hub_data();
600 $quarantined_files = $this->build_quarantined_files_hub_data();
601
602 return array(
603 // Domain name.
604 'domain' => network_home_url(),
605 // Last scan date.
606 'timestamp' => $scan_data['timestamp'],
607 // Scan issue count.
608 'warnings' => $scan_data['warning'],
609 // Security tweaks issue count.
610 'cautions' => $tweaks_data['cautions'],
611 'data_version' => wp_date( 'Ymd' ),
612 'scan_data' => wp_json_encode(
613 array(
614 'scan_result' => $scan_data['scan_result'],
615 'hardener_result' => array(
616 'issues' => $tweaks_data[ Security_Tweaks::STATUS_ISSUES ],
617 'ignored' => $tweaks_data[ Security_Tweaks::STATUS_IGNORE ],
618 'resolved' => $tweaks_data[ Security_Tweaks::STATUS_RESOLVE ],
619 ),
620 'scan_schedule' => $scan_data['scan_schedule'],
621 'audit_status' => array(
622 'events_in_month' => $audit_data['month'],
623 'audit_enabled' => $audit_data['enabled'],
624 'last_event_date' => $audit_data['last_event'],
625 ),
626 'audit_page_url' => network_admin_url( 'admin.php?page=wdf-logging' ),
627 'labels' => array(
628 // Todo: maybe should it remove because Scan Settings model has label() method for that?
629 'parent_integrity' => esc_html__( 'File change detection', 'defender-security' ),
630 'core_integrity' => esc_html__( 'Scan core files', 'defender-security' ),
631 'plugin_integrity' => esc_html__( 'Scan plugin files', 'defender-security' ),
632 'vulnerability_db' => esc_html__( 'Known vulnerabilities', 'defender-security' ),
633 'file_suspicious' => esc_html__( 'Suspicious code', 'defender-security' ),
634 ),
635 'scan_page_url' => network_admin_url( 'admin.php?page=wdf-scan' ),
636 'hardener_page_url' => network_admin_url( 'admin.php?page=wdf-hardener' ),
637 'new_scan_url' => network_admin_url( 'admin.php?page=wdf-scan&wdf-action=new_scan' ),
638 'schedule_scans_url' => network_admin_url( 'admin.php?page=wdf-schedule-scan' ),
639 'settings_page_url' => network_admin_url( 'admin.php?page=wdf-settings' ),
640 'ip_lockout_page_url' => network_admin_url( 'admin.php?page=wdf-ip-lockout' ),
641 'last_lockout' => $firewall_data['last_lockout'],
642 'login_lockout_enabled' => $firewall_data['lp'],
643 'login_lockout' => $firewall_data['lp_week'],
644 'lockout_404_enabled' => $firewall_data['nf'],
645 'lockout_404' => $firewall_data['nf_week'],
646 'lockout_ua_enabled' => $firewall_data['ua'],
647 'lockout_ua' => $firewall_data['ua_week'],
648 'total_lockout' => (int) $firewall_data['lp_week'] + (int) $firewall_data['nf_week'] + (int) $firewall_data['ua_week'],
649 'global_ip_list_enabled' => $firewall_data['global_ip_list_enabled'],
650 'antibot_enabled' => $firewall_data['antibot_enabled'],
651 'antibot_mode' => $firewall_data['antibot_mode'],
652 'advanced' => array(
653 // This is moved but still keep here for backward compatibility.
654 'multi_factors_auth' => array(
655 'active' => $two_fa['active'],
656 'enabled' => $two_fa['enabled'],
657 'active_users' => $two_fa['active_users'],
658 ),
659 'mask_login' => array(
660 'activate' => $mask_login['active'],
661 'masked_url' => $mask_login['masked_url'],
662 ),
663 'security_headers' => array(
664 'active' => $sec_headers['active'],
665 'inactive' => $sec_headers['inactive'],
666 ),
667 // We'll change 'google_recaptcha'-key to 'captcha' in the future.
668 'google_recaptcha' => array(
669 'active' => $captcha['active'],
670 ),
671 'password_protection' => array(
672 'active' => $pwned_password['active'],
673 ),
674 ),
675 'reports' => $this->build_notification_hub_data(),
676 'notifications' => array( 'firewall' => ( new Firewall_Notification() )->get_hub_data() ),
677 'quarantined_files' => $quarantined_files,
678 )
679 ),
680 );
681 }
682
683 /**
684 * Checks whether we're on WPMU DEV Hosting.
685 *
686 * @return bool
687 */
688 public function is_wpmu_hosting(): bool {
689 return isset( $_SERVER['WPMUDEV_HOSTED'] );
690 }
691
692 /**
693 * Checks whether we're on The Free HUB.
694 *
695 * @return bool
696 */
697 protected function is_tfh_account(): bool {
698 return class_exists( 'WPMUDEV_Dashboard' ) &&
699 is_object( WPMUDEV_Dashboard::$api ) &&
700 method_exists( WPMUDEV_Dashboard::$api, 'get_membership_status' ) &&
701 'free' === WPMUDEV_Dashboard::$api->get_membership_status();
702 }
703
704 /**
705 * Check if WPMUDEV Hosted site is connected to The Free HUB.
706 *
707 * @return bool
708 * @since 3.3.0
709 */
710 public function is_hosted_site_connected_to_tfh(): bool {
711 return $this->is_tfh_account() && $this->is_wpmu_hosting();
712 }
713
714 /**
715 * Check if a site from 3rd party hosting is connected to The Free HUB.
716 *
717 * @return bool
718 * @since 3.6.0
719 */
720 public function is_another_hosted_site_connected_to_tfh(): bool {
721 return $this->is_tfh_account() && ! $this->is_wpmu_hosting();
722 }
723
724 /**
725 * Build notification hub data.
726 *
727 * @return array
728 */
729 protected function build_quarantined_files_hub_data(): array {
730 if ( ! class_exists( 'WP_Defender\Component\Quarantine' ) ) {
731 return array();
732 }
733
734 return wd_di()->get( \WP_Defender\Component\Quarantine::class )->hub_list();
735 }
736
737 /**
738 * Schedule Hub Synchronization event.
739 */
740 public function schedule_hub_sync(): void {
741 if ( ! wp_next_scheduled( 'defender_hub_sync' ) ) {
742 wp_schedule_single_event( time(), 'defender_hub_sync' );
743 }
744 }
745
746 /**
747 * Returns the signup url.
748 * If Dashboard plugin is active the signup url returned will be the Dashboard signup page. Else Hub signup page.
749 *
750 * @return string
751 */
752 public function signup_url(): string {
753 if ( class_exists( 'WPMUDEV_Dashboard' ) && is_object( WPMUDEV_Dashboard::$api ) ) {
754 return add_query_arg(
755 array( 'page' => 'wpmudev' ),
756 is_multisite() ? network_admin_url() : get_admin_url()
757 );
758 }
759
760 return $this->hub_signup_url();
761 }
762
763 /**
764 * Returns the hub's signup url.
765 *
766 * @return string
767 */
768 public function hub_signup_url(): string {
769 return $this->get_api_base_url() . 'register/?signup=defender&defender_url=' . site_url();
770 }
771 }
772