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
231 changes: 231 additions & 0 deletions features/search-replace.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1592,3 +1592,234 @@ Feature: Do global search/replace
"""
Table is read-only
"""

@require-mysql
Scenario: Serialized array keys are left alone by default
Given a WP install
And a setup-recurse-keys.php file:
"""
<?php
update_option( 'recurse_keys_test', array( 'https://old.example.com/key' => 'https://old.example.com/value', 'nested' => array( 'https://old.example.com/inner' => 'plain' ) ) );
"""
And a check-recurse-keys.php file:
"""
<?php
echo json_encode( get_option( 'recurse_keys_test' ), JSON_UNESCAPED_SLASHES );
"""
And I run `wp eval-file setup-recurse-keys.php`

When I run `wp search-replace 'https://old.example.com' 'https://new.example.com'`
Then STDOUT should contain:
"""
Success:
"""

When I run `wp eval-file check-recurse-keys.php`
Then STDOUT should contain:
"""
https://old.example.com/key
"""
And STDOUT should contain:
"""
https://new.example.com/value
"""
And STDOUT should not contain:
"""
https://new.example.com/key
"""

@require-mysql
Scenario: Search/replace serialized array keys with --recurse-keys
Given a WP install
And a setup-recurse-keys.php file:
"""
<?php
update_option( 'recurse_keys_test', array( 'https://old.example.com/key' => 'https://old.example.com/value', 'nested' => array( 'https://old.example.com/inner' => 'plain' ) ) );
"""
And a check-recurse-keys.php file:
"""
<?php
echo json_encode( get_option( 'recurse_keys_test' ), JSON_UNESCAPED_SLASHES );
"""
And I run `wp eval-file setup-recurse-keys.php`

When I run `wp search-replace 'https://old.example.com' 'https://new.example.com' --recurse-keys`
Then STDOUT should contain:
"""
Success:
"""

When I run `wp eval-file check-recurse-keys.php`
Then STDOUT should contain:
"""
https://new.example.com/key
"""
And STDOUT should contain:
"""
https://new.example.com/value
"""
And STDOUT should contain:
"""
https://new.example.com/inner
"""
And STDOUT should not contain:
"""
old.example.com
"""

@require-mysql
Scenario: Search/replace object property names with --recurse-keys
Given a WP install
And a setup-recurse-keys-object.php file:
"""
<?php
update_option( 'recurse_keys_object_test', (object) array( 'https://old.example.com/prop' => 'https://old.example.com/value' ) );
"""
And a check-recurse-keys-object.php file:
"""
<?php
echo json_encode( get_option( 'recurse_keys_object_test' ), JSON_UNESCAPED_SLASHES );
"""
And I run `wp eval-file setup-recurse-keys-object.php`

When I run `wp search-replace 'https://old.example.com' 'https://new.example.com'`
Then STDOUT should contain:
"""
Success:
"""

When I run `wp eval-file check-recurse-keys-object.php`
Then STDOUT should contain:
"""
https://old.example.com/prop
"""
And STDOUT should contain:
"""
https://new.example.com/value
"""

When I run `wp eval-file setup-recurse-keys-object.php`
And I run `wp search-replace 'https://old.example.com' 'https://new.example.com' --recurse-keys`
Then STDOUT should contain:
"""
Success:
"""

When I run `wp eval-file check-recurse-keys-object.php`
Then STDOUT should contain:
"""
https://new.example.com/prop
"""
And STDOUT should not contain:
"""
old.example.com
"""

@require-mysql
Scenario: Refuse to rename colliding array keys with --recurse-keys
Given a WP install
And a setup-recurse-keys-collision.php file:
"""
<?php
update_option( 'recurse_keys_collision_test', array( 'https://old.example.com/key' => 'v1', 'https://new.example.com/key' => 'v2' ), false );
"""
And a check-recurse-keys-collision.php file:
"""
<?php
$value = get_option( 'recurse_keys_collision_test' );
echo json_encode( $value, JSON_UNESCAPED_SLASHES );
"""
And I run `wp eval-file setup-recurse-keys-collision.php`

When I try `wp search-replace 'https://old.example.com' 'https://new.example.com' --recurse-keys`
Then the return code should be 0
And STDOUT should contain:
"""
Success:
"""
And STDERR should contain:
"""
two keys would map onto the same key
"""

When I run `wp eval-file check-recurse-keys-collision.php`
Then STDOUT should contain:
"""
"https://old.example.com/key":"v1"
"""
And STDOUT should contain:
"""
"https://new.example.com/key":"v2"
"""

@require-mysql
Scenario: Refuse to rename colliding object property names with --recurse-keys
Given a WP install
And a setup-recurse-keys-object-collision.php file:
"""
<?php
update_option( 'recurse_keys_object_collision_test', (object) array( 'https://old.example.com/prop' => 'v1', 'https://new.example.com/prop' => 'v2' ), false );
"""
And a check-recurse-keys-object-collision.php file:
"""
<?php
$value = get_option( 'recurse_keys_object_collision_test' );
echo json_encode( $value, JSON_UNESCAPED_SLASHES );
"""
And I run `wp eval-file setup-recurse-keys-object-collision.php`

When I try `wp search-replace 'https://old.example.com' 'https://new.example.com' --recurse-keys`
Then the return code should be 0
And STDOUT should contain:
"""
Success:
"""
And STDERR should contain:
"""
two property names would map onto the same name
"""

When I run `wp eval-file check-recurse-keys-object-collision.php`
Then STDOUT should contain:
"""
"https://old.example.com/prop":"v1"
"""
And STDOUT should contain:
"""
"https://new.example.com/prop":"v2"
"""

@require-mysql
Scenario: Preserve shared references when renaming keys with --recurse-keys
Given a WP install
And a setup-recurse-keys-refs.php file:
"""
<?php
$shared = 'https://old.example.com/shared';
update_option( 'recurse_keys_ref_test', array( 'https://old.example.com/a' => &$shared, 'b' => &$shared ), false );
"""
And a check-recurse-keys-refs.php file:
"""
<?php
$raw = $GLOBALS['wpdb']->get_var( "SELECT option_value FROM {$GLOBALS['wpdb']->options} WHERE option_name = 'recurse_keys_ref_test'" );
$value = get_option( 'recurse_keys_ref_test' );
echo json_encode( $value, JSON_UNESCAPED_SLASHES ), "\n";
echo false !== strpos( $raw, 'R:2;' ) ? 'HASREF' : 'NOREF';
"""
And I run `wp eval-file setup-recurse-keys-refs.php`

When I run `wp search-replace 'https://old.example.com' 'https://new.example.com' --recurse-keys`
Then STDOUT should contain:
"""
Success:
"""

When I run `wp eval-file check-recurse-keys-refs.php`
Then STDOUT should contain:
"""
"https://new.example.com/a":"https://new.example.com/shared"
"""
And STDOUT should contain:
"""
HASREF
"""
16 changes: 13 additions & 3 deletions src/Search_Replace_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class Search_Replace_Command extends WP_CLI_Command {
*/
private $recurse_objects;

/**
* @var bool
*/
private $recurse_keys;

/**
* @var bool
*/
Expand Down Expand Up @@ -215,6 +220,10 @@ class Search_Replace_Command extends WP_CLI_Command {
* : Enable recursing into objects to replace strings. Defaults to true;
* pass --no-recurse-objects to disable.
*
* [--recurse-keys]
* : Enable recursing into array keys and object properties to replace strings.
* Defaults to false; pass --recurse-keys to enable.
*
* [--verbose]
* : Prints rows to the console as they're updated.
*
Expand Down Expand Up @@ -291,7 +300,7 @@ class Search_Replace_Command extends WP_CLI_Command {
* fi
*
* @param array<string> $args Positional 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.
* @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, 'recurse-keys'?: 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;
Expand Down Expand Up @@ -337,6 +346,7 @@ public function __invoke( $args, $assoc_args ) {
$this->dry_run = Utils\get_flag_value( $assoc_args, 'dry-run', false );
$php_only = Utils\get_flag_value( $assoc_args, 'precise', false );
$this->recurse_objects = Utils\get_flag_value( $assoc_args, 'recurse-objects', true );
$this->recurse_keys = Utils\get_flag_value( $assoc_args, 'recurse-keys', false );
$this->verbose = Utils\get_flag_value( $assoc_args, 'verbose', false );
$this->format = Utils\get_flag_value( $assoc_args, 'format' );
$this->regex = Utils\get_flag_value( $assoc_args, 'regex', false );
Expand Down Expand Up @@ -642,7 +652,7 @@ private function php_export_table( $table, $old, $new ) {
'chunk_size' => $chunk_size,
);

$replacer = new SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, false, $this->regex_limit );
$replacer = new SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, false, $this->regex_limit, $this->recurse_keys );
$col_counts = array_fill_keys( $all_columns, 0 );
if ( $this->verbose && 'table' === $this->format ) {
$this->start_time = microtime( true );
Expand Down Expand Up @@ -747,7 +757,7 @@ private function php_handle_col( $col, $primary_keys, $table, $old, $new ) {
global $wpdb;

$count = 0;
$replacer = new SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, null !== $this->log_handle, $this->regex_limit );
$replacer = new SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, null !== $this->log_handle, $this->regex_limit, $this->recurse_keys );

$table_sql = self::esc_sql_ident( $table );
$col_sql = self::esc_sql_ident( $col );
Expand Down
Loading
Loading