HEX
Server: Apache
System: Linux pdx1-shared-a1-38 6.6.104-grsec-jammy+ #3 SMP Tue Sep 16 00:28:11 UTC 2025 x86_64
User: mmickelson (3396398)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: //usr/local/wp/vendor/wp-cli/doctor-command/src/Command.php
<?php

namespace WP_CLI\Doctor;

use Exception;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use WP_CLI;
use WP_CLI\Formatter;
use WP_CLI\Utils;

/**
 * Diagnose what ails WordPress.
 *
 * ## EXAMPLES
 *
 *     # Verify WordPress core is up to date.
 *     $ wp doctor check core-update
 *     +-------------+---------+-----------------------------------------------------------+
 *     | name        | status  | message                                                   |
 *     +-------------+---------+-----------------------------------------------------------+
 *     | core-update | warning | A new major version of WordPress is available for update. |
 *     +-------------+---------+-----------------------------------------------------------+
 *
 *     # List checks to run.
 *     $ wp doctor list
 *     +----------------------------+--------------------------------------------------------------------------------+
 *     | name                       | description                                                                    |
 *     +----------------------------+--------------------------------------------------------------------------------+
 *     | autoload-options-size      | Warns when autoloaded options size exceeds threshold of 900 kb.                |
 *     | constant-savequeries-falsy | Confirms expected state of the SAVEQUERIES constant.                           |
 *     | constant-wp-debug-falsy    | Confirms expected state of the WP_DEBUG constant.                              |
 *     | core-update                | Errors when new WordPress minor release is available; warns for major release. |
 *
 * @package wp-cli
 */
class Command {

	/**
	 * Run a series of checks against WordPress to diagnose issues.
	 *
	 * A check is a routine run against some scope of WordPress that reports
	 * a 'status' and a 'message'. The status can be 'success', 'warning', or
	 * 'error'. The message is a human-readable explanation of the status. If
	 * any of the checks fail, then the command will exit with the code `1`.
	 *
	 * ## OPTIONS
	 *
	 * [<checks>...]
	 * : Names of one or more checks to run.
	 *
	 * [--all]
	 * : Run all registered checks.
	 *
	 * [--spotlight]
	 * : Focus on warnings and errors; ignore any successful checks.
	 *
	 * [--config=<file>]
	 * : Use checks registered in a specific configuration file.
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific fields.
	 *
	 * [--<field>=<value>]
	 * : Filter results by key=value pairs.
	 *
	 * [--format=<format>]
	 * : Render results in a particular format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - json
	 *   - csv
	 *   - yaml
	 *   - count
	 * ---
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields will be displayed by default for each check:
	 *
	 * * name
	 * * status
	 * * message
	 *
	 * ## EXAMPLES
	 *
	 *     # Verify WordPress core is up to date.
	 *     $ wp doctor check core-update
	 *     +-------------+---------+-----------------------------------------------------------+
	 *     | name        | status  | message                                                   |
	 *     +-------------+---------+-----------------------------------------------------------+
	 *     | core-update | warning | A new major version of WordPress is available for update. |
	 *     +-------------+---------+-----------------------------------------------------------+
	 *
	 *     # Verify the site is public as expected.
	 *     $ wp doctor check option-blog-public
	 *     +--------------------+--------+--------------------------------------------+
	 *     | name               | status | message                                    |
	 *     +--------------------+--------+--------------------------------------------+
	 *     | option-blog-public | error  | Site is private but expected to be public. |
	 *     +--------------------+--------+--------------------------------------------+
	 *
	 * @when before_wp_load
	 */
	public function check( $args, $assoc_args ) {

		$config = Utils\get_flag_value( $assoc_args, 'config', self::get_default_config() );
		Checks::register_config( $config );

		$all = Utils\get_flag_value( $assoc_args, 'all' );
		if ( empty( $args ) && ! $all ) {
			WP_CLI::error( 'Please specify one or more checks, or use --all.' );
		}

		$completed = array();
		$checks    = Checks::get_checks( array( 'name' => $args ) );
		if ( empty( $checks ) ) {
			if ( $args ) {
				WP_CLI::error( count( $args ) > 1 ? 'Invalid checks.' : 'Invalid check.' );
			} else {
				WP_CLI::error( 'No checks registered.' );
			}
		}
		$file_checks = array();
		$progress    = false;
		if ( $all && 'table' === $assoc_args['format'] ) {
			$progress = Utils\make_progress_bar( 'Running checks', count( $checks ) );
		}
		foreach ( $checks as $name => $check ) {
			$when = $check->get_when();
			if ( $when ) {
				WP_CLI::add_hook(
					$when,
					static function () use ( $name, $check, &$completed, &$progress ) {
						$check->run();
						$completed[ $name ] = $check;
						if ( $progress ) {
							$progress->tick();
						}
					}
				);
			} else {
				$file_check = 'WP_CLI\Doctor\Check\File';
				if ( is_a( $check, $file_check ) || is_subclass_of( $check, $file_check ) ) {
					$file_checks[ $name ] = $check;
				}
			}
		}
		if ( ! empty( $file_checks ) ) {
			WP_CLI::add_hook(
				'after_wp_config_load',
				static function () use ( $file_checks, &$completed, &$progress ) {
					try {
						$directory      = new RecursiveDirectoryIterator( ABSPATH, RecursiveDirectoryIterator::SKIP_DOTS );
						$iterator       = new RecursiveIteratorIterator( $directory, RecursiveIteratorIterator::CHILD_FIRST );
						$wp_content_dir = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR : ABSPATH . 'wp-content';
						foreach ( $iterator as $file ) {
							foreach ( $file_checks as $name => $check ) {
								$options = $check->get_options();
								if ( ! empty( $options['only_wp_content'] )
								&& 0 !== stripos( $file->getPath(), $wp_content_dir ) ) {
									continue;
								}
								if ( ! empty( $options['path'] )
								&& 0 !== stripos( $file->getPathname(), ABSPATH . $options['path'] ) ) {
									continue;
								}
								$extension = explode( '|', $options['extension'] );
								if ( ! in_array( $file->getExtension(), $extension, true ) ) {
									continue;
								}
								$check->check_file( $file );
							}
						}
					} catch ( Exception $e ) {
						WP_CLI::warning( $e->getMessage() );
					}
					foreach ( $file_checks as $name => $check ) {
						$check->run();
						$completed[ $name ] = $check;
						if ( $progress ) {
							$progress->tick();
						}
					}
				}
			);
		}

		if ( ! isset( WP_CLI::get_runner()->config['url'] ) ) {
			WP_CLI::add_wp_hook(
				'muplugins_loaded',
				static function () {
					WP_CLI::set_url( home_url( '/' ) );
				}
			);
		}

		try {
			$this->load_wordpress_with_template();
		} catch ( Exception $e ) {
			WP_CLI::warning( $e->getMessage() );
		}

		$results = array();
		foreach ( $completed as $name => $check ) {
			$results[] = array_merge( $check->get_results(), array( 'name' => $name ) );
		}

		if ( $progress ) {
			$progress->finish();
		}

		// @todo warn if a check provides invalid status

		if ( Utils\get_flag_value( $assoc_args, 'spotlight' ) ) {
			$check_count = count( $results );
			$results     = array_filter(
				$results,
				function ( $check ) {
					return in_array( $check['status'], array( 'warning', 'error' ), true );
				}
			);
			if ( empty( $results ) && 'table' === $assoc_args['format'] ) {
				if ( 1 === $check_count ) {
					$message = "The check reports 'success'.";
				} else {
					$message = "All {$check_count} checks report 'success'.";
				}
				WP_CLI::success( $message );
				return;
			}
		}

		$results_with_error = array_filter(
			$results,
			function ( $check ) {
				return 'error' === $check['status'];
			}
		);
		$should_error       = ! empty( $results_with_error );
		if ( $should_error && 'table' === $assoc_args['format'] ) {
			$check_count   = count( $results_with_error );
			$error_message = 1 === $check_count ? "1 check reports 'error'." : sprintf( "%d checks report 'error'.", $check_count );
		} else {
			$error_message = null;
		}

		$default_fields = array( 'name', 'status', 'message' );

		foreach ( $results as $key => $item ) {
			foreach ( $default_fields as $field ) {
				if ( ! empty( $assoc_args[ $field ] ) && $item[ $field ] !== $assoc_args[ $field ] ) {
					unset( $results[ $key ] );
					break;
				}
			}
		}

		$formatter = new Formatter( $assoc_args, $default_fields );

		$formatter->display_items( $results );

		if ( $should_error ) {
			if ( $error_message ) {
				WP_CLI::error( $error_message );
			} else {
				WP_CLI::halt( 1 );
			}
		}
	}

	/**
	 * List all available checks to run.
	 *
	 * ## OPTIONS
	 *
	 * [--config=<file>]
	 * : Use checks registered in a specific configuration file.
	 *
	 * [--fields=<fields>]
	 * : Limit the output to specific fields.
	 *
	 * [--format=<format>]
	 * : Render output in a specific format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - json
	 *   - csv
	 *   - count
	 * ---
	 *
	 * ## AVAILABLE FIELDS
	 *
	 * These fields will be displayed by default for each check:
	 *
	 * * name
	 * * description
	 *
	 * ## EXAMPLES
	 *
	 *     # List checks to run.
	 *     $ wp doctor list
	 *     +----------------------------+--------------------------------------------------------------------------------+
	 *     | name                       | description                                                                    |
	 *     +----------------------------+--------------------------------------------------------------------------------+
	 *     | autoload-options-size      | Warns when autoloaded options size exceeds threshold of 900 kb.                |
	 *     | constant-savequeries-falsy | Confirms expected state of the SAVEQUERIES constant.                           |
	 *     | constant-wp-debug-falsy    | Confirms expected state of the WP_DEBUG constant.                              |
	 *     | core-update                | Errors when new WordPress minor release is available; warns for major release. |
	 *
	 * @when before_wp_load
	 * @subcommand list
	 */
	public function list_( $args, $assoc_args ) {

		$assoc_args = array_merge(
			array(
				'fields' => 'name,description',
			),
			$assoc_args
		);

		$config = Utils\get_flag_value( $assoc_args, 'config', self::get_default_config() );
		Checks::register_config( $config );

		$items = array();
		foreach ( Checks::get_checks() as $check_name => $class ) {
			$reflection  = new \ReflectionClass( $class );
			$description = self::remove_decorations( $reflection->getDocComment() );
			$tokens      = array();
			foreach ( $reflection->getProperties() as $prop ) {
				$prop_name = $prop->getName();
				if ( '_' === $prop_name[0] ) {
					continue;
				}
				$prop->setAccessible( true );
				$value = $prop->getValue( $class );
				if ( is_array( $value ) ) {
					$value = json_encode( $value );
				}
				$tokens[ '%' . $prop_name . '%' ] = $value;
			}
			if ( ! empty( $tokens ) ) {
				$description = str_replace( array_keys( $tokens ), array_values( $tokens ), $description );
			}

			$items[] = array(
				'name'        => $check_name,
				'description' => $description,
			);
		}
		Utils\format_items( $assoc_args['format'], $items, explode( ',', $assoc_args['fields'] ) );
	}

	/**
	 * Runs through the entirety of the WP bootstrap process
	 */
	private function load_wordpress_with_template() {
		global $wp_query;

		WP_CLI::add_wp_hook(
			'wp_redirect',
			function ( $to ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
				ob_start();
				debug_print_backtrace();
				$message = ob_get_clean();
				throw new Exception( 'Incomplete check execution. Some code is trying to do a URL redirect. Backtrace:' . PHP_EOL . $message );
			},
			1
		);

		WP_CLI::get_runner()->load_wordpress();

		// Set up the main WordPress query.
		wp();

		$interpreted = array();
		foreach ( $wp_query as $key => $value ) {
			if ( 0 === stripos( $key, 'is_' ) && $value ) {
				$interpreted[] = $key;
			}
		}
		WP_CLI::debug( 'Main WP_Query: ' . implode( ', ', $interpreted ), 'doctor' );

		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress Core constant.
		define( 'WP_USE_THEMES', true );

		add_filter(
			'template_include',
			static function ( $template ) {
				$display_template = str_replace( dirname( get_template_directory() ) . '/', '', $template );
				WP_CLI::debug( "Theme template: {$display_template}", 'doctor' );
				return $template;
			},
			999
		);

		// Template is normally loaded in global scope, so we need to replicate
		foreach ( $GLOBALS as $key => $value ) {
			global ${$key}; // phpcs:ignore PHPCompatibility.Variables.ForbiddenGlobalVariableVariable.NonBareVariableFound -- Syntax is updated to compatible with php 5 and 7.
		}

		// Load the theme template.
		ob_start();
		require_once ABSPATH . WPINC . '/template-loader.php';
		ob_get_clean();
	}

	/**
	 * Remove unused cruft from PHPdoc comment.
	 *
	 * @param string $comment PHPdoc comment.
	 * @return string
	 */
	private static function remove_decorations( $comment ) {
		$comment = preg_replace( '|^/\*\*[\r\n]+|', '', $comment );
		$comment = preg_replace( '|\n[\t ]*\*/$|', '', $comment );
		$comment = preg_replace( '|^[\t ]*\* ?|m', '', $comment );
		return $comment;
	}

	/**
	 * Get the path to the default config file
	 */
	private static function get_default_config() {
		return dirname( __DIR__ ) . '/doctor.yml';
	}
}