PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.3.0
Defender Security – Malware Scanner, Login Security & Firewall v6.3.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
← All changes | src/controller/class-dashboard.php +345 -27 5.4.0 → 6.3.0 View file →
@@ -6,11 +6,16 @@
6 6 */
7 7
8 8 namespace WP_Defender\Controller;
9 9
10 +use WP_Defender\Component\Audit;
10 11 use WP_Defender\Event;
11 12 use Calotes\Helper\HTTP;
12 13 use Calotes\Helper\Route;
14 +use WP_Defender\Model\Setting\Login_Lockout;
15 +use WP_Defender\Model\Setting\Notfound_Lockout;
16 +use WP_Defender\Model\Setting\User_Agent_Lockout;
17 +use WP_Defender\Traits\Defender_Dashboard_Client;
13 18 use WP_Defender\Traits\IO;
14 19 use Calotes\Component\Request;
15 20 use Calotes\Component\Response;
16 21 use WP_Defender\Traits\Formats;
@@ -15,14 +20,16 @@
15 20 use Calotes\Component\Response;
16 21 use WP_Defender\Traits\Formats;
17 22 use WP_Defender\Behavior\WPMUDEV;
18 23 use WP_Defender\Component\Feature_Modal;
19 -use WP_Defender\Controller\Hub_Connector;
24 +use WP_Defender\Component\Hub_Connector as Hub_Connector_Component;
25 +use WP_Defender\Model\Setting\Audit_Logging as Audit_Logging_Settings;
26 +use WP_Defender\Model\Audit_Log;
20 27 use WP_Defender\Model\Setting\Global_Ip_Lockout;
21 28 use WP_Defender\Component\Config\Config_Hub_Helper;
22 29 use WP_Defender\Component\IP\Global_IP as Global_IP_Component;
23 -use WP_Defender\Controller\Antibot_Global_Firewall;
24 30 use WP_Defender\Model\Setting\Session_Protection;
31 +use WP_Defender\Controller\Session_Protection as Session_Protection_Controller;
25 32
26 33 /**
27 34 * Handles the main admin page.
28 35 */
@@ -29,8 +36,9 @@
29 36 class Dashboard extends Event {
30 37
31 38 use IO;
32 39 use Formats;
40 + use Defender_Dashboard_Client;
33 41
34 42 /**
35 43 * The slug identifier for this controller.
36 44 *
@@ -38,8 +46,18 @@
38 46 */
39 47 public $slug = 'wp-defender';
40 48
41 49 /**
50 + * Site option key for the one-time report-schedule upgrade notice.
51 + */
52 + public const REPORT_SCHEDULE_NOTICE_OPTION = 'wd_show_report_schedule_notice';
53 +
54 + /**
55 + * Site-wide dismissal key for the Dashboard plugin required modal.
56 + */
57 + public const DASHBOARD_REQUIRED_NOTICE_OPTION = 'wpdef_dashboard_required_notice_dismissed';
58 +
59 + /**
42 60 * Initializes the model and service, registers routes, and sets up scheduled events if the model is active.
43 61 */
44 62 public function __construct() {
45 63 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
@@ -47,8 +65,9 @@
47 65 $this->register_routes();
48 66 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
49 67 add_filter( 'custom_menu_order', '__return_true' );
50 68 add_filter( 'menu_order', array( $this, 'menu_order' ) );
69 + add_filter( 'plugins_api', array( $this, 'filter_dashboard_plugin_info' ), 101, 3 );
51 70 add_action( 'admin_init', array( $this, 'maybe_redirect_notification_request' ), 99 );
52 71 }
53 72
54 73 /**
@@ -53,11 +72,11 @@
53 72
54 73 /**
55 74 * Because we move the notifications on separate modules, so links from HUB should be redirected to correct URL.
56 75 *
57 - * @return void|null
76 + * @return void
58 77 */
59 - public function maybe_redirect_notification_request() {
78 + public function maybe_redirect_notification_request(): void {
60 79 $page = HTTP::get( 'page' );
61 80 if ( ! in_array( $page, array( 'wdf-scan', 'wdf-ip-lockout', 'wdf-hardener', 'wdf-logging' ), true ) ) {
62 81 return;
63 82 }
@@ -77,11 +96,12 @@
77 96 */
78 97 public function menu_order( $menu_order ) {
79 98 global $submenu;
80 99 if ( isset( $submenu['wp-defender'] ) ) {
81 - $defender_menu = $submenu['wp-defender'];
82 - $defender_menu[0][0] = esc_html__( 'Dashboard', 'defender-security' );
83 - $defender_menu = array_values( $defender_menu );
100 + $defender_menu = $submenu['wp-defender'];
101 + $defender_menu[0][0] = esc_html__( 'Dashboard', 'defender-security' );
102 + $defender_menu = array_values( $defender_menu );
103 + // Change the global $submenu variable, because otherwise the menu name/order will not change.
84 104 $submenu['wp-defender'] = $defender_menu; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
85 105 }
86 106
87 107 global $menu;
@@ -88,12 +108,13 @@
88 108 // Get the total scanning active issues.
89 109 $count = wd_di()->get( \WP_Defender\Component\Scan::class )->indicator_issue_count();
90 110
91 111 $indicator = $count > 0
92 - ? ' <span class="update-plugins wd-issue-indicator-sidebar"><span class="plugin-count">' . $count . '</span></span>'
112 + ? ' <span class="update-plugins wd-issue-indicator-sidebar"></span>'
93 113 : null;
94 114 foreach ( $menu as $k => $item ) {
95 115 if ( 'wp-defender' === $item[2] ) {
116 + // Add a badge next to the "Defender" menu item in the global $menu variable.
96 117 $menu[ $k ][0] .= $indicator; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
97 118 }
98 119 }
99 120
@@ -104,16 +125,14 @@
104 125 * Registers the main page in the WordPress admin menu.
105 126 */
106 127 protected function add_main_page() {
107 128 $this->register_page(
108 - $this->get_menu_title(),
129 + $this->get_page_title(),
109 130 $this->parent_slug,
110 - array(
111 - &$this,
112 - 'main_view',
113 - ),
131 + array( $this, 'main_view' ),
114 132 null,
115 - $this->get_menu_icon()
133 + $this->get_menu_icon(),
134 + $this->get_menu_title()
116 135 );
117 136 }
118 137
119 138 /**
@@ -123,8 +142,68 @@
123 142 $this->render( 'main' );
124 143 }
125 144
126 145 /**
146 + * Page-specific data for a concrete controller.
147 + *
148 + * @return array
149 + */
150 + protected function get_page_data(): array {
151 + $security_tweaks = wd_di()->get( \WP_Defender\Controller\Security_Tweaks::class );
152 + $security_tweaks->refresh_tweaks_status();
153 + $security_tweaks_data = $security_tweaks->dashboard_widget();
154 +
155 + $audit_model = wd_di()->get( Audit_Logging_Settings::class );
156 + $enabled_audit = $audit_model->is_active();
157 + // Audit summary.
158 + $audit_events_logged = 0;
159 + $last_event_time = esc_html__( '-', 'defender-security' );
160 + if ( $enabled_audit ) {
161 + $audit_events_logged = Audit::get_audit_events_per_week();
162 + $audit_last = Audit_Log::get_last();
163 + if ( is_object( $audit_last ) ) {
164 + $last_event_time = $this->get_date( $audit_last->timestamp );
165 + }
166 + }
167 +
168 + // Firewall summary.
169 + $firewall = wd_di()->get( Firewall::class )->get_summary();
170 + // Different lockout types.
171 + $enabled_login = wd_di()->get( Login_Lockout::class )->enabled;
172 + $enabled_nf = wd_di()->get( Notfound_Lockout::class )->enabled;
173 + $enabled_ua = wd_di()->get( User_Agent_Lockout::class )->enabled;
174 +
175 + return array(
176 + 'defenderSetupNonce' => wp_create_nonce( 'defender_quick_setup' ),
177 + 'securityTweaks' => $security_tweaks_data['summary']['issues_count'],
178 + 'scanData' => array(
179 + 'numberIssues' => wd_di()->get( \WP_Defender\Component\Scan::class )->indicator_issue_count(),
180 + 'settings' => wd_di()->get( \WP_Defender\Model\Setting\Scan::class )->export(),
181 + // Scan routes & nonces are set above.
182 + ),
183 + 'firewallData' => array(
184 + 'enabledLocalFirewall' => $enabled_login || $enabled_nf || $enabled_ua,
185 + 'enabledLogin' => $enabled_login,
186 + 'enabledNotFound' => $enabled_nf,
187 + 'enabledUserAgent' => $enabled_ua,
188 + 'loginLockoutMonth' => $firewall['lockout_login_this_month'],
189 + 'nfLockoutMonth' => $firewall['lockout_404_this_month'],
190 + 'uaLockoutMonth' => $firewall['lockout_ua_this_month'],
191 + 'antibot' => wd_di()->get( Antibot_Global_Firewall::class )->data_frontend(),
192 + ),
193 + 'site_id' => wd_di()->get( WPMUDEV::class )->get_site_id(),
194 + 'auditData' => array(
195 + 'enabled' => $enabled_audit,
196 + 'eventsLogged' => $audit_events_logged,
197 + 'lastEvent' => $last_event_time,
198 + ),
199 + 'sessionProtection' => wd_di()->get( Session_Protection::class )->export(),
200 + 'showReportScheduleNotice' => ! defender_is_wp_org_version()
201 + && (bool) get_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION, false ),
202 + );
203 + }
204 +
205 + /**
127 206 * Enqueues scripts and styles for this page.
128 207 * Only enqueues assets if the page is active.
129 208 */
130 209 public function enqueue_assets() {
@@ -130,18 +209,197 @@
130 209 public function enqueue_assets() {
131 210 if ( ! $this->is_page_active() ) {
132 211 return;
133 212 }
213 +
214 + $wizard_action = HTTP::get( 'wizard_action' );
215 + $wizard_source = HTTP::get( 'source' );
216 + $nonce = HTTP::get( '_wpnonce' );
217 +
218 + // Fallback completion marker for setup wizard integrations rendered on dashboard.
219 + // This prevents onboarding loops if client-side completion request fails.
220 + if (
221 + 'setup_wizard' === $wizard_source
222 + && in_array( $wizard_action, array( 'view_results', 'close_wizard', 'finish_wizard' ), true )
223 + && current_user_can( 'manage_options' )
224 + && is_string( $nonce )
225 + && false !== wp_verify_nonce( $nonce, 'wpdef_setup_wizard' )
226 + ) {
227 + update_site_option( 'wp_defender_shown_activator', true );
228 + }
229 +
230 + $show_onboarding = \WP_Defender\Model\Onboard::maybe_show_onboarding();
231 + $api_error = HTTP::get( 'api_error' );
232 + $hub_connection_source = HTTP::get( 'hub_connection_source' );
233 + $hub_connect_recovery = ! $show_onboarding
234 + && is_string( $api_error )
235 + && '' !== $api_error
236 + && 'profile_menu' !== $hub_connection_source
237 + && ! Hub_Connector_Component::is_logged_in()
238 + && ! Hub_Connector_Component::is_wpmudev_dashboard_connected();
239 +
240 + if ( $show_onboarding || $hub_connect_recovery ) {
241 + add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
242 + }
243 +
244 + $handle = 'defender-ui-dashboard';
245 + wp_enqueue_script(
246 + $handle,
247 + WP_DEFENDER_BASE_URL . 'assets/js/dashboard-ui.js',
248 + array( 'def-vue', 'def-manifest', 'def-core-ui', 'defender', 'wp-i18n' ),
249 + DEFENDER_VERSION,
250 + true
251 + );
252 + wp_set_script_translations( $handle, 'wpdef' );
253 +
254 + $setup_wizard_data = wd_di()->get( Setup_Wizard::class )->data_frontend();
255 + $tracking_data = wd_di()->get( Data_Tracking::class )->get_dashboard_notice_data();
256 + $dashboard_data = $this->dump_routes_and_nonces();
257 + $firewall_data = wd_di()->get( Firewall::class )->dump_routes_and_nonces();
258 + $scan_data = wd_di()->get( \WP_Defender\Controller\Scan::class )->dump_routes_and_nonces();
259 + $routes = array_merge(
260 + $setup_wizard_data['routes'] ?? array(),
261 + $tracking_data['routes'] ?? array(),
262 + $dashboard_data['routes'] ?? array(),
263 + $firewall_data['routes'] ?? array(),
264 + $scan_data['routes'] ?? array(),
265 + );
266 + $nonces = array_merge(
267 + $setup_wizard_data['nonces'] ?? array(),
268 + $tracking_data['nonces'] ?? array(),
269 + $dashboard_data['nonces'] ?? array(),
270 + $firewall_data['nonces'] ?? array(),
271 + $scan_data['nonces'] ?? array(),
272 + );
273 + unset( $setup_wizard_data['routes'], $setup_wizard_data['nonces'] );
274 + unset( $tracking_data['routes'], $tracking_data['nonces'] );
275 + unset( $firewall_data['routes'], $firewall_data['nonces'] );
134 276 wp_localize_script(
135 - 'def-dashboard',
136 - 'dashboard',
137 - array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
277 + $handle,
278 + 'defenderUIData',
279 + array_merge(
280 + $this->get_shared_data(),
281 + $this->get_page_data(),
282 + // Welcome modal's details.
283 + wd_di()->get( Feature_Modal::class )->get_dashboard_modals(),
284 + // Specific data.
285 + array(
286 + 'showOnboarding' => $show_onboarding,
287 + 'dashboardRequiredNotice' => $this->get_dashboard_required_notice_data(),
288 + 'routes' => $routes,
289 + 'nonces' => $nonces,
290 + ),
291 + $setup_wizard_data,
292 + $tracking_data,
293 + wd_di()->get( \WP_Defender\Controller\Scan::class )->get_initial_scan_data()
294 + )
138 295 );
139 - wp_enqueue_script( 'def-dashboard' );
296 +
297 + wp_enqueue_style(
298 + $handle,
299 + WP_DEFENDER_BASE_URL . 'assets/css/core-ui.css',
300 + array(),
301 + DEFENDER_VERSION
302 + );
303 +
140 304 $this->enqueue_main_assets();
141 305 }
142 306
143 307 /**
308 + * Get the state and action URLs for the Dashboard plugin required modal.
309 + *
310 + * @return array
311 + */
312 + private function get_dashboard_required_notice_data(): array {
313 + return array(
314 + 'isProPlugin' => WP_DEFENDER_PRO_PATH === DEFENDER_PLUGIN_BASENAME,
315 + 'dashboardActive' => $this->is_dash_activated(),
316 + 'dashboardInstalled' => $this->is_dash_installed(),
317 + 'dashboardPageUrl' => network_admin_url( 'admin.php?page=wpmudev' ),
318 + 'activateUrl' => add_query_arg(
319 + array(
320 + '_wpnonce' => wp_create_nonce( 'activate-plugin_wpmudev-updates/update-notifications.php' ),
321 + 'action' => 'activate',
322 + 'plugin' => 'wpmudev-updates/update-notifications.php',
323 + ),
324 + network_admin_url( 'plugins.php' )
325 + ),
326 + 'installUrl' => add_query_arg(
327 + array(
328 + '_wpnonce' => wp_create_nonce( 'install-plugin_install_wpmudev_dash' ),
329 + 'action' => 'install-plugin',
330 + 'plugin' => 'install_wpmudev_dash',
331 + ),
332 + network_admin_url( 'update.php' )
333 + ),
334 + 'dismissed' => (bool) get_site_option( self::DASHBOARD_REQUIRED_NOTICE_OPTION, false ),
335 + );
336 + }
337 +
338 + /**
339 + * Supply WordPress with the WPMU DEV Dashboard package details.
340 + *
341 + * @param mixed $result Existing Plugins API result.
342 + * @param string $action Requested Plugins API action.
343 + * @param object $args Requested plugin arguments.
344 + *
345 + * @return mixed
346 + */
347 + public function filter_dashboard_plugin_info( $result, $action, $args ) {
348 + if (
349 + 'plugin_information' !== $action
350 + || ! is_object( $args )
351 + || ! isset( $args->slug )
352 + || '' === trim( (string) $args->slug )
353 + || false === strpos( $args->slug, 'install_wpmudev_dash' )
354 + ) {
355 + return $result;
356 + }
357 +
358 + $plugin = new \stdClass();
359 + $plugin->name = 'WPMU DEV Dashboard';
360 + $plugin->slug = 'wpmu-dev-dashboard';
361 + $plugin->version = '';
362 + $plugin->rating = 100;
363 + $plugin->homepage = 'https://wpmudev.com/project/wpmu-dev-dashboard/';
364 + $plugin->download_link = 'https://wpmudev.com/api/dashboard/v1/download-dashboard';
365 + $plugin->tested = get_bloginfo( 'version' );
366 +
367 + return $plugin;
368 + }
369 +
370 + /**
371 + * Adds onboarding body classes on dashboard when onboarding wizard is active.
372 + *
373 + * @param string $classes Existing admin body classes.
374 + *
375 + * @return string
376 + */
377 + public function admin_body_class( $classes ): string {
378 + $classes .= ' wdf-onboarding-active ';
379 +
380 + return $classes;
381 + }
382 +
383 + /**
384 + * Returns the current hardening (security tweaks) issue count via AJAX.
385 + *
386 + * @return Response
387 + * @defender_route
388 + * @defender_redirect
389 + */
390 + public function get_hardening_count(): Response {
391 + $security_tweaks = wd_di()->get( \WP_Defender\Controller\Security_Tweaks::class )->dashboard_widget();
392 +
393 + return new Response(
394 + true,
395 + array(
396 + 'count' => (int) ( $security_tweaks['summary']['issues_count'] ?? 0 ),
397 + )
398 + );
399 + }
400 +
401 + /**
144 402 * Handles the request to hide new features modal.
145 403 *
146 404 * @param Request $request The request object containing data.
147 405 *
@@ -225,12 +483,74 @@
225 483 return new Response( true, array() );
226 484 }
227 485
228 486 /**
487 + * Dismiss the one-time report-schedule notice set during the 6.1.0 upgrade.
488 + *
489 + * @return Response
490 + * @defender_route
491 + */
492 + public function dismiss_report_schedule_notice(): Response {
493 + delete_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION );
494 +
495 + return new Response( true, array() );
496 + }
497 +
498 + /**
499 + * Permanently dismiss the Dashboard plugin required modal for this site.
500 + *
501 + * @return Response
502 + * @defender_route
503 + */
504 + public function dismiss_dashboard_required_notice(): Response {
505 + update_site_option( self::DASHBOARD_REQUIRED_NOTICE_OPTION, true );
506 +
507 + return new Response( true, array() );
508 + }
509 +
510 + /**
511 + * Toggle a dashboard feature by feature key.
512 + *
513 + * @param Request $request The current request data.
514 + *
515 + * @return Response
516 + * @defender_route
517 + */
518 + public function toggle_feature( Request $request ): Response {
519 + $data = $request->get_data(
520 + array(
521 + 'feature' => array(
522 + 'type' => 'string',
523 + 'sanitize' => 'sanitize_text_field',
524 + ),
525 + )
526 + );
527 + $feature = $data['feature'] ?? '';
528 +
529 + $feature_controllers = array(
530 + 'antibot' => Antibot_Global_Firewall::class,
531 + 'audit' => Audit_Logging::class,
532 + 'session_protection' => Session_Protection_Controller::class,
533 + );
534 + if ( isset( $feature_controllers[ $feature ] ) ) {
535 + return wd_di()->get( $feature_controllers[ $feature ] )->save_settings( $request );
536 + }
537 +
538 + return new Response(
539 + false,
540 + array(
541 + 'message' => esc_html__( 'Unsupported feature toggle request.', 'defender-security' ),
542 + )
543 + );
544 + }
545 +
546 + /**
229 547 * Removes settings for all submodules.
230 548 */
231 549 public function remove_settings() {
232 550 wd_di()->get( Feature_Modal::class )->upgrade_site_options();
551 +
552 + delete_site_option( self::REPORT_SCHEDULE_NOTICE_OPTION );
233 553 }
234 554
235 555 /**
236 556 * Delete all the data & the cache.
@@ -235,8 +555,9 @@
235 555 /**
236 556 * Delete all the data & the cache.
237 557 */
238 558 public function remove_data() {
559 + delete_site_option( self::DASHBOARD_REQUIRED_NOTICE_OPTION );
239 560 }
240 561
241 562 /**
242 563 * Provides data for the frontend.
@@ -251,23 +572,20 @@
251 572 wd_di()->get( Feature_Modal::class )->get_dashboard_modals(),
252 573 array(
253 574 'scan' => wd_di()->get( Scan::class )->data_frontend(),
254 575 'firewall' => $firewall->data_frontend(),
255 - 'waf' => wd_di()->get( WAF::class )->data_frontend(),
256 - 'audit' => wd_di()->get( Audit_Logging::class )->data_frontend(),
576 + 'blocklist_monitor' => wd_di()->get( Blocklist_Monitor::class )->data_frontend(),
257 577 'blacklist' => array(
258 578 'nonces' => $nonces,
259 579 'endpoints' => $endpoints,
260 580 ),
261 - 'blocklist_monitor' => wd_di()->get( Blocklist_Monitor::class )->data_frontend(),
262 581 'two_fa' => wd_di()->get( Two_Factor::class )->data_frontend(),
263 582 'advanced_tools' => array(
264 - 'mask_login' => wd_di()->get( Mask_Login::class )->dashboard_widget(),
265 - 'security_headers' => wd_di()->get( Security_Headers::class )->dashboard_widget(),
266 - 'pwned_passwords' => wd_di()->get( Password_Protection::class )->dashboard_widget(),
267 - 'recaptcha' => wd_di()->get( Recaptcha::class )->dashboard_widget(),
268 - 'strong_passwords' => wd_di()->get( Strong_Password::class )->dashboard_widget(),
269 - 'session_protection' => wd_di()->get( Session_Protection::class )->export(),
583 + 'mask_login' => wd_di()->get( Mask_Login::class )->dashboard_widget(),
584 + 'security_headers' => wd_di()->get( Security_Headers::class )->dashboard_widget(),
585 + 'pwned_passwords' => wd_di()->get( Password_Protection::class )->dashboard_widget(),
586 + 'captcha' => wd_di()->get( Captcha::class )->dashboard_widget(),
587 + 'strong_passwords' => wd_di()->get( Strong_Password::class )->dashboard_widget(),
270 588 ),
271 589 'security_tweaks' => wd_di()->get( Security_Tweaks::class )->dashboard_widget(),
272 590 'notifications' => wd_di()->get( Notification::class )->data_frontend(),
273 591 'settings' => wd_di()->get( Main_Setting::class )->data_frontend(),