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 +536 -195 3.0.1 → 6.3.0 View file →
@@ -1,9 +1,19 @@
1 1 <?php
2 +/**
3 + * Handles WordPress admin page related tasks.
4 + *
5 + * @package WP_Defender
6 + */
2 7
3 8 namespace WP_Defender;
4 9
10 +use WP_Defender\Component\Rate;
5 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 +use WP_Defender\Helper\Analytics\Deactivation_Survey;
6 16
7 17 if ( ! defined( 'ABSPATH' ) ) {
8 18 die();
9 19 }
@@ -8,9 +18,9 @@
8 18 die();
9 19 }
10 20
11 21 /**
12 - * Class Admin
22 + * Handles WordPress admin page related tasks.
13 23 *
14 24 * @since 2.4
15 25 */
16 26 class Admin {
@@ -15,66 +25,251 @@
15 25 */
16 26 class Admin {
17 27
18 28 /**
29 + * Is the Free version?
30 + *
19 31 * @var bool
20 32 */
21 - public $is_pro;
33 + public $is_wp_org_version;
22 34
23 35 /**
36 + * Constructor for the Admin class.
37 + */
38 + public function __construct() {
39 + $this->is_wp_org_version = defender_is_wp_org_version();
40 + add_action( 'wp_ajax_defender_ip_detection_notice_dismiss', array( $this, 'dismiss_notice' ) );
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' ) );
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' ) );
47 + }
48 +
49 + /**
50 + * Add global styles.
51 + */
52 + public function add_global_styles() {
53 + echo '<style>
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; }
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 + }
75 + }
76 +
77 + /**
24 78 * Init admin actions.
25 79 */
26 80 public function init() {
27 - $this->is_pro = ( new WPMUDEV() )->is_pro();
28 - $is_def_page = defender_current_page();
29 81 // Display plugin links.
30 82 add_filter( 'network_admin_plugin_action_links_' . DEFENDER_PLUGIN_BASENAME, array( $this, 'settings_link' ) );
31 83 add_filter( 'plugin_action_links_' . DEFENDER_PLUGIN_BASENAME, array( $this, 'settings_link' ) );
32 84 add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 3 );
33 - // Only for wordpress.org members.
34 - if ( ! $this->is_pro ) {
35 - // Add plugin upgrade notification.
36 - add_action( 'in_plugin_update_message-' . DEFENDER_PLUGIN_BASENAME, array( $this, 'show_upgrade_notice' ), 10, 2 );
37 - if ( $is_def_page && ! is_multisite() ) {
38 - add_action( 'admin_notices', array( $this, 'show_rating_notice' ) );
39 - } elseif ( $is_def_page && is_multisite() && is_main_site() ) {
40 - add_action( 'network_admin_notices', array( $this, 'show_rating_notice' ) );
85 + // Only for plugin pages and actions are only for wp.org members.
86 + if ( $this->is_wp_org_version ) {
87 + /**
88 + * Action hook that fires after a scan issue is fixed.
89 + *
90 + * @since 4.4.0
91 + */
92 + add_action(
93 + 'wpdef_fixed_scan_issue',
94 + array( $this, 'after_scan_fix' ),
95 + 10
96 + );
97 + // For submenu callout.
98 + add_action( 'admin_head', array( $this, 'retarget_submenu_callout' ) );
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 + );
105 + add_submenu_page(
106 + 'wp-defender',
107 + esc_html__( 'Upgrade to Pro', 'defender-security' ),
108 + $upsell_menu_title,
109 + is_multisite() ? 'manage_network_options' : 'manage_options',
110 + $this->get_link( 'upsell', 'defender_new-submenu_upsell' )
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 + }
41 117 }
42 - add_action( 'wp_ajax_defender_dismiss_notification', array( $this, 'dismiss_notice' ) );
43 - add_action( 'admin_init', array( $this, 'register_free_modules' ), 20 );
118 + }
119 +
120 + // Display IP detection notice.
121 + if ( is_multisite() ) {
122 + add_action( 'network_admin_notices', array( $this, 'admin_notices' ) );
44 123 } else {
45 - // For Pro version.
46 - add_action(
47 - 'load-plugins.php',
48 - function () {
49 - add_action( 'after_plugin_row_' . DEFENDER_PLUGIN_BASENAME, array(
50 - $this,
51 - 'show_upgrade_notice'
52 - ), 10, 2 );
53 - },
54 - 22
55 - );
124 + add_action( 'admin_notices', array( $this, 'admin_notices' ) );
56 125 }
57 126 }
58 127
59 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 + /**
151 + * Retrieves the display name of the plugin.
152 + *
153 + * @return string The display name of the plugin with a trailing hyphen.
154 + */
155 + public function get_plugin_display_name(): string {
156 + // Check if the plugin is the WordPress.org version (i.e., the free version) and set the label accordingly.
157 + $plugin_label = $this->is_wp_org_version
158 + ? esc_html__( 'Defender', 'defender-security' )
159 + : esc_html__( 'Defender Pro', 'defender-security' );
160 +
161 + // Instantiate the Dashboard_Whitelabel class only if necessary.
162 + $whitelabel = new Dashboard_Whitelabel();
163 +
164 + // If whitelabeling is enabled and a custom name is provided, use it.
165 + if ( $whitelabel->can_whitelabel() ) {
166 + $custom_label = $whitelabel->get_plugin_name( Config_Hub_Helper::WDP_ID );
167 + if ( is_string( $custom_label ) && '' !== trim( $custom_label ) ) {
168 + $plugin_label = $custom_label;
169 + }
170 + }
171 +
172 + // Return the final plugin label with the appended dash.
173 + return $plugin_label . ' - ';
174 + }
175 +
176 + /**
177 + * Generates the submenu callout for the WP Defender plugin.
178 + *
179 + * @return void
180 + */
181 + public function retarget_submenu_callout(): void {
182 + ?>
183 + <style>
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;
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 {
232 + margin-left: -4px;
233 + }
234 + </style>
235 + <script type='text/javascript'>
236 + jQuery(function ($) {
237 + $('#adminmenu li.defender-menu-upsell > a').attr("target", "_blank");
238 + });
239 + </script>
240 + <?php
241 + }
242 +
243 + /**
244 + * Fired when the scan issue is fixed.
245 + *
246 + * @return void
247 + */
248 + public function after_scan_fix(): void {
249 + Rate::run_counter_of_fixed_scans();
250 + }
251 +
252 + /**
60 253 * Return URL link.
61 254 *
62 - * @param string $link_for Accepts: 'docs', 'plugin', 'rate', 'support', 'roadmap'.
63 - * @param string $campaign Utm campaign tag to be used in link. Default: ''.
64 - * @param string $adv_path Advanced path. Default: ''.
255 + * @param string $link_for Accepts: 'docs', 'plugin', 'rate' and etc.
256 + * @param string $campaign Utm campaign tag to be used in link. Default: ''.
257 + * @param string $adv_path Advanced path. Default: ''.
258 + * @param string $source UTM source. Default: 'defender'.
65 259 *
66 260 * @return string
67 261 */
68 - public function get_link( $link_for, $campaign = '', $adv_path = '' ) {
69 - $domain = 'https://wpmudev.com';
262 + public function get_link( $link_for, $campaign = '', $adv_path = '', $source = 'defender' ): string {
263 + $domain = 'https://wpmudev.com';
70 264 $wp_org = 'https://wordpress.org';
71 - $utm_tags = "?utm_source=defender&utm_medium=plugin&utm_campaign={$campaign}";
265 + $utm_tags = "?utm_source={$source}&utm_medium=plugin&utm_campaign={$campaign}";
72 266 switch ( $link_for ) {
73 267 case 'docs':
74 268 $link = "{$domain}/docs/wpmu-dev-plugins/defender/{$utm_tags}";
75 269 break;
76 270 case 'plugin':
271 + case 'upsell':
77 272 $link = "{$domain}/project/wp-defender/{$utm_tags}";
78 273 break;
79 274 case 'rate':
80 275 $link = "{$wp_org}/support/plugin/defender-security/reviews/#new-post";
@@ -79,10 +274,15 @@
79 274 case 'rate':
80 275 $link = "{$wp_org}/support/plugin/defender-security/reviews/#new-post";
81 276 break;
82 277 case 'support':
83 - $link = $this->is_pro ? "{$domain}/get-support/" : "{$wp_org}/support/plugin/defender-security/";
278 + $link = $this->is_wp_org_version
279 + ? "{$wp_org}/support/plugin/defender-security/"
280 + : "{$domain}/get-support/";
84 281 break;
282 + case 'support_with_utm':
283 + $link = "{$domain}/hub2/support/{$utm_tags}";
284 + break;
85 285 case 'roadmap':
86 286 $link = "{$domain}/roadmap/";
87 287 break;
88 288 case 'pro_link':
@@ -95,28 +295,58 @@
95 295
96 296 return $link;
97 297 }
98 298
99 -
100 299 /**
101 300 * Adds a settings link on plugin page.
102 301 *
103 - * @param array $links Current links.
302 + * @param array $links Current links.
104 303 *
105 304 * @return array
106 305 */
107 306 public function settings_link( $links ) {
108 - $action_links = [];
109 - $wpmu_dev = new WPMUDEV();
110 - // Settings link.
111 - $action_links['dashboard'] = '<a href="' . network_admin_url( 'admin.php?page=wdf-setting' ) . '" aria-label="' . esc_attr( __( 'Go to Defender Settings', 'wpdef' ) ) . '">' . esc_html__( 'Settings', 'wpdef' ) . '</a>';
112 - // Documentation link.
113 - $action_links['docs'] = '<a target="_blank" href="' . $this->get_link('docs', 'defender_pluginlist_docs') . '" aria-label="' . esc_attr(__('Docs', 'wpdef')) . '">' . esc_html__('Docs', 'wpdef') . '</a>';
307 + $action_links = array();
308 + $wpmu_dev = new WPMUDEV();
309 + // Dashboard-link.
310 + $action_links['dashboard'] = '<a href="' . network_admin_url( 'admin.php?page=wp-defender' ) . '" aria-label="' . esc_attr(
311 + esc_html__(
312 + 'Go to Defender Dashboard',
313 + 'defender-security'
314 + )
315 + ) . '">' . esc_html__( 'Dashboard', 'defender-security' ) . '</a>';
316 + // Documentation-link.
317 + $action_links['docs'] = '<a target="_blank" href="' . $this->get_link(
318 + 'docs',
319 + 'defender_pluginlist_docs'
320 + ) . '" aria-label="' . esc_attr(
321 + esc_html__(
322 + 'Docs',
323 + 'defender-security'
324 + )
325 + ) . '">' . esc_html__( 'Docs', 'defender-security' ) . '</a>';
114 326 if ( ! $wpmu_dev->is_member() ) {
115 327 if ( WP_DEFENDER_PRO_PATH !== DEFENDER_PLUGIN_BASENAME ) {
116 - $action_links['upgrade'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link( 'plugin', 'defender_pluginlist_upgrade' ) . '" aria-label="' . esc_attr( __( 'Upgrade to Defender Pro', 'wpdef' ) ) . '">' . esc_html__( 'Upgrade', 'wpdef' ) . '</a>';
117 - } else {
118 - $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link( 'plugin', 'defender_pluginlist_renew' ) . '" aria-label="' . esc_attr( __( 'Renew Your Membership', 'wpdef' ) ) . '">' . esc_html__( 'Renew Membership', 'wpdef' ) . '</a>';
328 + if ( ! wd_di()->get( WPMUDEV::class )->is_wpmu_hosting() ) {
329 + $action_links['upgrade'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
330 + 'plugin',
331 + 'defender_pluginlist_upgrade'
332 + ) . '" aria-label="' . esc_attr(
333 + esc_html__(
334 + 'Upgrade to Defender Pro',
335 + 'defender-security'
336 + )
337 + ) . '">' . esc_html__( 'Get Defender Pro', 'defender-security' ) . '</a>';
338 + }
339 + } elseif ( ! $wpmu_dev->is_hosted_site_connected_to_tfh() ) {
340 + $action_links['renew'] = '<a style="color: #8D00B1;" target="_blank" href="' . $this->get_link(
341 + 'plugin',
342 + 'defender_pluginlist_renew'
343 + ) . '" aria-label="' . esc_attr(
344 + esc_html__(
345 + 'Renew Your Membership',
346 + 'defender-security'
347 + )
348 + ) . '">' . esc_html__( 'Renew Membership', 'defender-security' ) . '</a>';
119 349 }
120 350 }
121 351
122 352 return array_merge( $action_links, $links );
@@ -124,16 +354,16 @@
124 354
125 355 /**
126 356 * Show row meta on the plugin screen.
127 357 *
128 - * @param mixed $links Plugin Row Meta.
129 - * @param mixed $file Plugin Base file.
130 - * @param array $plugin_data Plugin data.
358 + * @param string[] $links Plugin Row Meta.
359 + * @param string $file Plugin Base file.
360 + * @param array $plugin_data Plugin data.
131 361 *
132 362 * @return array
133 363 */
134 364 public function plugin_row_meta( $links, $file, $plugin_data ) {
135 - $row_meta = [];
365 + $row_meta = array();
136 366 if ( ! defined( 'DEFENDER_PLUGIN_BASENAME' ) || DEFENDER_PLUGIN_BASENAME !== $file ) {
137 367 return $links;
138 368 }
139 369
@@ -138,18 +368,22 @@
138 368 }
139 369
140 370 // Change AuthorURI link.
141 371 if ( isset( $links[1] ) ) {
142 - $author_uri = $this->is_pro ? 'https://wpmudev.com/' : 'https://profiles.wordpress.org/wpmudev/';
372 + $author_uri = $this->is_wp_org_version ? 'https://profiles.wordpress.org/wpmudev/' : 'https://wpmudev.com/';
143 373 $author_uri = sprintf(
144 374 '<a href="%s" target="_blank">%s</a>',
145 375 $author_uri,
146 - __( 'WPMU DEV' )
376 + esc_html__( 'WPMU DEV', 'defender-security' )
147 377 );
148 - $links[1] = sprintf( /* translators: ... */ __( 'By %s' ), $author_uri );
378 + $links[1] = sprintf(
379 + /* translators: %s: Author URI. */
380 + esc_html__( 'By %s', 'defender-security' ),
381 + $author_uri
382 + );
149 383 }
150 384
151 - if ( ! $this->is_pro ) {
385 + if ( $this->is_wp_org_version ) {
152 386 // Change AuthorURI link.
153 387 if ( isset( $links[2] ) && false === strpos( $links[2], 'target="_blank"' ) ) {
154 388 if ( ! isset( $plugin_data['slug'] ) && $plugin_data['Name'] ) {
155 389 $links[2] = sprintf(
@@ -155,23 +389,28 @@
155 389 $links[2] = sprintf(
156 390 '<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
157 391 esc_url(
158 392 network_admin_url(
159 - 'plugin-install.php?tab=plugin-information&plugin=defender-security' .
160 - '&TB_iframe=true&width=600&height=550'
393 + 'plugin-install.php?tab=plugin-information&plugin=defender-security&TB_iframe=true&width=600&height=550'
161 394 )
162 395 ),
163 396 /* translators: %s: Plugin name. */
164 - esc_attr( sprintf( __( 'More information about %s' ), $plugin_data['Name'] ) ),
397 + esc_attr( sprintf( esc_html__( 'More information about %s', 'defender-security' ), $plugin_data['Name'] ) ),
165 398 esc_attr( $plugin_data['Name'] ),
166 - __( 'View details' )
399 + esc_html__( 'View details', 'defender-security' )
167 400 );
168 401 } else {
169 402 $links[2] = str_replace( 'href=', 'target="_blank" href=', $links[2] );
170 403 }
171 404 }
172 - $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__( 'Rate Defender', 'wpdef' ) . '" target="_blank">' . esc_html__( 'Rate Defender', 'wpdef' ) . '</a>';
173 - $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__( 'Support', 'wpdef' ) . '" target="_blank">' . esc_html__( 'Support', 'wpdef' ) . '</a>';
405 + $row_meta['rate'] = '<a href="' . esc_url( $this->get_link( 'rate' ) ) . '" aria-label="' . esc_attr__(
406 + 'Rate Defender',
407 + 'defender-security'
408 + ) . '" target="_blank">' . Rate::get_rate_button_title() . '</a>';
409 + $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
410 + 'Support',
411 + 'defender-security'
412 + ) . '" target="_blank">' . esc_html__( 'Support', 'defender-security' ) . '</a>';
174 413 } else {
175 414 // Change 'Visit plugins' link to 'View details'.
176 415 if ( isset( $links[2] ) && false !== strpos( $links[2], 'project/wp-defender' ) ) {
177 416 $links[2] = sprintf(
@@ -176,185 +415,287 @@
176 415 if ( isset( $links[2] ) && false !== strpos( $links[2], 'project/wp-defender' ) ) {
177 416 $links[2] = sprintf(
178 417 '<a href="%s" target="_blank">%s</a>',
179 418 esc_url( $this->get_link( 'pro_link', '', 'project/wp-defender/' ) ),
180 - __( 'View details' )
419 + esc_html__( 'View details', 'defender-security' )
181 420 );
182 421 }
183 - $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__( 'Premium Support', 'wpdef' ) . '" target="_blank">' . esc_html__( 'Premium Support', 'wpdef' ) . '</a>';
422 + $row_meta['support'] = '<a href="' . esc_url( $this->get_link( 'support' ) ) . '" aria-label="' . esc_attr__(
423 + 'Premium Support',
424 + 'defender-security'
425 + ) . '" target="_blank">' . esc_html__( 'Premium Support', 'defender-security' ) . '</a>';
184 426 }
185 - $row_meta['roadmap'] = '<a href="' . esc_url( $this->get_link( 'roadmap' ) ) . '" aria-label="' . esc_attr__( 'Roadmap', 'wpdef' ) . '" target="_blank">' . esc_html__( 'Roadmap', 'wpdef' ) . '</a>';
427 + $row_meta['roadmap'] = '<a href="' . esc_url( $this->get_link( 'roadmap' ) ) . '" aria-label="' . esc_attr__(
428 + 'Roadmap',
429 + 'defender-security'
430 + ) . '" target="_blank">' . esc_html__( 'Roadmap', 'defender-security' ) . '</a>';
186 431
187 432 return array_merge( $links, $row_meta );
188 433 }
189 434
190 435 /**
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.
439 + *
440 + * @return void
441 + */
442 + public function admin_notices(): void {
443 + if ( ! current_user_can( 'manage_options' ) || ! is_defender_page() ) {
444 + return;
445 + }
446 + $header = $this->get_plugin_display_name();
447 + if ( Firewall::is_cf_notice_ready() ) {
448 + $is_show = 'cf';
449 + $class_notice = 'notice-info';
450 + $header .= esc_html__(
451 + 'Cloudflare Usage Detected: Switched to CF-Connecting-IP for Better Compatibility',
452 + 'defender-security'
453 + );
454 + } elseif ( Firewall::is_xff_notice_ready() ) {
455 + $is_show = 'xff';
456 + $class_notice = 'notice-warning';
457 + $header .= esc_html__(
458 + 'Improve IP Detection: We suggest Switching to X-Forward-For IP Detection Method',
459 + 'defender-security'
460 + );
461 + } else {
462 + return;
463 + }
464 + ?>
465 + <div class="defender_ip_detection_notice notice <?php echo esc_attr( $class_notice ); ?> is-dismissible"
466 + data-nonce="<?php echo esc_attr( wp_create_nonce( 'defender_ip_detection_notice_dismiss' ) ); ?>"
467 + data-prop="notice-for-<?php echo esc_attr( $is_show ); ?>">
468 + <h3 style="margin-bottom:0;">
469 + <?php echo esc_html( $header ); ?>
470 + </h3>
471 + <?php if ( 'cf' === $is_show ) { ?>
472 + <p style="color: #72777C; line-height: 22px;">
473 + <?php
474 + printf(
475 + /* translators: %s: Link. */
476 + esc_html__(
477 + '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.',
478 + 'defender-security'
479 + ),
480 + '<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>'
481 + );
482 + ?>
483 + </p>
484 + <p>
485 + <button type="button" class="button button-primary button-large defender_ip_detection_action_hide"
486 + data-prop="defender_ip_detection_notice_success">
487 + <?php
488 + esc_html_e(
489 + 'Ok, I understand',
490 + 'defender-security'
491 + );
492 + ?>
493 + </button>
494 + </p>
495 + <?php } elseif ( 'xff' === $is_show ) { ?>
496 + <p style="color: #72777C; line-height: 22px;">
497 + <?php
498 + printf(
499 + /* translators: %s: Link. */
500 + esc_html__(
501 + '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.',
502 + 'defender-security'
503 + ),
504 + '<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>'
505 + );
506 + ?>
507 + </p>
508 + <p>
509 + <button type="button" class="button button-primary button-large"
510 + id="defender_ip_detection_action_switch"
511 + data-prop="defender_ip_detection_notice_success">
512 + <?php esc_html_e( 'Switch to X-Forwarded-For', 'defender-security' ); ?>
513 + </button>
514 + <a href="#" class="defender_ip_detection_action_hide"
515 + style="margin-left: 11px; line-height: 16px; text-decoration: none; font-weight: bold;"
516 + data-prop="defender_ip_detection_notice_dismiss"><?php esc_html_e( 'Dismiss', 'defender-security' ); ?></a>
517 + </p>
518 + <?php } ?>
519 + </div>
520 + <script type="text/javascript">
521 + //Switch.
522 + jQuery('#defender_ip_detection_action_switch').on('click', function (e) {
523 + e.preventDefault();
524 + var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
525 + ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
526 +
527 + jQuery.post(
528 + ajaxUrl,
529 + {
530 + action: 'defender_ip_detection_switch_to_xff',
531 + _ajax_nonce: $notice.data('nonce')
532 + }
533 + ).always(function () {
534 + $notice.hide();
535 + });
536 + });
537 + //Hide.
538 + jQuery('body').on('click', '.defender_ip_detection_notice .notice-dismiss, .defender_ip_detection_action_hide', function (e) {
539 + e.preventDefault();
540 + var $notice = jQuery(e.currentTarget).closest('.defender_ip_detection_notice'),
541 + ajaxUrl = '<?php echo esc_url_raw( admin_url( 'admin-ajax.php' ) ); ?>';
542 +
543 + jQuery.post(
544 + ajaxUrl,
545 + {
546 + action: 'defender_ip_detection_notice_dismiss',
547 + prop: $notice.data('prop'),
548 + _ajax_nonce: $notice.data('nonce')
549 + }
550 + ).always(function () {
551 + $notice.hide();
552 + });
553 + });
554 + </script>
555 + <?php
556 + }
557 +
558 + /**
191 559 * Dismiss notice.
560 + *
561 + * @return void
192 562 */
193 - public function dismiss_notice() {
194 - if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'defender_dismiss_notification' ) ) {
563 + public function dismiss_notice(): void {
564 + if (
565 + ! current_user_can( 'manage_options' ) ||
566 + ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
567 + ) {
195 568 wp_send_json_error(
196 - array(
197 - 'message' => __( 'Invalid request, you are not allowed to do that action.', 'wpdef' ),
198 - )
569 + array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
199 570 );
200 571 }
201 572
202 - $notification_name = ! empty( $_POST['prop'] ) ? sanitize_text_field( $_POST['prop'] ) : false;
203 - if ( false === $notification_name ) {
573 + $prop = defender_get_data_from_request( 'prop', 'p' );
574 + $notice_type = '' !== trim( $prop ) ? $prop : false;
575 + if ( 'notice-for-cf' === $notice_type ) {
576 + update_site_option( Firewall::IP_DETECTION_CF_DISMISS_SLUG, true );
577 + wp_send_json_success();
578 + } elseif ( 'notice-for-xff' === $notice_type ) {
579 + update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
580 + wp_send_json_success();
581 + } else {
204 582 wp_send_json_error(
205 - array(
206 - 'message' => __( 'Invalid request, allowed data not provided.', 'wpdef' ),
207 - )
583 + array( 'message' => esc_html__( 'Invalid request, allowed data not provided.', 'defender-security' ) )
208 584 );
209 585 }
210 - update_site_option( $notification_name, true );
211 -
212 - wp_send_json_success();
213 586 }
214 587
215 588 /**
216 - * Register sub-modules.
589 + * Switch to XFF option.
590 + *
591 + * @return void
217 592 */
218 - public function register_free_modules() {
593 + public function switch_to_xff(): void {
219 594 if (
220 - ! file_exists( defender_path( 'extra/free-dashboard/module.php' ) )
221 - || ! file_exists( defender_path( 'extra/recommended-plugins-notice/notice.php' ) )
595 + ! current_user_can( 'manage_options' ) ||
596 + ! check_ajax_referer( 'defender_ip_detection_notice_dismiss' )
222 597 ) {
223 - return;
598 + wp_send_json_error(
599 + array( 'message' => esc_html__( 'Invalid request, you are not allowed to do that action.', 'defender-security' ) )
600 + );
224 601 }
225 - /* @noinspection PhpIncludeInspection */
226 - require_once( defender_path( 'extra/free-dashboard/module.php' ) );
227 - /* @noinspection PhpIncludeInspection */
228 - require_once( defender_path( 'extra/recommended-plugins-notice/notice.php' ) );
229 -
230 - // Register the current plugin.
231 - do_action(
232 - 'wdev_register_plugin',
233 - /* 1 Plugin ID */ DEFENDER_PLUGIN_BASENAME,
234 - /* 2 Plugin Title */ 'Defender',
235 - /* 3 https://wordpress.org */ '/plugins/defender-security/',
236 - /* 4 Email Button CTA */ __( 'Get Fast!', 'wpdef' )
237 - /* 5 Mailchimp List id for the plugin - No */
602 + // Change model's data.
603 + $model_firewall = wd_di()->get( Model\Setting\Firewall::class );
604 + $model_firewall->http_ip_header = 'HTTP_X_FORWARDED_FOR';
605 + $xff_ip = defender_get_data_from_request( 'HTTP_X_FORWARDED_FOR', 's' );
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 + }
238 614 );
239 -
240 - // Recommended plugin notice.
241 - do_action(
242 - 'wpmudev-recommended-plugins-register-notice',
243 - DEFENDER_PLUGIN_BASENAME, // Plugin basename
244 - 'Defender', // Plugin Name
245 - array(
246 - 'toplevel_page_wp-defender',
247 - 'toplevel_page_wp-defender-network',
248 - ),
249 - array( 'after', '.sui-wrap .sui-header' )
250 - );
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 ) {
624 + $model_firewall->trusted_proxies_ip = $xff_ip;
625 + } else {
626 + // Todo: improve the code using a separate method. This will be useful when the user switches between different proxy headeres (IP detection options).
627 + $model_firewall->trusted_proxies_ip = $model_firewall->trusted_proxies_ip . $separator . $xff_ip;
628 + }
629 + $model_firewall->save();
630 + // Save Dismiss slug.
631 + update_site_option( Firewall::IP_DETECTION_XFF_DISMISS_SLUG, true );
632 + wp_send_json_success();
251 633 }
252 634
253 635 /**
254 - * Show the rating notice.
636 + * Load deactivation survey modal.
255 637 */
256 - public function show_rating_notice() {
257 - // @since 2.6.1
258 - if ( get_site_option( 'defender_rating_success', apply_filters( 'wd_display_rating', false ) ) ) {
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 ) ) {
259 646 return;
260 647 }
261 648
262 - $install_date = get_site_option( 'defender_free_install_date', false );
263 - // @since 2.6.1
264 - $days_later_dismiss = get_site_option( 'defender_days_rating_later_dismiss',
265 - apply_filters( 'wd_dismiss_rating', false )
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'
266 656 );
267 657
268 - if (
269 - $install_date && ! $days_later_dismiss
270 - && current_time( 'timestamp' ) > strtotime( '+7 days', $install_date )
271 - ) { ?>
272 - <div id="defender-free-usage-notice"
273 - class="defender-rating-notice notice notice-info"
274 - data-nonce="<?php echo esc_attr( wp_create_nonce( 'defender_dismiss_notification' ) ); ?>">
275 -
276 - <p style="color: #72777C; line-height: 22px;"><?php esc_html_e( 'We\'ve spent countless hours developing Defender and making it free for you to use. We would really appreciate it if you dropped us a quick rating!', 'wpdef' ); ?></p>
277 -
278 - <p>
279 - <button type="button" class="button button-primary button-large"
280 - data-prop="defender_rating_success"><?php esc_html_e( 'Rate Defender', 'wpdef' ); ?></button>
281 - <a href="#" class="dismiss"
282 - style="margin-left: 11px; color: #555; line-height: 16px; font-weight: 500; text-decoration: none;"
283 - data-prop="defender_days_rating_later_dismiss"><?php esc_html_e( 'Maybe later', 'wpdef' ); ?></a>
284 - </p>
285 - </div>
286 - <?php
287 - }
288 - ?>
289 -
290 - <script type="text/javascript">
291 - jQuery('.defender-rating-notice a, .defender-rating-notice button').on('click', function (e) {
292 - e.preventDefault();
293 -
294 - var $notice = jQuery(e.currentTarget).closest('.defender-rating-notice'),
295 - prop = jQuery(this).data('prop'),
296 - ajaxUrl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
297 -
298 - if ('defender_rating_success' === prop) {
299 - window.open('https://wordpress.org/support/plugin/defender-security/reviews/#new-post', '_blank');
300 - }
301 -
302 - jQuery.post(
303 - ajaxUrl,
304 - {
305 - action: 'defender_dismiss_notification',
306 - prop: prop,
307 - _ajax_nonce: $notice.data('nonce')
308 - }
309 - ).always(function () {
310 - $notice.hide();
311 - });
312 - });
313 - </script>
314 -
315 - <?php
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
316 662 }
317 663
318 664 /**
319 - * Show plugin update notice.
320 - *
321 - * @param string $plugin_file
322 - * @param array $response
323 - *
324 - * @since 2.8.3
665 + * Track deactivation.
325 666 */
326 - public function show_upgrade_notice( $plugin_file, $response ) {
327 - if ( version_compare( PHP_VERSION, '7.2', '>=' ) ) {
328 - return;
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 + );
329 675 }
330 - $plugin_data = (object) $response;
331 - // Preparing for the next major plugin v3.0.0 and subsequent versions.
332 - if ( isset( $plugin_data->new_version )
333 - && '3' === substr( trim( $plugin_data->new_version ), 0, 1 )
334 - && version_compare( DEFENDER_VERSION, '3', '<' )
335 - ){
336 - // Major plugin version or subsequent versions.
337 - $new_version = ( '3' === $plugin_data->new_version ) ? '3.0.0' : $plugin_data->new_version;
338 - // Collect notice.
339 - $notice = '<p>' . esc_html__( 'There is a new version of Defender available - Deprecating PHP version 7.1. and lower.', 'wpdef' ) . '</p>';
340 - $notice .= '<span style="display: inherit;background: #D63638;color: #FFFFFF;margin-top: 12px;padding: 10px 12px;border-radius: 2px;">';
341 - $notice .= '<strong>';
342 - $notice .= esc_html__( 'Important Upgrade Notice:', 'wpdef' );
343 - $notice .= '</strong> ';
344 - $notice .= sprintf(
345 - /* translators: %s: Version number. */
346 - esc_html__( 'The upcoming version %s is compatible only with PHP 7.2.0 and higher. Please make sure to upgrade your PHP version.', 'wpdef' ),
347 - $new_version
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' ) )
348 680 );
349 - $notice .= '</span>';
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 + }
350 696
351 - echo "<script type='text/javascript'>
352 - (function ($) {
353 - $(document).ready(function () {
354 - $( '.wp-list-table tr[data-plugin=\"" . esc_attr( $plugin_data->plugin ) . "\"] .notice-warning' ).html( '" . addslashes( $notice ) . "' ).css('padding-bottom', '10px');
355 - });
356 - })(jQuery);
357 - </script>";
358 - }
697 + wd_di()->get( Deactivation_Survey::class )->track_deactivation_survey( $properties );
698 +
699 + wp_send_json_success();
359 700 }
360 701 }