Problem/Motivation

CloudinaryAssetHelper::loadAssetByPublicId() calls the Cloudinary Admin API's asset() method with no exception handling. If the stored public_id no longer resolves, for example, because the asset was moved to a different folder in the Cloudinary DAM after the Media entity already referenced it, the API throws Cloudinary\Api\Exception\NotFound, which is never caught. This propagates up through CloudinaryFileHelper::generateUriForFile() / generateUriForFileWithTransformation() and crashes rendering wherever the affected media is displayed (observed via a Views field render and the Media Library widget's AJAX "edit" dialog).

This isn't specific to images, loadAssetByPublicId() accepts a resource_type (image/video/raw) and the code path is generic, so any asset type whose public_id goes stale will trigger the same crash.

Steps to reproduce

1. Upload/reference a Cloudinary asset (e.g. documents/reports/example-file) in a Media entity.
2. In the Cloudinary DAM, move that same asset to a different folder (changing its public_id/path).
3. View any page/dialog that renders the Media entity (e.g. a Views listing, or open the Media Library widget's edit dialog for a field referencing it).
4. Fatal error: Cloudinary\Api\Exception\NotFound: Resource not found - documents/reports/example-file

Stack trace

#0 /var/www/html/vendor/cloudinary/cloudinary_php/src/Api/BaseApiClient.php(332): Cloudinary\Api\BaseApiClient->handleApiResponse()
#1 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(209): Cloudinary\Api\BaseApiClient->Cloudinary\Api\{closure}()
#2 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(158): GuzzleHttp\Promise\Promise::callHandler()
#3 /var/www/html/vendor/guzzlehttp/promises/src/TaskQueue.php(52): GuzzleHttp\Promise\Promise::GuzzleHttp\Promise\{closure}()
#4 /var/www/html/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(167): GuzzleHttp\Promise\TaskQueue->run()
#5 /var/www/html/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(206): GuzzleHttp\Handler\CurlMultiHandler->tick()
#6 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\Handler\CurlMultiHandler->execute()
#7 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\Promise\Promise->invokeWaitFn()
#8 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\Promise\Promise->waitIfPending()
#9 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\Promise\Promise->invokeWaitList()
#10 /var/www/html/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\Promise\Promise->waitIfPending()
#11 /var/www/html/vendor/cloudinary/cloudinary_php/src/Api/BaseApiClient.php(144): GuzzleHttp\Promise\Promise->wait()
#12 /var/www/html/vendor/cloudinary/cloudinary_php/src/Api/Admin/AssetsTrait.php(264): Cloudinary\Api\BaseApiClient->get()
#13 /var/www/html/docroot/modules/contrib/cloudinary/modules/cloudinary_sdk/src/Service/CloudinaryAssetHelper.php(208): Cloudinary\Api\Admin\AdminApi->asset()
#14 /var/www/html/docroot/modules/contrib/cloudinary/modules/cloudinary_media_library_widget/src/Service/CloudinaryFileHelper.php(103): Drupal\cloudinary_sdk\Service\CloudinaryAssetHelper->loadAssetByPublicId()
#15 /var/www/html/docroot/modules/contrib/cloudinary/modules/cloudinary_media_library_widget/src/Service/CloudinaryFileHelper.php(133): Drupal\cloudinary_media_library_widget\Service\CloudinaryFileHelper->generateUriForFile()
#16 /var/www/html/docroot/modules/contrib/cloudinary/modules/cloudinary_media_library_widget/cloudinary_media_library_widget.module(213): Drupal\cloudinary_media_library_widget\Service\CloudinaryFileHelper->generateUriForFileWithTransformation()
#17 /var/www/html/docroot/core/lib/Drupal/Core/Extension/ModuleHandler.php(369): cloudinary_media_library_widget_preprocess_image_formatter()
#18 /var/www/html/docroot/core/lib/Drupal/Core/Theme/ThemeManager.php(304): Drupal\Core\Extension\ModuleHandler->invoke()
#19 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(497): Drupal\Core\Theme\ThemeManager->render()
#20 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#21 /var/www/html/docroot/core/lib/Drupal/Core/Template/TwigExtension.php(491): Drupal\Core\Render\Renderer->render()
#22 /var/www/html/vendor/twig/twig/src/Environment.php(420) : eval()'d code(92): Drupal\Core\Template\TwigExtension->escapeFilter()
#23 /var/www/html/vendor/twig/twig/src/Template.php(411): __TwigTemplate_012fe50eb9dba0190c7d1b6c7b14ff30->doDisplay()
#24 /var/www/html/vendor/twig/twig/src/Template.php(366): Twig\Template->yield()
#25 /var/www/html/vendor/twig/twig/src/Template.php(381): Twig\Template->display()
#26 /var/www/html/vendor/twig/twig/src/TemplateWrapper.php(51): Twig\Template->render()
#27 /var/www/html/docroot/core/themes/engines/twig/twig.engine(34): Twig\TemplateWrapper->render()
#28 /var/www/html/docroot/core/lib/Drupal/Core/Theme/ThemeManager.php(380): twig_render_template()
#29 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(497): Drupal\Core\Theme\ThemeManager->render()
#30 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(510): Drupal\Core\Render\Renderer->doRender()
#31 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#32 /var/www/html/docroot/core/lib/Drupal/Core/Template/TwigExtension.php(491): Drupal\Core\Render\Renderer->render()
#33 /var/www/html/vendor/twig/twig/src/Environment.php(420) : eval()'d code(55): Drupal\Core\Template\TwigExtension->escapeFilter()
#34 /var/www/html/vendor/twig/twig/src/Template.php(411): __TwigTemplate_0a9accfb708c772f25b519512004e36c->doDisplay()
#35 /var/www/html/vendor/twig/twig/src/Template.php(366): Twig\Template->yield()
#36 /var/www/html/vendor/twig/twig/src/Template.php(381): Twig\Template->display()
#37 /var/www/html/vendor/twig/twig/src/TemplateWrapper.php(51): Twig\Template->render()
#38 /var/www/html/docroot/core/themes/engines/twig/twig.engine(34): Twig\TemplateWrapper->render()
#39 /var/www/html/docroot/core/lib/Drupal/Core/Theme/ThemeManager.php(380): twig_render_template()
#40 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(497): Drupal\Core\Theme\ThemeManager->render()
#41 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#42 /var/www/html/docroot/core/modules/views/src/Plugin/views/field/FieldPluginBase.php(1215): Drupal\Core\Render\Renderer->render()
#43 /var/www/html/docroot/core/modules/views/views.theme.inc(230): Drupal\views\Plugin\views\field\FieldPluginBase->advancedRender()
#44 [internal function]: template_preprocess_views_view_field()
#45 /var/www/html/docroot/core/lib/Drupal/Core/Theme/ThemeManager.php(308): call_user_func_array()
#46 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(497): Drupal\Core\Theme\ThemeManager->render()
#47 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#48 /var/www/html/docroot/core/modules/views/src/Plugin/views/field/FieldPluginBase.php(1811): Drupal\Core\Render\Renderer->render()
#49 /var/www/html/docroot/core/modules/views/src/Plugin/views/style/StylePluginBase.php(778): Drupal\views\Plugin\views\field\FieldPluginBase->theme()
#50 [internal function]: Drupal\views\Plugin\views\style\StylePluginBase->elementPreRenderRow()
#51 /var/www/html/docroot/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(107): call_user_func_array()
#52 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(876): Drupal\Core\Render\Renderer->doTrustedCallback()
#53 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(438): Drupal\Core\Render\Renderer->doCallback()
#54 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#55 /var/www/html/docroot/core/modules/views/src/Plugin/views/style/StylePluginBase.php(715): Drupal\Core\Render\Renderer->render()
#56 /var/www/html/docroot/core/modules/views/src/Plugin/views/style/StylePluginBase.php(579): Drupal\views\Plugin\views\style\StylePluginBase->renderFields()
#57 /var/www/html/docroot/core/modules/views/src/Plugin/views/style/StylePluginBase.php(467): Drupal\views\Plugin\views\style\StylePluginBase->renderGrouping()
#58 /var/www/html/docroot/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php(2213): Drupal\views\Plugin\views\style\StylePluginBase->render()
#59 /var/www/html/docroot/core/modules/views/src/ViewExecutable.php(1595): Drupal\views\Plugin\views\display\DisplayPluginBase->render()
#60 /var/www/html/docroot/core/modules/views/src/Plugin/views/display/Page.php(201): Drupal\views\ViewExecutable->render()
#61 /var/www/html/docroot/core/modules/views/src/ViewExecutable.php(1692): Drupal\views\Plugin\views\display\Page->execute()
#62 /var/www/html/docroot/core/modules/views/src/Element/View.php(80): Drupal\views\ViewExecutable->executeDisplay()
#63 [internal function]: Drupal\views\Element\View::preRenderViewElement()
#64 /var/www/html/docroot/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(107): call_user_func_array()
#65 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(876): Drupal\Core\Render\Renderer->doTrustedCallback()
#66 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(438): Drupal\Core\Render\Renderer->doCallback()
#67 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(510): Drupal\Core\Render\Renderer->doRender()
#68 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(227): Drupal\Core\Render\Renderer->doRender()
#69 /var/www/html/docroot/core/lib/Drupal/Core/Template/TwigExtension.php(491): Drupal\Core\Render\Renderer->render()
#70 /var/www/html/vendor/twig/twig/src/Environment.php(420) : eval()'d code(52): Drupal\Core\Template\TwigExtension->escapeFilter()
#71 /var/www/html/vendor/twig/twig/src/Template.php(411): __TwigTemplate_2e1b392b24297221d1fd1c7706bd1faa->doDisplay()
#72 /var/www/html/vendor/twig/twig/src/Template.php(366): Twig\Template->yield()
#73 /var/www/html/vendor/twig/twig/src/Template.php(381): Twig\Template->display()
#74 /var/www/html/vendor/twig/twig/src/TemplateWrapper.php(51): Twig\Template->render()
#75 /var/www/html/docroot/core/themes/engines/twig/twig.engine(34): Twig\TemplateWrapper->render()
#76 /var/www/html/docroot/core/lib/Drupal/Core/Theme/ThemeManager.php(380): twig_render_template()
#77 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(497): Drupal\Core\Theme\ThemeManager->render()
#78 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(250): Drupal\Core\Render\Renderer->doRender()
#79 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(141): Drupal\Core\Render\Renderer->doRenderRoot()
#80 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(627): Drupal\Core\Render\Renderer->Drupal\Core\Render\{closure}()
#81 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(140): Drupal\Core\Render\Renderer->executeInRenderContext()
#82 /var/www/html/docroot/core/lib/Drupal/Core/Render/Renderer.php(111): Drupal\Core\Render\Renderer->renderInIsolation()
#83 /var/www/html/docroot/core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php(36): Drupal\Core\Render\Renderer->renderRoot()
#84 /var/www/html/docroot/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php(161): Drupal\Core\Ajax\OpenDialogCommand->getRenderedContent()
#85 /var/www/html/docroot/core/lib/Drupal/Core/Ajax/AjaxResponse.php(43): Drupal\Core\Ajax\OpenDialogCommand->render()
#86 /var/www/html/docroot/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php(823): Drupal\Core\Ajax\AjaxResponse->addCommand()
#87 [internal function]: Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::openMediaLibrary()
#88 /var/www/html/docroot/core/lib/Drupal/Core/Form/FormAjaxResponseBuilder.php(69): call_user_func_array()
#89 /var/www/html/docroot/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php(112): Drupal\Core\Form\FormAjaxResponseBuilder->buildResponse()
#90 /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php(246): Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber->onException()
#91 /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php(206): Symfony\Component\EventDispatcher\EventDispatcher::Symfony\Component\EventDispatcher\{closure}()
#92 /var/www/html/vendor/symfony/event-dispatcher/EventDispatcher.php(56): Symfony\Component\EventDispatcher\EventDispatcher->callListeners()
#93 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(241): Symfony\Component\EventDispatcher\EventDispatcher->dispatch()
#94 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(91): Symfony\Component\HttpKernel\HttpKernel->handleThrowable()
#95 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()
#96 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
#97 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#98 /var/www/html/docroot/core/modules/page_cache/src/StackMiddleware/PageCache.php(116): Drupal\Core\StackMiddleware\ContentLength->handle()
#99 /var/www/html/docroot/core/modules/page_cache/src/StackMiddleware/PageCache.php(90): Drupal\page_cache\StackMiddleware\PageCache->pass()
#100 /var/www/html/docroot/core/modules/ban/src/BanMiddleware.php(50): Drupal\page_cache\StackMiddleware\PageCache->handle()
#101 /var/www/html/docroot/modules/contrib/shield/src/ShieldMiddleware.php(263): Drupal\ban\BanMiddleware->handle()
#102 /var/www/html/docroot/modules/contrib/shield/src/ShieldMiddleware.php(130): Drupal\shield\ShieldMiddleware->bypass()
#103 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\shield\ShieldMiddleware->handle()
#104 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#105 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(53): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#106 /var/www/html/docroot/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\Core\StackMiddleware\AjaxPageState->handle()
#107 /var/www/html/docroot/core/lib/Drupal/Core/DrupalKernel.php(715): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
#108 /var/www/html/docroot/index.php(19): Drupal\Core\DrupalKernel->handle()
#109 {main}

Proposed resolution

Proposed fix (attached patch, workaround, not a full fix):
Catch the exception in loadAssetByPublicId(), log a warning via a new cloudinary_sdk.logger_channel service, and return an empty Asset so rendering doesn't crash. Note in the issue that this prevents the fatal error but does not restore the correct image, the affected asset renders as missing until its Drupal-side reference is corrected or the module implements a proper re-sync (e.g. re-lookup by filename/context, or a webhook-driven cache invalidation when Cloudinary assets move). Attach patches/cloudinary/cloudinary-missing-resource-fix.patch (including its accompanying unit test, see ticket 01 in this feature's issues/ once landed).

Remaining tasks

Related issues: #3078828, #3171412, same failure family (uncaught exception from a stale/malformed Cloudinary reference) but different specific triggers (AJAX upload timing / missing URL-encoding, rather than a post-sync DAM folder move). Note the distinction rather than implying duplication.

Issue fork cloudinary-3613941

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

aurora.luzzardi created an issue. See original summary.

alvar0hurtad0 made their first commit to this issue’s fork.

alvar0hurtad0’s picture

Status: Active » Needs review

Hello Aurora,

Thanks for your patch and raising up the issue. I've created a MR that I think fixes the issue.

https://git.drupalcode.org/project/cloudinary/-/merge_requests/43/diffs#...

Could you please check in your case?