PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.0.1
Defender Security – Malware Scanner, Login Security & Firewall v6.0.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 / controller / class-dashboard.php

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

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