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/class-admin.php +204 -56 5.4.1 → 6.3.0 View file →
@@ -11,8 +11,9 @@
11 11 use WP_Defender\Behavior\WPMUDEV;
12 12 use WP_Defender\Component\Firewall;
13 13 use WP_Defender\Integrations\Dashboard_Whitelabel;
14 14 use WP_Defender\Component\Config\Config_Hub_Helper;
15 +use WP_Defender\Helper\Analytics\Deactivation_Survey;
15 16
16 17 if ( ! defined( 'ABSPATH' ) ) {
17 18 die();
18 19 }
@@ -37,9 +38,13 @@
37 38 public function __construct() {
38 39 $this->is_wp_org_version = defender_is_wp_org_version();
39 40 add_action( 'wp_ajax_defender_ip_detection_notice_dismiss', array( $this, 'dismiss_notice' ) );
40 41 add_action( 'wp_ajax_defender_ip_detection_switch_to_xff', array( $this, 'switch_to_xff' ) );
42 + add_action( 'wp_ajax_defender_track_deactivate', array( $this, 'track_deactivate' ) );
41 43 add_action( 'admin_head', array( $this, 'add_global_styles' ) );
44 +
45 + // Deactivation survey.
46 + add_action( 'admin_footer-plugins.php', array( $this, 'load_deactivation_survey_modal' ) );
42 47 }
43 48
44 49 /**
45 50 * Add global styles.
@@ -46,9 +51,28 @@
46 51 */
47 52 public function add_global_styles() {
48 53 echo '<style>
49 54 #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; }
55 + #adminmenu li.wp-has-current-submenu a.wp-has-current-submenu { background-color: #4763E4 !important; }
56 + #adminmenu .toplevel_page_wp-defender div.wp-menu-image.svg { width: 16px; height: 16px; margin-top: 10px; margin-left: 10px; background-size: 16px auto; }
50 57 </style>';
58 + if ( ! $this->is_wp_org_version ) {
59 + echo '<style>
60 + #adminmenu .defender-admin-menu-pro-tag {
61 + display: inline-block;
62 + padding: 0;
63 + color: inherit;
64 + border: 1px solid currentColor;
65 + border-radius: 9px;
66 + line-height: 16px;
67 + font-size: 11px;
68 + height: 16px;
69 + width: 28px;
70 + text-align: center;
71 + margin-left: 5px;
72 + }
73 + </style>';
74 + }
51 75 }
52 76
53 77 /**
54 78 * Init admin actions.
@@ -59,10 +83,8 @@
59 83 add_filter( 'plugin_action_links_' . DEFENDER_PLUGIN_BASENAME, array( $this, 'settings_link' ) );
60 84 add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 3 );
61 85 // Only for plugin pages and actions are only for wp.org members.
62 86 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 87 /**
66 88 * Action hook that fires after a scan issue is fixed.
67 89 *
68 90 * @since 4.4.0
@@ -74,15 +96,25 @@
74 96 );
75 97 // For submenu callout.
76 98 add_action( 'admin_head', array( $this, 'retarget_submenu_callout' ) );
77 99 if ( ! wd_di()->get( WPMUDEV::class )->is_wpmu_hosting() ) {
100 + $upsell_menu_title = sprintf(
101 + '<span class="defender-upsell-label">%1$s<span class="defender-upsell-pro-tag">%2$s</span></span><svg class="defender-upsell-arrow" width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M0.225625 8.7625C0.0881251 8.62917 0.013125 8.47083 0.000624999 8.2875C-0.00770833 8.10417 0.0672918 7.93125 0.225625 7.76875L5.86313 2.125L7.16313 0.93125C7.31313 0.789584 7.47146 0.722917 7.63812 0.73125C7.80479 0.739583 7.94854 0.804167 8.06938 0.925C8.19021 1.04583 8.25062 1.18958 8.25062 1.35625C8.25479 1.52292 8.18812 1.67708 8.05062 1.81875L6.85063 3.11875L1.21313 8.75C1.05896 8.90417 0.890209 8.97917 0.706875 8.975C0.527709 8.975 0.367292 8.90417 0.225625 8.7625ZM7.61938 4.4625L7.76312 1.24375L4.43188 1.3625H2.30687C2.11521 1.3625 1.94854 1.29792 1.80688 1.16875C1.66521 1.03542 1.59438 0.875 1.59438 0.6875C1.59438 0.5 1.66104 0.339584 1.79438 0.20625C1.93188 0.0687502 2.10688 0 2.31938 0H8.25688C8.48188 0 8.66104 0.0687502 8.79437 0.20625C8.92771 0.34375 8.99438 0.520834 8.99438 0.7375V6.66875C8.99438 6.87292 8.92563 7.04583 8.78813 7.1875C8.65063 7.325 8.48812 7.39375 8.30062 7.39375C8.10896 7.39375 7.94646 7.325 7.81313 7.1875C7.68396 7.04583 7.61938 6.87708 7.61938 6.68125V4.4625Z" fill="white"/></svg>',
102 + esc_html__( 'Upgrade', 'defender-security' ),
103 + esc_html__( 'Pro', 'defender-security' )
104 + );
78 105 add_submenu_page(
79 106 'wp-defender',
80 - esc_html__( 'Limited-time offer!', 'defender-security' ),
81 - esc_html__( 'Limited-time offer!', 'defender-security' ),
107 + esc_html__( 'Upgrade to Pro', 'defender-security' ),
108 + $upsell_menu_title,
82 109 is_multisite() ? 'manage_network_options' : 'manage_options',
83 - $this->get_link( 'upsell', 'defender_submenu_upsell' )
110 + $this->get_link( 'upsell', 'defender_new-submenu_upsell' )
84 111 );
112 + global $submenu;
113 + if ( isset( $submenu['wp-defender'] ) && is_array( $submenu['wp-defender'] ) && array() !== $submenu['wp-defender'] ) {
114 + $last = array_key_last( $submenu['wp-defender'] );
115 + $submenu['wp-defender'][ $last ][4] = 'defender-menu-upsell'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
116 + }
85 117 }
86 118 }
87 119
88 120 // Display IP detection notice.
@@ -93,8 +125,30 @@
93 125 }
94 126 }
95 127
96 128 /**
129 + * Initialize the deactivation survey modal.
130 + */
131 + public function init_deactivation_survey() {
132 + global $pagenow;
133 +
134 + if ( 'plugins.php' !== $pagenow ) {
135 + return;
136 + }
137 +
138 + wp_enqueue_style( 'def-sui' );
139 + wp_enqueue_style( 'def-admin' );
140 +
141 + if ( ! wp_script_is( 'clipboard', 'enqueued' ) ) {
142 + wp_enqueue_script( 'clipboard' );
143 + }
144 +
145 + wp_enqueue_script( 'wpmudev-sui' );
146 + wp_enqueue_script( 'def-deactivation-survey' );
147 + wp_enqueue_script( 'def-admin' );
148 + }
149 +
150 + /**
97 151 * Retrieves the display name of the plugin.
98 152 *
99 153 * @return string The display name of the plugin with a trailing hyphen.
100 154 */
@@ -109,9 +163,9 @@
109 163
110 164 // If whitelabeling is enabled and a custom name is provided, use it.
111 165 if ( $whitelabel->can_whitelabel() ) {
112 166 $custom_label = $whitelabel->get_plugin_name( Config_Hub_Helper::WDP_ID );
113 - if ( ! empty( $custom_label ) ) {
167 + if ( is_string( $custom_label ) && '' !== trim( $custom_label ) ) {
114 168 $plugin_label = $custom_label;
115 169 }
116 170 }
117 171
@@ -119,14 +173,8 @@
119 173 return $plugin_label . ' - ';
120 174 }
121 175
122 176 /**
123 - * The method is a stub without content.
124 - */
125 - private function menu_nope(): void {
126 - }
127 -
128 - /**
129 177 * Generates the submenu callout for the WP Defender plugin.
130 178 *
131 179 * @return void
132 180 */
@@ -132,29 +180,62 @@
132 180 */
133 181 public function retarget_submenu_callout(): void {
134 182 ?>
135 183 <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;
184 + #adminmenu .wp-submenu li.defender-menu-upsell > a,
185 + #adminmenu .wp-submenu li.defender-menu-upsell > a:hover,
186 + #adminmenu .wp-submenu li.defender-menu-upsell > a:active,
187 + #adminmenu .wp-submenu li.defender-menu-upsell > a:focus {
188 + display: flex;
189 + align-items: center;
190 + justify-content: space-between;
191 + height: 32px;
192 + padding: 7px 12px;
193 + box-sizing: border-box;
194 + background: #571EE7;
195 + color: #ffffff;
196 + font-size: 14px;
197 + font-weight: 400;
198 + line-height: 18px;
199 + }
200 +
201 + #adminmenu .wp-submenu li.defender-menu-upsell > a .defender-upsell-label {
202 + display: inline-flex;
203 + align-items: center;
204 + }
205 +
206 + #adminmenu .wp-submenu li.defender-menu-upsell > a .defender-upsell-pro-tag {
207 + display: inline-flex;
208 + align-items: center;
209 + justify-content: center;
210 + margin-left: 6px;
211 + padding: 0 5px;
212 + border: 1px solid #ffffff;
213 + border-radius: 9px;
214 + width: 30px;
215 + height: 18px;
216 + box-sizing: border-box;
217 + font-family: 'Roboto', Arial, sans-serif;
141 218 font-size: 12px;
142 - background: #8D00B1;
143 - color: #ffffff;
144 - font-weight: 500;
219 + font-weight: 400;
220 + line-height: 20px;
221 + letter-spacing: 0.1px;
145 222 }
146 223
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 {
224 + #adminmenu .wp-submenu li.defender-menu-upsell > a .defender-upsell-arrow {
225 + margin-right: 4px;
226 + }
227 +
228 + #toplevel_page_wp-defender.wp-not-current-submenu .wp-submenu li.defender-menu-upsell > a,
229 + #toplevel_page_wp-defender.wp-not-current-submenu .wp-submenu li.defender-menu-upsell > a:hover,
230 + #toplevel_page_wp-defender.wp-not-current-submenu .wp-submenu li.defender-menu-upsell > a:active,
231 + #toplevel_page_wp-defender.wp-not-current-submenu .wp-submenu li.defender-menu-upsell > a:focus {
151 232 margin-left: -4px;
152 233 }
153 234 </style>
154 235 <script type='text/javascript'>
155 236 jQuery(function ($) {
156 - $('#toplevel_page_wp-defender > ul > li:last-child > a[href^="https://wpmudev.com/"]').attr("target", "_blank");
237 + $('#adminmenu li.defender-menu-upsell > a').attr("target", "_blank");
157 238 });
158 239 </script>
159 240 <?php
160 241 }
@@ -173,15 +254,16 @@
173 254 *
174 255 * @param string $link_for Accepts: 'docs', 'plugin', 'rate' and etc.
175 256 * @param string $campaign Utm campaign tag to be used in link. Default: ''.
176 257 * @param string $adv_path Advanced path. Default: ''.
258 + * @param string $source UTM source. Default: 'defender'.
177 259 *
178 260 * @return string
179 261 */
180 - public function get_link( $link_for, $campaign = '', $adv_path = '' ): string {
262 + public function get_link( $link_for, $campaign = '', $adv_path = '', $source = 'defender' ): string {
181 263 $domain = 'https://wpmudev.com';
182 264 $wp_org = 'https://wordpress.org';
183 - $utm_tags = "?utm_source=defender&utm_medium=plugin&utm_campaign={$campaign}";
265 + $utm_tags = "?utm_source={$source}&utm_medium=plugin&utm_campaign={$campaign}";
184 266 switch ( $link_for ) {
185 267 case 'docs':
186 268 $link = "{$domain}/docs/wpmu-dev-plugins/defender/{$utm_tags}";
187 269 break;
@@ -196,8 +278,11 @@
196 278 $link = $this->is_wp_org_version
197 279 ? "{$wp_org}/support/plugin/defender-security/"
198 280 : "{$domain}/get-support/";
199 281 break;
282 + case 'support_with_utm':
283 + $link = "{$domain}/hub2/support/{$utm_tags}";
284 + break;
200 285 case 'roadmap':
201 286 $link = "{$domain}/roadmap/";
202 287 break;
203 288 case 'pro_link':
@@ -248,9 +333,9 @@
248 333 esc_html__(
249 334 'Upgrade to Defender Pro',
250 335 'defender-security'
251 336 )
252 - ) . '">' . esc_html__( 'Limited-time offer!', 'defender-security' ) . '</a>';
337 + ) . '">' . esc_html__( 'Get Defender Pro', 'defender-security' ) . '</a>';
253 338 }
254 339 } elseif ( ! $wpmu_dev->is_hosted_site_connected_to_tfh() ) {
255 340 $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
256 341 'plugin',
@@ -319,9 +404,9 @@
319 404 }
320 405 $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__(
321 406 'Rate Defender',
322 407 'defender-security'
323 - ) . '" target="_blank">' . esc_html__( 'Rate Defender', 'defender-security' ) . '</a>';
408 + ) . '" target="_blank">' . Rate::get_rate_button_title() . '</a>';
324 409 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
325 410 'Support',
326 411 'defender-security'
327 412 ) . '" target="_blank">' . esc_html__( 'Support', 'defender-security' ) . '</a>';
@@ -347,34 +432,16 @@
347 432 return array_merge( $links, $row_meta );
348 433 }
349 434
350 435 /**
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 - }
369 -
370 - /**
371 - * Display IP detection notices for if user site is behind proxy, e.g. Cloudflare or something else, and only for admins.
436 + * Display IP detection notices:
437 + * - if user site is behind proxy, e.g. Cloudflare or something else, and only for admins,
438 + * - only on the plugin's pages.
372 439 *
373 440 * @return void
374 441 */
375 442 public function admin_notices(): void {
376 - if ( ! current_user_can( 'manage_options' ) ) {
443 + if ( ! current_user_can( 'manage_options' ) || ! is_defender_page() ) {
377 444 return;
378 445 }
379 446 $header = $this->get_plugin_display_name();
380 447 if ( Firewall::is_cf_notice_ready() ) {
@@ -503,9 +570,9 @@
503 570 );
504 571 }
505 572
506 573 $prop = defender_get_data_from_request( 'prop', 'p' );
507 - $notice_type = ! empty( $prop ) ? $prop : false;
574 + $notice_type = '' !== trim( $prop ) ? $prop : false;
508 575 if ( 'notice-for-cf' === $notice_type ) {
509 576 update_site_option( Firewall::IP_DETECTION_CF_DISMISS_SLUG, true );
510 577 wp_send_json_success();
511 578 } elseif ( 'notice-for-xff' === $notice_type ) {
@@ -535,19 +602,100 @@
535 602 // Change model's data.
536 603 $model_firewall = wd_di()->get( Model\Setting\Firewall::class );
537 604 $model_firewall->http_ip_header = 'HTTP_X_FORWARDED_FOR';
538 605 $xff_ip = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
539 - if ( empty( $model_firewall->trusted_proxies_ip ) ) {
606 + $xff_parts = preg_split( '/\s*,\s*/', $xff_ip );
607 + $xff_parts = is_array( $xff_parts ) ? $xff_parts : array();
608 + $xff_parts = array_map( 'trim', $xff_parts );
609 + $xff_parts = array_filter(
610 + $xff_parts,
611 + static function ( $ip ) {
612 + return false !== filter_var( $ip, FILTER_VALIDATE_IP );
613 + }
614 + );
615 + $separator = "\r\n";
616 + $xff_parts = array_unique( $xff_parts );
617 + $xff_ip = implode( $separator, $xff_parts );
618 + if ( '' === $xff_ip ) {
619 + wp_send_json_error(
620 + array( 'message' => esc_html__( 'Invalid trusted proxy IP(s) detected in X-Forwarded-For header.', 'defender-security' ) )
621 + );
622 + }
623 + if ( '' === $model_firewall->trusted_proxies_ip ) {
540 624 $model_firewall->trusted_proxies_ip = $xff_ip;
541 625 } else {
542 626 // Todo: improve the code using a separate method. This will be useful when the user switches between different proxy headeres (IP detection options).
543 - $separator = "\r\n";
544 - // Check if the XFF header contains multiple IPs.
545 - $xff_ip = str_replace( array( ',', ' ,' ), $separator, $xff_ip );
546 627 $model_firewall->trusted_proxies_ip = $model_firewall->trusted_proxies_ip . $separator . $xff_ip;
547 628 }
548 629 $model_firewall->save();
549 630 // Save Dismiss slug.
550 631 update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
632 + wp_send_json_success();
633 + }
634 +
635 + /**
636 + * Load deactivation survey modal.
637 + */
638 + public function load_deactivation_survey_modal() {
639 + $deactivation_survey_template_file = WP_DEFENDER_DIR .
640 + 'src' . DIRECTORY_SEPARATOR .
641 + 'view' . DIRECTORY_SEPARATOR .
642 + 'modal' . DIRECTORY_SEPARATOR .
643 + 'deactivation-survey.php';
644 +
645 + if ( ! file_exists( $deactivation_survey_template_file ) ) {
646 + return;
647 + }
648 +
649 + // Data to be passed to the template file.
650 + $is_pro = wd_di()->get( WPMUDEV::class )->is_pro();
651 + $docs_link = $this->get_link(
652 + 'support_with_utm',
653 + 'defender_deactivation_survey_help',
654 + '',
655 + $is_pro ? 'defender-pro' : 'defender'
656 + );
657 +
658 + ob_start();
659 + require_once $deactivation_survey_template_file;
660 + // Everything escaped in all template files.
661 + echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
662 + }
663 +
664 + /**
665 + * Track deactivation.
666 + */
667 + public function track_deactivate() {
668 + if (
669 + ! current_user_can( 'manage_options' ) ||
670 + ! check_ajax_referer( 'defender_deactivation_survey_modal' )
671 + ) {
672 + wp_send_json_error(
673 + array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
674 + );
675 + }
676 + $posted_data = defender_get_data_from_request( null, 'p' );
677 + if ( ! is_array( $posted_data['properties'] ) ) {
678 + wp_send_json_error(
679 + array( 'message' => esc_html__( 'Invalid request, allowed data not provided.', 'defender-security' ) )
680 + );
681 + }
682 + $properties = $posted_data['properties'];
683 + if (
684 + ! isset(
685 + $properties['Reason'],
686 + $properties['Message'],
687 + $properties['Modal Action'],
688 + $properties['Requested Assistance'],
689 + $properties['Tracking Status']
690 + )
691 + ) {
692 + wp_send_json_error(
693 + array( 'message' => esc_html__( 'Missing field(s).', 'defender-security' ) )
694 + );
695 + }
696 +
697 + wd_di()->get( Deactivation_Survey::class )->track_deactivation_survey( $properties );
698 +
551 699 wp_send_json_success();
552 700 }
553 701 }