Closed (fixed)
Project:
Trash
Version:
3.1.x-dev
Component:
Code
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Reporter:
Created:
5 Dec 2024 at 12:54 UTC
Updated:
12 Apr 2026 at 21:00 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
amateescu commentedThe problem with taxonomy terms is their hierarchy, i.e. when "trashing" a term, what do you do with its children?
One option could be to execute
\Drupal\taxonomy\Entity\Term::postDelete()directly on the trashed term, with the downside that restoring the deleted term from trash won't restore the hierarchy for its previous child items.A possible fix for that could be to store additional "metadata" somewhere, which would be used in this case to store the child item IDs, and on restore you could ask the user if they want to re-parent those terms to the newly restored parent.
It's not an impossible problem, it just needs a bit of time and thought :)
Comment #3
mambrus commentedThank you, @amateescu, I can see that the hierarchy thing is probably a bit complicated :)
I think the question here also is what should happen with children when the parent is trashed. For me, the logical thing would be to also trash its child nodes (recursively, if they have their own child nodes), so they don't get orphaned, nor added to an arbitrary new parent. If they, however do have another parent, then, just as in the example here, I'd leave them be and only store metadata of the parent that's being trashed. Otherwise, we'd probably need to store metadata of all the trashed children (and possibly their children) as well.
If trashing works like this, then we wouldn't even need to ask whether to re-parent the restored children once their parent gets restored, since they'd be restored alongside of it. Of, if they had another parent, then they'd get re-attached to the restored parent as well.
I checked how Drupal's current delete on parent terms work and it seems that children terms are indeed auto-removed with the parent. So that functionality logically doesn't change from the default Drupal behavior.
What do you think?
Comment #5
amateescu commentedThere's a tricky edge case here brought up by @Fabianx: what if a child term was trashed before its parent. In that case it shouldn't be automatically restored alongside the other children of that parent term. I've been thinking about this problem for quite a while, and I think it could be accomplished by ensuring that we only restore child terms with the deleted timestamp >= than the one of the parent term.
Comment #6
khaled.zaidan commentedJoining this thread as we're also interested in trash for taxonomy terms.
I like the idea of the storing this additional metadata. Maybe part of the metadata we have a list of any additional entities trashed as a result of trashing this current entity. Let's call it `trash_chain` or something. We only add direct children in that chain. Any grandchildren would only appear in their direct parents, so handling this trash chain is a recursive bit.
This way, when we trash an entity (taxonomy term or anything else), any pre-trashed children simply wouldn't be added to the trash chain. And it would also work for any other entity type with hierarchy (e.g. menu items) or any dependency at all. At one point, this might become more flexible to allow custom dependencies.
Comment #7
khaled.zaidan commentedWe might be interested in contributing time and effort to get this metadata bit added.
I'm thinking an additional serialised column `deleted_metadata`. It gets added/removed together with the `deleted` column.
And in there we store this `trash_chain` list of entities (entity type + id for each).
The trash chain could be hard-coded for now just to get these few entity types sorted and to see how well the approach works. We can later transition to using plugins (ideally) or just hooks.
Maybe we can also allow enabling these entity types as "Experimental" so there's a caveat that they might not work perfectly, yet.
What do you think @amateescu?
Comment #10
ajitsI started adding the support for trash to taxonomy. I have created an MR that enables trashing the terms. However, there is a challenge with the term hierarchy, as mentioned in the previous comments.
Any term being trashed resets its parent to root by design. See
Term::preSave.I had a chat with @amateescu, and we concluded that any other temporary storage like key/value will not be compatible with the core workspaces module. This data needs to stay with the term itself. Something like @khaled.zaidan mentioned above. I like the idea of saving the immediate child terms for the current term. Maybe in an implementation of hook_pre_trash_delete for taxonomy terms?
Comment #11
khaled.zaidan commentedThat's possible.
My think is it would be nice if it's not specific to one entity type. I think this can apply to any entity type that there would be dependencies to an entity being trashed.
So rather than implementing hook_pre_trash_delete for a specific entity type, I was thinking that it's a separate thing (hook or plugin) that allows these dependencies to built, and then we trash those. Maybe that can be done in a hook_pre_trash_delete(), but I think it's safer to do it separately before that hook has been invoked, to ensure our trash chain is always handled first (this might be important if the a child entity being deleted needs information from its parent).
And maybe `trash_chain` isn't the best name xD I'm thinking maybe `trash_dependent_entities`?
Here's some suggested pseudo-code to the `TrashStorageTrait`:
And we would something similar for restoring (just the other opposite order: restore parent first and then the dependencies).
Comment #13
amateescu commentedI liked a lot the idea of only storing the direct children of a term when it's trashed, but it still has the downside that we need an additional field to store them in, and I'd really like to avoid it if possible.
Thankfully, @eclipsegc had another idea: we could use the deleted timestamp that we already have, and restore child terms with the same timestamp as the parent. Since cascading deletes happen in the same request, all the deleted terms will have the same timestamp, so I think this approach is very feasible.
As for the
trash_dependent_entitiessuggestion above, I'm not sure we need to generalize the idea of "dependent deletes" just yet, because it would only apply to taxonomy terms at the moment. Menu links for example, which also have a hierarchy, are not auto-deleting their children.After looking at the MR posted by Ajit, I think there's something else we need to generalize first: we need to introduce "trash handlers", which would be responsible for all the entity type-specific customizations and hook implementations needed. Opened an issue for that: #3506131: Add the concept of trash handlers, and postponing this one for now.
Comment #14
mambrus commentedHeya guys, any news with regards to this functionality? I see that the underlying task is done and ready - which is amazing! It's be very cool if we could get the taxonomy trash working as well :)
Comment #15
ajitsThe trash handlers issue is now fixed. I plan to give this another shot.
Comment #18
ajitsI picked this issue up as part of Tag1's sponsored work for open source development. I used Claude code with Cline in VS Code to work through this.
We cannot rely on the
deletedvalue for the term in restore as mentioned in #13 above. This is because of the way core handles term deletions.Say there is a hierarchy of terms
A -> Band the termAis deleted:Drupal\taxonomy\Entity\Term::preSaveis called, and then the term is deleted. AndDrupal\taxonomy\Entity\Term::postDeleteis called to delete the orphaned terms.A'spostDelete, the termB->delete()is called.Drupal\taxonomy\Entity\Term::preSavebefore deleting. And the function resets the parent to root. The term gets deleted after that. This removes any trace of the previous hierarchy.We might have to rethink a way to store the hierarchy while trashing a term. Maybe we go with storing it with the term itself, as mentioned in previous comments.
I have added test for possible scenarios. Most of them should pass, except restoring multiple terms in the hierarchy.
Setting this to "Need review" for now to get quick feedback.
Comment #19
rajab natshahThank you, Very much needed feature.
Comment #20
rajab natshahTested, Thank you :)
The diff from the MR is working well
Comment #21
amateescu commentedThe MR is still a work in progress, see #18.
Comment #22
rajab natshahNoted; you are right Andrei
Having more testing rounds, with number of scenarios
I would love to follow with Drupal Core Behavior when deleting terms
Given that we have a News Categories vocabulary
And having
"Tech root level"as a root termAnd
"Tech 1 level 1","Tech 2 level 1","Tech 3 level 1"under the"Tech root level"termAnd
"Tech 1 level 2","Tech 1 level 2","Tech 1 level 2"under the"Tech 1 level 1"termAnd
"Tech 2 level 2","Tech 2 level 2","Tech 2 level 2"under the"Tech 2 level 1"termAnd
"Tech 3 level 2","Tech 3 level 2","Tech 3 level 2"under the"Tech 3 level 1"termAnd
"Tech 1 level 3","Tech 1 level 3","Tech 1 level 3"under each"Tech 1 level 2"termAnd
"Tech 2 level 3","Tech 2 level 3","Tech 2 level 3"under each"Tech 2 level 2"termAnd
"Tech 3 level 3","Tech 3 level 3","Tech 3 level 3"under each"Tech 3 level 2"termAnd having
"Sport root level"as a root termAnd
"Sport 1 level 1","Sport 2 level 1","Sport 3 level 1"under the"Sport root level"termAnd
"Sport 1 level 2","Sport 1 level 2","Sport 1 level 2"under the"Sport 1 level 1"termAnd
"Sport 2 level 2","Sport 2 level 2","Sport 2 level 2"under the"Sport 2 level 1"termAnd
"Sport 3 level 2","Sport 3 level 2","Sport 3 level 2"under the"Sport 3 level 1"termAnd
"Sport 1 level 3","Sport 1 level 3","Sport 1 level 3"under each"Sport 1 level 2"termAnd
"Sport 2 level 3","Sport 2 level 3","Sport 2 level 3"under each"Sport 2 level 2"termAnd
"Sport 3 level 3","Sport 3 level 3","Sport 3 level 3"under each"Sport 3 level 2"termWhat are the accountable scenarios?
1- Deletion / Restore scenarios for root terms
2- Deletion / Restore scenarios for 1st-level child terms
3- Deletion / Restore scenarios for 2nd-level child terms
4- Deletion / Restore scenarios for leaf terms
5- Cross-vocabulary isolation
6- Partial deletion within multiple branches
7- Mixed operations
Comment #23
rajab natshahThe tree could be serialized and stored as an extra nested data relation, which restore could read it and apply a restore for it after the root restore
Comment #24
mikell commentedPatch based on latest MR version
Comment #25
majid.ali commentedRe rolled https://www.drupal.org/project/trash/issues/3491947#comment-16258520 for 3.0.23
Comment #26
majid.ali commentedMade a mistake in #25.
Comment #27
amateescu commentedLooked into this today and figured out the answer to #18, instead of calling
Term::postDelete()we loop through the hierarchy and delete the orphans ourselves.Comment #30
amateescu commentedFound and fixed some bugs, I think the test coverage is sufficient at this point. So.. this is finally ready!