Problem/Motivation

I'm getting following PHP error when saving text format with CKEditor 5:

Warning: Trying to access array offset on value of type null in Drupal\ckeditor5\AdminUi::isSwitchingToCkeditor5() (line 137 of core/modules/ckeditor5/src/AdminUi.php).
Drupal\ckeditor5\AdminUi::isSwitchingToCkeditor5(Object) (Line: 62)
Drupal\ckeditor5\AdminUi::getSubmittedEditorAndFilterFormat(Object) (Line: 284)
Drupal\ckeditor5\Plugin\Editor\CKEditor5->buildConfigurationForm(Array, Object) (Line: 177)
editor_form_filter_format_form_alter(Array, Object, 'filter_format_edit_form') (Line: 539)
Drupal\Core\Extension\ModuleHandler->alter('form', Array, Object, 'filter_format_edit_form') (Line: 835)
Drupal\Core\Form\FormBuilder->prepareForm('filter_format_edit_form', Array, Object) (Line: 279)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 578)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 34)
Drupal\form_test\StackMiddleware\FormTestMiddleware->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 49)
Asm89\Stack\Cors->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
require('/Users/lauri.eskola/Projects/drupal/index.php') (Line: 65)

Steps to reproduce

  1. Revert the workaround added in #3228477.
  2. Use PHP 8.0.
  3. Install Standard and the CKEditor 5 module.
  4. Create a text format, granting administrators access, and select CKE5 as the editor for the format.
  5. Save the text format.
  6. Edit the text format you just created.
  7. Click save again (no changes needed).
  8. The error will appear onscreen if the site is configured to show warnings and notices; otherwise, check watchdog and it will be there.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Comments

lauriii created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new1.02 KB

What about something like this?

Status: Needs review » Needs work

The last submitted patch, 2: 3228355-2.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new534 bytes
new1.19 KB

Maybe this?

Otherwise, maybe something like

$this->assertSame('', file_get_contents($this->root . '/' . $this->siteDirectory . '/error.log'));

Status: Needs review » Needs work

The last submitted patch, 4: 3228355-4.patch, failed testing. View results

tim.plunkett’s picture

I don't have specific steps yet, but the fix is clear:
$form_state->getTriggeringElement() can return array or NULL, and that needs to be taken into account here.

xjm’s picture

Of note: For whatever reason, I was able to reproduce this on PHP 8.0 but not on PHP 7.3. The STR are:

  1. Install Standard and the CKEditor 5 module.
  2. Create a text format, granting administrators access, and select CKE5 as the editor for the format.
  3. Save the text format.
  4. Edit the text format you just created.
  5. Click save again (no changes needed).
  6. The error will appear onscreen if the site is configured to show warnings and notices; otherwise, check watchdog and it will be there.

Lauri was using PHP 8.0 and others were not, which explained the issues reproducing it.

The fact that the behavior is different on PHP 7.3 versus 8.0 is why we were concerned that we might have stumbled onto some regression with the FormState API.

wim leers’s picture

Assigned: Unassigned » wim leers

Looks like I’m almost at the point where I can catch PHP notices in functional JS tests.

This also looks like something that belongs in Drupal core… so once I get it working I’ll file a core issue 👍

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
StatusFileSize
new1.62 KB
new1.74 KB

Status: Needs review » Needs work

The last submitted patch, 9: 3228355-9.patch, failed testing. View results

wim leers’s picture

Issue tags: +Needs followup

NICE! That is working 🥳🤓 — that does mean this merits a Drupal core issue… so tagging Needs followup.

Now … let's figure out whether this is indeed a PHP 8-only problem. Queuing tests for PHP 7.3 & 7.4. I bet it's a problem starting with PHP 7.4.

tim.plunkett’s picture

Funny, the 7.3 tests catch a different PHP notice:
if (!(bool) $editor->getImageUploadSettings()['status']) {
causes
Notice: Undefined index: status in Drupal\ckeditor5\Plugin\CKEditor5PluginManager->isPluginDisabled()

:D

wim leers’s picture

Oh … hah :D That's indeed absolutely not what I expected :P

xjm’s picture

I can't believe we don't have a better way to fail tests on notices and warnings. Like that seems fundamentally broken at a basic level. We shouldn't have to check watchdog (although the method is pretty cool as a workaround).

xjm’s picture

Asked about this and got the following info from @larowlan:

larowlan:
\Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware::__invoke

tl;dr the site under test should communicate php errors back to the runner via `X-Drupal-Assertion` headers

xjm:
So this converts warnings etc. into exceptions to raise fails, but how do the headers get set in the first place?

larowlan:
by the error handler

see _drupal_error_header

which is called from _drupal_log_error

so if you could ask him to debug inside _drupal_log_error - there may be something preventing that hunk from running in this scenario, which we need to fix

tim.plunkett’s picture

Also this warning was interfering with debugging on #3228477: CKEditor5PluginConfigurableInterface should extend PluginFormInterface and ConfigurableInterface instead of adding ::settingsForm(), so I committed a temporary workaround (pointing to this issue) that got merged in that MR:
https://git.drupalcode.org/project/ckeditor5/-/commit/50852d5802f4e9f8af...

xjm’s picture

Issue summary: View changes
xjm’s picture

Issue summary: View changes

wim leers’s picture

I can't believe we don't have a better way to fail tests on notices and warnings. Like that seems fundamentally broken at a basic level. We shouldn't have to check watchdog (although the method is pretty cool as a workaround).

+1

Which is why I wrote in #8:

This also looks like something that belongs in Drupal core… so once I get it working I’ll file a core issue 👍

so if you could ask him to debug inside _drupal_log_error - there may be something preventing that hunk from running in this scenario, which we need to fix

I'm 90% certain that this simply never worked for functional JS tests. It's based on the user agent that the relevant middleware gets registered — \Drupal\Core\CoreServiceProvider::registerTest() does:

  /**
   * Registers services and event subscribers for a site under test.
   *
   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
   *   The container builder.
   */
  protected function registerTest(ContainerBuilder $container) {
    // Do nothing if we are not in a test environment.
    if (!drupal_valid_test_ua()) {
      return;
    }
    // The test middleware is not required for kernel tests as there is no child
    // site. DRUPAL_TEST_IN_CHILD_SITE is not defined in this case.
    if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) {
      return;
    }
    // Add the HTTP request middleware to Guzzle.
    $container
      ->register('test.http_client.middleware', 'Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware')
      ->addTag('http_client_middleware');
  }

… but when using functional JS tests, drupal_valid_test_ua() never returns TRUE because

  protected function prepareRequest() {
    $session = $this->getSession();
    $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
  }

only runs when using $this->drupalGet(…), not when the browser itself makes AJAX requests due to e.g. typing something or clicking something.

That's my theory at least 🙈

wim leers’s picture

Title: PHP warning when saving text format with CKE5 » [PP-1] PHP warning when saving text format with CKE5: $form_state->getTriggeringElement() unexpectedly returning NULL
Status: Needs work » Postponed (maintainer needs more info)
Issue tags: -Needs followup

@tim.plunkett already committed a work-around in https://git.drupalcode.org/project/ckeditor5/-/commit/50852d5802f4e9f8af....

That means that the only thing to do here now is to A) prevent regressions, B) figure out if this is actually a deeper bug in Drupal core. For B), I've opened #3228956: PHP notices are not detected for headless browser-triggered requests in FunctionalJavascript tests. Postponing on that now.

wim leers’s picture

Assigned: Unassigned » wim leers

Will push this forward on Monday.

wim leers’s picture

Title: [PP-1] PHP warning when saving text format with CKE5: $form_state->getTriggeringElement() unexpectedly returning NULL » PHP warning when saving text format with CKE5: $form_state->getTriggeringElement() unexpectedly returning NULL
Status: Postponed (maintainer needs more info) » Needs review
StatusFileSize
new2.12 KB

Fully confirmed over at #3228956: PHP notices are not detected for headless browser-triggered requests in FunctionalJavascript tests.

Per #3228956-9: PHP notices are not detected for headless browser-triggered requests in FunctionalJavascript tests and #3228956-12: PHP notices are not detected for headless browser-triggered requests in FunctionalJavascript tests, it's not clear yet how we want to address this in Drupal core. We should not wait for that. Instead, we can adopt the solution that Thunder uses per #3228956-10: PHP notices are not detected for headless browser-triggered requests in FunctionalJavascript tests, which is effectively a cleaned up version of the hack I applied here :) And we can simplify what they did, because … we do not want anything logged by PHP.

Status: Needs review » Needs work

The last submitted patch, 24: 3228355-24.patch, failed testing. View results

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new733 bytes
new2.83 KB

Nice, #24 failed on the PHP notice that @tim.plunkett mentioned in #12 — but now in PHP 8 :) Great!

@tim.plunkett already fixed the originally reported PHP notice in #3228477: CKEditor5PluginConfigurableInterface should extend PluginFormInterface and ConfigurableInterface instead of adding ::settingsForm() as he mentioned in #16.

If this comes back green, I'm going to go ahead and merge this.

+++ b/tests/src/FunctionalJavascript/CKEditor5TestBase.php
@@ -42,6 +47,40 @@ abstract class CKEditor5TestBase extends WebDriverTestBase {
+   * @todo Remove in https://www.drupal.org/project/drupal/issues/3228956.

This ensures we'll remove it when the core issue lands :)

  • Wim Leers committed cb309cc on 1.0.x
    Issue #3228355 by Wim Leers, xjm, tim.plunkett, lauriii, larowlan: PHP...
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs review » Fixed

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.