Problem/Motivation

Take the following two steps to reproduce the bug.

A1. Install Drupal in English.
A2. Install Language module.
A3. Install Content translation module.
A4. On admin/config/development/configuration/single/export export language.types.

B1. Install Drupal in English.
B2. Install Content translation module (it will automatically install Language module as a dependency).
B3. On admin/config/development/configuration/single/export export language.types.

You would expect to get the same output, right? No! In the first case, you get this (good) snippet as part of the export:

negotiation:
  language_content:
    enabled:
      language-interface: 0

In the second case, you get the URL method enabled only:

negotiation:
  language_content:
    enabled:
      language-url: 0

(Note the 0 is the weight of the method in both cases).

But you did not do anything other than installing modules slightly differently. Why does this happen?

function content_translation_install() {
  // ...
  \Drupal::service('language_negotiator')->saveConfiguration(LanguageInterface::TYPE_CONTENT, array(LanguageNegotiationUrl::METHOD_ID => 0));
}

This code is silly, it should leave the default shipped config alone. The default shipped config is good. Why is it that this only happens in interaction B? Well, language module and content translation are enabled in the same request, so this code does not kick in and leaves the updated (locked) content translation method intact:

/**
 * Implements hook_modules_installed().
 */
function language_modules_installed($modules) {
  if (!in_array('language', $modules)) {
    $negotiator = \Drupal::service('language_negotiator');
    $negotiator->updateConfiguration(array());
    $negotiator->purgeConfiguration();
  }
  // ...
}

In case A, when content translation enabled, this code kicks in and rewrites the config based on the locked value.

Proposed resolution

Do not modify the configuration for a non-configurable language type in content translation. Fix the tests assuming the old behavior.

Remaining tasks

Commit.

User interface changes

None.

API changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Title: Config import vs. hook_install() interaction exposes content translation bug » Language module vs. content translation module interaction exposes content translation bug
Component: install system » content_translation.module
Issue tags: -Configuration system

Well, the bug is due to this hook, which rebuilds the config IF language module was not among the ones enabled, and resets it to defaults (updateConfiguration() checks for locked state and resets the locked ones to where they should be). When you enable language module with content translation, this does not run and the modified config prevails. Not a systemic bug and has nothing to do with config or module installer.

/**
 * Implements hook_modules_installed().
 */
function language_modules_installed($modules) {
  if (!in_array('language', $modules)) {
    $negotiator = \Drupal::service('language_negotiator');
    $negotiator->updateConfiguration(array());
    $negotiator->purgeConfiguration();
  }
  // ...
}
alexpott’s picture

phew.

Gábor Hojtsy’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
909 bytes

Starting with throwing a plain exception for now where it should not actually save this config. Should introduce a custom exception for this but this is a first step.

Status: Needs review » Needs work

The last submitted patch, 3: 2424171-locked-language-types-modify.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
692 bytes

So LanguageNegotiator::saveConfiguration() is used as an API to persist locked settings too for easier lookup later. Let's not introduce unneeded API changes then and just fix the cause of this bug in content_translation_install(). Even if we would throw exceptions in saveConfiguration() it is still perfectly possible to update the config with the config API itself in whatever way. So there is no real foolproof way to protect that. We should just not do silly things ourselves at least.

Status: Needs review » Needs work

The last submitted patch, 5: 2424171-dont-update-locked.patch, failed testing.

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 5: 2424171-dont-update-locked.patch, failed testing.

vijaycs85’s picture

Status: Needs work » Needs review

Looks like the tests weren't really rendering /node. This patch fix them and the change of behaviour of code change in #5.

vijaycs85’s picture

FileSize
4.38 KB
3.71 KB

here is the patch for real...

Status: Needs review » Needs work

The last submitted patch, 10: 2424171-donot_update_locked-9.patch, failed testing.

vijaycs85’s picture

Status: Needs work » Needs review
FileSize
6.62 KB
2.24 KB

Here is another assert fix.

The last submitted patch, 12: 2424171-donot_update_locked-12.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 12: 2424171-diff-9-12.diff, failed testing.

Gábor Hojtsy’s picture

Not sure the fixes in #10 are correct? There are changes to many asserts that now assert the opposite compared to what they used to be. The patch should not make that happen, no?

victor-shelepen’s picture

The patch #5 breaks the system also. PathLanguageTest and BlockLanguageTest have common failures. Pages are not found. The alias manager could get a path.

vijaycs85’s picture

#16: yep as I mentioned in #9, the /node URLs weren't working.

victor-shelepen’s picture

Status: Needs work » Needs review
FileSize
1.94 KB
1.16 KB

The PathLanguageTest has been fixed.

$edit = array('preferred_langcode' => 'fr');
....
$this->drupalGet($english_alias);
$this->assertText($french_node->body->value, 'English alias, french preffered, french node.' . $french_node->body->value);

The fr language is preffered. If it see an english alises. It gets a node by path. But it uses the user preferred language - fr. So asking en alias we receive fr content.

Continue to check another tests.

Status: Needs review » Needs work

The last submitted patch, 18: 2424171-dont-update-locked-18.patch, failed testing.

victor-shelepen’s picture

Status: Needs work » Needs review
FileSize
3.32 KB
1.26 KB

Enables url negotiator for content.

'language_content[enabled][language-url]' => TRUE,

Result: The block exists:
There is not a block because of content language is en, block uses the content language.
$this->drupalGet('en/node');

The block exists on the page because of the query parameter sets the site language.

$this->drupalGet('en/node', ['query' => ['language' => 'fr']]);
$this->assertText('Powered by Drupal', 'The body of the block does not appear on the page.');
Gábor Hojtsy’s picture

Status: Needs review » Needs work

So I see some tests were assuming that the URL method was set for language negotiation. What about fixing what is asserted then? I see you did that fix in the path language test, so why not do that in the block language test too? Unless we loose some important tested feature?

+++ b/core/modules/path/src/Tests/PathLanguageTest.php
@@ -150,7 +150,7 @@ function testAliasTranslation() {
+    $this->assertText($french_node->body->value, 'English alias, french preffered, french node.' . $french_node->body->value);

@@ -162,7 +162,7 @@ function testAliasTranslation() {
+    $this->assertText($french_node->body->value, 'English alias, french preffered, french node.');

french => French

Also let's remove the added debug string.

victor-shelepen’s picture

In two tests I used the same fix. Yes, I've changed the test asserts. They are different to these were before. But they are logical. I want to highlight that if we use an english alias with user preferred language - French, The French content have to be shown. The assertion before was. If we opened an english alias with user user preferred language - French, we saw the english content. I do not know what is the right way. If test asserts were right we would open new issue. Elsewhere we have changed asserts logic a little bit. It is still logical. As for me. It is not clear, I do not see what should be changed.

Gábor Hojtsy’s picture

In two tests I used the same fix.

@likin: if you are fixing the two tests the same way then I must be terribly mistaken. What I see is:

1. BlockLanguageTest: you updated the content language to be URL based, therefore practically redoing what the install code did that we remove
2. In PathLanguageTest you modified what is being asserted rather than changing the content language setting

Are you doing the same fix in the two tests then? What am I missing?

Wim Leers’s picture

Wim Leers’s picture

Issue tags: +blocker
Wim Leers’s picture

Tested & confirmed that the patch in #20 fixes the bug discovered in #2444231: Fix CacheableInterface so it makes sense (was: "Make Config objects & Entities implement CacheableInterface + add BubbleableMetadata::createFromCacheableObject()") as well.

I'll leave it to Gábor to RTBC the test coverage though. But I consider the code changes RTBC already.

Wim Leers’s picture

Status: Needs work » Needs review
Wim Leers’s picture

PathLanguageTest
Fixed the grammar/spelling of the assertion messages.
[19/03/15 13:52:20] Gábor Hojtsy: its confusing that the test says $french_node while its $english_node_french_translation or something
[19/03/15 13:52:29] Gábor Hojtsy: its not a different node
[19/03/15 13:52:31] Wim Leers: yeah
[19/03/15 13:52:32] Gábor Hojtsy: tripped me up
[19/03/15 13:52:42] Wim Leers: I can fix that
[19/03/15 13:53:10] Wim Leers: $english_node_french_translation it is

BlockLanguageTest

#20 was on the right way: it enabled URL detection for the content language. But it should've also disabled the inheriting of the interface language. Once that's done, none of the assertion changes are necessary :)
Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Yay, looks amazing. Thanks for walking through that with me. Looks like my instinct was very right that we either need to change config or the assertions but definitely not both. I think the changes are fine now, especially the path tests, there are very lengthy comments there as to why the found node should be French, so its strange it was not before. Now the world order is restored.

Gábor Hojtsy’s picture

Issue summary: View changes

Updated issue summary.

victor-shelepen’s picture

:)

-    $french_node = $english_node->getTranslation('fr');
+    $english_node_french_translation = $english_node->getTranslation('fr');

Where is a magic? The variable is renamed, nothing else has been touched. Thank you.

Gábor Hojtsy’s picture

@likin: no magic involved. Asserting that accessing a French node when using an alias for an English node is confusing, and that is not what the test does. It asserts that it leads to the same node in a different language. Therefore $french_node and $english_node is confusing because they are both the same node. That change is not required to fix the bug but helped a tremendous amount to understand what is being tested and why the fix makes sense.

victor-shelepen’s picture

@Gábor Hojtsy Ok. I see. So the patch #33 also should pass tests,

Gábor Hojtsy’s picture

@likin: no, probably not. The changes you made before or Wim's changes are needed to pass, because those two tests assumed the (broken) configuration that content language had earlier due to its install hook.

The last submitted patch, 5: 2424171-dont-update-locked.patch, failed testing.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 8c5ffa3 and pushed to 8.0.x. Thanks!

  • alexpott committed 8c5ffa3 on 8.0.x
    Issue #2424171 by vijaycs85, likin, Gábor Hojtsy, Wim Leers: Language...
Gábor Hojtsy’s picture

Issue tags: -sprint

Thanks @likin, @Wim Leers!

Status: Fixed » Closed (fixed)

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