* Class for providing debug data based on a users WordPress environment.
* @subpackage Site_Health
#[AllowDynamicProperties]
* Calls all core functions to check for updates.
public static function check_for_updates() {
* Static function for generating site debug data when required.
* @since 5.3.0 Added database charset, database collation,
* and timezone information.
* @since 5.5.0 Added pretty permalinks support information.
* @since 6.7.0 Modularized into separate theme-oriented methods.
* @throws ImagickException
* @return array The debug data for the site.
public static function debug_data() {
* Set up the array that holds all debug information.
* When iterating through the debug data, the ordering of the sections
* occurs in insertion-order of the assignments into this array.
* This is the single assignment of the sections before filtering. Null-entries will
* be automatically be removed.
'wp-core' => self::get_wp_core(),
'wp-paths-sizes' => self::get_wp_paths_sizes(),
'wp-dropins' => self::get_wp_dropins(),
'wp-active-theme' => self::get_wp_active_theme(),
'wp-parent-theme' => self::get_wp_parent_theme(),
'wp-themes-inactive' => self::get_wp_themes_inactive(),
'wp-mu-plugins' => self::get_wp_mu_plugins(),
'wp-plugins-active' => self::get_wp_plugins_active(),
'wp-plugins-inactive' => self::get_wp_plugins_inactive(),
'wp-media' => self::get_wp_media(),
'wp-server' => self::get_wp_server(),
'wp-database' => self::get_wp_database(),
'wp-constants' => self::get_wp_constants(),
'wp-filesystem' => self::get_wp_filesystem(),
* Remove null elements from the array. The individual methods are
* allowed to return `null`, which communicates that the category
* of debug data isn't relevant and shouldn't be passed through.
static function ( $section ) {
return isset( $section );
* Filters the debug information shown on the Tools -> Site Health -> Info screen.
* Plugin or themes may wish to introduce their own debug information without creating
* additional admin pages. They can utilize this filter to introduce their own sections
* or add more data to existing sections.
* Array keys for sections added by core are all prefixed with `wp-`. Plugins and themes
* should use their own slug as a prefix, both for consistency as well as avoiding
* key collisions. Note that the array keys are used as labels for the copied data.
* All strings are expected to be plain text except `$description` that can contain
* inline HTML tags (see below).
* The debug information to be added to the core information page.
* This is an associative multi-dimensional array, up to three levels deep.
* The topmost array holds the sections, keyed by section ID.
* Each section has a `$fields` associative array (see below), and each `$value` in `$fields`
* can be another associative array of name/value pairs when there is more structured data
* @type string $label Required. The title for this section of the debug output.
* @type string $description Optional. A description for your information section which
* may contain basic HTML markup, inline tags only as it is
* outputted in a paragraph.
* @type bool $show_count Optional. If set to `true`, the amount of fields will be included
* in the title for this section. Default false.
* @type bool $private Optional. If set to `true`, the section and all associated fields
* will be excluded from the copied data. Default false.
* Required. An associative array containing the fields to be displayed in the section,
* An associative array containing the data to be displayed for the field.
* @type string $label Required. The label for this piece of information.
* @type mixed $value Required. The output that is displayed for this field.
* Text should be translated. Can be an associative array
* that is displayed as name/value pairs.
* Accepted types: `string|int|float|(string|int|float)[]`.
* @type string $debug Optional. The output that is used for this field when
* the user copies the data. It should be more concise and
* not translated. If not set, the content of `$value`
* is used. Note that the array keys are used as labels
* @type bool $private Optional. If set to `true`, the field will be excluded
* from the copied data, allowing you to show, for example,
* API keys here. Default false.
$info = apply_filters( 'debug_information', $info );
* Gets the WordPress core section of the debug data.
private static function get_wp_core(): array {
// Save few function calls.
$permalink_structure = get_option( 'permalink_structure' );
$users_can_register = get_option( 'users_can_register' );
$blog_public = get_option( 'blog_public' );
$default_comment_status = get_option( 'default_comment_status' );
$environment_type = wp_get_environment_type();
$core_version = wp_get_wp_version();
$core_updates = get_core_updates();
$core_update_needed = '';
if ( is_array( $core_updates ) ) {
foreach ( $core_updates as $core => $update ) {
if ( 'upgrade' === $update->response ) {
/* translators: %s: Latest WordPress version number. */
$core_update_needed = ' ' . sprintf( __( '(Latest version: %s)' ), $update->version );
$core_update_needed = '';
'label' => __( 'Version' ),
'value' => $core_version . $core_update_needed,
'debug' => $core_version,
'site_language' => array(
'label' => __( 'Site Language' ),
'user_language' => array(
'label' => __( 'User Language' ),
'value' => get_user_locale(),
'label' => __( 'Timezone' ),
'value' => wp_timezone_string(),
'label' => __( 'Home URL' ),
'value' => get_bloginfo( 'url' ),
'label' => __( 'Site URL' ),
'value' => get_bloginfo( 'wpurl' ),
'label' => __( 'Permalink structure' ),
'value' => $permalink_structure ? $permalink_structure : __( 'No permalink structure set' ),
'debug' => $permalink_structure,
'label' => __( 'Is this site using HTTPS?' ),
'value' => $is_ssl ? __( 'Yes' ) : __( 'No' ),
'label' => __( 'Is this a multisite?' ),
'value' => is_multisite() ? __( 'Yes' ) : __( 'No' ),
'debug' => is_multisite(),
'user_registration' => array(
'label' => __( 'Can anyone register on this site?' ),
'value' => $users_can_register ? __( 'Yes' ) : __( 'No' ),
'debug' => $users_can_register,
'label' => __( 'Is this site discouraging search engines?' ),
'value' => $blog_public ? __( 'No' ) : __( 'Yes' ),
'default_comment_status' => array(
'label' => __( 'Default comment status' ),
'value' => 'open' === $default_comment_status ? _x( 'Open', 'comment status' ) : _x( 'Closed', 'comment status' ),
'debug' => $default_comment_status,
'environment_type' => array(
'label' => __( 'Environment type' ),
'value' => $environment_type,
'debug' => $environment_type,
// Conditionally add debug information for multisite setups.
$site_id = get_current_blog_id();
$fields['site_id'] = array(
'label' => __( 'Site ID' ),
$network_query = new WP_Network_Query();
$network_ids = $network_query->query(
'no_found_rows' => false,
foreach ( $network_ids as $network_id ) {
$site_count += get_blog_count( $network_id );
$fields['site_count'] = array(
'label' => __( 'Site count' ),
$fields['network_count'] = array(
'label' => __( 'Network count' ),
'value' => $network_query->found_networks,
$fields['user_count'] = array(
'label' => __( 'User count' ),
'value' => get_user_count(),
// WordPress features requiring processing.
$wp_dotorg = wp_remote_get( 'https://wordpress.org', array( 'timeout' => 10 ) );
if ( ! is_wp_error( $wp_dotorg ) ) {
$fields['dotorg_communication'] = array(
'label' => __( 'Communication with WordPress.org' ),
'value' => __( 'WordPress.org is reachable' ),
$fields['dotorg_communication'] = array(
'label' => __( 'Communication with WordPress.org' ),
/* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */
__( 'Unable to reach WordPress.org at %1$s: %2$s' ),
gethostbyname( 'wordpress.org' ),
$wp_dotorg->get_error_message()
'debug' => $wp_dotorg->get_error_message(),
'label' => __( 'WordPress' ),
* Gets the WordPress drop-in section of the debug data.
private static function get_wp_dropins(): array {
// Get a list of all drop-in replacements.
$dropins = get_dropins();
// Get drop-ins descriptions.
$dropin_descriptions = _get_dropins();
foreach ( $dropins as $dropin_key => $dropin ) {
$fields[ sanitize_text_field( $dropin_key ) ] = array(
'value' => $dropin_descriptions[ $dropin_key ][0],
'label' => __( 'Drop-ins' ),
'description' => sprintf(
/* translators: %s: wp-content directory name. */
__( 'Drop-ins are single files, found in the %s directory, that replace or enhance WordPress features in ways that are not possible for traditional plugins.' ),
'<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
* Gets the WordPress server section of the debug data.
private static function get_wp_server(): array {
// Populate the server debug fields.
if ( function_exists( 'php_uname' ) ) {
$server_architecture = sprintf( '%s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'm' ) );
$server_architecture = 'unknown';
$php_version_debug = PHP_VERSION;
// Whether PHP supports 64-bit.
$php64bit = ( PHP_INT_SIZE * 8 === 64 );
( $php64bit ? __( '(Supports 64bit values)' ) : __( '(Does not support 64bit values)' ) )
$php_version_debug .= ' 64bit';
$fields['server_architecture'] = array(
'label' => __( 'Server architecture' ),
'value' => ( 'unknown' !== $server_architecture ? $server_architecture : __( 'Unable to determine server architecture' ) ),
'debug' => $server_architecture,
$fields['httpd_software'] = array(
'label' => __( 'Web server' ),
'value' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : __( 'Unable to determine what web server software is used' ) ),
'debug' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown' ),
$fields['php_version'] = array(
'label' => __( 'PHP version' ),
'debug' => $php_version_debug,
$fields['php_sapi'] = array(
'label' => __( 'PHP SAPI' ),
// Some servers disable `ini_set()` and `ini_get()`, we check this before trying to get configuration values.
if ( ! function_exists( 'ini_get' ) ) {
$fields['ini_get'] = array(
'label' => __( 'Server settings' ),
/* translators: %s: ini_get() */
__( 'Unable to determine some settings, as the %s function has been disabled.' ),
'debug' => 'ini_get() is disabled',
$fields['max_input_variables'] = array(
'label' => __( 'PHP max input variables' ),
'value' => ini_get( 'max_input_vars' ),
$fields['time_limit'] = array(
'label' => __( 'PHP time limit' ),
'value' => ini_get( 'max_execution_time' ),
if ( WP_Site_Health::get_instance()->php_memory_limit !== ini_get( 'memory_limit' ) ) {
$fields['memory_limit'] = array(
'label' => __( 'PHP memory limit' ),
'value' => WP_Site_Health::get_instance()->php_memory_limit,
$fields['admin_memory_limit'] = array(
'label' => __( 'PHP memory limit (only for admin screens)' ),
'value' => ini_get( 'memory_limit' ),
$fields['memory_limit'] = array(
'label' => __( 'PHP memory limit' ),
'value' => ini_get( 'memory_limit' ),
$fields['max_input_time'] = array(
'label' => __( 'Max input time' ),
'value' => ini_get( 'max_input_time' ),
$fields['upload_max_filesize'] = array(
'label' => __( 'Upload max filesize' ),
'value' => ini_get( 'upload_max_filesize' ),
$fields['php_post_max_size'] = array(
'label' => __( 'PHP post max size' ),
'value' => ini_get( 'post_max_size' ),
if ( function_exists( 'curl_version' ) ) {
$fields['curl_version'] = array(
'label' => __( 'cURL version' ),
'value' => sprintf( '%s %s', $curl['version'], $curl['ssl_version'] ),
$fields['curl_version'] = array(
'label' => __( 'cURL version' ),
'value' => __( 'Not available' ),
'debug' => 'not available',
$suhosin_loaded = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
$fields['suhosin'] = array(
'label' => __( 'Is SUHOSIN installed?' ),
'value' => ( $suhosin_loaded ? __( 'Yes' ) : __( 'No' ) ),
'debug' => $suhosin_loaded,
$imagick_loaded = extension_loaded( 'imagick' );
$fields['imagick_availability'] = array(
'label' => __( 'Is the Imagick library available?' ),
'value' => ( $imagick_loaded ? __( 'Yes' ) : __( 'No' ) ),
'debug' => $imagick_loaded,
$pretty_permalinks_supported = got_url_rewrite();
$fields['pretty_permalinks'] = array(
'label' => __( 'Are pretty permalinks supported?' ),
'value' => ( $pretty_permalinks_supported ? __( 'Yes' ) : __( 'No' ) ),
'debug' => $pretty_permalinks_supported,
// Check if a .htaccess file exists.
if ( is_file( ABSPATH . '.htaccess' ) ) {
// If the file exists, grab the content of it.
$htaccess_content = file_get_contents( ABSPATH . '.htaccess' );
// Filter away the core WordPress rules.
$filtered_htaccess_content = trim( preg_replace( '/\# BEGIN WordPress[\s\S]+?# END WordPress/si', '', $htaccess_content ) );
$filtered_htaccess_content = ! empty( $filtered_htaccess_content );
if ( $filtered_htaccess_content ) {
/* translators: %s: .htaccess */
$htaccess_rules_string = sprintf( __( 'Custom rules have been added to your %s file.' ), '.htaccess' );
/* translators: %s: .htaccess */
$htaccess_rules_string = sprintf( __( 'Your %s file contains only core WordPress features.' ), '.htaccess' );
$fields['htaccess_extra_rules'] = array(