PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.1
Defender Security – Malware Scanner, Login Security & Firewall v6.2.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / functions.php

functions.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.1, at src/functions.php

925 lines 25.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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
19 if ( ! defined( 'ABSPATH' ) ) {
20 die;
21 }
22
23 /**
24 * Generates the URL for a given asset path in the Defender plugin.
25 *
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.
30 */
31 function defender_asset_url( string $path, bool $print_url = false ) {
32 $url = untrailingslashit( WP_DEFENDER_BASE_URL ) . $path;
33
34 if ( ! $print_url ) {
35 return $url;
36 }
37
38 echo esc_url_raw( $url );
39 }
40
41 /**
42 * Generates the absolute path for a given path relative to the Defender plugin directory.
43 *
44 * @param string $path The relative path within the Defender plugin directory.
45 *
46 * @return string The absolute path.
47 */
48 function defender_path( string $path ): string {
49 $base_path = plugin_dir_path( __DIR__ );
50
51 return $base_path . $path;
52 }
53
54 /**
55 * Sanitize submitted data.
56 *
57 * @param array $data The data to sanitize.
58 *
59 * @return array
60 */
61 function defender_sanitize_data( $data ) {
62 foreach ( $data as $key => &$value ) {
63 if ( is_array( $value ) ) {
64 $value = defender_sanitize_data( $value );
65 } else {
66 $value = sanitize_textarea_field( $value );
67 }
68 }
69
70 return $data;
71 }
72
73 /**
74 * Retrieve wp-config.php file path.
75 *
76 * @return string
77 */
78 function defender_wp_config_path(): string {
79 if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
80 return ABSPATH . 'wp-config.php';
81 }
82
83 if (
84 file_exists( dirname( ABSPATH ) . '/wp-config.php' )
85 && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' )
86 ) {
87 return dirname( ABSPATH ) . '/wp-config.php';
88 }
89
90 return ( defined( 'WD_TEST' ) && WD_TEST ) ? '/tmp/wordpress-tests-lib/wp-tests-config.php' : '';
91 }
92
93 /**
94 * Check whether we're on Windows platform or not.
95 *
96 * @return bool
97 */
98 function defender_is_windows(): bool {
99 return '\\' === DIRECTORY_SEPARATOR;
100 }
101
102 /**
103 * Returns the global DI container for the WP Defender plugin.
104 *
105 * @return \WP_Defender\Component\Container The global DI container.
106 */
107 function wd_di() {
108 global $wp_defender_di;
109
110 return $wp_defender_di;
111 }
112
113 /**
114 * Returns the global Central object for the WP Defender plugin.
115 *
116 * @return Central
117 */
118 function wd_central() {
119 global $wp_defender_central;
120
121 return $wp_defender_central;
122 }
123
124 /**
125 * Get base action.
126 *
127 * @return string
128 * @since 2.8.0
129 */
130 function defender_base_action(): string {
131 return 'wp_defender/v1/hub/';
132 }
133
134 /**
135 * Get backward compatibility. Forminator uses this method.
136 *
137 * @return array
138 */
139 function defender_backward_compatibility() {
140 $wpmu_dev = new WPMUDEV();
141 $two_fa_settings = new \WP_Defender\Model\Setting\Two_Fa();
142 $controller = wd_di()->get( Two_Factor::class );
143 $collection = $controller->dump_routes_and_nonces();
144 $lost_url = add_query_arg(
145 array(
146 'action' => defender_base_action(),
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' ),
150 ),
151 admin_url( 'admin-ajax.php' )
152 );
153
154 return array(
155 'is_free' => ! $wpmu_dev->is_pro(),
156 'plugin_url' => defender_asset_url( '' ),
157 'two_fa_settings' => $two_fa_settings,
158 'two_fa_component' => Two_Fa::class,
159 'lost_url' => $lost_url,
160 );
161 }
162
163 /**
164 * Polyfill functions for supporting WordPress 5.3.
165 *
166 * @since 2.4.2
167 */
168 if ( ! function_exists( 'wp_timezone_string' ) ) {
169 /**
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.
172 *
173 * @return string PHP timezone string or a ±HH:MM offset.
174 * @since 5.3.0
175 */
176 function wp_timezone_string() {
177 $timezone_string = get_option( 'timezone_string' );
178
179 if ( '' !== $timezone_string ) {
180 return $timezone_string;
181 }
182
183 $offset = (float) get_option( 'gmt_offset' );
184 $hours = (int) $offset;
185 $minutes = ( $offset - $hours );
186
187 $sign = ( $offset < 0 ) ? '-' : '+';
188 $abs_hour = abs( $hours );
189 $abs_mins = abs( $minutes * 60 );
190 $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
191
192 return $tz_offset;
193 }
194 }
195
196 if ( ! function_exists( 'wp_timezone' ) ) {
197 /**
198 * Retrieves the timezone from site settings as a `DateTimeZone` object.
199 * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
200 *
201 * @return DateTimeZone Timezone object.
202 * @since 5.3.0
203 */
204 function wp_timezone() {
205 return new DateTimeZone( wp_timezone_string() );
206 }
207 }
208
209 /**
210 * Get hostname.
211 *
212 * @return string
213 */
214 function defender_get_hostname(): string {
215 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
216 $host = str_replace( 'www.', '', $host );
217 $host = explode( '.', $host );
218 $host = array_shift( $host );
219
220 return $host;
221 }
222
223 if ( ! function_exists( 'sanitize_mask_url' ) ) {
224 /**
225 * Sanitizes the mask login URL allowing uppercase letters,
226 * Replacing whitespace and a few other characters with dashes and
227 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
228 * Whitespace becomes a dash.
229 *
230 * @param string $title The title to be sanitized.
231 *
232 * @return string The sanitized title.
233 */
234 function sanitize_mask_url( $title ) {
235 $title = wp_strip_all_tags( $title );
236 // Preserve escaped octets.
237 $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
238 // Remove percent signs that are not part of an octet.
239 $title = str_replace( '%', '', $title );
240 // Restore octets.
241 $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );
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 }
253 }
254
255 // Kill entities.
256 $title = preg_replace( '/&.+?;/', '', $title );
257 $title = str_replace( '.', '-', $title );
258 $title = preg_replace( '/[^%a-zA-Z0-9 _-]/', '', $title );
259 $title = preg_replace( '/\s+/', '-', $title );
260
261 return preg_replace( '|-+|', '-', $title );
262 }
263 }
264
265 /**
266 * Return the noreply email.
267 * A utility function which will return common noreply from address.
268 *
269 * @param string $filter_tag Tag name of the filter to override email address.
270 *
271 * @return string Noreply email.
272 */
273 function defender_noreply_email( string $filter_tag = '' ) {
274 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
275
276 if ( 'www.' === substr( $host, 0, 4 ) ) {
277 $host = substr( $host, 4 );
278 }
279
280 $no_reply_email = 'noreply@' . $host;
281
282 if ( strlen( $filter_tag ) > 0 ) {
283 $no_reply_email = apply_filters( $filter_tag, $no_reply_email );
284 }
285
286 return $no_reply_email;
287 }
288
289 /**
290 * Get data of the whitelabel feature from WPMUDEV Dashboard:
291 * hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
292 *
293 * @return array
294 * @since 2.5.5
295 */
296 function defender_white_label_status() {
297 /* translators: %s: heart icon */
298 $footer_text = sprintf( esc_html__( 'Made with %s by WPMU DEV', 'defender-security' ), '<i class="sui-icon-heart"></i>' );
299 $custom_image = apply_filters( 'wpmudev_branding_hero_image', '' );
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;
304
305 return array(
306 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ),
307 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
308 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ),
309 'hero_image' => $custom_image,
310 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ),
311 'is_unbranded' => '' === $custom_image && $whitelabeled,
312 'is_rebranded' => '' !== $custom_image && $whitelabeled,
313 );
314 }
315
316 /**
317 * Get data of the whitelabel feature, e.g. hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
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 *
399 * @since 2.5.5
400 */
401 function defender_no_fresh_install() {
402 if ( false === get_site_option( 'wd_nofresh_install', false ) ) {
403 update_site_option( 'wd_nofresh_install', true );
404 }
405 }
406
407 /**
408 * Polyfill for PHP version < 7.3.
409 */
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 */
418 function array_key_first( array $arr ) {
419 $arr_keys = array_keys( $arr );
420
421 return $arr_keys[0] ?? null;
422 }
423 }
424
425 /**
426 * Fetch request url.
427 *
428 * @return string
429 */
430 function defender_get_request_url(): string {
431 return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) );
432 }
433
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 /**
444 * Check that current page is from Defender.
445 *
446 * @return bool
447 */
448 function is_defender_page(): bool {
449 $pages = array(
450 'wp-defender',
451 'wdf-hardener',
452 'wdf-scan',
453 'wdf-logging',
454 'wdf-ip-lockout',
455 'wdf-waf',
456 'wdf-2fa',
457 'wdf-advanced-tools',
458 'wdf-notification',
459 'wdf-setting',
460 'wdf-expert-services',
461 );
462
463 return in_array( defender_get_current_page(), $pages, true );
464 }
465
466 /**
467 * Return the high contrast css class if it is.
468 *
469 * @return bool
470 * @since 2.7.0
471 */
472 function defender_high_contrast() {
473 $model = new Main_Setting();
474
475 return $model->high_contrast_mode;
476 }
477
478 /**
479 * Add more cron schedules for plugin modules. E.g. schedules:
480 * cleaning completed Scan logs,
481 * cleaning temporary firewall IPs,
482 * send reports,
483 * update MaxMind DB.
484 *
485 * @param array $schedules The schedules.
486 *
487 * @return array
488 * @since 2.7.1
489 */
490 function defender_cron_schedules( $schedules ) {
491 if ( ! isset( $schedules['thirty_minutes'] ) ) {
492 $schedules['thirty_minutes'] = array(
493 'interval' => 30 * MINUTE_IN_SECONDS,
494 'display' => esc_html__( 'Every Half Hour', 'defender-security' ),
495 );
496 }
497 if ( ! isset( $schedules['weekly'] ) ) {
498 $schedules['weekly'] = array(
499 'interval' => WEEK_IN_SECONDS,
500 'display' => esc_html__( 'Weekly', 'defender-security' ),
501 );
502 }
503 if ( ! isset( $schedules['monthly'] ) ) {
504 $schedules['monthly'] = array(
505 'interval' => MONTH_IN_SECONDS,
506 'display' => esc_html__( 'Once Monthly', 'defender-security' ),
507 );
508 }
509 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
510 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
511 if ( ! isset( $schedules['thirty_days'] ) ) {
512 $schedules['thirty_days'] = array(
513 'interval' => 2592000,
514 'display' => esc_html__( '30 days', 'defender-security' ),
515 );
516 }
517 if ( ! isset( $schedules['sixty_days'] ) ) {
518 $schedules['sixty_days'] = array(
519 'interval' => 5184000,
520 'display' => esc_html__( '60 days', 'defender-security' ),
521 );
522 }
523 if ( ! isset( $schedules['ninety_days'] ) ) {
524 $schedules['ninety_days'] = array(
525 'interval' => 7776000,
526 'display' => esc_html__( '90 days', 'defender-security' ),
527 );
528 }
529 if ( ! isset( $schedules['six_months'] ) ) {
530 $schedules['six_months'] = array(
531 'interval' => 15780000,
532 'display' => esc_html__( '6 months', 'defender-security' ),
533 );
534 }
535 if ( ! isset( $schedules['one_year'] ) ) {
536 $schedules['one_year'] = array(
537 'interval' => 31536000,
538 'display' => esc_html__( '1 year', 'defender-security' ),
539 );
540 }
541
542 return $schedules;
543 }
544
545 /**
546 * Generate random string.
547 *
548 * @param int $length Length of random string.
549 * @param string $strings Characters to include in a random string.
550 *
551 * @return string
552 * @since 3.0.0
553 */
554 function defender_generate_random_string( int $length = 16, string $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
555 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
556 // Only use in test.
557 return constant( 'DEFENDER_2FA_SECRET' );
558 }
559
560 $secret = array();
561 for ( $i = 0; $i < $length; $i++ ) {
562 $secret[] = $strings[ Crypt::random_int( 0, strlen( $strings ) - 1 ) ];
563 }
564
565 return implode( '', $secret );
566 }
567
568 /**
569 * Either return array or echo json.
570 *
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.
574 *
575 * @return array|void
576 * @since 3.0.0
577 */
578 function defender_maybe_echo_json( $data, $success, $is_return ) {
579 if ( true === $is_return ) {
580 return array(
581 'success' => $success,
582 'data' => $data,
583 );
584 } else {
585 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
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 /**
696 * Handle deprecated functions by logging or triggering actions.
697 * This function is a wrapper for WordPress's _deprecated_function() function.
698 * It is used to handle deprecated functions by either logging a deprecation
699 * message or triggering an action. It checks if the current request is an AJAX
700 * request or a REST API request and acts accordingly.
701 *
702 * @param string $function_name The function that was called.
703 * @param string $version The version number that deprecated the function.
704 * @param string $replacement (Optional) The function that should be used instead.
705 *
706 * @return void
707 * @since 4.2.0
708 */
709 function defender_deprecated_function( string $function_name, string $version, string $replacement = '' ): void {
710 /**
711 * Filters whether to trigger an error for deprecated functions.
712 *
713 * @param bool $trigger Whether to trigger the error for deprecated functions. Default false.
714 *
715 * @since 4.2.1
716 */
717 if ( WP_DEBUG && apply_filters( 'defender_deprecated_function_trigger_error', false ) ) {
718 if ( wp_doing_ajax() || defender_is_rest_api_request() ) {
719 do_action( 'deprecated_function_run', $function_name, $replacement, $version );
720
721 $log_string = "Function {$function_name} is deprecated since version {$version}!";
722 $log_string .= '' !== $replacement ? " Use {$replacement} instead." : '';
723 wp_die( esc_html( $log_string ) );
724 } else {
725 _deprecated_function( esc_html( $function_name ), esc_html( $version ), esc_html( $replacement ) );
726 }
727 }
728 }
729
730 if ( ! function_exists( 'defender_get_data_from_request' ) ) {
731 /**
732 * Retrieves the value of a specific server data key after sanitizing it.
733 *
734 * @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.
735 * @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.
736 * @param string $nonce_key The nonce key for verification.
737 * @param string $nonce_action The nonce action for verification.
738 *
739 * @return array|string|null The sanitized value of the server data key.
740 */
741 function defender_get_data_from_request(
742 ?string $key,
743 string $source,
744 string $nonce_key = '',
745 string $nonce_action = ''
746 ) {
747 if ( in_array( $source, array( 'r', 'p', 'g' ), true ) ) {
748 if (
749 '' !== $nonce_key && ! wp_verify_nonce(
750 sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_key ] ?? '' ) ),
751 $nonce_action
752 )
753 ) {
754 return null;
755 }
756 }
757 switch ( $source ) {
758 case 'r':
759 $data = $_REQUEST;
760 break;
761 case 'p':
762 $data = $_POST;
763 break;
764 case 'g':
765 $data = $_GET;
766 break;
767 case 's':
768 $data = $_SERVER;
769 break;
770 case 'c':
771 $data = $_COOKIE;
772 break;
773 case 'f':
774 $data = $_FILES;
775 break;
776 default:
777 $data = array();
778 break;
779 }
780
781 if ( null === $key || '' === $key ) {
782 return $data;
783 } elseif ( 's' === $source ) {
784 return sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) );
785 } elseif ( 'data' === $key ) {
786 return json_decode( sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) ), true );
787 } elseif ( 'f' === $source && 'file' === $key && isset( $data[ $key ] ) ) {
788 if ( isset( $data[ $key ]['name'] ) && '' !== $data[ $key ]['name'] ) {
789 $data[ $key ]['name'] = sanitize_file_name( $data[ $key ]['name'] );
790 }
791
792 return $data[ $key ];
793 }
794
795 return sanitize_text_field( $data[ $key ] ?? '' );
796 }
797 }
798
799 /**
800 * Check if arrays are same.
801 *
802 * @param array $array1 The first array to compare.
803 * @param array $array2 The second array to compare.
804 *
805 * @return bool True if arrays are equal, false otherwise.
806 */
807 function defender_are_arrays_equal( $array1, $array2 ): bool {
808 if ( count( $array1 ) !== count( $array2 ) ) {
809 return false;
810 }
811
812 // Sort both arrays.
813 sort( $array1 );
814 sort( $array2 );
815
816 return $array1 === $array2;
817 }
818
819 /**
820 * Get a feature state on WPMU DEV hosting.
821 *
822 * @param string $feature_key The feature key, e.g. xmlrpc_block, globaliplist or waf.
823 *
824 * @return bool|string True or false if the feature is enabled or disabled, or an empty string if the feature is not found.
825 */
826 function defender_get_hosting_feature_state( string $feature_key ) {
827 if ( function_exists( 'wpmudev_hosting_features' ) ) {
828 $states = wpmudev_hosting_features();
829
830 return isset( $states[ $feature_key ] ) ? $states[ $feature_key ] : '';
831 }
832
833 return '';
834 }
835
836 /**
837 * Is a site is from WPMU DEV unlimited hosting?
838 *
839 * @return bool
840 */
841 function defender_is_unlimited_hosting(): bool {
842 return defined( 'WPMUDEV_HOSTING_WEBSITE_ID' ) && '' !== WPMUDEV_HOSTING_WEBSITE_ID;
843 }
844
845 /**
846 * Get an internal log file name.
847 *
848 * @return string
849 */
850 function wd_internal_log(): string {
851 return Central::INTERNAL_LOG;
852 }
853
854 /**
855 * Retrieves the current time, allowing overrides for testing.
856 *
857 * @return int The current timestamp.
858 */
859 function defender_get_current_time(): int {
860 /**
861 * Filter the current time.
862 *
863 * @since 5.2.0
864 *
865 * @param int $time The current timestamp.
866 *
867 * @return int The current/filtered timestamp.
868 */
869 $current_time = apply_filters( 'wpdef_current_time', time() );
870 return ! is_int( $current_time ) ? (int) $current_time : $current_time;
871 }
872
873 /**
874 * Get the current site domain.
875 *
876 * @return string The determined domain name.
877 */
878 function defender_get_domain() {
879 static $domain = '';
880
881 if ( '' === $domain ) {
882 $domain = is_multisite()
883 ? ( get_network()->domain ?? '' )
884 : wp_parse_url( get_site_url(), PHP_URL_HOST );
885 }
886
887 /**
888 * Filter the current site domain.
889 *
890 * @param string $domain The determined domain name.
891 *
892 * @return string The filtered domain name.
893 *
894 * @since 5.2.0
895 */
896 $domain = apply_filters( 'wpdef_current_site_domain', $domain );
897 $domain = is_string( $domain ) ? $domain : (string) $domain;
898
899 return esc_html( $domain );
900 }
901
902 /**
903 * WP_DEFENDER_PRO sometimes doesn't match WPMUDEV::is_pro().
904 *
905 * @return bool
906 */
907 function defender_is_wp_org_version(): bool {
908 return ! wd_di()->get( WPMUDEV::class )->is_pro()
909 && ( defined( 'WP_DEFENDER_PRO' ) && ! WP_DEFENDER_PRO );
910 }
911
912 /**
913 * Get the user agent of the plugin.
914 *
915 * @return string
916 */
917 function defender_get_own_user_agent(): string {
918 return defender_is_wp_org_version()
919 ? sprintf( 'Mozilla/5.0 (compatible; Defender/%1$s)', DEFENDER_VERSION )
920 : sprintf(
921 'Mozilla/5.0 (compatible; WPMU DEV Defender/%1$s; +https://wpmudev.com)',
922 DEFENDER_VERSION
923 );
924 }
925