1
1
<?php
2
2
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
+
3
10
class Search_Replace_Command extends WP_CLI_Command {
4
11
5
12
private $ dry_run ;
@@ -15,8 +22,9 @@ class Search_Replace_Command extends WP_CLI_Command {
15
22
private $ include_columns ;
16
23
private $ format ;
17
24
private $ report ;
18
- private $ report_changed_only ;
25
+ private $ verbose ;
19
26
27
+ private $ report_changed_only ;
20
28
private $ log_handle = null ;
21
29
private $ log_before_context = 40 ;
22
30
private $ log_after_context = 40 ;
@@ -167,24 +175,24 @@ public function __invoke( $args, $assoc_args ) {
167
175
$ new = array_shift ( $ args );
168
176
$ total = 0 ;
169
177
$ 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 );
176
184
177
185
if ( null !== $ this ->regex ) {
178
186
$ 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 ' , '' );
181
189
if ( '' === $ this ->regex_delimiter ) {
182
190
$ this ->regex_delimiter = chr ( 1 );
183
191
$ default_regex_delimiter = true ;
184
192
}
185
193
}
186
194
187
- $ regex_limit = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'regex-limit ' );
195
+ $ regex_limit = Utils \get_flag_value ( $ assoc_args , 'regex-limit ' );
188
196
if ( null !== $ regex_limit ) {
189
197
if ( ! preg_match ( '/^(?:[0-9]+|-1)$/ ' , $ regex_limit ) || 0 === (int ) $ regex_limit ) {
190
198
WP_CLI ::error ( '`--regex-limit` expects a non-zero positive integer or -1. ' );
@@ -215,16 +223,16 @@ public function __invoke( $args, $assoc_args ) {
215
223
}
216
224
}
217
225
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 ' ) ) );
221
229
222
230
if ( $ old === $ new && ! $ this ->regex ) {
223
231
WP_CLI ::warning ( "Replacement value ' {$ old }' is identical to search value ' {$ new }'. Skipping operation. " );
224
232
exit ;
225
233
}
226
234
227
- $ export = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'export ' );
235
+ $ export = Utils \get_flag_value ( $ assoc_args , 'export ' );
228
236
if ( null !== $ export ) {
229
237
if ( $ this ->dry_run ) {
230
238
WP_CLI ::error ( 'You cannot supply --dry-run and --export at the same time. ' );
@@ -239,15 +247,15 @@ public function __invoke( $args, $assoc_args ) {
239
247
WP_CLI ::error ( sprintf ( 'Unable to open export file "%s" for writing: %s. ' , $ assoc_args ['export ' ], $ error ['message ' ] ) );
240
248
}
241
249
}
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 );
243
251
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- See the code, this is deliberate.
244
252
if ( (int ) $ export_insert_size == $ export_insert_size && $ export_insert_size > 0 ) {
245
253
$ this ->export_insert_size = $ export_insert_size ;
246
254
}
247
255
$ php_only = true ;
248
256
}
249
257
250
- $ log = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'log ' );
258
+ $ log = Utils \get_flag_value ( $ assoc_args , 'log ' );
251
259
if ( null !== $ log ) {
252
260
if ( true === $ log || '- ' === $ log ) {
253
261
$ this ->log_handle = STDOUT ;
@@ -259,12 +267,12 @@ public function __invoke( $args, $assoc_args ) {
259
267
}
260
268
}
261
269
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 ' );
263
271
if ( null !== $ before_context && preg_match ( '/^[0-9]+$/ ' , $ before_context ) ) {
264
272
$ this ->log_before_context = (int ) $ before_context ;
265
273
}
266
274
267
- $ after_context = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'after_context ' );
275
+ $ after_context = Utils \get_flag_value ( $ assoc_args , 'after_context ' );
268
276
if ( null !== $ after_context && preg_match ( '/^[0-9]+$/ ' , $ after_context ) ) {
269
277
$ this ->log_after_context = (int ) $ after_context ;
270
278
}
@@ -297,14 +305,14 @@ public function __invoke( $args, $assoc_args ) {
297
305
);
298
306
}
299
307
300
- $ this ->log_colors = self :: get_colors ( $ assoc_args , $ default_log_colors );
308
+ $ this ->log_colors = $ this -> get_colors ( $ assoc_args , $ default_log_colors );
301
309
$ this ->log_encoding = 0 === strpos ( $ wpdb ->charset , 'utf8 ' ) ? 'UTF-8 ' : false ;
302
310
}
303
311
}
304
312
305
- $ this ->report = \ WP_CLI \ Utils \get_flag_value ( $ assoc_args , 'report ' , true );
313
+ $ this ->report = Utils \get_flag_value ( $ assoc_args , 'report ' , true );
306
314
// 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 );
308
316
309
317
if ( $ this ->regex_flags ) {
310
318
$ php_only = true ;
@@ -314,7 +322,7 @@ public function __invoke( $args, $assoc_args ) {
314
322
$ this ->skip_columns [] = 'user_pass ' ;
315
323
316
324
// 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 );
318
326
319
327
foreach ( $ tables as $ table ) {
320
328
@@ -418,7 +426,7 @@ public function __invoke( $args, $assoc_args ) {
418
426
}
419
427
420
428
if ( $ this ->report && ! empty ( $ report ) ) {
421
- $ table = new \ cli \ Table ();
429
+ $ table = new Table ();
422
430
$ table ->setHeaders ( array ( 'Table ' , 'Column ' , 'Replacements ' , 'Type ' ) );
423
431
$ table ->setRows ( $ report );
424
432
$ table ->display ();
@@ -429,7 +437,7 @@ public function __invoke( $args, $assoc_args ) {
429
437
$ success_message = 1 === $ total ? "Made 1 replacement and exported to {$ assoc_args ['export ' ]}. " : "Made {$ total } replacements and exported to {$ assoc_args ['export ' ]}. " ;
430
438
} else {
431
439
$ 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 () ) {
433
441
$ success_message .= ' Please remember to flush your persistent object cache with `wp cache flush`. ' ;
434
442
}
435
443
}
@@ -450,15 +458,15 @@ private function php_export_table( $table, $old, $new ) {
450
458
'chunk_size ' => $ chunk_size ,
451
459
);
452
460
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 );
454
462
$ col_counts = array_fill_keys ( $ all_columns , 0 );
455
463
if ( $ this ->verbose && 'table ' === $ this ->format ) {
456
464
$ this ->start_time = microtime ( true );
457
465
WP_CLI ::log ( sprintf ( 'Checking: %s ' , $ table ) );
458
466
}
459
467
460
468
$ rows = array ();
461
- foreach ( new \ WP_CLI \ Iterators \Table ( $ args ) as $ i => $ row ) {
469
+ foreach ( new Iterators \Table ( $ args ) as $ i => $ row ) {
462
470
$ row_fields = array ();
463
471
foreach ( $ all_columns as $ col ) {
464
472
$ value = $ row ->$ col ;
@@ -527,15 +535,15 @@ private function php_handle_col( $col, $primary_keys, $table, $old, $new ) {
527
535
global $ wpdb ;
528
536
529
537
$ 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 );
531
539
532
540
$ table_sql = self ::esc_sql_ident ( $ table );
533
541
$ col_sql = self ::esc_sql_ident ( $ col );
534
542
$ where = $ this ->regex ? '' : " WHERE $ col_sql " . $ wpdb ->prepare ( ' LIKE BINARY %s ' , '% ' . self ::esc_like ( $ old ) . '% ' );
535
543
$ escaped_primary_keys = self ::esc_sql_ident ( $ primary_keys );
536
544
$ primary_keys_sql = implode ( ', ' , $ escaped_primary_keys );
537
545
$ order_by_keys = array_map (
538
- function ( $ key ) {
546
+ static function ( $ key ) {
539
547
return "{$ key } ASC " ;
540
548
},
541
549
$ escaped_primary_keys
@@ -544,6 +552,10 @@ function( $key ) {
544
552
$ limit = 1000 ;
545
553
$ offset = 0 ;
546
554
555
+ // Updates have to be deferred to after the chunking is completed, as
556
+ // the offset will otherwise not work correctly.
557
+ $ updates = [];
558
+
547
559
// 2 errors:
548
560
// - WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
549
561
// - WordPress.CodeAnalysis.AssignmentInCondition -- no reason to do copy-paste for a single valid assignment in while
@@ -552,7 +564,7 @@ function( $key ) {
552
564
foreach ( $ rows as $ keys ) {
553
565
$ where_sql = '' ;
554
566
foreach ( (array ) $ keys as $ k => $ v ) {
555
- if ( strlen ( $ where_sql ) ) {
567
+ if ( '' !== $ where_sql ) {
556
568
$ where_sql .= ' AND ' ;
557
569
}
558
570
$ where_sql .= self ::esc_sql_ident ( $ k ) . ' = ' . self ::esc_sql_value ( $ v );
@@ -576,21 +588,24 @@ function( $key ) {
576
588
$ replacer ->clear_log_data ();
577
589
}
578
590
579
- if ( $ this ->dry_run ) {
580
- $ count ++;
581
- } else {
591
+ $ count ++;
592
+ if ( ! $ this ->dry_run ) {
582
593
$ update_where = array ();
583
594
foreach ( (array ) $ keys as $ k => $ v ) {
584
595
$ update_where [ $ k ] = $ v ;
585
596
}
586
597
587
- $ count += $ wpdb -> update ( $ table , array ( $ col => $ value ), $ update_where ) ;
598
+ $ updates [] = [ $ table , array ( $ col => $ value ), $ update_where ] ;
588
599
}
589
600
}
590
601
591
602
$ offset += $ limit ;
592
603
}
593
604
605
+ foreach ( $ updates as $ update ) {
606
+ $ wpdb ->update ( ...$ update );
607
+ }
608
+
594
609
if ( $ this ->verbose && 'table ' === $ this ->format ) {
595
610
$ time = round ( microtime ( true ) - $ this ->start_time , 3 );
596
611
WP_CLI ::log ( sprintf ( '%d rows affected using PHP (in %ss). ' , $ count , $ time ) );
@@ -728,7 +743,7 @@ private static function esc_like( $old ) {
728
743
* @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
729
744
*/
730
745
private static function esc_sql_ident ( $ idents ) {
731
- $ backtick = function ( $ v ) {
746
+ $ backtick = static function ( $ v ) {
732
747
// Escape any backticks in the identifier by doubling.
733
748
return '` ' . str_replace ( '` ' , '`` ' , $ v ) . '` ' ;
734
749
};
@@ -745,7 +760,7 @@ private static function esc_sql_ident( $idents ) {
745
760
* @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings.
746
761
*/
747
762
private static function esc_sql_value ( $ values ) {
748
- $ quote = function ( $ v ) {
763
+ $ quote = static function ( $ v ) {
749
764
// Don't quote integer values to avoid MySQL's implicit type conversion.
750
765
if ( preg_match ( '/^[+-]?[0-9]{1,20}$/ ' , $ v ) ) { // MySQL BIGINT UNSIGNED max 18446744073709551615 (20 digits).
751
766
return esc_sql ( $ v );
@@ -772,18 +787,18 @@ private static function esc_sql_value( $values ) {
772
787
private function get_colors ( $ assoc_args , $ colors ) {
773
788
$ color_reset = WP_CLI ::colorize ( '%n ' );
774
789
775
- $ color_code_callback = function ( $ v ) {
790
+ $ color_code_callback = static function ( $ v ) {
776
791
return substr ( $ v , 1 );
777
792
};
778
793
779
- $ color_codes = array_keys ( \ cli \ Colors::getColors () );
794
+ $ color_codes = array_keys ( Colors::getColors () );
780
795
$ color_codes = array_map ( $ color_code_callback , $ color_codes );
781
796
$ color_codes = implode ( '' , $ color_codes );
782
797
783
798
$ color_codes_regex = '/^(?:%[ ' . $ color_codes . '])*$/ ' ;
784
799
785
800
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 ' );
787
802
if ( null !== $ col_color_flag ) {
788
803
if ( ! preg_match ( $ color_codes_regex , $ col_color_flag , $ matches ) ) {
789
804
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 ) {
891
906
$ new_matches = array ();
892
907
$ new_data = preg_replace_callback (
893
908
$ 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 ) {
895
910
if ( $ is_regex ) {
896
911
// Sub in any back references, "$1", "\2" etc, in the replacement string.
897
912
$ new = preg_replace_callback (
898
913
'/(?<! \\\\)(?: \\\\\\\\)*((?: \\\\| \\$)[0-9]{1,2}| \\${[0-9]{1,2} \\})/ ' ,
899
- function ( $ m ) use ( $ matches ) {
914
+ static function ( $ m ) use ( $ matches ) {
900
915
$ idx = (int ) str_replace ( array ( '\\' , '$ ' , '{ ' , '} ' ), '' , $ m [0 ] );
901
916
return isset ( $ matches [ $ idx ] ) ? $ matches [ $ idx ] : '' ;
902
917
},
@@ -939,14 +954,14 @@ function ( $m ) use ( $matches ) {
939
954
940
955
// Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`.
941
956
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 );
944
959
}
945
960
if ( $ this ->log_after_context ) {
946
961
$ old_end_offset = $ old_offset + strlen ( $ old_match );
947
962
$ 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 );
950
965
// To lessen context duplication in output, shorten the after context if it overlaps with the next match.
951
966
if ( $ i + 1 < $ match_cnt && $ old_end_offset + strlen ( $ old_after ) > $ old_matches [0 ][ $ i + 1 ][1 ] ) {
952
967
$ old_after = substr ( $ old_after , 0 , $ old_matches [0 ][ $ i + 1 ][1 ] - $ old_end_offset );
0 commit comments