Skip to content
Draft
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
115 changes: 21 additions & 94 deletions features/search-replace.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1203,129 +1203,56 @@ Feature: Do global search/replace
a:1:{i:0;O:10:"CornFlakes":0:{}}
"""

@require-mysql @less-than-php-8.0
Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP < 8.0)
Given a WP install
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"`
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"`

When I try `wp search-replace mysqli_result stdClass`
Then STDERR should contain:
"""
Warning: WP_CLI\SearchReplacer::run_recursively(): Couldn't fetch mysqli_result
"""
And STDOUT should contain:
"""
Success: Made 1 replacement.
"""
@require-mysql
Scenario: The search_replace_unserialize_options hook allows overriding allowed_classes for unserialize

When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names`
Then STDOUT should contain:
"""
O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;}
"""
And save STDOUT as {SERIALIZED_RESULT}
And a test_php.php file:
Given a WP install
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:7:\"MyClass\":1:{s:3:\"foo\";s:13:\"cereal_marker\";}')"`
And a hook.php file:
"""
<?php print_r(unserialize('{SERIALIZED_RESULT}'));
<?php
class MyClass {
public $foo = '';
}
WP_CLI::add_hook( 'search_replace_unserialize_options', function() {
return [ 'allowed_classes' => [ 'stdClass', 'MyClass' ] ];
} );
"""

When I try `wp eval-file test_php.php`
Then STDOUT should contain:
"""
stdClass Object
"""
And STDOUT should contain:
When I try `wp search-replace cereal_marker cereal_replaced`
Then STDERR should contain:
"""
[current_field] => 1
Warning: Skipping an uninitialized class "MyClass", replacements might not be complete.
"""
And STDOUT should contain:
"""
[field_count] => 2
Success: Made 0 replacements.
"""

@require-mysql @require-php-8.0 @less-than-php-8.1
Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP 8.0)
Given a WP install
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"`
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"`

When I try `wp search-replace mysqli_result stdClass`
Then STDERR should contain:
"""
Warning: Skipping an inconvertible serialized object of type "mysqli_result", replacements might not be complete. Reason: mysqli_result object is already closed.
"""
When I run `wp --require=hook.php search-replace cereal_marker cereal_replaced`
Then STDERR should be empty
And STDOUT should contain:
"""
Success: Made 1 replacement.
"""

When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names`
Then STDOUT should contain:
"""
O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;}
"""
And save STDOUT as {SERIALIZED_RESULT}
And a test_php.php file:
"""
<?php print_r(unserialize('{SERIALIZED_RESULT}'));
"""

When I try `wp eval-file test_php.php`
Then STDOUT should contain:
"""
stdClass Object
"""
And STDOUT should contain:
"""
[current_field] => 1
"""
And STDOUT should contain:
"""
[field_count] => 2
"""
@require-mysql
Scenario: Warn and ignore type-hinted objects that have some error in deserialization

@require-mysql @require-php-8.1
Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP 8.1+)
Given a WP install
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation','O:13:\"mysqli_result\":5:{s:13:\"current_field\";N;s:11:\"field_count\";N;s:7:\"lengths\";N;s:8:\"num_rows\";N;s:4:\"type\";N;}')"`
And I run `wp db query "INSERT INTO wp_options (option_name,option_value) VALUES ('cereal_isation_2','O:8:\"mysqli_result\":5:{s:13:\"current_field\";i:1;s:11:\"field_count\";i:2;s:7:\"lengths\";a:1:{i:0;s:4:\"blah\";}s:8:\"num_rows\";i:1;s:4:\"type\";i:2;}')"`

When I try `wp search-replace mysqli_result stdClass`
Then STDERR should contain:
"""
Warning: Skipping an inconvertible serialized object: "O:13:"mysqli_result":5:{s:13:"current_field";N;s:11:"field_count";N;s:7:"lengths";N;s:8:"num_rows";N;s:4:"type";N;}", replacements might not be complete. Reason: Cannot assign null to property mysqli_result::$current_field of type int.
Warning: Skipping an uninitialized class "mysqli_result", replacements might not be complete.
"""
And STDOUT should contain:
"""
Success: Made 1 replacement.
"""

When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'" --skip-column-names`
Then STDOUT should contain:
"""
O:8:"stdClass":5:{s:13:"current_field";i:1;s:11:"field_count";i:2;s:7:"lengths";a:1:{i:0;s:4:"blah";}s:8:"num_rows";i:1;s:4:"type";i:2;}
"""
And save STDOUT as {SERIALIZED_RESULT}
And a test_php.php file:
"""
<?php print_r(unserialize('{SERIALIZED_RESULT}'));
"""

When I try `wp eval-file test_php.php`
Then STDOUT should contain:
"""
stdClass Object
"""
And STDOUT should contain:
"""
[current_field] => 1
"""
And STDOUT should contain:
"""
[field_count] => 2
"""

# See https://github.com/wp-cli/search-replace-command/issues/190
Scenario: Regex search/replace with `--regex-limit=1` option
Given a WP install
Expand Down
19 changes: 18 additions & 1 deletion src/WP_CLI/SearchReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ class SearchReplacer {
*/
private $max_recursion;

/**
* @var array<string, mixed>
*/
private $unserialize_options;

/**
* @param string $from String we're looking to replace.
* @param string $to What we want it to be replaced with.
Expand Down Expand Up @@ -94,6 +99,18 @@ public function __construct( $from, $to, $recurse_objects = false, $regex = fals

// Get the XDebug nesting level. Will be zero (no limit) if no value is set
$this->max_recursion = intval( ini_get( 'xdebug.max_nesting_level' ) );

/**
* Filter the options passed to unserialize() during search-replace.
*
* Defaults to `[ 'allowed_classes' => [ 'stdClass' ] ]` to allow the
* built-in stdClass (used extensively by WordPress, e.g. theme mods)
* while blocking arbitrary user-defined class instantiation. Use this
* hook to allow additional classes when needed.
*
* @param array<string, mixed> $options Options array for unserialize().
*/
$this->unserialize_options = \WP_CLI::do_hook( 'search_replace_unserialize_options', [ 'allowed_classes' => [ 'stdClass' ] ] );
}

/**
Expand Down Expand Up @@ -141,7 +158,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis
// reporting of notices and warnings as well.
$error_reporting = error_reporting();
error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING );
$unserialized = is_string( $data ) ? @unserialize( $data ) : false;
$unserialized = is_string( $data ) ? @unserialize( $data, $this->unserialize_options ) : false;
error_reporting( $error_reporting );

} catch ( \TypeError $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.typeerrorFound
Expand Down