PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.1
Defender Security – Malware Scanner, Login Security & Firewall v5.5.1
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 5.5.1, at src/traits/defender-hub-client.php

804 lines 25.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 DateTime;
12 use Exception;
13 use WP_User_Query;
14 use WPMUDEV_Dashboard;
15 use WP_Defender\Model\Scan;
16 use WP_Defender\Model\Audit_Log;
17 use WP_Defender\Model\Notification;
18 use WP_Defender\Controller\Firewall;
19 use WP_Defender\Component\Quarantine;
20 use WP_Defender\Model\Setting\Two_Fa;
21 use WP_Defender\Component\IP\Antibot_Global_Firewall;
22 use WP_Defender\Model\Setting\Recaptcha;
23 use WP_Defender\Model\Setting\Mask_Login;
24 use WP_Defender\Controller\Security_Tweaks;
25 use WP_Defender\Controller\Security_Headers;
26 use WP_Defender\Model\Setting\Audit_Logging;
27 use WP_Defender\Model\Setting\Login_Lockout;
28 use WP_Defender\Model\Setting\Notfound_Lockout;
29 use WP_Defender\Model\Notification\Audit_Report;
30 use WP_Defender\Model\Setting\Global_Ip_Lockout;
31 use WP_Defender\Model\Setting\User_Agent_Lockout;
32 use WP_Defender\Model\Notification\Malware_Report;
33 use WP_Defender\Model\Setting\Password_Protection;
34 use WP_Defender\Model\Notification\Firewall_Report;
35 use WP_Defender\Model\Setting\Scan as Scan_Settings;
36 use WP_Defender\Model\Notification\Firewall_Notification;
37 use WP_Defender\Model\Setting\Security_Tweaks as Model_Security_Tweaks;
38
39 trait Defender_Hub_Client {
40
41 use IP;
42
43 /**
44 * Get API base URL.
45 *
46 * @return string
47 * @since 3.4.0
48 */
49 public function get_api_base_url(): string {
50 return defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
51 ? trailingslashit( WPMUDEV_CUSTOM_API_SERVER )
52 : 'https://wpmudev.com/';
53 }
54
55 /**
56 * Retrieves the endpoint URL based on the given scenario.
57 *
58 * @param string $scenario The scenario identifier.
59 *
60 * @return string The endpoint URL.
61 */
62 public function get_endpoint( $scenario ): string {
63 $base = $this->get_api_base_url();
64 switch ( $scenario ) {
65 case self::API_SCAN_KNOWN_VULN:
66 return $base . 'api/defender/v1/vulnerabilities';
67 case self::API_SCAN_SIGNATURE:
68 return $base . 'api/defender/v1/yara-signatures';
69 case self::API_AUDIT:
70 // This is from another endpoint.
71 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
72 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
73 : 'https://audit.wpmudev.org/';
74
75 return $base . 'logs';
76 case self::API_AUDIT_ADD:
77 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
78 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
79 : 'https://audit.wpmudev.org/';
80
81 return $base . 'logs/add_multiple';
82 case self::API_BLACKLIST:
83 return $base . 'api/defender/v1/blacklist-monitoring?domain=' . network_site_url();
84 case self::API_GLOBAL_IP_LIST:
85 return $base . 'api/hub/v1/global-ip-list';
86 case self::API_PACKAGE_CONFIGS:
87 return $base . 'api/hub/v1/package-configs';
88 case self::API_IP_BLOCKLIST_SUBMIT_LOGS:
89 return $base . 'api/blocklist/v1/logs';
90 case self::API_ANTIBOT_GLOBAL_FIREWALL:
91 $site_id = $this->get_site_id();
92
93 return $base . "api/hub/v1/sites/$site_id/modules/hosting/antibot";
94 case self::API_HUB_SYNC:
95 default:
96 return $base . 'api/defender/v1/scan-results';
97 }
98 }
99
100 /**
101 * Get WPMUDEV site id.
102 *
103 * @return int|bool
104 */
105 public function get_site_id() {
106 if ( false !== $this->get_apikey() ) {
107 return (int) WPMUDEV_Dashboard::$api->get_site_id();
108 }
109
110 return false;
111 }
112
113 /**
114 * HUB API remote request method.
115 *
116 * @param string $scenario The scenario for the API request.
117 * @param array $body The body of the API request. Default is an empty array.
118 * @param array $args The arguments for the API request. Default is an empty array.
119 * @param bool $recheck Whether to recheck the API request. Default is false.
120 *
121 * @return array|WP_Error The response body of the API request or a WP_Error object.
122 * @throws Exception If the Dash plugin authentication API key is missing.
123 */
124 private function hub_api_request(
125 string $scenario,
126 array $body = array(),
127 array $args = array(),
128 bool $recheck = false
129 ) {
130 $api_key = $this->get_api_key();
131 $body['domain'] ??= network_site_url();
132
133 $headers = array(
134 'Authorization' => 'Basic ' . $api_key,
135 'apikey' => $api_key,
136 );
137
138 $timeout = isset( $args['timeout'] ) ? $args['timeout'] : 30;
139
140 $args = array_merge(
141 $args,
142 array(
143 'body' => $body,
144 'headers' => $headers,
145 'timeout' => $timeout,
146 'sslverify' => apply_filters( 'https_ssl_verify', true ),
147 )
148 );
149
150 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
151
152 if ( is_wp_error( $request ) ) {
153 if ( ! $recheck ) {
154 return $request;
155 }
156 // Sometimes a response comes with a curl error #52 so should delete Authorization header.
157 $args['headers'] = array( 'apikey' => $api_key );
158 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
159 if ( is_wp_error( $request ) ) {
160 return $request;
161 }
162 }
163
164 $result = wp_remote_retrieve_body( $request );
165 $result = json_decode( $result, true );
166
167 if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
168 return new WP_Error(
169 wp_remote_retrieve_response_code( $request ),
170 $result['message'] ?? 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' ), 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->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 *
377 * @return array An array containing the number of audit log entries, the timestamp of the
378 * last audit log entry, and a boolean indicating if audit logging is enabled.
379 */
380 public function build_audit_hub_data(): array {
381 $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )->setTime(
382 0,
383 0,
384 0
385 )->getTimestamp();
386 $date_to = ( new DateTime( wp_date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp();
387 $month_count = Audit_Log::count( $date_from, $date_to );
388 $last = Audit_Log::get_last();
389 if ( is_object( $last ) ) {
390 $last = wp_date( 'Y-m-d g:i a', $last->timestamp );
391 } else {
392 $last = 'n/a';
393 }
394
395 $settings = new Audit_Logging();
396
397 return array(
398 'month' => $month_count,
399 'last_event' => $last,
400 'enabled' => $settings->is_active(),
401 );
402 }
403
404 /**
405 * Builds an array of lockout data to be sent to the hub.
406 *
407 * @return array
408 */
409 public function build_lockout_hub_data(): array {
410 $firewall = wd_di()->get( Firewall::class )->data_frontend();
411 $antibot = wd_di()->get( Antibot_Global_Firewall::class );
412
413 return array(
414 'last_lockout' => $firewall['last_lockout'],
415 'lp' => wd_di()->get( Login_Lockout::class )->enabled,
416 'lp_week' => $firewall['login']['week'],
417 'nf' => wd_di()->get( Notfound_Lockout::class )->enabled,
418 'nf_week' => $firewall['nf']['week'],
419 'ua' => wd_di()->get( User_Agent_Lockout::class )->enabled,
420 'ua_week' => $firewall['ua']['week'],
421 'global_ip_list_enabled' => wd_di()->get( Global_Ip_Lockout::class )->enabled,
422 'antibot_enabled' => $antibot->frontend_is_enabled(),
423 'antibot_mode' => $antibot->frontend_mode(),
424 );
425 }
426
427 /**
428 * Builds an array of 2fa data to be sent to the hub.
429 *
430 * @return array
431 */
432 public function build_2fa_hub_data(): array {
433 $settings = new Two_Fa();
434 $service = wd_di()->get( \WP_Defender\Component\Two_Fa::class );
435 $query = new WP_User_Query(
436 array(
437 // Look over the network.
438 'blog_id' => 0,
439 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
440 array(
441 'key' => $service::DEFAULT_PROVIDER_USER_KEY,
442 'value' => array_keys( $service->get_providers() ),
443 'compare' => 'IN',
444 ),
445 ),
446 )
447 );
448 $active_users = array();
449 if ( $query->get_total() > 0 ) {
450 foreach ( $query->get_results() as $obj_user ) {
451 $active_users[] = array(
452 'display_name' => $obj_user->data->display_name,
453 );
454 }
455 }
456
457 return array(
458 'active' => $settings->enabled && array() !== $settings->user_roles,
459 'enabled' => $settings->enabled,
460 'active_users' => $active_users,
461 );
462 }
463
464 /**
465 * Builds an array of mask login data to be sent to the hub.
466 *
467 * @return array
468 */
469 public function build_mask_login_hub_data(): array {
470 $settings = new Mask_Login();
471
472 return array(
473 'active' => $settings->is_active(),
474 'masked_url' => $settings->mask_url,
475 );
476 }
477
478 /**
479 * Builds an array of security headers data to be sent to the hub.
480 *
481 * @return array
482 */
483 public function build_recaptcha_hub_data(): array {
484 $settings = new Recaptcha();
485
486 return array(
487 'active' => $settings->is_active(),
488 );
489 }
490
491 /**
492 * Builds an array of password protection data to be sent to the hub.
493 *
494 * @return array
495 */
496 public function build_password_protection_hub_data(): array {
497 $settings = new Password_Protection();
498
499 return array(
500 'active' => $settings->is_active(),
501 );
502 }
503
504 /**
505 * Get the notification day.
506 *
507 * @param object $module_report The report object.
508 *
509 * @return string
510 */
511 private function get_notification_day( $module_report ): string {
512 if ( ! ( isset( $module_report->frequency ) && isset( $module_report->day ) && isset( $module_report->day_n ) ) ) {
513 return '';
514 }
515
516 if ( 'daily' === $module_report->frequency ) {
517 $day = '1';
518 } elseif ( 'weekly' === $module_report->frequency ) {
519 $day = $module_report->day;
520 } else {
521 // For "monthly" frequency.
522 $day = $module_report->day_n;
523 }
524
525 return $day;
526 }
527
528 /**
529 * Frequency format:
530 * if frequency is day, e.g.: 'frequency' => 1, 'day' => '1', 'time' => '20:30'
531 * if frequency is week, e.g.: 'frequency' => 7, 'day' => 'wednesday', 'time' => '14:00'
532 * if frequency is month, e.g.: 'frequency' => 30, 'day' => '4', 'time' => '4:30'
533 *
534 * @return array
535 */
536 public function build_notification_hub_data(): array {
537 $audit_settings = new Audit_Logging();
538 $audit_report = new Audit_Report();
539 $firewall_report = new Firewall_Report();
540 $malware_report = new Malware_Report();
541 $scan_settings = new Scan_Settings();
542
543 return array(
544 'file_scanning' => array(
545 'active' => true,
546 // @since 2.7.0 move scheduled options to Scan settings, but we get status of Malware Scanning - Reporting here.
547 'enabled' => Notification::STATUS_ACTIVE === $malware_report->status,
548 // Report enabled bool value.
549 'frequency' => array(
550 'frequency' => $this->backward_frequency_compatibility( $scan_settings->frequency ),
551 'day' => $this->get_notification_day( $scan_settings ),
552 'time' => $scan_settings->time,
553 ),
554 ),
555 'audit_logging' => array(
556 'active' => $audit_settings->is_active(),
557 'enabled' => Notification::STATUS_ACTIVE === $audit_report->status,
558 'frequency' => array(
559 'frequency' => $this->backward_frequency_compatibility( $audit_report->frequency ),
560 'day' => $this->get_notification_day( $audit_report ),
561 'time' => $audit_report->time,
562 ),
563 ),
564 'ip_lockouts' => array(
565 // Always true as we have blacklist listening.
566 'active' => true,
567 'enabled' => Notification::STATUS_ACTIVE === $firewall_report->status,
568 // Report enabled bool value.
569 'frequency' => array(
570 'frequency' => $this->backward_frequency_compatibility( $firewall_report->frequency ),
571 'day' => $this->get_notification_day( $firewall_report ),
572 'time' => $firewall_report->time,
573 ),
574 ),
575 );
576 }
577
578 /**
579 * Build data for firewall notification.
580 *
581 * @return array
582 */
583 public function build_firewall_notification_hub_data(): array {
584 $firewall_notification = new Firewall_Notification();
585 if ( 'enabled' === $firewall_notification->status ) {
586 $login_lockout = $firewall_notification->configs['login_lockout'];
587 $nf_lockout = $firewall_notification->configs['nf_lockout'];
588 $ua_lockout = $firewall_notification->configs['ua_lockout'] ?? false;
589 } else {
590 $login_lockout = false;
591 $nf_lockout = false;
592 $ua_lockout = false;
593 }
594
595 return array(
596 'firewall' => array(
597 'login_lockout' => $login_lockout,
598 '404_lockout' => $nf_lockout,
599 'ua_lockout' => $ua_lockout,
600 ),
601 );
602 }
603
604 /**
605 * Build security headers hub data.
606 *
607 * @return array
608 */
609 public function build_security_headers_hub_data(): array {
610 $security_headers = wd_di()->get( Security_Headers::class )->get_type_headers();
611
612 return array(
613 'active' => $security_headers['active'],
614 'inactive' => $security_headers['inactive'],
615 );
616 }
617
618 /**
619 * Build data to be sent to the hub.
620 *
621 * @return array
622 */
623 public function build_stats_to_hub(): array {
624 $scan_data = $this->build_scan_hub_data();
625 $tweaks_data = $this->build_security_tweaks_hub_data();
626 $audit_data = $this->build_audit_hub_data();
627 $firewall_data = $this->build_lockout_hub_data();
628 $two_fa = $this->build_2fa_hub_data();
629 $mask_login = $this->build_mask_login_hub_data();
630 $sec_headers = $this->build_security_headers_hub_data();
631 $recaptcha = $this->build_recaptcha_hub_data();
632 $pwned_password = $this->build_password_protection_hub_data();
633 $quarantined_files = $this->build_quarantined_files_hub_data();
634
635 return array(
636 // Domain name.
637 'domain' => network_home_url(),
638 // Last scan date.
639 'timestamp' => $scan_data['timestamp'],
640 // Scan issue count.
641 'warnings' => $scan_data['warning'],
642 // Security tweaks issue count.
643 'cautions' => $tweaks_data['cautions'],
644 'data_version' => wp_date( 'Ymd' ),
645 'scan_data' => wp_json_encode(
646 array(
647 'scan_result' => $scan_data['scan_result'],
648 'hardener_result' => array(
649 'issues' => $tweaks_data[ Security_Tweaks::STATUS_ISSUES ],
650 'ignored' => $tweaks_data[ Security_Tweaks::STATUS_IGNORE ],
651 'resolved' => $tweaks_data[ Security_Tweaks::STATUS_RESOLVE ],
652 ),
653 'scan_schedule' => $scan_data['scan_schedule'],
654 'audit_status' => array(
655 'events_in_month' => $audit_data['month'],
656 'audit_enabled' => $audit_data['enabled'],
657 'last_event_date' => $audit_data['last_event'],
658 ),
659 'audit_page_url' => network_admin_url( 'admin.php?page=wdf-logging' ),
660 'labels' => array(
661 // Todo: maybe should it remove because Scan Settings model has label() method for that?
662 'parent_integrity' => esc_html__( 'File change detection', 'defender-security' ),
663 'core_integrity' => esc_html__( 'Scan core files', 'defender-security' ),
664 'plugin_integrity' => esc_html__( 'Scan plugin files', 'defender-security' ),
665 'vulnerability_db' => esc_html__( 'Known vulnerabilities', 'defender-security' ),
666 'file_suspicious' => esc_html__( 'Suspicious code', 'defender-security' ),
667 ),
668 'scan_page_url' => network_admin_url( 'admin.php?page=wdf-scan' ),
669 'hardener_page_url' => network_admin_url( 'admin.php?page=wdf-hardener' ),
670 'new_scan_url' => network_admin_url( 'admin.php?page=wdf-scan&wdf-action=new_scan' ),
671 'schedule_scans_url' => network_admin_url( 'admin.php?page=wdf-schedule-scan' ),
672 'settings_page_url' => network_admin_url( 'admin.php?page=wdf-settings' ),
673 'ip_lockout_page_url' => network_admin_url( 'admin.php?page=wdf-ip-lockout' ),
674 'last_lockout' => $firewall_data['last_lockout'],
675 'login_lockout_enabled' => $firewall_data['lp'],
676 'login_lockout' => $firewall_data['lp_week'],
677 'lockout_404_enabled' => $firewall_data['nf'],
678 'lockout_404' => $firewall_data['nf_week'],
679 'lockout_ua_enabled' => $firewall_data['ua'],
680 'lockout_ua' => $firewall_data['ua_week'],
681 'total_lockout' => (int) $firewall_data['lp_week'] + (int) $firewall_data['nf_week'] + (int) $firewall_data['ua_week'],
682 'global_ip_list_enabled' => $firewall_data['global_ip_list_enabled'],
683 'antibot_enabled' => $firewall_data['antibot_enabled'],
684 'antibot_mode' => $firewall_data['antibot_mode'],
685 'advanced' => array(
686 // This is moved but still keep here for backward compatibility.
687 'multi_factors_auth' => array(
688 'active' => $two_fa['active'],
689 'enabled' => $two_fa['enabled'],
690 'active_users' => $two_fa['active_users'],
691 ),
692 'mask_login' => array(
693 'activate' => $mask_login['active'],
694 'masked_url' => $mask_login['masked_url'],
695 ),
696 'security_headers' => array(
697 'active' => $sec_headers['active'],
698 'inactive' => $sec_headers['inactive'],
699 ),
700 'google_recaptcha' => array(
701 'active' => $recaptcha['active'],
702 ),
703 'password_protection' => array(
704 'active' => $pwned_password['active'],
705 ),
706 ),
707 'reports' => $this->build_notification_hub_data(),
708 'notifications' => $this->build_firewall_notification_hub_data(),
709 'quarantined_files' => $quarantined_files,
710 )
711 ),
712 );
713 }
714
715 /**
716 * Checks whether we're on WPMU DEV Hosting.
717 *
718 * @return bool
719 */
720 public function is_wpmu_hosting(): bool {
721 return isset( $_SERVER['WPMUDEV_HOSTED'] );
722 }
723
724 /**
725 * Checks whether we're on The Free HUB.
726 *
727 * @return bool
728 */
729 protected function is_tfh_account(): bool {
730 return class_exists( 'WPMUDEV_Dashboard' ) &&
731 is_object( WPMUDEV_Dashboard::$api ) &&
732 method_exists( WPMUDEV_Dashboard::$api, 'get_membership_status' ) &&
733 'free' === WPMUDEV_Dashboard::$api->get_membership_status();
734 }
735
736 /**
737 * Check if WPMUDEV Hosted site is connected to The Free HUB.
738 *
739 * @return bool
740 * @since 3.3.0
741 */
742 public function is_hosted_site_connected_to_tfh(): bool {
743 return $this->is_tfh_account() && $this->is_wpmu_hosting();
744 }
745
746 /**
747 * Check if a site from 3rd party hosting is connected to The Free HUB.
748 *
749 * @return bool
750 * @since 3.6.0
751 */
752 public function is_another_hosted_site_connected_to_tfh(): bool {
753 return $this->is_tfh_account() && ! $this->is_wpmu_hosting();
754 }
755
756 /**
757 * Build notification hub data.
758 *
759 * @return array
760 */
761 protected function build_quarantined_files_hub_data(): array {
762 if ( ! class_exists( 'WP_Defender\Component\Quarantine' ) ) {
763 return array();
764 }
765
766 return wd_di()->get( Quarantine::class )->hub_list();
767 }
768
769 /**
770 * Schedule Hub Synchronization event.
771 */
772 public function schedule_hub_sync(): void {
773 if ( ! wp_next_scheduled( 'defender_hub_sync' ) ) {
774 wp_schedule_single_event( time(), 'defender_hub_sync' );
775 }
776 }
777
778 /**
779 * Returns the signup url.
780 * If Dashboard plugin is active the signup url returned will be the Dashboard signup page. Else Hub signup page.
781 *
782 * @return string
783 */
784 public function signup_url(): string {
785 if ( class_exists( 'WPMUDEV_Dashboard' ) && is_object( WPMUDEV_Dashboard::$api ) ) {
786 return add_query_arg(
787 array( 'page' => 'wpmudev' ),
788 is_multisite() ? network_admin_url() : get_admin_url()
789 );
790 }
791
792 return $this->hub_signup_url();
793 }
794
795 /**
796 * Returns the hub's signup url.
797 *
798 * @return string
799 */
800 public function hub_signup_url(): string {
801 return $this->get_api_base_url() . 'register/?signup=defender&defender_url=' . site_url();
802 }
803 }
804