11<?php
22
3+ use cli \Colors ;
4+ use cli \Table ;
5+ use WP_CLI \Iterators ;
6+ use WP_CLI \SearchReplacer ;
7+ use WP_CLI \Utils ;
8+ use function cli \safe_substr ;
9+
310class Search_Replace_Command extends WP_CLI_Command {
411
512 private $ dry_run ;
@@ -15,8 +22,9 @@ class Search_Replace_Command extends WP_CLI_Command {
1522 private $ include_columns ;
1623 private $ format ;
1724 private $ report ;
18- private $ report_changed_only ;
25+ private $ verbose ;
1926
27+ private $ report_changed_only ;
2028 private $ log_handle = null ;
2129 private $ log_before_context = 40 ;
2230 private $ log_after_context = 40 ;
@@ -167,24 +175,24 @@ public function __invoke( $args, $assoc_args ) {
167175 $ new = array_shift ( $ args );
168176 $ total = 0 ;
169177 $ report = array ();
170- $ this ->dry_run = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'dry-run ' );
171- $ php_only = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'precise ' );
172- $ this ->recurse_objects = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'recurse-objects ' , true );
173- $ this ->verbose = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'verbose ' );
174- $ this ->format = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'format ' );
175- $ this ->regex = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'regex ' , false );
178+ $ this ->dry_run = Utils \get_flag_value ( $ assoc_args , 'dry-run ' );
179+ $ php_only = Utils \get_flag_value ( $ assoc_args , 'precise ' );
180+ $ this ->recurse_objects = Utils \get_flag_value ( $ assoc_args , 'recurse-objects ' , true );
181+ $ this ->verbose = Utils \get_flag_value ( $ assoc_args , 'verbose ' );
182+ $ this ->format = Utils \get_flag_value ( $ assoc_args , 'format ' );
183+ $ this ->regex = Utils \get_flag_value ( $ assoc_args , 'regex ' , false );
176184
177185 if ( null !== $ this ->regex ) {
178186 $ default_regex_delimiter = false ;
179- $ this ->regex_flags = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'regex-flags ' , false );
180- $ this ->regex_delimiter = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'regex-delimiter ' , '' );
187+ $ this ->regex_flags = Utils \get_flag_value ( $ assoc_args , 'regex-flags ' , false );
188+ $ this ->regex_delimiter = Utils \get_flag_value ( $ assoc_args , 'regex-delimiter ' , '' );
181189 if ( '' === $ this ->regex_delimiter ) {
182190 $ this ->regex_delimiter = chr ( 1 );
183191 $ default_regex_delimiter = true ;
184192 }
185193 }
186194
187- $ regex_limit = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'regex-limit ' );
195+ $ regex_limit = Utils \get_flag_value ( $ assoc_args , 'regex-limit ' );
188196 if ( null !== $ regex_limit ) {
189197 if ( ! preg_match ( '/^(?:[0-9]+|-1)$/ ' , $ regex_limit ) || 0 === (int ) $ regex_limit ) {
190198 WP_CLI ::error ( '`--regex-limit` expects a non-zero positive integer or -1. ' );
@@ -215,16 +223,16 @@ public function __invoke( $args, $assoc_args ) {
215223 }
216224 }
217225
218- $ this ->skip_columns = explode ( ', ' , \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'skip-columns ' ) );
219- $ this ->skip_tables = explode ( ', ' , \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'skip-tables ' ) );
220- $ this ->include_columns = array_filter ( explode ( ', ' , \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'include-columns ' ) ) );
226+ $ this ->skip_columns = explode ( ', ' , Utils \get_flag_value ( $ assoc_args , 'skip-columns ' ) );
227+ $ this ->skip_tables = explode ( ', ' , Utils \get_flag_value ( $ assoc_args , 'skip-tables ' ) );
228+ $ this ->include_columns = array_filter ( explode ( ', ' , Utils \get_flag_value ( $ assoc_args , 'include-columns ' ) ) );
221229
222230 if ( $ old === $ new && ! $ this ->regex ) {
223231 WP_CLI ::warning ( "Replacement value ' {$ old }' is identical to search value ' {$ new }'. Skipping operation. " );
224232 exit ;
225233 }
226234
227- $ export = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'export ' );
235+ $ export = Utils \get_flag_value ( $ assoc_args , 'export ' );
228236 if ( null !== $ export ) {
229237 if ( $ this ->dry_run ) {
230238 WP_CLI ::error ( 'You cannot supply --dry-run and --export at the same time. ' );
@@ -239,15 +247,15 @@ public function __invoke( $args, $assoc_args ) {
239247 WP_CLI ::error ( sprintf ( 'Unable to open export file "%s" for writing: %s. ' , $ assoc_args ['export ' ], $ error ['message ' ] ) );
240248 }
241249 }
242- $ export_insert_size = WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'export_insert_size ' , 50 );
250+ $ export_insert_size = Utils \get_flag_value ( $ assoc_args , 'export_insert_size ' , 50 );
243251 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- See the code, this is deliberate.
244252 if ( (int ) $ export_insert_size == $ export_insert_size && $ export_insert_size > 0 ) {
245253 $ this ->export_insert_size = $ export_insert_size ;
246254 }
247255 $ php_only = true ;
248256 }
249257
250- $ log = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'log ' );
258+ $ log = Utils \get_flag_value ( $ assoc_args , 'log ' );
251259 if ( null !== $ log ) {
252260 if ( true === $ log || '- ' === $ log ) {
253261 $ this ->log_handle = STDOUT ;
@@ -259,12 +267,12 @@ public function __invoke( $args, $assoc_args ) {
259267 }
260268 }
261269 if ( $ this ->log_handle ) {
262- $ before_context = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'before_context ' );
270+ $ before_context = Utils \get_flag_value ( $ assoc_args , 'before_context ' );
263271 if ( null !== $ before_context && preg_match ( '/^[0-9]+$/ ' , $ before_context ) ) {
264272 $ this ->log_before_context = (int ) $ before_context ;
265273 }
266274
267- $ after_context = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'after_context ' );
275+ $ after_context = Utils \get_flag_value ( $ assoc_args , 'after_context ' );
268276 if ( null !== $ after_context && preg_match ( '/^[0-9]+$/ ' , $ after_context ) ) {
269277 $ this ->log_after_context = (int ) $ after_context ;
270278 }
@@ -297,14 +305,14 @@ public function __invoke( $args, $assoc_args ) {
297305 );
298306 }
299307
300- $ this ->log_colors = self :: get_colors ( $ assoc_args , $ default_log_colors );
308+ $ this ->log_colors = $ this -> get_colors ( $ assoc_args , $ default_log_colors );
301309 $ this ->log_encoding = 0 === strpos ( $ wpdb ->charset , 'utf8 ' ) ? 'UTF-8 ' : false ;
302310 }
303311 }
304312
305- $ this ->report = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'report ' , true );
313+ $ this ->report = Utils \get_flag_value ( $ assoc_args , 'report ' , true );
306314 // Defaults to true if logging, else defaults to false.
307- $ this ->report_changed_only = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'report-changed-only ' , null !== $ this ->log_handle );
315+ $ this ->report_changed_only = Utils \get_flag_value ( $ assoc_args , 'report-changed-only ' , null !== $ this ->log_handle );
308316
309317 if ( $ this ->regex_flags ) {
310318 $ php_only = true ;
@@ -314,7 +322,7 @@ public function __invoke( $args, $assoc_args ) {
314322 $ this ->skip_columns [] = 'user_pass ' ;
315323
316324 // Get table names based on leftover $args or supplied $assoc_args
317- $ tables = \ WP_CLI \ Utils \wp_get_table_names ( $ args , $ assoc_args );
325+ $ tables = Utils \wp_get_table_names ( $ args , $ assoc_args );
318326
319327 foreach ( $ tables as $ table ) {
320328
@@ -418,7 +426,7 @@ public function __invoke( $args, $assoc_args ) {
418426 }
419427
420428 if ( $ this ->report && ! empty ( $ report ) ) {
421- $ table = new \ cli \ Table ();
429+ $ table = new Table ();
422430 $ table ->setHeaders ( array ( 'Table ' , 'Column ' , 'Replacements ' , 'Type ' ) );
423431 $ table ->setRows ( $ report );
424432 $ table ->display ();
@@ -429,7 +437,7 @@ public function __invoke( $args, $assoc_args ) {
429437 $ success_message = 1 === $ total ? "Made 1 replacement and exported to {$ assoc_args ['export ' ]}. " : "Made {$ total } replacements and exported to {$ assoc_args ['export ' ]}. " ;
430438 } else {
431439 $ success_message = 1 === $ total ? 'Made 1 replacement. ' : "Made $ total replacements. " ;
432- if ( $ total && 'Default ' !== WP_CLI \ Utils \wp_get_cache_type () ) {
440+ if ( $ total && 'Default ' !== Utils \wp_get_cache_type () ) {
433441 $ success_message .= ' Please remember to flush your persistent object cache with `wp cache flush`. ' ;
434442 }
435443 }
@@ -450,15 +458,15 @@ private function php_export_table( $table, $old, $new ) {
450458 'chunk_size ' => $ chunk_size ,
451459 );
452460
453- $ replacer = new \ WP_CLI \ SearchReplacer ( $ old , $ new , $ this ->recurse_objects , $ this ->regex , $ this ->regex_flags , $ this ->regex_delimiter , false , $ this ->regex_limit );
461+ $ replacer = new SearchReplacer ( $ old , $ new , $ this ->recurse_objects , $ this ->regex , $ this ->regex_flags , $ this ->regex_delimiter , false , $ this ->regex_limit );
454462 $ col_counts = array_fill_keys ( $ all_columns , 0 );
455463 if ( $ this ->verbose && 'table ' === $ this ->format ) {
456464 $ this ->start_time = microtime ( true );
457465 WP_CLI ::log ( sprintf ( 'Checking: %s ' , $ table ) );
458466 }
459467
460468 $ rows = array ();
461- foreach ( new \ WP_CLI \ Iterators \Table ( $ args ) as $ i => $ row ) {
469+ foreach ( new Iterators \Table ( $ args ) as $ i => $ row ) {
462470 $ row_fields = array ();
463471 foreach ( $ all_columns as $ col ) {
464472 $ value = $ row ->$ col ;
@@ -527,15 +535,15 @@ private function php_handle_col( $col, $primary_keys, $table, $old, $new ) {
527535 global $ wpdb ;
528536
529537 $ count = 0 ;
530- $ replacer = new \ WP_CLI \ SearchReplacer ( $ old , $ new , $ this ->recurse_objects , $ this ->regex , $ this ->regex_flags , $ this ->regex_delimiter , null !== $ this ->log_handle , $ this ->regex_limit );
538+ $ replacer = new SearchReplacer ( $ old , $ new , $ this ->recurse_objects , $ this ->regex , $ this ->regex_flags , $ this ->regex_delimiter , null !== $ this ->log_handle , $ this ->regex_limit );
531539
532540 $ table_sql = self ::esc_sql_ident ( $ table );
533541 $ col_sql = self ::esc_sql_ident ( $ col );
534542 $ where = $ this ->regex ? '' : " WHERE $ col_sql " . $ wpdb ->prepare ( ' LIKE BINARY %s ' , '% ' . self ::esc_like ( $ old ) . '% ' );
535543 $ escaped_primary_keys = self ::esc_sql_ident ( $ primary_keys );
536544 $ primary_keys_sql = implode ( ', ' , $ escaped_primary_keys );
537545 $ order_by_keys = array_map (
538- function ( $ key ) {
546+ static function ( $ key ) {
539547 return "{$ key } ASC " ;
540548 },
541549 $ escaped_primary_keys
@@ -544,6 +552,10 @@ function( $key ) {
544552 $ limit = 1000 ;
545553 $ offset = 0 ;
546554
555+ // Updates have to be deferred to after the chunking is completed, as
556+ // the offset will otherwise not work correctly.
557+ $ updates = [];
558+
547559 // 2 errors:
548560 // - WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
549561 // - WordPress.CodeAnalysis.AssignmentInCondition -- no reason to do copy-paste for a single valid assignment in while
@@ -552,7 +564,7 @@ function( $key ) {
552564 foreach ( $ rows as $ keys ) {
553565 $ where_sql = '' ;
554566 foreach ( (array ) $ keys as $ k => $ v ) {
555- if ( strlen ( $ where_sql ) ) {
567+ if ( '' !== $ where_sql ) {
556568 $ where_sql .= ' AND ' ;
557569 }
558570 $ where_sql .= self ::esc_sql_ident ( $ k ) . ' = ' . self ::esc_sql_value ( $ v );
@@ -576,21 +588,24 @@ function( $key ) {
576588 $ replacer ->clear_log_data ();
577589 }
578590
579- if ( $ this ->dry_run ) {
580- $ count ++;
581- } else {
591+ $ count ++;
592+ if ( ! $ this ->dry_run ) {
582593 $ update_where = array ();
583594 foreach ( (array ) $ keys as $ k => $ v ) {
584595 $ update_where [ $ k ] = $ v ;
585596 }
586597
587- $ count += $ wpdb -> update ( $ table , array ( $ col => $ value ), $ update_where ) ;
598+ $ updates [] = [ $ table , array ( $ col => $ value ), $ update_where ] ;
588599 }
589600 }
590601
591602 $ offset += $ limit ;
592603 }
593604
605+ foreach ( $ updates as $ update ) {
606+ $ wpdb ->update ( ...$ update );
607+ }
608+
594609 if ( $ this ->verbose && 'table ' === $ this ->format ) {
595610 $ time = round ( microtime ( true ) - $ this ->start_time , 3 );
596611 WP_CLI ::log ( sprintf ( '%d rows affected using PHP (in %ss). ' , $ count , $ time ) );
@@ -728,7 +743,7 @@ private static function esc_like( $old ) {
728743 * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
729744 */
730745 private static function esc_sql_ident ( $ idents ) {
731- $ backtick = function ( $ v ) {
746+ $ backtick = static function ( $ v ) {
732747 // Escape any backticks in the identifier by doubling.
733748 return '` ' . str_replace ( '` ' , '`` ' , $ v ) . '` ' ;
734749 };
@@ -745,7 +760,7 @@ private static function esc_sql_ident( $idents ) {
745760 * @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings.
746761 */
747762 private static function esc_sql_value ( $ values ) {
748- $ quote = function ( $ v ) {
763+ $ quote = static function ( $ v ) {
749764 // Don't quote integer values to avoid MySQL's implicit type conversion.
750765 if ( preg_match ( '/^[+-]?[0-9]{1,20}$/ ' , $ v ) ) { // MySQL BIGINT UNSIGNED max 18446744073709551615 (20 digits).
751766 return esc_sql ( $ v );
@@ -772,18 +787,18 @@ private static function esc_sql_value( $values ) {
772787 private function get_colors ( $ assoc_args , $ colors ) {
773788 $ color_reset = WP_CLI ::colorize ( '%n ' );
774789
775- $ color_code_callback = function ( $ v ) {
790+ $ color_code_callback = static function ( $ v ) {
776791 return substr ( $ v , 1 );
777792 };
778793
779- $ color_codes = array_keys ( \ cli \ Colors::getColors () );
794+ $ color_codes = array_keys ( Colors::getColors () );
780795 $ color_codes = array_map ( $ color_code_callback , $ color_codes );
781796 $ color_codes = implode ( '' , $ color_codes );
782797
783798 $ color_codes_regex = '/^(?:%[ ' . $ color_codes . '])*$/ ' ;
784799
785800 foreach ( array_keys ( $ colors ) as $ color_col ) {
786- $ col_color_flag = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , $ color_col . '_color ' );
801+ $ col_color_flag = Utils \get_flag_value ( $ assoc_args , $ color_col . '_color ' );
787802 if ( null !== $ col_color_flag ) {
788803 if ( ! preg_match ( $ color_codes_regex , $ col_color_flag , $ matches ) ) {
789804 WP_CLI ::warning ( "Unrecognized percent color code ' $ col_color_flag' for ' {$ color_col }_color'. " );
@@ -891,12 +906,12 @@ private function log_bits( $search_regex, $old_data, $old_matches, $new ) {
891906 $ new_matches = array ();
892907 $ new_data = preg_replace_callback (
893908 $ search_regex ,
894- function ( $ matches ) use ( $ old_matches , $ new , $ is_regex , &$ new_matches , &$ i , &$ diff ) {
909+ static function ( $ matches ) use ( $ old_matches , $ new , $ is_regex , &$ new_matches , &$ i , &$ diff ) {
895910 if ( $ is_regex ) {
896911 // Sub in any back references, "$1", "\2" etc, in the replacement string.
897912 $ new = preg_replace_callback (
898913 '/(?<! \\\\)(?: \\\\\\\\)*((?: \\\\| \\$)[0-9]{1,2}| \\${[0-9]{1,2} \\})/ ' ,
899- function ( $ m ) use ( $ matches ) {
914+ static function ( $ m ) use ( $ matches ) {
900915 $ idx = (int ) str_replace ( array ( '\\' , '$ ' , '{ ' , '} ' ), '' , $ m [0 ] );
901916 return isset ( $ matches [ $ idx ] ) ? $ matches [ $ idx ] : '' ;
902917 },
@@ -939,14 +954,14 @@ function ( $m ) use ( $matches ) {
939954
940955 // Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`.
941956 if ( $ this ->log_before_context && $ old_offset && ! $ append_next ) {
942- $ old_before = \ cli \ safe_substr ( substr ( $ old_data , $ last_old_offset , $ old_offset - $ last_old_offset ), -$ this ->log_before_context , null /*length*/ , false /*is_width*/ , $ encoding );
943- $ new_before = \ cli \ safe_substr ( substr ( $ new_data , $ last_new_offset , $ new_offset - $ last_new_offset ), -$ this ->log_before_context , null /*length*/ , false /*is_width*/ , $ encoding );
957+ $ old_before = safe_substr ( substr ( $ old_data , $ last_old_offset , $ old_offset - $ last_old_offset ), -$ this ->log_before_context , null /*length*/ , false /*is_width*/ , $ encoding );
958+ $ new_before = safe_substr ( substr ( $ new_data , $ last_new_offset , $ new_offset - $ last_new_offset ), -$ this ->log_before_context , null /*length*/ , false /*is_width*/ , $ encoding );
944959 }
945960 if ( $ this ->log_after_context ) {
946961 $ old_end_offset = $ old_offset + strlen ( $ old_match );
947962 $ new_end_offset = $ new_offset + strlen ( $ new_match );
948- $ old_after = \ cli \ safe_substr ( substr ( $ old_data , $ old_end_offset ), 0 , $ this ->log_after_context , false /*is_width*/ , $ encoding );
949- $ new_after = \ cli \ safe_substr ( substr ( $ new_data , $ new_end_offset ), 0 , $ this ->log_after_context , false /*is_width*/ , $ encoding );
963+ $ old_after = safe_substr ( substr ( $ old_data , $ old_end_offset ), 0 , $ this ->log_after_context , false /*is_width*/ , $ encoding );
964+ $ new_after = safe_substr ( substr ( $ new_data , $ new_end_offset ), 0 , $ this ->log_after_context , false /*is_width*/ , $ encoding );
950965 // To lessen context duplication in output, shorten the after context if it overlaps with the next match.
951966 if ( $ i + 1 < $ match_cnt && $ old_end_offset + strlen ( $ old_after ) > $ old_matches [0 ][ $ i + 1 ][1 ] ) {
952967 $ old_after = substr ( $ old_after , 0 , $ old_matches [0 ][ $ i + 1 ][1 ] - $ old_end_offset );
0 commit comments