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/functions.php +520 -179 3.1.0 → 6.3.0 View file →
@@ -1,26 +1,52 @@
1 1 <?php
2 +/**
3 + * This file contains all functions used in the plugin.
4 + *
5 + * @package WP_Defender
6 + */
7 +
8 +use WP_Defender\Central;
9 +use WP_Defender\Component\Crypt;
10 +use WP_Defender\Component\Two_Fa;
11 +use WP_Defender\Behavior\WPMUDEV;
12 +use WP_Defender\Component\User_Agent;
13 +use WP_Defender\Component\Config\Config_Hub_Helper;
14 +use WP_Defender\Controller\Two_Factor;
15 +use WP_Defender\Integrations\Dashboard_Whitelabel;
16 +use WP_Defender\Model\Setting\Main_Setting;
17 +use WP_Defender\Helper\Request;
18 +
2 19 if ( ! defined( 'ABSPATH' ) ) {
3 20 die;
4 21 }
5 22
6 23 /**
7 - * @param string $path
24 + * Generates the URL for a given asset path in the Defender plugin.
8 25 *
9 - * @return string
26 + * @param string $path The path of the asset.
27 + * @param bool $print_url Whether to print the URL or return it.
28 + *
29 + * @return string|void The URL of the asset.
10 30 */
11 -function defender_asset_url( $path ) {
12 - $base_url = plugin_dir_url( __DIR__ );
31 +function defender_asset_url( string $path, bool $print_url = false ) {
32 + $url = untrailingslashit( WP_DEFENDER_BASE_URL ) . $path;
13 33
14 - return untrailingslashit( $base_url ) . $path;
34 + if ( ! $print_url ) {
35 + return $url;
36 + }
37 +
38 + echo esc_url_raw( $url );
15 39 }
16 40
17 41 /**
18 - * @param string $path
42 + * Generates the absolute path for a given path relative to the Defender plugin directory.
19 43 *
20 - * @return string
44 + * @param string $path The relative path within the Defender plugin directory.
45 + *
46 + * @return string The absolute path.
21 47 */
22 -function defender_path( $path ) {
48 +function defender_path( string $path ): string {
23 49 $base_path = plugin_dir_path( __DIR__ );
24 50
25 51 return $base_path . $path;
26 52 }
@@ -27,9 +53,9 @@
27 53
28 54 /**
29 55 * Sanitize submitted data.
30 56 *
31 - * @param array $data
57 + * @param array $data The data to sanitize.
32 58 *
33 59 * @return array
34 60 */
35 61 function defender_sanitize_data( $data ) {
@@ -48,23 +74,21 @@
48 74 * Retrieve wp-config.php file path.
49 75 *
50 76 * @return string
51 77 */
52 -function defender_wp_config_path() {
78 +function defender_wp_config_path(): string {
53 79 if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
54 80 return ABSPATH . 'wp-config.php';
55 81 }
56 82
57 83 if (
58 - @file_exists( dirname( ABSPATH ) . '/wp-config.php' )
59 - && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' )
84 + file_exists( dirname( ABSPATH ) . '/wp-config.php' )
85 + && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' )
60 86 ) {
61 87 return dirname( ABSPATH ) . '/wp-config.php';
62 88 }
63 89
64 - if ( defined( 'WD_TEST' ) && WD_TEST ) {
65 - return '/tmp/wordpress-tests-lib/wp-tests-config.php';
66 - }
90 + return ( defined( 'WD_TEST' ) && WD_TEST ) ? '/tmp/wordpress-tests-lib/wp-tests-config.php' : '';
67 91 }
68 92
69 93 /**
70 94 * Check whether we're on Windows platform or not.
@@ -70,14 +94,16 @@
70 94 * Check whether we're on Windows platform or not.
71 95 *
72 96 * @return bool
73 97 */
74 -function defender_is_windows() {
98 +function defender_is_windows(): bool {
75 99 return '\\' === DIRECTORY_SEPARATOR;
76 100 }
77 101
78 102 /**
79 - * @return \DI\Container
103 + * Returns the global DI container for the WP Defender plugin.
104 + *
105 + * @return \WP_Defender\Component\Container The global DI container.
80 106 */
81 107 function wd_di() {
82 108 global $wp_defender_di;
83 109
@@ -84,9 +110,11 @@
84 110 return $wp_defender_di;
85 111 }
86 112
87 113 /**
88 - * @return \WP_Defender\Central
114 + * Returns the global Central object for the WP Defender plugin.
115 + *
116 + * @return Central
89 117 */
90 118 function wd_central() {
91 119 global $wp_defender_central;
92 120
@@ -93,12 +121,14 @@
93 121 return $wp_defender_central;
94 122 }
95 123
96 124 /**
125 + * Get base action.
126 + *
127 + * @return string
97 128 * @since 2.8.0
98 - * @return string
99 129 */
100 -function defender_base_action() {
130 +function defender_base_action(): string {
101 131 return 'wp_defender/v1/hub/';
102 132 }
103 133
104 134 /**
@@ -106,17 +136,18 @@
106 136 *
107 137 * @return array
108 138 */
109 139 function defender_backward_compatibility() {
110 - $wpmu_dev = new \WP_Defender\Behavior\WPMUDEV();
140 + $wpmu_dev = new WPMUDEV();
111 141 $two_fa_settings = new \WP_Defender\Model\Setting\Two_Fa();
112 - $controller = wd_di()->get( \WP_Defender\Controller\Two_Factor::class );
113 - $list = $controller->dump_routes_and_nonces();
142 + $controller = wd_di()->get( Two_Factor::class );
143 + $collection = $controller->dump_routes_and_nonces();
114 144 $lost_url = add_query_arg(
115 145 array(
116 146 'action' => defender_base_action(),
117 - '_def_nonce' => $list['nonces']['send_backup_code'],
118 - 'route' => $controller->check_route( $list['routes']['send_backup_code'] ),
147 + '_def_nonce' => $collection['nonces']['send_backup_code'],
148 + // Add a dummy values to avoid displaying errors, e.g. for the case with null.
149 + 'route' => $controller->check_route( $collection['routes']['send_backup_code'] ?? 'test' ),
119 150 ),
120 151 admin_url( 'admin-ajax.php' )
121 152 );
122 153
@@ -123,9 +154,9 @@
123 154 return array(
124 155 'is_free' => ! $wpmu_dev->is_pro(),
125 156 'plugin_url' => defender_asset_url( '' ),
126 157 'two_fa_settings' => $two_fa_settings,
127 - 'two_fa_component' => \WP_Defender\Component\Two_Fa::class,
158 + 'two_fa_component' => Two_Fa::class,
128 159 'lost_url' => $lost_url,
129 160 );
130 161 }
131 162
@@ -136,20 +167,17 @@
136 167 */
137 168 if ( ! function_exists( 'wp_timezone_string' ) ) {
138 169 /**
139 170 * Retrieves the timezone from site settings as a string.
171 + * Uses the `timezone_string` option to get a proper timezone if available, otherwise falls back to an offset.
140 172 *
141 - * Uses the `timezone_string` option to get a proper timezone if available,
142 - * otherwise falls back to an offset.
143 - *
173 + * @return string PHP timezone string or a ±HH:MM offset.
144 174 * @since 5.3.0
145 - *
146 - * @return string PHP timezone string or a ±HH:MM offset.
147 175 */
148 176 function wp_timezone_string() {
149 177 $timezone_string = get_option( 'timezone_string' );
150 178
151 - if ( $timezone_string ) {
179 + if ( '' !== $timezone_string ) {
152 180 return $timezone_string;
153 181 }
154 182
155 183 $offset = (float) get_option( 'gmt_offset' );
@@ -167,14 +195,12 @@
167 195
168 196 if ( ! function_exists( 'wp_timezone' ) ) {
169 197 /**
170 198 * Retrieves the timezone from site settings as a `DateTimeZone` object.
171 - *
172 199 * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
173 200 *
201 + * @return DateTimeZone Timezone object.
174 202 * @since 5.3.0
175 - *
176 - * @return DateTimeZone Timezone object.
177 203 */
178 204 function wp_timezone() {
179 205 return new DateTimeZone( wp_timezone_string() );
180 206 }
@@ -179,87 +205,18 @@
179 205 return new DateTimeZone( wp_timezone_string() );
180 206 }
181 207 }
182 208
183 -if ( ! function_exists( 'defender_wp_check_php_version' ) ) {
184 - /**
185 - * Polyfill for function of WP core wp_check_php_version().
186 - *
187 - * @since WP 5.1.0
188 - * @since WP 5.1.1 Added the {@see 'wp_is_php_version_acceptable'} filter.
189 - *
190 - * @return array|false Array of PHP version data. False on failure.
191 - */
192 - function defender_wp_check_php_version() {
193 - $version = phpversion();
194 - $key = md5( $version );
195 -
196 - $response = get_site_transient( 'php_check_' . $key );
197 - if ( false === $response ) {
198 - $url = 'http://api.wordpress.org/core/serve-happy/1.0/';
199 - if ( wp_http_supports( array( 'ssl' ) ) ) {
200 - $url = set_url_scheme( $url, 'https' );
201 - }
202 -
203 - $url = add_query_arg( 'php_version', $version, $url );
204 -
205 - $response = wp_remote_get( $url );
206 -
207 - if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
208 - return false;
209 - }
210 -
211 - /**
212 - * Response should be an array with:
213 - * 'recommended_version' - string - The PHP version recommended by WordPress.
214 - * 'is_supported' - boolean - Whether the PHP version is actively supported.
215 - * 'is_secure' - boolean - Whether the PHP version receives security updates.
216 - * 'is_acceptable' - boolean - Whether the PHP version is still acceptable for WordPress.
217 - */
218 - $response = json_decode( wp_remote_retrieve_body( $response ), true );
219 -
220 - if ( ! is_array( $response ) ) {
221 - return false;
222 - }
223 -
224 - set_site_transient( 'php_check_' . $key, $response, WEEK_IN_SECONDS );
225 - }
226 -
227 - if ( isset( $response['is_acceptable'] ) && $response['is_acceptable'] ) {
228 - /**
229 - * Filters whether the active PHP version is considered acceptable by WordPress.
230 - *
231 - * Returning false will trigger a PHP version warning to show up in the admin dashboard to administrators.
232 - *
233 - * This filter is only run if the wordpress.org Serve Happy API considers the PHP version acceptable, ensuring
234 - * that this filter can only make this check stricter, but not loosen it.
235 - *
236 - * @since 5.1.1
237 - *
238 - * @param bool $is_acceptable Whether the PHP version is considered acceptable. Default true.
239 - * @param string $version PHP version checked.
240 - */
241 - $response['is_acceptable'] = (bool) apply_filters( 'wp_is_php_version_acceptable', true, $version );
242 - }
243 -
244 - return $response;
245 - }
246 -}
247 -
248 209 /**
249 210 * Get hostname.
250 211 *
251 - * @return string|null
212 + * @return string
252 213 */
253 -function defender_get_hostname() {
254 - $host = parse_url( get_site_url(), PHP_URL_HOST );
214 +function defender_get_hostname(): string {
215 + $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
255 216 $host = str_replace( 'www.', '', $host );
256 217 $host = explode( '.', $host );
257 - if ( is_array( $host ) ) {
258 - $host = array_shift( $host );
259 - } else {
260 - $host = null;
261 - }
218 + $host = array_shift( $host );
262 219
263 220 return $host;
264 221 }
265 222
@@ -269,13 +226,14 @@
269 226 * Replacing whitespace and a few other characters with dashes and
270 227 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
271 228 * Whitespace becomes a dash.
272 229 *
273 - * @param string $title The title to be sanitized.
230 + * @param string $title The title to be sanitized.
231 + *
274 232 * @return string The sanitized title.
275 233 */
276 234 function sanitize_mask_url( $title ) {
277 - $title = strip_tags( $title );
235 + $title = wp_strip_all_tags( $title );
278 236 // Preserve escaped octets.
279 237 $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
280 238 // Remove percent signs that are not part of an octet.
281 239 $title = str_replace( '%', '', $title );
@@ -280,35 +238,40 @@
280 238 // Remove percent signs that are not part of an octet.
281 239 $title = str_replace( '%', '', $title );
282 240 // Restore octets.
283 241 $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );
284 -
285 - if ( seems_utf8( $title ) ) {
286 - $title = utf8_uri_encode( $title, 200 );
242 + // Support for WordPress 6.9+ and older versions.
243 + if ( function_exists( 'wp_is_valid_utf8' ) ) {
244 + // New function since WP 6.9.0.
245 + if ( wp_is_valid_utf8( $title ) ) {
246 + $title = utf8_uri_encode( $title, 200 );
247 + }
248 + } elseif ( function_exists( 'seems_utf8' ) ) {
249 + // Old method (before WP 6.9.0).
250 + if ( seems_utf8( $title ) ) { // phpcs:ignore WordPress.WP.DeprecatedFunctions.seems_utf8Found
251 + $title = utf8_uri_encode( $title, 200 );
252 + }
287 253 }
288 254
289 255 // Kill entities.
290 256 $title = preg_replace( '/&.+?;/', '', $title );
291 257 $title = str_replace( '.', '-', $title );
292 -
293 258 $title = preg_replace( '/[^%a-zA-Z0-9 _-]/', '', $title );
294 259 $title = preg_replace( '/\s+/', '-', $title );
295 - $title = preg_replace( '|-+|', '-', $title );
296 260
297 - return $title;
261 + return preg_replace( '|-+|', '-', $title );
298 262 }
299 263 }
300 264
301 265 /**
302 266 * Return the noreply email.
267 + * A utility function which will return common noreply from address.
303 268 *
304 - * An utility function which will return common noreply from address.
269 + * @param string $filter_tag Tag name of the filter to override email address.
305 270 *
306 - * @param string $filter_tag Tag name of the filter to override email address.
307 - *
308 271 * @return string Noreply email.
309 272 */
310 -function defender_noreply_email( $filter_tag = '' ) {
273 +function defender_noreply_email( string $filter_tag = '' ) {
311 274 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
312 275
313 276 if ( 'www.' === substr( $host, 0, 4 ) ) {
314 277 $host = substr( $host, 4 );
@@ -323,37 +286,22 @@
323 286 return $no_reply_email;
324 287 }
325 288
326 289 /**
327 - * Noreply email header.
328 - *
329 - * Generate noreply email header with HTML UTF-8 support.
330 - *
331 - * @param string $email Email.
332 - *
333 - * @return array Returns the email headers.
334 - */
335 -function defender_noreply_html_header( $email ) {
336 - $headers = array(
337 - 'From: Defender <' . $email . '>',
338 - 'Content-Type: text/html; charset=UTF-8',
339 - );
340 -
341 - return $headers;
342 -}
343 -
344 -/**
345 290 * Get data of the whitelabel feature from WPMUDEV Dashboard:
346 291 * hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
347 292 *
293 + * @return array
348 294 * @since 2.5.5
349 - * @return array
350 295 */
351 296 function defender_white_label_status() {
352 297 /* translators: %s: heart icon */
353 - $footer_text = sprintf( __( 'Made with %s by WPMU DEV', 'wpdef' ), '<i class="sui-icon-heart"></i>' );
298 + $footer_text = sprintf( esc_html__( 'Made with %s by WPMU DEV', 'defender-security' ), '<i class="sui-icon-heart"></i>' );
354 299 $custom_image = apply_filters( 'wpmudev_branding_hero_image', '' );
355 - $whitelabled = apply_filters( 'wpmudev_branding_hide_branding', false );
300 + $custom_image = is_string( $custom_image ) ? $custom_image : (string) $custom_image;
301 + $custom_image = trim( $custom_image );
302 + $whitelabeled = apply_filters( 'wpmudev_branding_hide_branding', false );
303 + $whitelabeled = is_bool( $whitelabeled ) ? $whitelabeled : (bool) $whitelabeled;
356 304
357 305 return array(
358 306 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ),
359 307 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
@@ -359,20 +307,100 @@
359 307 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
360 308 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ),
361 309 'hero_image' => $custom_image,
362 310 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ),
363 - 'is_unbranded' => empty( $custom_image ) && $whitelabled,
364 - 'is_rebranded' => ! empty( $custom_image ) && $whitelabled,
311 + 'is_unbranded' => '' === $custom_image && $whitelabeled,
312 + 'is_rebranded' => '' !== $custom_image && $whitelabeled,
365 313 );
366 314 }
367 315
368 316 /**
369 - * Indicate this is not fresh install.
317 + * Get data of the whitelabel feature, e.g. hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
370 318 *
319 + * @return array
320 + * @since 6.0.0
321 + */
322 +function defender_whitelabel_data(): array {
323 + $data = array(
324 + 'is_enabled' => false,
325 + );
326 +
327 + if ( wd_di()->get( WPMUDEV::class )->is_whitelabel_enabled() ) {
328 + /* translators: %s: heart icon */
329 + $footer_text = sprintf( esc_html__( 'Made with %s by WPMU DEV', 'defender-security' ), '<i class="sui-icon-heart"></i>' );
330 + $custom_image = apply_filters( 'wpmudev_branding_hero_image', '' );
331 + $custom_image = is_string( $custom_image ) ? $custom_image : (string) $custom_image;
332 + $custom_image = trim( $custom_image );
333 + $whitelabeled = apply_filters( 'wpmudev_branding_hide_branding', false );
334 + $whitelabeled = is_bool( $whitelabeled ) ? $whitelabeled : (bool) $whitelabeled;
335 + $whitelabel = new Dashboard_Whitelabel();
336 + $plugin_name = $whitelabel->can_whitelabel() ? $whitelabel->get_plugin_name( Config_Hub_Helper::WDP_ID ) : false;
337 +
338 + $plugin_icon_url = '';
339 + $plugin_icon_class = '';
340 + $plugin_icon_type = 'default';
341 +
342 + if ( $whitelabel->can_whitelabel() ) {
343 + $wl_settings = WPMUDEV_Dashboard::$whitelabel->get_settings();
344 + $pid = Config_Hub_Helper::WDP_ID;
345 + $labels_networkwide = (bool) ( $wl_settings['labels_networkwide'] ?? true );
346 + $labels_subsites = array_map( 'intval', (array) ( $wl_settings['labels_subsites'] ?? array() ) );
347 + $apply_labels = ! is_multisite()
348 + || is_network_admin()
349 + || $labels_networkwide
350 + || in_array( get_current_blog_id(), $labels_subsites, true );
351 +
352 + if ( $apply_labels && isset( $wl_settings['labels_config'][ $pid ] ) ) {
353 + $cfg = $wl_settings['labels_config'][ $pid ];
354 + $plugin_icon_type = sanitize_key( $cfg['icon_type'] ?? 'default' );
355 +
356 + switch ( $plugin_icon_type ) {
357 + case 'dashicon':
358 + $plugin_icon_class = sanitize_html_class( $cfg['icon_class'] ?? '' );
359 + break;
360 + case 'upload':
361 + $thumb_id = (int) ( $cfg['thumb_id'] ?? 0 );
362 + if ( $thumb_id ) {
363 + $url = wp_get_attachment_image_url( $thumb_id, 'thumbnail', true );
364 + $plugin_icon_url = $url ? $url : '';
365 + }
366 + break;
367 + case 'link':
368 + $plugin_icon_url = esc_url_raw( $cfg['icon_url'] ?? '' );
369 + break;
370 + case 'none':
371 + $plugin_icon_url = null;
372 + break;
373 + }
374 + }
375 + }
376 +
377 + $data = array(
378 + 'is_enabled' => true,
379 + 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ),
380 + 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
381 + 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ),
382 + 'hero_image' => $custom_image,
383 + 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ),
384 + 'is_unbranded' => '' === $custom_image && $whitelabeled,
385 + 'is_rebranded' => '' !== $custom_image && $whitelabeled,
386 + 'plugin_name' => is_string( $plugin_name ) && '' !== trim( $plugin_name ) ? $plugin_name : '',
387 + 'plugin_icon_url' => $plugin_icon_url,
388 + 'plugin_icon_class' => $plugin_icon_class,
389 + 'plugin_icon_type' => $plugin_icon_type,
390 + );
391 + }
392 +
393 + return $data;
394 +}
395 +
396 +/**
397 + * Indicate this is not fresh setup.
398 + *
371 399 * @since 2.5.5
372 400 */
373 401 function defender_no_fresh_install() {
374 - if ( empty( get_site_option( 'wd_nofresh_install' ) ) ) {
402 + if ( false === get_site_option( 'wd_nofresh_install', false ) ) {
375 403 update_site_option( 'wd_nofresh_install', true );
376 404 }
377 405 }
378 406
@@ -379,8 +407,15 @@
379 407 /**
380 408 * Polyfill for PHP version < 7.3.
381 409 */
382 410 if ( ! function_exists( 'array_key_first' ) ) {
411 + /**
412 + * Returns the first key of an array.
413 + *
414 + * @param array $arr The input array.
415 + *
416 + * @return mixed|null The first key of the array, or null if the array is empty.
417 + */
383 418 function array_key_first( array $arr ) {
384 419 $arr_keys = array_keys( $arr );
385 420
386 421 return $arr_keys[0] ?? null;
@@ -391,18 +426,27 @@
391 426 * Fetch request url.
392 427 *
393 428 * @return string
394 429 */
395 -function defender_get_request_url() {
430 +function defender_get_request_url(): string {
396 431 return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) );
397 432 }
398 433
399 434 /**
435 + * What is the current WP page?
436 + *
437 + * @return string
438 + */
439 +function defender_get_current_page(): string {
440 + return defender_get_data_from_request( 'page', 'g' );
441 +}
442 +
443 +/**
400 444 * Check that current page is from Defender.
401 445 *
402 446 * @return bool
403 -*/
404 -function defender_current_page() {
447 + */
448 +function is_defender_page(): bool {
405 449 $pages = array(
406 450 'wp-defender',
407 451 'wdf-hardener',
408 452 'wdf-scan',
@@ -412,23 +456,22 @@
412 456 'wdf-2fa',
413 457 'wdf-advanced-tools',
414 458 'wdf-notification',
415 459 'wdf-setting',
416 - 'wdf-tutorial',
460 + 'wdf-expert-services',
417 461 );
418 - $page = $_GET['page'] ?? null;
419 462
420 - return in_array( $page, $pages, true );
463 + return in_array( defender_get_current_page(), $pages, true );
421 464 }
422 465
423 466 /**
424 467 * Return the high contrast css class if it is.
425 468 *
469 + * @return bool
426 470 * @since 2.7.0
427 - * @return bool
428 471 */
429 472 function defender_high_contrast() {
430 - $model = new \WP_Defender\Model\Setting\Main_Setting();
473 + $model = new Main_Setting();
431 474
432 475 return $model->high_contrast_mode;
433 476 }
434 477
@@ -437,31 +480,31 @@
437 480 * cleaning completed Scan logs,
438 481 * cleaning temporary firewall IPs,
439 482 * send reports,
440 483 * update MaxMind DB.
441 - * @since 2.7.1
442 484 *
443 - * @param array $schedules
485 + * @param array $schedules The schedules.
444 486 *
445 487 * @return array
488 + * @since 2.7.1
446 489 */
447 490 function defender_cron_schedules( $schedules ) {
448 491 if ( ! isset( $schedules['thirty_minutes'] ) ) {
449 492 $schedules['thirty_minutes'] = array(
450 493 'interval' => 30 * MINUTE_IN_SECONDS,
451 - 'display' => __( 'Every Half Hour', 'wpdef' ),
494 + 'display' => esc_html__( 'Every Half Hour', 'defender-security' ),
452 495 );
453 496 }
454 497 if ( ! isset( $schedules['weekly'] ) ) {
455 498 $schedules['weekly'] = array(
456 499 'interval' => WEEK_IN_SECONDS,
457 - 'display' => __( 'Weekly', 'wpdef' ),
500 + 'display' => esc_html__( 'Weekly', 'defender-security' ),
458 501 );
459 502 }
460 503 if ( ! isset( $schedules['monthly'] ) ) {
461 504 $schedules['monthly'] = array(
462 505 'interval' => MONTH_IN_SECONDS,
463 - 'display' => __( 'Once Monthly', 'wpdef' ),
506 + 'display' => esc_html__( 'Once Monthly', 'defender-security' ),
464 507 );
465 508 }
466 509 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
467 510 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
@@ -467,33 +510,33 @@
467 510 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
468 511 if ( ! isset( $schedules['thirty_days'] ) ) {
469 512 $schedules['thirty_days'] = array(
470 513 'interval' => 2592000,
471 - 'display' => __( '30 days', 'wpdef' ),
514 + 'display' => esc_html__( '30 days', 'defender-security' ),
472 515 );
473 516 }
474 517 if ( ! isset( $schedules['sixty_days'] ) ) {
475 518 $schedules['sixty_days'] = array(
476 519 'interval' => 5184000,
477 - 'display' => __( '60 days', 'wpdef' ),
520 + 'display' => esc_html__( '60 days', 'defender-security' ),
478 521 );
479 522 }
480 523 if ( ! isset( $schedules['ninety_days'] ) ) {
481 524 $schedules['ninety_days'] = array(
482 525 'interval' => 7776000,
483 - 'display' => __( '90 days', 'wpdef' ),
526 + 'display' => esc_html__( '90 days', 'defender-security' ),
484 527 );
485 528 }
486 529 if ( ! isset( $schedules['six_months'] ) ) {
487 530 $schedules['six_months'] = array(
488 531 'interval' => 15780000,
489 - 'display' => __( '6 months', 'wpdef' ),
532 + 'display' => esc_html__( '6 months', 'defender-security' ),
490 533 );
491 534 }
492 535 if ( ! isset( $schedules['one_year'] ) ) {
493 536 $schedules['one_year'] = array(
494 537 'interval' => 31536000,
495 - 'display' => __( '1 year', 'wpdef' ),
538 + 'display' => esc_html__( '1 year', 'defender-security' ),
496 539 );
497 540 }
498 541
499 542 return $schedules;
@@ -501,27 +544,23 @@
501 544
502 545 /**
503 546 * Generate random string.
504 547 *
505 - * @param int $length Length of random string.
506 - * @param string $strings Characters to include in a random string.
548 + * @param int $length Length of random string.
549 + * @param string $strings Characters to include in a random string.
507 550 *
551 + * @return string
508 552 * @since 3.0.0
509 - * @return string
510 553 */
511 -function defender_generate_random_string( $length = 16, $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
554 +function defender_generate_random_string( int $length = 16, string $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
512 555 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
513 556 // Only use in test.
514 557 return constant( 'DEFENDER_2FA_SECRET' );
515 558 }
516 559
517 - if ( ! is_string( $strings ) ) {
518 - return '';
519 - }
520 -
521 560 $secret = array();
522 - for ( $i = 0; $i < $length; $i ++ ) {
523 - $secret[] = $strings[ random_int( 0, strlen( $strings ) - 1 ) ];
561 + for ( $i = 0; $i < $length; $i++ ) {
562 + $secret[] = $strings[ Crypt::random_int( 0, strlen( $strings ) - 1 ) ];
524 563 }
525 564
526 565 return implode( '', $secret );
527 566 }
@@ -528,17 +567,17 @@
528 567
529 568 /**
530 569 * Either return array or echo json.
531 570 *
532 - * @param mixed $data A Data to be returned or echoed.
533 - * @param bool $success Is it a success or failure.
534 - * @param bool $return True if data needs to be returned.
571 + * @param mixed $data A Data to be returned or echoed.
572 + * @param bool $success Is it a success or failure.
573 + * @param bool $is_return True if data needs to be returned.
535 574 *
575 + * @return array|void
536 576 * @since 3.0.0
537 - * @return array|void
538 577 */
539 -function defender_maybe_echo_json( $data, $success, $return ) {
540 - if ( true === $return ) {
578 +function defender_maybe_echo_json( $data, $success, $is_return ) {
579 + if ( true === $is_return ) {
541 580 return array(
542 581 'success' => $success,
543 582 'data' => $data,
544 583 );
@@ -544,5 +583,307 @@
544 583 );
545 584 } else {
546 585 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
547 586 }
587 +}
588 +
589 +/**
590 + * Get translations for the 'defender-security' text domain.
591 + *
592 + * @return array List of words/phrases and their translations.
593 + * @global Mo[] $l10n An array of all currently loaded text domains.
594 + */
595 +function defender_gettext_translations(): array {
596 + global $l10n;
597 +
598 + // Check if the 'defender-security' text domain is loaded.
599 + if ( ! isset( $l10n['defender-security'] ) ) {
600 + return array();
601 + }
602 +
603 + $items = array();
604 +
605 + /**
606 + * Go through all the translation entries in the 'defender-security' text domain.
607 + *
608 + * @var Translation_Entry $value
609 + */
610 + foreach ( $l10n['defender-security']->entries as $value ) {
611 + // If there's a translation, use it; otherwise, use the original word/phrase.
612 + $items[ $value->key() ] = isset( $value->translations[0] ) ? $value->translations[0] : $value->key();
613 + }
614 +
615 + // Return the list of words/phrases and their translations.
616 + return $items;
617 +}
618 +
619 +/**
620 + * Get string replacement regardless of the operating system.
621 + *
622 + * @param string $path The path to be replaced.
623 + *
624 + * @return string
625 + * @since 3.3.0
626 + */
627 +function defender_replace_line( $path ): string {
628 + return str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path );
629 +}
630 +
631 +/**
632 + * Generates the support ticket text for the Defender plugin.
633 + *
634 + * @return string The support ticket text, including the formatted link.
635 + */
636 +function defender_support_ticket_text(): string {
637 + return sprintf(
638 + /* translators: 1. Support link. */
639 + esc_html__( 'Still, having trouble? %1$s.', 'defender-security' ),
640 + '<a target="_blank" href="' . WP_DEFENDER_SUPPORT_LINK . '">' . esc_html__( 'Open a support ticket', 'defender-security' ) . '</a>'
641 + );
642 +}
643 +
644 +/**
645 + * The message is shown on the inappropriate access of Safe Repair feature.
646 + *
647 + * @return string
648 + */
649 +function defender_quarantine_pro_only(): string {
650 + return esc_html__( 'Safe Repair feature is only for Pro', 'defender-security' );
651 +}
652 +
653 +/**
654 + * Retrieves the user agent from the $_SERVER super global or returns a default string.
655 + *
656 + * @param string $default_string The default string to return if the user agent is empty. Default is an empty string.
657 + *
658 + * @return string The cleaned user agent or the default string.
659 + */
660 +function defender_get_user_agent( $default_string = '' ) {
661 + $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' );
662 +
663 + return '' !== $user_agent ? User_Agent::fast_cleaning( $user_agent ) : $default_string;
664 +}
665 +
666 +/**
667 + * Check if it is a CLI request.
668 + *
669 + * @return bool
670 + * @since 4.2.0
671 + */
672 +function defender_is_wp_cli() {
673 + return defined( 'WP_CLI' ) && WP_CLI;
674 +}
675 +
676 +/**
677 + * Check if the current request is a REST API request.
678 + * This function checks if the current request URI contains the REST API prefix,
679 + * indicating that it's a request to the WordPress REST API.
680 + *
681 + * @return bool Whether the current request is a REST API request.
682 + * @since 4.2.0
683 + */
684 +function defender_is_rest_api_request(): bool {
685 + $request_uri = Request::get_request_uri();
686 + if ( '' === $request_uri ) {
687 + return false;
688 + }
689 +
690 + $rest_prefix = trailingslashit( rest_get_url_prefix() );
691 +
692 + return false !== strpos( $request_uri, $rest_prefix );
693 +}
694 +
695 +if ( ! function_exists( 'defender_get_data_from_request' ) ) {
696 + /**
697 + * Retrieves the value of a specific server data key after sanitizing it.
698 + *
699 + * @param string|null $key The key of the data to retrieve from the request. If empty, the entire $_REQUEST or $_SERVER array will be returned.
700 + * @param string $source The source of the data. Default is 'r' for $_REQUEST. Other options are 's' for $_SERVER, 'c' for $_COOKIE and 'f' for $_FILES.
701 + * @param string $nonce_key The nonce key for verification.
702 + * @param string $nonce_action The nonce action for verification.
703 + *
704 + * @return array|string|null The sanitized value of the server data key.
705 + */
706 + function defender_get_data_from_request(
707 + ?string $key,
708 + string $source,
709 + string $nonce_key = '',
710 + string $nonce_action = ''
711 + ) {
712 + if ( in_array( $source, array( 'r', 'p', 'g' ), true ) ) {
713 + if (
714 + '' !== $nonce_key && ! wp_verify_nonce(
715 + sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_key ] ?? '' ) ),
716 + $nonce_action
717 + )
718 + ) {
719 + return null;
720 + }
721 + }
722 + switch ( $source ) {
723 + case 'r':
724 + $data = $_REQUEST;
725 + break;
726 + case 'p':
727 + $data = $_POST;
728 + break;
729 + case 'g':
730 + $data = $_GET;
731 + break;
732 + case 's':
733 + $data = $_SERVER;
734 + break;
735 + case 'c':
736 + $data = $_COOKIE;
737 + break;
738 + case 'f':
739 + $data = $_FILES;
740 + break;
741 + default:
742 + $data = array();
743 + break;
744 + }
745 +
746 + if ( null === $key || '' === $key ) {
747 + return $data;
748 + } elseif ( 's' === $source ) {
749 + return sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) );
750 + } elseif ( 'data' === $key ) {
751 + return json_decode( sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) ), true );
752 + } elseif ( 'f' === $source && 'file' === $key && isset( $data[ $key ] ) ) {
753 + if ( isset( $data[ $key ]['name'] ) && '' !== $data[ $key ]['name'] ) {
754 + $data[ $key ]['name'] = sanitize_file_name( $data[ $key ]['name'] );
755 + }
756 +
757 + return $data[ $key ];
758 + }
759 +
760 + return sanitize_text_field( $data[ $key ] ?? '' );
761 + }
762 +}
763 +
764 +/**
765 + * Check if arrays are same.
766 + *
767 + * @param array $array1 The first array to compare.
768 + * @param array $array2 The second array to compare.
769 + *
770 + * @return bool True if arrays are equal, false otherwise.
771 + */
772 +function defender_are_arrays_equal( $array1, $array2 ): bool {
773 + if ( count( $array1 ) !== count( $array2 ) ) {
774 + return false;
775 + }
776 +
777 + // Sort both arrays.
778 + sort( $array1 );
779 + sort( $array2 );
780 +
781 + return $array1 === $array2;
782 +}
783 +
784 +/**
785 + * Get a feature state on WPMU DEV hosting.
786 + *
787 + * @param string $feature_key The feature key, e.g. xmlrpc_block, globaliplist or waf.
788 + *
789 + * @return bool|string True or false if the feature is enabled or disabled, or an empty string if the feature is not found.
790 + */
791 +function defender_get_hosting_feature_state( string $feature_key ) {
792 + if ( function_exists( 'wpmudev_hosting_features' ) ) {
793 + $states = wpmudev_hosting_features();
794 +
795 + return isset( $states[ $feature_key ] ) ? $states[ $feature_key ] : '';
796 + }
797 +
798 + return '';
799 +}
800 +
801 +/**
802 + * Is a site is from WPMU DEV unlimited hosting?
803 + *
804 + * @return bool
805 + */
806 +function defender_is_unlimited_hosting(): bool {
807 + return defined( 'WPMUDEV_HOSTING_WEBSITE_ID' ) && '' !== WPMUDEV_HOSTING_WEBSITE_ID;
808 +}
809 +
810 +/**
811 + * Get an internal log file name.
812 + *
813 + * @return string
814 + */
815 +function wd_internal_log(): string {
816 + return Central::INTERNAL_LOG;
817 +}
818 +
819 +/**
820 + * Retrieves the current time, allowing overrides for testing.
821 + *
822 + * @return int The current timestamp.
823 + */
824 +function defender_get_current_time(): int {
825 + /**
826 + * Filter the current time.
827 + *
828 + * @since 5.2.0
829 + *
830 + * @param int $time The current timestamp.
831 + *
832 + * @return int The current/filtered timestamp.
833 + */
834 + $current_time = apply_filters( 'wpdef_current_time', time() );
835 + return ! is_int( $current_time ) ? (int) $current_time : $current_time;
836 +}
837 +
838 +/**
839 + * Get the current site domain.
840 + *
841 + * @return string The determined domain name.
842 + */
843 +function defender_get_domain() {
844 + static $domain = '';
845 +
846 + if ( '' === $domain ) {
847 + $domain = is_multisite()
848 + ? ( get_network()->domain ?? '' )
849 + : wp_parse_url( get_site_url(), PHP_URL_HOST );
850 + }
851 +
852 + /**
853 + * Filter the current site domain.
854 + *
855 + * @param string $domain The determined domain name.
856 + *
857 + * @return string The filtered domain name.
858 + *
859 + * @since 5.2.0
860 + */
861 + $domain = apply_filters( 'wpdef_current_site_domain', $domain );
862 + $domain = is_string( $domain ) ? $domain : (string) $domain;
863 +
864 + return esc_html( $domain );
865 +}
866 +
867 +/**
868 + * WP_DEFENDER_PRO sometimes doesn't match WPMUDEV::is_pro().
869 + *
870 + * @return bool
871 + */
872 +function defender_is_wp_org_version(): bool {
873 + return ! wd_di()->get( WPMUDEV::class )->is_pro()
874 + && ( defined( 'WP_DEFENDER_PRO' ) && ! WP_DEFENDER_PRO );
875 +}
876 +
877 +/**
878 + * Get the user agent of the plugin.
879 + *
880 + * @return string
881 + */
882 +function defender_get_own_user_agent(): string {
883 + return defender_is_wp_org_version()
884 + ? sprintf( 'Mozilla/5.0 (compatible; Defender/%1$s)', DEFENDER_VERSION )
885 + : sprintf(
886 + 'Mozilla/5.0 (compatible; WPMU DEV Defender/%1$s; +https://wpmudev.com)',
887 + DEFENDER_VERSION
888 + );
548 889 }