&$value ) { if ( is_array( $value ) ) { $value = defender_sanitize_data( $value ); } else { $value = sanitize_textarea_field( $value ); } } return $data; } /** * Retrieve wp-config.php file path. * * @return string */ function defender_wp_config_path() { if ( file_exists( ABSPATH . 'wp-config.php' ) ) { return ABSPATH . 'wp-config.php'; } if ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { return dirname( ABSPATH ) . '/wp-config.php'; } if ( defined( 'WD_TEST' ) && WD_TEST ) { return '/tmp/wordpress-tests-lib/wp-tests-config.php'; } } /** * Check whether we're on Windows platform or not. * * @return bool */ function defender_is_windows() { return '\\' === DIRECTORY_SEPARATOR; } /** * @return \DI\Container */ function wd_di() { global $wp_defender_di; return $wp_defender_di; } /** * @return \WP_Defender\Central */ function wd_central() { global $wp_defender_central; return $wp_defender_central; } /** * @since 2.8.0 * @return string */ function defender_base_action() { return 'wp_defender/v1/hub/'; } /** * Get backward compatibility. Forminator uses this method. * * @return array */ function defender_backward_compatibility() { $wpmu_dev = new \WP_Defender\Behavior\WPMUDEV(); $two_fa_settings = new \WP_Defender\Model\Setting\Two_Fa(); $controller = wd_di()->get( \WP_Defender\Controller\Two_Factor::class ); $list = $controller->dump_routes_and_nonces(); $lost_url = add_query_arg( array( 'action' => defender_base_action(), '_def_nonce' => $list['nonces']['send_backup_code'], 'route' => $controller->check_route( $list['routes']['send_backup_code'] ), ), admin_url( 'admin-ajax.php' ) ); return array( 'is_free' => ! $wpmu_dev->is_pro(), 'plugin_url' => defender_asset_url( '' ), 'two_fa_settings' => $two_fa_settings, 'two_fa_component' => \WP_Defender\Component\Two_Fa::class, 'lost_url' => $lost_url, ); } /** * Polyfill functions for supporting WordPress 5.3. * * @since 2.4.2 */ if ( ! function_exists( 'wp_timezone_string' ) ) { /** * Retrieves the timezone from site settings as a string. * * Uses the `timezone_string` option to get a proper timezone if available, * otherwise falls back to an offset. * * @since 5.3.0 * * @return string PHP timezone string or a ±HH:MM offset. */ function wp_timezone_string() { $timezone_string = get_option( 'timezone_string' ); if ( $timezone_string ) { return $timezone_string; } $offset = (float) get_option( 'gmt_offset' ); $hours = (int) $offset; $minutes = ( $offset - $hours ); $sign = ( $offset < 0 ) ? '-' : '+'; $abs_hour = abs( $hours ); $abs_mins = abs( $minutes * 60 ); $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); return $tz_offset; } } if ( ! function_exists( 'wp_timezone' ) ) { /** * Retrieves the timezone from site settings as a `DateTimeZone` object. * * Timezone can be based on a PHP timezone string or a ±HH:MM offset. * * @since 5.3.0 * * @return DateTimeZone Timezone object. */ function wp_timezone() { return new DateTimeZone( wp_timezone_string() ); } } if ( ! function_exists( 'defender_wp_check_php_version' ) ) { /** * Polyfill for function of WP core wp_check_php_version(). * * @since WP 5.1.0 * @since WP 5.1.1 Added the {@see 'wp_is_php_version_acceptable'} filter. * * @return array|false Array of PHP version data. False on failure. */ function defender_wp_check_php_version() { $version = phpversion(); $key = md5( $version ); $response = get_site_transient( 'php_check_' . $key ); if ( false === $response ) { $url = 'http://api.wordpress.org/core/serve-happy/1.0/'; if ( wp_http_supports( array( 'ssl' ) ) ) { $url = set_url_scheme( $url, 'https' ); } $url = add_query_arg( 'php_version', $version, $url ); $response = wp_remote_get( $url ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return false; } /** * Response should be an array with: * 'recommended_version' - string - The PHP version recommended by WordPress. * 'is_supported' - boolean - Whether the PHP version is actively supported. * 'is_secure' - boolean - Whether the PHP version receives security updates. * 'is_acceptable' - boolean - Whether the PHP version is still acceptable for WordPress. */ $response = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $response ) ) { return false; } set_site_transient( 'php_check_' . $key, $response, WEEK_IN_SECONDS ); } if ( isset( $response['is_acceptable'] ) && $response['is_acceptable'] ) { /** * Filters whether the active PHP version is considered acceptable by WordPress. * * Returning false will trigger a PHP version warning to show up in the admin dashboard to administrators. * * This filter is only run if the wordpress.org Serve Happy API considers the PHP version acceptable, ensuring * that this filter can only make this check stricter, but not loosen it. * * @since 5.1.1 * * @param bool $is_acceptable Whether the PHP version is considered acceptable. Default true. * @param string $version PHP version checked. */ $response['is_acceptable'] = (bool) apply_filters( 'wp_is_php_version_acceptable', true, $version ); } return $response; } } /** * Get hostname. * * @return string|null */ function defender_get_hostname() { $host = parse_url( get_site_url(), PHP_URL_HOST ); $host = str_replace( 'www.', '', $host ); $host = explode( '.', $host ); if ( is_array( $host ) ) { $host = array_shift( $host ); } else { $host = null; } return $host; } if ( ! function_exists( 'sanitize_mask_url' ) ) { /** * Sanitizes the mask login URL allowing uppercase letters, * Replacing whitespace and a few other characters with dashes and * Limits the output to alphanumeric characters, underscore (_) and dash (-). * Whitespace becomes a dash. * * @param string $title The title to be sanitized. * @return string The sanitized title. */ function sanitize_mask_url( $title ) { $title = strip_tags( $title ); // Preserve escaped octets. $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title ); // Remove percent signs that are not part of an octet. $title = str_replace( '%', '', $title ); // Restore octets. $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title ); if ( seems_utf8( $title ) ) { $title = utf8_uri_encode( $title, 200 ); } // Kill entities. $title = preg_replace( '/&.+?;/', '', $title ); $title = str_replace( '.', '-', $title ); $title = preg_replace( '/[^%a-zA-Z0-9 _-]/', '', $title ); $title = preg_replace( '/\s+/', '-', $title ); $title = preg_replace( '|-+|', '-', $title ); return $title; } } /** * Return the noreply email. * * An utility function which will return common noreply from address. * * @param string $filter_tag Tag name of the filter to override email address. * * @return string Noreply email. */ function defender_noreply_email( $filter_tag = '' ) { $host = wp_parse_url( get_site_url(), PHP_URL_HOST ); if ( 'www.' === substr( $host, 0, 4 ) ) { $host = substr( $host, 4 ); } $no_reply_email = 'noreply@' . $host; if ( strlen( $filter_tag ) > 0 ) { $no_reply_email = apply_filters( $filter_tag, $no_reply_email ); } return $no_reply_email; } /** * Noreply email header. * * Generate noreply email header with HTML UTF-8 support. * * @param string $email Email. * * @return array Returns the email headers. */ function defender_noreply_html_header( $email ) { $headers = array( 'From: Defender <' . $email . '>', 'Content-Type: text/html; charset=UTF-8', ); return $headers; } /** * Get data of the whitelabel feature from WPMUDEV Dashboard: * hide_branding, hide_doc_link, footer_text, hero_image, change_footer. * * @since 2.5.5 * @return array */ function defender_white_label_status() { /* translators: %s: heart icon */ $footer_text = sprintf( __( 'Made with %s by WPMU DEV', 'wpdef' ), '' ); $custom_image = apply_filters( 'wpmudev_branding_hero_image', '' ); $whitelabled = apply_filters( 'wpmudev_branding_hide_branding', false ); return array( 'hide_branding' => apply_filters( 'wpmudev_branding_hide_branding', false ), 'hide_doc_link' => apply_filters( 'wpmudev_branding_hide_doc_link', false ), 'footer_text' => apply_filters( 'wpmudev_branding_footer_text', $footer_text ), 'hero_image' => $custom_image, 'change_footer' => apply_filters( 'wpmudev_branding_change_footer', false ), 'is_unbranded' => empty( $custom_image ) && $whitelabled, 'is_rebranded' => ! empty( $custom_image ) && $whitelabled, ); } /** * Indicate this is not fresh install. * * @since 2.5.5 */ function defender_no_fresh_install() { if ( empty( get_site_option( 'wd_nofresh_install' ) ) ) { update_site_option( 'wd_nofresh_install', true ); } } /** * Polyfill for PHP version < 7.3. */ if ( ! function_exists( 'array_key_first' ) ) { function array_key_first( array $arr ) { $arr_keys = array_keys( $arr ); return $arr_keys[0] ?? null; } } /** * Fetch request url. * * @return string */ function defender_get_request_url() { return home_url( esc_url( filter_input( INPUT_SERVER, 'REQUEST_URI' ) ) ); } /** * Check that current page is from Defender. * * @return bool */ function defender_current_page() { $pages = array( 'wp-defender', 'wdf-hardener', 'wdf-scan', 'wdf-logging', 'wdf-ip-lockout', 'wdf-waf', 'wdf-2fa', 'wdf-advanced-tools', 'wdf-notification', 'wdf-setting', 'wdf-tutorial', ); $page = $_GET['page'] ?? null; return in_array( $page, $pages, true ); } /** * Return the high contrast css class if it is. * * @since 2.7.0 * @return bool */ function defender_high_contrast() { $model = new \WP_Defender\Model\Setting\Main_Setting(); return $model->high_contrast_mode; } /** * Add more cron schedules for plugin modules. E.g. schedules: * cleaning completed Scan logs, * cleaning temporary firewall IPs, * send reports, * update MaxMind DB. * @since 2.7.1 * * @param array $schedules * * @return array */ function defender_cron_schedules( $schedules ) { if ( ! isset( $schedules['thirty_minutes'] ) ) { $schedules['thirty_minutes'] = array( 'interval' => 30 * MINUTE_IN_SECONDS, 'display' => __( 'Every Half Hour', 'wpdef' ), ); } if ( ! isset( $schedules['weekly'] ) ) { $schedules['weekly'] = array( 'interval' => WEEK_IN_SECONDS, 'display' => __( 'Weekly', 'wpdef' ), ); } if ( ! isset( $schedules['monthly'] ) ) { $schedules['monthly'] = array( 'interval' => MONTH_IN_SECONDS, 'display' => __( 'Once Monthly', 'wpdef' ), ); } // Todo: find the right solution because 'monthly' (from Firewall)='thirty_days' (from Security_Key tweak). // For regeneration of security keys/salts. Schedules: 30, 60, 90 days, 6 months and 1 year. if ( ! isset( $schedules['thirty_days'] ) ) { $schedules['thirty_days'] = array( 'interval' => 2592000, 'display' => __( '30 days', 'wpdef' ), ); } if ( ! isset( $schedules['sixty_days'] ) ) { $schedules['sixty_days'] = array( 'interval' => 5184000, 'display' => __( '60 days', 'wpdef' ), ); } if ( ! isset( $schedules['ninety_days'] ) ) { $schedules['ninety_days'] = array( 'interval' => 7776000, 'display' => __( '90 days', 'wpdef' ), ); } if ( ! isset( $schedules['six_months'] ) ) { $schedules['six_months'] = array( 'interval' => 15780000, 'display' => __( '6 months', 'wpdef' ), ); } if ( ! isset( $schedules['one_year'] ) ) { $schedules['one_year'] = array( 'interval' => 31536000, 'display' => __( '1 year', 'wpdef' ), ); } return $schedules; } /** * Generate random string. * * @param int $length Length of random string. * @param string $strings Characters to include in a random string. * * @since 3.0.0 * @return string */ function defender_generate_random_string( $length = 16, $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567' ) { if ( defined( 'DEFENDER_2FA_SECRET' ) ) { // Only use in test. return constant( 'DEFENDER_2FA_SECRET' ); } if ( ! is_string( $strings ) ) { return ''; } $secret = array(); for ( $i = 0; $i < $length; $i ++ ) { $secret[] = $strings[ random_int( 0, strlen( $strings ) - 1 ) ]; } return implode( '', $secret ); } /** * Either return array or echo json. * * @param mixed $data A Data to be returned or echoed. * @param bool $success Is it a success or failure. * @param bool $return True if data needs to be returned. * * @since 3.0.0 * @return array|void */ function defender_maybe_echo_json( $data, $success, $return ) { if ( true === $return ) { return array( 'success' => $success, 'data' => $data, ); } else { $success ? wp_send_json_success( $data ) : wp_send_json_error( $data ); } }