{ $output[] = $value; } else { $output[ $key ] = $value; } } return $output; } /** * Get's a value from an array or object. * * @param array|object $item The item to retrieve the value from. * @param string $field The field or method name to use. * @param null $default The default value to return if no value is found. * * @return mixed|null */ public static function get( $item, $field, $default = null ) { if ( is_array( $item ) ) { return isset( $item[ $field ] ) ? $item[ $field ] : $default; } if ( is_object( $item ) ) { if ( is_callable( [ $item, $field ] ) ) { return $item->{$field}(); } return isset( $item->{$field} ) ? $item->{$field} : $default; } return $default; } /** * Finds the first item in a list matching the given predicate. * * @param iterable $list * @param callable $predicate * * @return mixed|null */ public static function find_where( iterable $list, callable $predicate ) { foreach ( $list as $item ) { if ( $predicate( $item ) ) { return $item; } } return null; } /** * Array unique implementation that allows for non-scalar values. * * Will compare elements using `serialize()`. * * Keys are preserved. If a numeric array is given, the array will be re-indexed. * * @param array $array * @param bool $stabilize If true, stabilizes the values first according to JSON semantics. * * @return array */ public static function non_scalar_array_unique( $array, $stabilize = false ) { $is_numeric = wp_is_numeric_array( $array ); $hashes = array(); foreach ( $array as $key => $value ) { if ( $stabilize ) { $value = rest_stabilize_value( $value ); } $hash = serialize( $value ); if ( isset( $hashes[ $hash ] ) ) { unset( $array[ $key ] ); } else { $hashes[ $hash ] = 1; } } if ( $is_numeric ) { return array_values( $array ); } return $array; } /** * Parse a complex header that has attributes like quality values. * * @param string $header * * @return array[] * @example Parsing the Accept-Language header. * * "en-US,en;q=0.9,de;q=0.8" transforms to: * * [ * 'en-US' => [], * 'en' => [ 'q' => 0.9 ], * 'de' => [ 'q' => 0.8' ], * ] * */ public static function parse_header_with_attributes( $header ) { $parsed = array(); $list = explode( ',', $header ); foreach ( $list as $value ) { $attrs = array(); $parts = explode( ';', trim( $value ) ); $main = trim( $parts[0], ' <>' ); foreach ( $parts as $part ) { if ( false === strpos( $part, '=' ) ) { continue; } list( $key, $value ) = array_map( 'trim', explode( '=', $part, 2 ) ); $attrs[ $key ] = trim( $value, '" ' ); } $parsed[ $main ] = $attrs; } return $parsed; } /** * Is a particular function allowed to be called. * * Checks disabled functions and the function blacklist. * * @param string $func * * @return bool */ public static function is_func_allowed( $func ) { static $cache = array(); static $disabled; static $suhosin; if ( isset( $cache[ $func ] ) ) { return $cache[ $func ]; } if ( $disabled === null ) { $disabled = preg_split( '/\s*,\s*/', (string) ini_get( 'disable_functions' ) ); } if ( $suhosin === null ) { $suhosin = preg_split( '/\s*,\s*/', (string) ini_get( 'suhosin.executor.func.blacklist' ) ); } if ( ! is_callable( $func ) ) { return $cache[ $func ] = false; } if ( in_array( $func, $disabled, true ) ) { return $cache[ $func ] = false; } if ( in_array( $func, $suhosin, true ) ) { return $cache[ $func ] = false; } return $cache[ $func ] = true; } /** * Get whatever backup plugin is being used on this site. * * @return string */ public static function get_backup_plugin() { $possible = array( 'backupbuddy/backupbuddy.php', 'updraftplus/updraftplus.php', 'backwpup/backwpup.php', 'xcloner-backup-and-restore/xcloner.php', 'duplicator/duplicator.php', 'backup/backup.php', 'wp-db-backup/wp-db-backup.php', 'backupwordpress/backupwordpress.php', 'blogvault-real-time-backup/blogvault.php', 'wp-all-backup/wp-all-backup.php', 'vaultpress/vaultpress.php', ); /** * Filter the list of possible backup plugins. * * @param string[] List of Backup Plugin __FILE__. */ $possible = apply_filters( 'itsec_possible_backup_plugins', $possible ); if ( ! function_exists( 'is_plugin_active' ) ) { require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } if ( ! function_exists( 'is_plugin_active' ) ) { return ''; } foreach ( $possible as $file ) { if ( is_plugin_active( $file ) ) { return $file; } } return ''; } /** * Generate a random token. * * @return string Hex token. */ public static function generate_token() { $length = 64; try { $random = bin2hex( random_bytes( $length / 2 ) ); } catch ( Exception $e ) { $unpacked = unpack( 'H*', wp_generate_password( $length / 2, true, true ) ); $random = reset( $unpacked ); } return $random; } /** * Generate a hash of the token for storage. * * @param string $token * * @return false|string */ public static function hash_token( $token ) { return hash_hmac( self::get_hash_algo(), $token, wp_salt() ); } /** * Check if the provided token matches the stored hashed token. * * @param string $provided_token * @param string $hashed_token * * @return bool */ public static function verify_token( $provided_token, $hashed_token ) { if ( ! $hashed_token || ! $provided_token ) { return false; } return hash_equals( $hashed_token, self::hash_token( $provided_token ) ); } /** * Get the hash algorithm to use. * * PHP can be compiled without the hash extension and the supported hash algos can be variable. WordPress shims * support for md5 and sha1 hashes with hash_hmac. * * @return string */ public static function get_hash_algo() { if ( ! function_exists( 'hash_algos' ) ) { return 'sha1'; } $algos = hash_algos(); if ( in_array( 'sha256', $algos, true ) ) { return 'sha256'; } return 'sha1'; } public static function get_url_from_file( $file, $auto_ssl = true, $prevent_recursion = false ) { $file = str_replace( '\\', '/', $file ); $url = ''; $upload_dir = ITSEC_Core::get_wp_upload_dir(); $upload_dir['basedir'] = str_replace( '\\', '/', $upload_dir['basedir'] ); if ( is_array( $upload_dir ) && ( false === $upload_dir['error'] ) ) { if ( 0 === strpos( $file, $upload_dir['basedir'] ) ) { $url = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $file ); } elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) { $path_pattern = 'wp-content/uploads'; $url_base = $upload_dir['baseurl']; if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) { if ( defined( 'MULTISITE' ) ) { $mu_path = '/sites/' . get_current_blog_id(); } else { $mu_path = '/' . get_current_blog_id(); } if ( false === strpos( $file, "$path_pattern$mu_path" ) ) { $url_base = substr( $url_base, 0, - strlen( $mu_path ) ); } else { $path_pattern .= $mu_path; } } $url = $url_base . substr( $file, strpos( $file, $path_pattern ) + strlen( $path_pattern ) ); } } if ( empty( $url ) ) { if ( ! isset( $GLOBALS['__itsec_cache_wp_content_dir'] ) ) { $GLOBALS['__itsec_cache_wp_content_dir'] = rtrim( str_replace( '\\', '/', WP_CONTENT_DIR ), '/' ); } if ( ! isset( $GLOBALS['__itsec_cache_abspath'] ) ) { $GLOBALS['__itsec_cache_abspath'] = rtrim( str_replace( '\\', '/', ABSPATH ), '/' ); } if ( 0 === strpos( $file, $GLOBALS['__itsec_cache_wp_content_dir'] ) ) { $url = WP_CONTENT_URL . str_replace( '\\', '/', preg_replace( '/^' . preg_quote( $GLOBALS['__itsec_cache_wp_content_dir'], '/' ) . '/', '', $file ) ); } elseif ( 0 === strpos( $file, $GLOBALS['__itsec_cache_abspath'] ) ) { $url = get_option( 'siteurl' ) . str_replace( '\\', '/', preg_replace( '/^' . preg_quote( $GLOBALS['__itsec_cache_abspath'], '/' ) . '/', '', $file ) ); } } if ( empty( $url ) && ! $prevent_recursion ) { $url = self::get_url_from_file( realpath( $file ), $auto_ssl, true ); } if ( empty( $url ) ) { return ''; } if ( $auto_ssl ) { $url = self::fix_url( $url ); } return $url; } public static function get_file_from_url( $url ) { $url = preg_replace( '/^https/', 'http', $url ); $url = preg_replace( '/\?.*$/', '', $url ); $file = ''; $upload_dir = ITSEC_Core::get_wp_upload_dir(); if ( is_array( $upload_dir ) && ( false === $upload_dir['error'] ) ) { if ( 0 === strpos( $url, $upload_dir['baseurl'] ) ) { $file = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $url ); } elseif ( false !== strpos( $url, 'wp-content/uploads' ) ) { $path_pattern = 'wp-content/uploads'; $file_base = $upload_dir['basedir']; if ( is_multisite() && ! ( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) { if ( defined( 'MULTISITE' ) ) { $mu_path = '/sites/' . get_current_blog_id(); } else { $mu_path = '/' . get_current_blog_id(); } if ( false === strpos( $url, "$path_pattern$mu_path" ) ) { $file_base = substr( $file_base, 0, - strlen( $mu_path ) ); } else { $path_pattern .= $mu_path; } } $file = $file_base . substr( $url, strpos( $url, $path_pattern ) + strlen( $path_pattern ) ); } } if ( empty( $file ) ) { if ( ! isset( $GLOBALS['__itsec_cache_wp_content_url'] ) ) { $GLOBALS['__itsec_cache_wp_content_url'] = preg_replace( '/^https/', 'http', WP_CONTENT_URL ); } if ( ! isset( $GLOBALS['__itsec_cache_siteurl'] ) ) { $GLOBALS['__itsec_cache_siteurl'] = preg_replace( '/^https/', 'http', get_option( 'siteurl' ) ); } if ( 0 === strpos( $url, $GLOBALS['__itsec_cache_wp_content_url'] ) ) { $file = rtrim( WP_CONTENT_DIR, '\\\/' ) . preg_replace( '/^' . preg_quote( $GLOBALS['__itsec_cache_wp_content_url'], '/' ) . '/', '', $url ); } elseif ( 0 === strpos( $url, $GLOBALS['__itsec_cache_siteurl'] ) ) { $file = rtrim( ABSPATH, '\\\/' ) . preg_replace( '/^' . preg_quote( $GLOBALS['__itsec_cache_siteurl'], '/' ) . '/', '', $url ); } } return $file; } public static function fix_url( $url ) { if ( is_ssl() ) { $url = preg_replace( '|^http://|', 'https://', $url ); } else { $url = preg_replace( '|^https://|', 'http://', $url ); } return $url; } /** * Set a cookie. * * @param string $name * @param string $value * @param array $args */ public static function set_cookie( $name, $value, $args = array() ) { $args = wp_parse_args( array( 'length' => 0, 'http_only' => true, ), $args ); $expires = $args['length'] ? ITSEC_Core::get_current_time_gmt() + $args['length'] : 0; setcookie( $name, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), $args['http_only'] ); } /** * Clear a cookie. * * @param string $name */ public static function clear_cookie( $name ) { setcookie( $name, ' ', ITSEC_Core::get_current_time_gmt() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, false ); } /** * Is the current request a loopback request. * * @return bool */ public static function is_loopback_request() { return in_array( self::get_ip(), ITSEC_Modules::get_setting( 'global', 'server_ips' ), true ); } /** * Version of {@see wp_slash()} that won't cast numbers to strings. * * @param array|string $value * * @return array|string */ public static function slash( $value ) { if ( is_array( $value ) ) { foreach ( $value as $k => $v ) { if ( is_array( $v ) ) { $value[ $k ] = self::slash( $v ); } elseif ( is_string( $v ) ) { $value[ $k ] = addslashes( $v ); } } } elseif ( is_string( $value ) ) { $value = addslashes( $value ); } return $value; } /** * Format as a ISO 8601 date. * * @param int|string|\DateTimeInterface $date Epoch or strtotime compatible date. * * @return string|false */ public static function to_rest_date( $date = 0 ) { if ( ! $date ) { $date = ITSEC_Core::get_current_time_gmt(); } elseif ( $date instanceof \DateTimeInterface ) { $date = $date->getTimestamp(); } elseif ( ! is_int( $date ) ) { $date = strtotime( $date ); } return gmdate( 'Y-m-d\TH:i:sP', $date ); } /** * Flatten an array. * * @param array $array * * @return array */ public static function flatten( $array ) { if ( ! is_array( $array ) ) { return array( $array ); } $merge = array(); foreach ( $array as $value ) { $merge[] = self::flatten( $value ); } return $merge ? call_user_func_array( 'array_merge', $merge ) : array(); } /** * Preload REST API requests. * * @param array $requests * * @return array */ public static function preload_rest_requests( $requests, string $page = '' ) { if ( $page ) { /** * Filters the list of API requests to preload. * * @param array $requests * @param string $page */ $requests = apply_filters( 'itsec_preload_requests', $requests, 'tools' ); } $preload = array(); foreach ( $requests as $key => $config ) { if ( is_string( $config ) ) { $key = $config; $config = array( 'route' => $config ); } $request = new WP_REST_Request( isset( $config['method'] ) ? $config['method'] : 'GET', $config['route'] ); if ( ! empty( $config['query'] ) ) { $request->set_query_params( $config['query'] ); } $response = rest_do_request( $request ); if ( $response->get_status() >= 200 && $response->get_status() < 300 ) { rest_send_allow_header( $response, rest_get_server(), $request ); if ( is_int( $key ) ) { $key = $config['route']; if ( ! empty( $config['query'] ) ) { $key = add_query_arg( $config['query'], $key ); } if ( ! empty( $config['embed'] ) ) { $key = add_query_arg( '_embed', '1', $key ); } } $preload[ $key ] = array( 'body' => rest_get_server()->response_to_data( $response, ! empty( $config['embed'] ) ), 'headers' => $response->get_headers() ); } } return $preload; } /** * Preloads a REST API request directly into a data store. * * This can be useful when we need the data to be immediately * available when the app renders. Typical preloading still has * a fractional delay, as it goes through an async fetch stack. * * @param string $store The data store handle. * @param string $action The data store action. * @param string $route The REST API route to fetch. * @param array $query Query parameters for the REST API route. * * @return bool */ public static function preload_request_for_data_store( string $store, string $action, string $route, array $query = [] ): bool { $request = new WP_REST_Request( 'GET', $route ); $request->set_query_params( $query ); $response = rest_do_request( $request ); if ( $response->is_error() ) { return false; } $data = rest_get_server()->response_to_data( $response, ! empty( $query['_embed'] ) ); return wp_add_inline_script( 'itsec-packages-data', sprintf( "wp.data.dispatch( '%s' ).%s( %s )", $store, $action, wp_json_encode( $data ) ) ); } /** * Check if the given string starts with the given needle. * * @param string $haystack * @param string $needle * * @return bool */ public static function str_starts_with( $haystack, $needle ) { return 0 === strpos( $haystack, $needle ); } public static function str_ends_with( $haystack, $needle ) { return '' === $needle || substr_compare( $haystack, $needle, - strlen( $needle ) ) === 0; } /** * Load a library class definition. * * @param string $name */ public static function load( $name ) { require_once( dirname( __FILE__ ) . "/lib/class-itsec-lib-{$name}.php" ); } /** * Combine multiple WP_Error instances. * * @param WP_Error|null ...$errors * * @return WP_Error */ public static function combine_wp_error( ...$errors ) { $combined = new WP_Error(); self::add_to_wp_error( $combined, ...$errors ); return $combined; } /** * Add the subsequent WP Error data to the first WP Error instance. * * @param WP_Error $add_to * @param WP_Error|null ...$errors */ public static function add_to_wp_error( WP_Error $add_to, ...$errors ) { foreach ( $errors as $error ) { if ( $error ) { foreach ( $error->get_error_codes() as $code ) { foreach ( $error->get_error_messages( $code ) as $message ) { $add_to->add( $code, $message ); } $data = $error->get_error_data( $code ); if ( null !== $data ) { $add_to->add_data( $data, $code ); } } } } } /** * Render a file with only the given vars in context. * * @param string $file * @param array $context * @param bool $echo * * @return string|void */ public static function render( $file, $context = array(), $echo = true ) { $__echo = $echo; $__file = $file; extract( $context, EXTR_OVERWRITE ); unset( $file, $context, $echo ); if ( ! $__echo ) { ob_start(); } require( $__file ); if ( ! $__echo ) { return ob_get_clean() ?: ''; } } /** * Utility to mark this page as not cacheable. */ public static function no_cache() { nocache_headers(); if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } } /** * Get the WordPress branch version. * * @return string * @example 5.2.4 => 5.2 * */ public static function get_wp_branch() { $version = get_bloginfo( 'version' ); list( $major, $minor ) = explode( '.', $version ); return $major . '.' . $minor; } /** * Are two lists equal ignoring order. * * @param array $a * @param array $b * @param callable|null $cmp * * @return bool */ public static function equal_sets( array $a, array $b, callable $cmp = null ) { if ( $cmp ) { usort( $a, $cmp ); usort( $b, $cmp ); } else { sort( $a ); sort( $b ); } return $a === $b; } /** * Convert the return val from {@see ITSEC_Modules::set_settings()} to a WP_Error object. * * @param array $updated * * @return WP_Error|null */ public static function updated_settings_to_wp_error( $updated ) { if ( is_wp_error( $updated ) ) { return $updated; } if ( $updated['saved'] ) { return null; } if ( $updated['errors'] ) { $error = self::combine_wp_error( ...$updated['errors'] ); } else { $error = new \WP_Error( 'itsec.settings.set-failed', __( 'Failed to update settings.', 'better-wp-security' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } return $error; } /** * Sanitize the list of roles. * * @param string[] $roles * * @return array */ public static function sanitize_roles( $roles ) { return array_filter( $roles, static function ( $role ) { return (bool) get_role( $role ); } ); } /** * Get a snapshot of $_SERVER properties. * * @return array */ public static function get_server_snapshot() { $whitelist = [ 'REQUEST_TIME', 'REQUEST_TIME_FLOAT', 'REQUEST_METHOD', 'HTTPS', 'REQUEST_SCHEME', 'SERVER_PROTOCOL', 'SCRIPT_FILENAME', ]; return array_filter( $_SERVER, static function ( $key ) use ( $whitelist ) { if ( $key === 'HTTP_COOKIE' ) { return false; } if ( self::str_starts_with( $key, 'HTTP_' ) ) { return true; } if ( self::str_starts_with( $key, 'CONTENT_' ) ) { return true; } return in_array( $key, $whitelist, true ); }, ARRAY_FILTER_USE_KEY ); } /** * Version of {@see is_super_admin()} that operates on a `WP_User` instance. * * This bypasses an issue where {@see is_super_admin()} cannot be used during the `determine_current_user` filter since * `is_super_admin` has a side effect of querying for the current user, causing an infinite loop. * * @param WP_User $user * * @return bool */ public static function is_super_admin( WP_User $user ) { if ( ! $user->exists() ) { return false; } if ( is_multisite() ) { $super_admins = get_super_admins(); if ( is_array( $super_admins ) && in_array( $user->user_login, $super_admins ) ) { return true; } } else { if ( $user->has_cap( 'delete_users' ) ) { return true; } } return false; } /** * Performs a {@see dbDelta()} but reports any errors encountered. * * @param string $delta * * @return WP_Error */ public static function db_delta_with_error_handling( $delta ) { global $wpdb, $EZSQL_ERROR; require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); $err_count = is_array( $EZSQL_ERROR ) ? count( $EZSQL_ERROR ) : 0; $showed_errors = $wpdb->show_errors( false ); dbDelta( $delta ); if ( $showed_errors ) { $wpdb->show_errors(); } $wp_error = new WP_Error(); if ( is_array( $EZSQL_ERROR ) ) { for ( $i = $err_count, $i_max = count( $EZSQL_ERROR ); $i < $i_max; $i ++ ) { $error = $EZSQL_ERROR[ $i ]; if ( empty( $error['error_str'] ) || empty( $error['query'] ) || 0 === strpos( $error['query'], 'DESCRIBE ' ) ) { continue; } $wp_error->add( 'db_delta_error', $error['error_str'] ); } } return $wp_error; } /** * Get info used to help evaluate requirements according to * {@see ITSEC_Lib::evaluate_requirements()}. * * @return array[] */ public static function get_requirements_info(): array { return [ 'load' => ITSEC_Core::is_loading_early() ? 'early' : 'normal', 'server' => [ 'php' => explode( '-', PHP_VERSION )[0], 'extensions' => [ 'OpenSSL' => self::is_func_allowed( 'openssl_verify' ), ], ], ]; } /** * Evaluate whether this site passes the given requirements. * * @param array $requirements * * @return WP_Error */ public static function evaluate_requirements( array $requirements ) { $schema = [ 'type' => 'object', 'additionalProperties' => false, 'properties' => [ 'version' => [ 'type' => 'object', 'additionalProperties' => false, 'properties' => [ 'pro' => [ 'type' => 'string', 'required' => true, ], 'free' => [ 'type' => 'string', 'required' => true, ], ], ], 'ssl' => [ 'type' => 'boolean', ], 'feature-flags' => [ 'type' => 'array', 'items' => [ 'type' => 'string', ], ], 'multisite' => [ 'type' => 'string', 'enum' => [ 'enabled', 'disabled' ], ], 'server' => [ 'type' => 'object', 'properties' => [ 'php' => [ 'type' => 'string', ], 'extensions' => [ 'type' => 'array', 'items' => [ 'type' => 'string', 'enum' => [ 'OpenSSL' ], ], ], ], ], 'load' => [ 'type' => 'string', 'enum' => [ 'normal', 'early' ], ], 'ip' => [ 'type' => 'boolean', ], ], ]; if ( ITSEC_Core::is_development() ) { $valid_requirements = rest_validate_value_from_schema( $requirements, $schema ); if ( is_wp_error( $valid_requirements ) ) { return $valid_requirements; } } $error = new WP_Error(); foreach ( $requirements as $kind => $requirement ) { switch ( $kind ) { case 'version': $key = ITSEC_Core::is_pro() ? 'pro' : 'free'; $version = $requirement[ $key ]; if ( version_compare( ITSEC_Core::get_plugin_version(), $version, '<' ) ) { $error->add( 'version', sprintf( __( 'You must be running at least version %s of Solid Security.', 'better-wp-security' ), $version ) ); } break; case 'ssl': if ( $requirement !== is_ssl() ) { $error->add( 'ssl', $requirement ? __( 'Your site must support SSL.', 'better-wp-security' ) : __( 'Your site must not support SSL.', 'better-wp-security' ) ); } break; case 'feature-flags': foreach ( $requirement as $flag ) { if ( ! ITSEC_Lib_Feature_Flags::is_enabled( $flag ) ) { $error->add( 'feature-flags', sprintf( __( 'The \'%s\' feature flag must be enabled.', 'better-wp-security' ), ( ITSEC_Lib_Feature_Flags::get_flag_config( $flag )['title'] ?? $flag ) ?: $flag ) ); } } break; case 'multisite': if ( $requirement === 'enabled' && ! is_multisite() ) { $error->add( 'multisite', __( 'Multisite must be enabled.', 'better-wp-security' ) ); } elseif ( $requirement === 'disabled' && is_multisite() ) { $error->add( 'multisite', __( 'Multisite is not supported.', 'better-wp-security' ) ); } break; case 'server': $info = self::get_requirements_info(); if ( isset( $requirement['php'] ) && version_compare( $info['server']['php'], $requirement['php'], '<' ) ) { $error->add( 'server', sprintf( __( 'You must be running PHP version %s or later.', 'better-wp-security' ), $requirement['php'] ) ); } $missing = array_filter( $requirement['extensions'] ?? [], function ( $extension ) use ( $info ) { return empty( $info['server']['extensions'][ $extension ] ); } ); if ( $missing ) { if ( count( $missing ) === 1 ) { $message = sprintf( __( 'The %s PHP extension is required.', 'better-wp-security' ), ITSEC_Lib::first( $missing ) ); } else { $message = wp_sprintf( _n( 'The following PHP extension is required: %l.', 'The following PHP extensions are required: %l.', count( $missing ), 'better-wp-security' ), $missing ); } $error->add( 'server', $message ); } break; case 'load': if ( $requirement === 'normal' && ITSEC_Core::is_loading_early() ) { $error->add( 'load', __( 'Loading Solid Security via an MU-Plugin is not supported.', 'better-wp-security' ) ); } elseif ( $requirement === 'early' && ! ITSEC_Core::is_loading_early() ) { $error->add( 'load', __( 'Loading Solid Security without an MU-Plugin is not supported.', 'better-wp-security' ) ); } break; case 'ip': if ( ! ITSEC_Lib_IP_Detector::is_configured() ) { $error->add( 'ip', __( 'You must select an IP Detection method in Global Settings.', 'better-wp-security' ) ); } break; } } return $error; } /** * Converts a JSON Schema to a WP-CLI synopsis. * * @param array $schema * * @return array */ public static function convert_schema_to_cli_synopsis( array $schema ) { $synopsis = []; $required = isset( $schema['required'] ) ? $schema['required'] : []; if ( isset( $schema['properties'] ) ) { foreach ( $schema['properties'] as $property => $config ) { $param = [ 'name' => $property, ]; if ( 'boolean' === $config['type'] ) { $param['type'] = 'flag'; } else { $param['type'] = 'assoc'; } if ( array_key_exists( 'default', $config ) ) { $param['default'] = $config['default']; } if ( isset( $config['enum'] ) ) { $param['options'] = $config['enum']; } if ( ( ! isset( $config['required'] ) || true !== $config['required'] ) && ! in_array( $property, $required, true ) ) { $param['optional'] = true; } if ( isset( $config['description'] ) ) { $param['description'] = $config['description']; } $synopsis[] = $param; } } if ( ! empty( $schema['additionalProperties'] ) ) { $synopsis[] = [ 'type' => 'generic', ]; } return $synopsis; } /** * Decode a string with URL-safe Base64. * * @param string $input A Base64 encoded string * * @return string A decoded string */ public static function url_safe_b64_decode( $input ) { $remainder = strlen( $input ) % 4; if ( $remainder ) { $padlen = 4 - $remainder; $input .= str_repeat( '=', $padlen ); } return base64_decode( strtr( $input, '-_', '+/' ) ); } /** * Encode a string with URL-safe Base64. * * @param string $input The string you want encoded * * @return string The base64 encode of what you passed in */ public static function url_safe_b64_encode( $input ) { return str_replace( '=', '', strtr( base64_encode( $input ), '+/', '-_' ) ); } /** * Compares the WordPress version with the given version. * * @param string $version The version to compare with. * @param string $operator The operator. * @param bool $allow_dev Whether to treat dev versions as stable. * * @return bool */ public static function wp_version_compare( $version, $operator, $allow_dev = true ) { global $wp_version; if ( $allow_dev ) { list( $wp_version ) = explode( '-', $wp_version ); } return version_compare( $wp_version, $version, $operator ); } /** * Checks if the WordPress version is at least the given version. * * @param string $version The version to check WP for. * @param bool $allow_dev Whether to treat dev versions as stable. * * @return bool */ public static function is_wp_version_at_least( $version, $allow_dev = true ) { return static::wp_version_compare( $version, '>=', $allow_dev ); } /** * Gets the WordPress login URL. * * @param string $action A particular login action to use. * @param string $redirect Where to redirect the user to after login. * @param string $scheme The scheme to use. Accepts `login_post` for form submissions. * * @return string */ public static function get_login_url( $action = '', $redirect = '', $scheme = 'login' ) { if ( 'login_post' === $scheme || ( $action && 'login' !== $action ) ) { $url = 'wp-login.php'; if ( $action ) { $url = add_query_arg( 'action', urlencode( $action ), $url ); } if ( $redirect ) { $url = add_query_arg( 'redirect_to', urlencode( $redirect ), $url ); } $url = site_url( $url, $scheme ); } else { $url = wp_login_url( $redirect ); if ( $action ) { $url = add_query_arg( 'action', urlencode( $action ), $url ); } } if ( function_exists( 'is_wpe' ) && is_wpe() ) { $url = add_query_arg( 'wpe-login', 'true', $url ); } return apply_filters( 'itsec_login_url', $url, $action, $redirect, $scheme ); } /** * Extends a service definition, ignoring if the service has been frozen. * * @param \iThemesSecurity\Strauss\Pimple\Container $c * @param string $id * @param callable $extend * * @return bool */ public static function extend_if_able( \iThemesSecurity\Strauss\Pimple\Container $c, string $id, callable $extend ): bool { try { $c->extend( $id, $extend ); return true; } catch ( \iThemesSecurity\Strauss\Pimple\Exception\FrozenServiceException $e ) { return false; } } /** * Resolve JSON Schema refs. * * @param array $schema * * @return array */ public static function resolve_schema_refs( array $schema ): array { if ( isset( $schema['definitions'] ) ) { array_walk( $schema, [ static::class, 'resolve_ref' ], $schema['definitions'] ); } return $schema; } /** * Resolves $ref entries at any point in the config. * * Currently, only a simplified form of JSON Pointers are supported where `/` is the only * allowed control character. * * Additionally, the `$ref` keyword must start with `#/definitions`. * * @param mixed $value The incoming value. * @param string $key The array key. * @param array $definitions The shared definitions. */ private static function resolve_ref( &$value, $key, $definitions ) { if ( ! is_array( $value ) ) { return; } if ( isset( $value['$ref'] ) ) { $ref = str_replace( '#/definitions/', '', $value['$ref'] ); $value = \ITSEC_Lib::array_get( $definitions, $ref, null, '/' ); return; } array_walk( $value, [ static::class, 'resolve_ref' ], $definitions ); } /** * Generates a v4 UUID using a CSPRNG. * * @return string */ public static function generate_uuid4(): string { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0x0fff ) | 0x4000, wp_rand( 0, 0x3fff ) | 0x8000, wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ) ); } /** * Clears the WordPress auth cookies. * * This function is safe to call before plugins have been loaded. * But the request MUST exist after calling it. * * @return void */ public static function clear_auth_cookie() { if ( ! function_exists( 'wp_clear_auth_cookie' ) ) { if ( is_multisite() ) { ms_cookie_constants(); } // Define constants after multisite is loaded. wp_cookie_constants(); require_once ABSPATH . 'wp-includes/pluggable.php'; } wp_clear_auth_cookie(); } public static function recursively_json_serialize( $value ) { if ( $value instanceof JsonSerializable ) { return $value->jsonSerialize(); } if ( is_array( $value ) ) { foreach ( $value as $k => $v ) { $value[ $k ] = self::recursively_json_serialize( $v ); } } return $value; } }