Problem/Motivation
When a translation of a trashable entity is deleted, Trash module does not intervene and just lets it be removed, even though the confirmation dialog says the entity will not actually be deleted.
Steps to reproduce
- Enable Trash support for Nodes.
- Enable Content Translation and add one or more extra languages.
- Create a node
- Add one or more translations
- Delete a translation. The confirm dialog says it will be trashed, but it actually gets deleted.
- Delete the entity via the source language. All existing translations get trashed
Proposed resolution
Add asymmetric trash support for translations by making the 'deleted' field translatable. When an entity is saved, check if a translation was deleted. If so, restore the translation and set the 'deleted' value on that translation.
Trash already depends on the storage backend being SqlContentEntityStorage compatible, which should also mean it has translation support, so assuming 'deleted' can always be set as translatable should be safe?
Remaining tasks
Test with untranslatable entities. Possibly adapt the code so it does not enable translation unless the entity is actually translatable?
Create asymmetric vs symmetric translation toggle?
User interface changes
The Trash list now includes a "Language" column and includes only the trashed translations of an entity.
(Multi-language listings aren't well supported by entity queries so we'll have to do with them being bunched together per entity - but that could also be desirable in some cases.)
API changes
None. This "just works" based on the actual translation sent as the $entity argument to existing functions/methods.
Data model changes
The 'deleted' field is now translatable, which does not directly alter it for Nodes or other content entities, but there may be cases where the field moves from one table to another.
| Comment | File | Size | Author |
|---|
Issue fork trash-3376216
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
Comment #3
twodComment #4
twodComment #5
twodJust discovered I forgot a few calls so it restores the original translation instead of the removed one.
Will see if I can adapt the tests to catch that as well.
Comment #6
twodThese changes improves the GUI somewhat and better track what happens when you normally delete an entity.
Translations can now be independently deleted, restored or purged without affecting the source language from the language overview page.
Deleting or purging a source language also affects all translations.
Restoring a source language does not automatically restore all translations, but that is easily done from the entity's translation overview page.
The new revisions when deleting are enforced to be the default revision, or you could end up with a translation that could not be edited because the latest (draft) revision was deleted but the default revision was still published.
Content Moderation may have "opinions" on when this is allowed, but since we have the storage trait we can ensure Trash gets the last word. Maybe we should even set the "is syncing" flag for these operations?
One side effect of that is that restoring something where the latest revision was a draft (unpublished) and the default revision was published, will not actually be published after a restore - as the default revision is now the unpublished draft (without the deleted timestamp). You would have to fix that either by publishing the draft or by restoring a previous revision as the default manually.
Toyed with the idea of removing the latest revision completely instead of just clearing the deleted timestamp (for revisionable entities), but ran into complications because you can't delete a default revision and it made me question if it was reasonable to always set the revision before the deleted revision as the new default before removing the "deleted" revision.
This could use some functional tests for the GUI changes in the translation overview and the trash listing itself, but I figured it'd be best to get some feedback first before spending that time in case big changes are requested (or this is rejected completely).
Comment #7
ezkg commentedThis MR needs to be added to handle the trasnlations deletion. As it stands the trasnlated pagges are being permanently deleted even if it states that it will be available in the trash. This occurs sppacialy with a content that has 2 or more trasnlation.
Comment #9
twod@EZKG, it should already be handing translations in that way, and there are tests included to verify that, but I can take another look soon.
There have been multiple changes since my version and the tests no longer pass so I'll need some time to dig into what's going on.
Comment #10
amateescu commented@TwoD, thanks a lot for working on this! This functionality is very much needed, so it's definitely not going to be rejected :)
I agree with @penyaskito above, the MR looks mostly ok to me as well. The biggest concern I have is the need for creating default revisions when deleting an entity (or a translation), because that would completely break the Workspaces use-case which only creates default revisions when a workspace is published to Live.
We now have
TrashStorageTrait::restoreFromTrash()(which wasn't available when this MR was created), can we restore all translations there? Or was there another reason for not doing it in the first place?Comment #11
twodMy reasoning at the time was that we don't easily know when a translation was deleted. If we force all of them to be restored it may unexpectedly restore some that were deleted much earlier, but I suppose the grouping in the trashcan should make that fairly easy to spot, unless you have a lot of languages. Either way, if the confirmation message explicitly lists the translations to be restored I would not be worried about that happening.
However, I would prefer if we then also had to explicitly pass in the translations to be restored to
TrashStorageTrait::restoreFromTrash, and that it only saved the entity once even if multiple translations were passed in.That it now explicitly saves the entities introduced some problems for this issues as it more or less broke the API.
The tests are calling
trash_restore_entity($translation)multiple times before saving an entity, and that is no longer possible without creating multiple pointless revisions.Granted, it was merged before 3.0 so I can forgive that, but it's a twist which means this will need some additional changes.
I'll see if I can implement that.
Comment #12
twodDidn't implement restoring all translations at once yet, just wanted to get it back to passing the tests while awaiting feedback on the rest of my comments. At least I think it's working the way it did, a bit late here so didn't do much manual testing yet.
Maybe we can make restoring all translations at once optional? I mean when it's done via the trashcan, as it I don't think it make much sense when doing it via the translation overview.
Comment #13
twodI think this needs another look from someone other than me at this point.
As noted above, I pulled out all remaining revision/content_moderation related code. A bit late I noticed I had also pulled out the lines which set a new revision, but everything was still working as it should - and as @amateescu mentioned, this module should probably not enforce it unless strictly necessary anyway.
Comment #14
twodI got tired of restoring translations one by one, so I implemented @amateescu's suggstion from a while back and made it so restoring the original language by default also restores all translations - but they can be manually excluded if you don't want that.
Restoring an individual translation now also asks if you would like to restore other translations as well, but then the default is not to do it.
I've got nothing more planned for this right now so just awaiting feedback. :)
Comment #15
amateescu commentedJust merged the latest 3.x, and I'll try to review this again ASAP :)
Comment #16
lawxen commentedI'm wondering whether the MR can solve this issue: https://www.drupal.org/project/trash/issues/3414901
Based on the comment here: https://www.drupal.org/project/trash/issues/3320566#comment-15356737
Comment #17
amateescu commentedWe've added a temporary safeguard for deleting translations in #3494419: Prevent deleting translations until we have full multilingual support, and updated this MR to remove it :)
Comment #18
dziabodo commentedHi,
i did fix for current patch. I had problem with entity language because always i got default langcode. Now we have translation specific language and we are able to restore entity translations.
Comment #19
amateescu commented@dziabodo, yeah I was a bit hasty with my "refactoring" for the purge and restore forms. For the moment I reverted those changes, so the current code from the MR should apply and work fine on 3.0.9. Now I need to rebase this on the latest -dev code.
Comment #20
aman_lnwebworks commentedHi
I followed these steps in Drupal 10.4.3 with PHP 8.2:
1: Enable Trash support for Nodes.
2: Enable Content Translation and add one or more extra languages.
3: Create a node
4: Add one or more translations
Now, when I try to delete the translation, the node gets set in the same language instead of the translation being deleted.
After deleting the node with the default language, the node is removed successfully, but different issues are appearing.
Issue 1: Trash Page Error
When I navigate to /admin/content/trash, I get the following error:
Error: Call to member function getCurrentLanguage() on null in Drupal\trash\Controller\TrashController->render()(line 111 of modules\contrib\trash\src\Controller\TrashController.php)Issue 2: Revisions Tab Error
When I open the Revisions tab of the node, I encounter this error:
Error: Call to a member function hasTranslation() on null in Drupal\node\Controller\NodeController->revisionOverview() (line 167 of core\modules\node\src\Controller\NodeController.php).An attached image illustrates the steps taken before encountering the error.
Comment #21
aman_lnwebworks commentedHi,
I have added a patch to delete the translation, but after deletion, it does not go into the trash.
Now, we need to store the deleted translation in the trash.
Comment #22
amateescu commentedUpdated the MR on top of latest 3.x. Might still need some work around trash access -> entity access, so leaving the status at NW for now.
Comment #23
igor mashevskyi commentedI really like the trash module, but I also need a way to completely delete translations on my site. I've created a patch to do this, which others might find useful. It's pretty much the same idea as in #21, just with the right messages.
Comment #24
twodI've done a fairly big refactor of the patch to accommodate the feedback by @plach and @amateescu here and via chats.
One major change due to this:
Trying to handle deleting and restoring pending revisions (drafts) was turning into a problem with more and more edge cases.
So, instead of trying to figure out which revision is actually the latest one (via
getActive()or otherwise), the patch now usesgetCanonical()per translation.It also also creates a new revision [if the entity is revisionable] on delete, restore, or save with a removed translation.
This has the effect that any pending revisions (drafts) are effectively ignored and repeated trash/restore operations will always act on the published/canonical/default revision per language, keeping what was published and "skipping" drafts.
You can still revert to previously pending revisions from the revisions overview if you wish, but because Content Moderation module does not have a "revert as draft" operation, it will always publish them as the new default revision. Another module could add such an operation if desired.
After a while, this felt more natural to work with than drafts becoming soft-deleted. Content Moderation doesn't allow you to delete drafts from the translation overview, but if you delete a revision from the revisions overview - it's a separate operation which Trash ignores - so they will always be permanently deleted.
I've added several more tests, and refactored some of the existing ones to be a bit more reussable for verifying that base operations still work the same with the steps of enabling only language, then content_translation,content_moderation, and to some degree workspaces.
During this testing we found that a recent change to Workspaces in 11.2 hinders creating drafts in the Live workspace, which limits the amount of testing which can be done without patching core to revert the change from #3499181: Disallow saving the current default revision as a non-default revision - or having Trash work around it.
I did not include such a workaround in this branch because I did not fully understand why that change was introduced and what consequences it may have for Workspaces.
The branch is becoming increasingly large due to the many things it has to cover, including compensating for in-memory caches used by some of the base classes for the "latest revision" - which may be different depending on the current Trash context.
I hope the new tests somewhat compensate for this as the coverage is pretty good.
(Depending on how #2620980: Add static and persistent caching to ContentEntityStorageBase::loadRevision() is resolved, we may need to compensate for that too.)
I apologize for the messy commits, I accidentally did much of the work without realizing I was in the middle of one of several times I had to pull in 3.x again. Love to see the progress, but you did throw me for a few loops. ;)
Btw, I did the changes on D11 and forgot not all of it works for D10 yet, planning to fix that soon.Done.Comment #27
codebymikey commentedAdded commits to get this over the line and having all the tests pass, thanks @amateescu and @twod for most of the heavy lifting!
1. Ensured the module was compatible with upstream changes from https://www.drupal.org/node/3535160 and https://www.drupal.org/node/3553211
2. Address bug where content could be permanently deleted when the
?in_trash=1is set, however the description stated otherwise.3. A regresssion in the access check implementation meant that anonymous users could access deleted entities by passing
?in_trash=1, I've now addressed it and added test cases for them so that there are no further risks of regression.Manual tests are more than welcome!
Comment #28
inregards2plutoWhat work is left to be completed on this? We just got a request to provide the ability to delete translations after someone ran into the "Moving translations to the trash bin is currently not supported. Unpublish the translation instead." message. It looks like the MR is able to be merged and tests are passing? I'm happy to do some manual testing if that's what we need to get this merged in. Otherwise, I can just roll up a patch based on the current MR for now.
Update: I did not refresh the page before posting and didn't see the comment about manual testing '^^ I can do that now!
Comment #29
inregards2plutoComment #30
twodI would say it's mainly testing that's needed. We already know of a few D11 issues that affect translation handling, and touch interaction with this module, I think #3546214: Can not create a new draft translation in a workspace is the main one. It doesn't directly break this feature, but makes it confusing to work with.
As we just got the cron job and Drush command taken care of I don't think there's anything significant left to code (except maybe some cleanup). Btw, make sure you have updated to a patch which includes that if you do use those, or you may suffer a whole entity being purged instead of just its translation!
At DrupalCon Vienna @amateescu and I discussed possibly splitting this issue up into smaller chunks to make it easier to review and commit. Maybe it could be done by first refactoring to use the general structure from the patch, along with the new cache swap, etc. Then we layer actual translation handling on top, but I'm not yet sure how difficult that would be.
We also briefly looked at (separate from this issue) getting in a new way to handle viewing and manipulating soft-deleted entities via separate routes, instead of trying to deal with passing around the 'in-trash' query parameter. That could affect this implementation slightly.
So, instead of
/node/123/?in-trash=1you would go to/trash/node/123/[view/restore/purge]. We though that may simplify some access logic and the URL rewriting, as well as allowing stricter access control on those routes , and reducing the risk of interfering with other modules's access checks.These things would then most likely become part of a new major release.
I am currently looking to get more time to work on this module through my job. My team actually decided today to propose that as part of my tasks before the end of the year, but that ship turns a bit slow so I don't have go on that yet. (This would be a good time for me to do that though.)
A huge thanks to the people jumping in and keeping this branch fresh!
Comment #31
amateescu commentedManual testing would be great, but what's really needed here from my POV is to split up as many things as possible into separate issues in order to get the current monster MR into a reviewable state.
One example is the access handling and the huge amount of test changes. Those could be moved into a smaller issue, and this MR would just add the translation handling.
Another example are the recent changes around
?in_trash=1. I'm not sure if the problems pointed out in #27 are caused by the current MR, but still it would be nice to split out that work.I tried to review the MR during DrupalCon Vienna, and all the stuff around revisions left me super confused. I'm not sure why we need to do so much special-casing instead of just relying on the fact that the entity objects received by
\Drupal\trash\TrashStorageTrait::delete()are already the intended (latest / translation-affected) revisions, and we only need to create a new revision for them using\Drupal\Core\Entity\RevisionableStorageInterface::createRevision(), which already handles all the heavy lifting around translations.Comment #32
twodI added some more comments to explain the reasoning behind some of it in the PR, but I don't remember if they were already there when you looked at it. Let me know if you want more details on something specific. It's easier for me to get time to document it better than to begin splitting it apart right away. It may also be best to be on the same page about how it works before deciding exactly how to split it.
I'm writing this a bit tired just before going to bed, so there may be caveats, but I hope it's helpful enough for now. 😅
Unfortunately, there is no way to fully guarantee that we do implicitly operate on the correct revisions or translation in the trait's methods as that is often up to the caller. We don't want to "fork" the revision tree, and that should at least not happen via any of the forms in Core, thanks to the new access checks.
The latest version does use
RevisionableStorageInterface::createRevision()where possible, except last in::doPreSave()due to that method always cloning the entity.doPreSave()is expecting the entity to be modified by reference and then passed on to::doSave(). Given we may create multiple new revisions indoPreSave()- something it really wasn't intended to do - we have to tread carefully to make sure we use the correct revision as the source for each language, and that the passed in active translation is handled last and its reference is preserved. Core has already checked that someone isn't trying to delete the default translation and thrown an exception, so that case we don't have to consider. But someone could have called$entity->save()with either the default translation or any other still existing translation active, so we can't make assumptions about that.Since we check if the entity has been modified in any way (other than deleting translations), it is then safe for the last part to do the bare minimum to force the parent method to create a new revision for the 'active' translation.
If we do send the default revision for each translation into
::createRevision()(as we should), it technically does not do anything more than set the new revision to be the default, tag it as affecting the passed in language, and invoke theentity_revision_createhooks.(Those hooks will not be invoked there for the initially 'active' language that
$entity->save()was called for, but the preparation of the entity form has already called them for that language before the entity was saved.)There is a bit of a gray area if something tries to delete a translation from an older revision. I don't think I have actually tested that. If everything else in Drupal actually allows that, there may be hidden dragons. I suppose we could say that behavior is undefined, and in that case you're on your own and should not only deactivate the trash context, but also ensure the other revisions still make logical sense.
Comment #33
codebymikey commentedAttached a static patch excluding the tests.
I'm a proponent for this, as it makes things easier.
The behaviour was only replicable within the current state of the MR at the time.
And if it doesn't introduce too much work, splitting the MR is fine, but I'd recommend we keep most of the existing functional tests within the current version where possible, so there are no unintended regressions. As from my manual tests, everything seems to work pretty well.
Comment #34
inregards2plutoAlso chiming in to say I manually tested the patch from #33 and, as far as I can tell, everything is working great :)
Comment #35
amateescu commentedI've opened #3560348: Improve handling of the 'in_trash' query parameter for splitting out these two commits:
https://git.drupalcode.org/project/trash/-/merge_requests/9/diffs?commit...
https://git.drupalcode.org/project/trash/-/merge_requests/9/diffs?commit...
The first one will be reverted because that issues takes a different direction (forbid delete forms if you're not in the
activetrash context), and the second one should be a clean merge after that MR is committed.I've tried the approach with a
trash/path prefix instead of the query parameter, but it turned out we'd just bump into the same problems because we need to handle other routes as well, not only view|restore|purge. An example is the Devel route, as can be seen in #3398774: Trash breaks any local tabs on trashed content.Comment #36
amateescu commented@TwoD, thanks for lengthy explanation from #32! It finally made me realize what was bugging me about the latests changes:
First question: why do we need to change things in the (pre)save process, instead of preparing every new translation revision in
::delete(), and saving it as usual?Second, especially about this part: "But someone could have called
$entity->save()with either the default translation or any other still existing translation active"Why do we care about someone calling
$entity->save()? Trash has only been interested in someone calling$entity->delete()until now.Also, I think the parts where we handle the latest revision static cache are a good candidate for splitting out into their own issue as well, because I'm pretty sure it's a preexisting problem.
Comment #37
twodThe "simple" explanation for that is
ContentEntityBase::removeTranslation(). It removes a translation, marks it as removed, and then does nothing else. Hence, we have to wait until$entity->save()is called until we can detect that, revert the translation, tag it as 'deleted' (possibly creating more revisions if more than one translation was removed) and finally let it save.Comment #38
amateescu commented@TwoD, right, that makes sense.. even though it's very unfortunate :/
I'll check with @plach, but if I remember correctly, it's ok to change multiple translations in a single revision.
Comment #39
amateescu commentedI committed this small change to remove some noise from the MR.
Next, I'm going to open the 3.1.x branch because a big feature like this shouldn't be introduced in a patch release, and it would be nice to go through a few alpha/beta/rc releases.
This also provides a nice advantage since we can update the core requirement to >= 11.2.6+ (or even 11.3.0 - we'll see), so we can clean up quite a few things from
TrashStorageTrait.Quoting myself from #38:
I checked an old conversation and I was not remembering correctly, what I said above was wishful thinking, only possible once we have a conflict management solution in core.
Comment #40
amateescu commentedComment #41
amateescu commentedI've worked on the static entity cache issue a bit. Surprisingly, while commenting out the relevant changes from
TrashStorageTrait, none of the tests added in this MR were failing (even when running them on11.1.x). But I was pretty sure the problem is real, so I investigated and fixed it separately in #3561099: Trash doesn't handle the static entity cache properly when switching contexts.Next, I'll be looking into the pre-save code, which still irks me a bit because the long-term plan is to get as much stuff as possible out of
TrashStorageTrait, instead of putting more stuff in :)Comment #42
amateescu commentedHere's the initial attempt at moving the storage
doPreSave()override to hook implementations: https://git.drupalcode.org/project/trash/-/merge_requests/9/diffs?commit... At least\Drupal\Tests\trash\Functional\MultilingualNodeTestpassed locally :)While working on this, another question came up: why do we need to loop through and soft-delete all langcodes in
\Drupal\trash\TrashStorageTrait::delete()instead of mirroring what\Drupal\Core\Entity\ContentEntityDeleteForm::submitForm()does:Comment #43
twod@amateescu That comes from wanting one revision per deleted translation - as we may not restore/purge all of them at once - and it was easier to deal with them that way since otherwise you would not get the 'deleted' flag set on all translations, leading to inconsistencies.
We basically are undoing what that method does, except we can't rely on it being that form which deleted the translation. Some other code may look like this without causing any errors.
(Things would have been so much easier if
addTranslation()andremoveTranslation()were "atomic" and saved the entity themselves.)I'm surprised tests passed after moving the code out of
TrashStorageTrait::doPreSave(). I was expecting$entity->original = $this->loadUnchanged($id);fromEntityStorageBase::doPreSave()to returnNULLin some cases, as the Trash context is still active when that runs, before the hooks are invoked, but maybe it will work in some cases. Or, I missed adding a test case which covered that problem. Will have to investigate and see if I can reproduce that... 😅Comment #44
borisson_There are currently merge conflicts in the merge request, can we try to resolve those @TwoD?
Comment #45
amateescu commentedUpdated the MR to latest 3.1.x.
There are a few things that I think should be discussed a bit more:
Comment #46
twodThanks for continuing this! I've not yet had time to look at breaking this apart more. I am however dealing with a D11 upgrade in which we need to port this. More about that below.
Without those revisions we would get very strange effects when trying to restore only the source language (leaving translations) or the source language + only some translations, or when purging just some translations while leaving the others in soft-deleted.
I'm not sure where those exceptions would be thrown. The access handler does most of the sanity checks now because it has the context needed to do so without further complicating the storage trait. The access handler also prevents the GUI from showing disallowed operations. If it does not perform those checks and we instead throw an exception elsewhere, those operations would look possible - but the user would instead be greeted by a WSOD. These checks also apply to things like JSON:API and the broken entity links it would otherwise return. The code does throw exceptions for the few cases where permission checks may be bypassed and it would try to perform an unrecoverable illogical operation.
For D11 I noticed the cache interception here no longer works. Thankfully it is because they've moved to a separate memory cache implementation, which can be more cleanly intercepted and abstracted. I'm testing a solution for that which I can post soon, but I'm not yet up to speed with this branch so have some merging to do.
Edit: I see this was dealt with in #3561099: Trash doesn't handle the static entity cache properly when switching contexts, I'll compare that to the solution I did, which took a bit of a different approach and created separate cache entries for each context [for the trash-enabled entity types], trying to avoid invalidating them every time the context changes (which can be very often).
Comment #47
amateescu commentedJust a quick note on caching, #3569155: Refactor TrashStorageTrait::setPersistentCache will probably help with that as well.
Comment #48
twodLOL, it's always a race keeping up with you. Thanks! :D
I saw your note about making the trait smaller. One nice thing about keeping most of the logic concentrated there was that it may be easier to override trash handling per entity type with a custom storage class using it and overriding just what needed to be changed, but I get the point about preferably not having to generate classes at all.
Btw, I just hit a problem where updating our nodes generated redirects and redirects are not trashable, but its hooks are still active so the
redirect_presaveimplementation crashed on trying to get adeletedfield which does not exist.The check
if (!$this->trashManager->isEntityTypeEnabled('redirect')) {return;}, doesn't work since redirects aren't enabled and the service was never configured. If we instead initialize the dependencies toNULLin the base class we can doif (!$this->trashManager?->isEntityTypeEnabled('redirect')) {return;}and it still works. I suspect something similar would happen if nodes weren't trashable andquery_search_node_search_alteris called but haven't verified. I haven't seen an issue for it yet, but maybe you're already aware?Comment #49
amateescu commentedI thought we fixed that in #3561877: RedirectTrashHandler should check if deleted field exists before accessing it to support Drupal ~11.3.0...
Comment #50
twodOh, I see it also removed the un-setting of the disabled handlers, yeah, that should fix it.
Comment #51
amateescu commentedEven if we set the
revision_translation_affectedflag to TRUE for all translations of the "deleted" revision? I feel like that should be our goal here, at least for handling$entity->delete(): ensure that the deleted revision is fully prepared for being restored in any combination possible (just the source language, another language + the source, all languages).Comment #52
twodHmm, I have not tested that scenario in a long time. I don't know how it would work with some of the other recent changes.
There are so many combinations to test I'm too tired to think about them all right now, especially witih content_moderation enabled and having drafts in various states. Maybe it works better now that we don't try to mark the draft revision itself as deleted and instead always create a new default revision for each change. That did simplify some things and made some of the flows less confusing.
(Draft revisions thus aren't touched at all and just end up as historical never published revisions.)
It could be worth a try.
Comment #53
codebymikey commentedTests are all passing, is there anything else left to get this over the line?
Also, whilst working on this, I think it might be worth creating a
\Drupal\trash\TrashStorageInterfaceinterface, so we can call\Drupal\trash\TrashStorageInterface::restoreFromTrash()without having to ignore it in phpstan, and it's clear which methods are available to the trash-enabled storages.Is it worth adding as part of this issue, or is it better to wait for this to be merged, then create it as a separate issue.
Comment #54
amateescu commentedI still want to explore the first bullet point from #45. I've been experimenting with that locally but it's not fully baked yet, so atm I'm just chipping away at the MR trying to reduce it as much as possible.
I've been considering
\Drupal\trash\TrashStorageInterface::restoreFromTrash()as well, but it kinda goes against my goal of slimming downTrashStorageTrait, so haven't developed a firm opinion on it yet. But definitely a followup :)Comment #56
banoodle commentedI validated this on a D11.3.8 multilingual site without content moderation enabled.
It works well, but one nitpicky suggestion: in the scenario where I delete one of the translations in the set (not the source node), it would be nice if there were some sort of indicator on the translation set form. Perhaps "Published" could be replaced with "In trash"?
Comment #57
amateescu commented@banoodle, I just checked the translation form, and we already replace the status of a trashed translation with
Deleted, and the default operation right next to it isRestore. Is the site from that screenshot using the latest version of this MR?Comment #58
banoodle commentedNo, @amateescu, that screenshot was based on an earlier version of the MR. Thanks!
Comment #59
amateescu commentedI've been working on this in the past few days, and still have more work to do. The MR branch might be a bit "unstable" for a bit.
Comment #60
amateescu commentedI think this is ready now :)
Comment #62
amateescu commentedThe MR wasn't quite ready, it still needed to handle all the stuff that has been added to trash in the past.. 3 years, like the trash-specific hooks and various entity type integrations. But all of that is done now, so happily merging into 3.1.x!
Thanks @twod for all the work and patience you had with my questions, and everyone else who helped along the way!