Skip to content
3 changes: 2 additions & 1 deletion features/check-file-type.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Feature: Check the type of file

@skip-object-cache @skip-windows
Scenario: Check that object-cache.php isn't a symlink
Given a WP install
And a config.yml file:
Expand Down Expand Up @@ -32,7 +33,7 @@ Feature: Check the type of file
"""
And the return code should be 1


@skip-object-cache @skip-windows
Scenario: Check that object-cache.php is a symlink
Given a WP install
And a config.yml file:
Expand Down
10 changes: 5 additions & 5 deletions features/check-network-site-option-value.feature
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Feature: Check the value of a network option
option: registration
value: all
"""
And I run `wp eval 'update_site_option( "registration", "none" );'`
And I run `wp eval "update_site_option( 'registration', 'none' );"`

When I try `wp doctor check network-registration --config=config.yml`
Then STDOUT should be a table containing rows:
Expand All @@ -38,7 +38,7 @@ Feature: Check the value of a network option
"""
And the return code should be 1

When I run `wp eval 'update_site_option( "registration", "all" );'`
When I run `wp eval "update_site_option( 'registration', 'all' );"`
Then STDOUT should be empty

When I run `wp doctor check network-registration --config=config.yml`
Expand All @@ -56,7 +56,7 @@ Feature: Check the value of a network option
option: registration
value_is_not: none
"""
And I run `wp eval 'update_site_option( "registration", "none" );'`
And I run `wp eval "update_site_option( 'registration', 'none' );"`

When I try `wp doctor check network-registration --config=config.yml`
Then STDOUT should be a table containing rows:
Expand All @@ -68,7 +68,7 @@ Feature: Check the value of a network option
"""
And the return code should be 1

When I run `wp eval 'update_site_option( "registration", "all" );'`
When I run `wp eval "update_site_option( 'registration', 'all' );"`
Then STDOUT should be empty

When I run `wp doctor check network-registration --config=config.yml`
Expand All @@ -86,7 +86,7 @@ Feature: Check the value of a network option
option: registration
value: none
"""
And I run `wp eval 'update_site_option( "registration", array( "users" => "all" ) );'`
And I run `wp eval "update_site_option( 'registration', array( 'users' => 'all' ) );"`

When I try `wp doctor check network-registration --config=config.yml`
Then STDOUT should contain:
Expand Down
4 changes: 2 additions & 2 deletions features/check.feature
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ Feature: Basic check usage
Then STDOUT should be:
"""
name,status
cache-flush,warning
option-blog-public,error
php-in-upload,success
cache-flush,warning
"""
And the return code should be 1

Expand All @@ -104,8 +104,8 @@ Feature: Basic check usage
Then STDOUT should be:
"""
name,status
cache-flush,warning
option-blog-public,error
cache-flush,warning
"""
And the return code should be 1

Expand Down
14 changes: 14 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
parameters:
level: 9
paths:
- src
- doctor-command.php
scanDirectories:
- vendor/wp-cli/wp-cli/php
scanFiles:
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
- tests/phpstan/scan-files.php
treatPhpDocTypesAsCertain: false
ignoreErrors:
- '#Call to deprecated method setAccessible\(\) of class ReflectionProperty\.#'

11 changes: 10 additions & 1 deletion src/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ abstract class Check {

/**
* Initialize the check.
*
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {

Expand All @@ -48,6 +50,8 @@ public function __construct( $options = array() ) {

/**
* Get when the check is expected to run.
*
* @return string
*/
public function get_when() {
return $this->_when;
Expand All @@ -57,6 +61,7 @@ public function get_when() {
* Set when the check is expected to run.
*
* @param string $when
* @return void
*/
public function set_when( $when ) {
$this->_when = $when;
Expand All @@ -66,6 +71,7 @@ public function set_when( $when ) {
* Set the status of the check.
*
* @param string $status
* @return void
*/
protected function set_status( $status ) {
$this->_status = $status;
Expand All @@ -75,6 +81,7 @@ protected function set_status( $status ) {
* Set the message of the check.
*
* @param string $message
* @return void
*/
protected function set_message( $message ) {
$this->_message = $message;
Expand All @@ -85,13 +92,15 @@ protected function set_message( $message ) {
*
* Because each check checks for something different, this method must be
* subclassed. Method is expected to set $status_code and $status_message.
*
* @return void
*/
abstract public function run();

/**
* Get results of the check.
*
* @return array
* @return array<string, string>
*/
public function get_results() {
return array(
Expand Down
10 changes: 9 additions & 1 deletion src/Check/Autoload_Options_Size.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class Autoload_Options_Size extends Check {
*/
protected $threshold_kb = 900;

/**
* @return void
*/
public function run() {
ob_start();
WP_CLI::run_command(
Expand All @@ -40,9 +43,14 @@ public function run() {
}
}

/**
* @param int|float $size Size in bytes.
* @param int $precision Precision.
* @return string
*/
private static function format_bytes( $size, $precision = 2 ) {
$base = log( $size, 1024 );
$suffixes = array( '', 'kb', 'mb', 'g', 't' );
return round( pow( 1024, $base - floor( $base ) ), $precision ) . $suffixes[ floor( $base ) ];
return round( pow( 1024, $base - floor( $base ) ), $precision ) . $suffixes[ (int) floor( $base ) ];
}
}
10 changes: 9 additions & 1 deletion src/Check/Cache_Flush.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
*/
class Cache_Flush extends File_Contents {

/**
* @return void
*/
public function run() {

// Path to wp-content directory.
Expand All @@ -22,6 +25,9 @@ public function run() {
$this->regex = 'wp_cache_flush\(\)';

foreach ( $iterator as $file ) {
if ( ! $file instanceof \SplFileInfo ) {
continue;
}
$this->check_file( $file );
}

Expand All @@ -34,7 +40,9 @@ public function run() {
// Show relative paths in output.
$relative_paths = array_map(
function ( $file ) use ( $wp_content_dir ) {
return str_replace( $wp_content_dir . '/', '', $file );
$normalized_file = \WP_CLI\Path::normalize( (string) $file );
$normalized_dir = \WP_CLI\Path::normalize( $wp_content_dir );
return str_replace( rtrim( $normalized_dir, '/' ) . '/', '', $normalized_file );
},
$this->_matches
);
Expand Down
26 changes: 20 additions & 6 deletions src/Check/Constant_Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class Constant_Definition extends Check {
/**
* Whether or not the constant is expected to be a falsy value.
*
* @var bool
* @var bool|null
*/
protected $falsy;
protected $falsy = null;

/**
* Expected value of the constant.
Expand All @@ -39,6 +39,8 @@ class Constant_Definition extends Check {

/**
* Initialize the constant check
*
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );
Expand All @@ -47,6 +49,9 @@ public function __construct( $options = array() ) {
}
}

/**
* @return void
*/
public function run() {

if ( isset( $this->falsy ) ) {
Expand Down Expand Up @@ -99,7 +104,7 @@ public function run() {
return;
}

if ( $this->defined && ! isset( $this->value ) ) {
if ( ! isset( $this->value ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Removing $this->defined from this condition changes the behavior of the check. Previously, this block only executed if the configuration explicitly requested a definition check (e.g., defined: true in YAML). Now, it will execute for any constant check where a value is not specified, potentially leading to incorrect success reports if the constant is not actually defined. If PHPStan is flagging $this->defined as an undefined property, it should be explicitly declared in the class rather than removed from the logic.

if ( $this->defined && ! isset( $this->value ) ) {

$this->set_status( 'success' );
$this->set_message( "Constant '{$this->constant}' is defined." );
return;
Expand All @@ -118,12 +123,21 @@ public function run() {
}
}

/**
* @param mixed $value
* @return string
*/
private static function human_value( $value ) {
if ( true === $value ) {
$value = 'true';
return 'true';
} elseif ( false === $value ) {
$value = 'false';
return 'false';
} elseif ( is_null( $value ) ) {
return 'null';
}
if ( is_scalar( $value ) ) {
return (string) $value;
}
return $value;
return gettype( $value );
}
}
10 changes: 8 additions & 2 deletions src/Check/Core_Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ class Core_Update extends Check {
public function run() {
ob_start();
WP_CLI::run_command( array( 'core', 'check-update' ), array( 'format' => 'json' ) );
$ret = ob_get_clean();
$updates = ! empty( $ret ) ? json_decode( $ret, true ) : array();
$ret = ob_get_clean();
$updates = ! empty( $ret ) ? json_decode( $ret, true ) : array();
if ( ! is_array( $updates ) ) {
$updates = array();
}
$has_minor = false;
$has_major = false;
foreach ( $updates as $update ) {
if ( ! is_array( $update ) || ! isset( $update['update_type'] ) ) {
continue;
}
switch ( $update['update_type'] ) {
case 'minor':
$has_minor = true;
Expand Down
6 changes: 6 additions & 0 deletions src/Check/Core_Verify_Checksums.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
*/
class Core_Verify_Checksums extends Check {

/**
* @param array<string, mixed> $options
*/
public function __construct( $options = array() ) {
parent::__construct( $options );
$this->set_when( 'before_wp_load' );
}

/**
* @return void
*/
public function run() {
$return_code = WP_CLI::runcommand(
'core verify-checksums',
Expand Down
15 changes: 13 additions & 2 deletions src/Check/Cron.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

abstract class Cron extends Check {

/**
* @var array<array<string, mixed>>|null
*/
protected static $crons;

/**
* @return array<array<string, mixed>>
*/
protected static function get_crons() {

if ( isset( self::$crons ) ) {
Expand All @@ -23,8 +29,13 @@ protected static function get_crons() {
'fields' => 'hook,args',
)
);
$ret = ob_get_clean();
self::$crons = ! empty( $ret ) ? json_decode( $ret, true ) : array();
$ret = ob_get_clean();
$decoded = ! empty( $ret ) ? json_decode( $ret, true ) : array();
if ( ! is_array( $decoded ) ) {
$decoded = array();
}
/** @var array<array<string, mixed>> $decoded */
self::$crons = $decoded;
return self::$crons;
}
}
3 changes: 3 additions & 0 deletions src/Check/Cron_Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Cron_Count extends Cron {
*/
protected $threshold_count = 50;

/**
* @return void
*/
public function run() {
$crons = self::get_crons();
if ( count( $crons ) >= $this->threshold_count ) {
Expand Down
6 changes: 6 additions & 0 deletions src/Check/Cron_Duplicates.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ class Cron_Duplicates extends Cron {
*/
protected $threshold_count = 10;

/**
* @return void
*/
public function run() {
$crons = self::get_crons();
$job_counts = array();
$excess_duplicates = false;
foreach ( $crons as $job ) {
if ( ! isset( $job['hook'] ) ) {
continue;
}
$key_data = array( $job['hook'], isset( $job['args'] ) ? $job['args'] : array() );
if ( function_exists( 'wp_json_encode' ) ) {
$key = wp_json_encode( $key_data );
Expand Down
Loading