Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contr
## Using

~~~
wp search-replace <old> <new> [<table>...] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=<file>]] [--export_insert_size=<rows>] [--skip-tables=<tables>] [--skip-columns=<columns>] [--include-columns=<columns>] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=<regex-flags>] [--regex-delimiter=<regex-delimiter>] [--regex-limit=<regex-limit>] [--format=<format>] [--report] [--report-changed-only] [--log[=<file>]] [--before_context=<num>] [--after_context=<num>]
wp search-replace [<old>] [<new>] [<table>...] [--old=<value>] [--new=<value>] [--dry-run] [--network] [--all-tables-with-prefix] [--all-tables] [--export[=<file>]] [--export_insert_size=<rows>] [--skip-tables=<tables>] [--skip-columns=<columns>] [--include-columns=<columns>] [--precise] [--recurse-objects] [--verbose] [--regex] [--regex-flags=<regex-flags>] [--regex-delimiter=<regex-delimiter>] [--regex-limit=<regex-limit>] [--format=<format>] [--report] [--report-changed-only] [--log[=<file>]] [--before_context=<num>] [--after_context=<num>]
~~~

Searches through all rows in a selection of tables and replaces
Expand All @@ -25,16 +25,24 @@ change primary key values.

**OPTIONS**

<old>
[<old>]
A string to search for within the database.

<new>
[<new>]
Replace instances of the first string with this new string.

[<table>...]
List of database tables to restrict the replacement to. Wildcards are
supported, e.g. `'wp_*options'` or `'wp_post*'`.

[--old=<value>]
An alternative way to specify the search string. Use this when the
search string starts with '--' (e.g., --old='--some-text').

[--new=<value>]
An alternative way to specify the replacement string. Use this when the
replacement string starts with '--' (e.g., --new='--other-text').

[--dry-run]
Run the entire search/replace operation and show report, but don't save
changes to the database.
Expand Down Expand Up @@ -141,6 +149,9 @@ change primary key values.
# Search/replace to a SQL file without transforming the database
$ wp search-replace foo bar --export=database.sql

# Search/replace string containing hyphens
$ wp search-replace --old='--old-string' --new='new-string'

# Use precise mode for complex serialized data
$ wp search-replace 'oldurl.com' 'newurl.com' --precise

Expand Down
142 changes: 142 additions & 0 deletions features/search-replace.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1392,3 +1392,145 @@ Feature: Do global search/replace
"""
Success: Made 0 replacements.
"""

@require-mysql
Scenario: Search/replace strings starting with hyphens using --old and --new flags
Given a WP install
And I run `wp post create --post_title="Test Post" --post_content="This is --old-content and more text" --porcelain`
Then save STDOUT as {POST_ID}

When I run `wp search-replace --old='--old-content' --new='--new-content'`
Then STDOUT should contain:
"""
wp_posts
"""
And the return code should be 0

When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should contain:
"""
--new-content
"""
And STDOUT should not contain:
"""
--old-content
"""

@require-mysql
Scenario: Error when neither positional args nor flags provided
Given a WP install

When I try `wp search-replace`
Then STDERR should contain:
"""
Please provide both <old> and <new> arguments
"""
And STDERR should contain:
"""
--old
"""
And STDERR should contain:
"""
--new
"""
And the return code should be 1

@require-mysql
Scenario: Error when only --old flag provided without --new
Given a WP install

When I try `wp search-replace --old='test-value'`
Then STDERR should contain:
"""
Please provide the <new> argument
"""
And the return code should be 1

@require-mysql
Scenario: Error when only --new flag provided without --old
Given a WP install

When I try `wp search-replace --new='test-value'`
Then STDERR should contain:
"""
Please provide the <old> argument
"""
And the return code should be 1

@require-mysql
Scenario: Error when both flags and positional arguments provided
Given a WP install

When I try `wp search-replace --old='flag-old' --new='flag-new' 'positional-arg'`
Then STDERR should contain:
"""
Cannot use both positional arguments and --old/--new flags
"""
And the return code should be 1

@require-mysql
Scenario: Error when empty string provided via --old flag
Given a WP install

When I try `wp search-replace --old='' --new='replacement'`
Then STDERR should contain:
"""
Please provide the <old> argument
"""
And the return code should be 1

@require-mysql
Scenario: No error when empty string provided via --new flag
Given a WP install

When I try `wp search-replace --old='search' --new=''`
Then STDERR should not contain:
"""
Please provide the <new> argument
"""

@require-mysql
Scenario: Search/replace string starting with single hyphen works with positional args
Given a WP install
And I run `wp post create --post_title="Test Post" --post_content="This is -single-hyphen content" --porcelain`
Then save STDOUT as {POST_ID}

When I run `wp search-replace '-single-hyphen' '-replaced-hyphen'`
Then STDOUT should contain:
"""
wp_posts
"""
And the return code should be 0

When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should contain:
"""
-replaced-hyphen
"""
And STDOUT should not contain:
"""
-single-hyphen
"""

@require-mysql
Scenario: Allow mixing one flag with one positional argument
Given a WP install
And I run `wp post create --post_title="Test Post" --post_content="This is --old-content text" --porcelain`
Then save STDOUT as {POST_ID}

When I run `wp search-replace --old='--old-content' 'new-content'`
Then STDOUT should contain:
"""
wp_posts
"""
And the return code should be 0

When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should contain:
"""
new-content
"""
And STDOUT should not contain:
"""
--old-content
"""
56 changes: 51 additions & 5 deletions src/Search_Replace_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,24 @@ class Search_Replace_Command extends WP_CLI_Command {
*
* ## OPTIONS
*
* <old>
* [<old>]
* : A string to search for within the database.
*
* <new>
* [<new>]
* : Replace instances of the first string with this new string.
*
* [<table>...]
* : List of database tables to restrict the replacement to. Wildcards are
* supported, e.g. `'wp_*options'` or `'wp_post*'`.
*
* [--old=<value>]
* : An alternative way to specify the search string. Use this when the
* search string starts with '--' (e.g., --old='--some-text').
*
* [--new=<value>]
* : An alternative way to specify the replacement string. Use this when the
* replacement string starts with '--' (e.g., --new='--other-text').
*
* [--dry-run]
* : Run the entire search/replace operation and show report, but don't save
* changes to the database.
Expand Down Expand Up @@ -250,6 +258,9 @@ class Search_Replace_Command extends WP_CLI_Command {
* # Search/replace to a SQL file without transforming the database
* $ wp search-replace foo bar --export=database.sql
*
* # Search/replace string containing hyphens
* $ wp search-replace --old='--old-string' --new='new-string'
*
* # Use precise mode for complex serialized data
* $ wp search-replace 'oldurl.com' 'newurl.com' --precise
*
Expand All @@ -262,12 +273,47 @@ class Search_Replace_Command extends WP_CLI_Command {
* fi
*
* @param array<string> $args Positional arguments.
* @param array{'dry-run'?: bool, 'network'?: bool, 'all-tables-with-prefix'?: bool, 'all-tables'?: bool, 'export'?: string, 'export_insert_size'?: string, 'skip-tables'?: string, 'skip-columns'?: string, 'include-columns'?: string, 'precise'?: bool, 'recurse-objects'?: bool, 'verbose'?: bool, 'regex'?: bool, 'regex-flags'?: string, 'regex-delimiter'?: string, 'regex-limit'?: string, 'format': string, 'report'?: bool, 'report-changed-only'?: bool, 'log'?: string, 'before_context'?: string, 'after_context'?: string} $assoc_args Associative arguments.
* @param array{'old'?: string, 'new'?: string, 'dry-run'?: bool, 'network'?: bool, 'all-tables-with-prefix'?: bool, 'all-tables'?: bool, 'export'?: string, 'export_insert_size'?: string, 'skip-tables'?: string, 'skip-columns'?: string, 'include-columns'?: string, 'precise'?: bool, 'recurse-objects'?: bool, 'verbose'?: bool, 'regex'?: bool, 'regex-flags'?: string, 'regex-delimiter'?: string, 'regex-limit'?: string, 'format': string, 'report'?: bool, 'report-changed-only'?: bool, 'log'?: string, 'before_context'?: string, 'after_context'?: string} $assoc_args Associative arguments.
*/
public function __invoke( $args, $assoc_args ) {
global $wpdb;
$old = array_shift( $args );
$new = array_shift( $args );

// Support --old and --new flags as an alternative to positional arguments.
// This allows users to search/replace strings that start with '--'.
$old_flag = Utils\get_flag_value( $assoc_args, 'old' );
$new_flag = Utils\get_flag_value( $assoc_args, 'new' );

// Check if both flags and positional arguments are provided.
$both_flags_provided = null !== $old_flag && null !== $new_flag;
$has_positional_args = ! empty( $args );
if ( $both_flags_provided && $has_positional_args ) {
WP_CLI::error( 'Cannot use both positional arguments and --old/--new flags. Please use one method or the other.' );
}

// Determine old and new values.
$old = null !== $old_flag ? $old_flag : array_shift( $args );
$new = null !== $new_flag ? $new_flag : array_shift( $args );

// Validate that both old and new values are provided and not empty.
if ( null === $old || null === $new || '' === $old ) {
$missing = array();
if ( null === $old || '' === $old ) {
$missing[] = '<old>';
}
// new value is allowed to be empty.
if ( null === $new ) {
$missing[] = '<new>';
}
$error_msg = count( $missing ) === 2
? 'Please provide both <old> and <new> arguments.'
: sprintf( 'Please provide the %s argument.', $missing[0] );

$error_msg .= "\n\nNote: If your search or replacement string starts with '--', use the flag syntax instead:"
. "\n wp search-replace --old='--text' --new='replacement'";

WP_CLI::error( $error_msg );
}

$total = 0;
$report = array();
$this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run', false );
Expand Down