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 / controller / class-dashboard.php

class-dashboard.php in Defender Security – Malware Scanner, Login Security & Firewall 6.1.0, at src/controller/class-dashboard.php

523 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles the main admin page.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use WP_Defender\Event;
11 use Calotes\Helper\HTTP;
12 use Calotes\Helper\Route;
13 use WP_Defender\Model\Setting\Login_Lockout;
14 use WP_Defender\Model\Setting\Notfound_Lockout;
15 use WP_Defender\Model\Setting\User_Agent_Lockout;
16 use WP_Defender\Traits\Defender_Dashboard_Client;
17 use WP_Defender\Traits\IO;
18 use Calotes\Component\Request;
19 use Calotes\Component\Response;
20 use WP_Defender\Traits\Formats;
21 use WP_Defender\Behavior\WPMUDEV;
22 use WP_Defender\Component\Feature_Modal;
23 use WP_Defender\Component\Hub_Connector as Hub_Connector_Component;
24 use WP_Defender\Model\Setting\Audit_Logging as Audit_Logging_Settings;
25 use WP_Defender\Model\Setting\Global_Ip_Lockout;
26 use WP_Defender\Component\Config\Config_Hub_Helper;
27 use WP_Defender\Component\IP\Global_IP as Global_IP_Component;
28 use WP_Defender\Model\Setting\Session_Protection;
29 use WP_Defender\Controller\Session_Protection as Session_Protection_Controller;
30
31 /**
32 * Handles the main admin page.
33 */
34 class Dashboard extends Event {
35
36 use IO;
37 use Formats;
38 use Defender_Dashboard_Client;
39
40 /**
41 * The slug identifier for this controller.
42 *
43 * @var string
44 */
45 public $slug = 'wp-defender';
46
47 /**
48 * Site option key for the one-time report-schedule upgrade notice.
49 */
50 public const REPORT_SCHEDULE_NOTICE_OPTION = 'wd_show_report_schedule_notice';
51
52 /**
53 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
54 */
55 public function __construct() {
56 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
57 $this->add_main_page();
58 $this->register_routes();
59 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
60 add_filter( 'custom_menu_order', '__return_true' );
61 add_filter( 'menu_order', array( $this, 'menu_order' ) );
62 add_action( 'admin_init', array( $this, 'maybe_redirect_notification_request' ), 99 );
63 }
64
65 /**
66 * Because we move the notifications on separate modules, so links from HUB should be redirected to correct URL.
67 *
68 * @return void
69 */
70 public function maybe_redirect_notification_request(): void {
71 $page = HTTP::get( 'page' );
72 if ( ! in_array( $page, array( 'wdf-scan', 'wdf-ip-lockout', 'wdf-hardener', 'wdf-logging' ), true ) ) {
73 return;
74 }
75 $view = HTTP::get( 'view' );
76 if ( in_array( $view, array( 'reporting', 'notification', 'report' ), true ) ) {
77 wp_safe_redirect( network_admin_url( 'admin.php?page=wdf-notification' ) );
78 exit;
79 }
80 }
81
82 /**
83 * Filter out the defender menu for changing text.
84 *
85 * @param array $menu_order The current menu order.
86 *
87 * @return array
88 */
89 public function menu_order( $menu_order ) {
90 global $submenu;
91 if ( isset( $submenu['wp-defender'] ) ) {
92 $defender_menu = $submenu['wp-defender'];
93 $defender_menu[0][0] = esc_html__( 'Dashboard', 'defender-security' );
94 $defender_menu = array_values( $defender_menu );
95 // Change the global $submenu variable, because otherwise the menu name/order will not change.
96 $submenu['wp-defender'] = $defender_menu; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
97 }
98
99 global $menu;
100 // Get the total scanning active issues.
101 $count = wd_di()->get( \WP_Defender\Component\Scan::class )->indicator_issue_count();
102
103 $indicator = $count > 0
104 ? ' <span class="update-plugins wd-issue-indicator-sidebar"></span>'
105 : null;
106 foreach ( $menu as $k => $item ) {
107 if ( 'wp-defender' === $item[2] ) {
108 // Add a badge next to the "Defender" menu item in the global $menu variable.
109 $menu[ $k ][0] .= $indicator; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
110 }
111 }
112
113 return $menu_order;
114 }
115
116 /**
117 * Registers the main page in the WordPress admin menu.
118 */
119 protected function add_main_page() {
120 $this->register_page(
121 $this->get_page_title(),
122 $this->parent_slug,
123 array( $this, 'main_view' ),
124 null,
125 $this->get_menu_icon(),
126 $this->get_menu_title()
127 );
128 }
129
130 /**
131 * Renders the main view for this page.
132 */
133 public function main_view() {
134 $this->render( 'main' );
135 }
136
137 /**
138 * Page-specific data for a concrete controller.
139 *
140 * @return array
141 */
142 protected function get_page_data(): array {
143 $security_tweaks = wd_di()->get( \WP_Defender\Controller\Security_Tweaks::class );
144 $security_tweaks->refresh_tweaks_status();
145 $security_tweaks_data = $security_tweaks->dashboard_widget();
146
147 $audit_model = wd_di()->get( Audit_Logging_Settings::class );
148 $firewall = wd_di()->get( Firewall::class )->get_summary();
149 // Different lockout types.
150 $enabled_login = wd_di()->get( Login_Lockout::class )->enabled;
151 $enabled_nf = wd_di()->get( Notfound_Lockout::class )->enabled;
152 $enabled_ua = wd_di()->get( User_Agent_Lockout::class )->enabled;
153
154 return array(
155 'hide_onboarding' => ! wd_di()->get( HUB::class )->get_onboarding_status(),
156 'defenderSetupNonce' => wp_create_nonce( 'defender_quick_setup' ),
157 'securityTweaks' => $security_tweaks_data['summary']['issues_count'],
158 'scanData' => array(
159 'numberIssues' => wd_di()->get( \WP_Defender\Component\Scan::class )->indicator_issue_count(),
160 'settings' => wd_di()->get( \WP_Defender\Model\Setting\Scan::class )->export(),
161 // Scan routes & nonces are set above.
162 ),
163 'firewallData' => array(
164 'enabledLocalFirewall' => $enabled_login || $enabled_nf || $enabled_ua,
165 'enabledLogin' => $enabled_login,
166 'enabledNotFound' => $enabled_nf,
167 'enabledUserAgent' => $enabled_ua,
168 'loginLockoutMonth' => $firewall['lockout_login_this_month'],
169 'nfLockoutMonth' => $firewall['lockout_404_this_month'],
170 'uaLockoutMonth' => $firewall['lockout_ua_this_month'],
171 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
172 ),
173 'site_id' => wd_di()->get( WPMUDEV::class )->get_site_id(),
174 'auditData' => array(
175 'enabled' => $audit_model->is_active(),
176 ),
177 'sessionProtection' => wd_di()->get( Session_Protection::class )->export(),
178 'show_report_schedule_notice' => (bool) get_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION, false ),
179 );
180 }
181
182 /**
183 * Enqueues scripts and styles for this page.
184 * Only enqueues assets if the page is active.
185 */
186 public function enqueue_assets() {
187 if ( ! $this->is_page_active() ) {
188 return;
189 }
190
191 $wizard_action = HTTP::get( 'wizard_action' );
192 $wizard_source = HTTP::get( 'source' );
193
194 // Fallback completion marker for setup wizard integrations rendered on dashboard.
195 // This prevents onboarding loops if client-side completion request fails.
196 if (
197 'setup_wizard' === $wizard_source
198 && in_array( $wizard_action, array( 'view_results', 'close_wizard', 'finish_wizard' ), true )
199 && current_user_can( 'manage_options' )
200 ) {
201 update_site_option( 'wp_defender_shown_activator', true );
202 }
203
204 $show_onboarding = \WP_Defender\Model\Onboard::maybe_show_onboarding();
205 $api_error = HTTP::get( 'api_error' );
206 $hub_connection_source = HTTP::get( 'hub_connection_source' );
207 $hub_connect_recovery = ! $show_onboarding
208 && is_string( $api_error )
209 && '' !== $api_error
210 && 'profile_menu' !== $hub_connection_source
211 && ! Hub_Connector_Component::is_logged_in()
212 && ! Hub_Connector_Component::is_wpmudev_dashboard_connected();
213
214 if ( $show_onboarding || $hub_connect_recovery ) {
215 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
216 }
217
218 $handle = 'defender-ui-dashboard';
219 wp_enqueue_script(
220 $handle,
221 WP_DEFENDER_BASE_URL . 'assets/js/dashboard-ui.js',
222 array( 'def-vue', 'def-manifest', 'def-core-ui', 'defender', 'wp-i18n' ),
223 DEFENDER_VERSION,
224 true
225 );
226 wp_set_script_translations( $handle, 'wpdef' );
227
228 $setup_wizard_data = wd_di()->get( Setup_Wizard::class )->data_frontend();
229 $tracking_data = wd_di()->get( Data_Tracking::class )->get_dashboard_notice_data();
230 $dashboard_data = $this->dump_routes_and_nonces();
231 $firewall_data = wd_di()->get( Firewall::class )->dump_routes_and_nonces();
232 $scan_data = wd_di()->get( \WP_Defender\Controller\Scan::class )->dump_routes_and_nonces();
233 $routes = array_merge(
234 $setup_wizard_data['routes'] ?? array(),
235 $tracking_data['routes'] ?? array(),
236 $dashboard_data['routes'] ?? array(),
237 $firewall_data['routes'] ?? array(),
238 $scan_data['routes'] ?? array(),
239 );
240 $nonces = array_merge(
241 $setup_wizard_data['nonces'] ?? array(),
242 $tracking_data['nonces'] ?? array(),
243 $dashboard_data['nonces'] ?? array(),
244 $firewall_data['nonces'] ?? array(),
245 $scan_data['nonces'] ?? array(),
246 );
247 unset( $setup_wizard_data['routes'], $setup_wizard_data['nonces'] );
248 unset( $tracking_data['routes'], $tracking_data['nonces'] );
249 unset( $firewall_data['routes'], $firewall_data['nonces'] );
250 wp_localize_script(
251 $handle,
252 'defenderUIData',
253 array_merge(
254 $this->get_shared_data(),
255 $this->get_page_data(),
256 // Welcome modal's details.
257 wd_di()->get( Feature_Modal::class )->get_dashboard_modals(),
258 // Specific data.
259 array(
260 'showOnboarding' => $show_onboarding,
261 'routes' => $routes,
262 'nonces' => $nonces,
263 ),
264 $setup_wizard_data,
265 $tracking_data,
266 wd_di()->get( \WP_Defender\Controller\Scan::class )->get_initial_scan_data()
267 )
268 );
269
270 wp_enqueue_style(
271 $handle,
272 WP_DEFENDER_BASE_URL . 'assets/css/showcase.css',
273 array(),
274 DEFENDER_VERSION
275 );
276
277 $this->enqueue_main_assets();
278 }
279
280 /**
281 * Adds onboarding body classes on dashboard when onboarding wizard is active.
282 *
283 * @param string $classes Existing admin body classes.
284 *
285 * @return string
286 */
287 public function admin_body_class( $classes ): string {
288 $classes .= ' wdf-onboarding-active ';
289
290 return $classes;
291 }
292
293 /**
294 * Returns the current hardening (security tweaks) issue count via AJAX.
295 *
296 * @return Response
297 * @defender_route
298 * @defender_redirect
299 */
300 public function get_hardening_count(): Response {
301 $security_tweaks = wd_di()->get( \WP_Defender\Controller\Security_Tweaks::class )->dashboard_widget();
302
303 return new Response(
304 true,
305 array(
306 'count' => (int) ( $security_tweaks['summary']['issues_count'] ?? 0 ),
307 )
308 );
309 }
310
311 /**
312 * Handles the request to hide new features modal.
313 *
314 * @param Request $request The request object containing data.
315 *
316 * @return Response The response object indicating success or failure.
317 * @defender_route
318 */
319 public function hide_new_features( Request $request ): Response {
320 $data = $request->get_data(
321 array(
322 'intention' => array(
323 'type' => 'string',
324 'sanitize' => 'sanitize_text_field',
325 ),
326 )
327 );
328 $intention = $data['intention'] ?? false;
329 if ( 'welcome_modal' === $intention ) {
330 Feature_Modal::delete_modal_key();
331 }
332
333 return new Response( true, array() );
334 }
335
336 /**
337 * Activate Global IP submodule with the enabled Auto sync option.
338 *
339 * @return Response
340 * @defender_route
341 */
342 public function activate_global_ip(): Response {
343 // Changes for Global IP.
344 $model = wd_di()->get( Global_Ip_Lockout::class );
345 $model->enabled = true;
346 $model->blocklist_autosync = true;
347 $model->save();
348 // Clear Global IP reminder.
349 wd_di()->get( Global_IP_Component::class )->delete_dashboard_notice_reminder();
350 // Changes for Hub.
351 Config_Hub_Helper::set_clear_active_flag();
352
353 return new Response(
354 true,
355 array(
356 'redirect' => network_admin_url( 'admin.php?page=wdf-ip-lockout&view=global-ip' ),
357 'interval' => 1,
358 )
359 );
360 }
361
362 /**
363 * Activate Session Protection submodule.
364 *
365 * @return Response
366 * @defender_route
367 */
368 public function activate_session_protection(): Response {
369 $model = wd_di()->get( Session_Protection::class );
370 $model->enabled = true;
371 $model->save();
372 // Changes for Hub.
373 Config_Hub_Helper::set_clear_active_flag();
374
375 return new Response(
376 true,
377 array(
378 'redirect' => network_admin_url( 'admin.php?page=wdf-advanced-tools&view=session-protection' ),
379 'interval' => 1,
380 )
381 );
382 }
383
384 /**
385 * Remove Global IP notice reminder.
386 *
387 * @return Response
388 * @defender_route
389 */
390 public function remove_global_ip_notice_reminder(): Response {
391 wd_di()->get( Global_IP_Component::class )->delete_dashboard_notice_reminder();
392
393 return new Response( true, array() );
394 }
395
396 /**
397 * Dismiss the one-time report-schedule notice set during the 6.1.0 upgrade.
398 *
399 * @return Response
400 * @defender_route
401 */
402 public function dismiss_report_schedule_notice(): Response {
403 delete_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION );
404
405 return new Response( true, array() );
406 }
407
408 /**
409 * Toggle a dashboard feature by feature key.
410 *
411 * @param Request $request The current request data.
412 *
413 * @return Response
414 * @defender_route
415 */
416 public function toggle_feature( Request $request ): Response {
417 $data = $request->get_data(
418 array(
419 'feature' => array(
420 'type' => 'string',
421 'sanitize' => 'sanitize_text_field',
422 ),
423 )
424 );
425 $feature = $data['feature'] ?? '';
426
427 $feature_controllers = array(
428 'antibot' => Antibot_Global_Firewall::class,
429 'audit' => Audit_Logging::class,
430 'session_protection' => Session_Protection_Controller::class,
431 );
432 if ( isset( $feature_controllers[ $feature ] ) ) {
433 return wd_di()->get( $feature_controllers[ $feature ] )->save_settings( $request );
434 }
435
436 return new Response(
437 false,
438 array(
439 'message' => esc_html__( 'Unsupported feature toggle request.', 'defender-security' ),
440 )
441 );
442 }
443
444 /**
445 * Removes settings for all submodules.
446 */
447 public function remove_settings() {
448 wd_di()->get( Feature_Modal::class )->upgrade_site_options();
449
450 delete_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION );
451 }
452
453 /**
454 * Delete all the data & the cache.
455 */
456 public function remove_data() {
457 }
458
459 /**
460 * Provides data for the frontend.
461 *
462 * @return array An array of data for the frontend.
463 */
464 public function data_frontend(): array {
465 [ $endpoints, $nonces ] = Route::export_routes( 'dashboard' );
466 $firewall = wd_di()->get( Firewall::class );
467
468 return array_merge(
469 wd_di()->get( Feature_Modal::class )->get_dashboard_modals(),
470 array(
471 'scan' => wd_di()->get( Scan::class )->data_frontend(),
472 'firewall' => $firewall->data_frontend(),
473 'blocklist_monitor' => wd_di()->get( Blocklist_Monitor::class )->data_frontend(),
474 'blacklist' => array(
475 'nonces' => $nonces,
476 'endpoints' => $endpoints,
477 ),
478 'two_fa' => wd_di()->get( Two_Factor::class )->data_frontend(),
479 'advanced_tools' => array(
480 'mask_login' => wd_di()->get( Mask_Login::class )->dashboard_widget(),
481 'security_headers' => wd_di()->get( Security_Headers::class )->dashboard_widget(),
482 'pwned_passwords' => wd_di()->get( Password_Protection::class )->dashboard_widget(),
483 'captcha' => wd_di()->get( Captcha::class )->dashboard_widget(),
484 'strong_passwords' => wd_di()->get( Strong_Password::class )->dashboard_widget(),
485 ),
486 'security_tweaks' => wd_di()->get( Security_Tweaks::class )->dashboard_widget(),
487 'notifications' => wd_di()->get( Notification::class )->data_frontend(),
488 'settings' => wd_di()->get( Main_Setting::class )->data_frontend(),
489 'countries' => $firewall->dashboard_widget(),
490 'global_ip' => wd_di()->get( Global_Ip::class )->data_frontend(),
491 'hub_connector' => wd_di()->get( Hub_Connector::class )->data_frontend(),
492 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
493 )
494 );
495 }
496
497 /**
498 * Converts the current object state to an array.
499 *
500 * @return array The array representation of the object.
501 */
502 public function to_array(): array {
503 return array();
504 }
505
506 /**
507 * Imports data into the model.
508 *
509 * @param array $data Data to be imported into the model.
510 */
511 public function import_data( array $data ) {
512 }
513
514 /**
515 * Exports strings.
516 *
517 * @return array An array of strings.
518 */
519 public function export_strings(): array {
520 return array();
521 }
522 }
523