PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.4.0
Defender Security – Malware Scanner, Login Security & Firewall v5.4.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
defender-security / src / functions.php

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

815 lines 21.0 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-expert-services',
372 );
373
374 return in_array( defender_get_current_page(), $pages, true );
375 }
376
377 /**
378 * Return the high contrast css class if it is.
379 *
380 * @return bool
381 * @since 2.7.0
382 */
383 function defender_high_contrast() {
384 $model = new Main_Setting();
385
386 return $model->high_contrast_mode;
387 }
388
389 /**
390 * Add more cron schedules for plugin modules. E.g. schedules:
391 * cleaning completed Scan logs,
392 * cleaning temporary firewall IPs,
393 * send reports,
394 * update MaxMind DB.
395 *
396 * @param array $schedules The schedules.
397 *
398 * @return array
399 * @since 2.7.1
400 */
401 function defender_cron_schedules( $schedules ) {
402 if ( ! isset( $schedules['thirty_minutes'] ) ) {
403 $schedules['thirty_minutes'] = array(
404 'interval' => 30 * MINUTE_IN_SECONDS,
405 'display' => esc_html__( 'Every Half Hour', 'defender-security' ),
406 );
407 }
408 if ( ! isset( $schedules['weekly'] ) ) {
409 $schedules['weekly'] = array(
410 'interval' => WEEK_IN_SECONDS,
411 'display' => esc_html__( 'Weekly', 'defender-security' ),
412 );
413 }
414 if ( ! isset( $schedules['monthly'] ) ) {
415 $schedules['monthly'] = array(
416 'interval' => MONTH_IN_SECONDS,
417 'display' => esc_html__( 'Once Monthly', 'defender-security' ),
418 );
419 }
420 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
421 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
422 if ( ! isset( $schedules['thirty_days'] ) ) {
423 $schedules['thirty_days'] = array(
424 'interval' => 2592000,
425 'display' => esc_html__( '30 days', 'defender-security' ),
426 );
427 }
428 if ( ! isset( $schedules['sixty_days'] ) ) {
429 $schedules['sixty_days'] = array(
430 'interval' => 5184000,
431 'display' => esc_html__( '60 days', 'defender-security' ),
432 );
433 }
434 if ( ! isset( $schedules['ninety_days'] ) ) {
435 $schedules['ninety_days'] = array(
436 'interval' => 7776000,
437 'display' => esc_html__( '90 days', 'defender-security' ),
438 );
439 }
440 if ( ! isset( $schedules['six_months'] ) ) {
441 $schedules['six_months'] = array(
442 'interval' => 15780000,
443 'display' => esc_html__( '6 months', 'defender-security' ),
444 );
445 }
446 if ( ! isset( $schedules['one_year'] ) ) {
447 $schedules['one_year'] = array(
448 'interval' => 31536000,
449 'display' => esc_html__( '1 year', 'defender-security' ),
450 );
451 }
452
453 return $schedules;
454 }
455
456 /**
457 * Generate random string.
458 *
459 * @param int $length Length of random string.
460 * @param string $strings Characters to include in a random string.
461 *
462 * @return string
463 * @since 3.0.0
464 */
465 function defender_generate_random_string( $length = 16, $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
466 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
467 // Only use in test.
468 return constant( 'DEFENDER_2FA_SECRET' );
469 }
470
471 if ( ! is_string( $strings ) ) {
472 return '';
473 }
474
475 $secret = array();
476 for ( $i = 0; $i < $length; $i++ ) {
477 $secret[] = $strings[ Crypt::random_int( 0, strlen( $strings ) - 1 ) ];
478 }
479
480 return implode( '', $secret );
481 }
482
483 /**
484 * Either return array or echo json.
485 *
486 * @param mixed $data A Data to be returned or echoed.
487 * @param bool $success Is it a success or failure.
488 * @param bool $is_return True if data needs to be returned.
489 *
490 * @return array|void
491 * @since 3.0.0
492 */
493 function defender_maybe_echo_json( $data, $success, $is_return ) {
494 if ( true === $is_return ) {
495 return array(
496 'success' => $success,
497 'data' => $data,
498 );
499 } else {
500 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
501 }
502 }
503
504 /**
505 * Get translations for the 'defender-security' text domain.
506 *
507 * @return array List of words/phrases and their translations.
508 * @global Mo[] $l10n An array of all currently loaded text domains.
509 */
510 function defender_gettext_translations(): array {
511 global $l10n;
512
513 // Check if the 'defender-security' text domain is loaded.
514 if ( ! isset( $l10n['defender-security'] ) ) {
515 return array();
516 }
517
518 $items = array();
519
520 /**
521 * Go through all the translation entries in the 'defender-security' text domain.
522 *
523 * @var Translation_Entry $value
524 */
525 foreach ( $l10n['defender-security']->entries as $value ) {
526 // If there's a translation, use it; otherwise, use the original word/phrase.
527 $items[ $value->key() ] = empty( $value->translations ) ? $value->key() : $value->translations[0];
528 }
529
530 // Return the list of words/phrases and their translations.
531 return $items;
532 }
533
534 /**
535 * Get string replacement regardless of the operating system.
536 *
537 * @param string $path The path to be replaced.
538 *
539 * @return string
540 * @since 3.3.0
541 */
542 function defender_replace_line( $path ): string {
543 return str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path );
544 }
545
546 /**
547 * Generates the support ticket text for the Defender plugin.
548 *
549 * @return string The support ticket text, including the formatted link.
550 */
551 function defender_support_ticket_text(): string {
552 return sprintf(
553 /* translators: 1. Support link. */
554 esc_html__( 'Still, having trouble? %1$s.', 'defender-security' ),
555 '<a target="_blank" href="' . WP_DEFENDER_SUPPORT_LINK . '">' . esc_html__( 'Open a support ticket', 'defender-security' ) . '</a>'
556 );
557 }
558
559 /**
560 * The message is shown on the inappropriate access of Safe Repair feature.
561 *
562 * @return string
563 */
564 function defender_quarantine_pro_only(): string {
565 return esc_html__( 'Safe Repair feature is only for Pro', 'defender-security' );
566 }
567
568 /**
569 * Retrieves the user agent from the $_SERVER super global or returns a default string.
570 *
571 * @param string $default_string The default string to return if the user agent is empty. Default is an empty string.
572 *
573 * @return string The cleaned user agent or the default string.
574 */
575 function defender_get_user_agent( $default_string = '' ) {
576 $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' );
577
578 return ! empty( $user_agent ) ? User_Agent::fast_cleaning( $user_agent ) : $default_string;
579 }
580
581 /**
582 * Check if it is a CLI request.
583 *
584 * @return bool
585 * @since 4.2.0
586 */
587 function defender_is_wp_cli() {
588 return defined( 'WP_CLI' ) && WP_CLI;
589 }
590
591 /**
592 * Check if the current request is a REST API request.
593 * This function checks if the current request URI contains the REST API prefix,
594 * indicating that it's a request to the WordPress REST API.
595 *
596 * @return bool Whether the current request is a REST API request.
597 * @since 4.2.0
598 */
599 function defender_is_rest_api_request(): bool {
600 $request_uri = Request::get_request_uri();
601 if ( empty( $request_uri ) ) {
602 return false;
603 }
604
605 $rest_prefix = trailingslashit( rest_get_url_prefix() );
606
607 return false !== strpos( $request_uri, $rest_prefix );
608 }
609
610 /**
611 * Handle deprecated functions by logging or triggering actions.
612 * This function is a wrapper for WordPress's _deprecated_function() function.
613 * It is used to handle deprecated functions by either logging a deprecation
614 * message or triggering an action. It checks if the current request is an AJAX
615 * request or a REST API request and acts accordingly.
616 *
617 * @param string $function_name The function that was called.
618 * @param string $version The version number that deprecated the function.
619 * @param string $replacement (Optional) The function that should be used instead.
620 *
621 * @return void
622 * @since 4.2.0
623 */
624 function defender_deprecated_function( string $function_name, string $version, string $replacement = '' ): void {
625 /**
626 * Filters whether to trigger an error for deprecated functions.
627 *
628 * @param bool $trigger Whether to trigger the error for deprecated functions. Default false.
629 *
630 * @since 4.2.1
631 */
632 if ( WP_DEBUG && apply_filters( 'defender_deprecated_function_trigger_error', false ) ) {
633 if ( wp_doing_ajax() || defender_is_rest_api_request() ) {
634 do_action( 'deprecated_function_run', $function_name, $replacement, $version );
635
636 $log_string = "Function {$function_name} is deprecated since version {$version}!";
637 $log_string .= $replacement ? " Use {$replacement} instead." : '';
638 wp_die( esc_html( $log_string ) );
639 } else {
640 _deprecated_function( esc_html( $function_name ), esc_html( $version ), esc_html( $replacement ) );
641 }
642 }
643 }
644
645 if ( ! function_exists( 'defender_get_data_from_request' ) ) {
646 /**
647 * Retrieves the value of a specific server data key after sanitizing it.
648 *
649 * @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.
650 * @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.
651 * @param string $nonce_key The nonce key for verification.
652 * @param string $nonce_action The nonce action for verification.
653 *
654 * @return array|string|null The sanitized value of the server data key.
655 */
656 function defender_get_data_from_request(
657 ?string $key,
658 string $source,
659 string $nonce_key = '',
660 string $nonce_action = ''
661 ) {
662 if ( in_array( $source, array( 'r', 'p', 'g' ), true ) ) {
663 if (
664 ! empty( $nonce_key ) && ! wp_verify_nonce(
665 sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_key ] ?? '' ) ),
666 $nonce_action
667 )
668 ) {
669 return null;
670 }
671 }
672 switch ( $source ) {
673 case 'r':
674 $data = $_REQUEST;
675 break;
676 case 'p':
677 $data = $_POST;
678 break;
679 case 'g':
680 $data = $_GET;
681 break;
682 case 's':
683 $data = $_SERVER;
684 break;
685 case 'c':
686 $data = $_COOKIE;
687 break;
688 case 'f':
689 $data = $_FILES;
690 break;
691 default:
692 $data = array();
693 break;
694 }
695
696 if ( empty( $key ) ) {
697 return $data;
698 } elseif ( 's' === $source ) {
699 return sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) );
700 } elseif ( 'data' === $key ) {
701 return json_decode( sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) ), true );
702 } elseif ( 'f' === $source && 'file' === $key && isset( $data[ $key ] ) ) {
703 if ( ! empty( $data[ $key ]['name'] ) ) {
704 $data[ $key ]['name'] = sanitize_file_name( $data[ $key ]['name'] );
705 }
706
707 return $data[ $key ];
708 }
709
710 return sanitize_text_field( $data[ $key ] ?? '' );
711 }
712 }
713
714 /**
715 * Check if arrays are same.
716 *
717 * @param array $array1 The first array to compare.
718 * @param array $array2 The second array to compare.
719 *
720 * @return bool True if arrays are equal, false otherwise.
721 */
722 function defender_are_arrays_equal( $array1, $array2 ): bool {
723 if ( count( $array1 ) !== count( $array2 ) ) {
724 return false;
725 }
726
727 // Sort both arrays.
728 sort( $array1 );
729 sort( $array2 );
730
731 return $array1 === $array2;
732 }
733
734 /**
735 * Get a feature state on WPMU DEV hosting.
736 *
737 * @param string $feature_key The feature key, e.g. xmlrpc_block, globaliplist or waf.
738 *
739 * @return bool|string True or false if the feature is enabled or disabled, or an empty string if the feature is not found.
740 */
741 function defender_get_hosting_feature_state( string $feature_key ) {
742 if ( function_exists( 'wpmudev_hosting_features' ) ) {
743 $states = wpmudev_hosting_features();
744
745 return isset( $states[ $feature_key ] ) ? $states[ $feature_key ] : '';
746 }
747
748 return '';
749 }
750
751 /**
752 * Get an internal log file name.
753 *
754 * @return string
755 */
756 function wd_internal_log(): string {
757 return Central::INTERNAL_LOG;
758 }
759
760 /**
761 * Retrieves the current time, allowing overrides for testing.
762 *
763 * @return int The current timestamp.
764 */
765 function defender_get_current_time() {
766 /**
767 * Filter the current time.
768 *
769 * @since 5.2.0
770 *
771 * @param int $time The current timestamp.
772 *
773 * @return int The current/filtered timestamp.
774 */
775 return (int) apply_filters( 'wpdef_current_time', time() );
776 }
777
778 /**
779 * Get the current site domain.
780 *
781 * @return string The determined domain name.
782 */
783 function defender_get_domain() {
784 static $domain = '';
785
786 if ( '' === $domain ) {
787 $domain = is_multisite()
788 ? ( get_network()->domain ?? '' )
789 : wp_parse_url( get_site_url(), PHP_URL_HOST );
790 }
791
792 /**
793 * Filter the current site domain.
794 *
795 * @param string $domain The determined domain name.
796 *
797 * @return string The filtered domain name.
798 *
799 * @since 5.2.0
800 */
801 $domain = (string) apply_filters( 'wpdef_current_site_domain', $domain );
802
803 return esc_html( $domain );
804 }
805
806 /**
807 * WP_DEFENDER_PRO sometimes doesn't match WPMUDEV::is_pro().
808 *
809 * @return bool.
810 */
811 function defender_is_wp_org_version(): bool {
812 return ! wd_di()->get( WPMUDEV::class )->is_pro()
813 && ( defined( 'WP_DEFENDER_PRO' ) && ! WP_DEFENDER_PRO );
814 }
815