Fail gracefully if CKEditor plugins fail to provide file URI

Created on 14 February 2022, almost 3 years ago
Updated 26 December 2023, 12 months ago

Problem/Motivation

When a CKEditor plugin returns an empty string at \Drupal\ckeditor\CKEditorPluginInterface::getFile(), currently drupal breaks hard with:

TypeError: Argument 1 passed to Drupal\Core\File\FileUrlGenerator::generateString() must be of the type string, null given in Drupal\Core\File\FileUrlGenerator->generateString() (line 58 of /var/www/html/core/lib/Drupal/Core/File/FileUrlGenerator.php)
#0 [internal function]: Drupal\Core\File\FileUrlGenerator->generateString(NULL)
#1 /var/www/html/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php(343): array_map(Array, Array)
#2 /var/www/html/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php(265): Drupal\ckeditor\Plugin\Editor\CKEditor->getJSSettings(Object(Drupal\editor\Entity\Editor))
#3 /var/www/html/core/modules/editor/editor.module(177): Drupal\ckeditor\Plugin\Editor\CKEditor->buildConfigurationForm(Array, Object(Drupal\Core\Form\SubformState))
#4 /var/www/html/core/lib/Drupal/Core/Extension/ModuleHandler.php(539): editor_form_filter_format_form_alter(Array, Object(Drupal\Core\Form\FormState), 'filter_format_e...')
#5 /var/www/html/core/lib/Drupal/Core/Form/FormBuilder.php(835): Drupal\Core\Extension\ModuleHandler->alter('form', Array, Object(Drupal\Core\Form\FormState), 'filter_format_e...')
#6 /var/www/html/core/lib/Drupal/Core/Form/FormBuilder.php(279): Drupal\Core\Form\FormBuilder->prepareForm('filter_format_e...', Array, Object(Drupal\Core\Form\FormState))
#7 /var/www/html/core/lib/Drupal/Core/Controller/FormController.php(73): Drupal\Core\Form\FormBuilder->buildForm(Object(Drupal\filter\FilterFormatEditForm), Object(Drupal\Core\Form\FormState))
#8 [internal function]: Drupal\Core\Controller\FormController->getContentResult(Object(Symfony\Component\HttpFoundation\Request), Object(Drupal\Core\Routing\RouteMatch))
#9 /var/www/html/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array)
#10 /var/www/html/core/lib/Drupal/Core/Render/Renderer.php(564): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
#11 /var/www/html/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\Core\Render\Renderer->executeInRenderContext(Object(Drupal\Core\Render\RenderContext), Object(Closure))
#12 /var/www/html/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array)
#13 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(158): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
#14 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(80): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#15 /var/www/html/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#16 /var/www/html/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#17 /var/www/html/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#18 /var/www/html/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#19 /var/www/html/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#20 /var/www/html/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#21 /var/www/html/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#22 /var/www/html/core/lib/Drupal/Core/DrupalKernel.php(709): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#23 /var/www/html/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#24 {main}

Although it's an error condition (a plugin is expected to return a string on that method), it's possible that misconfigured contrib/custom plugins could lead to that scenario, and core should fail more gracefully.

Steps to reproduce

1- Install Drupal with the standard profile
2- Add the contributed https://www.drupal.org/project/ckeditor_a11ychecker module and its dependency https://www.drupal.org/project/ckeditor_balloonpanel .
3- Install these modules without placing their library in /libraries. In other words, just download the drupal modules and enable them.
4- Visit /admin/config/content/formats/manage/full_html

Expected: You can use the filter and toolbar, ideally with an error message in the logs.

Actual: WSOD with the error above in the logs.

Proposed resolution

In \Drupal\ckeditor\Plugin\Editor\CKEditor::getJSSettings(), skip plugins that provide an empty file URI, and log a watchdog error message.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

🐛 Bug report
Status

Needs review

Version

1.0

Component

Code

Created by

🇪🇸Spain marcoscano Barcelona, Spain

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024