Skip to content
Open
50 changes: 50 additions & 0 deletions features/comment.feature
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,53 @@ Feature: Manage WordPress comments
And I run `wp comment unspam {COMMENT_ID} --url=www.example.com`
And I run `wp comment trash {COMMENT_ID} --url=www.example.com`
And I run `wp comment untrash {COMMENT_ID} --url=www.example.com`

Scenario: Delete comments using ID ranges
Given a WP install

When I run `wp comment create --comment_post_ID=1 --comment_content='Comment A' --comment_author='A' --porcelain`
Then STDOUT should be a number
And save STDOUT as {COMMENT_ID_1}

When I run `wp comment create --comment_post_ID=1 --comment_content='Comment B' --comment_author='B' --porcelain`
Then STDOUT should be a number
And save STDOUT as {COMMENT_ID_2}

When I run `wp comment create --comment_post_ID=1 --comment_content='Comment C' --comment_author='C' --porcelain`
Then STDOUT should be a number
And save STDOUT as {COMMENT_ID_3}

When I run `wp comment delete {COMMENT_ID_1}-{COMMENT_ID_3} --force`
Then STDOUT should contain:
"""
Deleted comment {COMMENT_ID_1}.
"""
And STDOUT should contain:
"""
Deleted comment {COMMENT_ID_2}.
"""
And STDOUT should contain:
"""
Deleted comment {COMMENT_ID_3}.
"""

Scenario: Trash comments using ID ranges
Given a WP install

When I run `wp comment create --comment_post_ID=1 --comment_content='Comment D' --comment_author='D' --porcelain`
Then STDOUT should be a number
And save STDOUT as {COMMENT_ID_1}

When I run `wp comment create --comment_post_ID=1 --comment_content='Comment E' --comment_author='E' --porcelain`
Then STDOUT should be a number
And save STDOUT as {COMMENT_ID_2}

When I run `wp comment trash {COMMENT_ID_1}-{COMMENT_ID_2}`
Then STDOUT should contain:
"""
Trashed comment {COMMENT_ID_1}.
"""
And STDOUT should contain:
"""
Trashed comment {COMMENT_ID_2}.
"""
58 changes: 58 additions & 0 deletions features/post.feature
Original file line number Diff line number Diff line change
Expand Up @@ -602,3 +602,61 @@ Feature: Manage WordPress posts
"""
{"block_version":1}
"""

Scenario: Delete posts using ID ranges
Given a WP install

When I run `wp post create --post_title='Post A' --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_1}

When I run `wp post create --post_title='Post B' --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_2}

When I run `wp post create --post_title='Post C' --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_3}

When I run `wp post delete {POST_ID_1}-{POST_ID_3} --force`
Then STDOUT should contain:
"""
Deleted post {POST_ID_1}.
"""
And STDOUT should contain:
"""
Deleted post {POST_ID_2}.
"""
And STDOUT should contain:
"""
Deleted post {POST_ID_3}.
"""

Scenario: Update posts using ID ranges
Given a WP install

When I run `wp post create --post_title='Post A' --post_status=draft --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_1}

When I run `wp post create --post_title='Post B' --post_status=draft --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_2}

When I run `wp post create --post_title='Post C' --post_status=draft --porcelain`
Then STDOUT should be a number
And save STDOUT as {POST_ID_3}

When I run `wp post update {POST_ID_1}-{POST_ID_3} --post_status=publish`
Then STDOUT should contain:
"""
Updated post {POST_ID_1}.
"""
And STDOUT should contain:
"""
Updated post {POST_ID_2}.
"""
And STDOUT should contain:
"""
Updated post {POST_ID_3}.
"""
23 changes: 23 additions & 0 deletions features/site.feature
Original file line number Diff line number Diff line change
Expand Up @@ -897,3 +897,26 @@ Feature: Manage sites in a multisite installation
"""
1
"""

Scenario: Archive sites using ID ranges
Given a WP multisite install
And I run `wp site create --slug=rangesite1 --porcelain`
And save STDOUT as {SITE_ID_1}
And I run `wp site create --slug=rangesite2 --porcelain`
And save STDOUT as {SITE_ID_2}
And I run `wp site create --slug=rangesite3 --porcelain`
And save STDOUT as {SITE_ID_3}

When I run `wp site archive {SITE_ID_1}-{SITE_ID_3}`
Then STDOUT should contain:
"""
Success: Site {SITE_ID_1} archived.
"""
And STDOUT should contain:
"""
Success: Site {SITE_ID_2} archived.
"""
And STDOUT should contain:
"""
Success: Site {SITE_ID_3} archived.
"""
29 changes: 29 additions & 0 deletions features/term.feature
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,32 @@ Feature: Manage WordPress terms
"term_group":0
}]
"""

Scenario: Delete terms using ID ranges
Given a WP install

When I run `wp term create category 'Range Term A' --porcelain`
Then STDOUT should be a number
And save STDOUT as {TERM_ID_1}

When I run `wp term create category 'Range Term B' --porcelain`
Then STDOUT should be a number
And save STDOUT as {TERM_ID_2}

When I run `wp term create category 'Range Term C' --porcelain`
Then STDOUT should be a number
And save STDOUT as {TERM_ID_3}

When I run `wp term delete category {TERM_ID_1}-{TERM_ID_3}`
Then STDOUT should contain:
"""
Deleted category {TERM_ID_1}.
"""
And STDOUT should contain:
"""
Deleted category {TERM_ID_2}.
"""
And STDOUT should contain:
"""
Deleted category {TERM_ID_3}.
"""
54 changes: 54 additions & 0 deletions features/user.feature
Original file line number Diff line number Diff line change
Expand Up @@ -811,3 +811,57 @@ Feature: Manage WordPress users
"""
newtestuser
"""

Scenario: Delete users using ID ranges
Given a WP install

When I run `wp user create testrange1 testrange1@example.com --role=subscriber --porcelain`
Then STDOUT should be a number
And save STDOUT as {USER_ID_1}

When I run `wp user create testrange2 testrange2@example.com --role=subscriber --porcelain`
Then STDOUT should be a number
And save STDOUT as {USER_ID_2}

When I run `wp user create testrange3 testrange3@example.com --role=subscriber --porcelain`
Then STDOUT should be a number
And save STDOUT as {USER_ID_3}

When I run `wp user delete {USER_ID_1}-{USER_ID_3} --yes`
Then STDOUT should contain:
"""
Removed user {USER_ID_1}
"""
And STDOUT should contain:
"""
Removed user {USER_ID_2}
"""
And STDOUT should contain:
"""
Removed user {USER_ID_3}
"""

Scenario: Reset passwords using ID ranges
Given a WP install

When I run `wp user create resetrange1 resetrange1@example.com --role=subscriber --porcelain`
Then STDOUT should be a number
And save STDOUT as {USER_ID_1}

When I run `wp user create resetrange2 resetrange2@example.com --role=subscriber --porcelain`
Then STDOUT should be a number
And save STDOUT as {USER_ID_2}

When I run `wp user reset-password {USER_ID_1}-{USER_ID_2} --skip-email`
Then STDOUT should contain:
"""
Reset password for resetrange1.
"""
And STDOUT should contain:
"""
Reset password for resetrange2.
"""
And STDOUT should contain:
"""
Passwords reset for 2 users.
"""
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<rule ref="WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound">
<exclude-pattern>*/src/WP_CLI/Fetchers/(Comment|Post|Signup|Site|User)\.php$</exclude-pattern>
<exclude-pattern>*/src/WP_CLI/CommandWith(DBObject|Meta|Terms)\.php$</exclude-pattern>
<exclude-pattern>*/src/WP_CLI/ExpandsIdRanges\.php$</exclude-pattern>
</rule>

<rule ref="WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound">
Expand Down
31 changes: 31 additions & 0 deletions src/Comment_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ function ( $params ) {
* @param array<string, mixed> $assoc_args Associative arguments.
*/
public function update( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
$assoc_args = wp_slash( $assoc_args );
parent::_update(
$args,
Expand Down Expand Up @@ -486,6 +487,7 @@ function ( $comment ) {
* Success: Deleted comment 2341.
*/
public function delete( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
parent::_delete(
$args,
$assoc_args,
Expand Down Expand Up @@ -556,6 +558,7 @@ private function check_server_name() {
* Success: Trashed comment 1337.
*/
public function trash( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->call( $id, __FUNCTION__, 'Trashed %s.', 'Failed trashing %s.' );
}
Expand All @@ -577,6 +580,7 @@ public function trash( $args, $assoc_args ) {
*/
public function untrash( $args, $assoc_args ) {
$this->check_server_name();
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->call( $id, __FUNCTION__, 'Untrashed %s.', 'Failed untrashing %s.' );
}
Expand All @@ -597,6 +601,7 @@ public function untrash( $args, $assoc_args ) {
* Success: Marked as spam comment 1337.
*/
public function spam( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->call( $id, __FUNCTION__, 'Marked %s as spam.', 'Failed marking %s as spam.' );
}
Expand All @@ -618,6 +623,7 @@ public function spam( $args, $assoc_args ) {
*/
public function unspam( $args, $assoc_args ) {
$this->check_server_name();
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->call( $id, __FUNCTION__, 'Unspammed %s.', 'Failed unspamming %s.' );
}
Expand All @@ -639,6 +645,7 @@ public function unspam( $args, $assoc_args ) {
*/
public function approve( $args, $assoc_args ) {
$this->check_server_name();
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->set_status( $id, 'approve', 'Approved' );
}
Expand All @@ -660,6 +667,7 @@ public function approve( $args, $assoc_args ) {
*/
public function unapprove( $args, $assoc_args ) {
$this->check_server_name();
$args = self::expand_id_ranges( $args, [ $this, 'get_comment_ids_in_range' ] );
foreach ( $args as $id ) {
$this->set_status( $id, 'hold', 'Unapproved' );
}
Expand Down Expand Up @@ -786,4 +794,27 @@ public function exists( $args ) {
WP_CLI::success( "Comment with ID {$args[0]} exists." );
}
}

/**
* Returns existing comment IDs within the given range.
*
* @param int $start Start of the ID range (inclusive).
* @param int|null $end End of the ID range (inclusive), or null for no upper bound.
* @return int[] List of existing comment IDs.
*/
protected function get_comment_ids_in_range( int $start, ?int $end ): array {
global $wpdb;

if ( null === $end ) {
return array_map(
'intval',
$wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_ID >= %d ORDER BY comment_ID ASC", $start ) )
);
}

return array_map(
'intval',
$wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM {$wpdb->comments} WHERE comment_ID BETWEEN %d AND %d ORDER BY comment_ID ASC", $start, $end ) )
);
}
Comment on lines +805 to +819
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This method can be made more concise and DRY by building the query dynamically instead of having two separate return paths.

	protected function get_comment_ids_in_range( int $start, ?int $end ): array {
		global $wpdb;

		$query  = "SELECT comment_ID FROM {$wpdb->comments} WHERE ";
		$params = [ $start ];

		if ( null === $end ) {
			$query .= 'comment_ID >= %d';
		} else {
			$query   .= 'comment_ID BETWEEN %d AND %d';
			$params[] = $end;
		}

		$query .= ' ORDER BY comment_ID ASC';

		return array_map(
			'intval',
			$wpdb->get_col( $wpdb->prepare( $query, ...$params ) )
		);
	}

}
25 changes: 25 additions & 0 deletions src/Post_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ function ( $params ) {
* Success: Updated post 456.
*/
public function update( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_post_ids_in_range' ] );

foreach ( $args as $key => $arg ) {
if ( is_numeric( $arg ) ) {
Expand Down Expand Up @@ -561,6 +562,7 @@ public function get( $args, $assoc_args ) {
* Success: Deleted post 1294.
*/
public function delete( $args, $assoc_args ) {
$args = self::expand_id_ranges( $args, [ $this, 'get_post_ids_in_range' ] );
$defaults = [ 'force' => false ];
$assoc_args = array_merge( $defaults, $assoc_args );

Expand Down Expand Up @@ -1241,4 +1243,27 @@ private function maybe_convert_hyphenated_date_format( $date_string ) {
}
return $date_string;
}

/**
* Returns existing post IDs within the given range.
*
* @param int $start Start of the ID range (inclusive).
* @param int|null $end End of the ID range (inclusive), or null for no upper bound.
* @return int[] List of existing post IDs.
*/
protected function get_post_ids_in_range( int $start, ?int $end ): array {
global $wpdb;

if ( null === $end ) {
return array_map(
'intval',
$wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID >= %d ORDER BY ID ASC", $start ) )
);
}

return array_map(
'intval',
$wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE ID BETWEEN %d AND %d ORDER BY ID ASC", $start, $end ) )
);
}
Comment on lines +1254 to +1268
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This method can be made more concise and DRY by building the query dynamically instead of having two separate return paths.

	protected function get_post_ids_in_range( int $start, ?int $end ): array {
		global $wpdb;

		$query  = "SELECT ID FROM {$wpdb->posts} WHERE ";
		$params = [ $start ];

		if ( null === $end ) {
			$query .= 'ID >= %d';
		} else {
			$query   .= 'ID BETWEEN %d AND %d';
			$params[] = $end;
		}

		$query .= ' ORDER BY ID ASC';

		return array_map(
			'intval',
			$wpdb->get_col( $wpdb->prepare( $query, ...$params ) )
		);
	}

}
Loading