updates ); if ( $core_known ) { foreach ( $core->updates as $offer ) { if ( isset( $offer->response ) && 'upgrade' === $offer->response && ! empty( $offer->current ) ) { $latest = $offer->current; break; } } } if ( $latest && version_compare( $latest, $wp_version, '>' ) ) { /* translators: 1: installed, 2: available */ $findings[] = Catalog::finding( 'health_wp_update', '', sprintf( __( 'Installed %1$s, available %2$s.', 'siteaudithub' ), $wp_version, $latest ) ); } $overview[] = array( 'label' => __( 'WordPress version', 'siteaudithub' ), 'value' => $wp_version, 'status' => $latest && version_compare( $latest, $wp_version, '>' ) ? 'warning' : ( $core_known ? 'good' : 'unknown' ), 'note' => $latest && version_compare( $latest, $wp_version, '>' ) /* translators: %s: version */ ? sprintf( __( 'Update available: %s', 'siteaudithub' ), $latest ) : ( $core_known ? __( 'Up to date according to the last update check.', 'siteaudithub' ) : __( 'Update information unavailable (no recent update check).', 'siteaudithub' ) ), ); // PHP version. Active support ends ~2 years after release; security support ~3 years. $php_status = 'good'; if ( version_compare( PHP_VERSION, '8.1', '<' ) ) { $php_status = 'warning'; /* translators: %s: PHP version */ $findings[] = Catalog::finding( 'health_php_version', '', sprintf( __( 'PHP %s is end-of-life upstream.', 'siteaudithub' ), PHP_VERSION ), array( 'severity' => version_compare( PHP_VERSION, '8.0', '<' ) ? 'high' : 'medium' ) ); } $overview[] = array( 'label' => __( 'PHP version', 'siteaudithub' ), 'value' => PHP_VERSION, 'status' => $php_status, 'note' => 'good' === $php_status ? __( 'Supported by the PHP project at the time of this release.', 'siteaudithub' ) : __( 'Consider upgrading with your host.', 'siteaudithub' ), ); // Plugin and theme updates. $plugin_updates = get_site_transient( 'update_plugins' ); $pcount = is_object( $plugin_updates ) && ! empty( $plugin_updates->response ) ? count( (array) $plugin_updates->response ) : 0; if ( $pcount ) { $names = array(); $all = Paths::plugins(); foreach ( array_keys( (array) $plugin_updates->response ) as $file ) { $names[] = isset( $all[ $file ] ) ? $all[ $file ]['Name'] : $file; } /* translators: %d: count */ $findings[] = Catalog::finding( 'health_plugin_updates', '', sprintf( _n( '%d plugin has an update:', '%d plugins have updates:', $pcount, 'siteaudithub' ), $pcount ) . "\n" . implode( "\n", $names ) ); } $overview[] = array( 'label' => __( 'Plugin updates', 'siteaudithub' ), 'value' => (string) $pcount, 'status' => $pcount ? 'warning' : ( is_object( $plugin_updates ) ? 'good' : 'unknown' ), 'note' => is_object( $plugin_updates ) && ! empty( $plugin_updates->last_checked ) /* translators: %s: human time diff */ ? sprintf( __( 'Last checked %s ago.', 'siteaudithub' ), human_time_diff( (int) $plugin_updates->last_checked ) ) : __( 'WordPress has not checked for updates recently.', 'siteaudithub' ), ); $theme_updates = get_site_transient( 'update_themes' ); $tcount = is_object( $theme_updates ) && ! empty( $theme_updates->response ) ? count( (array) $theme_updates->response ) : 0; if ( $tcount ) { /* translators: %s: theme slugs */ $findings[] = Catalog::finding( 'health_theme_updates', '', sprintf( __( 'Themes: %s', 'siteaudithub' ), implode( ', ', array_keys( (array) $theme_updates->response ) ) ) ); } $overview[] = array( 'label' => __( 'Theme updates', 'siteaudithub' ), 'value' => (string) $tcount, 'status' => $tcount ? 'warning' : ( is_object( $theme_updates ) ? 'good' : 'unknown' ), 'note' => '', ); // HTTPS. $https = 0 === strpos( home_url(), 'https://' ) && 0 === strpos( site_url(), 'https://' ); if ( ! $https ) { $findings[] = Catalog::finding( 'health_not_https', '', home_url() ); } $overview[] = array( 'label' => __( 'HTTPS', 'siteaudithub' ), 'value' => $https ? __( 'Enabled', 'siteaudithub' ) : __( 'Not used', 'siteaudithub' ), 'status' => $https ? 'good' : 'critical', 'note' => '', ); // Debug configuration. $debug_display = defined( 'WP_DEBUG' ) && WP_DEBUG && ( ! defined( 'WP_DEBUG_DISPLAY' ) || WP_DEBUG_DISPLAY ) && 'production' === wp_get_environment_type(); if ( $debug_display ) { $findings[] = Catalog::finding( 'health_debug_display', '' ); } $overview[] = array( 'label' => __( 'Debug display', 'siteaudithub' ), 'value' => $debug_display ? __( 'On', 'siteaudithub' ) : __( 'Off', 'siteaudithub' ), 'status' => $debug_display ? 'warning' : 'good', /* translators: %s: environment type */ 'note' => sprintf( __( 'Environment type: %s', 'siteaudithub' ), wp_get_environment_type() ), ); $overview[] = Login_Guard::health_row(); if ( get_user_by( 'login', 'admin' ) ) { $findings[] = Catalog::finding( 'health_admin_user', '' ); } // Cron. $cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON; if ( $cron_disabled ) { $findings[] = Catalog::finding( 'health_cron_disabled', '' ); } $overdue = 0; $crons = function_exists( '_get_cron_array' ) ? _get_cron_array() : array(); foreach ( (array) $crons as $timestamp => $hooks ) { if ( (int) $timestamp < time() - HOUR_IN_SECONDS ) { $overdue += count( (array) $hooks ); } } if ( $overdue > 0 ) { /* translators: %d: count */ $findings[] = Catalog::finding( 'health_cron_overdue', '', sprintf( __( '%d event(s) overdue by more than one hour.', 'siteaudithub' ), $overdue ) ); } $overview[] = array( 'label' => __( 'WP-Cron', 'siteaudithub' ), 'value' => $cron_disabled ? __( 'Disabled (server cron expected)', 'siteaudithub' ) : __( 'Enabled', 'siteaudithub' ), 'status' => $overdue ? 'warning' : 'good', /* translators: %d: count */ 'note' => $overdue ? sprintf( __( '%d overdue event(s).', 'siteaudithub' ), $overdue ) : __( 'No events overdue by more than an hour.', 'siteaudithub' ), ); // Autoloaded options size. // phpcs:ignore WordPress.DB.DirectDatabaseQuery $autoload = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT SUM(LENGTH(option_value)) FROM %i WHERE autoload IN (%s, %s, %s, %s)', $wpdb->options, 'yes', 'on', 'auto-on', 'auto' ) ); if ( $autoload > 1024 * 1024 ) { $findings[] = Catalog::finding( 'health_autoload', '', size_format( $autoload ), array( 'severity' => $autoload > 3 * 1024 * 1024 ? 'high' : 'medium' ) ); } $overview[] = array( 'label' => __( 'Autoloaded options', 'siteaudithub' ), 'value' => size_format( $autoload ), 'status' => $autoload > 1024 * 1024 ? 'warning' : 'good', 'note' => __( 'Loaded on every request; under 1 MB is comfortable.', 'siteaudithub' ), ); // PHP environment. $missing = array(); foreach ( array( 'curl', 'dom', 'mbstring', 'json', 'openssl', 'zip', 'fileinfo', 'xml' ) as $ext ) { if ( ! extension_loaded( $ext ) ) { $missing[] = $ext; } } if ( $missing ) { $findings[] = Catalog::finding( 'health_php_extensions', '', implode( ', ', $missing ) ); } $overview[] = array( 'label' => __( 'PHP extensions', 'siteaudithub' ), 'value' => $missing ? implode( ', ', $missing ) . ' ' . __( 'missing', 'siteaudithub' ) : __( 'All recommended loaded', 'siteaudithub' ), 'status' => $missing ? 'warning' : 'good', 'note' => '', ); $memory = wp_convert_hr_to_bytes( (string) ini_get( 'memory_limit' ) ); if ( $memory > 0 && $memory < 128 * MB_IN_BYTES ) { $findings[] = Catalog::finding( 'health_memory_limit', '', (string) ini_get( 'memory_limit' ) ); } $overview[] = array( 'label' => __( 'PHP memory limit', 'siteaudithub' ), 'value' => (string) ini_get( 'memory_limit' ), 'status' => ( $memory > 0 && $memory < 128 * MB_IN_BYTES ) ? 'warning' : 'good', /* translators: %s: seconds */ 'note' => sprintf( __( 'max_execution_time: %s', 'siteaudithub' ), (string) ini_get( 'max_execution_time' ) ), ); // Inactive plugins/themes. $plugins = Paths::plugins(); $inactive = array(); $active = (array) get_option( 'active_plugins', array() ); $network = is_multisite() ? (array) get_site_option( 'active_sitewide_plugins', array() ) : array(); foreach ( $plugins as $file => $p ) { if ( ! in_array( $file, $active, true ) && ! isset( $network[ $file ] ) ) { $inactive[] = $p['Name']; } } if ( $inactive ) { $findings[] = Catalog::finding( 'health_inactive_plugins', '', implode( "\n", $inactive ) ); } $themes = wp_get_themes(); $active_theme = get_stylesheet(); $parent_theme = get_template(); $inactive_themes = array(); foreach ( $themes as $slug => $theme ) { if ( $slug !== $active_theme && $slug !== $parent_theme ) { $inactive_themes[] = $theme->get( 'Name' ); } } if ( count( $inactive_themes ) > 1 ) { $findings[] = Catalog::finding( 'health_inactive_themes', '', implode( "\n", $inactive_themes ) ); } $uploads = wp_get_upload_dir(); if ( empty( $uploads['error'] ) && ! wp_is_writable( $uploads['basedir'] ) ) { $findings[] = Catalog::finding( 'health_uploads_writable', '' ); } // Loopback checks. if ( $remote ) { $rest = Http::fetch( rest_url(), array( 'max_bytes' => 256 * 1024 ) ); $rest_ok = $rest['status'] >= 200 && $rest['status'] < 300; if ( ! $rest_ok ) { $findings[] = Catalog::finding( 'health_rest_api', '', $rest['status'] ? 'HTTP ' . $rest['status'] : $rest['error'] ); } $overview[] = array( 'label' => __( 'REST API', 'siteaudithub' ), 'value' => $rest['status'] ? 'HTTP ' . $rest['status'] : __( 'No response', 'siteaudithub' ), 'status' => $rest_ok ? 'good' : 'warning', 'note' => $rest_ok ? '' : $rest['error'], ); $robots = Http::fetch( home_url( '/robots.txt' ), array( 'max_bytes' => 64 * 1024 ) ); if ( 200 !== $robots['status'] ) { $findings[] = Catalog::finding( 'health_robots_missing', '', $robots['status'] ? 'HTTP ' . $robots['status'] : $robots['error'] ); } $overview[] = array( 'label' => __( 'robots.txt', 'siteaudithub' ), 'value' => $robots['status'] ? 'HTTP ' . $robots['status'] : __( 'No response', 'siteaudithub' ), 'status' => 200 === $robots['status'] ? 'good' : 'notice', 'note' => '', ); // Only probe the default log location; never read its contents into reports. if ( file_exists( Paths::content() . 'debug.log' ) ) { $content_url = content_url( '/debug.log' ); $log = Http::fetch( $content_url, array( 'method' => 'HEAD', 'max_bytes' => 1024, ) ); $public = $log['status'] >= 200 && $log['status'] < 300; if ( $public ) { $findings[] = Catalog::finding( 'health_debug_log_public', '', $content_url ); } $overview[] = array( 'label' => __( 'debug.log', 'siteaudithub' ), 'value' => $public ? __( 'Publicly accessible', 'siteaudithub' ) : __( 'Present, not publicly served', 'siteaudithub' ), 'status' => $public ? 'critical' : 'good', 'note' => '', ); } if ( ! $https ) { $https_url = set_url_scheme( home_url( '/' ), 'https' ); $probe = Http::fetch( $https_url, array( 'method' => 'HEAD', 'max_redirects' => 2, 'timeout' => 8, ) ); if ( 0 === $probe['status'] ) { $findings[] = Catalog::finding( 'health_https_unavailable', '', $probe['error'] ); } } } set_transient( self::TRANSIENT, array( 'time' => time(), 'remote' => $remote, 'overview' => $overview, 'findings' => $findings, ), WEEK_IN_SECONDS ); return $findings; } /** * Cached results for the UI (runs local-only checks if nothing cached). * * @return array */ public static function cached() { $cached = get_transient( self::TRANSIENT ); if ( ! is_array( $cached ) ) { self::run( false ); $cached = get_transient( self::TRANSIENT ); } return is_array( $cached ) ? $cached : array( 'time' => 0, 'remote' => false, 'overview' => array(), 'findings' => array(), ); } }