1<?php
2/**
3 * Class for providing debug data based on a users WordPress environment.
4 *
5 * @package WordPress
6 * @subpackage Site_Health
7 * @since 5.2.0
8 */
9
10class WP_Debug_Data {
11	/**
12	 * Calls all core functions to check for updates.
13	 *
14	 * @since 5.2.0
15	 */
16	static function check_for_updates() {
17		wp_version_check();
18		wp_update_plugins();
19		wp_update_themes();
20	}
21
22	/**
23	 * Static function for generating site debug data when required.
24	 *
25	 * @since 5.2.0
26	 * @since 5.3.0 Added database charset, database collation,
27	 *              and timezone information.
28	 * @since 5.5.0 Added pretty permalinks support information.
29	 *
30	 * @throws ImagickException
31	 * @global wpdb $wpdb WordPress database abstraction object.
32	 *
33	 * @return array The debug data for the site.
34	 */
35	static function debug_data() {
36		global $wpdb;
37
38		// Save few function calls.
39		$upload_dir             = wp_upload_dir();
40		$permalink_structure    = get_option( 'permalink_structure' );
41		$is_ssl                 = is_ssl();
42		$is_multisite           = is_multisite();
43		$users_can_register     = get_option( 'users_can_register' );
44		$blog_public            = get_option( 'blog_public' );
45		$default_comment_status = get_option( 'default_comment_status' );
46		$environment_type       = wp_get_environment_type();
47		$core_version           = get_bloginfo( 'version' );
48		$core_updates           = get_core_updates();
49		$core_update_needed     = '';
50
51		if ( is_array( $core_updates ) ) {
52			foreach ( $core_updates as $core => $update ) {
53				if ( 'upgrade' === $update->response ) {
54					/* translators: %s: Latest WordPress version number. */
55					$core_update_needed = ' ' . sprintf( __( '(Latest version: %s)' ), $update->version );
56				} else {
57					$core_update_needed = '';
58				}
59			}
60		}
61
62		// Set up the array that holds all debug information.
63		$info = array();
64
65		$info['wp-core'] = array(
66			'label'  => __( 'WordPress' ),
67			'fields' => array(
68				'version'                => array(
69					'label' => __( 'Version' ),
70					'value' => $core_version . $core_update_needed,
71					'debug' => $core_version,
72				),
73				'site_language'          => array(
74					'label' => __( 'Site Language' ),
75					'value' => get_locale(),
76				),
77				'user_language'          => array(
78					'label' => __( 'User Language' ),
79					'value' => get_user_locale(),
80				),
81				'timezone'               => array(
82					'label' => __( 'Timezone' ),
83					'value' => wp_timezone_string(),
84				),
85				'home_url'               => array(
86					'label'   => __( 'Home URL' ),
87					'value'   => get_bloginfo( 'url' ),
88					'private' => true,
89				),
90				'site_url'               => array(
91					'label'   => __( 'Site URL' ),
92					'value'   => get_bloginfo( 'wpurl' ),
93					'private' => true,
94				),
95				'permalink'              => array(
96					'label' => __( 'Permalink structure' ),
97					'value' => $permalink_structure ? $permalink_structure : __( 'No permalink structure set' ),
98					'debug' => $permalink_structure,
99				),
100				'https_status'           => array(
101					'label' => __( 'Is this site using HTTPS?' ),
102					'value' => $is_ssl ? __( 'Yes' ) : __( 'No' ),
103					'debug' => $is_ssl,
104				),
105				'multisite'              => array(
106					'label' => __( 'Is this a multisite?' ),
107					'value' => $is_multisite ? __( 'Yes' ) : __( 'No' ),
108					'debug' => $is_multisite,
109				),
110				'user_registration'      => array(
111					'label' => __( 'Can anyone register on this site?' ),
112					'value' => $users_can_register ? __( 'Yes' ) : __( 'No' ),
113					'debug' => $users_can_register,
114				),
115				'blog_public'            => array(
116					'label' => __( 'Is this site discouraging search engines?' ),
117					'value' => $blog_public ? __( 'No' ) : __( 'Yes' ),
118					'debug' => $blog_public,
119				),
120				'default_comment_status' => array(
121					'label' => __( 'Default comment status' ),
122					'value' => 'open' === $default_comment_status ? _x( 'Open', 'comment status' ) : _x( 'Closed', 'comment status' ),
123					'debug' => $default_comment_status,
124				),
125				'environment_type'       => array(
126					'label' => __( 'Environment type' ),
127					'value' => $environment_type,
128					'debug' => $environment_type,
129				),
130			),
131		);
132
133		if ( ! $is_multisite ) {
134			$info['wp-paths-sizes'] = array(
135				'label'  => __( 'Directories and Sizes' ),
136				'fields' => array(),
137			);
138		}
139
140		$info['wp-dropins'] = array(
141			'label'       => __( 'Drop-ins' ),
142			'show_count'  => true,
143			'description' => sprintf(
144				/* translators: %s: wp-content directory name. */
145				__( '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.' ),
146				'<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>'
147			),
148			'fields'      => array(),
149		);
150
151		$info['wp-active-theme'] = array(
152			'label'  => __( 'Active Theme' ),
153			'fields' => array(),
154		);
155
156		$info['wp-parent-theme'] = array(
157			'label'  => __( 'Parent Theme' ),
158			'fields' => array(),
159		);
160
161		$info['wp-themes-inactive'] = array(
162			'label'      => __( 'Inactive Themes' ),
163			'show_count' => true,
164			'fields'     => array(),
165		);
166
167		$info['wp-mu-plugins'] = array(
168			'label'      => __( 'Must Use Plugins' ),
169			'show_count' => true,
170			'fields'     => array(),
171		);
172
173		$info['wp-plugins-active'] = array(
174			'label'      => __( 'Active Plugins' ),
175			'show_count' => true,
176			'fields'     => array(),
177		);
178
179		$info['wp-plugins-inactive'] = array(
180			'label'      => __( 'Inactive Plugins' ),
181			'show_count' => true,
182			'fields'     => array(),
183		);
184
185		$info['wp-media'] = array(
186			'label'  => __( 'Media Handling' ),
187			'fields' => array(),
188		);
189
190		$info['wp-server'] = array(
191			'label'       => __( 'Server' ),
192			'description' => __( 'The options shown below relate to your server setup. If changes are required, you may need your web host&#8217;s assistance.' ),
193			'fields'      => array(),
194		);
195
196		$info['wp-database'] = array(
197			'label'  => __( 'Database' ),
198			'fields' => array(),
199		);
200
201		// Check if WP_DEBUG_LOG is set.
202		$wp_debug_log_value = __( 'Disabled' );
203
204		if ( is_string( WP_DEBUG_LOG ) ) {
205			$wp_debug_log_value = WP_DEBUG_LOG;
206		} elseif ( WP_DEBUG_LOG ) {
207			$wp_debug_log_value = __( 'Enabled' );
208		}
209
210		// Check CONCATENATE_SCRIPTS.
211		if ( defined( 'CONCATENATE_SCRIPTS' ) ) {
212			$concatenate_scripts       = CONCATENATE_SCRIPTS ? __( 'Enabled' ) : __( 'Disabled' );
213			$concatenate_scripts_debug = CONCATENATE_SCRIPTS ? 'true' : 'false';
214		} else {
215			$concatenate_scripts       = __( 'Undefined' );
216			$concatenate_scripts_debug = 'undefined';
217		}
218
219		// Check COMPRESS_SCRIPTS.
220		if ( defined( 'COMPRESS_SCRIPTS' ) ) {
221			$compress_scripts       = COMPRESS_SCRIPTS ? __( 'Enabled' ) : __( 'Disabled' );
222			$compress_scripts_debug = COMPRESS_SCRIPTS ? 'true' : 'false';
223		} else {
224			$compress_scripts       = __( 'Undefined' );
225			$compress_scripts_debug = 'undefined';
226		}
227
228		// Check COMPRESS_CSS.
229		if ( defined( 'COMPRESS_CSS' ) ) {
230			$compress_css       = COMPRESS_CSS ? __( 'Enabled' ) : __( 'Disabled' );
231			$compress_css_debug = COMPRESS_CSS ? 'true' : 'false';
232		} else {
233			$compress_css       = __( 'Undefined' );
234			$compress_css_debug = 'undefined';
235		}
236
237		// Check WP_LOCAL_DEV.
238		if ( defined( 'WP_LOCAL_DEV' ) ) {
239			$wp_local_dev       = WP_LOCAL_DEV ? __( 'Enabled' ) : __( 'Disabled' );
240			$wp_local_dev_debug = WP_LOCAL_DEV ? 'true' : 'false';
241		} else {
242			$wp_local_dev       = __( 'Undefined' );
243			$wp_local_dev_debug = 'undefined';
244		}
245
246		$info['wp-constants'] = array(
247			'label'       => __( 'WordPress Constants' ),
248			'description' => __( 'These settings alter where and how parts of WordPress are loaded.' ),
249			'fields'      => array(
250				'ABSPATH'             => array(
251					'label'   => 'ABSPATH',
252					'value'   => ABSPATH,
253					'private' => true,
254				),
255				'WP_HOME'             => array(
256					'label' => 'WP_HOME',
257					'value' => ( defined( 'WP_HOME' ) ? WP_HOME : __( 'Undefined' ) ),
258					'debug' => ( defined( 'WP_HOME' ) ? WP_HOME : 'undefined' ),
259				),
260				'WP_SITEURL'          => array(
261					'label' => 'WP_SITEURL',
262					'value' => ( defined( 'WP_SITEURL' ) ? WP_SITEURL : __( 'Undefined' ) ),
263					'debug' => ( defined( 'WP_SITEURL' ) ? WP_SITEURL : 'undefined' ),
264				),
265				'WP_CONTENT_DIR'      => array(
266					'label' => 'WP_CONTENT_DIR',
267					'value' => WP_CONTENT_DIR,
268				),
269				'WP_PLUGIN_DIR'       => array(
270					'label' => 'WP_PLUGIN_DIR',
271					'value' => WP_PLUGIN_DIR,
272				),
273				'WP_MEMORY_LIMIT'     => array(
274					'label' => 'WP_MEMORY_LIMIT',
275					'value' => WP_MEMORY_LIMIT,
276				),
277				'WP_MAX_MEMORY_LIMIT' => array(
278					'label' => 'WP_MAX_MEMORY_LIMIT',
279					'value' => WP_MAX_MEMORY_LIMIT,
280				),
281				'WP_DEBUG'            => array(
282					'label' => 'WP_DEBUG',
283					'value' => WP_DEBUG ? __( 'Enabled' ) : __( 'Disabled' ),
284					'debug' => WP_DEBUG,
285				),
286				'WP_DEBUG_DISPLAY'    => array(
287					'label' => 'WP_DEBUG_DISPLAY',
288					'value' => WP_DEBUG_DISPLAY ? __( 'Enabled' ) : __( 'Disabled' ),
289					'debug' => WP_DEBUG_DISPLAY,
290				),
291				'WP_DEBUG_LOG'        => array(
292					'label' => 'WP_DEBUG_LOG',
293					'value' => $wp_debug_log_value,
294					'debug' => WP_DEBUG_LOG,
295				),
296				'SCRIPT_DEBUG'        => array(
297					'label' => 'SCRIPT_DEBUG',
298					'value' => SCRIPT_DEBUG ? __( 'Enabled' ) : __( 'Disabled' ),
299					'debug' => SCRIPT_DEBUG,
300				),
301				'WP_CACHE'            => array(
302					'label' => 'WP_CACHE',
303					'value' => WP_CACHE ? __( 'Enabled' ) : __( 'Disabled' ),
304					'debug' => WP_CACHE,
305				),
306				'CONCATENATE_SCRIPTS' => array(
307					'label' => 'CONCATENATE_SCRIPTS',
308					'value' => $concatenate_scripts,
309					'debug' => $concatenate_scripts_debug,
310				),
311				'COMPRESS_SCRIPTS'    => array(
312					'label' => 'COMPRESS_SCRIPTS',
313					'value' => $compress_scripts,
314					'debug' => $compress_scripts_debug,
315				),
316				'COMPRESS_CSS'        => array(
317					'label' => 'COMPRESS_CSS',
318					'value' => $compress_css,
319					'debug' => $compress_css_debug,
320				),
321				'WP_LOCAL_DEV'        => array(
322					'label' => 'WP_LOCAL_DEV',
323					'value' => $wp_local_dev,
324					'debug' => $wp_local_dev_debug,
325				),
326				'DB_CHARSET'          => array(
327					'label' => 'DB_CHARSET',
328					'value' => ( defined( 'DB_CHARSET' ) ? DB_CHARSET : __( 'Undefined' ) ),
329					'debug' => ( defined( 'DB_CHARSET' ) ? DB_CHARSET : 'undefined' ),
330				),
331				'DB_COLLATE'          => array(
332					'label' => 'DB_COLLATE',
333					'value' => ( defined( 'DB_COLLATE' ) ? DB_COLLATE : __( 'Undefined' ) ),
334					'debug' => ( defined( 'DB_COLLATE' ) ? DB_COLLATE : 'undefined' ),
335				),
336			),
337		);
338
339		$is_writable_abspath            = wp_is_writable( ABSPATH );
340		$is_writable_wp_content_dir     = wp_is_writable( WP_CONTENT_DIR );
341		$is_writable_upload_dir         = wp_is_writable( $upload_dir['basedir'] );
342		$is_writable_wp_plugin_dir      = wp_is_writable( WP_PLUGIN_DIR );
343		$is_writable_template_directory = wp_is_writable( get_theme_root( get_template() ) );
344
345		$info['wp-filesystem'] = array(
346			'label'       => __( 'Filesystem Permissions' ),
347			'description' => __( 'Shows whether WordPress is able to write to the directories it needs access to.' ),
348			'fields'      => array(
349				'wordpress'  => array(
350					'label' => __( 'The main WordPress directory' ),
351					'value' => ( $is_writable_abspath ? __( 'Writable' ) : __( 'Not writable' ) ),
352					'debug' => ( $is_writable_abspath ? 'writable' : 'not writable' ),
353				),
354				'wp-content' => array(
355					'label' => __( 'The wp-content directory' ),
356					'value' => ( $is_writable_wp_content_dir ? __( 'Writable' ) : __( 'Not writable' ) ),
357					'debug' => ( $is_writable_wp_content_dir ? 'writable' : 'not writable' ),
358				),
359				'uploads'    => array(
360					'label' => __( 'The uploads directory' ),
361					'value' => ( $is_writable_upload_dir ? __( 'Writable' ) : __( 'Not writable' ) ),
362					'debug' => ( $is_writable_upload_dir ? 'writable' : 'not writable' ),
363				),
364				'plugins'    => array(
365					'label' => __( 'The plugins directory' ),
366					'value' => ( $is_writable_wp_plugin_dir ? __( 'Writable' ) : __( 'Not writable' ) ),
367					'debug' => ( $is_writable_wp_plugin_dir ? 'writable' : 'not writable' ),
368				),
369				'themes'     => array(
370					'label' => __( 'The themes directory' ),
371					'value' => ( $is_writable_template_directory ? __( 'Writable' ) : __( 'Not writable' ) ),
372					'debug' => ( $is_writable_template_directory ? 'writable' : 'not writable' ),
373				),
374			),
375		);
376
377		// Conditionally add debug information for multisite setups.
378		if ( is_multisite() ) {
379			$network_query = new WP_Network_Query();
380			$network_ids   = $network_query->query(
381				array(
382					'fields'        => 'ids',
383					'number'        => 100,
384					'no_found_rows' => false,
385				)
386			);
387
388			$site_count = 0;
389			foreach ( $network_ids as $network_id ) {
390				$site_count += get_blog_count( $network_id );
391			}
392
393			$info['wp-core']['fields']['user_count'] = array(
394				'label' => __( 'User count' ),
395				'value' => get_user_count(),
396			);
397
398			$info['wp-core']['fields']['site_count'] = array(
399				'label' => __( 'Site count' ),
400				'value' => $site_count,
401			);
402
403			$info['wp-core']['fields']['network_count'] = array(
404				'label' => __( 'Network count' ),
405				'value' => $network_query->found_networks,
406			);
407		} else {
408			$user_count = count_users();
409
410			$info['wp-core']['fields']['user_count'] = array(
411				'label' => __( 'User count' ),
412				'value' => $user_count['total_users'],
413			);
414		}
415
416		// WordPress features requiring processing.
417		$wp_dotorg = wp_remote_get( 'https://wordpress.org', array( 'timeout' => 10 ) );
418
419		if ( ! is_wp_error( $wp_dotorg ) ) {
420			$info['wp-core']['fields']['dotorg_communication'] = array(
421				'label' => __( 'Communication with WordPress.org' ),
422				'value' => __( 'WordPress.org is reachable' ),
423				'debug' => 'true',
424			);
425		} else {
426			$info['wp-core']['fields']['dotorg_communication'] = array(
427				'label' => __( 'Communication with WordPress.org' ),
428				'value' => sprintf(
429					/* translators: 1: The IP address WordPress.org resolves to. 2: The error returned by the lookup. */
430					__( 'Unable to reach WordPress.org at %1$s: %2$s' ),
431					gethostbyname( 'wordpress.org' ),
432					$wp_dotorg->get_error_message()
433				),
434				'debug' => $wp_dotorg->get_error_message(),
435			);
436		}
437
438		// Remove accordion for Directories and Sizes if in Multisite.
439		if ( ! $is_multisite ) {
440			$loading = __( 'Loading&hellip;' );
441
442			$info['wp-paths-sizes']['fields'] = array(
443				'wordpress_path' => array(
444					'label' => __( 'WordPress directory location' ),
445					'value' => untrailingslashit( ABSPATH ),
446				),
447				'wordpress_size' => array(
448					'label' => __( 'WordPress directory size' ),
449					'value' => $loading,
450					'debug' => 'loading...',
451				),
452				'uploads_path'   => array(
453					'label' => __( 'Uploads directory location' ),
454					'value' => $upload_dir['basedir'],
455				),
456				'uploads_size'   => array(
457					'label' => __( 'Uploads directory size' ),
458					'value' => $loading,
459					'debug' => 'loading...',
460				),
461				'themes_path'    => array(
462					'label' => __( 'Themes directory location' ),
463					'value' => get_theme_root(),
464				),
465				'themes_size'    => array(
466					'label' => __( 'Themes directory size' ),
467					'value' => $loading,
468					'debug' => 'loading...',
469				),
470				'plugins_path'   => array(
471					'label' => __( 'Plugins directory location' ),
472					'value' => WP_PLUGIN_DIR,
473				),
474				'plugins_size'   => array(
475					'label' => __( 'Plugins directory size' ),
476					'value' => $loading,
477					'debug' => 'loading...',
478				),
479				'database_size'  => array(
480					'label' => __( 'Database size' ),
481					'value' => $loading,
482					'debug' => 'loading...',
483				),
484				'total_size'     => array(
485					'label' => __( 'Total installation size' ),
486					'value' => $loading,
487					'debug' => 'loading...',
488				),
489			);
490		}
491
492		// Get a list of all drop-in replacements.
493		$dropins = get_dropins();
494
495		// Get dropins descriptions.
496		$dropin_descriptions = _get_dropins();
497
498		// Spare few function calls.
499		$not_available = __( 'Not available' );
500
501		foreach ( $dropins as $dropin_key => $dropin ) {
502			$info['wp-dropins']['fields'][ sanitize_text_field( $dropin_key ) ] = array(
503				'label' => $dropin_key,
504				'value' => $dropin_descriptions[ $dropin_key ][0],
505				'debug' => 'true',
506			);
507		}
508
509		// Populate the media fields.
510		$info['wp-media']['fields']['image_editor'] = array(
511			'label' => __( 'Active editor' ),
512			'value' => _wp_image_editor_choose(),
513		);
514
515		// Get ImageMagic information, if available.
516		if ( class_exists( 'Imagick' ) ) {
517			// Save the Imagick instance for later use.
518			$imagick             = new Imagick();
519			$imagemagick_version = $imagick->getVersion();
520		} else {
521			$imagemagick_version = __( 'Not available' );
522		}
523
524		$info['wp-media']['fields']['imagick_module_version'] = array(
525			'label' => __( 'ImageMagick version number' ),
526			'value' => ( is_array( $imagemagick_version ) ? $imagemagick_version['versionNumber'] : $imagemagick_version ),
527		);
528
529		$info['wp-media']['fields']['imagemagick_version'] = array(
530			'label' => __( 'ImageMagick version string' ),
531			'value' => ( is_array( $imagemagick_version ) ? $imagemagick_version['versionString'] : $imagemagick_version ),
532		);
533
534		$imagick_version = phpversion( 'imagick' );
535
536		$info['wp-media']['fields']['imagick_version'] = array(
537			'label' => __( 'Imagick version' ),
538			'value' => ( $imagick_version ) ? $imagick_version : __( 'Not available' ),
539		);
540
541		if ( ! function_exists( 'ini_get' ) ) {
542			$info['wp-media']['fields']['ini_get'] = array(
543				'label' => __( 'File upload settings' ),
544				'value' => sprintf(
545					/* translators: %s: ini_get() */
546					__( 'Unable to determine some settings, as the %s function has been disabled.' ),
547					'ini_get()'
548				),
549				'debug' => 'ini_get() is disabled',
550			);
551		} else {
552			// Get the PHP ini directive values.
553			$post_max_size       = ini_get( 'post_max_size' );
554			$upload_max_filesize = ini_get( 'upload_max_filesize' );
555			$max_file_uploads    = ini_get( 'max_file_uploads' );
556			$effective           = min( wp_convert_hr_to_bytes( $post_max_size ), wp_convert_hr_to_bytes( $upload_max_filesize ) );
557
558			// Add info in Media section.
559			$info['wp-media']['fields']['file_uploads']        = array(
560				'label' => __( 'File uploads' ),
561				'value' => empty( ini_get( 'file_uploads' ) ) ? __( 'Disabled' ) : __( 'Enabled' ),
562				'debug' => 'File uploads is turned off',
563			);
564			$info['wp-media']['fields']['post_max_size']       = array(
565				'label' => __( 'Max size of post data allowed' ),
566				'value' => $post_max_size,
567			);
568			$info['wp-media']['fields']['upload_max_filesize'] = array(
569				'label' => __( 'Max size of an uploaded file' ),
570				'value' => $upload_max_filesize,
571			);
572			$info['wp-media']['fields']['max_effective_size']  = array(
573				'label' => __( 'Max effective file size' ),
574				'value' => size_format( $effective ),
575			);
576			$info['wp-media']['fields']['max_file_uploads']    = array(
577				'label' => __( 'Max number of files allowed' ),
578				'value' => number_format( $max_file_uploads ),
579			);
580		}
581
582		// If Imagick is used as our editor, provide some more information about its limitations.
583		if ( 'WP_Image_Editor_Imagick' === _wp_image_editor_choose() && isset( $imagick ) && $imagick instanceof Imagick ) {
584			$limits = array(
585				'area'   => ( defined( 'imagick::RESOURCETYPE_AREA' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_AREA ) ) : $not_available ),
586				'disk'   => ( defined( 'imagick::RESOURCETYPE_DISK' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_DISK ) : $not_available ),
587				'file'   => ( defined( 'imagick::RESOURCETYPE_FILE' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_FILE ) : $not_available ),
588				'map'    => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : $not_available ),
589				'memory' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : $not_available ),
590				'thread' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : $not_available ),
591			);
592
593			$limits_debug = array(
594				'imagick::RESOURCETYPE_AREA'   => ( defined( 'imagick::RESOURCETYPE_AREA' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_AREA ) ) : 'not available' ),
595				'imagick::RESOURCETYPE_DISK'   => ( defined( 'imagick::RESOURCETYPE_DISK' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_DISK ) : 'not available' ),
596				'imagick::RESOURCETYPE_FILE'   => ( defined( 'imagick::RESOURCETYPE_FILE' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_FILE ) : 'not available' ),
597				'imagick::RESOURCETYPE_MAP'    => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : 'not available' ),
598				'imagick::RESOURCETYPE_MEMORY' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : 'not available' ),
599				'imagick::RESOURCETYPE_THREAD' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : 'not available' ),
600			);
601
602			$info['wp-media']['fields']['imagick_limits'] = array(
603				'label' => __( 'Imagick Resource Limits' ),
604				'value' => $limits,
605				'debug' => $limits_debug,
606			);
607
608			try {
609				$formats = Imagick::queryFormats( '*' );
610			} catch ( Exception $e ) {
611				$formats = array();
612			}
613
614			$info['wp-media']['fields']['imagemagick_file_formats'] = array(
615				'label' => __( 'ImageMagick supported file formats' ),
616				'value' => ( empty( $formats ) ) ? __( 'Unable to determine' ) : implode( ', ', $formats ),
617				'debug' => ( empty( $formats ) ) ? 'Unable to determine' : implode( ', ', $formats ),
618			);
619		}
620
621		// Get GD information, if available.
622		if ( function_exists( 'gd_info' ) ) {
623			$gd = gd_info();
624		} else {
625			$gd = false;
626		}
627
628		$info['wp-media']['fields']['gd_version'] = array(
629			'label' => __( 'GD version' ),
630			'value' => ( is_array( $gd ) ? $gd['GD Version'] : $not_available ),
631			'debug' => ( is_array( $gd ) ? $gd['GD Version'] : 'not available' ),
632		);
633
634		$gd_image_formats     = array();
635		$gd_supported_formats = array(
636			'GIF Create' => 'GIF',
637			'JPEG'       => 'JPEG',
638			'PNG'        => 'PNG',
639			'WebP'       => 'WebP',
640			'BMP'        => 'BMP',
641			'AVIF'       => 'AVIF',
642			'HEIF'       => 'HEIF',
643			'TIFF'       => 'TIFF',
644			'XPM'        => 'XPM',
645		);
646
647		foreach ( $gd_supported_formats as $format_key => $format ) {
648			$index = $format_key . ' Support';
649			if ( isset( $gd[ $index ] ) && $gd[ $index ] ) {
650				array_push( $gd_image_formats, $format );
651			}
652		}
653
654		if ( ! empty( $gd_image_formats ) ) {
655			$info['wp-media']['fields']['gd_formats'] = array(
656				'label' => __( 'GD supported file formats' ),
657				'value' => implode( ', ', $gd_image_formats ),
658			);
659		}
660
661		// Get Ghostscript information, if available.
662		if ( function_exists( 'exec' ) ) {
663			$gs = exec( 'gs --version' );
664
665			if ( empty( $gs ) ) {
666				$gs       = $not_available;
667				$gs_debug = 'not available';
668			} else {
669				$gs_debug = $gs;
670			}
671		} else {
672			$gs       = __( 'Unable to determine if Ghostscript is installed' );
673			$gs_debug = 'unknown';
674		}
675
676		$info['wp-media']['fields']['ghostscript_version'] = array(
677			'label' => __( 'Ghostscript version' ),
678			'value' => $gs,
679			'debug' => $gs_debug,
680		);
681
682		// Populate the server debug fields.
683		if ( function_exists( 'php_uname' ) ) {
684			$server_architecture = sprintf( '%s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'm' ) );
685		} else {
686			$server_architecture = 'unknown';
687		}
688
689		if ( function_exists( 'phpversion' ) ) {
690			$php_version_debug = phpversion();
691			// Whether PHP supports 64-bit.
692			$php64bit = ( PHP_INT_SIZE * 8 === 64 );
693
694			$php_version = sprintf(
695				'%s %s',
696				$php_version_debug,
697				( $php64bit ? __( '(Supports 64bit values)' ) : __( '(Does not support 64bit values)' ) )
698			);
699
700			if ( $php64bit ) {
701				$php_version_debug .= ' 64bit';
702			}
703		} else {
704			$php_version       = __( 'Unable to determine PHP version' );
705			$php_version_debug = 'unknown';
706		}
707
708		if ( function_exists( 'php_sapi_name' ) ) {
709			$php_sapi = php_sapi_name();
710		} else {
711			$php_sapi = 'unknown';
712		}
713
714		$info['wp-server']['fields']['server_architecture'] = array(
715			'label' => __( 'Server architecture' ),
716			'value' => ( 'unknown' !== $server_architecture ? $server_architecture : __( 'Unable to determine server architecture' ) ),
717			'debug' => $server_architecture,
718		);
719		$info['wp-server']['fields']['httpd_software']      = array(
720			'label' => __( 'Web server' ),
721			'value' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : __( 'Unable to determine what web server software is used' ) ),
722			'debug' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown' ),
723		);
724		$info['wp-server']['fields']['php_version']         = array(
725			'label' => __( 'PHP version' ),
726			'value' => $php_version,
727			'debug' => $php_version_debug,
728		);
729		$info['wp-server']['fields']['php_sapi']            = array(
730			'label' => __( 'PHP SAPI' ),
731			'value' => ( 'unknown' !== $php_sapi ? $php_sapi : __( 'Unable to determine PHP SAPI' ) ),
732			'debug' => $php_sapi,
733		);
734
735		// Some servers disable `ini_set()` and `ini_get()`, we check this before trying to get configuration values.
736		if ( ! function_exists( 'ini_get' ) ) {
737			$info['wp-server']['fields']['ini_get'] = array(
738				'label' => __( 'Server settings' ),
739				'value' => sprintf(
740					/* translators: %s: ini_get() */
741					__( 'Unable to determine some settings, as the %s function has been disabled.' ),
742					'ini_get()'
743				),
744				'debug' => 'ini_get() is disabled',
745			);
746		} else {
747			$info['wp-server']['fields']['max_input_variables'] = array(
748				'label' => __( 'PHP max input variables' ),
749				'value' => ini_get( 'max_input_vars' ),
750			);
751			$info['wp-server']['fields']['time_limit']          = array(
752				'label' => __( 'PHP time limit' ),
753				'value' => ini_get( 'max_execution_time' ),
754			);
755
756			if ( WP_Site_Health::get_instance()->php_memory_limit !== ini_get( 'memory_limit' ) ) {
757				$info['wp-server']['fields']['memory_limit']       = array(
758					'label' => __( 'PHP memory limit' ),
759					'value' => WP_Site_Health::get_instance()->php_memory_limit,
760				);
761				$info['wp-server']['fields']['admin_memory_limit'] = array(
762					'label' => __( 'PHP memory limit (only for admin screens)' ),
763					'value' => ini_get( 'memory_limit' ),
764				);
765			} else {
766				$info['wp-server']['fields']['memory_limit'] = array(
767					'label' => __( 'PHP memory limit' ),
768					'value' => ini_get( 'memory_limit' ),
769				);
770			}
771
772			$info['wp-server']['fields']['max_input_time']      = array(
773				'label' => __( 'Max input time' ),
774				'value' => ini_get( 'max_input_time' ),
775			);
776			$info['wp-server']['fields']['upload_max_filesize'] = array(
777				'label' => __( 'Upload max filesize' ),
778				'value' => ini_get( 'upload_max_filesize' ),
779			);
780			$info['wp-server']['fields']['php_post_max_size']   = array(
781				'label' => __( 'PHP post max size' ),
782				'value' => ini_get( 'post_max_size' ),
783			);
784		}
785
786		if ( function_exists( 'curl_version' ) ) {
787			$curl = curl_version();
788
789			$info['wp-server']['fields']['curl_version'] = array(
790				'label' => __( 'cURL version' ),
791				'value' => sprintf( '%s %s', $curl['version'], $curl['ssl_version'] ),
792			);
793		} else {
794			$info['wp-server']['fields']['curl_version'] = array(
795				'label' => __( 'cURL version' ),
796				'value' => $not_available,
797				'debug' => 'not available',
798			);
799		}
800
801		// SUHOSIN.
802		$suhosin_loaded = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
803
804		$info['wp-server']['fields']['suhosin'] = array(
805			'label' => __( 'Is SUHOSIN installed?' ),
806			'value' => ( $suhosin_loaded ? __( 'Yes' ) : __( 'No' ) ),
807			'debug' => $suhosin_loaded,
808		);
809
810		// Imagick.
811		$imagick_loaded = extension_loaded( 'imagick' );
812
813		$info['wp-server']['fields']['imagick_availability'] = array(
814			'label' => __( 'Is the Imagick library available?' ),
815			'value' => ( $imagick_loaded ? __( 'Yes' ) : __( 'No' ) ),
816			'debug' => $imagick_loaded,
817		);
818
819		// Pretty permalinks.
820		$pretty_permalinks_supported = got_url_rewrite();
821
822		$info['wp-server']['fields']['pretty_permalinks'] = array(
823			'label' => __( 'Are pretty permalinks supported?' ),
824			'value' => ( $pretty_permalinks_supported ? __( 'Yes' ) : __( 'No' ) ),
825			'debug' => $pretty_permalinks_supported,
826		);
827
828		// Check if a .htaccess file exists.
829		if ( is_file( ABSPATH . '.htaccess' ) ) {
830			// If the file exists, grab the content of it.
831			$htaccess_content = file_get_contents( ABSPATH . '.htaccess' );
832
833			// Filter away the core WordPress rules.
834			$filtered_htaccess_content = trim( preg_replace( '/\# BEGIN WordPress[\s\S]+?# END WordPress/si', '', $htaccess_content ) );
835			$filtered_htaccess_content = ! empty( $filtered_htaccess_content );
836
837			if ( $filtered_htaccess_content ) {
838				/* translators: %s: .htaccess */
839				$htaccess_rules_string = sprintf( __( 'Custom rules have been added to your %s file.' ), '.htaccess' );
840			} else {
841				/* translators: %s: .htaccess */
842				$htaccess_rules_string = sprintf( __( 'Your %s file contains only core WordPress features.' ), '.htaccess' );
843			}
844
845			$info['wp-server']['fields']['htaccess_extra_rules'] = array(
846				'label' => __( '.htaccess rules' ),
847				'value' => $htaccess_rules_string,
848				'debug' => $filtered_htaccess_content,
849			);
850		}
851
852		// Populate the database debug fields.
853		if ( is_resource( $wpdb->dbh ) ) {
854			// Old mysql extension.
855			$extension = 'mysql';
856		} elseif ( is_object( $wpdb->dbh ) ) {
857			// mysqli or PDO.
858			$extension = get_class( $wpdb->dbh );
859		} else {
860			// Unknown sql extension.
861			$extension = null;
862		}
863
864		$server = $wpdb->get_var( 'SELECT VERSION()' );
865
866		if ( isset( $wpdb->use_mysqli ) && $wpdb->use_mysqli ) {
867			$client_version = $wpdb->dbh->client_info;
868		} else {
869			// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info,PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
870			if ( preg_match( '|[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}|', mysql_get_client_info(), $matches ) ) {
871				$client_version = $matches[0];
872			} else {
873				$client_version = null;
874			}
875		}
876
877		$info['wp-database']['fields']['extension'] = array(
878			'label' => __( 'Extension' ),
879			'value' => $extension,
880		);
881
882		$info['wp-database']['fields']['server_version'] = array(
883			'label' => __( 'Server version' ),
884			'value' => $server,
885		);
886
887		$info['wp-database']['fields']['client_version'] = array(
888			'label' => __( 'Client version' ),
889			'value' => $client_version,
890		);
891
892		$info['wp-database']['fields']['database_user'] = array(
893			'label'   => __( 'Database username' ),
894			'value'   => $wpdb->dbuser,
895			'private' => true,
896		);
897
898		$info['wp-database']['fields']['database_host'] = array(
899			'label'   => __( 'Database host' ),
900			'value'   => $wpdb->dbhost,
901			'private' => true,
902		);
903
904		$info['wp-database']['fields']['database_name'] = array(
905			'label'   => __( 'Database name' ),
906			'value'   => $wpdb->dbname,
907			'private' => true,
908		);
909
910		$info['wp-database']['fields']['database_prefix'] = array(
911			'label'   => __( 'Table prefix' ),
912			'value'   => $wpdb->prefix,
913			'private' => true,
914		);
915
916		$info['wp-database']['fields']['database_charset'] = array(
917			'label'   => __( 'Database charset' ),
918			'value'   => $wpdb->charset,
919			'private' => true,
920		);
921
922		$info['wp-database']['fields']['database_collate'] = array(
923			'label'   => __( 'Database collation' ),
924			'value'   => $wpdb->collate,
925			'private' => true,
926		);
927
928		// List must use plugins if there are any.
929		$mu_plugins = get_mu_plugins();
930
931		foreach ( $mu_plugins as $plugin_path => $plugin ) {
932			$plugin_version = $plugin['Version'];
933			$plugin_author  = $plugin['Author'];
934
935			$plugin_version_string       = __( 'No version or author information is available.' );
936			$plugin_version_string_debug = 'author: (undefined), version: (undefined)';
937
938			if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) {
939				/* translators: 1: Plugin version number. 2: Plugin author name. */
940				$plugin_version_string       = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author );
941				$plugin_version_string_debug = sprintf( 'version: %s, author: %s', $plugin_version, $plugin_author );
942			} else {
943				if ( ! empty( $plugin_author ) ) {
944					/* translators: %s: Plugin author name. */
945					$plugin_version_string       = sprintf( __( 'By %s' ), $plugin_author );
946					$plugin_version_string_debug = sprintf( 'author: %s, version: (undefined)', $plugin_author );
947				}
948
949				if ( ! empty( $plugin_version ) ) {
950					/* translators: %s: Plugin version number. */
951					$plugin_version_string       = sprintf( __( 'Version %s' ), $plugin_version );
952					$plugin_version_string_debug = sprintf( 'author: (undefined), version: %s', $plugin_version );
953				}
954			}
955
956			$info['wp-mu-plugins']['fields'][ sanitize_text_field( $plugin['Name'] ) ] = array(
957				'label' => $plugin['Name'],
958				'value' => $plugin_version_string,
959				'debug' => $plugin_version_string_debug,
960			);
961		}
962
963		// List all available plugins.
964		$plugins        = get_plugins();
965		$plugin_updates = get_plugin_updates();
966		$transient      = get_site_transient( 'update_plugins' );
967
968		$auto_updates = array();
969
970		$auto_updates_enabled = wp_is_auto_update_enabled_for_type( 'plugin' );
971
972		if ( $auto_updates_enabled ) {
973			$auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
974		}
975
976		foreach ( $plugins as $plugin_path => $plugin ) {
977			$plugin_part = ( is_plugin_active( $plugin_path ) ) ? 'wp-plugins-active' : 'wp-plugins-inactive';
978
979			$plugin_version = $plugin['Version'];
980			$plugin_author  = $plugin['Author'];
981
982			$plugin_version_string       = __( 'No version or author information is available.' );
983			$plugin_version_string_debug = 'author: (undefined), version: (undefined)';
984
985			if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) {
986				/* translators: 1: Plugin version number. 2: Plugin author name. */
987				$plugin_version_string       = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author );
988				$plugin_version_string_debug = sprintf( 'version: %s, author: %s', $plugin_version, $plugin_author );
989			} else {
990				if ( ! empty( $plugin_author ) ) {
991					/* translators: %s: Plugin author name. */
992					$plugin_version_string       = sprintf( __( 'By %s' ), $plugin_author );
993					$plugin_version_string_debug = sprintf( 'author: %s, version: (undefined)', $plugin_author );
994				}
995
996				if ( ! empty( $plugin_version ) ) {
997					/* translators: %s: Plugin version number. */
998					$plugin_version_string       = sprintf( __( 'Version %s' ), $plugin_version );
999					$plugin_version_string_debug = sprintf( 'author: (undefined), version: %s', $plugin_version );
1000				}
1001			}
1002
1003			if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
1004				/* translators: %s: Latest plugin version number. */
1005				$plugin_version_string       .= ' ' . sprintf( __( '(Latest version: %s)' ), $plugin_updates[ $plugin_path ]->update->new_version );
1006				$plugin_version_string_debug .= sprintf( ' (latest version: %s)', $plugin_updates[ $plugin_path ]->update->new_version );
1007			}
1008
1009			if ( $auto_updates_enabled ) {
1010				if ( isset( $transient->response[ $plugin_path ] ) ) {
1011					$item = $transient->response[ $plugin_path ];
1012				} elseif ( isset( $transient->no_update[ $plugin_path ] ) ) {
1013					$item = $transient->no_update[ $plugin_path ];
1014				} else {
1015					$item = array(
1016						'id'            => $plugin_path,
1017						'slug'          => '',
1018						'plugin'        => $plugin_path,
1019						'new_version'   => '',
1020						'url'           => '',
1021						'package'       => '',
1022						'icons'         => array(),
1023						'banners'       => array(),
1024						'banners_rtl'   => array(),
1025						'tested'        => '',
1026						'requires_php'  => '',
1027						'compatibility' => new stdClass(),
1028					);
1029					$item = wp_parse_args( $plugin, $item );
1030				}
1031
1032				$auto_update_forced = wp_is_auto_update_forced_for_item( 'plugin', null, (object) $item );
1033
1034				if ( ! is_null( $auto_update_forced ) ) {
1035					$enabled = $auto_update_forced;
1036				} else {
1037					$enabled = in_array( $plugin_path, $auto_updates, true );
1038				}
1039
1040				if ( $enabled ) {
1041					$auto_updates_string = __( 'Auto-updates enabled' );
1042				} else {
1043					$auto_updates_string = __( 'Auto-updates disabled' );
1044				}
1045
1046				/**
1047				 * Filters the text string of the auto-updates setting for each plugin in the Site Health debug data.
1048				 *
1049				 * @since 5.5.0
1050				 *
1051				 * @param string $auto_updates_string The string output for the auto-updates column.
1052				 * @param string $plugin_path         The path to the plugin file.
1053				 * @param array  $plugin              An array of plugin data.
1054				 * @param bool   $enabled             Whether auto-updates are enabled for this item.
1055				 */
1056				$auto_updates_string = apply_filters( 'plugin_auto_update_debug_string', $auto_updates_string, $plugin_path, $plugin, $enabled );
1057
1058				$plugin_version_string       .= ' | ' . $auto_updates_string;
1059				$plugin_version_string_debug .= ', ' . $auto_updates_string;
1060			}
1061
1062			$info[ $plugin_part ]['fields'][ sanitize_text_field( $plugin['Name'] ) ] = array(
1063				'label' => $plugin['Name'],
1064				'value' => $plugin_version_string,
1065				'debug' => $plugin_version_string_debug,
1066			);
1067		}
1068
1069		// Populate the section for the currently active theme.
1070		global $_wp_theme_features;
1071		$theme_features = array();
1072
1073		if ( ! empty( $_wp_theme_features ) ) {
1074			foreach ( $_wp_theme_features as $feature => $options ) {
1075				$theme_features[] = $feature;
1076			}
1077		}
1078
1079		$active_theme  = wp_get_theme();
1080		$theme_updates = get_theme_updates();
1081		$transient     = get_site_transient( 'update_themes' );
1082
1083		$active_theme_version       = $active_theme->version;
1084		$active_theme_version_debug = $active_theme_version;
1085
1086		$auto_updates         = array();
1087		$auto_updates_enabled = wp_is_auto_update_enabled_for_type( 'theme' );
1088		if ( $auto_updates_enabled ) {
1089			$auto_updates = (array) get_site_option( 'auto_update_themes', array() );
1090		}
1091
1092		if ( array_key_exists( $active_theme->stylesheet, $theme_updates ) ) {
1093			$theme_update_new_version = $theme_updates[ $active_theme->stylesheet ]->update['new_version'];
1094
1095			/* translators: %s: Latest theme version number. */
1096			$active_theme_version       .= ' ' . sprintf( __( '(Latest version: %s)' ), $theme_update_new_version );
1097			$active_theme_version_debug .= sprintf( ' (latest version: %s)', $theme_update_new_version );
1098		}
1099
1100		$active_theme_author_uri = $active_theme->display( 'AuthorURI' );
1101
1102		if ( $active_theme->parent_theme ) {
1103			$active_theme_parent_theme = sprintf(
1104				/* translators: 1: Theme name. 2: Theme slug. */
1105				__( '%1$s (%2$s)' ),
1106				$active_theme->parent_theme,
1107				$active_theme->template
1108			);
1109			$active_theme_parent_theme_debug = sprintf(
1110				'%s (%s)',
1111				$active_theme->parent_theme,
1112				$active_theme->template
1113			);
1114		} else {
1115			$active_theme_parent_theme       = __( 'None' );
1116			$active_theme_parent_theme_debug = 'none';
1117		}
1118
1119		$info['wp-active-theme']['fields'] = array(
1120			'name'           => array(
1121				'label' => __( 'Name' ),
1122				'value' => sprintf(
1123					/* translators: 1: Theme name. 2: Theme slug. */
1124					__( '%1$s (%2$s)' ),
1125					$active_theme->name,
1126					$active_theme->stylesheet
1127				),
1128			),
1129			'version'        => array(
1130				'label' => __( 'Version' ),
1131				'value' => $active_theme_version,
1132				'debug' => $active_theme_version_debug,
1133			),
1134			'author'         => array(
1135				'label' => __( 'Author' ),
1136				'value' => wp_kses( $active_theme->author, array() ),
1137			),
1138			'author_website' => array(
1139				'label' => __( 'Author website' ),
1140				'value' => ( $active_theme_author_uri ? $active_theme_author_uri : __( 'Undefined' ) ),
1141				'debug' => ( $active_theme_author_uri ? $active_theme_author_uri : '(undefined)' ),
1142			),
1143			'parent_theme'   => array(
1144				'label' => __( 'Parent theme' ),
1145				'value' => $active_theme_parent_theme,
1146				'debug' => $active_theme_parent_theme_debug,
1147			),
1148			'theme_features' => array(
1149				'label' => __( 'Theme features' ),
1150				'value' => implode( ', ', $theme_features ),
1151			),
1152			'theme_path'     => array(
1153				'label' => __( 'Theme directory location' ),
1154				'value' => get_stylesheet_directory(),
1155			),
1156		);
1157
1158		if ( $auto_updates_enabled ) {
1159			if ( isset( $transient->response[ $active_theme->stylesheet ] ) ) {
1160				$item = $transient->response[ $active_theme->stylesheet ];
1161			} elseif ( isset( $transient->no_update[ $active_theme->stylesheet ] ) ) {
1162				$item = $transient->no_update[ $active_theme->stylesheet ];
1163			} else {
1164				$item = array(
1165					'theme'        => $active_theme->stylesheet,
1166					'new_version'  => $active_theme->version,
1167					'url'          => '',
1168					'package'      => '',
1169					'requires'     => '',
1170					'requires_php' => '',
1171				);
1172			}
1173
1174			$auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, (object) $item );
1175
1176			if ( ! is_null( $auto_update_forced ) ) {
1177				$enabled = $auto_update_forced;
1178			} else {
1179				$enabled = in_array( $active_theme->stylesheet, $auto_updates, true );
1180			}
1181
1182			if ( $enabled ) {
1183				$auto_updates_string = __( 'Enabled' );
1184			} else {
1185				$auto_updates_string = __( 'Disabled' );
1186			}
1187
1188			/** This filter is documented in wp-admin/includes/class-wp-debug-data.php */
1189			$auto_updates_string = apply_filters( 'theme_auto_update_debug_string', $auto_updates_string, $active_theme, $enabled );
1190
1191			$info['wp-active-theme']['fields']['auto_update'] = array(
1192				'label' => __( 'Auto-updates' ),
1193				'value' => $auto_updates_string,
1194				'debug' => $auto_updates_string,
1195			);
1196		}
1197
1198		$parent_theme = $active_theme->parent();
1199
1200		if ( $parent_theme ) {
1201			$parent_theme_version       = $parent_theme->version;
1202			$parent_theme_version_debug = $parent_theme_version;
1203
1204			if ( array_key_exists( $parent_theme->stylesheet, $theme_updates ) ) {
1205				$parent_theme_update_new_version = $theme_updates[ $parent_theme->stylesheet ]->update['new_version'];
1206
1207				/* translators: %s: Latest theme version number. */
1208				$parent_theme_version       .= ' ' . sprintf( __( '(Latest version: %s)' ), $parent_theme_update_new_version );
1209				$parent_theme_version_debug .= sprintf( ' (latest version: %s)', $parent_theme_update_new_version );
1210			}
1211
1212			$parent_theme_author_uri = $parent_theme->display( 'AuthorURI' );
1213
1214			$info['wp-parent-theme']['fields'] = array(
1215				'name'           => array(
1216					'label' => __( 'Name' ),
1217					'value' => sprintf(
1218						/* translators: 1: Theme name. 2: Theme slug. */
1219						__( '%1$s (%2$s)' ),
1220						$parent_theme->name,
1221						$parent_theme->stylesheet
1222					),
1223				),
1224				'version'        => array(
1225					'label' => __( 'Version' ),
1226					'value' => $parent_theme_version,
1227					'debug' => $parent_theme_version_debug,
1228				),
1229				'author'         => array(
1230					'label' => __( 'Author' ),
1231					'value' => wp_kses( $parent_theme->author, array() ),
1232				),
1233				'author_website' => array(
1234					'label' => __( 'Author website' ),
1235					'value' => ( $parent_theme_author_uri ? $parent_theme_author_uri : __( 'Undefined' ) ),
1236					'debug' => ( $parent_theme_author_uri ? $parent_theme_author_uri : '(undefined)' ),
1237				),
1238				'theme_path'     => array(
1239					'label' => __( 'Theme directory location' ),
1240					'value' => get_template_directory(),
1241				),
1242			);
1243
1244			if ( $auto_updates_enabled ) {
1245				if ( isset( $transient->response[ $parent_theme->stylesheet ] ) ) {
1246					$item = $transient->response[ $parent_theme->stylesheet ];
1247				} elseif ( isset( $transient->no_update[ $parent_theme->stylesheet ] ) ) {
1248					$item = $transient->no_update[ $parent_theme->stylesheet ];
1249				} else {
1250					$item = array(
1251						'theme'        => $parent_theme->stylesheet,
1252						'new_version'  => $parent_theme->version,
1253						'url'          => '',
1254						'package'      => '',
1255						'requires'     => '',
1256						'requires_php' => '',
1257					);
1258				}
1259
1260				$auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, (object) $item );
1261
1262				if ( ! is_null( $auto_update_forced ) ) {
1263					$enabled = $auto_update_forced;
1264				} else {
1265					$enabled = in_array( $parent_theme->stylesheet, $auto_updates, true );
1266				}
1267
1268				if ( $enabled ) {
1269					$parent_theme_auto_update_string = __( 'Enabled' );
1270				} else {
1271					$parent_theme_auto_update_string = __( 'Disabled' );
1272				}
1273
1274				/** This filter is documented in wp-admin/includes/class-wp-debug-data.php */
1275				$parent_theme_auto_update_string = apply_filters( 'theme_auto_update_debug_string', $auto_updates_string, $parent_theme, $enabled );
1276
1277				$info['wp-parent-theme']['fields']['auto_update'] = array(
1278					'label' => __( 'Auto-update' ),
1279					'value' => $parent_theme_auto_update_string,
1280					'debug' => $parent_theme_auto_update_string,
1281				);
1282			}
1283		}
1284
1285		// Populate a list of all themes available in the install.
1286		$all_themes = wp_get_themes();
1287
1288		foreach ( $all_themes as $theme_slug => $theme ) {
1289			// Exclude the currently active theme from the list of all themes.
1290			if ( $active_theme->stylesheet === $theme_slug ) {
1291				continue;
1292			}
1293
1294			// Exclude the currently active parent theme from the list of all themes.
1295			if ( ! empty( $parent_theme ) && $parent_theme->stylesheet === $theme_slug ) {
1296				continue;
1297			}
1298
1299			$theme_version = $theme->version;
1300			$theme_author  = $theme->author;
1301
1302			// Sanitize.
1303			$theme_author = wp_kses( $theme_author, array() );
1304
1305			$theme_version_string       = __( 'No version or author information is available.' );
1306			$theme_version_string_debug = 'undefined';
1307
1308			if ( ! empty( $theme_version ) && ! empty( $theme_author ) ) {
1309				/* translators: 1: Theme version number. 2: Theme author name. */
1310				$theme_version_string       = sprintf( __( 'Version %1$s by %2$s' ), $theme_version, $theme_author );
1311				$theme_version_string_debug = sprintf( 'version: %s, author: %s', $theme_version, $theme_author );
1312			} else {
1313				if ( ! empty( $theme_author ) ) {
1314					/* translators: %s: Theme author name. */
1315					$theme_version_string       = sprintf( __( 'By %s' ), $theme_author );
1316					$theme_version_string_debug = sprintf( 'author: %s, version: (undefined)', $theme_author );
1317				}
1318
1319				if ( ! empty( $theme_version ) ) {
1320					/* translators: %s: Theme version number. */
1321					$theme_version_string       = sprintf( __( 'Version %s' ), $theme_version );
1322					$theme_version_string_debug = sprintf( 'author: (undefined), version: %s', $theme_version );
1323				}
1324			}
1325
1326			if ( array_key_exists( $theme_slug, $theme_updates ) ) {
1327				/* translators: %s: Latest theme version number. */
1328				$theme_version_string       .= ' ' . sprintf( __( '(Latest version: %s)' ), $theme_updates[ $theme_slug ]->update['new_version'] );
1329				$theme_version_string_debug .= sprintf( ' (latest version: %s)', $theme_updates[ $theme_slug ]->update['new_version'] );
1330			}
1331
1332			if ( $auto_updates_enabled ) {
1333				if ( isset( $transient->response[ $theme_slug ] ) ) {
1334					$item = $transient->response[ $theme_slug ];
1335				} elseif ( isset( $transient->no_update[ $theme_slug ] ) ) {
1336					$item = $transient->no_update[ $theme_slug ];
1337				} else {
1338					$item = array(
1339						'theme'        => $theme_slug,
1340						'new_version'  => $theme->version,
1341						'url'          => '',
1342						'package'      => '',
1343						'requires'     => '',
1344						'requires_php' => '',
1345					);
1346				}
1347
1348				$auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, (object) $item );
1349
1350				if ( ! is_null( $auto_update_forced ) ) {
1351					$enabled = $auto_update_forced;
1352				} else {
1353					$enabled = in_array( $theme_slug, $auto_updates, true );
1354				}
1355
1356				if ( $enabled ) {
1357					$auto_updates_string = __( 'Auto-updates enabled' );
1358				} else {
1359					$auto_updates_string = __( 'Auto-updates disabled' );
1360				}
1361
1362				/**
1363				 * Filters the text string of the auto-updates setting for each theme in the Site Health debug data.
1364				 *
1365				 * @since 5.5.0
1366				 *
1367				 * @param string   $auto_updates_string The string output for the auto-updates column.
1368				 * @param WP_Theme $theme               An object of theme data.
1369				 * @param bool     $enabled             Whether auto-updates are enabled for this item.
1370				 */
1371				$auto_updates_string = apply_filters( 'theme_auto_update_debug_string', $auto_updates_string, $theme, $enabled );
1372
1373				$theme_version_string       .= ' | ' . $auto_updates_string;
1374				$theme_version_string_debug .= ', ' . $auto_updates_string;
1375			}
1376
1377			$info['wp-themes-inactive']['fields'][ sanitize_text_field( $theme->name ) ] = array(
1378				'label' => sprintf(
1379					/* translators: 1: Theme name. 2: Theme slug. */
1380					__( '%1$s (%2$s)' ),
1381					$theme->name,
1382					$theme_slug
1383				),
1384				'value' => $theme_version_string,
1385				'debug' => $theme_version_string_debug,
1386			);
1387		}
1388
1389		// Add more filesystem checks.
1390		if ( defined( 'WPMU_PLUGIN_DIR' ) && is_dir( WPMU_PLUGIN_DIR ) ) {
1391			$is_writable_wpmu_plugin_dir = wp_is_writable( WPMU_PLUGIN_DIR );
1392
1393			$info['wp-filesystem']['fields']['mu-plugins'] = array(
1394				'label' => __( 'The must use plugins directory' ),
1395				'value' => ( $is_writable_wpmu_plugin_dir ? __( 'Writable' ) : __( 'Not writable' ) ),
1396				'debug' => ( $is_writable_wpmu_plugin_dir ? 'writable' : 'not writable' ),
1397			);
1398		}
1399
1400		/**
1401		 * Add or modify the debug information.
1402		 *
1403		 * Plugin or themes may wish to introduce their own debug information without creating additional admin pages
1404		 * they can utilize this filter to introduce their own sections or add more data to existing sections.
1405		 *
1406		 * Array keys for sections added by core are all prefixed with `wp-`, plugins and themes should use their own slug as
1407		 * a prefix, both for consistency as well as avoiding key collisions. Note that the array keys are used as labels
1408		 * for the copied data.
1409		 *
1410		 * All strings are expected to be plain text except $description that can contain inline HTML tags (see below).
1411		 *
1412		 * @since 5.2.0
1413		 *
1414		 * @param array $args {
1415		 *     The debug information to be added to the core information page.
1416		 *
1417		 *     This is an associative multi-dimensional array, up to three levels deep. The topmost array holds the sections.
1418		 *     Each section has a `$fields` associative array (see below), and each `$value` in `$fields` can be
1419		 *     another associative array of name/value pairs when there is more structured data to display.
1420		 *
1421		 *     @type string  $label        The title for this section of the debug output.
1422		 *     @type string  $description  Optional. A description for your information section which may contain basic HTML
1423		 *                                 markup, inline tags only as it is outputted in a paragraph.
1424		 *     @type boolean $show_count   Optional. If set to `true` the amount of fields will be included in the title for
1425		 *                                 this section.
1426		 *     @type boolean $private      Optional. If set to `true` the section and all associated fields will be excluded
1427		 *                                 from the copied data.
1428		 *     @type array   $fields {
1429		 *         An associative array containing the data to be displayed.
1430		 *
1431		 *         @type string  $label    The label for this piece of information.
1432		 *         @type string  $value    The output that is displayed for this field. Text should be translated. Can be
1433		 *                                 an associative array that is displayed as name/value pairs.
1434		 *         @type string  $debug    Optional. The output that is used for this field when the user copies the data.
1435		 *                                 It should be more concise and not translated. If not set, the content of `$value` is used.
1436		 *                                 Note that the array keys are used as labels for the copied data.
1437		 *         @type boolean $private  Optional. If set to `true` the field will not be included in the copied data
1438		 *                                 allowing you to show, for example, API keys here.
1439		 *     }
1440		 * }
1441		 */
1442		$info = apply_filters( 'debug_information', $info );
1443
1444		return $info;
1445	}
1446
1447	/**
1448	 * Format the information gathered for debugging, in a manner suitable for copying to a forum or support ticket.
1449	 *
1450	 * @since 5.2.0
1451	 *
1452	 * @param array  $info_array Information gathered from the `WP_Debug_Data::debug_data` function.
1453	 * @param string $type       The data type to return, either 'info' or 'debug'.
1454	 * @return string The formatted data.
1455	 */
1456	public static function format( $info_array, $type ) {
1457		$return = "`\n";
1458
1459		foreach ( $info_array as $section => $details ) {
1460			// Skip this section if there are no fields, or the section has been declared as private.
1461			if ( empty( $details['fields'] ) || ( isset( $details['private'] ) && $details['private'] ) ) {
1462				continue;
1463			}
1464
1465			$section_label = 'debug' === $type ? $section : $details['label'];
1466
1467			$return .= sprintf(
1468				"### %s%s ###\n\n",
1469				$section_label,
1470				( isset( $details['show_count'] ) && $details['show_count'] ? sprintf( ' (%d)', count( $details['fields'] ) ) : '' )
1471			);
1472
1473			foreach ( $details['fields'] as $field_name => $field ) {
1474				if ( isset( $field['private'] ) && true === $field['private'] ) {
1475					continue;
1476				}
1477
1478				if ( 'debug' === $type && isset( $field['debug'] ) ) {
1479					$debug_data = $field['debug'];
1480				} else {
1481					$debug_data = $field['value'];
1482				}
1483
1484				// Can be array, one level deep only.
1485				if ( is_array( $debug_data ) ) {
1486					$value = '';
1487
1488					foreach ( $debug_data as $sub_field_name => $sub_field_value ) {
1489						$value .= sprintf( "\n\t%s: %s", $sub_field_name, $sub_field_value );
1490					}
1491				} elseif ( is_bool( $debug_data ) ) {
1492					$value = $debug_data ? 'true' : 'false';
1493				} elseif ( empty( $debug_data ) && '0' !== $debug_data ) {
1494					$value = 'undefined';
1495				} else {
1496					$value = $debug_data;
1497				}
1498
1499				if ( 'debug' === $type ) {
1500					$label = $field_name;
1501				} else {
1502					$label = $field['label'];
1503				}
1504
1505				$return .= sprintf( "%s: %s\n", $label, $value );
1506			}
1507
1508			$return .= "\n";
1509		}
1510
1511		$return .= '`';
1512
1513		return $return;
1514	}
1515
1516	/**
1517	 * Fetch the total size of all the database tables for the active database user.
1518	 *
1519	 * @since 5.2.0
1520	 *
1521	 * @return int The size of the database, in bytes.
1522	 */
1523	public static function get_database_size() {
1524		global $wpdb;
1525		$size = 0;
1526		$rows = $wpdb->get_results( 'SHOW TABLE STATUS', ARRAY_A );
1527
1528		if ( $wpdb->num_rows > 0 ) {
1529			foreach ( $rows as $row ) {
1530				$size += $row['Data_length'] + $row['Index_length'];
1531			}
1532		}
1533
1534		return (int) $size;
1535	}
1536
1537	/**
1538	 * Fetch the sizes of the WordPress directories: `wordpress` (ABSPATH), `plugins`, `themes`, and `uploads`.
1539	 * Intended to supplement the array returned by `WP_Debug_Data::debug_data()`.
1540	 *
1541	 * @since 5.2.0
1542	 *
1543	 * @return array The sizes of the directories, also the database size and total installation size.
1544	 */
1545	public static function get_sizes() {
1546		$size_db    = self::get_database_size();
1547		$upload_dir = wp_get_upload_dir();
1548
1549		/*
1550		 * We will be using the PHP max execution time to prevent the size calculations
1551		 * from causing a timeout. The default value is 30 seconds, and some
1552		 * hosts do not allow you to read configuration values.
1553		 */
1554		if ( function_exists( 'ini_get' ) ) {
1555			$max_execution_time = ini_get( 'max_execution_time' );
1556		}
1557
1558		// The max_execution_time defaults to 0 when PHP runs from cli.
1559		// We still want to limit it below.
1560		if ( empty( $max_execution_time ) ) {
1561			$max_execution_time = 30;
1562		}
1563
1564		if ( $max_execution_time > 20 ) {
1565			// If the max_execution_time is set to lower than 20 seconds, reduce it a bit to prevent
1566			// edge-case timeouts that may happen after the size loop has finished running.
1567			$max_execution_time -= 2;
1568		}
1569
1570		// Go through the various installation directories and calculate their sizes.
1571		// No trailing slashes.
1572		$paths = array(
1573			'wordpress_size' => untrailingslashit( ABSPATH ),
1574			'themes_size'    => get_theme_root(),
1575			'plugins_size'   => WP_PLUGIN_DIR,
1576			'uploads_size'   => $upload_dir['basedir'],
1577		);
1578
1579		$exclude = $paths;
1580		unset( $exclude['wordpress_size'] );
1581		$exclude = array_values( $exclude );
1582
1583		$size_total = 0;
1584		$all_sizes  = array();
1585
1586		// Loop over all the directories we want to gather the sizes for.
1587		foreach ( $paths as $name => $path ) {
1588			$dir_size = null; // Default to timeout.
1589			$results  = array(
1590				'path' => $path,
1591				'raw'  => 0,
1592			);
1593
1594			if ( microtime( true ) - WP_START_TIMESTAMP < $max_execution_time ) {
1595				if ( 'wordpress_size' === $name ) {
1596					$dir_size = recurse_dirsize( $path, $exclude, $max_execution_time );
1597				} else {
1598					$dir_size = recurse_dirsize( $path, null, $max_execution_time );
1599				}
1600			}
1601
1602			if ( false === $dir_size ) {
1603				// Error reading.
1604				$results['size']  = __( 'The size cannot be calculated. The directory is not accessible. Usually caused by invalid permissions.' );
1605				$results['debug'] = 'not accessible';
1606
1607				// Stop total size calculation.
1608				$size_total = null;
1609			} elseif ( null === $dir_size ) {
1610				// Timeout.
1611				$results['size']  = __( 'The directory size calculation has timed out. Usually caused by a very large number of sub-directories and files.' );
1612				$results['debug'] = 'timeout while calculating size';
1613
1614				// Stop total size calculation.
1615				$size_total = null;
1616			} else {
1617				if ( null !== $size_total ) {
1618					$size_total += $dir_size;
1619				}
1620
1621				$results['raw']   = $dir_size;
1622				$results['size']  = size_format( $dir_size, 2 );
1623				$results['debug'] = $results['size'] . " ({$dir_size} bytes)";
1624			}
1625
1626			$all_sizes[ $name ] = $results;
1627		}
1628
1629		if ( $size_db > 0 ) {
1630			$database_size = size_format( $size_db, 2 );
1631
1632			$all_sizes['database_size'] = array(
1633				'raw'   => $size_db,
1634				'size'  => $database_size,
1635				'debug' => $database_size . " ({$size_db} bytes)",
1636			);
1637		} else {
1638			$all_sizes['database_size'] = array(
1639				'size'  => __( 'Not available' ),
1640				'debug' => 'not available',
1641			);
1642		}
1643
1644		if ( null !== $size_total && $size_db > 0 ) {
1645			$total_size    = $size_total + $size_db;
1646			$total_size_mb = size_format( $total_size, 2 );
1647
1648			$all_sizes['total_size'] = array(
1649				'raw'   => $total_size,
1650				'size'  => $total_size_mb,
1651				'debug' => $total_size_mb . " ({$total_size} bytes)",
1652			);
1653		} else {
1654			$all_sizes['total_size'] = array(
1655				'size'  => __( 'Total size is not available. Some errors were encountered when determining the size of your installation.' ),
1656				'debug' => 'not available',
1657			);
1658		}
1659
1660		return $all_sizes;
1661	}
1662}
1663