PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.1
Defender Security – Malware Scanner, Login Security & Firewall v5.6.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.6.1, at src/class-admin.php

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