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 -54 5.5.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,27 +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 - background: #8D00B1;
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;
141 195 color: #ffffff;
142 - font-weight: 500;
196 + font-size: 14px;
197 + font-weight: 400;
198 + line-height: 18px;
143 199 }
144 200
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 {
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;
218 + font-size: 12px;
219 + font-weight: 400;
220 + line-height: 20px;
221 + letter-spacing: 0.1px;
222 + }
223 +
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 {
149 232 margin-left: -4px;
150 233 }
151 234 </style>
152 235 <script type='text/javascript'>
153 236 jQuery(function ($) {
154 - $('#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");
155 238 });
156 239 </script>
157 240 <?php
158 241 }
@@ -171,15 +254,16 @@
171 254 *
172 255 * @param string $link_for Accepts: 'docs', 'plugin', 'rate' and etc.
173 256 * @param string $campaign Utm campaign tag to be used in link. Default: ''.
174 257 * @param string $adv_path Advanced path. Default: ''.
258 + * @param string $source UTM source. Default: 'defender'.
175 259 *
176 260 * @return string
177 261 */
178 - public function get_link( $link_for, $campaign = '', $adv_path = '' ): string {
262 + public function get_link( $link_for, $campaign = '', $adv_path = '', $source = 'defender' ): string {
179 263 $domain = 'https://wpmudev.com';
180 264 $wp_org = 'https://wordpress.org';
181 - $utm_tags = "?utm_source=defender&utm_medium=plugin&utm_campaign={$campaign}";
265 + $utm_tags = "?utm_source={$source}&utm_medium=plugin&utm_campaign={$campaign}";
182 266 switch ( $link_for ) {
183 267 case 'docs':
184 268 $link = "{$domain}/docs/wpmu-dev-plugins/defender/{$utm_tags}";
185 269 break;
@@ -194,8 +278,11 @@
194 278 $link = $this->is_wp_org_version
195 279 ? "{$wp_org}/support/plugin/defender-security/"
196 280 : "{$domain}/get-support/";
197 281 break;
282 + case 'support_with_utm':
283 + $link = "{$domain}/hub2/support/{$utm_tags}";
284 + break;
198 285 case 'roadmap':
199 286 $link = "{$domain}/roadmap/";
200 287 break;
201 288 case 'pro_link':
@@ -246,9 +333,9 @@
246 333 esc_html__(
247 334 'Upgrade to Defender Pro',
248 335 'defender-security'
249 336 )
250 - ) . '">' . esc_html__( 'Limited-time offer!', 'defender-security' ) . '</a>';
337 + ) . '">' . esc_html__( 'Get Defender Pro', 'defender-security' ) . '</a>';
251 338 }
252 339 } elseif ( ! $wpmu_dev->is_hosted_site_connected_to_tfh() ) {
253 340 $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
254 341 'plugin',
@@ -317,9 +404,9 @@
317 404 }
318 405 $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__(
319 406 'Rate Defender',
320 407 'defender-security'
321 - ) . '" target="_blank">' . esc_html__( 'Rate Defender', 'defender-security' ) . '</a>';
408 + ) . '" target="_blank">' . Rate::get_rate_button_title() . '</a>';
322 409 $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
323 410 'Support',
324 411 'defender-security'
325 412 ) . '" target="_blank">' . esc_html__( 'Support', 'defender-security' ) . '</a>';
@@ -345,34 +432,16 @@
345 432 return array_merge( $links, $row_meta );
346 433 }
347 434
348 435 /**
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.
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.
370 439 *
371 440 * @return void
372 441 */
373 442 public function admin_notices(): void {
374 - if ( ! current_user_can( 'manage_options' ) ) {
443 + if ( ! current_user_can( 'manage_options' ) || ! is_defender_page() ) {
375 444 return;
376 445 }
377 446 $header = $this->get_plugin_display_name();
378 447 if ( Firewall::is_cf_notice_ready() ) {
@@ -501,9 +570,9 @@
501 570 );
502 571 }
503 572
504 573 $prop = defender_get_data_from_request( 'prop', 'p' );
505 - $notice_type = ! empty( $prop ) ? $prop : false;
574 + $notice_type = '' !== trim( $prop ) ? $prop : false;
506 575 if ( 'notice-for-cf' === $notice_type ) {
507 576 update_site_option( Firewall::IP_DETECTION_CF_DISMISS_SLUG, true );
508 577 wp_send_json_success();
509 578 } elseif ( 'notice-for-xff' === $notice_type ) {
@@ -533,19 +602,100 @@
533 602 // Change model's data.
534 603 $model_firewall = wd_di()->get( Model\Setting\Firewall::class );
535 604 $model_firewall->http_ip_header = 'HTTP_X_FORWARDED_FOR';
536 605 $xff_ip = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
537 - 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 ) {
538 624 $model_firewall->trusted_proxies_ip = $xff_ip;
539 625 } else {
540 626 // 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 627 $model_firewall->trusted_proxies_ip = $model_firewall->trusted_proxies_ip . $separator . $xff_ip;
545 628 }
546 629 $model_firewall->save();
547 630 // Save Dismiss slug.
548 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 +
549 699 wp_send_json_success();
550 700 }
551 701 }