Problem/Motivation
#2513094: ContentEntityBase::getTranslatedField and ContentEntityBase::__clone break field reference to parent entity apparently introduced a nasty regression that all the reviewers missed. There was test coverage, but it was changed by the patch.
See #2540556: Translated paragraphs is not displayed in the correct translation, this broke tests in paragraphs. I reproduced it manually with an entity reference to nodes as well:
Viewing a german node, with a translated paragraph and referencing a node that also has a german translation, both the paragraph and the node is displayed in the EN version.:
We have some test coverage for this in EntityReferenceFieldTranslatedReferenceViewTest but that uses a translatable field. Which is actually a rather uncommon case when you are displaying translated references, since the reference usually doesn't change.
I'm not sure if I fully understand the problem, but have a look at the following line in EntityReferenceFormatter:getEntitiesToView():
$parent_entity_langcode = $items->getEntity()->language()->getId();
Because $items is a non-translatable field, it points to the original translation of the parent entity, so that's en. Somehow, we need to know there that the entity that we are displaying is actually being displayed in german. And i have no idea how to get that information now. We can *not* rely on the current content language, since we want to display the reference in the same language as the parent entity, if possible. There's a second bug here, and that's that it should use getTranslationFromContext(), to fall back to the best possible available translation.
Proposed resolution
Pass $langcode as an optional argument to view(), default it there to the current content language and pass it through to viewElements() and getEntitiesToView(), then use that language to get the referenced entity, using the proper API's.
Remaining tasks
None
User interface changes
None
API changes
See CR: https://www.drupal.org/node/2575729
public function view(FieldItemListInterface $items);
+ public function view(FieldItemListInterface $items, $langcode = NULL);
- public function viewElements(FieldItemListInterface $items);
+ public function viewElements(FieldItemListInterface $items, $langcode);
Data model changes
None
Beta phase evaluation
Issue category | Bug because this breaks translatable entity references |
---|---|
Issue priority | Major because it is not a data loss or security issue but it is a serious problem for I18N and a contributed modules blocker. |
Prioritized changes | None of the official priorized changes points, but it is a major bug, the only viable solution and a contributed modules blocker without workaround. |
Disruption | Minimally disruptive for contrib and custom field formatters but trivial to update. |
Comment | File | Size | Author |
---|---|---|---|
#52 | entity_references_of-2543258-52-interdiff.txt | 9.67 KB | Berdir |
#52 | entity_references_of-2543258-52.patch | 46.7 KB | Berdir |
#50 | interdiff_46_48.txt | 11.22 KB | LKS90 |
#50 | entity_references_of-2543258-48.patch | 50.03 KB | LKS90 |
#46 | interdiff_39-46.txt | 42.38 KB | LKS90 |
Comments
Comment #1
BerdirPatch from @plach from the other issue.
Comment #3
hchonovThat behaviour was expected, I also mentioned it in the other issue:
#2513094-22: ContentEntityBase::getTranslatedField and ContentEntityBase::__clone break field reference to parent entity
Comment #4
BerdirYou might have expected it, but it is wrong.
Whether the *field* is translatable isn't related to in which language the referenced entity is displayed. Or isn't supposed to.
The field being translatable just means that you can have *different* references (e.g. a different image). IMHO, the 80% use case for references is to make them not translatable and point to the same reference, which is then displaying the correct translation.
Comment #5
hchonovMaking ImageFormatterBase::getEntitiesToView compatible to EntityReferenceFormatterBase::getEntitiesToView.
Comment #6
sasanikolic CreditAttribution: sasanikolic at MD Systems GmbH commentedCreated a subclass for EntityReferenceFieldTranslatedReferenceViewTest that sets the fields to untranslatable, reverted the trait from the other patch, fixed the test warning and added a @param for the langcode.
Comment #7
hchonovresolved some nitpicks.
I would say, now the changes reflecting ER from the other issue are reverted properly and with the additional changes from plach we get back the right behaviour.
Comment #8
BerdirWe need to add $langcode to FormatterInterface and document it.
What are we going to do about FieldItem(List)Interface::view() and the bunch of methods that it calls until it ends up in EntityViewDisplay::buildMultiple() again?
Because that also calls $item->getEntity(), so the entity is then again in the wrong language... should we extend that as well with a $langcode column?
Also, we discussed in IRC that $langcode NULL should default to the current content language. I guess viewElements() and so on would then have $langcode as a required parameter and can always rely on it.
Then the fallback here can do away.
too many empty lines.
missing empty line between class and docblock.
Comment #9
yched CreditAttribution: yched commentedSomething bugs me here.
The original code in EntityReferenceFormatterBase::getEntitiesToView() tries to display referenced entities in the same language than the containing entity (which, agreed, is the behavior we want), with this code :
This worked before #2513094: ContentEntityBase::getTranslatedField and ContentEntityBase::__clone break field reference to parent entity. If it doesn't now, this seems to mean we now have :
Meaning,
That looks very wrong ?
$entity->field->getEntity() sould be === $entity, whether the $entity is translated or not, and the field translatable or not ?
Comment #10
sasanikolic CreditAttribution: sasanikolic at MD Systems GmbH commentedAdded $langcode to FormatterInterface, corrected the test.
Comment #12
plachWe cannot have that: untranslatable field items are shared among translation objects.
Comment #13
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedI amended the test so it should pass tests instead of causing exceptions (that the exception happens is the whole point of the test, isn't it?). Is no one working on the issue currently or what's the status? I am waiting for a fix so I can continue with this issuse.
Edit: Nope, I think we want the test to pass again?
Comment #14
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedComment #15
BerdirThat test is completely unrelated and shouldn't be in here it was accidently added from a different issue/patch.
Comment #16
hchonovremoved the test.
Comment #17
Berdirif empty($langcode) then use the current content language, see EntityViewBuilder.
As discussed, $langcode should be a required argument at this point.
Then this can go away completely.
viewElements() also has $langcode no longer optional then.
Missin empty lines above the docblocks.
Update this too.
Comment #18
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is your feedback implemented.
I had a problem with point 4 though:
The viewElements() method has to match the FormatterInterface, which we can't modify anymore. So I left the $langcode = NULL there and use the language manager to get the current language in case the the langcode isn't defined.
Edit:
Uhhh...?
Exception message
Comment #20
BerdirThere are around 10 formatters that have their own injection. This requires that every single of them gets changed + all those in contrib that are doing it "correctly".
I suggest you just access the language manager directly where you need it.
looks like you tried to make the $langcode change here.
Comment #21
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is an updated patch, using
\Drupal::languageManager
instead of modifying the constructor.Also fixed a couple of errors I had which complained about the missing $langcode attribute.
Comment #23
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is the complete version. All core formatters that use viewElements now have the langcode as a parameter, where necessary I added the
languageManager()->getCurrentLanguage()
to ensure we always have the langcode.Comment #24
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedForgot some usages of LanguageInterface, added them.
Comment #25
miro_dietikerNice!
But if the use statements from #24 are needed why didn't the patch in #23 fail?
Comment #26
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedThe tests all supply a langcode, it seems. When I add the line
$langcode = NULL;
before any of the missing Interfaces, the tests fail and complain about the missing interface.Comment #27
plachLooks good, thanks :)
Not to be fixed here, but I'm wondering whether it would make sense to have a trait encapsulating this logic once and for all. We have already many instances of it in core...
Comment #29
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is a freshly rebased patch. This one should apply again.
Comment #30
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedAnother rebase, if someone could finally review this thing and help get this committed, that would be awesome!
Comment #31
yched CreditAttribution: yched commentedWas RTBC before, so back to RTBC if the reroll is green ?
Comment #34
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedThat's not supposed to be there...
Comment #35
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is the correctly rebased version.
Comment #36
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedTest green, setting back to RTBC.
Edit: I also checked all changes this time, I'm not just relying on the testbot :).
Comment #37
alexpottNot used.
Whilst this might be correct it is totally out of scope.
Comment #38
alexpottGiven the solution on this issue I think we have to get this fixed before the release candidate - tagging as such.
Comment #39
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedFeedback implemented, mostly:
1. The interface change forced me to update a lot of classes that might need testing, I'm not really sure, someone who knows what could go wrong could possibly create an issue to extend test coverage for untranslatable fields and their content in a translated entity (I can understand the issue with entity_references, but I have a hard time to imagine the same scenario for the ImageFormatter, as an example).
2. removed
3. removed
Comment #40
tassilogroeper CreditAttribution: tassilogroeper commentedJust to be sure, there are several scenarios we are talking about:
so the bug used to mess up scenario b) resulting in only rendering the default language of the referenced entity (eg. taxonomy term)
hopefully this helps a bit
Comment #41
tassilogroeper CreditAttribution: tassilogroeper commentedI had a look at the code base. If we would abstract away the check for the langcode, we'd end up with a much cleaner code. Hope this will help a bit.
Comment #43
tassilogroeper CreditAttribution: tassilogroeper commentedupdated broken files
Comment #44
BerdirMissing description on return. We usually also don't use private unless we have a really good reason. Not sure about the name, safe sounds like it has something to do with security (safe markup), but it really doesn't. maybe just make it really explicit, with getLangcodeOrFallback() or something like that.
I'd rather not make the users of $langcode responsible for figuring out the default. I'm still not sure about the $langcode = NULL in the view() method. It was just an attempt to not change the interface but if we add it in the FormatterInterface, then we can just as well make it required there so that it's clear that can not be NULL.
Comment #46
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedAlright, I made the $langcode non-optional, updated all the formatters and added a method which set's the langcode in case it isn't defined yet.
Still no additional tests, I was thinking about adding one for the
EntityReferenceTaxonomyTermRssFormatter
, but I'd like to have some feedback about the general patch first (like a testbot run) :).Comment #47
yched CreditAttribution: yched commentedThat's... really weird :-)
- A method named as a setter, that doesn't set anything but instead adjusts the passed param by ref. When you see a setter you expect it changes some state in the object, so that makes you think Formatters hold a state on langcode, that's misleading.
- That's just helper code for "fallback to some langcode if I don't have one", I'm not sure it belongs to Formatters / FormatterInterface. I'd really like to avoid adding kludge to FormatterInterface.
- Also, it's feels really a bummer that each (ER) formatter seems to have to call the method in various places (the patch adds calls from within view() implementations and getEntitiesToView() implementations) ?
What case is it actually needed for ? What is this trying to cover ?
phpdoc misses the typehint, and the description is not really accurate. In the context of that method, that's the langcode in which we want to display the referenced entities.
Comment #48
kataku CreditAttribution: kataku as a volunteer commented#46 - I've applied this to the head of 8.0.x and tested it as part of my investigation of #2540556 and can confirm that it fixes this issue.
Comment #49
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedI've renamed the helper method (
setMissingLangcode
->languageFallback
), better suggestions are welcome.The method is not in the FormatterInterface anymore.
I only call it once in the FormatterBase now, let's see if it is necessary to call the method in other classes.
I also fixed the missing type in the phpdocblock and added the information that it is the language code of the entities to reference.
More test coverage is necessary, but I'm still not sure how to actually test it. (Currently, one test has the added
$translatable = true;
property and extends the same test but sets the translatable to false, can I do the same for tests which are also field formatters of entity references?)Comment #50
LKS90 CreditAttribution: LKS90 at MD Systems GmbH commentedHere is the patch, had some problems after some comment suddenly appeared :D.
Comment #51
BerdirWe only need a fallback in the place(s) where langcode is an optional argument. For the methods where we make it required, we don't.
view() is the main entry point that only has a single override. So lets make it optional here with $langcode = NULL and ensure that we have it set. Then viewElements() and everything below that can assume that the correct langcode is set.
Looking at the patch, I see that's basically what you are doing now, it's just that the argument actually needs to be optional now and the documentation fixed, see below.
As @yched said, this doesn't set anything, it returns.
Also, if we only have a single call left, then we don't really need a helper method for that now, do we? That would avoid that problem (naming, documentation).
This then needs to change accordingly, it's optional on view() but not on viewElements() so there won't be a default there.
Comment #52
BerdirDid that. Removed the helper method, fixed docs, remove use statements that aren't needed.
This is as minimal as possible now I think.
Comment #53
yched CreditAttribution: yched commentedYay. RTBC if green
Comment #54
BerdirComment #55
BerdirCR created (https://www.drupal.org/node/2575729), issue summary and beta evaluation updated.
We should be good to go here.
Comment #56
alexpottDiscussed with @yched and @Berdir - without this change we can;t deliver the expected functionality of entity references in Drupal 8 - this for me is very close to a critical - therefore I think implementing this with a very obvious and easy to fix API break makes sense. Committed d0c1376 and pushed to 8.0.x. Thanks!
Comment #58
s_leu CreditAttribution: s_leu at MD Systems GmbH commentedI had some issues with untranslatable entities and translatable entity references that is quite related to this, documented this here #2584745: Entity references should be displayed translated on non translatable entity types.