From aff9a8f6b726cb27d7ed30121642f6d293ba1a7a Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:49:13 -0600 Subject: [PATCH 01/26] skip deserialization of S&R objects that return TypeErrors --- src/WP_CLI/SearchReplacer.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index f8e314ad..a94d2965 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -83,12 +83,24 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis } } - // The error suppression operator is not enough in some cases, so we disable - // 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; - error_reporting( $error_reporting ); + try { + // The error suppression operator is not enough in some cases, so we disable + // 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; + error_reporting($error_reporting); + } catch (\TypeError $e) { + // catch incompatible deserialized object type conversions between different PHP versions and skip them + \WP_CLI::warning( + sprintf( + 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', + $data + ) + ); + + $unserialized = false; + } if ( false !== $unserialized ) { $data = $this->run_recursively( $unserialized, true, $recursion_level + 1 ); From 4ef93f72c80ac623a558cf5d116eeb98f2c4d856 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:16:13 -0600 Subject: [PATCH 02/26] Update SearchReplacer.php based off PHP CS --- src/WP_CLI/SearchReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index a94d2965..43aa2cd5 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -90,7 +90,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING ); $unserialized = is_string( $data ) ? @unserialize( $data ) : false; error_reporting($error_reporting); - } catch (\TypeError $e) { + } catch ( \TypeError $e ) { // catch incompatible deserialized object type conversions between different PHP versions and skip them \WP_CLI::warning( sprintf( From fde70ec9b67ae795fffceed0c6726bbda86c37c1 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:06:16 -0600 Subject: [PATCH 03/26] Update SearchReplacer.php with changes from PHPCS --- src/WP_CLI/SearchReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 43aa2cd5..f6dd14cb 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -89,7 +89,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis $error_reporting = error_reporting(); error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING ); $unserialized = is_string( $data ) ? @unserialize( $data ) : false; - error_reporting($error_reporting); + error_reporting( $error_reporting ); } catch ( \TypeError $e ) { // catch incompatible deserialized object type conversions between different PHP versions and skip them \WP_CLI::warning( From 2d7ce6703e3e36b6ea35cb4a9419cc128c5b0b8e Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:05:32 -0600 Subject: [PATCH 04/26] adding skip on phpcs incompat as testing shows its safe for PHP5.6 & adding behat tests --- features/search-replace.feature | 22 ++++++++++++++++++++++ src/WP_CLI/SearchReplacer.php | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index afc1684b..9614e4aa 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1106,6 +1106,28 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ + # Regression test for https://github.com/wp-cli/search-replace-command/issues/191 + Scenario: Deserialization for empty, type-hinted objects are handled gracefully + + Given a WP install + And I run `wp option add 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;}'` + + When I try `wp search-replace current_field current_field1` + Then STDERR should contain:s + """ + 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. + """ + And STDOUT should contain: + """ + Success: Made 1 replacement. + """ + + When I run `wp option get cereal_isation` + Then STDOUT should contain: + """ + O:13:"mysqli_result":5:{s:13:"current_field1";N;s:11:"field_count";N;s:7:"lengths";N;s:8:"num_rows";N;s:4:"type";N;} + """ + Scenario: Regex search/replace with `--regex-limit=1` option Given a WP install And I run `wp post create --post_content="I have a pen, I have an apple. Pen, pine-apple, apple-pen."` diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index f6dd14cb..eb9fc1aa 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -90,8 +90,10 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis error_reporting( $error_reporting & ~E_NOTICE & ~E_WARNING ); $unserialized = is_string( $data ) ? @unserialize( $data ) : false; error_reporting( $error_reporting ); - } catch ( \TypeError $e ) { + + } catch ( \TypeError $e ) { // phpcs:ignore // catch incompatible deserialized object type conversions between different PHP versions and skip them + \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', From 9c46168506cbb5748c7ee2df02dde9b0d6b37c1e Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:42:34 -0600 Subject: [PATCH 05/26] fixing small typo in behat scenario --- features/search-replace.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 9614e4aa..a71578af 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1113,7 +1113,7 @@ Feature: Do global search/replace And I run `wp option add 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;}'` When I try `wp search-replace current_field current_field1` - Then STDERR should contain:s + 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. """ From afa1d7b03ddf37a92ee02191f623e600d56bf3d6 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:20:55 -0600 Subject: [PATCH 06/26] removing unncessary comment --- features/search-replace.feature | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index a71578af..3e230902 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1105,8 +1105,7 @@ Feature: Do global search/replace """ a:1:{i:0;O:10:"CornFlakes":0:{}} """ - - # Regression test for https://github.com/wp-cli/search-replace-command/issues/191 + Scenario: Deserialization for empty, type-hinted objects are handled gracefully Given a WP install From 7c373b4df38923f457305ce33f7e4babcd481279 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:22:28 -0600 Subject: [PATCH 07/26] removing unncessary comments --- features/search-replace.feature | 2 +- src/WP_CLI/SearchReplacer.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 3e230902..469c1f44 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1105,7 +1105,7 @@ Feature: Do global search/replace """ a:1:{i:0;O:10:"CornFlakes":0:{}} """ - + Scenario: Deserialization for empty, type-hinted objects are handled gracefully Given a WP install diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index eb9fc1aa..8f3b7a26 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -92,8 +92,6 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis error_reporting( $error_reporting ); } catch ( \TypeError $e ) { // phpcs:ignore - // catch incompatible deserialized object type conversions between different PHP versions and skip them - \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', From 8f338d93ad8603299d74bcece6cccb4e799d165c Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:30:59 -0600 Subject: [PATCH 08/26] Update features/search-replace.feature scenario description Co-authored-by: Daniel Bachhuber --- features/search-replace.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 469c1f44..f01c34fe 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1106,7 +1106,7 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ - Scenario: Deserialization for empty, type-hinted objects are handled gracefully + Scenario: Warn and ignore type-hinted objects that have some error in deserialization Given a WP install And I run `wp option add 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;}'` From f6e1a3e4a4c66146caa023761fa0cdf3607ba991 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:39:13 -0600 Subject: [PATCH 09/26] skip over replacements if TypeError is executed. --- features/search-replace.feature | 8 ++++---- src/WP_CLI/SearchReplacer.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index f01c34fe..028411a0 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1109,7 +1109,7 @@ Feature: Do global search/replace Scenario: Warn and ignore type-hinted objects that have some error in deserialization Given a WP install - And I run `wp option add 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','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;}')"` When I try `wp search-replace current_field current_field1` Then STDERR should contain: @@ -1118,13 +1118,13 @@ Feature: Do global search/replace """ And STDOUT should contain: """ - Success: Made 1 replacement. + Success: Made 0 replacements. """ - When I run `wp option get cereal_isation` + When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation'"` Then STDOUT should contain: """ - O:13:"mysqli_result":5:{s:13:"current_field1";N;s:11:"field_count";N;s:7:"lengths";N;s:8:"num_rows";N;s:4:"type";N;} + 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;} """ Scenario: Regex search/replace with `--regex-limit=1` option diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 8f3b7a26..83fefab7 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -99,7 +99,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); - $unserialized = false; + throw new Exception(); } if ( false !== $unserialized ) { From 57cb6ebc832eaeade2dc3cd8c877305fee17a2f4 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:48:21 -0600 Subject: [PATCH 10/26] converting obj to array to avoid errors with funky/blank objects on deserialization --- src/WP_CLI/SearchReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 83fefab7..433c6d1d 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -119,7 +119,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); } else { - foreach ( $data as $key => $value ) { + foreach ( (array) $data as $key => $value ) { $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } } From 6ff1d6b0bac5507754da0be05f13704d8d8b6068 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:54:23 -0600 Subject: [PATCH 11/26] adding safe object to search & replace over in test --- features/search-replace.feature | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 028411a0..fd8e574a 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1110,21 +1110,22 @@ Feature: Do global search/replace 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 current_field current_field1` + 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. """ And STDOUT should contain: """ - Success: Made 0 replacements. + Success: Made 1 replacement. """ - When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation'"` + When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'"` Then STDOUT should contain: """ - 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;} + 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;} """ Scenario: Regex search/replace with `--regex-limit=1` option From d6437f203bf01c0de6f8e697835589f2f1575cd7 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:36:00 -0600 Subject: [PATCH 12/26] removing array cast & testing serialized, safe object to confirm serialization was successful --- features/search-replace.feature | 22 ++++++++++++++++++++-- src/WP_CLI/SearchReplacer.php | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index fd8e574a..3f46ec7c 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1107,7 +1107,6 @@ Feature: Do global search/replace """ Scenario: Warn and ignore type-hinted objects that have some error in deserialization - 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;}')"` @@ -1122,11 +1121,30 @@ Feature: Do global search/replace Success: Made 1 replacement. """ - When I run `wp db query "SELECT option_value from wp_options where option_name='cereal_isation_2'"` + 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;} """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ Scenario: Regex search/replace with `--regex-limit=1` option Given a WP install diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 433c6d1d..83fefab7 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -119,7 +119,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); } else { - foreach ( (array) $data as $key => $value ) { + foreach ( $data as $key => $value ) { $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } } From ac1ae3be079d6e15753fa72b5f5c9783ce2df2f4 Mon Sep 17 00:00:00 2001 From: Mark Berube <10816162+MarkBerube@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:09:46 -0600 Subject: [PATCH 13/26] catch errors in unsafe php objects as they are iterated on --- src/WP_CLI/SearchReplacer.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 83fefab7..1dec107a 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -119,8 +119,19 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); } else { - foreach ( $data as $key => $value ) { - $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); + try { + foreach ( $data as $key => $value ) { + $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); + } + } catch ( \Error $e ) { + \WP_CLI::warning( + sprintf( + 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', + $data + ) + ); + + throw new Exception(); } } } elseif ( is_string( $data ) ) { From 8889a18727e9264ddccba003a3bdc59d99248eae Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 13:56:38 +0000 Subject: [PATCH 14/26] Fix PHPCS issue --- src/WP_CLI/SearchReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 1dec107a..097413ea 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -123,7 +123,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis foreach ( $data as $key => $value ) { $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } - } catch ( \Error $e ) { + } catch ( \Error $e ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', From 9404019d5f94576b8c2d49fcf967d11fd6bba51a Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 14:02:41 +0000 Subject: [PATCH 15/26] Include reason in error messages --- features/search-replace.feature | 150 ++++++++++++++++---------------- src/WP_CLI/SearchReplacer.php | 16 ++-- 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 3f46ec7c..a0c9f53e 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -37,8 +37,8 @@ Feature: Do global search/replace When I run `wp search-replace foo bar --include-columns=post_content` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 0 | SQL | Scenario: Multisite search/replace @@ -100,8 +100,8 @@ Feature: Do global search/replace When I run `wp search-replace bar burrito wp_post\?` And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_title | 1 | SQL | And STDOUT should not contain: """ wp_options @@ -134,9 +134,9 @@ Feature: Do global search/replace When I run `wp search-replace fooz burrito wp_opt\* wp_postme\*` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_key | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_key | 1 | SQL | And STDOUT should not contain: """ wp_posts @@ -213,23 +213,23 @@ Feature: Do global search/replace When I run `wp theme mod get header_image_data` Then STDOUT should be a table containing rows: - | key | value | - | header_image_data | {"url":"https:\/\/subdomain.example.com\/foo.jpg"} | + | key | value | + | header_image_data | {"url":"https:\/\/subdomain.example.com\/foo.jpg"} | When I run `wp search-replace subdomain.example.com example.com --no-recurse-objects` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 0 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 0 | PHP | When I run `wp search-replace subdomain.example.com example.com` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | When I run `wp theme mod get header_image_data` Then STDOUT should be a table containing rows: - | key | value | - | header_image_data | {"url":"https:\/\/example.com\/foo.jpg"} | + | key | value | + | header_image_data | {"url":"https:\/\/example.com\/foo.jpg"} | Scenario: Search and replace with quoted strings Given a WP install @@ -245,13 +245,13 @@ Feature: Do global search/replace When I run `wp search-replace 'Apple' 'Google' --dry-run` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | When I run `wp search-replace 'Apple' 'Google'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | When I run `wp search-replace 'Google' 'Apple' --dry-run` Then STDOUT should contain: @@ -351,8 +351,8 @@ Feature: Do global search/replace When I run `wp search-replace 'EXAMPLE.com' 'BAXAMPLE.com' wp_options --regex` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 0 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 0 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -362,8 +362,8 @@ Feature: Do global search/replace When I run `wp search-replace 'EXAMPLE.com' 'BAXAMPLE.com' wp_options --regex --regex-flags=i` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 5 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 5 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -376,8 +376,8 @@ Feature: Do global search/replace When I run `wp search-replace 'HTTPS://EXAMPLE.COM' 'https://example.jp/' wp_options --regex --regex-flags=i --regex-delimiter='#'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 2 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 2 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -387,8 +387,8 @@ Feature: Do global search/replace When I run `wp search-replace 'https://example.jp/' 'https://example.com/' wp_options --regex-delimiter='/'` Then STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 2 | PHP | + | Table | Column | Replacements | Type | + | wp_options | option_value | 2 | PHP | When I run `wp option get home` Then STDOUT should be: @@ -548,12 +548,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz1 baz2 --report` @@ -562,12 +562,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz2 baz3 --no-report` @@ -595,12 +595,12 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should be a table containing rows: - | Table | Column | Replacements | Type | - | wp_commentmeta | meta_key | 0 | SQL | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | - | wp_users | display_name | 0 | SQL | + | Table | Column | Replacements | Type | + | wp_commentmeta | meta_key | 0 | SQL | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | + | wp_users | display_name | 0 | SQL | And STDERR should be empty When I run `wp search-replace baz4 baz5 --report-changed-only` @@ -609,10 +609,10 @@ Feature: Do global search/replace Success: Made 3 replacements. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_options | option_value | 1 | PHP | - | wp_postmeta | meta_value | 1 | SQL | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_options | option_value | 1 | PHP | + | wp_postmeta | meta_value | 1 | SQL | + | wp_posts | post_title | 1 | SQL | And STDOUT should not contain: """ wp_commentmeta meta_key 0 SQL @@ -653,8 +653,8 @@ Feature: Do global search/replace Success: Made 0 replacements. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | no_key | | skipped | | + | Table | Column | Replacements | Type | + | no_key | | skipped | | And STDERR should be empty And I run `wp search-replace foo bar no_key --report-changed-only --all-tables` @@ -729,9 +729,9 @@ Feature: Do global search/replace Success: 2 replacements to be made. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | SQL | - | wp_posts | post_title | 1 | SQL | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | SQL | + | wp_posts | post_title | 1 | SQL | And STDOUT should contain: """ @@ -882,9 +882,9 @@ Feature: Do global search/replace Success: 2 replacements to be made. """ And STDOUT should end with a table containing rows: - | Table | Column | Replacements | Type | - | wp_posts | post_content | 1 | PHP | - | wp_posts | post_title | 1 | PHP | + | Table | Column | Replacements | Type | + | wp_posts | post_content | 1 | PHP | + | wp_posts | post_title | 1 | PHP | And STDOUT should contain: """ @@ -1037,11 +1037,11 @@ Feature: Do global search/replace And a test_db.sql file: """ CREATE TABLE `wp_123_test` ( - `name` varchar(50), - `value` varchar(5000), - `created_at` datetime NOT NULL, - `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`name`) + `name` varchar(50), + `value` varchar(5000), + `created_at` datetime NOT NULL, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`name`) ) ENGINE=InnoDB; INSERT INTO `wp_123_test` VALUES ('test_val','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); INSERT INTO `wp_123_test` VALUES ('123.','wp_123_test_value_X','2016-11-15 14:41:33','2016-11-15 21:41:33'); @@ -1114,7 +1114,7 @@ Feature: Do global search/replace 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. + 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. """ And STDOUT should contain: """ @@ -1199,19 +1199,19 @@ Feature: Do global search/replace index=1 while [[ $index -le 199 ]]; do - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql - index=`expr $index + 1` + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql + index=`expr $index + 1` done - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql echo "CREATE TABLE \`wp_123_test_multikey\` (\`key1\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`key2\` INT(5) UNSIGNED NOT NULL, \`key3\` INT(5) UNSIGNED NOT NULL, \`text\` TEXT, PRIMARY KEY (\`key1\`,\`key2\`,\`key3\`) );" >> test_db.sql echo "INSERT INTO \`wp_123_test_multikey\` (\`key2\`,\`key3\`,\`text\`) VALUES" >> test_db.sql index=1 while [[ $index -le 204 ]]; do - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql - index=`expr $index + 1` + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql + index=`expr $index + 1` done - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` @@ -1250,19 +1250,19 @@ Feature: Do global search/replace index=1 while [[ $index -le 199 ]]; do - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql - index=`expr $index + 1` + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc')," >> test_db.sql + index=`expr $index + 1` done - echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql + echo "('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc'),('abc');" >> test_db.sql echo "CREATE TABLE \`wp_123_test_multikey\` (\`key1\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`key2\` INT(5) UNSIGNED NOT NULL, \`key3\` INT(5) UNSIGNED NOT NULL, \`text\` TEXT, PRIMARY KEY (\`key1\`,\`key2\`,\`key3\`) );" >> test_db.sql echo "INSERT INTO \`wp_123_test_multikey\` (\`key2\`,\`key3\`,\`text\`) VALUES" >> test_db.sql index=1 while [[ $index -le 204 ]]; do - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql - index=`expr $index + 1` + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc')," >> test_db.sql + index=`expr $index + 1` done - echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql + echo "(0,0,'abc'),(1,1,'abc'),(2,2,'abc'),(3,3,'abc'),(4,4,'abc'),(5,0,'abc'),(6,1,'abc'),(7,2,'abc'),(8,3,'abc'),(9,4,'abc');" >> test_db.sql """ And I run `bash create_sql_file.sh` And I run `wp db query "SOURCE test_db.sql;"` diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 097413ea..e8e0c5be 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -91,11 +91,12 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis $unserialized = is_string( $data ) ? @unserialize( $data ) : false; error_reporting( $error_reporting ); - } catch ( \TypeError $e ) { // phpcs:ignore + } catch ( \TypeError $exception ) { // phpcs:ignore \WP_CLI::warning( sprintf( - 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', - $data + 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', + $data, + $exception->getMessage() ) ); @@ -123,11 +124,12 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis foreach ( $data as $key => $value ) { $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } - } catch ( \Error $e ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound + } catch ( \Error $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound \WP_CLI::warning( sprintf( - 'Skipping an inconvertible serialized object: "%s", replacements might not be complete.', - $data + 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', + $data, + $exception->getMessage() ) ); @@ -165,7 +167,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis if ( $serialised ) { return serialize( $data ); } - } catch ( Exception $error ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Deliberally empty. + } catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Intentionally empty. } From 251042b0100a6ddb7beef2d7ef57ec043f22ed3d Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 14:38:28 +0000 Subject: [PATCH 16/26] Avoid casting objects to string --- src/WP_CLI/SearchReplacer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index e8e0c5be..bb662ddc 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -127,8 +127,8 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis } catch ( \Error $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound \WP_CLI::warning( sprintf( - 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', - $data, + 'Skipping an inconvertible serialized object of type "%s", replacements might not be complete. Reason: %s.', + is_object( $data ) ? get_class( $data ) : gettype( $data ), $exception->getMessage() ) ); From b3a17345a4d6652600ba265fbacfd42335aa5b81 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:30:38 +0000 Subject: [PATCH 17/26] Split into two separate tests per PHP version --- features/search-replace.feature | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index a0c9f53e..df88a33e 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1106,7 +1106,49 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ - Scenario: Warn and ignore type-hinted objects that have some error in deserialization + @less-than-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: Warning: Skipping an inconvertible serialized object of type "mysqli_result", replacements might not be complete. Reason: mysqli_result object is already closed. + """ + 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;} + """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ + + @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;}')"` From 42ef6c25d84a9cee92877a8f80d009af6b791c21 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:44:54 +0000 Subject: [PATCH 18/26] Fully form forwarded exception --- features/search-replace.feature | 47 ++++++++++++++++++++++++++++++--- src/WP_CLI/SearchReplacer.php | 2 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index df88a33e..ba61092f 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1106,8 +1106,8 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ - @less-than-php-8.1 - Scenario: Warn and ignore type-hinted objects that have some error in deserialization (PHP < 8.1) + @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;}')"` @@ -1115,7 +1115,48 @@ Feature: Do global search/replace When I try `wp search-replace mysqli_result stdClass` Then STDERR should contain: """ - Warning: Warning: Skipping an inconvertible serialized object of type "mysqli_result", replacements might not be complete. Reason: mysqli_result object is already closed. + Warning: Skipping an inconvertible serialized object of type "mysqli_result", replacements might not be complete. Reason: mysqli_result object is already closed. + """ + 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;} + """ + Then save STDOUT as {SERIALIZED_RESULT} + And a test_php.php file: + """ + 1 + """ + Then STDOUT should contain: + """ + [field_count] => 2 + """ + + @requires-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. """ And STDOUT should contain: """ diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index bb662ddc..4f1fad85 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -133,7 +133,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); - throw new Exception(); + throw new Exception( $exception->getMessage(), $exception->getCode(), $exception ); } } } elseif ( is_string( $data ) ) { From d6430f99ece13b07bdb424e4a922f6f3535c7d40 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:45:55 +0000 Subject: [PATCH 19/26] Be precise about PHPCS ignore --- src/WP_CLI/SearchReplacer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index 4f1fad85..fa94bc45 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -91,7 +91,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis $unserialized = is_string( $data ) ? @unserialize( $data ) : false; error_reporting( $error_reporting ); - } catch ( \TypeError $exception ) { // phpcs:ignore + } catch ( \TypeError $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.typeerrorFound \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', From 586522bb06107b2c26a2a6923f3a861a54c30c52 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:49:01 +0000 Subject: [PATCH 20/26] Add clarifying comment about type error --- src/WP_CLI/SearchReplacer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index fa94bc45..fb559967 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -92,6 +92,10 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis error_reporting( $error_reporting ); } catch ( \TypeError $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.typeerrorFound + // This type error is thrown when trying to unserialize a string that does not fit the + // type declarations of the properties it is supposed to fill. + // This type checking was introduced with PHP 8.1. + // See https://github.com/wp-cli/search-replace-command/issues/191 \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object: "%s", replacements might not be complete. Reason: %s.', @@ -100,7 +104,7 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis ) ); - throw new Exception(); + throw new Exception( $exception->getMessage(), $exception->getCode(), $exception ); } if ( false !== $unserialized ) { From 4509c7da5d24f33bbfdd91da89800b3c243835ce Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:52:05 +0000 Subject: [PATCH 21/26] Add another clarifying comment --- src/WP_CLI/SearchReplacer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/WP_CLI/SearchReplacer.php b/src/WP_CLI/SearchReplacer.php index fb559967..06fada15 100644 --- a/src/WP_CLI/SearchReplacer.php +++ b/src/WP_CLI/SearchReplacer.php @@ -129,6 +129,9 @@ private function run_recursively( $data, $serialised, $recursion_level = 0, $vis $data->$key = $this->run_recursively( $value, false, $recursion_level + 1, $visited_data ); } } catch ( \Error $exception ) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound + // This error is thrown when the object that was unserialized cannot be iterated upon. + // The most notable reason is an empty `mysqli_result` object which is then considered to be "already closed". + // See https://github.com/wp-cli/search-replace-command/pull/192#discussion_r1412310179 \WP_CLI::warning( sprintf( 'Skipping an inconvertible serialized object of type "%s", replacements might not be complete. Reason: %s.', From 9c1268426683122b71e59e49f2949a93da8db612 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 17:53:43 +0000 Subject: [PATCH 22/26] Adapt warning for PHP < 8.0 --- features/search-replace.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index ba61092f..25ad7b64 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1115,7 +1115,7 @@ Feature: Do global search/replace 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. + Warning: WP_CLI\SearchReplacer::run_recursively(): Couldn't fetch mysqli_result in src/WP_CLI/SearchReplacer.php """ And STDOUT should contain: """ From e915dd821819af98d1ace8fceea3461f0af779a0 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Mon, 18 Dec 2023 19:18:16 +0000 Subject: [PATCH 23/26] Fix warning text in test --- features/search-replace.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index 25ad7b64..bb6cb216 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1115,7 +1115,7 @@ Feature: Do global search/replace When I try `wp search-replace mysqli_result stdClass` Then STDERR should contain: """ - Warning: WP_CLI\SearchReplacer::run_recursively(): Couldn't fetch mysqli_result in src/WP_CLI/SearchReplacer.php + Warning: WP_CLI\SearchReplacer::run_recursively(): Couldn't fetch mysqli_result """ And STDOUT should contain: """ From 813403ed86ae5320a8ad9fefe0f2400f72e78ccf Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 19 Dec 2023 08:32:04 +0000 Subject: [PATCH 24/26] Fix bad anntotation --- features/search-replace.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/search-replace.feature b/features/search-replace.feature index bb6cb216..0a2e2105 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1147,7 +1147,7 @@ Feature: Do global search/replace [field_count] => 2 """ - @requires-php-8.0 @less-than-php-8.1 + @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;}')"` From 43abfb5a299101369b2628764c7dd4716991d90b Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 19 Dec 2023 09:49:49 +0000 Subject: [PATCH 25/26] Add safeguard for partial processing to not be counted as a success --- src/Search_Replace_Command.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Search_Replace_Command.php b/src/Search_Replace_Command.php index a8470e03..e109bf77 100644 --- a/src/Search_Replace_Command.php +++ b/src/Search_Replace_Command.php @@ -585,6 +585,12 @@ static function ( $key ) { continue; } + // In case a needed re-serialization was unsuccessful, we should not update the value, + // as this implies we hit an exception while processing. + if ( gettype( $value ) !== gettype( $col_value ) ) { + continue; + } + if ( $this->log_handle ) { $this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() ); $replacer->clear_log_data(); From da1acc772c355b528f4c3f121ea85260ff9a8f62 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 19 Dec 2023 12:13:37 +0000 Subject: [PATCH 26/26] Skip broken SQLite testing for now --- features/search-replace-export.feature | 9 +++++++ features/search-replace.feature | 33 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/features/search-replace-export.feature b/features/search-replace-export.feature index e1cb6447..6bbacc52 100644 --- a/features/search-replace-export.feature +++ b/features/search-replace-export.feature @@ -1,5 +1,6 @@ Feature: Search / replace with file export + @require-mysql Scenario: Search / replace export to STDOUT Given a WP install And I run `echo ' '` @@ -76,6 +77,7 @@ Feature: Search / replace with file export https://example.net """ + @require-mysql Scenario: Search / replace export to file Given a WP install And I run `wp post generate --count=100` @@ -129,6 +131,7 @@ Feature: Search / replace with file export 101 """ + @require-mysql Scenario: Search / replace export to file with verbosity Given a WP install @@ -151,6 +154,7 @@ Feature: Search / replace with file export Error: You cannot supply --dry-run and --export at the same time. """ + @require-mysql Scenario: Search / replace shouldn't affect primary key Given a WP install And I run `wp post create --post_title=foo --porcelain` @@ -191,6 +195,7 @@ Feature: Search / replace with file export Error: Unable to open export file "foo/bar.sql" for writing: """ + @require-mysql Scenario: Search / replace specific table Given a WP install @@ -225,6 +230,7 @@ Feature: Search / replace with file export foo """ + @require-mysql Scenario: Search / replace export should cater for field/table names that use reserved words or unusual characters Given a WP install # Unlike search-replace.features version, don't use `back``tick` column name as WP_CLI\Iterators\Table::build_fields() can't handle it. @@ -268,6 +274,7 @@ Feature: Search / replace with file export """ And STDERR should be empty + @require-mysql Scenario: Suppress report or only report changes on export to file Given a WP install @@ -365,6 +372,7 @@ Feature: Search / replace with file export """ And STDERR should be empty + @require-mysql Scenario: Search / replace should remove placeholder escape on export Given a WP install And I run `wp post create --post_title=test-remove-placeholder-escape% --porcelain` @@ -380,6 +388,7 @@ Feature: Search / replace with file export 'test-remove-placeholder-escape{' """ + @require-mysql Scenario: NULLs exported as NULL and not null string Given a WP install And I run `wp db query "INSERT INTO wp_postmeta VALUES (9999, 9999, NULL, 'foo')"` diff --git a/features/search-replace.feature b/features/search-replace.feature index 0a2e2105..0fc72252 100644 --- a/features/search-replace.feature +++ b/features/search-replace.feature @@ -1,5 +1,6 @@ Feature: Do global search/replace + @require-mysql Scenario: Basic search/replace Given a WP install @@ -41,6 +42,7 @@ Feature: Do global search/replace | wp_posts | post_content | 0 | SQL | + @require-mysql Scenario: Multisite search/replace Given a WP multisite install And I run `wp site create --slug="foo" --title="foo" --email="foo@example.com"` @@ -50,6 +52,7 @@ Feature: Do global search/replace | wp_2_options | option_value | 4 | PHP | | wp_blogs | path | 1 | SQL | + @require-mysql Scenario: Don't run on unregistered tables by default Given a WP install And I run `wp db query "CREATE TABLE wp_awesome ( id int(11) unsigned NOT NULL AUTO_INCREMENT, awesome_stuff TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"` @@ -66,6 +69,7 @@ Feature: Do global search/replace wp_awesome """ + @require-mysql Scenario: Run on unregistered, unprefixed tables with --all-tables flag Given a WP install And I run `wp db query "CREATE TABLE awesome_table ( id int(11) unsigned NOT NULL AUTO_INCREMENT, awesome_stuff TEXT, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"` @@ -82,6 +86,7 @@ Feature: Do global search/replace awesome_table """ + @require-mysql Scenario: Run on all tables matching string with wildcard Given a WP install @@ -154,12 +159,14 @@ Feature: Do global search/replace bar """ + @require-mysql Scenario: Quiet search/replace Given a WP install When I run `wp search-replace foo bar --quiet` Then STDOUT should be empty + @require-mysql Scenario: Verbose search/replace Given a WP install And I run `wp post create --post_title='Replace this text' --porcelain` @@ -202,6 +209,7 @@ Feature: Do global search/replace """ And the return code should be 1 + @require-mysql Scenario: Search and replace within theme mods Given a WP install And a setup-theme-mod.php file: @@ -231,6 +239,7 @@ Feature: Do global search/replace | key | value | | header_image_data | {"url":"https:\/\/example.com\/foo.jpg"} | + @require-mysql Scenario: Search and replace with quoted strings Given a WP install @@ -276,6 +285,7 @@ Feature: Do global search/replace And STDOUT should be empty And the return code should be 0 + @require-mysql Scenario: Search and replace a table that has a multi-column primary key Given a WP install And I run `wp db query "CREATE TABLE wp_multicol ( "id" bigint(20) NOT NULL AUTO_INCREMENT,"name" varchar(60) NOT NULL,"value" text NOT NULL,PRIMARY KEY ("id","name"),UNIQUE KEY "name" ("name") ) ENGINE=InnoDB DEFAULT CHARSET=utf8 "` @@ -308,6 +318,7 @@ Feature: Do global search/replace | https://newdomain.com | | | https://newdomain.com | --dry-run | + @require-mysql Scenario Outline: Choose replacement method (PHP or MySQL/MariaDB) given proper flags or data. Given a WP install And I run `wp option get siteurl` @@ -324,6 +335,7 @@ Feature: Do global search/replace | | PHP | SQL | | --precise | PHP | PHP | + @require-mysql Scenario Outline: Ensure search and replace uses PHP (precise) mode when serialized data is found Given a WP install And I run `wp post create --post_content='' --porcelain` @@ -346,6 +358,7 @@ Feature: Do global search/replace | a:1:{s:3:"bar";s:3:"foo";} | | O:8:"stdClass":1:{s:1:"a";s:3:"foo";} | + @require-mysql Scenario: Search replace with a regex flag Given a WP install @@ -371,6 +384,7 @@ Feature: Do global search/replace https://BAXAMPLE.com """ + @require-mysql Scenario: Search replace with a regex delimiter Given a WP install @@ -469,6 +483,7 @@ Feature: Do global search/replace """ And the return code should be 1 + @require-mysql Scenario: Formatting as count-only Given a WP install And I run `wp option set foo 'ALPHA.example.com'` @@ -498,6 +513,7 @@ Feature: Do global search/replace 0 """ + @require-mysql Scenario: Search / replace should cater for field/table names that use reserved words or unusual characters Given a WP install And a esc_sql_ident.sql file: @@ -525,7 +541,7 @@ Feature: Do global search/replace """ And STDERR should be empty - @suppress_report__only_changes + @require-mysql @suppress_report__only_changes Scenario: Suppress report or only report changes Given a WP install @@ -634,7 +650,7 @@ Feature: Do global search/replace """ And STDERR should be empty - @no_table__no_primary_key + @require-mysql @no_table__no_primary_key Scenario: Deal with non-existent table and table with no primary keys Given a WP install @@ -684,6 +700,7 @@ Feature: Do global search/replace """ And the return code should be 0 + @require-mysql Scenario: Search / replace is case sensitive Given a WP install When I run `wp post create --post_title='Case Sensitive' --porcelain` @@ -717,6 +734,7 @@ Feature: Do global search/replace """ And STDERR should be empty + @require-mysql Scenario: Logging with simple replace Given a WP install @@ -932,6 +950,7 @@ Feature: Do global search/replace Content_ab\1z__baz_1234567890_eb\1z__bez_1234567890_ib\1z__biz_1234567890_ob\1z__boz_1234567890_ub\1z__buz_ """ + @require-mysql Scenario: Logging with prefixes and custom colors Given a WP install And I run `wp option set blogdescription 'Just another WordPress site'` @@ -1032,6 +1051,7 @@ Feature: Do global search/replace And STDERR should be empty # Regression test for https://github.com/wp-cli/search-replace-command/issues/58 + @require-mysql Scenario: The parameters --regex and --all-tables-with-prefix produce valid SQL Given a WP install And a test_db.sql file: @@ -1085,6 +1105,7 @@ Feature: Do global search/replace """ # Regression test for https://github.com/wp-cli/search-replace-command/issues/68 + @require-mysql Scenario: Incomplete classes are handled gracefully during (un)serialization Given a WP install @@ -1106,7 +1127,7 @@ Feature: Do global search/replace a:1:{i:0;O:10:"CornFlakes":0:{}} """ - @less-than-php-8.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;}')"` @@ -1147,7 +1168,7 @@ Feature: Do global search/replace [field_count] => 2 """ - @require-php-8.0 @less-than-php-8.1 + @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;}')"` @@ -1188,7 +1209,7 @@ Feature: Do global search/replace [field_count] => 2 """ - @require-php-8.1 + @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;}')"` @@ -1272,6 +1293,7 @@ Feature: Do global search/replace Success: """ + @require-mysql Scenario: Chunking a precise search and replace works without skipping lines Given a WP install And a create_sql_file.sh file: @@ -1323,6 +1345,7 @@ Feature: Do global search/replace Success: Made 0 replacements. """ + @require-mysql Scenario: Chunking a regex search and replace works without skipping lines Given a WP install And a create_sql_file.sh file: