'Jetpack', ); /** * Register hooks for the enabled switches. */ public static function init() { if ( self::on( 'disable_xmlrpc' ) ) { self::block_xmlrpc(); } if ( self::on( 'disable_theme_editor' ) || self::on( 'disable_plugin_editor' ) ) { add_filter( 'map_meta_cap', array( __CLASS__, 'deny_editors' ), 10, 2 ); } if ( self::on( 'header_nosniff' ) || self::on( 'header_frame' ) || self::on( 'header_referrer' ) ) { add_filter( 'wp_headers', array( __CLASS__, 'security_headers' ) ); } if ( self::on( 'block_user_enum' ) ) { add_action( 'template_redirect', array( __CLASS__, 'block_author_query' ), 1 ); add_filter( 'rest_endpoints', array( __CLASS__, 'hide_user_endpoints' ) ); add_filter( 'wp_sitemaps_add_provider', array( __CLASS__, 'remove_users_sitemap' ), 10, 2 ); } if ( self::on( 'hide_wp_version' ) ) { remove_action( 'wp_head', 'wp_generator' ); add_filter( 'the_generator', '__return_empty_string' ); add_filter( 'style_loader_src', array( __CLASS__, 'strip_core_version' ), 9999 ); add_filter( 'script_loader_src', array( __CLASS__, 'strip_core_version' ), 9999 ); } } /** * Is a switch on? * * @param string $key Setting key. * @return bool */ public static function on( $key ) { return (bool) (int) Settings::get( $key ); } /** * Is the XML-RPC switch on? * * @return bool */ public static function xmlrpc_disabled() { return self::on( 'disable_xmlrpc' ); } /** * Is the theme editor switch on? * * @return bool */ public static function theme_editor_disabled() { return self::on( 'disable_theme_editor' ); } /** * Whether the file editors are already disabled in wp-config.php. * * @return bool */ public static function editor_disabled_by_config() { return ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) || ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ); } /** * Active plugins that need XML-RPC. * * @return array Names. */ public static function xmlrpc_dependents() { $active = (array) get_option( 'active_plugins', array() ); $found = array(); foreach ( self::XMLRPC_DEPENDENTS as $file => $name ) { if ( in_array( $file, $active, true ) ) { $found[] = $name; } } return $found; } /** * Switch off XML-RPC completely. */ private static function block_xmlrpc() { // Runs on init so translations are available. A plain response is used // because wp_die() hands XML-RPC requests to an IXR-based handler that // is not loaded yet at this point. if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { add_action( 'init', array( __CLASS__, 'refuse_xmlrpc_request' ), 0 ); } // Belt and braces: no authenticated methods, no methods at all. add_filter( 'xmlrpc_enabled', '__return_false' ); add_filter( 'xmlrpc_methods', '__return_empty_array', PHP_INT_MAX ); // Stop advertising the endpoint. add_filter( 'wp_headers', array( __CLASS__, 'remove_pingback_header' ) ); add_filter( 'pings_open', '__return_false', PHP_INT_MAX ); remove_action( 'wp_head', 'rsd_link' ); } /** * Answer a request to xmlrpc.php with 403 Forbidden. */ public static function refuse_xmlrpc_request() { status_header( 403 ); nocache_headers(); if ( ! headers_sent() ) { header( 'Content-Type: text/plain; charset=utf-8' ); } echo esc_html__( 'XML-RPC services are disabled on this site.', 'siteaudithub' ); exit; } /** * Remove the X-Pingback response header. * * @param array $headers Headers. * @return array */ public static function remove_pingback_header( $headers ) { if ( is_array( $headers ) ) { unset( $headers['X-Pingback'] ); } return $headers; } /** * Deny the capabilities behind the file editors. * * @param array $caps Primitive caps. * @param string $cap Requested cap. * @return array */ public static function deny_editors( $caps, $cap ) { if ( ( 'edit_themes' === $cap && self::on( 'disable_theme_editor' ) ) || ( 'edit_plugins' === $cap && self::on( 'disable_plugin_editor' ) ) ) { return array( 'do_not_allow' ); } return $caps; } /** * Add the enabled security headers to front-end responses. * * Existing values (from the server, another plugin or the theme) are kept. * * @param array $headers Headers. * @return array */ public static function security_headers( $headers ) { if ( ! is_array( $headers ) ) { return $headers; } $add = array(); if ( self::on( 'header_nosniff' ) ) { $add['X-Content-Type-Options'] = 'nosniff'; } if ( self::on( 'header_frame' ) ) { $add['X-Frame-Options'] = 'SAMEORIGIN'; } if ( self::on( 'header_referrer' ) ) { $add['Referrer-Policy'] = 'strict-origin-when-cross-origin'; } foreach ( $add as $name => $value ) { if ( ! isset( $headers[ $name ] ) ) { $headers[ $name ] = $value; } } return $headers; } /** * Send logged-out visitors who try ?author=N to the home page. */ public static function block_author_query() { if ( is_user_logged_in() || is_admin() ) { return; } if ( isset( $_GET['author'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checks for presence; nothing is processed. wp_safe_redirect( home_url( '/' ), 301 ); exit; } } /** * Remove the REST API users endpoints for logged-out visitors. * * @param array $endpoints Endpoints. * @return array */ public static function hide_user_endpoints( $endpoints ) { if ( is_user_logged_in() || ! is_array( $endpoints ) ) { return $endpoints; } foreach ( array_keys( $endpoints ) as $route ) { if ( 0 === strpos( $route, '/wp/v2/users' ) ) { unset( $endpoints[ $route ] ); } } return $endpoints; } /** * Drop the users sitemap (it lists author archive URLs). * * @param \WP_Sitemaps_Provider|false $provider Provider. * @param string $name Provider name. * @return \WP_Sitemaps_Provider|false */ public static function remove_users_sitemap( $provider, $name ) { return 'users' === $name ? false : $provider; } /** * Remove ?ver= from front-end asset URLs. * * Versions set by plugins and themes are left alone so their cache busting * keeps working. * * @param string $src Asset URL. * @return string */ public static function strip_core_version( $src ) { if ( is_admin() || ! is_string( $src ) || false === strpos( $src, 'ver=' ) ) { return $src; } $query = wp_parse_url( $src, PHP_URL_QUERY ); if ( ! $query ) { return $src; } parse_str( $query, $args ); if ( isset( $args['ver'] ) && get_bloginfo( 'version' ) === $args['ver'] ) { $src = remove_query_arg( 'ver', $src ); } return $src; } }