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

822 lines 21.4 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 ( ! $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
211 */
212 function defender_get_hostname(): string {
213 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
214 $host = str_replace( 'www.', '', $host );
215 $host = explode( '.', $host );
216 $host = array_shift( $host );
217
218 return $host;
219 }
220
221 if ( ! function_exists( 'sanitize_mask_url' ) ) {
222 /**
223 * Sanitizes the mask login URL allowing uppercase letters,
224 * Replacing whitespace and a few other characters with dashes and
225 * Limits the output to alphanumeric characters, underscore (_) and dash (-).
226 * Whitespace becomes a dash.
227 *
228 * @param string $title The title to be sanitized.
229 *
230 * @return string The sanitized title.
231 */
232 function sanitize_mask_url( $title ) {
233 $title = wp_strip_all_tags( $title );
234 // Preserve escaped octets.
235 $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
236 // Remove percent signs that are not part of an octet.
237 $title = str_replace( '%', '', $title );
238 // Restore octets.
239 $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );
240
241 if ( seems_utf8( $title ) ) {
242 $title = utf8_uri_encode( $title, 200 );
243 }
244
245 // Kill entities.
246 $title = preg_replace( '/&.+?;/', '', $title );
247 $title = str_replace( '.', '-', $title );
248 $title = preg_replace( '/[^%a-zA-Z0-9 _-]/', '', $title );
249 $title = preg_replace( '/\s+/', '-', $title );
250
251 return preg_replace( '|-+|', '-', $title );
252 }
253 }
254
255 /**
256 * Return the noreply email.
257 * A utility function which will return common noreply from address.
258 *
259 * @param string $filter_tag Tag name of the filter to override email address.
260 *
261 * @return string Noreply email.
262 */
263 function defender_noreply_email( string $filter_tag = '' ) {
264 $host = wp_parse_url( get_site_url(), PHP_URL_HOST );
265
266 if ( 'www.' === substr( $host, 0, 4 ) ) {
267 $host = substr( $host, 4 );
268 }
269
270 $no_reply_email = 'noreply@' . $host;
271
272 if ( strlen( $filter_tag ) > 0 ) {
273 $no_reply_email = apply_filters( $filter_tag, $no_reply_email );
274 }
275
276 return $no_reply_email;
277 }
278
279 /**
280 * Get data of the whitelabel feature from WPMUDEV Dashboard:
281 * hide_branding, hide_doc_link, footer_text, hero_image, change_footer.
282 *
283 * @return array
284 * @since 2.5.5
285 */
286 function defender_white_label_status() {
287 /* translators: %s: heart icon */
288 $footer_text = sprintf( esc_html__( 'Made with %s by WPMU DEV', 'defender-security' ), '<i class="sui-icon-heart"></i>' );
289 $custom_image = (string) apply_filters( 'wpmudev_branding_hero_image', '' );
290 $custom_image = trim( $custom_image );
291 $whitelabled = (bool) apply_filters( 'wpmudev_branding_hide_branding', false );
292
293 return array(
294 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ),
295 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ),
296 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ),
297 'hero_image' => $custom_image,
298 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ),
299 'is_unbranded' => '' === $custom_image && $whitelabled,
300 'is_rebranded' => '' !== $custom_image && $whitelabled,
301 );
302 }
303
304 /**
305 * Indicate this is not fresh setup.
306 *
307 * @since 2.5.5
308 */
309 function defender_no_fresh_install() {
310 if ( ! get_site_option( 'wd_nofresh_install' ) ) {
311 update_site_option( 'wd_nofresh_install', true );
312 }
313 }
314
315 /**
316 * Polyfill for PHP version < 7.3.
317 */
318 if ( ! function_exists( 'array_key_first' ) ) {
319 /**
320 * Returns the first key of an array.
321 *
322 * @param array $arr The input array.
323 *
324 * @return mixed|null The first key of the array, or null if the array is empty.
325 */
326 function array_key_first( array $arr ) {
327 $arr_keys = array_keys( $arr );
328
329 return $arr_keys[0] ?? null;
330 }
331 }
332
333 /**
334 * Fetch request url.
335 *
336 * @return string
337 */
338 function defender_get_request_url(): string {
339 return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) );
340 }
341
342 /**
343 * What is the current WP page?
344 *
345 * @return string
346 */
347 function defender_get_current_page(): string {
348 return defender_get_data_from_request( 'page', 'g' );
349 }
350
351 /**
352 * Check that current page is from Defender.
353 *
354 * @return bool
355 */
356 function is_defender_page(): bool {
357 $pages = array(
358 'wp-defender',
359 'wdf-hardener',
360 'wdf-scan',
361 'wdf-logging',
362 'wdf-ip-lockout',
363 'wdf-waf',
364 'wdf-2fa',
365 'wdf-advanced-tools',
366 'wdf-notification',
367 'wdf-setting',
368 'wdf-expert-services',
369 );
370
371 return in_array( defender_get_current_page(), $pages, true );
372 }
373
374 /**
375 * Return the high contrast css class if it is.
376 *
377 * @return bool
378 * @since 2.7.0
379 */
380 function defender_high_contrast() {
381 $model = new Main_Setting();
382
383 return $model->high_contrast_mode;
384 }
385
386 /**
387 * Add more cron schedules for plugin modules. E.g. schedules:
388 * cleaning completed Scan logs,
389 * cleaning temporary firewall IPs,
390 * send reports,
391 * update MaxMind DB.
392 *
393 * @param array $schedules The schedules.
394 *
395 * @return array
396 * @since 2.7.1
397 */
398 function defender_cron_schedules( $schedules ) {
399 if ( ! isset( $schedules['thirty_minutes'] ) ) {
400 $schedules['thirty_minutes'] = array(
401 'interval' => 30 * MINUTE_IN_SECONDS,
402 'display' => esc_html__( 'Every Half Hour', 'defender-security' ),
403 );
404 }
405 if ( ! isset( $schedules['weekly'] ) ) {
406 $schedules['weekly'] = array(
407 'interval' => WEEK_IN_SECONDS,
408 'display' => esc_html__( 'Weekly', 'defender-security' ),
409 );
410 }
411 if ( ! isset( $schedules['monthly'] ) ) {
412 $schedules['monthly'] = array(
413 'interval' => MONTH_IN_SECONDS,
414 'display' => esc_html__( 'Once Monthly', 'defender-security' ),
415 );
416 }
417 // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak).
418 // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year.
419 if ( ! isset( $schedules['thirty_days'] ) ) {
420 $schedules['thirty_days'] = array(
421 'interval' => 2592000,
422 'display' => esc_html__( '30 days', 'defender-security' ),
423 );
424 }
425 if ( ! isset( $schedules['sixty_days'] ) ) {
426 $schedules['sixty_days'] = array(
427 'interval' => 5184000,
428 'display' => esc_html__( '60 days', 'defender-security' ),
429 );
430 }
431 if ( ! isset( $schedules['ninety_days'] ) ) {
432 $schedules['ninety_days'] = array(
433 'interval' => 7776000,
434 'display' => esc_html__( '90 days', 'defender-security' ),
435 );
436 }
437 if ( ! isset( $schedules['six_months'] ) ) {
438 $schedules['six_months'] = array(
439 'interval' => 15780000,
440 'display' => esc_html__( '6 months', 'defender-security' ),
441 );
442 }
443 if ( ! isset( $schedules['one_year'] ) ) {
444 $schedules['one_year'] = array(
445 'interval' => 31536000,
446 'display' => esc_html__( '1 year', 'defender-security' ),
447 );
448 }
449
450 return $schedules;
451 }
452
453 /**
454 * Generate random string.
455 *
456 * @param int $length Length of random string.
457 * @param string $strings Characters to include in a random string.
458 *
459 * @return string
460 * @since 3.0.0
461 */
462 function defender_generate_random_string( int $length = 16, string $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) {
463 if ( defined( 'DEFENDER_2FA_SECRET' ) ) {
464 // Only use in test.
465 return constant( 'DEFENDER_2FA_SECRET' );
466 }
467
468 $secret = array();
469 for ( $i = 0; $i < $length; $i++ ) {
470 $secret[] = $strings[ Crypt::random_int( 0, strlen( $strings ) - 1 ) ];
471 }
472
473 return implode( '', $secret );
474 }
475
476 /**
477 * Either return array or echo json.
478 *
479 * @param mixed $data A Data to be returned or echoed.
480 * @param bool $success Is it a success or failure.
481 * @param bool $is_return True if data needs to be returned.
482 *
483 * @return array|void
484 * @since 3.0.0
485 */
486 function defender_maybe_echo_json( $data, $success, $is_return ) {
487 if ( true === $is_return ) {
488 return array(
489 'success' => $success,
490 'data' => $data,
491 );
492 } else {
493 $success ? wp_send_json_success( $data ) : wp_send_json_error( $data );
494 }
495 }
496
497 /**
498 * Get translations for the 'defender-security' text domain.
499 *
500 * @return array List of words/phrases and their translations.
501 * @global Mo[] $l10n An array of all currently loaded text domains.
502 */
503 function defender_gettext_translations(): array {
504 global $l10n;
505
506 // Check if the 'defender-security' text domain is loaded.
507 if ( ! isset( $l10n['defender-security'] ) ) {
508 return array();
509 }
510
511 $items = array();
512
513 /**
514 * Go through all the translation entries in the 'defender-security' text domain.
515 *
516 * @var Translation_Entry $value
517 */
518 foreach ( $l10n['defender-security']->entries as $value ) {
519 // If there's a translation, use it; otherwise, use the original word/phrase.
520 $items[ $value->key() ] = isset( $value->translations[0] ) ? $value->translations[0] : $value->key();
521 }
522
523 // Return the list of words/phrases and their translations.
524 return $items;
525 }
526
527 /**
528 * Get string replacement regardless of the operating system.
529 *
530 * @param string $path The path to be replaced.
531 *
532 * @return string
533 * @since 3.3.0
534 */
535 function defender_replace_line( $path ): string {
536 return str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path );
537 }
538
539 /**
540 * Generates the support ticket text for the Defender plugin.
541 *
542 * @return string The support ticket text, including the formatted link.
543 */
544 function defender_support_ticket_text(): string {
545 return sprintf(
546 /* translators: 1. Support link. */
547 esc_html__( 'Still, having trouble? %1$s.', 'defender-security' ),
548 '<a target="_blank" href="' . WP_DEFENDER_SUPPORT_LINK . '">' . esc_html__( 'Open a support ticket', 'defender-security' ) . '</a>'
549 );
550 }
551
552 /**
553 * The message is shown on the inappropriate access of Safe Repair feature.
554 *
555 * @return string
556 */
557 function defender_quarantine_pro_only(): string {
558 return esc_html__( 'Safe Repair feature is only for Pro', 'defender-security' );
559 }
560
561 /**
562 * Retrieves the user agent from the $_SERVER super global or returns a default string.
563 *
564 * @param string $default_string The default string to return if the user agent is empty. Default is an empty string.
565 *
566 * @return string The cleaned user agent or the default string.
567 */
568 function defender_get_user_agent( $default_string = '' ) {
569 $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' );
570
571 return '' !== $user_agent ? User_Agent::fast_cleaning( $user_agent ) : $default_string;
572 }
573
574 /**
575 * Check if it is a CLI request.
576 *
577 * @return bool
578 * @since 4.2.0
579 */
580 function defender_is_wp_cli() {
581 return defined( 'WP_CLI' ) && WP_CLI;
582 }
583
584 /**
585 * Check if the current request is a REST API request.
586 * This function checks if the current request URI contains the REST API prefix,
587 * indicating that it's a request to the WordPress REST API.
588 *
589 * @return bool Whether the current request is a REST API request.
590 * @since 4.2.0
591 */
592 function defender_is_rest_api_request(): bool {
593 $request_uri = Request::get_request_uri();
594 if ( '' === $request_uri ) {
595 return false;
596 }
597
598 $rest_prefix = trailingslashit( rest_get_url_prefix() );
599
600 return false !== strpos( $request_uri, $rest_prefix );
601 }
602
603 /**
604 * Handle deprecated functions by logging or triggering actions.
605 * This function is a wrapper for WordPress's _deprecated_function() function.
606 * It is used to handle deprecated functions by either logging a deprecation
607 * message or triggering an action. It checks if the current request is an AJAX
608 * request or a REST API request and acts accordingly.
609 *
610 * @param string $function_name The function that was called.
611 * @param string $version The version number that deprecated the function.
612 * @param string $replacement (Optional) The function that should be used instead.
613 *
614 * @return void
615 * @since 4.2.0
616 */
617 function defender_deprecated_function( string $function_name, string $version, string $replacement = '' ): void {
618 /**
619 * Filters whether to trigger an error for deprecated functions.
620 *
621 * @param bool $trigger Whether to trigger the error for deprecated functions. Default false.
622 *
623 * @since 4.2.1
624 */
625 if ( WP_DEBUG && apply_filters( 'defender_deprecated_function_trigger_error', false ) ) {
626 if ( wp_doing_ajax() || defender_is_rest_api_request() ) {
627 do_action( 'deprecated_function_run', $function_name, $replacement, $version );
628
629 $log_string = "Function {$function_name} is deprecated since version {$version}!";
630 $log_string .= '' !== $replacement ? " Use {$replacement} instead." : '';
631 wp_die( esc_html( $log_string ) );
632 } else {
633 _deprecated_function( esc_html( $function_name ), esc_html( $version ), esc_html( $replacement ) );
634 }
635 }
636 }
637
638 if ( ! function_exists( 'defender_get_data_from_request' ) ) {
639 /**
640 * Retrieves the value of a specific server data key after sanitizing it.
641 *
642 * @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.
643 * @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.
644 * @param string $nonce_key The nonce key for verification.
645 * @param string $nonce_action The nonce action for verification.
646 *
647 * @return array|string|null The sanitized value of the server data key.
648 */
649 function defender_get_data_from_request(
650 ?string $key,
651 string $source,
652 string $nonce_key = '',
653 string $nonce_action = ''
654 ) {
655 if ( in_array( $source, array( 'r', 'p', 'g' ), true ) ) {
656 if (
657 '' !== $nonce_key && ! wp_verify_nonce(
658 sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_key ] ?? '' ) ),
659 $nonce_action
660 )
661 ) {
662 return null;
663 }
664 }
665 switch ( $source ) {
666 case 'r':
667 $data = $_REQUEST;
668 break;
669 case 'p':
670 $data = $_POST;
671 break;
672 case 'g':
673 $data = $_GET;
674 break;
675 case 's':
676 $data = $_SERVER;
677 break;
678 case 'c':
679 $data = $_COOKIE;
680 break;
681 case 'f':
682 $data = $_FILES;
683 break;
684 default:
685 $data = array();
686 break;
687 }
688
689 if ( null === $key || '' === $key ) {
690 return $data;
691 } elseif ( 's' === $source ) {
692 return sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) );
693 } elseif ( 'data' === $key ) {
694 return json_decode( sanitize_text_field( wp_unslash( $data[ $key ] ?? '' ) ), true );
695 } elseif ( 'f' === $source && 'file' === $key && isset( $data[ $key ] ) ) {
696 if ( isset( $data[ $key ]['name'] ) && '' !== $data[ $key ]['name'] ) {
697 $data[ $key ]['name'] = sanitize_file_name( $data[ $key ]['name'] );
698 }
699
700 return $data[ $key ];
701 }
702
703 return sanitize_text_field( $data[ $key ] ?? '' );
704 }
705 }
706
707 /**
708 * Check if arrays are same.
709 *
710 * @param array $array1 The first array to compare.
711 * @param array $array2 The second array to compare.
712 *
713 * @return bool True if arrays are equal, false otherwise.
714 */
715 function defender_are_arrays_equal( $array1, $array2 ): bool {
716 if ( count( $array1 ) !== count( $array2 ) ) {
717 return false;
718 }
719
720 // Sort both arrays.
721 sort( $array1 );
722 sort( $array2 );
723
724 return $array1 === $array2;
725 }
726
727 /**
728 * Get a feature state on WPMU DEV hosting.
729 *
730 * @param string $feature_key The feature key, e.g. xmlrpc_block, globaliplist or waf.
731 *
732 * @return bool|string True or false if the feature is enabled or disabled, or an empty string if the feature is not found.
733 */
734 function defender_get_hosting_feature_state( string $feature_key ) {
735 if ( function_exists( 'wpmudev_hosting_features' ) ) {
736 $states = wpmudev_hosting_features();
737
738 return isset( $states[ $feature_key ] ) ? $states[ $feature_key ] : '';
739 }
740
741 return '';
742 }
743
744 /**
745 * Get an internal log file name.
746 *
747 * @return string
748 */
749 function wd_internal_log(): string {
750 return Central::INTERNAL_LOG;
751 }
752
753 /**
754 * Retrieves the current time, allowing overrides for testing.
755 *
756 * @return int The current timestamp.
757 */
758 function defender_get_current_time() {
759 /**
760 * Filter the current time.
761 *
762 * @since 5.2.0
763 *
764 * @param int $time The current timestamp.
765 *
766 * @return int The current/filtered timestamp.
767 */
768 return (int) apply_filters( 'wpdef_current_time', time() );
769 }
770
771 /**
772 * Get the current site domain.
773 *
774 * @return string The determined domain name.
775 */
776 function defender_get_domain() {
777 static $domain = '';
778
779 if ( '' === $domain ) {
780 $domain = is_multisite()
781 ? ( get_network()->domain ?? '' )
782 : wp_parse_url( get_site_url(), PHP_URL_HOST );
783 }
784
785 /**
786 * Filter the current site domain.
787 *
788 * @param string $domain The determined domain name.
789 *
790 * @return string The filtered domain name.
791 *
792 * @since 5.2.0
793 */
794 $domain = (string) apply_filters( 'wpdef_current_site_domain', $domain );
795
796 return esc_html( $domain );
797 }
798
799 /**
800 * WP_DEFENDER_PRO sometimes doesn't match WPMUDEV::is_pro().
801 *
802 * @return bool
803 */
804 function defender_is_wp_org_version(): bool {
805 return ! wd_di()->get( WPMUDEV::class )->is_pro()
806 && ( defined( 'WP_DEFENDER_PRO' ) && ! WP_DEFENDER_PRO );
807 }
808
809 /**
810 * Get the user agent of the plugin.
811 *
812 * @return string
813 */
814 function defender_get_own_user_agent(): string {
815 return defender_is_wp_org_version()
816 ? sprintf( 'Mozilla/5.0 (compatible; Defender/%1$s)', DEFENDER_VERSION )
817 : sprintf(
818 'Mozilla/5.0 (compatible; WPMU DEV Defender/%1$s; +https://wpmudev.com)',
819 DEFENDER_VERSION
820 );
821 }
822