Skip to content

Report failed DAM imports instead of claiming success - #1358

Open
gabrielcld2 wants to merge 1 commit into
developfrom
fix/download-sync-failed-imports
Open

gabrielcld2 wants to merge 1 commit into
developfrom
fix/download-sync-failed-imports

Conversation

@gabrielcld2

@gabrielcld2 gabrielcld2 commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes failed imports from the Cloudinary DAM being reported as successful.

The issue

When the server fails to download an asset inserted from the DAM, for example a network error or a 401/404/5xx response, the import is reported as a success:

  • Download_Sync::download_asset() returns a WP_Error on failure, but import_asset() only caught exceptions. The error was dropped and the REST endpoint responded with success: true.
  • The editor was left with an attachment that has no file (grey placeholder, broken image on the frontend).
  • handle_failed_download() never ran, and it didn't log anything anyway.
  • On a non-200 response, the empty response body was streamed into the file primed in the uploads folder, which was then left behind.

One way to hit this is an account using authenticated delivery: inserting an asset with a crop or an effect applied makes the plugin download the unsigned derived URL, which Cloudinary answers with a 401. That underlying download issue is out of scope here and will be handled separately. This PR makes sure any such failure is reported and cleaned up.

The solution

  • import_asset() now returns the download error. The REST endpoint then calls handle_failed_download(), which deletes the attachment and sends the error to the media modal, which shows it in an alert.
  • handle_failed_download() logs the attachment ID, public_id and error, both to the plugin debug log (System Report) and to the PHP error log.
  • download_asset() deletes the primed file when the request fails or the response isn't a 200. This also covers the other callers (Storage, Unsync).
  • Added tests/phpunit/tests/test-download-sync.php (6 tests). 4 of them fail on develop; the other 2 guard existing behaviour.

Known limitation: after a failed import, a tile without a thumbnail can stay in the media modal grid until the page is reloaded. It comes from the existing .fail() handler in media-modal.js, which was rarely reached before this fix. The attachment itself is deleted.

QA notes

Prerequisites

  • A WP site with the Cloudinary plugin connected and the DAM available in the media modal.
  • WP_DEBUG and WP_DEBUG_LOG enabled (both on by default in the repo's wp-env).

Simulate a failed download. Add this mu-plugin, e.g. .wp-env/mu-plugins/qa-block-cloudinary-download.php on wp-env. It makes the server's download of the asset from Cloudinary fail, while the DAM keeps working in the browser:

<?php
add_filter(
	'pre_http_request',
	static function ( $pre, $args, $url ) {
		// Only the full download of the asset; leave the partial file type check alone.
		if ( false !== strpos( $url, 'res.cloudinary.com' ) && ! empty( $args['stream'] ) && empty( $args['limit_response_size'] ) ) {
			return new WP_Error( 'http_request_failed', 'QA: blocked download from Cloudinary' );
		}
		return $pre;
	},
	10,
	3
);

Steps

  1. Note the files currently in wp-content/uploads/<year>/<month>/.
  2. In the block editor, add an Image block, open the media modal and go to the Cloudinary tab.
  3. Insert an image that isn't in the WordPress Media Library yet.
    • On develop: no error is shown. Depending on WP_DEBUG_DISPLAY, the item either stays in the modal with a grey placeholder or silently disappears (a PHP warning breaks the JSON response). After a reload, Media > Library has a new attachment with no image, there's a leftover file in the uploads folder (a few bytes, or empty on a 4xx/5xx response), and nothing is logged.
    • On this branch: an alert shows QA: blocked download from Cloudinary. No new attachment is in Media > Library after a reload, no new file is in the uploads folder, and wp-content/debug.log has a line Cloudinary: failed to import asset into attachment <id> (public_id: <public_id>). Deleting the attachment. Error: QA: blocked download from Cloudinary.
  4. Remove the mu-plugin and insert an image from the DAM again, with and without an effect or crop applied. It imports as before: a local file is created, and the preview shows the transformation.

Optional: if you have an asset using authenticated delivery, insert it from the DAM with an effect applied (without the mu-plugin). On this branch you get an alert with Cloudinary's error (e.g. deny or ACL failure) instead of a broken attachment.

download_asset() returns a WP_Error when the download fails, but
import_asset() only caught exceptions, so the error was dropped and the
REST endpoint answered with success. The editor got a broken attachment
with no file, and handle_failed_download() never ran.

- import_asset() now returns the download error, so the attachment is
  deleted and the error is sent to the media modal.
- handle_failed_download() logs the attachment ID, public_id and error
  to the plugin debug log and the PHP error log.
- download_asset() deletes the file it primed for the download when the
  request fails or the response isn't a 200, instead of leaving an
  empty file in the uploads folder.

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.

2 participants