Problem/Motivation

There were a lot of optimizations for node revisions and translations shortly before 8.0. However, they caused a regression when trying to view revisions of a node that is not translated into the current language.

NodeController::revisionOverview() is supposed to filter the list of revisions to only show those revisions that affect the currently shown translation of the node.
In case that the requested node translation doesn't exist and a language fallback was applied, the list of revisions is erroneously empty instead of showing the revisions that affected the fallback translation.

Proposed resolution

Filter the list of revisions by the current language of the node (with language fallbacks applied) instead of the current content language.

Remaining tasks

None.

User interface changes

None.

API changes

None.

Data model changes

None.

CommentFileSizeAuthor
#57 revisions_for_node_without_transl-2713587-57.patch4.93 KBtduong
#57 interdiff-2713587-40-57.txt978 bytestduong
#40 revisions_for_node_without_transl-2713587-40.patch4.81 KBtduong
#40 revisions_for_node_without_transl-2713587-40-test_only.patch3.45 KBtduong
#40 interdiff-2713587-38-40.txt2.13 KBtduong
#38 revisions_for_node_without_transl-2713587-38.patch4.96 KBtduong
#38 interdiff-2713587-35-38.txt843 bytestduong
#35 revisions_for_node_without_transl-2713587-35.patch5.01 KBtduong
#35 revisions_for_node_without_transl-2713587-35-test_only.patch3.3 KBtduong
#35 interdiff-2713587-28-35.txt2.57 KBtduong
#28 ES:EN revision UI.png81.9 KBtduong
#28 DE revision UI.png94.49 KBtduong
#28 revisions_for_node_without_transl-2713587-28.patch5.37 KBtduong
#28 revisions_for_node_without_transl-2713587-28-test_only.patch3.28 KBtduong
#28 interdiff-2713587-15-28.txt3.98 KBtduong
#20 15-20-interdiff.txt834 bytesmkalkbrenner
#20 revisions_for_node_without_transl-2713587-20-test_only.patch1.93 KBmkalkbrenner
#15 revisions_for_node_without_transl-2713587-15.patch3.57 KBtduong
#15 revisions_for_node_without_transl-2713587-15-test_only.patch1.77 KBtduong
#15 interdiff-2713587-13-15.txt1.82 KBtduong
#13 revisions_for_node_without_transl-2713587-13.patch3.7 KBtduong
#13 revisions_for_node_without_transl-2713587-13-test_only.patch1.91 KBtduong
#13 interdiff-2713587-10-13.txt1.82 KBtduong
#10 revisions_for_node_without_transl-2713587-10.patch3.61 KBtduong
#10 revisions_for_node_without_transl-2713587-10-test_only.patch1.93 KBtduong
#10 interdiff-2713587-7-11.txt2.6 KBtduong
#7 revisions_for_node_without_transl-2713587-7.patch3.82 KBtduong
#7 revisions_for_node_without_transl-2713587-7-test_only.patch2.14 KBtduong
#7 revisions after.png86.19 KBtduong
#7 revisions before.png67.24 KBtduong
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Berdir created an issue. See original summary.

Gábor Hojtsy’s picture

Issue tags: +D8MI, +language-content

Hm, that may be a bit confusing, given the view tab deals with fallback, so you would see something, go to its revisions and see something else :/

Berdir’s picture

What would be confusing exactly? My suggestion or what's there at the moment?

IMHO, the UX of the language filtering is *really* bad right now. For example, at the moment, you might not even see the current revision if it didn't change in your current language.

The changes that we made might have improved things for some use cases, but we also made three steps backwards for many other scenarios:

* Default revision might not be visible, as mentioned. IMHO, we should always display that.
* No indication that some revisions might not be shown
* No revisions at all if node doesn't have a translation for the current language.

Gábor Hojtsy’s picture

So the current behavior may have flaws, but what you see in View is what you see in Edit is what you see revisions for in Revisions, right? I think that is generally a good pattern. I can see how that is problematic when View falls back on other things but Revisions does not. Since you are using admin language as well, do you think this is related to #2677778: Administration pages language setting makes it impossible to edit content translations for some roles and/or #2189267: When content language detection is different from interface language detection, the detected language is not applied to the rendered content?

Berdir’s picture

No, that's not the case.

Just create content and select "Not specified" as language. You can view it, you can edit it, everything works fine. But there are no visible revisions, never will be.

View has a fallback, edit in a way too, revisions has no fallback at all.

Filtering only makes sense if an entity has more than one language, and even there it is IMHO filtering too much.

Gábor Hojtsy’s picture

I think we agree that if there is only one language, it should not filter. I am not sure about including eg. the current revision if its not in the current language, if you revert to there and loose translations, that will be a data loss. (Or that revert needs to work differently from the others).

tduong’s picture

Can reproduce. Here a patch and test. Needs to be improved, but now I have to leave. I'll do it tomorrow.

The last submitted patch, 7: revisions_for_node_without_transl-2713587-7-test_only.patch, failed testing.

Berdir’s picture

Status: Needs review » Needs work

What you are testing now is changing the language of a node. That's slightly different to the defined case. Lets make a new node instead with some revisions.

tduong’s picture

Testing with new node (langcode as UND), site langcode as EN, edit node settings langcode as EN and create new revision, checking revisions overview where both revision messages are displayed.

The last submitted patch, 10: revisions_for_node_without_transl-2713587-10-test_only.patch, failed testing.

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/node/src/Controller/NodeController.php
    @@ -175,11 +176,12 @@ public function revisionOverview(NodeInterface $node) {
           /** @var \Drupal\node\NodeInterface $revision */
           $revision = $node_storage->loadRevision($vid);
    -      if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
    +      if ($single_language || ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected())) {
             $username = [
    

    A comment that explains this condition would be good. Explain when and how we filter.

  2. +++ b/core/modules/node/src/Tests/NodeRevisionsTest.php
    @@ -211,6 +213,32 @@ function testRevisions() {
    +    // Set node langcode as 'EN', create a new revision and new log message.
    +    $node = Node::load($node->id());
    +    $node->langcode = 'en';
    +    $node->body->value = 'New text (EN)';
    +    $node->revision_log = 'New revision message (EN)';
    

    Don't change the language code. Just keep it UND. We just need two revisions so the revisions tab is visible at all.

The last submitted patch, 13: revisions_for_node_without_transl-2713587-13-test_only.patch, failed testing.

Gábor Hojtsy’s picture

Issue tags: +sprint

@berdir asked me to review this issue. The meat of the fix is as follows:

+++ b/core/modules/node/src/Controller/NodeController.php
@@ -175,11 +176,14 @@ public function revisionOverview(NodeInterface $node) {
-      if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
+      // Show node revisions either there are revisions of the same language or
+      // there are translations.
+      if ($single_language || ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected())) {

I agree this fix is an improvement. The comment needs English cleanup though. (Update: crossposted, seems like this got fixed in the meantime).

The last submitted patch, 15: revisions_for_node_without_transl-2713587-15-test_only.patch, failed testing.

mkalkbrenner’s picture

First, showing all revisions when the node is 'untranslated' is how it is meant to work!
So we don't need to discuss that :-)

But I'm not sure if this "cosmetic" patch is the right solution.

Obviously function hasTranslation() doesn't handle LANGCODE_NOT_SPECIFIED or LANGCODE_NOT_APPLICABLE:

public function hasTranslation($langcode) {
  if ($langcode == $this->defaultLangcode) {
    $langcode = LanguageInterface::LANGCODE_DEFAULT;
  }
  return !empty($this->translations[$langcode]['status']);
}

I wonder if the change timestamp tracking works for these nodes where the revision table is currently erroneously empty. The ChangedItem code uses hasTranslation() as well.

As far as I remember plach and myself were of the opinion that there's always a "translation". I wonder if the LANGCODE_NOT_SPECIFIED key exists in the $this->translations array above.

Berdir’s picture

I think hasTranslation() works fine for those language codes.

However, the "current content language" is never UND. It's en or de or something. So hasTranslation('en') correctly returns FALSE.

Note that UND is just one of the cases that this patch fixes. Another is looking at a node that is EN and has no translations and your current content language is DE. Then this will also show all revisions, which IMHO is correct, an empty table there weird in that case.

However, if you go one step further, you can just as easily run into the following scenario if you have 3 languages: You have an EN node with a DE translation and your current content language is ES. What should you see then? IMHO, I'd prefer all revisions over an confusing empty table, possibly with a message that says that the node has no translation for your current content language and that everything is shown?

My initial proposal of checking hasTranslation() of the current revision would "solve" that as well and in that case, also show all revisions.

Also, repeating my question from above, what about the current default revision? IMHO, I'd expect that to be visible always. That *is* what is being used right now, even if it didn't change the current language. So I'd expect that to be always visible?

mkalkbrenner’s picture

Status: Needs review » Needs work

The last submitted patch, 20: revisions_for_node_without_transl-2713587-20-test_only.patch, failed testing.

mkalkbrenner’s picture

However, if you go one step further, you can just as easily run into the following scenario if you have 3 languages: You have an EN node with a DE translation and your current content language is ES. What should you see then?

Which node view is shown in that case? I second Gabor that the revision table should be in sync with the view.

mkalkbrenner’s picture

OK, like expected, my test only patch from #20 fails.
I think it shows that the problem with the revisions table is just one symptom.
When saving a new revision of a ContentEntity having LANGCODE_NOT_SPECIFIED its changed timestamp isn't updated.
I'm sure we'll find more issues regarding the languages LANGCODE_NOT_SPECIFIED and LANGCODE_NOT_APPLICABLE.
I think that requires a deeper inspection of the issue...

Berdir’s picture

What you get is the node in the language that Drupal chose for you, with fallbacks applied.

If you want the same behavior as node view, then you need switch the $langcode from the current content language to the current language of the node.

also, your test if flawed. ChangedItem uses the REQUEST_TIME constant. sleep(1) does nothing to change that. You can't change it like that, you need to edit the node through the UI to do that.

mkalkbrenner’s picture

also, your test if flawed. ChangedItem uses the REQUEST_TIME constant. sleep(1) does nothing to change that.

Yes, you're right! My fault.

I had a closer look at the test. It first creates a node in 'en' and then switches its langcode to 'und'.
It seems that the function call hasTranslation('und') returns TRUE afterwards. So the ChangedItem seems to work as it should.

So when your content language is 'es' and there's no corresponding translation of the node, it falls back to its 'und' translation, right? (At least in common setups.)

Lets assume that a site has three languages: 'en', 'de' and 'es'.
A node is "translated" to 'en' and 'und'.

If the content language is set to 'en' the English translation will be shown. If the content language is set to 'de' or 'es' the node's 'und' translation will be shown as fall back, right?

If we agree that the content of the revision table should be in sync with the current node view, the revision table has to show the revisions that affected the 'und' translation if the content language is set to 'de' or 'es'.

This will not happen with the latest patch which just handles the case that the node only consists of a single language (better: a single translation).

From my point o view the right solution would be to apply the language fallback first if necessary before filtering the content of the revisions table. This should solve the initial bug report here and my more complex example.

BTW I proposed a more sophisticated revisions tab when we worked on translation revisions. But this has been refused by the maintainers. I therefor created https://www.drupal.org/project/revision_ui to incubate a really good solution. I hope to find the time to work on this soon.

Gábor Hojtsy’s picture

I don't think its valid that an entity has a 'und' language and something else. It either does not have a 'und' language or it only has a 'und' language, right?

Berdir’s picture

Yes, you can't translate to or from UND anyway. But the example still makes sense, just switch UND with any other "real" langcode.

As I said, upcasting automatically applies the language fallback. So if we do what you are suggesting, we already have the node in the correct language and would just filter on that. Fine with me, solves my problems mostly too. I still think it would be worth to show the language somehow, since it might not be visible in the backend. And figure out what to do with the default revision.

tduong’s picture

In the code: get the node language instead of current content language.
In the test:

  • switched UND to EN langcode for the first node example
  • added a second node example in EN, translated in DE, view revision in ES, DE and EN

Now what is displayed...

  • ... in the UI (see also screenshots)
    • "es/node/ID/revisions": original EN revision only
    • "de/node/ID/revisions": original EN revision + translated DE revision
    • "en/node/ID/revisions": original EN revision only
  • ... in local test
    • "es/node/ID/revisions": original EN revision only
    • "de/node/ID/revisions": translated DE revision only
    • "en/node/ID/revisions": original EN revision only

IMO it should display all language revisions at least in one place (e.g. for the original node revisions, like I also check in the test), but agree that if there are a lot of revisions it could be a "noisy view" then sure we need to display also the "revision langcode" for each row. What should be the correct result ?

The last submitted patch, 28: revisions_for_node_without_transl-2713587-28-test_only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 28: revisions_for_node_without_transl-2713587-28.patch, failed testing.

mkalkbrenner’s picture

IMO it should display all language revisions at least in one place

We had a long discussion about this before 8.0.0 release. The consensus was not to do that because Drupal 7 didn't do that as well. In Drupal 7 every translation had it's own revisions.
From the user's perspective displaying all revisions across all translation would be a new feature compared to Drupal 7.

As I already mentioned in #18 initially I totally agree, that we have a bug here, but it should not be mixed with that new feature.

That new feature is not that easy, because after just displaying all revisions across the translations you have to decide about the revision revert strategy. Could you revert hardly to any previous revision even if it means that you remove translations?

Again, a lot of questions like that have been discussed already.

Solving that requires a sophisticated UI protected by access rights.
I can invite you to join the Revision UI module for that.

Lets fix the bug now.

Berdir’s picture

Agreed, showing all is a separate topic.

About the Revision UI. entity.module has a generic revision UI now, I think you should consider improving that instead of making another module that then wouldn't be compatible with that. It's not yet used for node, but we can change that.

tduong’s picture

Status: Needs work » Needs review

Yep, agree it should be a separate issue.

… displaying all revisions across the translations you have to decide about the revision revert strategy. Could you revert hardly to any previous revision even if it means that you remove translations?

Sorry, missing explanation: what I mean is that could be nice to just show all available revisions of a node, somewhere, like a "neutral history" of all revisions made, like chronological revisions overview (host or translated node). I don't know, it's just a personal opinion about a nice and clean overview feature I appreciate to use in any app/in general. Or maybe I'm just strange ;P

So, back to this bug issue, in the end what it is supposed to display is like it is in the test, right ? Why is it different for the UI... ?

Berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/node/src/Controller/NodeController.php
    @@ -157,11 +157,12 @@ public function revisionPageTitle($node_revision) {
         $has_translations = (count($languages) > 1);
    -    $node_storage = $this->entityManager()->getStorage('node');
    +    /** @var \Drupal\node\NodeStorage $node_storage */
    +    $node_storage = $this->entityTypeManager()->getStorage('node');
    

    Adding the @var is kind of unrelated now, since we don't use $node_storage anywhere where it wasn't already before.

  2. +++ b/core/modules/node/src/Controller/NodeController.php
    @@ -175,11 +176,14 @@ public function revisionOverview(NodeInterface $node) {
         $vids = $node_storage->revisionIds($node);
     
         $latest_revision = TRUE;
    +    $single_language = count($node->getTranslationLanguages()) == 1;
    

    $single_language can go away with the new approach, it doesn't matter anymore.

    By getting the language from the node, we know that we always have at least one revision to show.

  3. +++ b/core/modules/node/src/Controller/NodeController.php
    @@ -175,11 +176,14 @@ public function revisionOverview(NodeInterface $node) {
           /** @var \Drupal\node\NodeInterface $revision */
           $revision = $node_storage->loadRevision($vid);
    -      if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
    +      // If the node has translations, only show revisions of the current
    +      // language, otherwise show all revisions.
    +      if ($single_language || ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected())) {
    

    Since we remove $single_language, we need to update the comment, the first part doesn't matter anymore, remove it and the otherwise:

    Only show revisions that are affected by the language that is being displayed.

The last submitted patch, 35: revisions_for_node_without_transl-2713587-35-test_only.patch, failed testing.

Berdir’s picture

Status: Needs review » Needs work
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -176,14 +175,13 @@
+      // Only show revisions of the current language. Otherwise only show
+      // revisions that are affected by the language that is being displayed.
+      if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {

There is no otherwise anymore. There is only one case. just keep the second sentence, without otherwise.

tduong’s picture

Status: Needs work » Needs review
FileSize
843 bytes
4.96 KB

Done.

mkalkbrenner’s picture

Just to verify, is the difference described in #28 between the UI and the tests gone?

  1. +++ b/core/modules/node/src/Controller/NodeController.php
    @@ -157,11 +157,11 @@ public function revisionPageTitle($node_revision) {
    -    $node_storage = $this->entityManager()->getStorage('node');
    +    $node_storage = $this->entityTypeManager()->getStorage('node');
    

    The deprecated entityManager() method is used multiple times in this controller. We should replace them all or none of them.

  2. +++ b/core/modules/node/src/Tests/NodeRevisionsTest.php
    @@ -211,6 +213,64 @@ function testRevisions() {
    +    // Enable additional languages.
    +    ConfigurableLanguage::createFromLangcode('de')->save();
    +    ConfigurableLanguage::createFromLangcode('es')->save();
    

    The setUp() of the test already contains ConfigurableLanguage::createFromLangcode('it')->save().
    I suggest to remove these lines here and to add one additional language in the setUp() function.

  3. +++ b/core/modules/node/src/Tests/NodeRevisionsTest.php
    @@ -211,6 +213,64 @@ function testRevisions() {
    +    // View the revision UI in 'DE', only the translated node revision is shown.
    +    $this->drupalGet("de/node/" . $node->id() . "/revisions");
    

    Maybe that's too much, but I would like to see a test for the expected behavior if the languages of TYPE_CONTENT and TYPE_INTERFACE are set to different values.

tduong’s picture

is the difference described in #28 between the UI and the tests gone?

Yep, now everything works as in the test.

1. I guess we want these fixes in another separate issue. Reverted it.
2. Done.
3. Discussed with @Berdir and he said that it is not necessary, we don't do anything here about TYPE_CONTENT and TYPE_INTERFACE that needs to be tested.

tduong’s picture

The last submitted patch, 40: revisions_for_node_without_transl-2713587-40-test_only.patch, failed testing.

mkalkbrenner’s picture

Assigned: tduong » Unassigned
Status: Needs review » Reviewed & tested by the community

Looks good.

catch’s picture

So for me personally, I think we should just show all revisions - that's the actual revision history of the node.

Also:

I am not sure about including eg. the current revision if its not in the current language, if you revert to there and loose translations, that will be a data loss. (Or that revert needs to work differently from the others).

Reverting doesn't delete anything, it creates a new revision and sets that as the default, so you can always get back to the previous state. Also what if you wanted to revert specifically to drop a translation you just added?

Patch itself looks fine, and I'm OK committing this - but can we open a follow-up - I don't think the issue summary nor the patch really covers everything going on in this issue.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update, +Needs followup

I agree with @catch that the IS needs updating to reflect the current patch.

Reading through all the issue comments makes me feel a follow up issue to address the originally proposed solution of showing all revisions somewhere should be created - but as pointed out several times on issue that'd be a new feature.

catch’s picture

tduong’s picture

Issue summary: View changes
Status: Needs work » Needs review

IS updated.

tduong’s picture

tduong’s picture

juliaschwarz’s picture

mkalkbrenner’s picture

@tudog has updated the issue summary.

But isn't it easier?

Problem/Motivation

NodeController::revisionOverview() is supposed to filter the list of revisions to only show those revisions that affect the currently shown translation of the node.
In case that the requested node translation doesn't exist and a language fallback was applied, the list of revisions is erroneously empty instead of showing the revisions that affected the fallback translation.

Proposed resolution

Filter the list of revisions by the translation language of the currently shown node instead of the requested translation.

Berdir’s picture

requested translation vs currently shown node isn't clear to me, otherwise that seems to be on the right track.

I would use the terms "Current content language" instead of "requested translation" and "Current language of the node (with language fallbacks applied) instead of "translation language of the currently shown node". The fallback stuff is important difference between the two cases.

tduong’s picture

Issue summary: View changes

Updated IS as suggested in the comments above.

mkalkbrenner’s picture

Issue summary: View changes

I triggered a re-test in #40 and removed the Remaining tasks from the IS because this potential issue already exists regardless of the proposed patch here and is covered by #2722307: Consider showing all revisions on the revision overview.

mkalkbrenner’s picture

Status: Needs review » Reviewed & tested by the community

re-test of #40 passed

catch’s picture

Status: Reviewed & tested by the community » Needs work
FILE: ...ore/modules/node/src/Controller/NodeController.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 9 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 100ms; Memory: 6.75Mb


FILE: ...core/modules/node/src/Tests/NodeRevisionsTest.php
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 5 | WARNING | [x] Unused use statement
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------


tduong’s picture

Status: Needs work » Needs review
FileSize
978 bytes
4.93 KB

Fixed the 2 marked sniff violations. Needed a while to figure out that it was only about the unused import line :p

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC then.

  • catch committed 934a4b4 on 8.2.x
    Issue #2713587 by tduong, mkalkbrenner, Berdir, Gábor Hojtsy:...

  • catch committed 5ba8499 on 8.1.x
    Issue #2713587 by tduong, mkalkbrenner, Berdir, Gábor Hojtsy:...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.2.x and cherry-picked to 8.1.x. Thanks!

Gábor Hojtsy’s picture

Issue tags: -sprint

Yay, thanks!

Status: Fixed » Closed (fixed)

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