PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.3.1
Defender Security – Malware Scanner, Login Security & Firewall v5.3.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 5.3.1, at src/functions.php

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