99
1010class Search_Replace_Command extends WP_CLI_Command {
1111
12+ /**
13+ * @var bool
14+ */
1215 private $ dry_run ;
16+
17+ /**
18+ * @var false|resource
19+ */
1320 private $ export_handle = false ;
21+
22+ /**
23+ * @var int
24+ */
1425 private $ export_insert_size ;
26+
27+ /**
28+ * @var bool
29+ */
1530 private $ recurse_objects ;
31+
32+ /**
33+ * @var bool
34+ */
1635 private $ regex ;
36+
37+ /**
38+ * @var string
39+ */
1740 private $ regex_flags ;
41+
42+ /**
43+ * @var string
44+ */
1845 private $ regex_delimiter ;
46+
47+ /**
48+ * @var int
49+ */
1950 private $ regex_limit = -1 ;
51+
52+ /**
53+ * @var string[]
54+ */
2055 private $ skip_tables ;
56+
57+ /**
58+ * @var string[]
59+ */
2160 private $ skip_columns ;
61+
62+ /**
63+ * @var string[]
64+ */
2265 private $ include_columns ;
66+
67+ /**
68+ * @var string
69+ */
2370 private $ format ;
71+
72+ /**
73+ * @var bool
74+ */
2475 private $ report ;
76+
77+ /**
78+ * @var bool
79+ */
2580 private $ verbose ;
2681
82+ /**
83+ * @var bool
84+ */
2785 private $ report_changed_only ;
28- private $ log_handle = null ;
86+
87+ /**
88+ * @var false|resource
89+ */
90+ private $ log_handle = null ;
91+
92+ /**
93+ * @var int
94+ */
2995 private $ log_before_context = 40 ;
30- private $ log_after_context = 40 ;
31- private $ log_prefixes = array ( '< ' , '> ' );
96+
97+ /**
98+ * @var int
99+ */
100+ private $ log_after_context = 40 ;
101+
102+ /**
103+ * @var array{0: string, 1: string}
104+ */
105+ private $ log_prefixes = array ( '< ' , '> ' );
106+
107+ /**
108+ * @var array<string, array<mixed>>
109+ */
32110 private $ log_colors ;
111+
112+ /**
113+ * @var string|false
114+ */
33115 private $ log_encoding ;
116+
117+ /**
118+ * @var float
119+ */
34120 private $ start_time ;
35121
36122 /**
@@ -169,24 +255,28 @@ class Search_Replace_Command extends WP_CLI_Command {
169255 * else
170256 * wp search-replace 'http://example.com' 'http://example.test' --recurse-objects --skip-columns=guid --skip-tables=wp_users
171257 * fi
258+ *
259+ * @param array<string> $args Positional arguments.
260+ * @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.
172261 */
173262 public function __invoke ( $ args , $ assoc_args ) {
174263 global $ wpdb ;
175264 $ old = array_shift ( $ args );
176265 $ new = array_shift ( $ args );
177266 $ total = 0 ;
178267 $ report = array ();
179- $ this ->dry_run = Utils \get_flag_value ( $ assoc_args , 'dry-run ' );
180- $ php_only = Utils \get_flag_value ( $ assoc_args , 'precise ' );
268+ $ this ->dry_run = Utils \get_flag_value ( $ assoc_args , 'dry-run ' , false );
269+ $ php_only = Utils \get_flag_value ( $ assoc_args , 'precise ' , false );
181270 $ this ->recurse_objects = Utils \get_flag_value ( $ assoc_args , 'recurse-objects ' , true );
182- $ this ->verbose = Utils \get_flag_value ( $ assoc_args , 'verbose ' );
271+ $ this ->verbose = Utils \get_flag_value ( $ assoc_args , 'verbose ' , false );
183272 $ this ->format = Utils \get_flag_value ( $ assoc_args , 'format ' );
184273 $ this ->regex = Utils \get_flag_value ( $ assoc_args , 'regex ' , false );
185274
275+ $ default_regex_delimiter = false ;
276+
186277 if ( null !== $ this ->regex ) {
187- $ default_regex_delimiter = false ;
188- $ this ->regex_flags = Utils \get_flag_value ( $ assoc_args , 'regex-flags ' , false );
189- $ this ->regex_delimiter = Utils \get_flag_value ( $ assoc_args , 'regex-delimiter ' , '' );
278+ $ this ->regex_flags = Utils \get_flag_value ( $ assoc_args , 'regex-flags ' , false );
279+ $ this ->regex_delimiter = Utils \get_flag_value ( $ assoc_args , 'regex-delimiter ' , '' );
190280 if ( '' === $ this ->regex_delimiter ) {
191281 $ this ->regex_delimiter = chr ( 1 );
192282 $ default_regex_delimiter = true ;
@@ -213,7 +303,7 @@ public function __invoke( $args, $assoc_args ) {
213303 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Preventing a warning when testing the regex.
214304 if ( false === @preg_match ( $ search_regex , '' ) ) {
215305 $ error = error_get_last ();
216- $ preg_error_message = ( ! empty ( $ error ) && array_key_exists ( ' message ' , $ error ) ) ? "\n{$ error ['message ' ]}. " : '' ;
306+ $ preg_error_message = ! empty ( $ error ) ? "\n{$ error ['message ' ]}. " : '' ;
217307 if ( $ default_regex_delimiter ) {
218308 $ flags_msg = $ this ->regex_flags ? "flags ' $ this ->regex_flags ' " : 'no flags ' ;
219309 $ msg = "The regex pattern ' $ old' with default delimiter 'chr(1)' and {$ flags_msg } fails. " ;
@@ -242,16 +332,16 @@ public function __invoke( $args, $assoc_args ) {
242332 $ this ->export_handle = STDOUT ;
243333 $ this ->verbose = false ;
244334 } else {
245- $ this ->export_handle = @fopen ( $ assoc_args [ ' export ' ] , 'w ' );
335+ $ this ->export_handle = @fopen ( $ export , 'w ' );
246336 if ( false === $ this ->export_handle ) {
247337 $ error = error_get_last ();
248- WP_CLI ::error ( sprintf ( 'Unable to open export file "%s" for writing: %s. ' , $ assoc_args [ ' export ' ] , $ error ['message ' ] ) );
338+ WP_CLI ::error ( sprintf ( 'Unable to open export file "%s" for writing: %s. ' , $ export , $ error ['message ' ] ?? ' (unknown error) ' ) );
249339 }
250340 }
251341 $ export_insert_size = Utils \get_flag_value ( $ assoc_args , 'export_insert_size ' , 50 );
252342 // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- See the code, this is deliberate.
253343 if ( (int ) $ export_insert_size == $ export_insert_size && $ export_insert_size > 0 ) {
254- $ this ->export_insert_size = $ export_insert_size ;
344+ $ this ->export_insert_size = ( int ) $ export_insert_size ;
255345 }
256346 $ php_only = true ;
257347 }
@@ -261,54 +351,53 @@ public function __invoke( $args, $assoc_args ) {
261351 if ( true === $ log || '- ' === $ log ) {
262352 $ this ->log_handle = STDOUT ;
263353 } else {
264- $ this ->log_handle = @fopen ( $ assoc_args [ ' log ' ] , 'w ' );
354+ $ this ->log_handle = @fopen ( $ log , 'w ' );
265355 if ( false === $ this ->log_handle ) {
266356 $ error = error_get_last ();
267- WP_CLI ::error ( sprintf ( 'Unable to open log file "%s" for writing: %s. ' , $ assoc_args [ ' log ' ] , $ error ['message ' ] ) );
357+ WP_CLI ::error ( sprintf ( 'Unable to open log file "%s" for writing: %s. ' , $ log , $ error ['message ' ] ?? ' (unknown error) ' ) );
268358 }
269359 }
270- if ( $ this ->log_handle ) {
271- $ before_context = Utils \get_flag_value ( $ assoc_args , 'before_context ' );
272- if ( null !== $ before_context && preg_match ( '/^[0-9]+$/ ' , $ before_context ) ) {
273- $ this ->log_before_context = (int ) $ before_context ;
274- }
275360
276- $ after_context = Utils \get_flag_value ( $ assoc_args , 'after_context ' );
277- if ( null !== $ after_context && preg_match ( '/^[0-9]+$/ ' , $ after_context ) ) {
278- $ this ->log_after_context = (int ) $ after_context ;
279- }
361+ $ before_context = Utils \get_flag_value ( $ assoc_args , 'before_context ' );
362+ if ( null !== $ before_context && preg_match ( '/^[0-9]+$/ ' , $ before_context ) ) {
363+ $ this ->log_before_context = (int ) $ before_context ;
364+ }
280365
281- $ log_prefixes = getenv ( ' WP_CLI_SEARCH_REPLACE_LOG_PREFIXES ' );
282- if ( false !== $ log_prefixes && preg_match ( '/^([^,]*),([^,]*) $/ ' , $ log_prefixes , $ matches ) ) {
283- $ this ->log_prefixes = array ( $ matches [ 1 ], $ matches [ 2 ] ) ;
284- }
366+ $ after_context = Utils \get_flag_value ( $ assoc_args , ' after_context ' );
367+ if ( null !== $ after_context && preg_match ( '/^[0-9]+ $/ ' , $ after_context ) ) {
368+ $ this ->log_after_context = ( int ) $ after_context ;
369+ }
285370
286- if ( STDOUT === $ this ->log_handle ) {
287- $ default_log_colors = array (
288- 'log_table_column_id ' => '%B ' ,
289- 'log_old ' => '%R ' ,
290- 'log_new ' => '%G ' ,
291- );
292- } else {
293- $ default_log_colors = array (
294- 'log_table_column_id ' => '' ,
295- 'log_old ' => '' ,
296- 'log_new ' => '' ,
297- );
298- }
371+ $ log_prefixes = getenv ( 'WP_CLI_SEARCH_REPLACE_LOG_PREFIXES ' );
372+ if ( false !== $ log_prefixes && preg_match ( '/^([^,]*),([^,]*)$/ ' , $ log_prefixes , $ matches ) ) {
373+ $ this ->log_prefixes = array ( $ matches [1 ], $ matches [2 ] );
374+ }
299375
300- $ log_colors = getenv ( 'WP_CLI_SEARCH_REPLACE_LOG_COLORS ' );
301- if ( false !== $ log_colors && preg_match ( '/^([^,]*),([^,]*),([^,]*)$/ ' , $ log_colors , $ matches ) ) {
302- $ default_log_colors = array (
303- 'log_table_column_id ' => $ matches [1 ],
304- 'log_old ' => $ matches [2 ],
305- 'log_new ' => $ matches [3 ],
306- );
307- }
376+ if ( STDOUT === $ this ->log_handle ) {
377+ $ default_log_colors = array (
378+ 'log_table_column_id ' => '%B ' ,
379+ 'log_old ' => '%R ' ,
380+ 'log_new ' => '%G ' ,
381+ );
382+ } else {
383+ $ default_log_colors = array (
384+ 'log_table_column_id ' => '' ,
385+ 'log_old ' => '' ,
386+ 'log_new ' => '' ,
387+ );
388+ }
308389
309- $ this ->log_colors = $ this ->get_colors ( $ assoc_args , $ default_log_colors );
310- $ this ->log_encoding = 0 === strpos ( $ wpdb ->charset , 'utf8 ' ) ? 'UTF-8 ' : false ;
390+ $ log_colors = getenv ( 'WP_CLI_SEARCH_REPLACE_LOG_COLORS ' );
391+ if ( false !== $ log_colors && preg_match ( '/^([^,]*),([^,]*),([^,]*)$/ ' , $ log_colors , $ matches ) ) {
392+ $ default_log_colors = array (
393+ 'log_table_column_id ' => $ matches [1 ],
394+ 'log_old ' => $ matches [2 ],
395+ 'log_new ' => $ matches [3 ],
396+ );
311397 }
398+
399+ $ this ->log_colors = $ this ->get_colors ( $ assoc_args , $ default_log_colors );
400+ $ this ->log_encoding = 0 === strpos ( $ wpdb ->charset , 'utf8 ' ) ? 'UTF-8 ' : false ;
312401 }
313402
314403 $ this ->report = Utils \get_flag_value ( $ assoc_args , 'report ' , true );
@@ -383,6 +472,8 @@ public function __invoke( $args, $assoc_args ) {
383472 WP_CLI ::log ( sprintf ( 'Checking: %s.%s ' , $ table , $ col ) );
384473 }
385474
475+ $ serial_row = false ;
476+
386477 if ( ! $ php_only && ! $ this ->regex ) {
387478 $ col_sql = self ::esc_sql_ident ( $ col );
388479 $ wpdb ->last_error = '' ;
@@ -422,7 +513,7 @@ public function __invoke( $args, $assoc_args ) {
422513 }
423514
424515 if ( 'count ' === $ this ->format ) {
425- WP_CLI ::line ( $ total );
516+ WP_CLI ::line ( ( string ) $ total );
426517 return ;
427518 }
428519
@@ -698,8 +789,9 @@ private function write_sql_row_fields( $table, $rows ) {
698789
699790 if ( method_exists ( $ wpdb , 'remove_placeholder_escape ' ) ) {
700791 // since 4.8.3
701- // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
702- $ sql = $ wpdb ->remove_placeholder_escape ( $ wpdb ->prepare ( $ sql , array_values ( $ values ) ) );
792+
793+ // @phpstan-ignore method.nonObject
794+ $ sql = $ wpdb ->remove_placeholder_escape ( $ wpdb ->prepare ( $ sql , array_values ( $ values ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
703795 } else {
704796 // 4.8.2 or less
705797 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
@@ -766,8 +858,9 @@ private static function esc_like( $old ) {
766858 // 4.0
767859 $ old = $ wpdb ->esc_like ( $ old );
768860 } else {
769- // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less.
770- $ old = like_escape ( esc_sql ( $ old ) ); // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`.
861+ // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`.
862+ // @phpstan-ignore function.deprecated
863+ $ old = like_escape ( esc_sql ( $ old ) ); // phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less.
771864 }
772865
773866 return $ old ;
@@ -777,8 +870,9 @@ private static function esc_like( $old ) {
777870 * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
778871 * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
779872 *
780- * @param string|array $idents A single identifier or an array of identifiers.
781- * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
873+ * @param string|string[] $idents A single identifier or an array of identifiers.
874+ * @return string|string[] An escaped string if given a string, or an array of escaped strings if given an array of strings.
875+ * @phpstan-return ($idents is string ? string : string[])
782876 */
783877 private static function esc_sql_ident ( $ idents ) {
784878 $ backtick = static function ( $ v ) {
@@ -794,8 +888,9 @@ private static function esc_sql_ident( $idents ) {
794888 /**
795889 * Puts MySQL string values in single quotes, to avoid them being interpreted as column names.
796890 *
797- * @param string|array $values A single value or an array of values.
798- * @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings.
891+ * @param string|string[] $values A single value or an array of values.
892+ * @return string|string[] A quoted string if given a string, or an array of quoted strings if given an array of strings.
893+ * @phpstan-return ($values is string ? string : string[])
799894 */
800895 private static function esc_sql_value ( $ values ) {
801896 $ quote = static function ( $ v ) {
@@ -1035,6 +1130,10 @@ static function ( $m ) use ( $matches ) {
10351130 * @param array $new_bits Array of new replacement log strings.
10361131 */
10371132 private function log_write ( $ col , $ keys , $ table , $ old_bits , $ new_bits ) {
1133+ if ( ! $ this ->log_handle ) {
1134+ return ;
1135+ }
1136+
10381137 $ id_log = $ keys ? ( ': ' . implode ( ', ' , (array ) $ keys ) ) : '' ;
10391138 $ table_column_id_log = $ this ->log_colors ['log_table_column_id ' ][0 ] . $ table . '. ' . $ col . $ id_log . $ this ->log_colors ['log_table_column_id ' ][1 ];
10401139
0 commit comments