Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When transferring all in-content images, ensure any image URLs in the content are updated to the new image #1283

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1718294
After we download images to a site, update the URLs in the content if…
dkotter Oct 10, 2024
414cc68
Update image URLs in the classic editor
dkotter Oct 10, 2024
0a3f6fa
More efficient processing
dkotter Oct 10, 2024
7efd4a4
Instead of always using the full image size, try and use the same ima…
dkotter Oct 11, 2024
3e39ff2
Make things more efficient by only updating the post content once, af…
dkotter Oct 11, 2024
49d66e6
Ensure we only update images in the classic editor if they match the …
dkotter Oct 11, 2024
3778154
Don't update the image src if we don't have an image URL. Better fall…
dkotter Oct 11, 2024
9d8a8fb
Merge branch 'develop' into feature/update-in-content-image-urls
Sidsector9 Oct 22, 2024
04632fa
Merge branch 'develop' into feature/update-in-content-image-urls
iamdharmesh Jan 21, 2025
434c0e0
Fix ID comparison.
iamdharmesh Jan 21, 2025
6c8f0f4
Fix duplicate media creation.
iamdharmesh Jan 22, 2025
0fbf1f1
Add image parsing from post content for the classic editor.
iamdharmesh Jan 23, 2025
40e59e7
Add E2E tests.
iamdharmesh Jan 23, 2025
7e12ecd
update tests for the wp minimum env.
iamdharmesh Jan 23, 2025
8bcd31f
Fix tests in wp trunk env.
iamdharmesh Jan 23, 2025
0cbd14d
Revert "Fix duplicate media creation."
iamdharmesh Jan 23, 2025
45afeb2
Fix PHPunit tests.
iamdharmesh Jan 23, 2025
9ec968a
Merge branch 'develop' of github.com:10up/distributor into feature/up…
iamdharmesh Jan 31, 2025
b6eef64
Remove TODO statement
dkotter Feb 6, 2025
8ef0ea3
Merge pull request #1283 from 10up/feature/update-in-content-image-urls
dkotter Feb 6, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public function push( $post, $args = array() ) {
*/
if ( apply_filters( 'dt_push_post_media', true, $new_post_id, $post_media, $post_id, $args, $this ) ) {
Utils\set_media( $new_post_id, $post_media, [ 'use_filesystem' => true ] );
};
}

$media_errors = get_transient( 'dt_media_errors_' . $new_post_id );

Expand Down
182 changes: 182 additions & 0 deletions includes/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,8 @@ function set_media( $post_id, $media, $args = [] ) {
$media = ( false !== $featured_key ) ? array( $media[ $featured_key ] ) : array();
}

$image_urls_to_update = [];

foreach ( $media as $media_item ) {

$args['source_file'] = $media_item['source_file'];
Expand Down Expand Up @@ -782,6 +784,11 @@ function set_media( $post_id, $media, $args = [] ) {
set_meta( $image_id, $media_item['meta'] );
}

// Save the images that we need to try updating in the content.
if ( 'featured' !== $settings['media_handling'] ) { // TODO: do we want a new setting for this?
dkotter marked this conversation as resolved.
Show resolved Hide resolved
$image_urls_to_update[ $image_id ] = $media_item;
}

// Transfer post properties
wp_update_post(
[
Expand All @@ -793,6 +800,11 @@ function set_media( $post_id, $media, $args = [] ) {
);
}

// Update image URLs in content if needed.
if ( ! empty( $image_urls_to_update ) ) {
update_content_image_urls( (int) $post_id, $image_urls_to_update );
}

if ( ! $found_featured_image ) {
delete_post_meta( $post_id, '_thumbnail_id' );
}
Expand Down Expand Up @@ -1031,6 +1043,176 @@ function process_media( $url, $post_id, $args = [] ) {
return (int) $result;
}

/**
* Find and update an image tag.
*
* @param string $content The post content.
* @param array $media_item The old media item details.
* @param int $image_id The new image ID.
* @return string
*/
function update_image_tag( string $content, array $media_item, int $image_id ) {
$processor = new \WP_HTML_Tag_Processor( $content );

while ( $processor->next_tag( 'img' ) ) {
$classes = explode( ' ', $processor->get_attribute( 'class' ) ?? ' ' );

// Only process the image that matches the old ID.
if (
! is_array( $classes ) ||
! in_array( 'wp-image-' . $media_item['id'], $classes, true )
) {
continue;
}

// Try to determine the image size from the size class WordPress adds.
$image_size = 'full';
$classes = explode( ' ', $processor->get_attribute( 'class' ) ?? [] );
$size_classes = array_filter(
$classes,
function ( $image_class ) {
return false !== strpos( $image_class, 'size-' );
}
);

if ( ! empty( $size_classes ) ) {
// If an image happens to have multiple size classes, just use the first.
$size_class = reset( $size_classes );
$image_size = str_replace( 'size-', '', $size_class );
}

$src = wp_get_attachment_image_url( $image_id, $image_size );

// If the image size can't be found, try to get the full size.
if ( ! $src ) {
$src = wp_get_attachment_image_url( $image_id, 'full' );

// If we still don't have an image, skip this block.
if ( ! $src ) {
continue;
}
}

$processor->set_attribute( 'src', $src );
$processor->add_class( 'wp-image-' . $image_id );
$processor->remove_class( 'wp-image-' . $media_item['id'] );
$processor->remove_attribute( 'srcset' );
$processor->remove_attribute( 'sizes' );
}

return $processor->get_updated_html();
}

/**
* Find and update an image block.
*
* @param array $blocks All blocks in a post.
* @param array $media_item The old media item details.
* @param int $image_id The new image ID.
* @return array
*/
function update_image_block( array $blocks, array $media_item, int $image_id ) {
// Find and update all image blocks that match the old image ID.
foreach ( $blocks as $key => $block ) {
// Recurse into inner blocks.
if ( ! empty( $block['innerBlocks'] ) ) {
$blocks[ $key ]['innerBlocks'] = update_image_block( $block['innerBlocks'], $media_item, $image_id );
}

// If the block is an image block and the ID matches, update the ID and URL.
if ( 'core/image' === $block['blockName'] && $media_item['id'] === $block['attrs']['id'] ) {
$image_size = $block['attrs']['sizeSlug'] ?? 'full';

$blocks[ $key ]['attrs']['id'] = $image_id;

$processor = new \WP_HTML_Tag_Processor( $blocks[ $key ]['innerHTML'] );

// Use the HTML API to update the image src and class.
if ( $processor->next_tag( 'img' ) ) {
$src = wp_get_attachment_image_url( $image_id, $image_size );

// If the image size can't be found, try to get the full size.
if ( ! $src ) {
$src = wp_get_attachment_image_url( $image_id, 'full' );

// If we still don't have an image, skip this block.
if ( ! $src ) {
continue;
}
}

$processor->set_attribute( 'src', $src );
$processor->add_class( 'wp-image-' . $image_id );
$processor->remove_class( 'wp-image-' . $media_item['id'] );

$blocks[ $key ]['innerHTML'] = $processor->get_updated_html();
$blocks[ $key ]['innerContent'][0] = $processor->get_updated_html();
Comment on lines +1139 to +1157
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love feedback on if there's a better approach here. I was hoping I could just update the block attributes and then re-render the block to have it rebuild the saved block markup but nothing I tried actually worked, so I ended up with this approach of basically doing a search/replace from the old URL to the new URL

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried using render_block and serialize_block to avoid directly assigning to innerHTML, but that broke the image block due to inconsistency between the content returned by save() and edit().

}
}
}

return $blocks;
}

/**
* Update all old image URLs with the new ones.
*
* @param int $post_id The post ID.
* @param array $images The old image details.
*/
function update_content_image_urls( int $post_id, array $images ) {
$dt_post = new DistributorPost( $post_id );

if ( ! $dt_post ) {
return;
}

/**
* Filter whether image URLS should be updated in the content.
*
* @since x.x.x
* @hook dt_update_content_image_urls
*
* @param {bool} true Whether image URLs should be updated. Default `true`.
* @param {int} $post_id The post ID.
* @param {array} $images The old image details.
*
* @return {bool} Whether image URLs should be updated.
*/
if ( ! apply_filters( 'dt_update_content_image_urls', true, $post_id, $images ) ) {
return;
}

$content = $dt_post->post->post_content;
$has_blocks = $dt_post->has_blocks();

foreach ( $images as $image_id => $media_item ) {
// Process block and classic editor content differently.
if ( $has_blocks ) {
$blocks = parse_blocks( $content );

// Update the image block attributes.
$updated_blocks = update_image_block( $blocks, $media_item, $image_id );
$content = serialize_blocks( $updated_blocks );
} else {
$content = update_image_tag( $content, $media_item, $image_id );
}
}

// No need to update if the content wasn't modified.
if ( $content === $dt_post->post->post_content ) {
return;
}

// Update the post content.
wp_update_post(
[
'ID' => $post_id,
'post_content' => $content,
]
);
}

/**
* Return whether a post type is compatible with the block editor.
*
Expand Down
Loading