From d8c60d09d0b27940ca27eecd283a93ba90c14671 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 18 May 2025 13:21:48 +0200 Subject: [PATCH 1/6] PHPStan level 6 --- composer.json | 7 +++- features/post.feature | 34 ----------------- phpstan.neon.dist | 20 ++++++++++ src/Comment_Command.php | 8 +++- src/Comment_Meta_Command.php | 4 +- src/Menu_Command.php | 3 +- src/Menu_Item_Command.php | 8 ++-- src/Menu_Location_Command.php | 10 ++++- src/Option_Command.php | 6 ++- src/Post_Command.php | 22 +++++------ src/Post_Meta_Command.php | 4 +- src/Post_Type_Command.php | 5 ++- src/Signup_Command.php | 2 +- src/Site_Command.php | 14 +++++-- src/Site_Meta_Command.php | 4 +- src/Taxonomy_Command.php | 15 ++------ src/Term_Command.php | 21 ++++++++--- src/Term_Meta_Command.php | 2 +- src/User_Application_Password_Command.php | 4 +- src/User_Command.php | 45 +++++++++++++---------- src/User_Meta_Command.php | 2 +- src/User_Session_Command.php | 2 +- src/WP_CLI/CommandWithDBObject.php | 25 +++---------- src/WP_CLI/CommandWithMeta.php | 6 +-- 24 files changed, 139 insertions(+), 134 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index 0b0a2a7cb..ffeae7d2f 100644 --- a/composer.json +++ b/composer.json @@ -20,14 +20,15 @@ "wp-cli/extension-command": "^1.2 || ^2", "wp-cli/media-command": "^1.1 || ^2", "wp-cli/super-admin-command": "^1 || ^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/wp-cli-tests": "dev-main" }, "config": { "process-timeout": 7200, "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, - "johnpbloch/wordpress-core-installer": true + "johnpbloch/wordpress-core-installer": true, + "phpstan/extension-installer": true }, "lock": false }, @@ -230,12 +231,14 @@ "behat-rerun": "rerun-behat-tests", "lint": "run-linter-tests", "phpcs": "run-phpcs-tests", + "phpstan": "run-phpstan-tests", "phpunit": "run-php-unit-tests", "phpcbf": "run-phpcbf-cleanup", "prepare-tests": "install-package-tests", "test": [ "@lint", "@phpcs", + "@phpstan", "@phpunit", "@behat" ] diff --git a/features/post.feature b/features/post.feature index c3f72a522..858668bfc 100644 --- a/features/post.feature +++ b/features/post.feature @@ -451,40 +451,6 @@ Feature: Manage WordPress posts Test Post """ - @less-than-wp-4.4 - Scenario: Creating/updating posts with meta keys for WP < 4.4 has no effect so should give warning - When I try `wp post create --post_title='Test Post' --post_content='Test post content' --meta_input='{"key1":"value1","key2":"value2"}' --porcelain` - Then the return code should be 0 - And STDOUT should be a number - And save STDOUT as {POST_ID} - And STDERR should be: - """ - Warning: The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect. - """ - - When I run `wp post meta list {POST_ID} --format=count` - Then STDOUT should be: - """ - 0 - """ - - When I try `wp post update {POST_ID} --meta_input='{"key2":"value2b","key3":"value3"}'` - Then the return code should be 0 - And STDERR should be: - """ - Warning: The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect. - """ - And STDOUT should be: - """ - Success: Updated post {POST_ID}. - """ - - When I run `wp post meta list {POST_ID} --format=count` - Then STDOUT should be: - """ - 0 - """ - Scenario: Publishing a post and setting a date fails if the edit_date flag is not passed. Given a WP install diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 000000000..5cf50e82f --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,20 @@ +parameters: + level: 6 + paths: + - src + - entity-command.php + scanDirectories: + - vendor/wp-cli/wp-cli/php + - vendor/wp-cli/wp-cli-tests + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php + treatPhpDocTypesAsCertain: false + dynamicConstantNames: + - WP_DEBUG + - WP_DEBUG_LOG + - WP_DEBUG_DISPLAY + ignoreErrors: + - identifier: missingType.iterableValue + - identifier: missingType.property + - identifier: missingType.parameter + - identifier: missingType.return diff --git a/src/Comment_Command.php b/src/Comment_Command.php index 0661ba91d..1a7cf4ef4 100644 --- a/src/Comment_Command.php +++ b/src/Comment_Command.php @@ -243,6 +243,7 @@ public function get( $args, $assoc_args ) { } if ( ! isset( $comment->url ) ) { + // @phpstan-ignore property.notFound $comment->url = get_comment_link( $comment ); } @@ -366,6 +367,8 @@ public function get( $args, $assoc_args ) { public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); + // To be fixed in wp-cli/wp-cli. + // @phpstan-ignore property.notFound if ( 'ids' === $formatter->format ) { $assoc_args['fields'] = 'comment_ID'; } @@ -642,16 +645,17 @@ public function unapprove( $args, $assoc_args ) { * total_comments: 19 */ public function count( $args, $assoc_args ) { - $post_id = Utils\get_flag_value( $args, 0, 0 ); + $post_id = $args[0] ?? null; $count = wp_count_comments( $post_id ); // Move total_comments to the end of the object $total = $count->total_comments; unset( $count->total_comments ); + // @phpstan-ignore assign.propertyReadOnly $count->total_comments = $total; - foreach ( $count as $status => $count ) { + foreach ( (array) $count as $status => $count ) { WP_CLI::line( str_pad( "$status:", 17 ) . $count ); } } diff --git a/src/Comment_Meta_Command.php b/src/Comment_Meta_Command.php index 7cb018211..38b6c3985 100644 --- a/src/Comment_Meta_Command.php +++ b/src/Comment_Meta_Command.php @@ -104,11 +104,11 @@ protected function delete_metadata( $object_id, $meta_key, $meta_value = '' ) { /** * Check that the comment ID exists * - * @param int + * @param int $object_id */ protected function check_object_id( $object_id ) { $fetcher = new CommentFetcher(); - $comment = $fetcher->get_check( $object_id ); + $comment = $fetcher->get_check( (string) $object_id ); return $comment->comment_ID; } } diff --git a/src/Menu_Command.php b/src/Menu_Command.php index e68065300..b23df5c7a 100644 --- a/src/Menu_Command.php +++ b/src/Menu_Command.php @@ -70,7 +70,7 @@ public function create( $args, $assoc_args ) { } elseif ( Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { - WP_CLI::line( $menu_id ); + WP_CLI::line( (string) $menu_id ); } else { WP_CLI::success( "Created menu {$menu_id}." ); } @@ -166,6 +166,7 @@ public function list_( $args, $assoc_args ) { $menu_locations = get_nav_menu_locations(); foreach ( $menus as &$menu ) { + // @phpstan-ignore property.notFound $menu->locations = []; foreach ( $menu_locations as $location => $term_id ) { diff --git a/src/Menu_Item_Command.php b/src/Menu_Item_Command.php index c61d9f7d8..43ee2bdad 100644 --- a/src/Menu_Item_Command.php +++ b/src/Menu_Item_Command.php @@ -92,7 +92,7 @@ class Menu_Item_Command extends WP_CLI_Command { public function list_( $args, $assoc_args ) { $items = wp_get_nav_menu_items( $args[0] ); - if ( false === $items || is_wp_error( $items ) ) { + if ( false === $items ) { WP_CLI::error( 'Invalid menu.' ); } @@ -408,10 +408,10 @@ public function delete( $args, $assoc_args ) { private function add_or_update_item( $method, $type, $args, $assoc_args ) { $menu = $args[0]; - $menu_item_db_id = Utils\get_flag_value( $args, 1, 0 ); + $menu_item_db_id = $args[1] ?? 0; $menu = wp_get_nav_menu_object( $menu ); - if ( ! $menu || is_wp_error( $menu ) ) { + if ( false === $menu ) { WP_CLI::error( 'Invalid menu.' ); } @@ -502,7 +502,7 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) { } if ( 'add' === $method && ! empty( $assoc_args['porcelain'] ) ) { - WP_CLI::line( $result ); + WP_CLI::line( (string) $result ); } elseif ( 'add' === $method ) { WP_CLI::success( 'Menu item added.' ); } elseif ( 'update' === $method ) { diff --git a/src/Menu_Location_Command.php b/src/Menu_Location_Command.php index 540c87e47..a5bc555d3 100644 --- a/src/Menu_Location_Command.php +++ b/src/Menu_Location_Command.php @@ -77,6 +77,8 @@ public function list_( $args, $assoc_args ) { $formatter = new Formatter( $assoc_args, [ 'location', 'description' ] ); + // To be fixed in wp-cli/wp-cli. + // @phpstan-ignore property.notFound if ( 'ids' === $formatter->format ) { $ids = array_map( function ( $o ) { @@ -107,6 +109,8 @@ function ( $o ) { * Success: Assigned location primary to menu primary-menu. * * @subcommand assign + * + * @param array{string, string} $args */ public function assign( $args, $assoc_args ) { @@ -147,18 +151,20 @@ public function assign( $args, $assoc_args ) { * Success: Removed location from menu. * * @subcommand remove + * + * @param array{string, string} $args */ public function remove( $args, $assoc_args ) { list( $menu, $location ) = $args; $menu = wp_get_nav_menu_object( $menu ); - if ( ! $menu || is_wp_error( $menu ) ) { + if ( false === $menu ) { WP_CLI::error( 'Invalid menu.' ); } $locations = get_nav_menu_locations(); - if ( Utils\get_flag_value( $locations, $location ) !== $menu->term_id ) { + if ( ( $locations[ $location ] ?? null ) !== $menu->term_id ) { WP_CLI::error( "Menu isn't assigned to location." ); } diff --git a/src/Option_Command.php b/src/Option_Command.php index 73bc32b24..126e19395 100644 --- a/src/Option_Command.php +++ b/src/Option_Command.php @@ -133,6 +133,7 @@ public function add( $args, $assoc_args ) { $autoload = 'yes'; } + // @phpstan-ignore argument.type if ( ! add_option( $key, $value, '', $autoload ) ) { WP_CLI::error( "Could not add option '{$key}'. Does it already exist?" ); } else { @@ -321,8 +322,8 @@ public function list_( $args, $assoc_args ) { function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order - ? $a->$orderby > $b->$orderby - : $a->$orderby < $b->$orderby; + ? $a[ $orderby ] <=> $b[ $orderby ] + : $b[ $orderby ] <=> $a[ $orderby ]; } ); } elseif ( 'option_id' === $orderby && 'desc' === $order ) { // Sort by default descending. @@ -431,6 +432,7 @@ public function update( $args, $assoc_args ) { if ( $value === $old_value && null === $autoload ) { WP_CLI::success( "Value passed for '{$key}' option is unchanged." ); + // @phpstan-ignore argument.type } elseif ( update_option( $key, $value, $autoload ) ) { WP_CLI::success( "Updated '{$key}' option." ); } else { diff --git a/src/Post_Command.php b/src/Post_Command.php index a4a5ba42d..14364b2be 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -182,10 +182,6 @@ public function create( $args, $assoc_args ) { $assoc_args['post_category'] = $this->get_category_ids( $assoc_args['post_category'] ); } - if ( isset( $assoc_args['meta_input'] ) && Utils\wp_version_compare( '4.4', '<' ) ) { - WP_CLI::warning( "The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect." ); - } - $array_arguments = [ 'meta_input' ]; $assoc_args = Utils\parse_shell_arrays( $assoc_args, $array_arguments ); @@ -351,10 +347,6 @@ public function update( $args, $assoc_args ) { $assoc_args['post_category'] = $this->get_category_ids( $assoc_args['post_category'] ); } - if ( isset( $assoc_args['meta_input'] ) && Utils\wp_version_compare( '4.4', '<' ) ) { - WP_CLI::warning( "The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect." ); - } - $array_arguments = [ 'meta_input' ]; $assoc_args = Utils\parse_shell_arrays( $assoc_args, $array_arguments ); @@ -387,7 +379,7 @@ public function edit( $args, $assoc_args ) { $result = $this->_edit( $post->post_content, "WP-CLI post {$post->ID}" ); if ( false === $result ) { - WP_CLI::warning( 'No change made to post content.', 'Aborted' ); + WP_CLI::warning( 'No change made to post content.' ); } else { $this->update( $args, [ 'post_content' => $result ] ); } @@ -644,9 +636,12 @@ public function list_( $args, $assoc_args ) { $query_args['post_type'] = explode( ',', $query_args['post_type'] ); } + // To be fixed in wp-cli/wp-cli. + // @phpstan-ignore property.notFound if ( 'ids' === $formatter->format ) { $query_args['fields'] = 'ids'; $query = new WP_Query( $query_args ); + // @phpstan-ignore argument.type echo implode( ' ', $query->posts ); } elseif ( 'count' === $formatter->format ) { $query_args['fields'] = 'ids'; @@ -656,8 +651,13 @@ public function list_( $args, $assoc_args ) { $query = new WP_Query( $query_args ); $posts = array_map( function ( $post ) { - $post->url = get_permalink( $post->ID ); - return $post; + /** + * @var \WP_Post $post + */ + + // @phpstan-ignore property.notFound + $post->url = get_permalink( $post->ID ); + return $post; }, $query->posts ); diff --git a/src/Post_Meta_Command.php b/src/Post_Meta_Command.php index 96aa2341c..f225851db 100644 --- a/src/Post_Meta_Command.php +++ b/src/Post_Meta_Command.php @@ -30,11 +30,11 @@ class Post_Meta_Command extends CommandWithMeta { /** * Check that the post ID exists * - * @param int + * @param int $object_id */ protected function check_object_id( $object_id ) { $fetcher = new PostFetcher(); - $post = $fetcher->get_check( $object_id ); + $post = $fetcher->get_check( (string) $object_id ); return $post->ID; } diff --git a/src/Post_Type_Command.php b/src/Post_Type_Command.php index 88c01029a..335477bc2 100644 --- a/src/Post_Type_Command.php +++ b/src/Post_Type_Command.php @@ -147,8 +147,9 @@ public function list_( $args, $assoc_args ) { $types = array_map( function ( $type ) use ( $counts ) { - $type->count = isset( $counts[ $type->name ] ) ? $counts[ $type->name ] : 0; - return $type; + // @phpstan-ignore property.notFound + $type->count = isset( $counts[ $type->name ] ) ? $counts[ $type->name ] : 0; + return $type; }, $types ); diff --git a/src/Signup_Command.php b/src/Signup_Command.php index 4144257ef..33431e2a4 100644 --- a/src/Signup_Command.php +++ b/src/Signup_Command.php @@ -308,7 +308,7 @@ public function delete( $args, $assoc_args ) { /** * Deletes signup. * - * @param stdClasss $signup + * @param object{signup_id: int|string} $signup * @return bool True if success; otherwise false. */ private function delete_signup( $signup ) { diff --git a/src/Site_Command.php b/src/Site_Command.php index de5451a5e..5c47c612a 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -89,6 +89,9 @@ private function empty_posts() { * Delete terms, taxonomies, and tax relationships. */ private function empty_taxonomies() { + /** + * @var \wpdb $wpdb + */ global $wpdb; // Empty taxonomies and terms @@ -360,7 +363,7 @@ public function delete( $args, $assoc_args ) { WP_CLI::confirm( "Are you sure you want to delete the '{$site_url}' site?", $assoc_args ); - wpmu_delete_blog( $blog->blog_id, ! Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); + wpmu_delete_blog( (int) $blog->blog_id, ! Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); WP_CLI::success( "The site at '{$site_url}' was deleted." ); } @@ -479,7 +482,7 @@ public function create( $args, $assoc_args ) { } if ( Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { - WP_CLI::line( $id ); + WP_CLI::line( (string) $id ); } else { $site_url = trailingslashit( get_site_url( $id ) ); WP_CLI::success( "Site {$id} created: {$site_url}" ); @@ -830,6 +833,9 @@ public function list_( $args, $assoc_args ) { $iterator = new TableIterator( $iterator_args ); + /** + * @var iterable $iterator + */ $iterator = Utils\iterator_map( $iterator, function ( $blog ) { @@ -1142,6 +1148,8 @@ public function set_private( $args, $assoc_args ) { private function update_site_status( $ids, $pref, $value ) { $value = (int) $value; + $action = 'updated'; + switch ( $pref ) { case 'archived': $action = $value ? 'archived' : 'unarchived'; @@ -1175,7 +1183,7 @@ private function update_site_status( $ids, $pref, $value ) { continue; } - update_blog_status( $site->blog_id, $pref, $value ); + update_blog_status( $site->blog_id, $pref, (string) $value ); WP_CLI::success( "Site {$site->blog_id} {$action}." ); } } diff --git a/src/Site_Meta_Command.php b/src/Site_Meta_Command.php index acc587417..e9bcfadb7 100644 --- a/src/Site_Meta_Command.php +++ b/src/Site_Meta_Command.php @@ -30,11 +30,11 @@ class Site_Meta_Command extends CommandWithMeta { /** * Check that the site ID exists * - * @param int + * @param int $object_id */ protected function check_object_id( $object_id ) { $fetcher = new SiteFetcher(); - $site = $fetcher->get_check( $object_id ); + $site = $fetcher->get_check( (string) $object_id ); return $site->blog_id; } diff --git a/src/Taxonomy_Command.php b/src/Taxonomy_Command.php index 1f1d05f97..8bae29616 100644 --- a/src/Taxonomy_Command.php +++ b/src/Taxonomy_Command.php @@ -38,16 +38,6 @@ class Taxonomy_Command extends WP_CLI_Command { 'public', ); - public function __construct() { - - if ( Utils\wp_version_compare( 3.7, '<' ) ) { - // remove description for wp <= 3.7 - $this->fields = array_values( array_diff( $this->fields, array( 'description' ) ) ); - } - - parent::__construct(); - } - /** * Gets the term counts for each supplied taxonomy. * @@ -173,8 +163,11 @@ public function list_( $args, $assoc_args ) { $taxonomies = array_map( function ( $taxonomy ) use ( $counts ) { + // @phpstan-ignore assign.propertyType $taxonomy->object_type = implode( ', ', $taxonomy->object_type ); - $taxonomy->count = isset( $counts[ $taxonomy->name ] ) ? $counts[ $taxonomy->name ] : 0; + + // @phpstan-ignore property.notFound + $taxonomy->count = isset( $counts[ $taxonomy->name ] ) ? $counts[ $taxonomy->name ] : 0; return $taxonomy; }, $taxonomies diff --git a/src/Term_Command.php b/src/Term_Command.php index e02189090..0cb7dd891 100644 --- a/src/Term_Command.php +++ b/src/Term_Command.php @@ -133,6 +133,9 @@ public function list_( $args, $assoc_args ) { $assoc_args = array_merge( $defaults, $assoc_args ); if ( ! empty( $assoc_args['term_id'] ) ) { + /** + * @var \WP_Term $term + */ $term = get_term_by( 'id', $assoc_args['term_id'], $args[0] ); $terms = [ $term ]; } elseif ( ! empty( $assoc_args['include'] ) @@ -143,13 +146,16 @@ public function list_( $args, $assoc_args ) { $term_ids = explode( ',', $assoc_args['include'] ); foreach ( $term_ids as $term_id ) { $term = get_term_by( 'id', $term_id, $args[0] ); - if ( $term && ! is_wp_error( $term ) ) { + if ( $term instanceof \WP_Term ) { $terms[] = $term; } else { WP_CLI::warning( "Invalid term {$term_id}." ); } } } else { + /** + * @var \WP_Term[] $terms + */ // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found -- Required for backward compatibility. $terms = get_terms( $args, $assoc_args ); } @@ -158,7 +164,9 @@ public function list_( $args, $assoc_args ) { function ( $term ) { $term->count = (int) $term->count; $term->parent = (int) $term->parent; - $term->url = get_term_link( $term ); + + // @phpstan-ignore property.notFound + $term->url = get_term_link( $term ); return $term; }, $terms @@ -227,7 +235,7 @@ public function create( $args, $assoc_args ) { if ( is_wp_error( $result ) ) { WP_CLI::error( $result->get_error_message() ); } elseif ( $porcelain ) { - WP_CLI::line( $result['term_id'] ); + WP_CLI::line( (string) $result['term_id'] ); } else { WP_CLI::success( "Created {$taxonomy} {$result['term_id']}." ); } @@ -291,6 +299,7 @@ public function get( $args, $assoc_args ) { } if ( ! isset( $term->url ) ) { + // @phpstan-ignore property.notFound $term->url = get_term_link( $term ); } @@ -709,9 +718,9 @@ public function migrate( $args, $assoc_args ) { $post_ids = get_objects_in_term( $term->term_id, $original_taxonomy->name ); foreach ( $post_ids as $post_id ) { - $type = get_post_type( $post_id ); + $type = get_post_type( (int) $post_id ); if ( in_array( $type, $original_taxonomy->object_type, true ) ) { - $term_taxonomy_id = wp_set_object_terms( $post_id, $id['term_id'], $destination_taxonomy, true ); + $term_taxonomy_id = wp_set_object_terms( (int) $post_id, $id['term_id'], $destination_taxonomy, true ); if ( is_wp_error( $term_taxonomy_id ) ) { WP_CLI::error( "Failed to assign the term '{$term->slug}' to the post {$post_id}. Reason: " . $term_taxonomy_id->get_error_message() ); @@ -720,7 +729,7 @@ public function migrate( $args, $assoc_args ) { WP_CLI::log( "Term '{$term->slug}' assigned to post {$post_id}." ); } - clean_post_cache( $post_id ); + clean_post_cache( (int) $post_id ); } clean_term_cache( $term->term_id ); diff --git a/src/Term_Meta_Command.php b/src/Term_Meta_Command.php index f751b6eba..b95c98292 100644 --- a/src/Term_Meta_Command.php +++ b/src/Term_Meta_Command.php @@ -29,7 +29,7 @@ class Term_Meta_Command extends CommandWithMeta { /** * Check that the term ID exists * - * @param int + * @param int $object_id */ protected function check_object_id( $object_id ) { $term = get_term( $object_id ); diff --git a/src/User_Application_Password_Command.php b/src/User_Application_Password_Command.php index 89cb4ea9e..edd6c4f82 100644 --- a/src/User_Application_Password_Command.php +++ b/src/User_Application_Password_Command.php @@ -163,8 +163,8 @@ public function list_( $args, $assoc_args ) { static function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order - ? $a[ $orderby ] > $b[ $orderby ] - : $a[ $orderby ] < $b[ $orderby ]; + ? $a[ $orderby ] <=> $b[ $orderby ] + : $b[ $orderby ] <=> $a[ $orderby ]; } ); diff --git a/src/User_Command.php b/src/User_Command.php index 5bad384c0..da42810bd 100644 --- a/src/User_Command.php +++ b/src/User_Command.php @@ -152,6 +152,8 @@ public function list_( $args, $assoc_args ) { $formatter = $this->get_formatter( $assoc_args ); + // To be fixed in wp-cli/wp-cli. + // @phpstan-ignore property.notFound if ( in_array( $formatter->format, [ 'ids', 'count' ], true ) ) { $assoc_args['fields'] = 'ids'; } else { @@ -184,8 +186,14 @@ function ( $user ) { return $user; } + /** + * @var \WP_User $user + */ + + // @phpstan-ignore assign.propertyType $user->roles = implode( ',', $user->roles ); - $user->url = get_author_posts_url( $user->ID, $user->user_nicename ); + // @phpstan-ignore property.notFound + $user->url = get_author_posts_url( $user->ID, $user->user_nicename ); return $user; } ); @@ -434,7 +442,7 @@ public function create( $args, $assoc_args ) { if ( is_multisite() ) { $result = wpmu_validate_user_signup( $user->user_login, $user->user_email ); - if ( is_wp_error( $result['errors'] ) && ! empty( $result['errors']->errors ) ) { + if ( ! empty( $result['errors']->errors ) ) { WP_CLI::error( $result['errors'] ); } $user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email ); @@ -465,7 +473,7 @@ public function create( $args, $assoc_args ) { } if ( Utils\get_flag_value( $assoc_args, 'porcelain' ) ) { - WP_CLI::line( $user_id ); + WP_CLI::line( (string) $user_id ); } else { WP_CLI::success( "Created user {$user_id}." ); if ( isset( $generated_pass ) ) { @@ -710,7 +718,7 @@ public function exists( $args ) { public function set_role( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); - $role = Utils\get_flag_value( $args, 1, get_option( 'default_role' ) ); + $role = $args[1] ?? get_option( 'default_role' ); self::validate_role( $role ); @@ -881,6 +889,9 @@ public function add_cap( $args, $assoc_args ) { * @subcommand remove-cap */ public function remove_cap( $args, $assoc_args ) { + /** + * @var \WP_User $user + */ $user = $this->fetcher->get_check( $args[0] ); if ( $user ) { $cap = $args[1]; @@ -1086,6 +1097,8 @@ public function import_csv( $args, $assoc_args ) { continue; } + $data = []; + foreach ( $indexes as $n => $key ) { $data[ $key ] = $line[ $n ]; } @@ -1157,7 +1170,7 @@ public function import_csv( $args, $assoc_args ) { if ( is_multisite() ) { $result = wpmu_validate_user_signup( $new_user['user_login'], $new_user['user_email'] ); - if ( is_wp_error( $result['errors'] ) && ! empty( $result['errors']->errors ) ) { + if ( ! empty( $result['errors']->errors ) ) { WP_CLI::warning( $result['errors'] ); continue; } @@ -1313,24 +1326,13 @@ private static function validate_role( $role, $warn_user_only = false ) { } /** - * Accommodates three different behaviors for wp_new_user_notification() - * - 4.3.1 and above: expect second argument to be deprecated - * - 4.3: Second argument was repurposed as $notify - * - Below 4.3: Send the password in the notification + * Wrapper around wp_new_user_notification(). * - * @param string $user_id - * @param string $password + * @param string|int $user_id + * @param mixed $password */ public static function wp_new_user_notification( $user_id, $password ) { - if ( Utils\wp_version_compare( '4.3.1', '>=' ) ) { - wp_new_user_notification( $user_id, null, 'both' ); - } elseif ( Utils\wp_version_compare( '4.3', '>=' ) ) { - // phpcs:ignore WordPress.WP.DeprecatedParameters.Wp_new_user_notificationParam2Found -- Only called in valid conditions. - wp_new_user_notification( $user_id, 'both' ); - } else { - // phpcs:ignore WordPress.WP.DeprecatedParameters.Wp_new_user_notificationParam2Found -- Only called in valid conditions. - wp_new_user_notification( $user_id, $password ); - } + wp_new_user_notification( $user_id, null, 'both' ); } /** @@ -1381,6 +1383,9 @@ private function update_msuser_status( $user_ids, $pref, $value ) { WP_CLI::error( 'This is not a multisite installation.' ); } + $action = 'updated'; + $verb = 'update'; + if ( 'spam' === $pref ) { $action = (int) $value ? 'marked as spam' : 'removed from spam'; $verb = (int) $value ? 'spam' : 'unspam'; diff --git a/src/User_Meta_Command.php b/src/User_Meta_Command.php index c5ee80337..ccee72cbf 100644 --- a/src/User_Meta_Command.php +++ b/src/User_Meta_Command.php @@ -316,7 +316,7 @@ protected function delete_metadata( $object_id, $meta_key, $meta_value = '' ) { * Replaces user_login value with user ID * user meta is a special case that also supports user_login * - * @param array + * @param array $args * @return array */ private function replace_login_with_user_id( $args ) { diff --git a/src/User_Session_Command.php b/src/User_Session_Command.php index d5b9e1957..b70d4805f 100644 --- a/src/User_Session_Command.php +++ b/src/User_Session_Command.php @@ -71,7 +71,7 @@ public function __construct() { */ public function destroy( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); - $token = Utils\get_flag_value( $args, 1, null ); + $token = $args[1] ?? null; $all = Utils\get_flag_value( $assoc_args, 'all', false ); $manager = WP_Session_Tokens::get_instance( $user->ID ); diff --git a/src/WP_CLI/CommandWithDBObject.php b/src/WP_CLI/CommandWithDBObject.php index cb3215bb8..767a0bb27 100644 --- a/src/WP_CLI/CommandWithDBObject.php +++ b/src/WP_CLI/CommandWithDBObject.php @@ -33,9 +33,9 @@ abstract class CommandWithDBObject extends WP_CLI_Command { * Create a given database object. * Exits with status. * - * @param array $args Arguments passed to command. Generally unused. - * @param array $assoc_args Parameters passed to command to be passed to callback. - * @param string $callback Function used to create object. + * @param array $args Arguments passed to command. Generally unused. + * @param array $assoc_args Parameters passed to command to be passed to callback. + * @param callable $callback Function used to create object. */ protected function _create( $args, $assoc_args, $callback ) { unset( $assoc_args[ $this->obj_id_key ] ); @@ -57,9 +57,9 @@ protected function _create( $args, $assoc_args, $callback ) { * Update a given database object. * Exits with status. * - * @param array $args Collection of one or more object ids to update. - * @param array $assoc_args Fields => values to update on each object. - * @param string $callback Function used to update object. + * @param array $args Collection of one or more object ids to update. + * @param array $assoc_args Fields => values to update on each object. + * @param callable $callback Function used to update object. */ protected function _update( $args, $assoc_args, $callback ) { $status = 0; @@ -184,17 +184,4 @@ protected function get_formatter( &$assoc_args ) { } return new Formatter( $assoc_args, $fields, $this->obj_type ); } - - /** - * Given a callback, display the URL for one or more objects. - * - * @param array $args One or more object references. - * @param string $callback Function to get URL for the object. - */ - protected function _url( $args, $callback ) { - foreach ( $args as $obj_id ) { - $object = $this->fetcher->get_check( $obj_id ); - WP_CLI::line( $callback( $object->{$this->obj_id_key} ) ); - } - } } diff --git a/src/WP_CLI/CommandWithMeta.php b/src/WP_CLI/CommandWithMeta.php index 984f66c14..b1a0e3c96 100644 --- a/src/WP_CLI/CommandWithMeta.php +++ b/src/WP_CLI/CommandWithMeta.php @@ -112,8 +112,8 @@ public function list_( $args, $assoc_args ) { function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order - ? $a->$orderby > $b->$orderby - : $a->$orderby < $b->$orderby; + ? $a[ $orderby ] <=> $b[ $orderby ] + : $b[ $orderby ] <=> $a[ $orderby ]; } ); @@ -545,7 +545,7 @@ private function get_fields() { /** * Check that the object ID exists * - * @param int + * @param int $object_id */ protected function check_object_id( $object_id ) { // Needs to be set in subclass From db4f48e5c687ee4a9d8c7806a67ee6a8fd0a69ce Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 18 May 2025 13:55:32 +0200 Subject: [PATCH 2/6] Fix object access --- src/Option_Command.php | 4 ++-- src/WP_CLI/CommandWithMeta.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Option_Command.php b/src/Option_Command.php index 126e19395..6cbe3e34b 100644 --- a/src/Option_Command.php +++ b/src/Option_Command.php @@ -322,8 +322,8 @@ public function list_( $args, $assoc_args ) { function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order - ? $a[ $orderby ] <=> $b[ $orderby ] - : $b[ $orderby ] <=> $a[ $orderby ]; + ? $a->$orderby <=> $b->$orderby + : $b->$orderby <=> $a->$orderby; } ); } elseif ( 'option_id' === $orderby && 'desc' === $order ) { // Sort by default descending. diff --git a/src/WP_CLI/CommandWithMeta.php b/src/WP_CLI/CommandWithMeta.php index b1a0e3c96..b1f44bd1d 100644 --- a/src/WP_CLI/CommandWithMeta.php +++ b/src/WP_CLI/CommandWithMeta.php @@ -112,8 +112,8 @@ public function list_( $args, $assoc_args ) { function ( $a, $b ) use ( $orderby, $order ) { // Sort array. return 'asc' === $order - ? $a[ $orderby ] <=> $b[ $orderby ] - : $b[ $orderby ] <=> $a[ $orderby ]; + ? $a->$orderby <=> $b->$orderby + : $b->$orderby <=> $a->$orderby; } ); From 99de66ce625442d733f9452bf28010e1c68bb881 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 May 2025 10:49:35 +0200 Subject: [PATCH 3/6] Almost level 7 --- entity-command.php | 24 ++------------------- src/Comment_Command.php | 32 +++++++++++++--------------- src/Menu_Item_Command.php | 4 ++++ src/Option_Command.php | 14 ++++--------- src/Post_Command.php | 4 ++-- src/Site_Command.php | 5 +++++ src/Site_Option_Command.php | 14 ++++--------- src/Term_Command.php | 37 ++++++++++++++++++++------------- src/Term_Meta_Command.php | 8 +++---- src/User_Command.php | 34 ++++++++++++++++++++++-------- src/WP_CLI/CommandWithTerms.php | 3 +++ 11 files changed, 90 insertions(+), 89 deletions(-) diff --git a/entity-command.php b/entity-command.php index 2146f8867..6cb54ff3c 100644 --- a/entity-command.php +++ b/entity-command.php @@ -60,17 +60,7 @@ ); WP_CLI::add_command( 'taxonomy', 'Taxonomy_Command' ); WP_CLI::add_command( 'term', 'Term_Command' ); -WP_CLI::add_command( - 'term meta', - 'Term_Meta_Command', - array( - 'before_invoke' => function () { - if ( Utils\wp_version_compare( '4.4', '<' ) ) { - WP_CLI::error( 'Requires WordPress 4.4 or greater.' ); - } - }, - ) -); +WP_CLI::add_command( 'term meta', 'Term_Meta_Command' ); WP_CLI::add_command( 'user', 'User_Command' ); WP_CLI::add_command( 'user application-password', @@ -84,17 +74,7 @@ ) ); WP_CLI::add_command( 'user meta', 'User_Meta_Command' ); -WP_CLI::add_command( - 'user session', - 'User_Session_Command', - array( - 'before_invoke' => function () { - if ( Utils\wp_version_compare( '4.0', '<' ) ) { - WP_CLI::error( 'Requires WordPress 4.0 or greater.' ); - } - }, - ) -); +WP_CLI::add_command( 'user session', 'User_Session_Command' ); WP_CLI::add_command( 'user term', 'User_Term_Command' ); if ( class_exists( 'WP_CLI\Dispatcher\CommandNamespace' ) ) { diff --git a/src/Comment_Command.php b/src/Comment_Command.php index 1a7cf4ef4..2b1ed57c2 100644 --- a/src/Comment_Command.php +++ b/src/Comment_Command.php @@ -379,29 +379,22 @@ public function list_( $args, $assoc_args ) { $assoc_args['count'] = true; } - if ( ! empty( $assoc_args['comment__in'] ) - && ! empty( $assoc_args['orderby'] ) - && 'comment__in' === $assoc_args['orderby'] - && Utils\wp_version_compare( '4.4', '<' ) ) { - $comments = []; - foreach ( $assoc_args['comment__in'] as $comment_id ) { - $comment = get_comment( $comment_id ); - if ( $comment ) { - $comments[] = $comment; - } else { - WP_CLI::warning( "Invalid comment {$comment_id}." ); - } - } - } else { - $query = new WP_Comment_Query(); - $comments = $query->query( $assoc_args ); - } + $query = new WP_Comment_Query(); + $comments = $query->query( $assoc_args ); if ( 'count' === $formatter->format ) { + /** + * @var int $comments + */ echo $comments; } else { if ( 'ids' === $formatter->format ) { - $comments = wp_list_pluck( $comments, 'comment_ID' ); + /** + * @var \WP_Comment[] $comments + */ + $items = wp_list_pluck( $comments, 'comment_ID' ); + + $comments = $items; } elseif ( is_array( $comments ) ) { $comments = array_map( function ( $comment ) { @@ -460,6 +453,9 @@ function ( $comment_id, $assoc_args ) { private function call( $args, $status, $success, $failure ) { $comment_id = absint( $args ); + /** + * @var callable $func + */ $func = "wp_{$status}_comment"; if ( ! $func( $comment_id ) ) { diff --git a/src/Menu_Item_Command.php b/src/Menu_Item_Command.php index 43ee2bdad..606c9dee8 100644 --- a/src/Menu_Item_Command.php +++ b/src/Menu_Item_Command.php @@ -422,6 +422,10 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) { if ( 'update' === $method ) { $menu_item_obj = get_post( $menu_item_db_id ); + + /** + * @var object{title: string, url: string, description: string, object: string, object_id: int, menu_item_parent: int, attr_title: string, target: string, classes: string[], xfn: string, post_status: string, menu_order: int} $menu_item_obj + */ $menu_item_obj = wp_setup_nav_menu_item( $menu_item_obj ); // Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140 diff --git a/src/Option_Command.php b/src/Option_Command.php index 6cbe3e34b..823adaac7 100644 --- a/src/Option_Command.php +++ b/src/Option_Command.php @@ -746,17 +746,11 @@ function ( $key ) { } private static function esc_like( $old ) { + /** + * @var \wpdb $wpdb + */ global $wpdb; - // Remove notices in 4.0 and support backwards compatibility - if ( method_exists( $wpdb, 'esc_like' ) ) { - // 4.0 - $old = $wpdb->esc_like( $old ); - } else { - // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- called in WordPress 3.9 or less. - $old = like_escape( esc_sql( $old ) ); - } - - return $old; + return $wpdb->esc_like( $old ); } } diff --git a/src/Post_Command.php b/src/Post_Command.php index 14364b2be..534687113 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -933,7 +933,7 @@ private function read_from_file_or_stdin( $arg ) { } else { $readfile = 'php://stdin'; } - return file_get_contents( $readfile ); + return (string) file_get_contents( $readfile ); } /** @@ -1010,7 +1010,7 @@ private function get_category( $post_id ) { private function get_tags( $post_id ) { $tag_data = get_the_tags( $post_id ); $tag_arr = []; - if ( $tag_data ) { + if ( $tag_data && ! is_wp_error( $tag_data ) ) { foreach ( $tag_data as $tag ) { array_push( $tag_arr, $tag->slug ); } diff --git a/src/Site_Command.php b/src/Site_Command.php index 5c47c612a..ed0d8e654 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -29,6 +29,8 @@ * Success: The site at 'http://www.example.com/example' was deleted. * * @package wp-cli + * + * @phpstan-type UserSite object{userblog_id: int, blogname: string, domain: string, path: string, site_id: int, siteurl: string, archived: int, spam: int, deleted: int} */ class Site_Command extends CommandWithDBObject { @@ -809,6 +811,9 @@ public function list_( $args, $assoc_args ) { $user = ( new UserFetcher() )->get_check( $assoc_args['site_user'] ); if ( $user ) { + /** + * @phpstan-var UserSite[] $blogs + */ $blogs = get_blogs_of_user( $user->ID ); foreach ( $blogs as $blog ) { diff --git a/src/Site_Option_Command.php b/src/Site_Option_Command.php index b2a96883e..c4e9e4b4f 100644 --- a/src/Site_Option_Command.php +++ b/src/Site_Option_Command.php @@ -406,17 +406,11 @@ function ( $key ) { } private static function esc_like( $old ) { + /** + * @var \wpdb $wpdb + */ global $wpdb; - // Remove notices in 4.0 and support backwards compatibility - if ( method_exists( $wpdb, 'esc_like' ) ) { - // 4.0 - $old = $wpdb->esc_like( $old ); - } else { - // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- called in WordPress 3.9 or less. - $old = like_escape( esc_sql( $old ) ); - } - - return $old; + return $wpdb->esc_like( $old ); } } diff --git a/src/Term_Command.php b/src/Term_Command.php index 0cb7dd891..26ed010ba 100644 --- a/src/Term_Command.php +++ b/src/Term_Command.php @@ -223,11 +223,6 @@ public function create( $args, $assoc_args ) { $porcelain = Utils\get_flag_value( $assoc_args, 'porcelain' ); unset( $assoc_args['porcelain'] ); - // Compatibility for < WP 4.0 - if ( $assoc_args['parent'] > 0 && ! term_exists( (int) $assoc_args['parent'] ) ) { - WP_CLI::error( 'Parent term does not exist.' ); - } - $assoc_args = wp_slash( $assoc_args ); $term = wp_slash( $term ); $result = wp_insert_term( $term, $taxonomy, $assoc_args ); @@ -537,10 +532,13 @@ public function generate( $args, $assoc_args ) { WP_CLI::error( "'{$taxonomy}' is not a registered taxonomy." ); } - $label = get_taxonomy( $taxonomy )->labels->singular_name; - $slug = sanitize_title_with_dashes( $label ); + /** + * @var \WP_Taxonomy $tax + */ + $tax = get_taxonomy( $taxonomy ); - $hierarchical = get_taxonomy( $taxonomy )->hierarchical; + $label = $tax->labels->singular_name; + $slug = sanitize_title_with_dashes( $label ); $format = Utils\get_flag_value( $assoc_args, 'format', 'progress' ); @@ -560,7 +558,7 @@ public function generate( $args, $assoc_args ) { for ( $index = $max_id + 1; $index <= $max_id + $count; $index++ ) { - if ( $hierarchical ) { + if ( $tax->hierarchical ) { if ( $previous_term_id && $this->maybe_make_child() && $current_depth < $max_depth ) { @@ -645,6 +643,10 @@ public function recount( $args ) { WP_CLI::warning( "Taxonomy {$taxonomy} does not exist." ); } else { + /** + * @var \WP_Term[] $terms + */ + // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found -- Required for backward compatibility. $terms = get_terms( $taxonomy, [ 'hide_empty' => false ] ); $term_taxonomy_ids = wp_list_pluck( $terms, 'term_taxonomy_id' ); @@ -699,7 +701,11 @@ public function migrate( $args, $assoc_args ) { WP_CLI::error( "Taxonomy term '{$term_reference}' for taxonomy '{$original_taxonomy}' doesn't exist." ); } - $original_taxonomy = get_taxonomy( $original_taxonomy ); + $tax = get_taxonomy( $original_taxonomy ); + + if ( ! $tax ) { + WP_CLI::error( "Taxonomy '{$original_taxonomy}' doesn't exist." ); + } $id = wp_insert_term( $term->name, @@ -715,11 +721,14 @@ public function migrate( $args, $assoc_args ) { WP_CLI::error( $id->get_error_message() ); } - $post_ids = get_objects_in_term( $term->term_id, $original_taxonomy->name ); + /** + * @var string[] $post_ids + */ + $post_ids = get_objects_in_term( $term->term_id, $tax->name ); foreach ( $post_ids as $post_id ) { $type = get_post_type( (int) $post_id ); - if ( in_array( $type, $original_taxonomy->object_type, true ) ) { + if ( in_array( $type, $tax->object_type, true ) ) { $term_taxonomy_id = wp_set_object_terms( (int) $post_id, $id['term_id'], $destination_taxonomy, true ); if ( is_wp_error( $term_taxonomy_id ) ) { @@ -736,7 +745,7 @@ public function migrate( $args, $assoc_args ) { WP_CLI::log( "Term '{$term->slug}' migrated." ); - $del = wp_delete_term( $term->term_id, $original_taxonomy->name ); + $del = wp_delete_term( $term->term_id, $tax->name ); if ( is_wp_error( $del ) ) { WP_CLI::error( "Failed to delete the term '{$term->slug}'. Reason: " . $del->get_error_message() ); @@ -745,7 +754,7 @@ public function migrate( $args, $assoc_args ) { WP_CLI::log( "Old instance of term '{$term->slug}' removed from its original taxonomy." ); $post_count = count( $post_ids ); $post_plural = Utils\pluralize( 'post', $post_count ); - WP_CLI::success( "Migrated the term '{$term->slug}' from taxonomy '{$original_taxonomy->name}' to taxonomy '{$destination_taxonomy}' for {$post_count} {$post_plural}." ); + WP_CLI::success( "Migrated the term '{$term->slug}' from taxonomy '{$tax->name}' to taxonomy '{$destination_taxonomy}' for {$post_count} {$post_plural}." ); } private function maybe_make_child() { diff --git a/src/Term_Meta_Command.php b/src/Term_Meta_Command.php index b95c98292..29d2ed240 100644 --- a/src/Term_Meta_Command.php +++ b/src/Term_Meta_Command.php @@ -33,7 +33,7 @@ class Term_Meta_Command extends CommandWithMeta { */ protected function check_object_id( $object_id ) { $term = get_term( $object_id ); - if ( ! $term ) { + if ( ! $term || is_wp_error( $term ) ) { WP_CLI::error( "Could not find the term with ID {$object_id}." ); } return $term->term_id; @@ -52,7 +52,7 @@ protected function check_object_id( $object_id ) { * value for the specified metadata key, no change * will be made. * - * @return int|false The meta ID on success, false on failure. + * @return int|false|WP_Error The meta ID on success, false on failure, WP_Error when term_id is ambiguous between taxonomies. */ protected function add_metadata( $object_id, $meta_key, $meta_value, $unique = false ) { return add_term_meta( $object_id, $meta_key, $meta_value, $unique ); @@ -69,8 +69,8 @@ protected function add_metadata( $object_id, $meta_key, $meta_value, $unique = f * metadata entries with the specified value. * Otherwise, update all entries. * - * @return int|bool Meta ID if the key didn't exist, true on successful - * update, false on failure. + * @return int|bool|WP_Error Meta ID if the key didn't exist, true on successful + * update, false on failure, WP_Error when term_id is ambiguous between taxonomies. */ protected function update_metadata( $object_id, $meta_key, $meta_value, $prev_value = '' ) { return update_term_meta( $object_id, $meta_key, $meta_value, $prev_value ); diff --git a/src/User_Command.php b/src/User_Command.php index da42810bd..1c9454877 100644 --- a/src/User_Command.php +++ b/src/User_Command.php @@ -31,6 +31,8 @@ * Success: Removed user 123 from http://example.com. * * @package wp-cli + * + * @phpstan-import-type UserSite from Site_Command */ class User_Command extends CommandWithDBObject { @@ -645,7 +647,7 @@ public function generate( $args, $assoc_args ) { ] ); - if ( false === $role ) { + if ( false === $role && ! is_wp_error( $user_id ) ) { delete_user_option( $user_id, 'capabilities' ); delete_user_option( $user_id, 'user_level' ); } @@ -653,7 +655,10 @@ public function generate( $args, $assoc_args ) { if ( 'progress' === $format ) { $notify->tick(); } elseif ( 'ids' === $format ) { - echo $user_id; + if ( ! is_wp_error( $user_id ) ) { + echo $user_id; + } + if ( $index < $limit - 1 ) { echo ' '; } @@ -1164,6 +1169,11 @@ public function import_csv( $args, $assoc_args ) { WP_CLI::log( "{$existing_user->user_login} added as {$new_user['role']}." ); } + if ( is_wp_error( $user_id ) ) { + WP_CLI::warning( $user_id ); + continue; + } + // Create the user } else { unset( $new_user['ID'] ); // Unset else it will just return the ID @@ -1189,22 +1199,24 @@ public function import_csv( $args, $assoc_args ) { $user_id = wp_insert_user( $new_user ); } + if ( is_wp_error( $user_id ) ) { + WP_CLI::warning( $user_id ); + continue; + } + if ( Utils\get_flag_value( $assoc_args, 'send-email' ) ) { self::wp_new_user_notification( $user_id, $new_user['user_pass'] ); } } - if ( is_wp_error( $user_id ) ) { - WP_CLI::warning( $user_id ); - continue; - - } - if ( false === $new_user['role'] ) { delete_user_option( $user_id, 'capabilities' ); delete_user_option( $user_id, 'user_level' ); } + /** + * @var \WP_User $user + */ $user = get_user_by( 'id', $user_id ); foreach ( $secondary_roles as $secondary_role ) { $user->add_role( $secondary_role ); @@ -1332,7 +1344,7 @@ private static function validate_role( $role, $warn_user_only = false ) { * @param mixed $password */ public static function wp_new_user_notification( $user_id, $password ) { - wp_new_user_notification( $user_id, null, 'both' ); + wp_new_user_notification( (int) $user_id, null, 'both' ); } /** @@ -1414,6 +1426,10 @@ private function update_msuser_status( $user_ids, $pref, $value ) { } // Make that user's blog as spam too. + + /** + * @phpstan-var UserSite[] $blogs + */ $blogs = (array) get_blogs_of_user( $user_id, true ); foreach ( $blogs as $details ) { // Only mark site as spam if not main site. diff --git a/src/WP_CLI/CommandWithTerms.php b/src/WP_CLI/CommandWithTerms.php index 1fa4562f1..1294fa38d 100644 --- a/src/WP_CLI/CommandWithTerms.php +++ b/src/WP_CLI/CommandWithTerms.php @@ -103,6 +103,9 @@ public function list_( $args, $assoc_args ) { $taxonomy_args['fields'] = 'ids'; } + /** + * @var \WP_Term[] $items + */ $items = wp_get_object_terms( $object_id, $taxonomy_names, $taxonomy_args ); $formatter = $this->get_formatter( $assoc_args ); From cd773aec5d0b29fffb021b44185509f86d292c29 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 May 2025 16:04:02 +0200 Subject: [PATCH 4/6] Almost level 8 --- src/Comment_Command.php | 3 +++ src/Menu_Item_Command.php | 4 ++++ src/Post_Command.php | 11 +++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Comment_Command.php b/src/Comment_Command.php index 2b1ed57c2..1422c9444 100644 --- a/src/Comment_Command.php +++ b/src/Comment_Command.php @@ -673,6 +673,9 @@ public function count( $args, $assoc_args ) { public function recount( $args ) { foreach ( $args as $id ) { if ( wp_update_comment_count( $id ) ) { + /** + * @var \WP_Post $post + */ $post = get_post( $id ); WP_CLI::log( "Updated post {$post->ID} comment count to {$post->comment_count}." ); } else { diff --git a/src/Menu_Item_Command.php b/src/Menu_Item_Command.php index 606c9dee8..418d3fa45 100644 --- a/src/Menu_Item_Command.php +++ b/src/Menu_Item_Command.php @@ -423,6 +423,10 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) { $menu_item_obj = get_post( $menu_item_db_id ); + if ( ! $menu_item_obj ) { + WP_CLI::error( 'Invalid menu.' ); + } + /** * @var object{title: string, url: string, description: string, object: string, object_id: int, menu_item_parent: int, attr_title: string, target: string, classes: string[], xfn: string, post_status: string, menu_order: int} $menu_item_obj */ diff --git a/src/Post_Command.php b/src/Post_Command.php index 534687113..a9c1a60e8 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -806,11 +806,14 @@ public function generate( $args, $assoc_args ) { // Get the total number of posts. $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = %s", $post_data['post_type'] ) ); + /** + * @var \WP_Post_Type $post_type + */ + $post_type = get_post_type_object( $post_data['post_type'] ); + $label = ! empty( $post_data['post_title'] ) ? $post_data['post_title'] - : get_post_type_object( $post_data['post_type'] )->labels->singular_name; - - $hierarchical = get_post_type_object( $post_data['post_type'] )->hierarchical; + : $post_type->labels->singular_name; $limit = $post_data['count'] + $total; @@ -827,7 +830,7 @@ public function generate( $args, $assoc_args ) { for ( $index = $total; $index < $limit; $index++ ) { - if ( $hierarchical ) { + if ( $post_type->hierarchical ) { if ( $this->maybe_make_child() && $current_depth < $post_data['max_depth'] ) { From 97853555ee94f2ea05d13a05913d547dba077562 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 19 May 2025 16:39:38 +0200 Subject: [PATCH 5/6] Almost level 9 --- src/Comment_Command.php | 2 +- src/Option_Command.php | 4 +++ src/Signup_Command.php | 7 +++-- src/Site_Command.php | 13 ++++++++- src/Term_Command.php | 32 +++++++++++++++++++---- src/User_Application_Password_Command.php | 9 ++++++- src/User_Command.php | 27 ++++++++++++++++--- src/User_Session_Command.php | 6 ++++- src/WP_CLI/CommandWithMeta.php | 6 +++-- src/WP_CLI/CommandWithTerms.php | 18 +++++++++++-- 10 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/Comment_Command.php b/src/Comment_Command.php index 1422c9444..c11dc1d04 100644 --- a/src/Comment_Command.php +++ b/src/Comment_Command.php @@ -435,7 +435,7 @@ public function delete( $args, $assoc_args ) { $args, $assoc_args, function ( $comment_id, $assoc_args ) { - $force = Utils\get_flag_value( $assoc_args, 'force' ); + $force = (bool) Utils\get_flag_value( $assoc_args, 'force' ); $status = wp_get_comment_status( $comment_id ); $result = wp_delete_comment( $comment_id, $force ); diff --git a/src/Option_Command.php b/src/Option_Command.php index 823adaac7..d9f3a60e1 100644 --- a/src/Option_Command.php +++ b/src/Option_Command.php @@ -423,7 +423,11 @@ public function update( $args, $assoc_args ) { $autoload = null; } + /** + * @var string $value + */ $value = sanitize_option( $key, $value ); + // Sanitization WordPress normally performs when getting an option if ( in_array( $key, [ 'siteurl', 'home', 'category_base', 'tag_base' ], true ) ) { $value = untrailingslashit( $value ); diff --git a/src/Signup_Command.php b/src/Signup_Command.php index 33431e2a4..4125ceea8 100644 --- a/src/Signup_Command.php +++ b/src/Signup_Command.php @@ -130,9 +130,12 @@ public function list_( $args, $assoc_args ) { $signups = array(); - $per_page = (int) Utils\get_flag_value( $assoc_args, 'per_page' ); + /** + * @var string|null $per_page + */ + $per_page = Utils\get_flag_value( $assoc_args, 'per_page' ); - $limit = $per_page ? $wpdb->prepare( 'LIMIT %d', $per_page ) : ''; + $limit = $per_page ? $wpdb->prepare( 'LIMIT %d', (int) $per_page ) : ''; $query = "SELECT * FROM $wpdb->signups {$limit}"; diff --git a/src/Site_Command.php b/src/Site_Command.php index ed0d8e654..132e080c1 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -283,6 +283,10 @@ public function empty_( $args, $assoc_args ) { $files_to_unlink = []; $directories_to_delete = []; $is_main_site = is_main_site(); + + /** + * @var \SplFileInfo $fileinfo + */ foreach ( $files as $fileinfo ) { $realpath = $fileinfo->getRealPath(); // Don't clobber subsites when operating on the main site @@ -405,7 +409,11 @@ public function create( $args, $assoc_args ) { global $wpdb, $current_site; - $base = $assoc_args['slug']; + $base = $assoc_args['slug']; + + /** + * @var string $title + */ $title = Utils\get_flag_value( $assoc_args, 'title', ucfirst( $base ) ); $email = empty( $assoc_args['email'] ) ? '' : $assoc_args['email']; @@ -1203,6 +1211,9 @@ private function update_site_status( $ids, $pref, $value ) { * @throws ExitException */ private function get_sites_ids( $args, $assoc_args ) { + /** + * @var string|false $slug + */ $slug = Utils\get_flag_value( $assoc_args, 'slug', false ); if ( $slug ) { diff --git a/src/Term_Command.php b/src/Term_Command.php index 26ed010ba..4ae0aeaf7 100644 --- a/src/Term_Command.php +++ b/src/Term_Command.php @@ -287,7 +287,12 @@ public function get( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; - $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); + /** + * @var string $field + */ + $field = Utils\get_flag_value( $assoc_args, 'by' ); + + $term = get_term_by( $field, $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); @@ -372,7 +377,12 @@ public function update( $args, $assoc_args ) { $assoc_args = wp_slash( $assoc_args ); - $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); + /** + * @var string $field + */ + $field = Utils\get_flag_value( $assoc_args, 'by' ); + + $term = get_term_by( $field, $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); @@ -691,11 +701,23 @@ public function recount( $args ) { * Success: Migrated the term 'video' from taxonomy 'category' to taxonomy 'post_tag' for 1 post. */ public function migrate( $args, $assoc_args ) { - $term_reference = $args[0]; - $original_taxonomy = Utils\get_flag_value( $assoc_args, 'from' ); + $term_reference = $args[0]; + + /** + * @var string $original_taxonomy + */ + $original_taxonomy = Utils\get_flag_value( $assoc_args, 'from' ); + /** + * @var string $destination_taxonomy + */ $destination_taxonomy = Utils\get_flag_value( $assoc_args, 'to' ); - $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term_reference, $original_taxonomy ); + /** + * @var string $field + */ + $field = Utils\get_flag_value( $assoc_args, 'by' ); + + $term = get_term_by( $field, $term_reference, $original_taxonomy ); if ( ! $term ) { WP_CLI::error( "Taxonomy term '{$term_reference}' for taxonomy '{$original_taxonomy}' doesn't exist." ); diff --git a/src/User_Application_Password_Command.php b/src/User_Application_Password_Command.php index edd6c4f82..0dffef714 100644 --- a/src/User_Application_Password_Command.php +++ b/src/User_Application_Password_Command.php @@ -315,8 +315,15 @@ public function get( $args, $assoc_args ) { public function create( $args, $assoc_args ) { $args = $this->replace_login_with_user_id( $args ); + /** + * @var string $user_id + * @var string $app_name + */ list( $user_id, $app_name ) = $args; + /** + * @var string $app_id + */ $app_id = Utils\get_flag_value( $assoc_args, 'app-id', '' ); $arguments = [ @@ -324,7 +331,7 @@ public function create( $args, $assoc_args ) { 'app_id' => $app_id, ]; - $result = WP_Application_Passwords::create_new_application_password( $user_id, $arguments ); + $result = WP_Application_Passwords::create_new_application_password( (int) $user_id, $arguments ); if ( $result instanceof WP_Error ) { WP_CLI::error( $result ); diff --git a/src/User_Command.php b/src/User_Command.php index 1c9454877..ae6a9e80b 100644 --- a/src/User_Command.php +++ b/src/User_Command.php @@ -283,8 +283,13 @@ public function get( $args, $assoc_args ) { * $ wp user delete $(wp user list --role=contributor --field=ID | head -n 100) */ public function delete( $args, $assoc_args ) { - $network = Utils\get_flag_value( $assoc_args, 'network' ) && is_multisite(); + $network = Utils\get_flag_value( $assoc_args, 'network' ) && is_multisite(); + + /** + * @var string|null $reassign + */ $reassign = Utils\get_flag_value( $assoc_args, 'reassign' ); + $reassign = (int) $reassign; if ( $network && $reassign ) { WP_CLI::error( 'Reassigning content to a different user is not supported on multisite.' ); @@ -982,7 +987,9 @@ public function list_caps( $args, $assoc_args ) { } break; case 'user': - // Get the user's capabilities + /** + * @var array $user_capabilities + */ $user_capabilities = get_user_meta( $user->ID, 'wp_capabilities', true ); // Loop through each capability and only return the non-inherited ones @@ -993,7 +1000,9 @@ public function list_caps( $args, $assoc_args ) { } break; case 'role': - // Get the user's capabilities + /** + * @var array $user_capabilities + */ $user_capabilities = get_user_meta( $user->ID, 'wp_capabilities', true ); // Loop through each capability and only return the inherited ones (including the role name) @@ -1092,6 +1101,10 @@ public function import_csv( $args, $assoc_args ) { $file_object->setFlags( SplFileObject::READ_CSV ); $csv_data = []; $indexes = []; + + /** + * @var string[] $line + */ foreach ( $file_object as $line ) { if ( empty( $line[0] ) ) { continue; @@ -1102,6 +1115,9 @@ public function import_csv( $args, $assoc_args ) { continue; } + /** + * @var array $data + */ $data = []; foreach ( $indexes as $n => $key ) { @@ -1114,6 +1130,9 @@ public function import_csv( $args, $assoc_args ) { $csv_data = new CsvIterator( $filename ); } + /** + * @var array{ID: string, role: string, roles: string, user_pass: string, user_registered: string, display_name: string|false, user_login: string, user_email: string} $new_user + */ foreach ( $csv_data as $new_user ) { $defaults = [ 'role' => get_option( 'default_role' ), @@ -1281,7 +1300,7 @@ public function import_csv( $args, $assoc_args ) { */ public function reset_password( $args, $assoc_args ) { $porcelain = Utils\get_flag_value( $assoc_args, 'porcelain' ); - $skip_email = Utils\get_flag_value( $assoc_args, 'skip-email' ); + $skip_email = (bool) Utils\get_flag_value( $assoc_args, 'skip-email' ); $show_new_pass = Utils\get_flag_value( $assoc_args, 'show-password' ); if ( $skip_email ) { diff --git a/src/User_Session_Command.php b/src/User_Session_Command.php index b70d4805f..3949d6f91 100644 --- a/src/User_Session_Command.php +++ b/src/User_Session_Command.php @@ -72,7 +72,7 @@ public function __construct() { public function destroy( $args, $assoc_args ) { $user = $this->fetcher->get_check( $args[0] ); $token = $args[1] ?? null; - $all = Utils\get_flag_value( $assoc_args, 'all', false ); + $all = (bool) Utils\get_flag_value( $assoc_args, 'all', false ); $manager = WP_Session_Tokens::get_instance( $user->ID ); if ( $token && $all ) { @@ -174,6 +174,10 @@ protected function get_all_sessions( WP_Session_Tokens $manager ) { // Make the private session data accessible to WP-CLI $get_sessions = new ReflectionMethod( $manager, 'get_sessions' ); $get_sessions->setAccessible( true ); + + /** + * @var array $sessions + */ $sessions = $get_sessions->invoke( $manager ); array_walk( diff --git a/src/WP_CLI/CommandWithMeta.php b/src/WP_CLI/CommandWithMeta.php index b1f44bd1d..23cc5baf9 100644 --- a/src/WP_CLI/CommandWithMeta.php +++ b/src/WP_CLI/CommandWithMeta.php @@ -91,7 +91,7 @@ public function list_( $args, $assoc_args ) { foreach ( $values as $item_value ) { if ( Utils\get_flag_value( $assoc_args, 'unserialize' ) ) { - $item_value = maybe_unserialize( $item_value ); + $item_value = maybe_unserialize( (string) $item_value ); } $items[] = (object) [ @@ -159,7 +159,7 @@ public function get( $args, $assoc_args ) { list( $object_id, $meta_key ) = $args; $object_id = $this->check_object_id( $object_id ); - $single = Utils\get_flag_value( $assoc_args, 'single', true ); + $single = (bool) Utils\get_flag_value( $assoc_args, 'single', true ); $value = $this->get_metadata( $object_id, $meta_key, $single ); @@ -504,6 +504,8 @@ protected function update_metadata( $object_id, $meta_key, $meta_value, $prev_va * specified. * * @return mixed Single metadata value, or array of values. + * + * @phpstan-return ($single is true ? string : array) */ protected function get_metadata( $object_id, $meta_key = '', $single = false ) { return get_metadata( $this->meta_type, $object_id, $meta_key, $single ); diff --git a/src/WP_CLI/CommandWithTerms.php b/src/WP_CLI/CommandWithTerms.php index 1294fa38d..0e4f9c4da 100644 --- a/src/WP_CLI/CommandWithTerms.php +++ b/src/WP_CLI/CommandWithTerms.php @@ -148,12 +148,15 @@ public function remove( $args, $assoc_args ) { $this->taxonomy_exists( $taxonomy ); + /** + * @var string|null $field + */ $field = Utils\get_flag_value( $assoc_args, 'by' ); if ( $field ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); } - if ( Utils\get_flag_value( $assoc_args, 'all' ) ) { + if ( (bool) Utils\get_flag_value( $assoc_args, 'all' ) ) { // No need to specify terms while removing all terms. if ( $terms ) { @@ -167,7 +170,12 @@ public function remove( $args, $assoc_args ) { if ( 'category' === $taxonomy ) { // Set default category to post. - $default_category = (int) get_option( 'default_category' ); + + /** + * @var string $default_category + */ + $default_category = get_option( 'default_category' ); + $default_category = (int) $default_category; $default_category = ( ! empty( $default_category ) ) ? $default_category : 1; $default_category = wp_set_object_terms( $object_id, [ $default_category ], $taxonomy, true ); @@ -238,6 +246,9 @@ public function add( $args, $assoc_args ) { $this->taxonomy_exists( $taxonomy ); + /** + * @var string|null $field + */ $field = Utils\get_flag_value( $assoc_args, 'by' ); if ( $field ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); @@ -286,6 +297,9 @@ public function set( $args, $assoc_args ) { $this->taxonomy_exists( $taxonomy ); + /** + * @var string|null $field + */ $field = Utils\get_flag_value( $assoc_args, 'by' ); if ( $field ) { $terms = $this->prepare_terms( $field, $terms, $taxonomy ); From 9799ea834bc2985f5cee07693d68115748a092e9 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 20 May 2025 10:30:48 +0200 Subject: [PATCH 6/6] Remove some pre-4.7 compat code --- src/Term_Command.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Term_Command.php b/src/Term_Command.php index 4ae0aeaf7..489ddf3e0 100644 --- a/src/Term_Command.php +++ b/src/Term_Command.php @@ -138,20 +138,6 @@ public function list_( $args, $assoc_args ) { */ $term = get_term_by( 'id', $assoc_args['term_id'], $args[0] ); $terms = [ $term ]; - } elseif ( ! empty( $assoc_args['include'] ) - && ! empty( $assoc_args['orderby'] ) - && 'include' === $assoc_args['orderby'] - && Utils\wp_version_compare( '4.7', '<' ) ) { - $terms = []; - $term_ids = explode( ',', $assoc_args['include'] ); - foreach ( $term_ids as $term_id ) { - $term = get_term_by( 'id', $term_id, $args[0] ); - if ( $term instanceof \WP_Term ) { - $terms[] = $term; - } else { - WP_CLI::warning( "Invalid term {$term_id}." ); - } - } } else { /** * @var \WP_Term[] $terms