PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.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 / class-admin.php

class-admin.php in Defender Security – Malware Scanner, Login Security & Firewall 5.3.1, at src/class-admin.php

581 lines 19.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles WordPress admin page related tasks.
4 *
5 * @package WP_Defender
6 */
7
8 namespace WP_Defender;
9
10 use WP_Defender\Component\Rate;
11 use WP_Defender\Behavior\WPMUDEV;
12 use WP_Defender\Component\Firewall;
13 use WP_Defender\Integrations\Dashboard_Whitelabel;
14 use WP_Defender\Component\Config\Config_Hub_Helper;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 die();
18 }
19
20 /**
21 * Handles WordPress admin page related tasks.
22 *
23 * @since 2.4
24 */
25 class Admin {
26
27 /**
28 * Is the Free version?
29 *
30 * @var bool
31 */
32 public $is_wp_org_version;
33
34 /**
35 * Constructor for the Admin class.
36 */
37 public function __construct() {
38 $this->is_wp_org_version = defender_is_wp_org_version();
39 add_action( 'wp_ajax_defender_ip_detection_notice_dismiss', array( $this, 'dismiss_notice' ) );
40 add_action( 'wp_ajax_defender_ip_detection_switch_to_xff', array( $this, 'switch_to_xff' ) );
41 add_action( 'admin_head', array( $this, 'add_global_styles' ) );
42 }
43
44 /**
45 * Add global styles.
46 */
47 public function add_global_styles() {
48 echo '<style>
49 #toplevel_page_wp-defender ul.wp-submenu li a[href="admin.php?page=wdf-ip-lockout"] { display: flex; justify-content: space-between; align-items: center; }
50 </style>';
51 }
52
53 /**
54 * Init admin actions.
55 */
56 public function init() {
57 // Display plugin links.
58 add_filter( 'network_admin_plugin_action_links_' . DEFENDER_PLUGIN_BASENAME, array( $this, 'settings_link' ) );
59 add_filter( 'plugin_action_links_' . DEFENDER_PLUGIN_BASENAME, array( $this, 'settings_link' ) );
60 add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 3 );
61 // Only for plugin pages and actions are only for wp.org members.
62 if ( $this->is_wp_org_version ) {
63 wd_di()->get( Rate::class )->init();
64 add_action( 'admin_init', array( $this, 'register_free_modules' ), 20 );
65 /**
66 * Action hook that fires after a scan issue is fixed.
67 *
68 * @since 4.4.0
69 */
70 add_action(
71 'wpdef_fixed_scan_issue',
72 array( $this, 'after_scan_fix' ),
73 10
74 );
75 // For submenu callout.
76 add_action( 'admin_head', array( $this, 'retarget_submenu_callout' ) );
77 if ( ! wd_di()->get( WPMUDEV::class )->is_wpmu_hosting() ) {
78 add_submenu_page(
79 'wp-defender',
80 esc_html__( 'Upgrade For 80% Off!', 'defender-security' ),
81 esc_html__( 'Upgrade For 80% Off!', 'defender-security' ),
82 is_multisite() ? 'manage_network_options' : 'manage_options',
83 $this->get_link( 'upsell', 'defender_submenu_upsell' )
84 );
85 }
86 }
87
88 // Display IP detection notice.
89 if ( is_multisite() ) {
90 add_action( 'network_admin_notices', array( $this, 'admin_notices' ) );
91 } else {
92 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
93 }
94 }
95
96 /**
97 * Retrieves the display name of the plugin.
98 *
99 * @return string The display name of the plugin with a trailing hyphen.
100 */
101 public function get_plugin_display_name(): string {
102 // Check if the plugin is the WordPress.org version (i.e., the free version) and set the label accordingly.
103 $plugin_label = $this->is_wp_org_version
104 ? esc_html__( 'Defender', 'defender-security' )
105 : esc_html__( 'Defender Pro', 'defender-security' );
106
107 // Instantiate the Dashboard_Whitelabel class only if necessary.
108 $whitelabel = new Dashboard_Whitelabel();
109
110 // If whitelabeling is enabled and a custom name is provided, use it.
111 if ( $whitelabel->can_whitelabel() ) {
112 $custom_label = $whitelabel->get_plugin_name( Config_Hub_Helper::WDP_ID );
113 if ( ! empty( $custom_label ) ) {
114 $plugin_label = $custom_label;
115 }
116 }
117
118 // Return the final plugin label with the appended dash.
119 return $plugin_label . ' - ';
120 }
121
122 /**
123 * The method is a stub without content.
124 */
125 private function menu_nope(): void {
126 }
127
128 /**
129 * Generates the submenu callout for the WP Defender plugin.
130 *
131 * @return void
132 */
133 public function retarget_submenu_callout(): void {
134 ?>
135 <style>
136 #toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"],
137 #toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]:hover,
138 #toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]:active,
139 #toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]:focus {
140 font-family: Roboto, sans-serif;
141 font-size: 12px;
142 background: #8D00B1;
143 color: #ffffff;
144 font-weight: 500;
145 }
146
147 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"],
148 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:hover,
149 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:active,
150 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:focus {
151 margin-left: -4px;
152 }
153 </style>
154 <script type='text/javascript'>
155 jQuery(function ($) {
156 $('#toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]').attr("target", "_blank");
157 });
158 </script>
159 <?php
160 }
161
162 /**
163 * Fired when the scan issue is fixed.
164 *
165 * @return void
166 */
167 public function after_scan_fix(): void {
168 Rate::run_counter_of_fixed_scans();
169 }
170
171 /**
172 * Return URL link.
173 *
174 * @param string $link_for Accepts: 'docs', 'plugin', 'rate' and etc.
175 * @param string $campaign Utm campaign tag to be used in link. Default: ''.
176 * @param string $adv_path Advanced path. Default: ''.
177 *
178 * @return string
179 */
180 public function get_link( $link_for, $campaign = '', $adv_path = '' ): string {
181 $domain = 'https://wpmudev.com';
182 $wp_org = 'https://wordpress.org';
183 $utm_tags = "?utm_source=defender&utm_medium=plugin&utm_campaign={$campaign}";
184 switch ( $link_for ) {
185 case 'docs':
186 $link = "{$domain}/docs/wpmu-dev-plugins/defender/{$utm_tags}";
187 break;
188 case 'plugin':
189 case 'upsell':
190 $link = "{$domain}/project/wp-defender/{$utm_tags}";
191 break;
192 case 'rate':
193 $link = "{$wp_org}/support/plugin/defender-security/reviews/#new-post";
194 break;
195 case 'support':
196 $link = $this->is_wp_org_version
197 ? "{$wp_org}/support/plugin/defender-security/"
198 : "{$domain}/get-support/";
199 break;
200 case 'roadmap':
201 $link = "{$domain}/roadmap/";
202 break;
203 case 'pro_link':
204 $link = "{$domain}/$adv_path";
205 break;
206 default:
207 $link = '';
208 break;
209 }
210
211 return $link;
212 }
213
214 /**
215 * Adds a settings link on plugin page.
216 *
217 * @param array $links Current links.
218 *
219 * @return array
220 */
221 public function settings_link( $links ) {
222 $action_links = array();
223 $wpmu_dev = new WPMUDEV();
224 // Dashboard-link.
225 $action_links['dashboard'] = '<a href="' . network_admin_url( 'admin.php?page=wp-defender' ) . '" aria-label="' . esc_attr(
226 esc_html__(
227 'Go to Defender Dashboard',
228 'defender-security'
229 )
230 ) . '">' . esc_html__( 'Dashboard', 'defender-security' ) . '</a>';
231 // Documentation-link.
232 $action_links['docs'] = '<a target="_blank" href="' . $this->get_link(
233 'docs',
234 'defender_pluginlist_docs'
235 ) . '" aria-label="' . esc_attr(
236 esc_html__(
237 'Docs',
238 'defender-security'
239 )
240 ) . '">' . esc_html__( 'Docs', 'defender-security' ) . '</a>';
241 if ( ! $wpmu_dev->is_member() ) {
242 if ( WP_DEFENDER_PRO_PATH !== DEFENDER_PLUGIN_BASENAME ) {
243 if ( ! wd_di()->get( WPMUDEV::class )->is_wpmu_hosting() ) {
244 $action_links['upgrade'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
245 'plugin',
246 'defender_pluginlist_upgrade'
247 ) . '" aria-label="' . esc_attr(
248 esc_html__(
249 'Upgrade to Defender Pro',
250 'defender-security'
251 )
252 ) . '">' . esc_html__( 'Upgrade For 80% Off!', 'defender-security' ) . '</a>';
253 }
254 } elseif ( ! $wpmu_dev->is_hosted_site_connected_to_tfh() ) {
255 $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
256 'plugin',
257 'defender_pluginlist_renew'
258 ) . '" aria-label="' . esc_attr(
259 esc_html__(
260 'Renew Your Membership',
261 'defender-security'
262 )
263 ) . '">' . esc_html__( 'Renew Membership', 'defender-security' ) . '</a>';
264 }
265 }
266
267 return array_merge( $action_links, $links );
268 }
269
270 /**
271 * Show row meta on the plugin screen.
272 *
273 * @param string[] $links Plugin Row Meta.
274 * @param string $file Plugin Base file.
275 * @param array $plugin_data Plugin data.
276 *
277 * @return array
278 */
279 public function plugin_row_meta( $links, $file, $plugin_data ) {
280 $row_meta = array();
281 if ( ! defined( 'DEFENDER_PLUGIN_BASENAME' ) || DEFENDER_PLUGIN_BASENAME !== $file ) {
282 return $links;
283 }
284
285 // Change AuthorURI link.
286 if ( isset( $links[1] ) ) {
287 $author_uri = $this->is_wp_org_version ? 'https://profiles.wordpress.org/wpmudev/' : 'https://wpmudev.com/';
288 $author_uri = sprintf(
289 '<a href="%s" target="_blank">%s</a>',
290 $author_uri,
291 esc_html__( 'WPMU DEV', 'defender-security' )
292 );
293 $links[1] = sprintf(
294 /* translators: %s: Author URI. */
295 esc_html__( 'By %s', 'defender-security' ),
296 $author_uri
297 );
298 }
299
300 if ( $this->is_wp_org_version ) {
301 // Change AuthorURI link.
302 if ( isset( $links[2] ) && false === strpos( $links[2], 'target="_blank"' ) ) {
303 if ( ! isset( $plugin_data['slug'] ) && $plugin_data['Name'] ) {
304 $links[2] = sprintf(
305 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
306 esc_url(
307 network_admin_url(
308 'plugin-install.php?tab=plugin-information&plugin=defender-security&TB_iframe=true&width=600&height=550'
309 )
310 ),
311 /* translators: %s: Plugin name. */
312 esc_attr( sprintf( esc_html__( 'More information about %s', 'defender-security' ), $plugin_data['Name'] ) ),
313 esc_attr( $plugin_data['Name'] ),
314 esc_html__( 'View details', 'defender-security' )
315 );
316 } else {
317 $links[2] = str_replace( 'href=', 'target="_blank" href=', $links[2] );
318 }
319 }
320 $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__(
321 'Rate Defender',
322 'defender-security'
323 ) . '" target="_blank">' . esc_html__( 'Rate Defender', 'defender-security' ) . '</a>';
324 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
325 'Support',
326 'defender-security'
327 ) . '" target="_blank">' . esc_html__( 'Support', 'defender-security' ) . '</a>';
328 } else {
329 // Change 'Visit plugins' link to 'View details'.
330 if ( isset( $links[2] ) && false !== strpos( $links[2], 'project/wp-defender' ) ) {
331 $links[2] = sprintf(
332 '<a href="%s" target="_blank">%s</a>',
333 esc_url( $this->get_link( 'pro_link', '', 'project/wp-defender/' ) ),
334 esc_html__( 'View details', 'defender-security' )
335 );
336 }
337 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
338 'Premium Support',
339 'defender-security'
340 ) . '" target="_blank">' . esc_html__( 'Premium Support', 'defender-security' ) . '</a>';
341 }
342 $row_meta['roadmap'] = '<a href="' . esc_url( $this->get_link( 'roadmap' ) ) . '" aria-label="' . esc_attr__(
343 'Roadmap',
344 'defender-security'
345 ) . '" target="_blank">' . esc_html__( 'Roadmap', 'defender-security' ) . '</a>';
346
347 return array_merge( $links, $row_meta );
348 }
349
350 /**
351 * Register sub-modules.
352 */
353 public function register_free_modules() {
354 $module_path = defender_path( 'extra/free-dashboard/module.php' );
355 if ( ! file_exists( $module_path ) ) {
356 return;
357 }
358 /* @noinspection PhpIncludeInspection */
359 require_once $module_path;
360 // Register the current plugin.
361 do_action(
362 'wdev_register_plugin',
363 /* 1 Plugin ID */ DEFENDER_PLUGIN_BASENAME,
364 /* 2 Plugin Title */ 'Defender',
365 /* 3 https://wordpress.org */ '/plugins/defender-security/',
366 /* 4 Email Button CTA */ esc_html__( 'Get Fast!', 'defender-security' )
367 );
368 // Recommended plugin notice.
369 $this->register_recommended_plugin_notice();
370 }
371
372 /**
373 * Register the recommended plugin notice.
374 *
375 * @return void
376 */
377 protected function register_recommended_plugin_notice() {
378 if ( ! file_exists( defender_path( 'extra/recommended-plugins-notice/notice.php' ) ) ) {
379 return;
380 }
381 /* @noinspection PhpIncludeInspection */
382 require_once defender_path( 'extra/recommended-plugins-notice/notice.php' );
383
384 do_action(
385 // It's from the extra WPMUDEV_Recommended_Plugins_Notice_Registered_Plugin package.
386 'wpmudev-recommended-plugins-register-notice', // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
387 DEFENDER_PLUGIN_BASENAME, // Plugin basename.
388 'Defender', // Plugin Name.
389 array(
390 'toplevel_page_wp-defender',
391 'toplevel_page_wp-defender-network',
392 ),
393 array( 'after', '.sui-wrap .sui-header' )
394 );
395 }
396
397 /**
398 * Display IP detection notices for if user site is behind proxy, e.g. Cloudflare or something else, and only for admins.
399 *
400 * @return void
401 */
402 public function admin_notices(): void {
403 if ( ! current_user_can( 'manage_options' ) ) {
404 return;
405 }
406 $header = $this->get_plugin_display_name();
407 if ( Firewall::is_cf_notice_ready() ) {
408 $is_show = 'cf';
409 $class_notice = 'notice-info';
410 $header .= esc_html__(
411 'Cloudflare Usage Detected: Switched to CF-Connecting-IP for Better Compatibility',
412 'defender-security'
413 );
414 } elseif ( Firewall::is_xff_notice_ready() ) {
415 $is_show = 'xff';
416 $class_notice = 'notice-warning';
417 $header .= esc_html__(
418 'Improve IP Detection: We suggest Switching to X-Forward-For IP Detection Method',
419 'defender-security'
420 );
421 } else {
422 return;
423 }
424 ?>
425 <div class="defender_ip_detection_notice notice <?php echo esc_attr( $class_notice ); ?> is-dismissible"
426 data-nonce="<?php echo esc_attr( wp_create_nonce( 'defender_ip_detection_notice_dismiss' ) ); ?>"
427 data-prop="notice-for-<?php echo esc_attr( $is_show ); ?>">
428 <h3 style="margin-bottom:0;">
429 <?php echo esc_html( $header ); ?>
430 </h3>
431 <?php if ( 'cf' === $is_show ) { ?>
432 <p style="color: #72777C; line-height: 22px;">
433 <?php
434 printf(
435 /* translators: %s: Link. */
436 esc_html__(
437 'We have switched to using the CF-Connecting-IP HTTP header for IP detection, offering enhanced compatibility for users behind Cloudflare Proxy. If you wish to change this setting, you can do so from %s.',
438 'defender-security'
439 ),
440 '<a style="font-weight:bold;" href="' . esc_url_raw( network_admin_url( 'admin.php?page=wdf-ip-lockout&view=settings#detect-ip-addresses' ) ) . '">' . esc_html__( 'here', 'defender-security' ) . '</a>'
441 );
442 ?>
443 </p>
444 <p>
445 <button type="button" class="button button-primary button-large defender_ip_detection_action_hide"
446 data-prop="defender_ip_detection_notice_success">
447 <?php
448 esc_html_e(
449 'Ok, I understand',
450 'defender-security'
451 );
452 ?>
453 </button>
454 </p>
455 <?php } elseif ( 'xff' === $is_show ) { ?>
456 <p style="color: #72777C; line-height: 22px;">
457 <?php
458 printf(
459 /* translators: %s: Link. */
460 esc_html__(
461 'Based on your server configuration, we recommend switching to the X-Forwarded-For method for accurate IP detection and to prevent firewall blocks. Easily modify your settings %s.',
462 'defender-security'
463 ),
464 '<a style="font-weight:bold;" href="' . esc_url_raw( network_admin_url( 'admin.php?page=wdf-ip-lockout&view=settings#detect-ip-addresses' ) ) . '">' . esc_html__( 'here', 'defender-security' ) . '</a>'
465 );
466 ?>
467 </p>
468 <p>
469 <button type="button" class="button button-primary button-large"
470 id="defender_ip_detection_action_switch"
471 data-prop="defender_ip_detection_notice_success">
472 <?php esc_html_e( 'Switch to X-Forwarded-For', 'defender-security' ); ?>
473 </button>
474 <a href="#" class="defender_ip_detection_action_hide"
475 style="margin-left: 11px; line-height: 16px; text-decoration: none; font-weight: bold;"
476 data-prop="defender_ip_detection_notice_dismiss"><?php esc_html_e( 'Dismiss', 'defender-security' ); ?></a>
477 </p>
478 <?php } ?>
479 </div>
480 <script type="text/javascript">
481 //Switch.
482 jQuery('#defender_ip_detection_action_switch').on('click', function (e) {
483 e.preventDefault();
484 var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
485 ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
486
487 jQuery.post(
488 ajaxUrl,
489 {
490 action: 'defender_ip_detection_switch_to_xff',
491 _ajax_nonce: $notice.data('nonce')
492 }
493 ).always(function () {
494 $notice.hide();
495 });
496 });
497 //Hide.
498 jQuery('body').on('click', '.defender_ip_detection_notice .notice-dismiss, .defender_ip_detection_action_hide', function (e) {
499 e.preventDefault();
500 var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
501 ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
502
503 jQuery.post(
504 ajaxUrl,
505 {
506 action: 'defender_ip_detection_notice_dismiss',
507 prop: $notice.data('prop'),
508 _ajax_nonce: $notice.data('nonce')
509 }
510 ).always(function () {
511 $notice.hide();
512 });
513 });
514 </script>
515 <?php
516 }
517
518 /**
519 * Dismiss notice.
520 *
521 * @return void
522 */
523 public function dismiss_notice(): void {
524 if (
525 ! current_user_can( 'manage_options' ) ||
526 ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
527 ) {
528 wp_send_json_error(
529 array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
530 );
531 }
532
533 $prop = defender_get_data_from_request( 'prop', 'p' );
534 $notice_type = ! empty( $prop ) ? $prop : false;
535 if ( 'notice-for-cf' === $notice_type ) {
536 update_site_option( Firewall::IP_DETECTION_CF_DISMISS_SLUG, true );
537 wp_send_json_success();
538 } elseif ( 'notice-for-xff' === $notice_type ) {
539 update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
540 wp_send_json_success();
541 } else {
542 wp_send_json_error(
543 array( 'message' => esc_html__( 'Invalid request, allowed data not provided.', 'defender-security' ) )
544 );
545 }
546 }
547
548 /**
549 * Switch to XFF option.
550 *
551 * @return void
552 */
553 public function switch_to_xff(): void {
554 if (
555 ! current_user_can( 'manage_options' ) ||
556 ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
557 ) {
558 wp_send_json_error(
559 array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
560 );
561 }
562 // Change model's data.
563 $model_firewall = wd_di()->get( Model\Setting\Firewall::class );
564 $model_firewall->http_ip_header = 'HTTP_X_FORWARDED_FOR';
565 $xff_ip = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
566 if ( empty( $model_firewall->trusted_proxies_ip ) ) {
567 $model_firewall->trusted_proxies_ip = $xff_ip;
568 } else {
569 // Todo: improve the code using a separate method. This will be useful when the user switches between different proxy headeres (IP detection options).
570 $separator = "\r\n";
571 // Check if the XFF header contains multiple IPs.
572 $xff_ip = str_replace( array( ',', ' ,' ), $separator, $xff_ip );
573 $model_firewall->trusted_proxies_ip = $model_firewall->trusted_proxies_ip . $separator . $xff_ip;
574 }
575 $model_firewall->save();
576 // Save Dismiss slug.
577 update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
578 wp_send_json_success();
579 }
580 }
581