Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6b308fd
Initial plan
Copilot Nov 10, 2025
41bbda5
Add progress bar support to search-replace command
Copilot Nov 10, 2025
cd31f29
Add Behat tests for progress bar functionality
Copilot Nov 10, 2025
61e02c0
Remove unused progress_bar property
Copilot Nov 10, 2025
77534f5
Refactor progress tick to reduce code duplication
Copilot Nov 10, 2025
59217ca
Lint fix
swissspidy Nov 10, 2025
681d4c0
Revert to early continue pattern and fix progress tick to count by batch
Copilot Nov 10, 2025
1f1bb20
Suppress progress bar when logging is enabled
Copilot Nov 13, 2025
8d65105
Update test to include search string in generated post content
Copilot Nov 13, 2025
b5da516
Fix test by explicitly setting option with search string
Copilot Nov 14, 2025
46ba4ed
Fix test to check STDERR for progress bar output
Copilot Nov 14, 2025
3e0e5a0
Add WP_CLI::log message before progress bar to show processing status
Copilot Nov 14, 2025
3efdf0a
Fix progress bar sprintf format strings
Copilot Nov 14, 2025
0d531c9
Move progress bar initialization into while loop
Copilot Nov 14, 2025
077285b
Merge branch 'main' into copilot/add-progress-bar-indicator
swissspidy Jan 20, 2026
b0eeefa
Merge branch 'main' into copilot/add-progress-bar-indicator
swissspidy Mar 11, 2026
b6b7e3b
Apply suggestion from @swissspidy
swissspidy Mar 11, 2026
b3f2da1
Remove WP_CLI::log calls that break existing tests
Copilot Mar 11, 2026
9a2facf
Merge branch 'main' into copilot/add-progress-bar-indicator
swissspidy Mar 22, 2026
f81af96
Merge branch 'main' into copilot/add-progress-bar-indicator
swissspidy May 19, 2026
9abe70a
Flush persistent object cache after replacements
Copilot May 19, 2026
146a8ca
Remove redundant function_exists check for wp_cache_flush
Copilot May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions features/search-replace.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1392,3 +1392,29 @@ Feature: Do global search/replace
"""
Success: Made 0 replacements.
"""

@require-mysql
Scenario: Progress bar shows when not in verbose mode
Given a WP install
And I run `wp post generate --count=100`

When I run `wp search-replace http://example.com http://example.org --precise`
Then STDOUT should contain:
"""
Updating
"""

@require-mysql
Scenario: Progress bar does not show in verbose mode
Given a WP install
And I run `wp post generate --count=10`

When I run `wp search-replace http://example.com http://example.org --verbose`
Then STDOUT should contain:
"""
Checking:
"""
And STDOUT should not contain:
"""
Updating
"""
112 changes: 89 additions & 23 deletions src/Search_Replace_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,18 @@ private function php_export_table( $table, $old, $new ) {
WP_CLI::log( sprintf( 'Checking: %s', $table ) );
}

// Set up progress bar if appropriate
$progress = null;
if ( $this->should_show_progress_bar() ) {
global $wpdb;
$table_sql = self::esc_sql_ident( $table );
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
$total_rows = $wpdb->get_var( "SELECT COUNT(*) FROM {$table_sql}" );
if ( $total_rows > 0 ) {
$progress = \WP_CLI\Utils\make_progress_bar( sprintf( 'Exporting %s', $table ), $total_rows );
}
}

$rows = array();
foreach ( new Iterators\Table( $args ) as $i => $row ) {
$row_fields = array();
Expand All @@ -572,9 +584,17 @@ private function php_export_table( $table, $old, $new ) {
$row_fields[ $col ] = $value;
}
$rows[] = $row_fields;

if ( $progress ) {
$progress->tick();
}
}
$this->write_sql_row_fields( $table, $rows );

if ( $progress ) {
$progress->finish();
}

$table_report = array();
$total_rows = 0;
$total_cols = 0;
Expand Down Expand Up @@ -650,6 +670,17 @@ static function ( $key ) {
$order_by_sql = 'ORDER BY ' . implode( ',', $order_by_keys );
$limit = 1000;

// Set up progress bar if appropriate
$progress = null;
if ( $this->should_show_progress_bar() ) {
// Count total rows to process
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
$total_rows = $wpdb->get_var( "SELECT COUNT(*) FROM {$table_sql} {$where_key}" );
if ( $total_rows > 0 ) {
$progress = \WP_CLI\Utils\make_progress_bar( sprintf( 'Updating %s.%s', $table, $col ), $total_rows );
}
}

// 2 errors:
// - WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
// - WordPress.CodeAnalysis.AssignmentInCondition -- no reason to do copy-paste for a single valid assignment in while
Expand All @@ -667,35 +698,32 @@ static function ( $key ) {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
$col_value = $wpdb->get_var( "SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql}" );

if ( '' === $col_value ) {
continue;
}

$value = $replacer->run( $col_value );
if ( '' !== $col_value ) {
$value = $replacer->run( $col_value );

if ( $value === $col_value ) {
continue;
}
if ( $value !== $col_value && gettype( $value ) === gettype( $col_value ) ) {
// In case a needed re-serialization was unsuccessful, we should not update the value,
// as this implies we hit an exception while processing.

// 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();
}

if ( $this->log_handle ) {
$this->log_php_diff( $col, $keys, $table, $old, $new, $replacer->get_log_data() );
$replacer->clear_log_data();
}
++$count;
if ( ! $this->dry_run ) {
$update_where = array();
foreach ( (array) $keys as $k => $v ) {
$update_where[ $k ] = $v;
}

++$count;
if ( ! $this->dry_run ) {
$update_where = array();
foreach ( (array) $keys as $k => $v ) {
$update_where[ $k ] = $v;
$wpdb->update( $table, [ $col => $value ], $update_where );
}
}
}
Comment thread
swissspidy marked this conversation as resolved.
Outdated

$wpdb->update( $table, [ $col => $value ], $update_where );
if ( $progress ) {
$progress->tick();
}
}

Comment thread
swissspidy marked this conversation as resolved.
Outdated
Expand Down Expand Up @@ -735,6 +763,10 @@ static function ( $key ) {
$where_key = 'WHERE ' . implode( ' AND ', $where_key_conditions );
}

if ( $progress ) {
$progress->finish();
}

if ( $this->verbose && 'table' === $this->format ) {
$time = round( microtime( true ) - $this->start_time, 3 );
WP_CLI::log( sprintf( '%d rows affected using PHP (in %ss).', $count, $time ) );
Expand Down Expand Up @@ -850,6 +882,40 @@ private static function is_text_col( $type ) {
return false;
}

/**
* Determines whether a progress bar should be shown.
*
* @return bool True if progress bar should be shown.
*/
private function should_show_progress_bar() {
// Don't show progress bar if in quiet mode
if ( WP_CLI::get_config( 'quiet' ) ) {
return false;
}

// Don't show progress bar if exporting to STDOUT
if ( STDOUT === $this->export_handle ) {
return false;
}

// Don't show progress bar if logging to STDOUT
if ( STDOUT === $this->log_handle ) {
return false;
}

// Don't show progress bar if verbose mode is enabled (it shows row-by-row updates)
if ( $this->verbose ) {
return false;
}

// Don't show progress bar if format is 'count'
if ( 'count' === $this->format ) {
return false;
}

return true;
}

private static function esc_like( $old ) {
global $wpdb;

Expand Down
Loading