PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.0
Defender Security – Malware Scanner, Login Security & Firewall v6.2.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.2.0, at src/traits/defender-hub-client.php

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