Merge pull request #453 from wp-cli/add/phpstan

This commit is contained in:
Pascal Birchler 2025-08-15 17:59:55 +02:00 committed by GitHub
commit fa25c5525a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 275 additions and 89 deletions

View file

@ -18,14 +18,14 @@
],
"require": {
"composer/semver": "^1.4 || ^2 || ^3",
"wp-cli/wp-cli": "^2.12"
"wp-cli/wp-cli": "^2.13"
},
"require-dev": {
"wp-cli/cache-command": "^2.0",
"wp-cli/entity-command": "^1.3 || ^2",
"wp-cli/language-command": "^2.0",
"wp-cli/scaffold-command": "^1.2 || ^2",
"wp-cli/wp-cli-tests": "^5.0.1"
"wp-cli/wp-cli-tests": "^5"
},
"config": {
"process-timeout": 7200,

15
phpstan.neon.dist Normal file
View file

@ -0,0 +1,15 @@
parameters:
level: 9
paths:
- src
- extension-command.php
scanDirectories:
- vendor/wp-cli/wp-cli/php
scanFiles:
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
treatPhpDocTypesAsCertain: false
ignoreErrors:
- identifier: missingType.iterableValue
- identifier: missingType.property
- identifier: missingType.parameter
- identifier: missingType.return

View file

@ -72,6 +72,9 @@ class Plugin_AutoUpdates_Command {
* $ wp plugin auto-updates enable hello
* Plugin auto-updates for 'hello' enabled.
* Success: Enabled 1 of 1 plugin auto-updates.
*
* @param string[] $args Positional arguments.
* @param array{all?: bool, 'disabled-only'?: bool} $assoc_args Associative arguments.
*/
public function enable( $args, $assoc_args ) {
$all = Utils\get_flag_value( $assoc_args, 'all', false );

View file

@ -1,5 +1,6 @@
<?php
use WP_CLI\CommandWithUpgrade;
use WP_CLI\ParsePluginNameInput;
use WP_CLI\Utils;
use WP_CLI\WpOrgApi;
@ -41,9 +42,11 @@ use function WP_CLI\Utils\normalize_path;
* Success: Installed 1 of 1 plugins.
*
* @package wp-cli
*
* @phpstan-type PluginInformation object{name: string, slug: non-empty-string, version: string, new_version: string, download_link: string, requires_php?: string, requires?: string, package: string}&\stdClass
* @extends CommandWithUpgrade<string,>
*/
class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
class Plugin_Command extends CommandWithUpgrade {
use ParsePluginNameInput;
protected $item_type = 'plugin';
@ -66,13 +69,6 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
'auto_update',
);
/**
* Plugin fetcher instance.
*
* @var \WP_CLI\Fetchers\Plugin
*/
protected $fetcher;
public function __construct() {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
@ -209,6 +205,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
}
protected function status_single( $args ) {
/**
* @var object{name: string, file: string} $plugin
*/
$plugin = $this->fetcher->get_check( $args[0] );
$file = $plugin->file;
@ -345,11 +344,18 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* Plugin 'bbpress' network activated.
* Plugin 'buddypress' network activated.
* Success: Activated 2 of 2 plugins.
*
* @param array $args
* @param array $assoc_args
*/
public function activate( $args, $assoc_args = array() ) {
public function activate( $args, $assoc_args = [] ) {
$network_wide = Utils\get_flag_value( $assoc_args, 'network', false );
$all = Utils\get_flag_value( $assoc_args, 'all', false );
$all_exclude = Utils\get_flag_value( $assoc_args, 'exclude' );
$all_exclude = Utils\get_flag_value( $assoc_args, 'exclude', '' );
/**
* @var string $all_exclude
*/
$args = $this->check_optional_args_and_all( $args, $all, 'activate', $all_exclude );
if ( ! $args ) {
@ -358,7 +364,11 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
$successes = 0;
$errors = 0;
$plugins = $this->fetcher->get_many( $args );
/**
* @var array<object{name: string, file: string}> $plugins
*/
$plugins = $this->fetcher->get_many( $args );
if ( count( $plugins ) < count( $args ) ) {
$errors = count( $args ) - count( $plugins );
}
@ -387,7 +397,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
if ( is_wp_error( $result ) ) {
$message = $result->get_error_message();
$message = preg_replace( '/<a\s[^>]+>.*<\/a>/im', '', $message );
$message = (string) preg_replace( '/<a\s[^>]+>.*<\/a>/im', '', $message );
$message = wp_strip_all_tags( $message );
$message = str_replace( 'Error: ', '', $message );
WP_CLI::warning( "Failed to activate plugin. {$message}" );
@ -437,10 +447,14 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* Plugin 'ninja-forms' deactivated.
* Success: Deactivated 2 of 2 plugins.
*/
public function deactivate( $args, $assoc_args = array() ) {
public function deactivate( $args, $assoc_args = [] ) {
$network_wide = Utils\get_flag_value( $assoc_args, 'network' );
$disable_all = Utils\get_flag_value( $assoc_args, 'all' );
$disable_all_exclude = Utils\get_flag_value( $assoc_args, 'exclude' );
$disable_all_exclude = Utils\get_flag_value( $assoc_args, 'exclude', '' );
/**
* @var string $disable_all_exclude
*/
$args = $this->check_optional_args_and_all( $args, $disable_all, 'deactivate', $disable_all_exclude );
if ( ! $args ) {
@ -530,7 +544,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* Plugin 'akismet' activated.
* Success: Toggled 1 of 1 plugins.
*/
public function toggle( $args, $assoc_args = array() ) {
public function toggle( $args, $assoc_args ) {
$network_wide = Utils\get_flag_value( $assoc_args, 'network' );
$successes = 0;
@ -574,6 +588,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
$path = untrailingslashit( WP_PLUGIN_DIR );
if ( ! empty( $args ) ) {
/**
* @var object{name: string, file: string} $plugin
*/
$plugin = $this->fetcher->get_check( $args[0] );
$path .= '/' . $plugin->file;
@ -591,6 +608,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
list($wp_core_version) = explode( '-', $wp_version );
$wp_core_version = implode( '.', array_slice( explode( '.', $wp_core_version ), 0, 2 ) );
/**
* @var \WP_Error|PluginInformation $api
*/
$api = plugins_api( 'plugin_information', array( 'slug' => $slug ) );
if ( is_wp_error( $api ) ) {
@ -755,6 +775,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
$auto_updates = [];
}
/**
* @var string[] $recently_active
*/
$recently_active = is_network_admin() ? get_site_option( 'recently_activated' ) : get_option( 'recently_activated' );
if ( false === $recently_active ) {
@ -763,10 +786,11 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
foreach ( $this->get_all_plugins() as $file => $details ) {
$all_update_info = $this->get_update_info();
$update_info = ( isset( $all_update_info->response[ $file ] ) && null !== $all_update_info->response[ $file ] ) ? (array) $all_update_info->response[ $file ] : null;
$name = Utils\get_plugin_name( $file );
$wporg_info = $this->get_wporg_data( $name );
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false );
// @phpstan-ignore notIdentical.alwaysTrue
$update_info = ( isset( $all_update_info->response[ $file ] ) && null !== $all_update_info->response[ $file ] ) ? (array) $all_update_info->response[ $file ] : null;
$name = Utils\get_plugin_name( $file );
$wporg_info = $this->get_wporg_data( $name );
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $file, false, false );
if ( ! isset( $duplicate_names[ $name ] ) ) {
$duplicate_names[ $name ] = array();
@ -875,7 +899,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
if ( isset( $plugin_update_info->requires ) && version_compare( $wp_version, $requires, '>=' ) ) {
$reason = "This update requires WordPress version $plugin_update_info->requires, but the version installed is $wp_version.";
} elseif ( ! isset( $update_info['package'] ) ) {
} elseif ( ! isset( $plugin_update_info->package ) ) {
$reason = 'Update file not provided. Contact author for more details';
} else {
$reason = 'Update not available';
@ -904,7 +928,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
*
* @param string $plugin_name The plugin slug.
*
* @return string The status of the plugin, includes the last update date.
* @return array{status: string, last_updated: string|false, status?: string, last_updated?: string} The status of the plugin, includes the last update date.
*/
protected function get_wporg_data( $plugin_name ) {
$data = [
@ -947,10 +971,12 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
$r_body = wp_remote_retrieve_body( $request );
if ( strpos( $r_body, 'pubDate' ) !== false ) {
// Very raw check, not validating the format or anything else.
$xml = simplexml_load_string( $r_body );
$xml_pub_date = $xml->xpath( '//pubDate' );
if ( $xml_pub_date ) {
$data['last_updated'] = wp_date( 'Y-m-d', (string) strtotime( $xml_pub_date[0] ) );
$xml = simplexml_load_string( $r_body );
if ( false !== $xml ) {
$xml_pub_date = $xml->xpath( '//pubDate' );
if ( $xml_pub_date ) {
$data['last_updated'] = wp_date( 'Y-m-d', strtotime( $xml_pub_date[0] ) ?: null );
}
}
}
@ -1115,6 +1141,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
'status',
);
/**
* @var object{name: string, file: string} $plugin
*/
$plugin = $this->fetcher->get_check( $args[0] );
$file = $plugin->file;
@ -1173,11 +1202,14 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* Uninstalled and deleted 'tinymce-templates' plugin.
* Success: Uninstalled 2 of 2 plugins.
*/
public function uninstall( $args, $assoc_args = array() ) {
public function uninstall( $args, $assoc_args = [] ) {
$all = Utils\get_flag_value( $assoc_args, 'all', false );
$all_exclude = Utils\get_flag_value( $assoc_args, 'exclude', false );
/**
* @var string $all_exclude
*/
// Check if plugin names or --all is passed.
$args = $this->check_optional_args_and_all( $args, $all, 'uninstall', $all_exclude );
if ( ! $args ) {
@ -1222,6 +1254,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) {
$translations = $plugin_translations[ $plugin_slug ];
/**
* @var \WP_Filesystem_Base $wp_filesystem
*/
global $wp_filesystem;
require_once ABSPATH . '/wp-admin/includes/file.php';
WP_Filesystem();
@ -1233,7 +1268,11 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
$json_translation_files = glob( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '-*.json' );
if ( $json_translation_files ) {
array_map( array( $wp_filesystem, 'delete' ), $json_translation_files );
/**
* @var callable $callback
*/
$callback = [ $wp_filesystem, 'delete' ];
array_map( $callback, $json_translation_files );
}
}
}
@ -1257,6 +1296,10 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
// Remove deleted plugins from the plugin updates list.
$current = get_site_transient( $this->upgrade_transient );
if ( $current ) {
/**
* @var object{response: array<string, mixed>, checked: array<string, mixed>}&\stdClass $current
*/
// Don't remove the plugins that weren't deleted.
$deleted = array_diff( $deleted_plugin_files, $delete_errors );
@ -1292,7 +1335,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
*
* @subcommand is-installed
*/
public function is_installed( $args, $assoc_args = array() ) {
public function is_installed( $args, $assoc_args ) {
if ( $this->fetcher->get( $args[0] ) ) {
WP_CLI::halt( 0 );
} else {
@ -1322,7 +1365,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
*
* @subcommand is-active
*/
public function is_active( $args, $assoc_args = array() ) {
public function is_active( $args, $assoc_args ) {
$network_wide = Utils\get_flag_value( $assoc_args, 'network' );
$plugin = $this->fetcher->get( $args[0] );
@ -1366,10 +1409,14 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* Deleted 'tinymce-templates' plugin.
* Success: Deleted 2 of 2 plugins.
*/
public function delete( $args, $assoc_args = array() ) {
public function delete( $args, $assoc_args ) {
$all = Utils\get_flag_value( $assoc_args, 'all', false );
$all_exclude = Utils\get_flag_value( $assoc_args, 'exclude', false );
/**
* @var string $all_exclude
*/
// Check if plugin names or --all is passed.
$args = $this->check_optional_args_and_all( $args, $all, 'delete', $all_exclude );
if ( ! $args ) {
@ -1505,7 +1552,11 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
* @subcommand list
*/
public function list_( $_, $assoc_args ) {
/**
* @var string $fields
*/
$fields = Utils\get_flag_value( $assoc_args, 'fields' );
if ( ! empty( $fields ) ) {
$fields = explode( ',', $fields );
$this->check_wporg['status'] = in_array( 'wporg_status', $fields, true );
@ -1578,7 +1629,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
/**
* Gets the details of a plugin.
*
* @param object
* @param string $file Plugin file name.
* @return array
*/
private function get_details( $file ) {
@ -1591,8 +1642,8 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade {
/**
* Performs deletion of plugin files
*
* @param $plugin - Plugin fetcher object (name, file)
* @return bool - If plugin was deleted
* @param $plugin Plugin fetcher object (name, file)
* @return bool Whether plugin was deleted
*/
private function delete_plugin( $plugin ) {
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

View file

@ -28,6 +28,9 @@ use WP_CLI\Utils;
*/
class Theme_AutoUpdates_Command {
/**
* @use ParseThemeNameInput<\WP_Theme>
*/
use ParseThemeNameInput;
/**

View file

@ -41,9 +41,15 @@ use WP_CLI\Utils;
* Author: the WordPress team
*
* @package wp-cli
*
* @phpstan-type ThemeInformation object{name: string, slug: non-empty-string, version: string, new_version: string, download_link: string, requires_php?: string, requires?: string}&\stdClass
* @extends CommandWithUpgrade<\WP_Theme>
*/
class Theme_Command extends CommandWithUpgrade {
/**
* @use ParseThemeNameInput<\WP_Theme>
*/
use ParseThemeNameInput;
protected $item_type = 'theme';
@ -211,8 +217,11 @@ class Theme_Command extends CommandWithUpgrade {
*
* $ wp theme activate twentysixteen
* Success: Switched to 'Twenty Sixteen' theme.
*
* @param string[] $args Positional arguments.
* @param array $assoc_args Associative arguments. Unused.
*/
public function activate( $args = array() ) {
public function activate( $args, $assoc_args = [] ) {
$theme = $this->fetcher->get_check( $args[0] );
$errors = $theme->errors();
@ -232,7 +241,7 @@ class Theme_Command extends CommandWithUpgrade {
WP_CLI::error( "The '{$theme->get_stylesheet()}' theme cannot be activated without its parent, '{$theme->get_template()}'." );
}
switch_theme( $theme->get_template(), $theme->get_stylesheet() );
switch_theme( $theme->get_stylesheet() );
if ( $this->is_active_theme( $theme ) ) {
WP_CLI::success( "Switched to '$name' theme." );
@ -279,6 +288,9 @@ class Theme_Command extends CommandWithUpgrade {
WP_CLI::error( 'This is not a multisite installation.' );
}
/**
* @var \WP_Theme $theme
*/
$theme = $this->fetcher->get_check( $args[0] );
$name = $theme->get( 'Name' );
@ -290,6 +302,11 @@ class Theme_Command extends CommandWithUpgrade {
if ( empty( $allowed_themes ) ) {
$allowed_themes = array();
}
/**
* @var array<string, bool> $allowed_themes
*/
$allowed_themes[ $theme->get_stylesheet() ] = true;
call_user_func( "update{$_site}_option", 'allowedthemes', $allowed_themes );
@ -344,6 +361,11 @@ class Theme_Command extends CommandWithUpgrade {
# Add the current theme to the allowed themes option or site option
$allowed_themes = call_user_func( "get{$_site}_option", 'allowedthemes' );
/**
* @var array<string, bool> $allowed_themes
*/
if ( ! empty( $allowed_themes[ $theme->get_stylesheet() ] ) ) {
unset( $allowed_themes[ $theme->get_stylesheet() ] );
}
@ -400,6 +422,9 @@ class Theme_Command extends CommandWithUpgrade {
list($wp_core_version) = explode( '-', $wp_version );
$wp_core_version = implode( '.', array_slice( explode( '.', $wp_core_version ), 0, 2 ) );
/**
* @var \WP_Error|ThemeInformation $api
*/
$api = themes_api( 'theme_information', array( 'slug' => $slug ) );
if ( is_wp_error( $api ) ) {
@ -732,7 +757,7 @@ class Theme_Command extends CommandWithUpgrade {
*
* @subcommand is-installed
*/
public function is_installed( $args, $assoc_args = array() ) {
public function is_installed( $args, $assoc_args ) {
$theme = wp_get_theme( $args[0] );
if ( $theme->exists() ) {
@ -761,7 +786,7 @@ class Theme_Command extends CommandWithUpgrade {
*
* @subcommand is-active
*/
public function is_active( $args, $assoc_args = array() ) {
public function is_active( $args, $assoc_args ) {
$theme = wp_get_theme( $args[0] );
if ( ! $theme->exists() ) {

View file

@ -74,8 +74,11 @@ class Theme_Mod_Command extends WP_CLI_Command {
* | background_color | dd3333 |
* | header_textcolor | |
* +------------------+--------+
*
* @param string[] $args Positional arguments.
* @param array{field?: string, all?: bool, format: string} $assoc_args Associative arguments.
*/
public function get( $args = array(), $assoc_args = array() ) {
public function get( $args, $assoc_args ) {
if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
WP_CLI::error( 'You must specify at least one mod or use --all.' );
@ -162,10 +165,13 @@ class Theme_Mod_Command extends WP_CLI_Command {
* +------------------+---------+
*
* @subcommand list
*
* @param string[] $args Positional arguments. Unused.
* @param array{field?: string, format: string} $assoc_args Associative arguments.
*/
public function list_( $args = array(), $assoc_args = array() ) {
public function list_( $args, $assoc_args ) {
$assoc_args['all'] = 1;
$assoc_args['all'] = true;
$this->get( $args, $assoc_args );
}
@ -194,8 +200,11 @@ class Theme_Mod_Command extends WP_CLI_Command {
* # Remove multiple theme mods.
* $ wp theme mod remove background_color header_textcolor
* Success: 2 mods removed.
*
* @param string[] $args Positional arguments.
* @param array{all?: bool} $assoc_args Associative arguments.
*/
public function remove( $args = array(), $assoc_args = array() ) {
public function remove( $args, $assoc_args ) {
if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
WP_CLI::error( 'You must specify at least one mod or use --all.' );
@ -232,8 +241,11 @@ class Theme_Mod_Command extends WP_CLI_Command {
* # Set theme mod
* $ wp theme mod set background_color 000000
* Success: Theme mod background_color set to 000000.
*
* @param array{0: string, 1: string} $args Positional arguments.
* @param array $assoc_args Associative arguments. Unused.
*/
public function set( $args = array(), $assoc_args = array() ) {
public function set( $args, $assoc_args ) {
list( $mod, $value ) = $args;
set_theme_mod( $mod, $value );

View file

@ -11,6 +11,12 @@ use WP_CLI\Loggers;
use WP_CLI\Utils;
use WP_Error;
/**
* @phpstan-import-type ThemeInformation from \Theme_Command
* @phpstan-import-type PluginInformation from \Plugin_Command
*
* @template T
*/
abstract class CommandWithUpgrade extends \WP_CLI_Command {
protected $fetcher;
@ -61,25 +67,43 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
$this->fetcher = new Fetchers\Plugin();
}
/**
* @return class-string<\WP_Upgrader>
*/
abstract protected function get_upgrader_class( $force );
abstract protected function get_item_list();
/**
* @param array List of update candidates
* @param array List of item names
* @param array $items List of update candidates
* @param array $args List of item names
* @return array List of update candidates
*/
abstract protected function filter_item_list( $items, $args );
abstract protected function get_all_items();
/**
* Get the status for a given extension.
*
* @param T $file Extension to get the status for.
*
* @return string Status of the extension.
*/
abstract protected function get_status( $file );
abstract protected function status_single( $args );
abstract protected function install_from_repo( $slug, $assoc_args );
/**
* Activates an extension.
*
* @param string[] $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
abstract public function activate( $args, $assoc_args = [] );
public function status( $args ) {
// Force WordPress to check for updates.
call_user_func( $this->upgrade_refresh );
@ -197,13 +221,16 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
$filter = false;
// If a GitHub URL, do some guessing as to the correct plugin/theme directory.
if ( $is_remote && 'github.com' === $this->parse_url_host_component( $slug, PHP_URL_HOST )
if ( $is_remote && 'github.com' === Utils\parse_url( $slug, PHP_URL_HOST )
// Don't attempt to rename ZIPs uploaded to the releases page or coming from a raw source.
&& ! preg_match( '#github\.com/[^/]+/[^/]+/(?:releases/download|raw)/#', $slug ) ) {
$filter = function ( $source ) use ( $slug ) {
$slug_dir = Utils\basename( $this->parse_url_host_component( $slug, PHP_URL_PATH ), '.zip' );
/**
* @var string $path
*/
$path = Utils\parse_url( $slug, PHP_URL_PATH );
$slug_dir = Utils\basename( $path, '.zip' );
// Don't use the zip name if archive attached to release, as name likely to contain version tag/branch.
if ( preg_match( '#github\.com/[^/]+/([^/]+)/archive/#', $slug, $matches ) ) {
@ -215,7 +242,7 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
if ( $source_dir === $slug_dir ) {
return $source;
}
$new_path = substr_replace( $source, $slug_dir, strrpos( $source, $source_dir ), strlen( $source_dir ) );
$new_path = substr_replace( $source, $slug_dir, (int) strrpos( $source, $source_dir ), strlen( $source_dir ) );
if ( $GLOBALS['wp_filesystem']->move( $source, $new_path ) ) {
WP_CLI::log( sprintf( "Renamed Github-based project from '%s' to '%s'.", $source_dir, $slug_dir ) );
@ -294,8 +321,10 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
/**
* Prepare an API response for downloading a particular version of an item.
*
* @param object $response wordpress.org API response
* @param string $version The desired version of the package
* @param object $response Wordpress.org API response.
* @param string $version The desired version of the package.
*
* @phpstan-param PluginInformation|ThemeInformation $response
*/
protected static function alter_api_response( $response, $version ) {
if ( $response->version === $version ) {
@ -346,8 +375,8 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
}
protected function get_upgrader( $assoc_args ) {
$force = (bool) Utils\get_flag_value( $assoc_args, 'force', false );
$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$force = Utils\get_flag_value( $assoc_args, 'force', false );
$insecure = Utils\get_flag_value( $assoc_args, 'insecure', false );
$upgrader_class = $this->get_upgrader_class( $force );
return Utils\get_upgrader( $upgrader_class, $insecure );
}
@ -384,19 +413,22 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
}
);
$minor = (bool) Utils\get_flag_value( $assoc_args, 'minor', false );
$patch = (bool) Utils\get_flag_value( $assoc_args, 'patch', false );
$minor = Utils\get_flag_value( $assoc_args, 'minor', false );
$patch = Utils\get_flag_value( $assoc_args, 'patch', false );
if (
in_array( $this->item_type, [ 'plugin', 'theme' ], true ) &&
( $minor || $patch )
) {
$type = $minor ? 'minor' : 'patch';
$insecure = (bool) Utils\get_flag_value( $assoc_args, 'insecure', false );
$insecure = Utils\get_flag_value( $assoc_args, 'insecure', false );
$items_to_update = self::get_minor_or_patch_updates( $items_to_update, $type, $insecure, true, $this->item_type );
}
/**
* @var string|null $exclude
*/
$exclude = Utils\get_flag_value( $assoc_args, 'exclude' );
if ( isset( $exclude ) ) {
$exclude_items = explode( ',', trim( $assoc_args['exclude'], ',' ) );
@ -475,6 +507,9 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
foreach ( $items_to_update as $name => $item_data ) {
if ( isset( $transient->response[ $name ] ) ) {
if ( is_object( $transient->response[ $name ] ) ) {
/**
* @var object{response: array<string, ThemeInformation|PluginInformation>} $transient
*/
$transient->response[ $name ]->new_version = $item_data['update_version'];
$transient->response[ $name ]->package = $item_data['update_package'];
} else {
@ -490,6 +525,10 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
remove_filter( 'site_transient_' . $this->upgrade_transient, $transient_filter, 999 );
}
/**
* @var array $items_to_update
*/
// Let the user know the results.
$num_to_update = count( $items_to_update );
$num_updated = count(
@ -545,14 +584,14 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
protected function _list( $_, $assoc_args ) {
// Force WordPress to check for updates if `--skip-update-check` is not passed.
if ( false === (bool) Utils\get_flag_value( $assoc_args, 'skip-update-check', false ) ) {
if ( false === Utils\get_flag_value( $assoc_args, 'skip-update-check', false ) ) {
delete_site_transient( $this->upgrade_transient );
call_user_func( $this->upgrade_refresh );
}
$all_items = $this->get_all_items();
if ( false !== (bool) Utils\get_flag_value( $assoc_args, 'recently-active', false ) ) {
if ( false !== Utils\get_flag_value( $assoc_args, 'recently-active', false ) ) {
$all_items = array_filter(
$all_items,
function ( $value ) {
@ -624,6 +663,9 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
* @return bool
*/
protected function has_update( $slug ) {
/**
* @var object{checked: array<string, string>, response: array<string, string>, no_update: array<string, object{new_version: string, package: string, requires: string}&\stdClass>} $update_list
*/
$update_list = get_site_transient( $this->upgrade_transient );
return isset( $update_list->response[ $slug ] );
@ -632,10 +674,15 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
/**
* Get the available update info
*
* @return mixed
* @return object{checked: array<string, string>, response: array<string, array<string, string|null>>, no_update: array<string, object{new_version: string, package: string, requires: string}&\stdClass>} $update_list
*/
protected function get_update_info() {
return get_site_transient( $this->upgrade_transient );
/**
* @var object{checked: array<string, string>, response: array<string, array<string, string|null>>, no_update: array<string, object{new_version: string, package: string, requires: string}&\stdClass>} $update_list
*/
$update_list = get_site_transient( $this->upgrade_transient );
return $update_list;
}
private $map = [
@ -688,8 +735,12 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
$wp_org_api = new WpOrgApi( [ 'insecure' => $insecure ] );
foreach ( $items as $i => $item ) {
try {
$data = call_user_func(
[ $wp_org_api, "get_{$item_type}_info" ],
/**
* @var callable $callback
*/
$callback = [ $wp_org_api, "get_{$item_type}_info" ];
$data = call_user_func(
$callback,
$item['name'],
// The default.
'en_US',
@ -780,9 +831,15 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
if ( 'plugin' === $this->item_type ) {
$api = plugins_api( 'query_plugins', $api_args );
} else {
// fields[screenshot_count] could be an int, not a bool.
// @phpstan-ignore argument.type
$api = themes_api( 'query_themes', $api_args );
}
/**
* @var \WP_Error|object{info: object{page: int, pages: int, results: int}} $api
*/
if ( is_wp_error( $api ) ) {
WP_CLI::error( $api->get_error_message() . __( ' Try again' ) );
}
@ -803,7 +860,10 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
}
if ( 'table' === $format ) {
$count = Utils\get_flag_value( $api->info, 'results', 'unknown' );
/**
* @var string $count
*/
$count = Utils\get_flag_value( (array) $api->info, 'results', 'unknown' );
WP_CLI::success( sprintf( 'Showing %s of %s %s.', count( $items ), $count, $plural ) );
}
@ -832,18 +892,6 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
return true;
}
/**
* Retrieves PHP_URL_HOST component from URL.
*
* @param int $component The component to retrieve.
*
* @return string
*/
private function parse_url_host_component( $url, $component ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- parse_url will only be used in absence of wp_parse_url.
return function_exists( 'wp_parse_url' ) ? wp_parse_url( $url, $component ) : parse_url( $url, $component );
}
/**
* Add versioned GitHub URLs to cache allowlist.
*
@ -895,6 +943,9 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
}
if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
/**
* @var object{status: string, message: string} $decoded_body
*/
return new \WP_Error(
$decoded_body->status,
$decoded_body->message
@ -905,6 +956,10 @@ abstract class CommandWithUpgrade extends \WP_CLI_Command {
return new \WP_Error( 500, 'Empty response received from GitHub.com API' );
}
/**
* @var array<int, object{name: string}> $decoded_body
*/
if ( ! isset( $decoded_body[0] ) ) {
return new \WP_Error( '400', 'The given Github repository does not have any releases' );
}

View file

@ -4,6 +4,8 @@ namespace WP_CLI\Fetchers;
/**
* Fetch a WordPress plugin based on one of its attributes.
*
* @extends Base<object{name: string, file: string}>
*/
class Plugin extends Base {
@ -15,10 +17,12 @@ class Plugin extends Base {
/**
* Get a plugin object by name
*
* @param string $name
* @return object|false
* @param string|int $name Plugin name.
* @return object{name: string, file: string}|false
*/
public function get( $name ) {
$name = (string) $name;
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
foreach ( apply_filters( 'all_plugins', get_plugins() ) as $file => $_ ) {
if ( "$name.php" === $file ||

View file

@ -6,6 +6,8 @@ use WP_CLI\Utils;
/**
* Fetch a WordPress theme based on one of its attributes.
*
* @extends Base<\WP_Theme>
*/
class Theme extends Base {
@ -17,8 +19,8 @@ class Theme extends Base {
/**
* Get a theme object by name
*
* @param string $name
* @return object|false
* @param string|int $name
* @return \WP_Theme|false
*/
public function get( $name ) {
// Workaround to equalize folder naming conventions across Win/Mac/Linux.
@ -26,7 +28,7 @@ class Theme extends Base {
$existing_themes = wp_get_themes( array( 'errors' => null ) );
$existing_stylesheets = array_keys( $existing_themes );
if ( ! in_array( $name, $existing_stylesheets, true ) ) {
$inexact_match = $this->find_inexact_match( $name, $existing_themes );
$inexact_match = $this->find_inexact_match( (string) $name, $existing_themes );
if ( false !== $inexact_match ) {
$this->msg .= sprintf( " Did you mean '%s'?", $inexact_match );
}

View file

@ -5,6 +5,9 @@ namespace WP_CLI;
use WP_CLI;
use Theme_AutoUpdates_Command;
/**
* @template T of \WP_Theme
*/
trait ParseThemeNameInput {
/**
@ -54,11 +57,17 @@ trait ParseThemeNameInput {
$theme_version_info = array();
if ( is_multisite() ) {
/**
* @var array<string, array{enabled: string}>} $site_enabled
*/
$site_enabled = get_option( 'allowedthemes' );
if ( empty( $site_enabled ) ) {
$site_enabled = array();
}
/**
* @var array<string, array{enabled: string}>} $network_enabled
*/
$network_enabled = get_site_option( 'allowedthemes' );
if ( empty( $network_enabled ) ) {
$network_enabled = array();
@ -169,7 +178,9 @@ trait ParseThemeNameInput {
* @return bool|string
*/
protected function is_theme_version_valid( $slug, $version ) {
// Get Theme Info.
/**
* @var \WP_Error|object{name: string, slug: string, version: string, download_link: string} $theme_info
*/
$theme_info = themes_api( 'theme_information', array( 'slug' => $slug ) );
// Return empty string for themes not on WP.org.
@ -184,7 +195,7 @@ trait ParseThemeNameInput {
/**
* Get the status for a given theme.
*
* @param WP_Theme $theme Theme to get the status for.
* @param T $theme Theme to get the status for.
*
* @return string Status of the theme.
*/
@ -203,7 +214,7 @@ trait ParseThemeNameInput {
/**
* Check whether a given theme is the active theme.
*
* @param WP_Theme $theme Theme to check.
* @param \WP_Theme $theme Theme to check.
*
* @return bool Whether the provided theme is the active theme.
*/
@ -214,7 +225,7 @@ trait ParseThemeNameInput {
/**
* Check whether a given theme is the active theme parent.
*
* @param WP_Theme $theme Theme to check.
* @param \WP_Theme $theme Theme to check.
*
* @return bool Whether the provided theme is the active theme.
*/
@ -225,9 +236,14 @@ trait ParseThemeNameInput {
/**
* Get the available update info.
*
* @return mixed Available update info.
* @return object{checked: array<string, string>, response: array<string, array<string, string|null>>, no_update: array<string, object{new_version: string, package: string, requires: string}&\stdClass>} Available update info.
*/
protected function get_update_info() {
return get_site_transient( 'update_themes' );
/**
* @var object{checked: array<string, string>, response: array<string, array<string, string|null>>, no_update: array<string, object{new_version: string, package: string, requires: string}&\stdClass>} $result
*/
$result = get_site_transient( 'update_themes' );
return $result;
}
}