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

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

552 lines 18.2 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__( 'Limited-time offer!', 'defender-security' ),
81 esc_html__( 'Limited-time offer!', '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 background: #8D00B1;
141 color: #ffffff;
142 font-weight: 500;
143 }
144
145 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"],
146 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:hover,
147 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:active,
148 #toplevel_page_wp-defender.wp-not-current-submenu > ul > li:last-child > a[href^="https://wpmudev.com/"]:focus {
149 margin-left: -4px;
150 }
151 </style>
152 <script type='text/javascript'>
153 jQuery(function ($) {
154 $('#toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]').attr("target", "_blank");
155 });
156 </script>
157 <?php
158 }
159
160 /**
161 * Fired when the scan issue is fixed.
162 *
163 * @return void
164 */
165 public function after_scan_fix(): void {
166 Rate::run_counter_of_fixed_scans();
167 }
168
169 /**
170 * Return URL link.
171 *
172 * @param string $link_for Accepts: 'docs', 'plugin', 'rate' and etc.
173 * @param string $campaign Utm campaign tag to be used in link. Default: ''.
174 * @param string $adv_path Advanced path. Default: ''.
175 *
176 * @return string
177 */
178 public function get_link( $link_for, $campaign = '', $adv_path = '' ): string {
179 $domain = 'https://wpmudev.com';
180 $wp_org = 'https://wordpress.org';
181 $utm_tags = "?utm_source=defender&utm_medium=plugin&utm_campaign={$campaign}";
182 switch ( $link_for ) {
183 case 'docs':
184 $link = "{$domain}/docs/wpmu-dev-plugins/defender/{$utm_tags}";
185 break;
186 case 'plugin':
187 case 'upsell':
188 $link = "{$domain}/project/wp-defender/{$utm_tags}";
189 break;
190 case 'rate':
191 $link = "{$wp_org}/support/plugin/defender-security/reviews/#new-post";
192 break;
193 case 'support':
194 $link = $this->is_wp_org_version
195 ? "{$wp_org}/support/plugin/defender-security/"
196 : "{$domain}/get-support/";
197 break;
198 case 'roadmap':
199 $link = "{$domain}/roadmap/";
200 break;
201 case 'pro_link':
202 $link = "{$domain}/$adv_path";
203 break;
204 default:
205 $link = '';
206 break;
207 }
208
209 return $link;
210 }
211
212 /**
213 * Adds a settings link on plugin page.
214 *
215 * @param array $links Current links.
216 *
217 * @return array
218 */
219 public function settings_link( $links ) {
220 $action_links = array();
221 $wpmu_dev = new WPMUDEV();
222 // Dashboard-link.
223 $action_links['dashboard'] = '<a href="' . network_admin_url( 'admin.php?page=wp-defender' ) . '" aria-label="' . esc_attr(
224 esc_html__(
225 'Go to Defender Dashboard',
226 'defender-security'
227 )
228 ) . '">' . esc_html__( 'Dashboard', 'defender-security' ) . '</a>';
229 // Documentation-link.
230 $action_links['docs'] = '<a target="_blank" href="' . $this->get_link(
231 'docs',
232 'defender_pluginlist_docs'
233 ) . '" aria-label="' . esc_attr(
234 esc_html__(
235 'Docs',
236 'defender-security'
237 )
238 ) . '">' . esc_html__( 'Docs', 'defender-security' ) . '</a>';
239 if ( ! $wpmu_dev->is_member() ) {
240 if ( WP_DEFENDER_PRO_PATH !== DEFENDER_PLUGIN_BASENAME ) {
241 if ( ! wd_di()->get( WPMUDEV::class )->is_wpmu_hosting() ) {
242 $action_links['upgrade'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
243 'plugin',
244 'defender_pluginlist_upgrade'
245 ) . '" aria-label="' . esc_attr(
246 esc_html__(
247 'Upgrade to Defender Pro',
248 'defender-security'
249 )
250 ) . '">' . esc_html__( 'Limited-time offer!', 'defender-security' ) . '</a>';
251 }
252 } elseif ( ! $wpmu_dev->is_hosted_site_connected_to_tfh() ) {
253 $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
254 'plugin',
255 'defender_pluginlist_renew'
256 ) . '" aria-label="' . esc_attr(
257 esc_html__(
258 'Renew Your Membership',
259 'defender-security'
260 )
261 ) . '">' . esc_html__( 'Renew Membership', 'defender-security' ) . '</a>';
262 }
263 }
264
265 return array_merge( $action_links, $links );
266 }
267
268 /**
269 * Show row meta on the plugin screen.
270 *
271 * @param string[] $links Plugin Row Meta.
272 * @param string $file Plugin Base file.
273 * @param array $plugin_data Plugin data.
274 *
275 * @return array
276 */
277 public function plugin_row_meta( $links, $file, $plugin_data ) {
278 $row_meta = array();
279 if ( ! defined( 'DEFENDER_PLUGIN_BASENAME' ) || DEFENDER_PLUGIN_BASENAME !== $file ) {
280 return $links;
281 }
282
283 // Change AuthorURI link.
284 if ( isset( $links[1] ) ) {
285 $author_uri = $this->is_wp_org_version ? 'https://profiles.wordpress.org/wpmudev/' : 'https://wpmudev.com/';
286 $author_uri = sprintf(
287 '<a href="%s" target="_blank">%s</a>',
288 $author_uri,
289 esc_html__( 'WPMU DEV', 'defender-security' )
290 );
291 $links[1] = sprintf(
292 /* translators: %s: Author URI. */
293 esc_html__( 'By %s', 'defender-security' ),
294 $author_uri
295 );
296 }
297
298 if ( $this->is_wp_org_version ) {
299 // Change AuthorURI link.
300 if ( isset( $links[2] ) && false === strpos( $links[2], 'target="_blank"' ) ) {
301 if ( ! isset( $plugin_data['slug'] ) && $plugin_data['Name'] ) {
302 $links[2] = sprintf(
303 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
304 esc_url(
305 network_admin_url(
306 'plugin-install.php?tab=plugin-information&plugin=defender-security&TB_iframe=true&width=600&height=550'
307 )
308 ),
309 /* translators: %s: Plugin name. */
310 esc_attr( sprintf( esc_html__( 'More information about %s', 'defender-security' ), $plugin_data['Name'] ) ),
311 esc_attr( $plugin_data['Name'] ),
312 esc_html__( 'View details', 'defender-security' )
313 );
314 } else {
315 $links[2] = str_replace( 'href=', 'target="_blank" href=', $links[2] );
316 }
317 }
318 $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__(
319 'Rate Defender',
320 'defender-security'
321 ) . '" target="_blank">' . esc_html__( 'Rate Defender', 'defender-security' ) . '</a>';
322 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
323 'Support',
324 'defender-security'
325 ) . '" target="_blank">' . esc_html__( 'Support', 'defender-security' ) . '</a>';
326 } else {
327 // Change 'Visit plugins' link to 'View details'.
328 if ( isset( $links[2] ) && false !== strpos( $links[2], 'project/wp-defender' ) ) {
329 $links[2] = sprintf(
330 '<a href="%s" target="_blank">%s</a>',
331 esc_url( $this->get_link( 'pro_link', '', 'project/wp-defender/' ) ),
332 esc_html__( 'View details', 'defender-security' )
333 );
334 }
335 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
336 'Premium Support',
337 'defender-security'
338 ) . '" target="_blank">' . esc_html__( 'Premium Support', 'defender-security' ) . '</a>';
339 }
340 $row_meta['roadmap'] = '<a href="' . esc_url( $this->get_link( 'roadmap' ) ) . '" aria-label="' . esc_attr__(
341 'Roadmap',
342 'defender-security'
343 ) . '" target="_blank">' . esc_html__( 'Roadmap', 'defender-security' ) . '</a>';
344
345 return array_merge( $links, $row_meta );
346 }
347
348 /**
349 * Register sub-modules.
350 */
351 public function register_free_modules() {
352 $module_path = defender_path( 'extra/free-dashboard/module.php' );
353 if ( ! file_exists( $module_path ) ) {
354 return;
355 }
356 /* @noinspection PhpIncludeInspection */
357 require_once $module_path;
358 // Register the current plugin.
359 do_action(
360 'wdev_register_plugin',
361 /* 1 Plugin ID */ DEFENDER_PLUGIN_BASENAME,
362 /* 2 Plugin Title */ 'Defender',
363 /* 3 https://wordpress.org */ '/plugins/defender-security/',
364 /* 4 Email Button CTA */ esc_html__( 'Get Fast!', 'defender-security' )
365 );
366 }
367
368 /**
369 * Display IP detection notices for if user site is behind proxy, e.g. Cloudflare or something else, and only for admins.
370 *
371 * @return void
372 */
373 public function admin_notices(): void {
374 if ( ! current_user_can( 'manage_options' ) ) {
375 return;
376 }
377 $header = $this->get_plugin_display_name();
378 if ( Firewall::is_cf_notice_ready() ) {
379 $is_show = 'cf';
380 $class_notice = 'notice-info';
381 $header .= esc_html__(
382 'Cloudflare Usage Detected: Switched to CF-Connecting-IP for Better Compatibility',
383 'defender-security'
384 );
385 } elseif ( Firewall::is_xff_notice_ready() ) {
386 $is_show = 'xff';
387 $class_notice = 'notice-warning';
388 $header .= esc_html__(
389 'Improve IP Detection: We suggest Switching to X-Forward-For IP Detection Method',
390 'defender-security'
391 );
392 } else {
393 return;
394 }
395 ?>
396 <div class="defender_ip_detection_notice notice <?php echo esc_attr( $class_notice ); ?> is-dismissible"
397 data-nonce="<?php echo esc_attr( wp_create_nonce( 'defender_ip_detection_notice_dismiss' ) ); ?>"
398 data-prop="notice-for-<?php echo esc_attr( $is_show ); ?>">
399 <h3 style="margin-bottom:0;">
400 <?php echo esc_html( $header ); ?>
401 </h3>
402 <?php if ( 'cf' === $is_show ) { ?>
403 <p style="color: #72777C; line-height: 22px;">
404 <?php
405 printf(
406 /* translators: %s: Link. */
407 esc_html__(
408 '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.',
409 'defender-security'
410 ),
411 '<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>'
412 );
413 ?>
414 </p>
415 <p>
416 <button type="button" class="button button-primary button-large defender_ip_detection_action_hide"
417 data-prop="defender_ip_detection_notice_success">
418 <?php
419 esc_html_e(
420 'Ok, I understand',
421 'defender-security'
422 );
423 ?>
424 </button>
425 </p>
426 <?php } elseif ( 'xff' === $is_show ) { ?>
427 <p style="color: #72777C; line-height: 22px;">
428 <?php
429 printf(
430 /* translators: %s: Link. */
431 esc_html__(
432 '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.',
433 'defender-security'
434 ),
435 '<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>'
436 );
437 ?>
438 </p>
439 <p>
440 <button type="button" class="button button-primary button-large"
441 id="defender_ip_detection_action_switch"
442 data-prop="defender_ip_detection_notice_success">
443 <?php esc_html_e( 'Switch to X-Forwarded-For', 'defender-security' ); ?>
444 </button>
445 <a href="#" class="defender_ip_detection_action_hide"
446 style="margin-left: 11px; line-height: 16px; text-decoration: none; font-weight: bold;"
447 data-prop="defender_ip_detection_notice_dismiss"><?php esc_html_e( 'Dismiss', 'defender-security' ); ?></a>
448 </p>
449 <?php } ?>
450 </div>
451 <script type="text/javascript">
452 //Switch.
453 jQuery('#defender_ip_detection_action_switch').on('click', function (e) {
454 e.preventDefault();
455 var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
456 ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
457
458 jQuery.post(
459 ajaxUrl,
460 {
461 action: 'defender_ip_detection_switch_to_xff',
462 _ajax_nonce: $notice.data('nonce')
463 }
464 ).always(function () {
465 $notice.hide();
466 });
467 });
468 //Hide.
469 jQuery('body').on('click', '.defender_ip_detection_notice .notice-dismiss, .defender_ip_detection_action_hide', function (e) {
470 e.preventDefault();
471 var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
472 ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
473
474 jQuery.post(
475 ajaxUrl,
476 {
477 action: 'defender_ip_detection_notice_dismiss',
478 prop: $notice.data('prop'),
479 _ajax_nonce: $notice.data('nonce')
480 }
481 ).always(function () {
482 $notice.hide();
483 });
484 });
485 </script>
486 <?php
487 }
488
489 /**
490 * Dismiss notice.
491 *
492 * @return void
493 */
494 public function dismiss_notice(): void {
495 if (
496 ! current_user_can( 'manage_options' ) ||
497 ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
498 ) {
499 wp_send_json_error(
500 array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
501 );
502 }
503
504 $prop = defender_get_data_from_request( 'prop', 'p' );
505 $notice_type = ! empty( $prop ) ? $prop : false;
506 if ( 'notice-for-cf' === $notice_type ) {
507 update_site_option( Firewall::IP_DETECTION_CF_DISMISS_SLUG, true );
508 wp_send_json_success();
509 } elseif ( 'notice-for-xff' === $notice_type ) {
510 update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
511 wp_send_json_success();
512 } else {
513 wp_send_json_error(
514 array( 'message' => esc_html__( 'Invalid request, allowed data not provided.', 'defender-security' ) )
515 );
516 }
517 }
518
519 /**
520 * Switch to XFF option.
521 *
522 * @return void
523 */
524 public function switch_to_xff(): void {
525 if (
526 ! current_user_can( 'manage_options' ) ||
527 ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
528 ) {
529 wp_send_json_error(
530 array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
531 );
532 }
533 // Change model's data.
534 $model_firewall = wd_di()->get( Model\Setting\Firewall::class );
535 $model_firewall->http_ip_header = 'HTTP_X_FORWARDED_FOR';
536 $xff_ip = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
537 if ( empty( $model_firewall->trusted_proxies_ip ) ) {
538 $model_firewall->trusted_proxies_ip = $xff_ip;
539 } else {
540 // Todo: improve the code using a separate method. This will be useful when the user switches between different proxy headeres (IP detection options).
541 $separator = "\r\n";
542 // Check if the XFF header contains multiple IPs.
543 $xff_ip = str_replace( array( ',', ' ,' ), $separator, $xff_ip );
544 $model_firewall->trusted_proxies_ip = $model_firewall->trusted_proxies_ip . $separator . $xff_ip;
545 }
546 $model_firewall->save();
547 // Save Dismiss slug.
548 update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
549 wp_send_json_success();
550 }
551 }
552