Skip to content

fix(delivery): track Cloudinary src URLs back to their attachment - #1344

Open
gabrielcld2 wants to merge 2 commits into
developfrom
fix/frontend-attachment-image-delivery
Open

gabrielcld2 wants to merge 2 commits into
developfrom
fix/frontend-attachment-image-delivery

Conversation

@gabrielcld2

Copy link
Copy Markdown
Collaborator

Fixes a front-end regression from #1303 (WPP-1183), found while QAing 3.3.9-build-1.

On the front end, an image rendered by theme code with wp_get_attachment_image() (a template, a shortcode, a widget) loses Cloudinary's delivery features. In 3.3.8 it has lazy loading, responsive breakpoints, data-public-id/SEO attributes and the wp-image-{id} class. On develop it renders as a plain <img> with a static Cloudinary URL. Featured images, image blocks and galleries aren't affected.

Approach

Since #1303, image_downsize() and wp_get_attachment_image_src() return Cloudinary URLs on the front end, so these tags reach Delivery with a Cloudinary src instead of a local one. Delivery::parse_element() already tracks a Cloudinary src back to its attachment through the public ID. It then looked the item up by the relation's sized_url, which is relative to the content dir (/uploads/2026/10/image.jpg), while the keys of $this->known are full URLs (//example.com/wp-content/uploads/2026/10/image.jpg). The lookup never matched, so the tag was left unprocessed.

  • Extracted the URL that set_usability() builds for the known keys into Delivery::get_searchable_url(). Both hooks (cloudinary_content_url and cloudinary_delivery_searchable_url) still apply.
  • parse_element() now uses it for the track-back.
  • Added tests/phpunit/tests/test-delivery-parse-element.php. The track-back test fails on develop and passes on this branch.

The fix is in Delivery, so the front-end protection from #1303 stays in place.

QA notes

  1. Sync an image, then output it from theme code on a front-end page, e.g. with a temporary shortcode in an mu-plugin:
    add_shortcode( 'qa_img', function ( $atts ) {
        return wp_get_attachment_image( (int) $atts['id'], 'medium' );
    } );
  2. Add [qa_img id="<attachment ID>"] to a post and view it.
    • On develop: the <img> has a plain res.cloudinary.com/.../w_300,... src with no data-public-id or data-cloudinary="lazy".
    • On this branch: the <img> has a lazy-load placeholder src, data-public-id, data-responsive, data-seo, and the wp-image-{id} class, the same as in 3.3.8.
  3. Featured images, image blocks, galleries and Cloudinary URLs hard-coded in post content render the same as before.

Verified on wp-env (WP 7.1, PHP 8.5): every image on a test page with a featured image, an image block, a gallery and a shortcode image is attribute-for-attribute identical to 3.3.8, including with a competing image_downsize filter active. PHPUnit, phpcs and phpstan pass.

Since WPP-1183, image_downsize() and wp_get_attachment_image_src() return
Cloudinary URLs on the front end, so tags built by theme code (e.g.
wp_get_attachment_image() in a template or shortcode) reach Delivery with a
Cloudinary src. parse_element() tracks such a src back through the public ID,
but then looked the item up by the relation's sized_url, which is relative to
the content dir, while the keys of $this->known are full URLs. The lookup never
matched, so these tags lost lazy loading, responsive breakpoints and the SEO
attributes they had in 3.3.8.

Extracts the URL set_usability() builds for the known keys into
get_searchable_url(), and uses it for the track-back.

@utkarshcloudinary utkarshcloudinary left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good ✅

Comment thread php/class-delivery.php

$url = $this->known[ $public_id ]['sized_url'];
// The relation's sized_url is relative to the content dir, while the URL keys of $this->known are full URLs.
$url = $this->get_searchable_url( $this->known[ $public_id ] );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking question: This fix also makes the lookup match Cloudinary URLs that are typed into post content by hand. Before, a hard-coded Cloudinary <img> with a synced public ID and no wp-image-{id} class never matched here, so the tag stayed as it was. Now it matches, and rebuild_tag() replaces its src with cloudinary_url().

parse_element() only keeps transformations from the attachment, from cld_params, and from cld_overwrite. Transformations in the URL path (for example e_grayscale or a manual crop) are not read, so the rebuilt URL can lose them.

This is how the 2021 code was meant to work, and tags with the wp-image-{id} class already behave this way, so I don't think this blocks the PR. Can you confirm that your QA of hard-coded Cloudinary URLs included one with path transformations and no wp-image-{id} class? If they are lost, we can handle it in a follow-up, because it also affects tags with the class.

…s is

The track-back also matched Cloudinary URLs typed into content by hand, and
rebuilding them from the attachment dropped any transformations in their path
(e.g. an effect or a manual crop). Before the track-back worked, these tags
were left as they were.

Only track a URL back when its transformations are ones the plugin applies to
the attachment itself: its size, and the attachment's own and global
transformations.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants