Problem/Motivation

Drupal\language\LanguageNegotiator::initializeType does not handle Drupal\Component\Plugin\Exception\PluginNotFoundException at all. If a language negotiation plugin was removed by mistake or uninstalled improperly, the whole Drupal site will fail. All drush command or page rendering would be stopped by the issue.

Proposed resolution

I think the negotiation process should handle a missing plugin more gracefully. It should either simply log the error with watchdog or display a message on frontend to prompt user to handle it. This should not have been a site breaking problem.

Add a messenger argument to language.services.yml to handle exception with plugin missing message.

Remaining tasks

Patch
Add test case
code review

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

None

Issue fork drupal-3134349

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

yookoala created an issue. See original summary.

yookoala’s picture

Version: 8.8.5 » 8.9.x-dev
yookoala’s picture

StatusFileSize
new1.45 KB
new1.45 KB

These patches are the proposed changes to the language negotiator.

yookoala’s picture

If these patches are in the right direction, I can also patch 9.0 and 9.1 development.

yookoala’s picture

StatusFileSize
new1.45 KB
new1.45 KB
sharma.amitt16’s picture

Assigned: Unassigned » sharma.amitt16
sharma.amitt16’s picture

Status: Active » Needs review
StatusFileSize
new3.45 KB
new4.06 KB

Replaced the deprecated code.

drupal_set_message() with
$this->messenger->addError();
sharma.amitt16’s picture

Assigned: sharma.amitt16 » Unassigned
sharma.amitt16’s picture

StatusFileSize
new663 bytes
new4.81 KB

Correction in the previous patch. Missed the modification in the service file to add messenger argument.

narendra.rajwar27’s picture

+++ b/core/modules/language/language.services.yml
@@ -4,7 +4,7 @@ services:
+    arguments: ['@language_manager', '@plugin.manager.language_negotiation_method', '@config.factory', '@settings', '@request_stack', '@messenger']

Patch looks good to me since it is handling the language-negotiator plugin exception by adding messenger service to language service. Seems very appropriate way.
+1 RTBC

narendra.rajwar27’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update
narendra.rajwar27’s picture

Issue summary: View changes
Status: Needs work » Needs review
narendra.rajwar27’s picture

freality’s picture

I think it's safe to state that a missing plugin should not collapse an entire system. If that's the case, I don't understand why it taken so long for this simple patch to be adopted into the codebase.

Consider this comment acknowledgement that the problem still exists and a vote to adopt this patch.

texas-bronius’s picture

This is great! This is the sort of thing that makes us look as stable as a stable D8/D9 site is when properly working :)

Is there any use case where having the site completely crash with the uncaught exception might be the preferred route (ie: some chain of events that would follow that should never have occurred), or is this not our burden to consider?

joachim’s picture

Status: Needs review » Needs work
+++ b/core/modules/language/src/LanguageNegotiator.php
@@ -329,13 +351,11 @@ public function updateConfiguration(array $types) {
-
         // If the language type is locked we can just store its default language
         // negotiation settings if it has some, since it is not configurable.
         if ($has_default_settings) {
           $method_weights = [];
           // Default settings are in $info['fixed'].
-
           foreach ($info['fixed'] as $weight => $method_id) {

These whitespace changes are unrelated.

+++ b/core/modules/language/src/LanguageNegotiator.php
@@ -128,7 +140,17 @@ public function initializeType($type) {
+            $this->messenger->addError(t('Error finding language negotiator plugin %method-id: @message', [
+              '%method-id' => $method_id,
+              '@message' => $e->getMessage(),
+            ]), 'error');

@@ -329,13 +351,11 @@ public function updateConfiguration(array $types) {
-

I'm not sure about outputting a message, which is a UI operation, in a service like this which is fairly deep. A watchdog error should be sufficient?

swatichouhan012’s picture

StatusFileSize
new4.11 KB
new887 bytes

#16 1st point fixes, reverted unrelated whitespace changes.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

s_leu’s picture

Status: Needs work » Needs review
StatusFileSize
new3.74 KB

Re-roll for 9.5.x

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative, +Needs tests

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

As a bug this will need a test case.

sleitner’s picture

Version: 9.5.x-dev » 10.1.x-dev
StatusFileSize
new1.41 KB

Test only patch.

sleitner’s picture

Status: Needs work » Needs review
StatusFileSize
new5.15 KB
new1.23 KB

#21 and #24 combined, please review

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

sleitner’s picture

Issue tags: -Needs tests
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

New parameter should default to null with a trigger_error that it will be required in D11. Would search the repo for exact working. Then a simple unit or kernel test that when you pass null to the constructor you get that trigger_error response.

Also with a new parameter it will need a change record to announce this constructor now takes a new parameter.

Thanks

joachim’s picture

  1. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -130,7 +142,17 @@ public function initializeType($type) {
    +            // If a plugin is not found, simply log the error so user may handle it.
    

    Comment is too long, should be wrapped.

    Also, we're not 'just logging the error' since we're also showing an error message.

  2. +++ b/core/modules/language/tests/src/Functional/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,45 @@
    +    $this->assertSession()->statusCodeEquals(200);
    

    If we're only testing the status, could this be done in a kernel test?

sleitner’s picture

Status: Needs work » Needs review
StatusFileSize
new5.52 KB
new4.44 KB

Please review, covers #28 and #29

penyaskito’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -70,6 +72,13 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
    +   * Messenger Service.
    

    We use

       * The messenger.
    

    everywhere in Drupal core. Change to that.

  2. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -70,6 +72,13 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
    +  protected $messenger;
    

    Nitpick: Put $messenger after $requestStack (before $currentUser and $negotiatedLanguages). I wouldn't be so nitpicky if I didn't have more feedback.

  3. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -83,13 +92,20 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
    +   *   Messenger Service.
    

    Same here. Use "The messenger"

  4. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -83,13 +92,20 @@ class LanguageNegotiator implements LanguageNegotiatorInterface {
    +      @trigger_error('Calling ' . __METHOD__ . ' without the $messenger argument is deprecated in drupal:10.1.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3280207', E_USER_DEPRECATED);
    

    The linked change record is unrelated. We need to create a draft one for the changes in this issue and link it here.

  5. +++ b/core/modules/language/src/LanguageNegotiator.php
    @@ -130,7 +146,17 @@ public function initializeType($type) {
    +            watchdog_exception('language', $e);
    

    We can use \Drupal::logger() instead?

  6. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,40 @@
    +  public function testLanguageNegotiatorNoPlugin() {
    

    This test has no assertions.

    I suggest:

    + We assert we don't have the exception we had until now.
    + We assert the message is shown.
    + We assert the error is logged.

I also tempt to agree with @joachim at #16. A visitor could see this error which is scary and he can't do anything about it. We might want to just log it.

sahil.goyal’s picture

StatusFileSize
new6.02 KB
new2.88 KB

Addressing the suggestions of #31 fixing these issues, Only point #4 is left as i not clear with it completely. Apart all issues being addresses in attached Patch with interdiffs. Leaving issue still in NW.

Vidushi Mehta’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Needs work

Was mentioned in #32 that #31.4 has not been addressed.

sleitner’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
StatusFileSize
new3.64 KB
new5.23 KB

Removed the messenger, just logs the error. No change record needed any more, because interface does not change.

penyaskito’s picture

Status: Needs review » Needs work

#35 looks close!

  1. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,52 @@
    +use ColinODell\PsrTestLogger\TestLogger;
    

    TIL :-)

  2. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,52 @@
    +    // Assert we don't have the exception we had until now.
    +    $this->assertNotInstanceOf('\Drupal\Component\Plugin\Exception\PluginNotFoundException', $switchLinks);
    

    I doubt this is the right assertion, as the exception would be thrown, not returned.
    Can we upload a test-only patch to verify?

sleitner’s picture

StatusFileSize
new1.86 KB

I deleted the assertNotInstanceOf

sleitner’s picture

Status: Needs work » Needs review
StatusFileSize
new3.5 KB
penyaskito’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
@@ -0,0 +1,50 @@
+    $this->assertFalse($logger->hasErrorThatContains('Valid plugin IDs for Drupal\language\LanguageNegotiationMethodManager are'));

I'd prefer to assert by "The "Drupal\Tests\language\Kernel\LanguageNegotiatorPluginTest" plugin does not exist." but don't want to nitpick. Is RTBC for me.

Thanks for your work on this!

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
@@ -0,0 +1,50 @@
+    $this->assertFalse($logger->hasErrorThatContains('Valid plugin IDs for Drupal\language\LanguageNegotiationMethodManager are'));

is assert false right here?

The comment says 'assert the error is logged'

but then we assertFalse?

penyaskito’s picture

My bad, overlooked:

  1. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,50 @@
    +    $this->container->get('logger.factory')
    +      ->get('package_manager')
    +      ->addLogger($logger);
    

    This needs to be language, not package_manager.

    Also, even changing that didn't get it to work.

    But

        $logger_factory = $this->createMock(LoggerChannelFactory::class);
        $logger_factory->expects($this->once())
          ->method('get')
          ->with('language')
          ->willReturn($logger);
        $this->container->set('logger.factory', $logger_factory);
    
    

    worked for me to fix the test.

  2. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -0,0 +1,50 @@
    +    $this->assertFalse($logger->hasErrorThatContains('Valid plugin IDs for Drupal\language\LanguageNegotiationMethodManager are'));
    

    As @larowlan pointed out, this needs to be assertTrue.

    I'd change the string to 'The "Drupal\Tests\language\Kernel\LanguageNegotiatorPluginTest" plugin does not exist.' while we are there.

penyaskito’s picture

+++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
@@ -0,0 +1,50 @@
+    $languageNegotiator->initializeType(LanguageInterface::TYPE_URL);
+    $switchLinks = $languageManager->getLanguageSwitchLinks(LanguageInterface::TYPE_INTERFACE, new Url('<current>'));

Let's also change the test to be explicit about the exception:

    try {
      $languageNegotiator->initializeType(LanguageInterface::TYPE_URL);
      $switchLinks = $languageManager->getLanguageSwitchLinks(LanguageInterface::TYPE_INTERFACE, new Url('<current>'));
    }
    catch(PluginNotFoundException $exception) {
      $this->fail('Plugin not found exception unhandled.');
    }

sleitner’s picture

Status: Needs work » Needs review
StatusFileSize
new3.94 KB
new2.9 KB

Includes issues of #39-#42

penyaskito’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -13,6 +16,7 @@
    +  use ProphecyTrait;
    
    @@ -24,27 +28,32 @@
    +    $languageNegotiator->setCurrentUser($this->getProphet()->prophesize('Drupal\Core\Session\AccountInterface')->reveal());
    

    We can call prophesize directly as you were doing. No need to include the trait.

  2. +++ b/core/modules/language/tests/src/Kernel/LanguageNegotiatorPluginTest.php
    @@ -24,27 +28,32 @@
    +      $switchLinks = $languageManager->getLanguageSwitchLinks(LanguageInterface::TYPE_INTERFACE, new Url('<current>'));
    

    We don't even need this line now that I see it, as it would fail on the initializeType method and we are not asserting on the switchLinks

akram khan’s picture

StatusFileSize
new4.92 KB
new2.75 KB

address #44

akram khan’s picture

StatusFileSize
new4.9 KB
new761 bytes

try to fixed CCF #45

akram khan’s picture

Status: Needs work » Needs review
penyaskito’s picture

Status: Needs review » Reviewed & tested by the community

Looks great now, all feedback was addressed. Thanks!

larowlan’s picture

Updating issue credits

  • larowlan committed 8c76fe6d on 10.0.x
    Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan, sahil....

  • larowlan committed b279c994 on 10.1.x
    Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan, sahil....
larowlan’s picture

Title: \Drupal\language\LanguageNegotiator does not handle PluginNotFoundException and break the site completely » [needs backport] \Drupal\language\LanguageNegotiator does not handle PluginNotFoundException and break the site completely
Version: 10.1.x-dev » 9.5.x-dev
Issue tags: +Bug Smash Initiative

removed these out of scope whitespace changes on commit

diff --git a/core/modules/language/src/LanguageNegotiator.php b/core/modules/language/src/LanguageNegotiator.php
index 72bf83cea9e..0007dab69ed 100644
--- a/core/modules/language/src/LanguageNegotiator.php
+++ b/core/modules/language/src/LanguageNegotiator.php
@@ -340,13 +340,11 @@ public function updateConfiguration(array $types) {
         // settings are always considered non-configurable. In turn if default
         // settings are missing, the language type is always considered
         // configurable.
-
         // If the language type is locked we can just store its default language
         // negotiation settings if it has some, since it is not configurable.
         if ($has_default_settings) {
           $method_weights = [];
           // Default settings are in $info['fixed'].
-
           foreach ($info['fixed'] as $weight => $method_id) {
             if (isset($method_definitions[$method_id])) {
               $method_weights[$method_id] = $weight;

Committed to 10.1.x and backported to 10.0.x

Will backport to 9.5.x if test run passes

  • larowlan committed 05fb6a25 on 10.0.x
    Revert "Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan...
larowlan’s picture

Version: 9.5.x-dev » 10.0.x-dev
Status: Reviewed & tested by the community » Needs work

Reverted from 10.0.x as TestLogger does not exist there.

You could possibly use BufferingLogger instead

  • larowlan committed 9fd7dd69 on 10.1.x
    Revert "Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan...
larowlan’s picture

Title: [needs backport] \Drupal\language\LanguageNegotiator does not handle PluginNotFoundException and break the site completely » \Drupal\language\LanguageNegotiator does not handle PluginNotFoundException and break the site completely
Version: 10.0.x-dev » 10.1.x-dev

Reverted from 10.1.x too, if we use BufferingLogger instead, we should have the same test in all three branches for consistency.

penyaskito’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new2.64 KB
new4.33 KB

Took https://git.drupalcode.org/project/drupal/-/commit/b279c9946176bfc7a7dd5....
Changed TestLogger for BufferingLogger. Took as a reference \Drupal\Tests\field\Kernel\EntityReference\EntityReferenceSettingsTest that uses that class, but kept the changes to the minimum.
As I just did what @larowlan said, I think this is small enough to RTBC it myself.

I've applied and run locally the test for 9.5.x, 10.0.x, 10.1.x, all green.

penyaskito’s picture

One thing that I'm not sure how we operate with. There was a change record required at some point, and the draft was created. But it's not true anymore. Do we keep it as draft? Delete it? It can be confusing if we leave it as is.

  • larowlan committed d505351e on 10.0.x
    Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan,...

  • larowlan committed 67fc8fbf on 10.1.x
    Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan,...

  • larowlan committed faeae2b8 on 9.5.x
    Issue #3134349 by sleitner, yookoala, sharma.amitt16, Akram Khan,...
larowlan’s picture

Version: 10.1.x-dev » 9.5.x-dev
Status: Reviewed & tested by the community » Fixed

Committed to 10.1.x and backported to 10.0.x and 9.5.x

Deleted the change record

Status: Fixed » Closed (fixed)

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