Problem/Motivation
When the content translation module is enabled, the "Translate" local task is always visible, even if content translation has not been enabled for the bundle.
Steps to replicate
- Install a new site with the minimal profile.
- Enable the Language and Content Translation modules.
- Verify at /admin/config/people/accounts that content translation has NOT been enabled for users.
- Go to /user/1
Result: the "Translate" tab is shown and displays an empty translation overview when clicked. This appears to occur for all entities (confirmed for nodes and taxonomy terms). If you add a language, the translation tab on taxonomy terms for example will list them as translation targets. Actually attempting to visit those links will result in a WSOD.
Proposed resolution
The "Translate" tab should not be visible (the route should not have access) when an entity type's bundle does not have translation supported.
Remaining tasks
Review. Commit.
User interface changes
"Translate" tab will only be shown / tab will only have access when translation is enabled for the entity bundle.
API changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #110 | 2188675-translate-local-task-110.patch | 12.47 KB | gábor hojtsy |
| #104 | 2188675-translate-local-task-104.patch | 12.39 KB | gábor hojtsy |
| #104 | interdiff.txt | 3.75 KB | gábor hojtsy |
| #103 | interdiff.txt | 4.31 KB | gábor hojtsy |
| #103 | 2188675-translate-local-task-103.patch | 11.41 KB | gábor hojtsy |
Comments
Comment #1
gábor hojtsyComment #2
pfrenssenComment #3
pfrenssenThis is caused by a malconfiguration of the access checks of the route to the translation overview in ContentTranslationOverviewAccess. It checks if ANY of these conditions are met:
Meaning that if a user has the 'translate any entity' permission, the overview is always shown, even if the other access check returns DENY for good reason, for example if translation is disabled.
This problem is also present for the other routes, they all are accessible for users with the 'translate any entity' permission. Shall we expand the scope of this issue to fix those too, or do it in separate issues?
Attached patch fixes the problem for the overview route. I ran a few tests and some of them need work. For example ContentTranslationContextualLinksTest only passes because it relies on the overly relaxed access check. I wrote that test so I solely blame myself for that one.
Comment #4
gábor hojtsyThis makes sense, I think the other routes would be good to fix here, they should hopefully only be a couple lines. :) IMHO it would be superfluous to open even more issues for them.
Comment #6
pfrenssenDid some work in trying to get ContentTranslationContextualLinksTest to pass, but did not succeed yet.
Unassigning myself as I won't have time to work on this in the coming days.
Comment #7
yesct commentedI think next time it was set to review, it would go to the testbot anyway, so might as well send it now.
I'm rerolling this.
Comment #10
yesct commentedeasy one. no interdiff because it's a reroll.
conflict was in ContentTranslationRouteSubscriber
patch was just taking out a line, and a context line above it changed:
So I kept the context line from head and took out the line.
yielding:
was caused by #2165155: Change $entity_type to $entity_type_id and $entity_info to $entity_type/$entity_types
Comment #12
gábor hojtsyLooks like the contextual links code / tests still need some work :/
Comment #13
vijaycs85Ok, after few hours investigation, found some wired behaviour with contextual link
Comment #14
kfritscheGoing to work on this now - #DrupalDevDays
Comment #15
kfritscheDid first some changes, because menu_router_rebuild was removed (#2107533: Remove {menu_router}) and field config entities were renamed.
The problem here is that the access callback does two different things. Checking if the entity is translatable and the access to translate the entity.
I did some refactoring to move the first check out of the access callback and the access callback is really only checking if you are allowed to translate the content.
Therefore Eric and me decided to introduce a new method at the Entity to check if it is translatable and moved the old isTranslatable to isBundleTranslatable, because thats what it was.
The "Content translation contextual links" is now working. Lets see what else this maybe breaks now ;)
Comment #18
kfritscheFixing the failing tests.
Was an error because of the changing of splitting the access hook into two separate functions and missed a location.
Also we do not save anything anymore in content_translation if the content is not translatable, like the description of the NodeTranslationUITest::testDisabledBundle noted, but looked before that there is one row in it.
Comment #20
sweetchuckOne more problem is detected:
The "translate" link is visible on the "admin/content/comment" page, even if only one language is enabled.
Comment #21
kfritscheFixed the unit test, but I'm not very confident that this is a good way. Would need some input here how to do it better.
What I did was - added a mock for TypedDataManager::getPropertyInstance which returns FieldItemList, so that the langcode can be set and the defaultLanguage for the entity will be set. But as it is a Mock it always returns LANGCODE_NOT_SPECIFIED and thats why I added this to the return of LanguageManagerInterface::getLanguages, but with the english language. I know this is incorrect and should be fixed. But I'm not sure if we can somehow Mock the getPropertyInstance in a better way?
Another option would be to Mock $entity->get('langcode'), so we are skipping all of this. But that would mean, that we create this Mock somehow before we construct the $entity. Is that possible somehow?
Also renamed isBundleTranslatable to supportsTranslation, like it will be used in #2143291: Clarify handling of field translatability and I feel that this name is much better.
If the general idea of this is accepted, the other locations should be fixed too in a similar way.
These are:
* Route alter for '/add/{source}/{target}'
* Route alter for '/edit/{language}'
* Route alter for '/delete/{language}'
Comment #23
kfritsche21: 2188675-21-content_translation-overview_access.patch queued for re-testing.
Comment #24
dlu commentedComment #25
dlu commentedComment #26
dlu commentedReroll of #2188675-21: "Translate" local task always visible, leads to WSOD.
Comment #28
dlu commentedFixed reroll for #21, replacing #26.
The code below, from ContentEntityBaseUnitTest.php:
this patch was trying to add the langcode array element to a hunk of code that was removed in #2211949: Support keeping new revision id for migrate, so the resolution of the conflict was to keep the code from HEAD (nothing) – in other words this change was left out.
Also picked up the change in the context line (up casing of Translate) to resolve the conflict below:
This is how I resolved it:
Comment #29
dlu commentedBow to the 'bot's sensibilities.
Comment #30
dlu commentedComment #31
dlu commentedWhen you're up to your posterior in re-rolling sometimes it is hard to remember that the original mission was to review the patch…
So here goes. After applying the patch from #2188675-28: "Translate" local task always visible, leads to WSOD the resulting behavior is:
Route: page not found.
Route: access denied.
Route: access denied.
Route: access denied.
Route: accessible.
I think that covers the expected combinations and they work correctly. I suppose if I were a better person I would have written the tests for these…
After reading the patch, I have a few comments/questions:
#1
Question: assuming the conditions are in order, I don't understand how the second test works – is it really that convoluted to find out of an entity has a language defined. I'm also wondering what it means for a entity to have a "language defined." I would think that the tests should be: 1) Is the entity translatable (i.e. the content type has the translatable box checked), 2) Is this a multilingual site, and 3) Do we have more than one language installed on the site. Is that what the test is doing? Or am I thinking about this wrong?
#2
The @param and the actual parameters to the function need to match.
#3
These two functions seem like they are doing about the same thing. Is that true? If they are could they be implemented as one function or named similarly, say isEntityTranslatable() and isBundleTranslatable()?
#4
Either: 1) Schrödinger's cat is lurking nearby, or 2) this test doesn't actually run, or 3) it works for some reason that could really use a comment…
#5
The cat again?
Comment #32
dlu commentedA couple of the problems (if YesCT is to be believed) noted in the review above, are core gates that will have to be resolved before RTBC.
Comment #33
vijaycs85Thanks for the review and great report in #31. here is some updates on them:
#31.1 - Yes, it checks for translatable and multilingual site (internally multi-lingual is true, we have more than one language. Check core/modules/language/lib/Drupal/language/ConfigurableLanguageManager::isMultilingual for more info).
#31.2 - Fixed.
#31.3 - supportTranslation() is more of configurable and renaming the method is out of this issue scope.
#31.4 - its the mock object that does the magic. As you can see few lines above assert
We return true only first time when 'getBundleInfo' method call.
#31.5 - Same as #31.4.
Comment #34
dlu commentedThanks for taking the time to explain the magic of PHP Unit and what goes on in the test for locked languages.
Comment #35
plachPlease, let's not make this method part of the public API, as we have not reached an actual consensus on the "enabled" vs "supported" terminology yet (see #2143291: Clarify handling of field translatability). We can make it public and add it to the interface later, if that turns out to be the solution we agreed upon. Doing the opposite would imply an API change.
Also,
supportsTranslation()here means the opposite of what we are referring to with "translation support" in the linked issue:isTranslationEnabled()would be more like it.Comment #36
vijaycs85Sorry for my fault push back for @dlu review. Didn't realise that method introduced in this issue. +1 for isTranslationEnabled.
Comment #37
Jalandhar commentedHi Plach and vijaycs85,
I have changed supportsTranslation() as isTranslationEnabled() and made it as Protected. Updating the patch with these 2 changes.
Comment #40
Jalandhar commentedRemoved isTranslationEnabled() from TranslatableInterface.php and updating the patch
Comment #42
Jalandhar commentedHi,
Error in the previous patch was because of changing method isTranslationEnabled() to protected.
(PHP Fatal error: Call to protected method Drupal\Core\Entity\ContentEntityBase::isTranslationEnabled()).
PS: Did not get the above error when running tests locally.
Comment #43
vijaycs85the patch in #33 has it is public. just making the renaming.
Comment #44
gábor hojtsySome changes are not clear to me in the patch:
Where is this access function used? I assume it is used in combination with the entity access check or otherwise the removal of the conditions would not be possible(?). Is the new idea that entity specific checks would be in the entity access checker and global things kept here?
Looks confusing on a quick look.
Why would this now change? None of the other code in the file changed?
Comment #45
plachPlease, let's make this protected and remove the unit test. We can restore both if needed but there is really no reason to expose this detail in the API atm.
Comment #46
gábor hojtsy43: 2188675-translate-local-task-43.patch queued for re-testing.
Comment #48
vijaycs851. Reroll
2. Changes as per #45
3.
#44.1 ContentEntityBase::isTranslatable() has got the checks already covered.
#44.2 - not sure, Seems the test started failing because of this change in #15.
Comment #49
gábor hojtsyThanks for making this work again :) I guess I'm still hung up on the same two things. I just don't know enough and need to be enlightened.
Param documented twice.
So as I said above this is much less checks than it was before. How is this used in combination with the above methods? How does it ensure all the same checks are still done?! Why did they move places now?
Still don't understand this change. Its doing the same logic but then a different check and still passes? How is this patch making no entry appearing anymore in that table? How is that related?
Comment #50
robertdbailey commentedI've re-rolled the patch, and then based on Gábor's review have separated the tests in the patch to run separately. We can then hopefully get a better idea of why the test change was made and whether the tests are failing properly before the patch and succeeding now because of the patch.
I have also updated the summary with some screenshots and remaining tasks.
Comment #52
gábor hojtsySo do I understand it right that the screenshots are pre-patch? That is not what we expect to happen WITH the patch.
Comment #54
robertdbailey commentedCorrect, @Gábor, the screenshots just demonstrate the problem, not the fix.
There are many more failures/exceptions with the tests only than I would have expected. Are these reasonable? Also, the rerolled patch is now failing because of a missing "cherry" plugin. Was this something introduced in the last two weeks since the last patch?
Comment #55
vijaycs8550: 2188675-translate-local-task-50.patch queued for re-testing.
Comment #56
vijaycs85There was some random fails. It is green now.
Comment #57
gábor hojtsyCan someone respond to my concerns in #49? Instead of leaving this lingering, we could get this in. We only need me understand it or someone else who understands it to positively review :) This been going on for so long. Let's get there soon?
Comment #58
vijaycs85Sorry @Gábor Hojtsy. I thought of having a deeper look for the update and missed this issue. Here is the update:
#49.2:
We had 5 checks on content_translation_translate_access.
1. $entity instanceof ContentEntityInterface
2. empty($entity->getUntranslated()->language()->locked)
3. \Drupal::languageManager()->isMultilingual()
4. $entity->isTranslatable()
5. (user_access('create content translations') || user_access('update content translations') || user_access('delete content translations'));
Here is the status of updated code on each item above:
#1 This has been removed because content_translation_translate_access got the typehint as ContentEntityInterface instead of EntityInterface. Guess, this can produce exception for access check. So we can rollback this change.
#2 and #3 aren't specific to content translation. So they are part of $entity->isTranslatable() now.
#4 called explicitly on every place where we checked content_translation_translate_access. IMHO, this can be moved back to content_translation_translate_access
#5 already there in content_translation_translate_access
#49.3:
It looks like the test used to be checking wrong behaviour. The initial comment says 'Create a bundle that does not have translation enabled.', then asserting one row in content_translation table?. The update make sure we don't create a row, if no content translation.
here is a rerolled batch (for PSR-4) and updates.
Comment #60
vijaycs85Impact of #2167167: Remove field_info_*()
Comment #62
gábor hojtsyYeah, that we always call $entity->isTranslatable() along with calling access sounds problematic. Once someone just calls the access check and assume the response is trustable they will get bitten. I don't have strong opinions on the typehint, if the hook signature does not allow this, then better not do it, but if we can do this, then its fine.
Comment #63
vijaycs85the empty($this->getUntranslated()->language()->locked) part causing the fail. Not sure if anything changed recently around this.
Comment #64
vijaycs85Spent couple of hours on this today. let's add credit to this sprint :)
Comment #65
robertdbailey commentedWhile working through the case matrix in #31, I was not able to enable translation for users, so logged a separate issue, #2300347: Regression: checkbox to enable translation for users does not save .
Comment #68
vijaycs85Back to work on this issue :)
Comment #69
fran seva commented@vijaycs85 I'm rerolling :)
Comment #70
fran seva commentedAttached reroll
Comment #72
gábor hojtsyThis is now called field_storage_config, this is the reason for the exception.
Comment #73
robertdbailey commentedUpdated string field_config -> field_storage_config per #72
Comment #75
marthinal commentedI'm sorry vijaycs85 because I know you are working on this, but I was very curious about this bug. :)
I think that I found the problem... Adding 'und' as langcode by default.
Hope it helps!
Cheers!!
Comment #76
robertdbailey commentedI reviewed the states in the matrix in #31 and all the right behaviors of the translate link and resulting translate page appear to be working properly, correctly hiding and showing the link and page per the matrix. I've also separated the current tests only into their own patch in order to review the failures.
Has the concern in #62 been resolved yet? I see from the code that $entity->isTranslatable() is still called every time from within function content_translation_translate_access. Other than possibly that comment, it appears any concerns have been addressed.
Comment #78
vijaycs85Actually it is answer for #58.4. As @Gábor Hojtsy current implementation doesn't harm and may introduce problems if we plan to merge them together.
- No problem. Assigned to me as I moved away from this issue sometime back and want to keep it in my list.
Overall, patch in #75 is ready for review.
Comment #79
gábor hojtsyThe patch looks good to me, but it would be important to get validation from @plach that the content entity base changes are good. He had concerns about exactly this in #35.
Comment #80
plachChanges to the base content entity look fine to me, thanks.
Comment #81
gábor hojtsyAll righto, looks good to both of us then :)
Comment #82
vijaycs85Needs a re-roll...
Comment #83
vijaycs85Back to RTBC
Comment #84
alexpottWhy are we creating this new method? Its only use is in isTranslatable().
We're dealing with typed data here of which entities are an example. Not sure the docblock change is necessary.
Unrelated change.
Unrelated change (as far as I can tell)
Unrelated change
This looks like madness - I think it is worth breaking this test into 3 separate test methods. $this->at() is very fragile and kind of horrible.
Comment #85
gábor hojtsyThis results in WSOD, because the translate tab will show, it will display the list of languages, but going to one of the pages from the list will WSOD. Until you actually configure it to support translation. If the user has no access to the routes in the first place, then we avoid that WSOD.
Reproduced this WSOD as part of my Drupalaton training. Elevating to critical due to the WSOD.
Updated and shortened the issue summary.
Comment #86
vijaycs85#84.1 - Please check #58. Answers same question asked by @Gábor Hojtsy at #49.
#84.2 - Reverted.
#84.3 - Reverted.
#84.4 - Reverted.
#84.5 - Reverted.
#84.6 - fixed.
Comment #88
vijaycs85#86 was agains 8.x :(.
Comment #94
vijaycs85Missed few test fixes on merge conflict...
Comment #95
gábor hojtsyI think the only remaining concern was #84/1 and I see no reason not to do that. A minor change.
Comment #96
gábor hojtsyI only did a minor update. @vijaycs85 addressed other concerns from @alexpott.
Comment #97
alexpottNeeds a reroll
Comment #98
gábor hojtsyRerolled.
Comment #99
alexpottUnnecessary change.
This appears to be completely unnecessary
$language here is the english language and 'und' needs to be locked. I don't think we're testing what we think we are testing.
This is very convoluted
What is also weird is I can't see a test added for the appearance and non-appearance of the "Translate" local task.
Comment #100
vijaycs85Reroll + fix for #99.3
Comment #102
gábor hojtsyLooking at the rest of the issues.
Comment #103
gábor hojtsy- Fixed the fix for #99.3, now with actual language objects. The test indeed does not dabble with locked/unlocked languages. The entity is created in English, so whether the 'und' language is locked or not does not have any repercussions for this test. Once/if we test with a 'und' entity (which we don't yet), that becomes interesting. Will do that in an upcoming patch. (In other words, testIsTranslatableForUndefined() is not testing what it thinks it tests).
- #99.4 solved by separating the methods, so now not complicating with number of invocation tricks :D
- #99.5 solved by adding tests for the translate page accessibility itself.
Comment #104
gábor hojtsy#99.2 removed now, indeed not necessary.
#99.3 final solution now here :) The test indeed tested something else then it thought. The entity was supposed to be created English but was in fact 'und'. Once we defined 'und' as a locked language, existing stuff started to fail. So I fixed the original entity to have an 'en' langcode. Also added an additional entity with 'und'. Then we test the entities are 'en' and 'und' respectively and that their locked status is correct, and with only that variable test against a system where their bundle is translatable and multiple languages exist. Then 'en' will be translatable, 'und' will not be. testIsTranslatable() is a tiny bit too defensive now maybe, but since we did so much where we did not know the test data, I think its best to keep the test data asserts as well.
All @alexpott's concerns resolved.
Anything else?
Comment #105
sutharsan commentedI can not contribute to the code of this patch. But I've put the end result to a test.
* Fault can no longer be reproduced as described in the issue summary.
* "Translate" tabs only appear when Translation of the entity type is enabled AND a second language is added.
* Removing the first language makes the Translate tab disappear.
* Translation entity list pages are Ok, no WSOD.
It works good.
I found two problems, but those are not introduced by this patch. I will report these separately.
Comment #106
sutharsan commentedFound, but not related, problems:
#2328161: "Enable Translation" checkbox default non-predictable for new bundles
#2328167: Warning array_filter() in ContentTranslationController->overview
Comment #107
penyaskitoFrom code standards perspective, this looks good. Code makes sense too, and adds more testing and cleans up misnamed tests.
As @Sutharsan tested it and said that works as expected, I guess we can RTBC this one.
Comment #108
sutharsan commentedFound an other bug, but fortunately that gets fixed by this patch: #2328333: Notice: Undefined index: column_groups in content_translation_field_sync_widget()
Comment #109
webchickSorry, no longer applies.
Comment #110
gábor hojtsyDone :)
Comment #111
webchickOk great, thanks!
Committed and pushed to 8.x. Woohoo!
Comment #113
vijaycs85yay! thanks!