diff --git a/features/media-regenerate.feature b/features/media-regenerate.feature index 6b48d1ad..edec0726 100644 --- a/features/media-regenerate.feature +++ b/features/media-regenerate.feature @@ -1933,3 +1933,103 @@ Feature: Regenerate WordPress attachments """ site_icon-270 """ + + Scenario: Update post content references when regenerating a specific image size + Given download: + | path | url | + | {CACHE_DIR}/canola.jpg | http://wp-cli.org/behat-data/canola.jpg | + And a wp-content/mu-plugins/media-settings.php file: + """ + =' ) ) { @@ -212,7 +218,7 @@ public function regenerate( $args, $assoc_args = array() ) { // @phpstan-ignore function.deprecated Utils\wp_clear_object_cache(); } - $this->process_regeneration( $post_id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $number . '/' . $count, $successes, $errors, $skips ); + $this->process_regeneration( $post_id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $update_attachment_refs, $number . '/' . $count, $successes, $errors, $skips ); } if ( isset( $image_size_filters ) ) { @@ -384,7 +390,7 @@ public function import( $args, $assoc_args = array() ) { } else { $tempfile = $this->make_copy( $file ); } - $name = Utils\basename( $file ); + $name = Path::basename( $file ); if ( Utils\get_flag_value( $assoc_args, 'preserve-filetime' ) ) { $file_time = @filemtime( $file ); @@ -402,7 +408,7 @@ public function import( $args, $assoc_args = array() ) { ++$errors; continue; } - $name = (string) strtok( Utils\basename( $file ), '?' ); + $name = (string) strtok( Path::basename( $file ), '?' ); } if ( ! empty( $assoc_args['file_name'] ) ) { @@ -448,7 +454,7 @@ public function import( $args, $assoc_args = array() ) { } if ( empty( $post_array['post_title'] ) ) { - $post_array['post_title'] = preg_replace( '/\.[^.]+$/', '', Utils\basename( $file ) ); + $post_array['post_title'] = preg_replace( '/\.[^.]+$/', '', Path::basename( $file ) ); } if ( Utils\get_flag_value( $assoc_args, 'skip-copy' ) ) { @@ -679,7 +685,7 @@ private function gcd( $num1, $num2 ) { */ private function make_copy( $path ) { $dir = get_temp_dir(); - $filename = Utils\basename( $path ); + $filename = Path::basename( $path ); if ( empty( $filename ) ) { $filename = (string) time(); } @@ -715,6 +721,7 @@ private function get_image_sizes_description( array $sizes, $noun, $default_if_e * @param bool $only_missing * @param bool $delete_unknown * @param string[] $image_sizes + * @param bool $update_attachment_refs * @param string $progress * @param int $successes * @param int $errors @@ -724,7 +731,7 @@ private function get_image_sizes_description( array $sizes, $noun, $default_if_e * @param-out int $skips * @return void */ - private function process_regeneration( $id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $progress, &$successes, &$errors, &$skips ) { + private function process_regeneration( $id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $update_attachment_refs, $progress, &$successes, &$errors, &$skips ) { $title = get_the_title( $id ); if ( '' === $title ) { @@ -752,6 +759,20 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete $original_meta = wp_get_attachment_metadata( $id ); + $old_size_urls = array(); + if ( $update_attachment_refs && is_array( $original_meta ) && ! empty( $original_meta['sizes'] ) ) { + $attachment_url = wp_get_attachment_url( $id ); + if ( $attachment_url ) { + $dir_url = trailingslashit( dirname( $attachment_url ) ); + $sizes_to_track = $image_sizes ?: array_keys( $original_meta['sizes'] ); + foreach ( $sizes_to_track as $size ) { + if ( ! empty( $original_meta['sizes'][ $size ]['file'] ) ) { + $old_size_urls[ $size ] = $dir_url . $original_meta['sizes'][ $size ]['file']; + } + } + } + } + if ( $delete_unknown ) { $this->delete_unknown_image_sizes( $id, $fullsizepath ); @@ -846,6 +867,35 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete WP_CLI::log( "$progress Regenerated thumbnails for $att_desc." ); } + + if ( $update_attachment_refs && ! empty( $old_size_urls ) && is_array( $metadata ) && ! empty( $metadata['sizes'] ) ) { + $attachment_url = wp_get_attachment_url( $id ); + if ( $attachment_url ) { + $dir_url = trailingslashit( dirname( $attachment_url ) ); + /** + * @var array> $new_sizes + */ + $new_sizes = is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array(); + $url_replacements = array(); + foreach ( $old_size_urls as $size => $old_url ) { + $size_data = $new_sizes[ $size ] ?? null; + if ( ! is_array( $size_data ) || empty( $size_data['file'] ) ) { + continue; + } + /** + * @var array{file: string} $size_data + */ + $new_url = $dir_url . $size_data['file']; + if ( $old_url !== $new_url ) { + $url_replacements[ $old_url ] = $new_url; + } + } + if ( ! empty( $url_replacements ) ) { + $this->update_post_content_for_attachment( $url_replacements ); + } + } + } + ++$successes; } @@ -1762,4 +1812,62 @@ private function delete_unknown_image_sizes( $id, $fullsizepath ) { // @phpstan-ignore argument.type wp_update_attachment_metadata( $id, $original_meta ); } + + /** + * Updates post content replacing old attachment URLs with new ones in a single query. + * + * Applies all replacements as nested REPLACE() calls so only one table scan is needed. + * + * @param array $url_replacements Map of old URL => new URL. + * @return void + */ + private function update_post_content_for_attachment( array $url_replacements ) { + global $wpdb; + + if ( empty( $url_replacements ) ) { + return; + } + + $replace_expr = 'post_content'; + $replace_args = array(); + $where_clauses = array(); + $where_args = array(); + + foreach ( $url_replacements as $old_url => $new_url ) { + $replace_expr = "REPLACE($replace_expr, %s, %s)"; + $replace_args[] = $old_url; + $replace_args[] = $new_url; + $where_clauses[] = 'post_content LIKE %s'; + $where_args[] = '%' . $wpdb->esc_like( $old_url ) . '%'; + } + + $where_sql = implode( ' OR ', $where_clauses ); + + // First, find the IDs of posts whose content will be updated so we can clear their object cache entries. + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $post_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID FROM {$wpdb->posts} WHERE post_type <> 'revision' AND ({$where_sql})", + ...$where_args + ) + ); + + $result = $wpdb->query( + $wpdb->prepare( + "UPDATE {$wpdb->posts} SET post_content = {$replace_expr} WHERE post_type <> 'revision' AND ({$where_sql})", + ...array_merge( $replace_args, $where_args ) + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + if ( false === $result ) { + WP_CLI::warning( 'Failed to update post content references for attachment.' ); + } else { + if ( ! empty( $post_ids ) ) { + foreach ( $post_ids as $post_id ) { + clean_post_cache( (int) $post_id ); + } + } + wp_cache_set( 'last_changed', microtime(), 'posts' ); + } + } }