Problem/Motivation

When editing a taxonomy term which has more than one parent, where one of the parents is root, root will be lost on save.

Steps to reproduce:

  1. Create a term (term1).
  2. Create another term (term2) which has the parents set to root and term1.
  3. Edit term2, save without editing the parents.
  4. Term2 will now only have one parent, term1. It would've lost root as a parent.

This is because when loading a terms parents it loads an array of terms, root is not a term so this doesn't get loaded.

Proposed resolution

Don't assume a parent is always a term entity.

Remaining tasks

- Reroll the latest patch based on 9.4.x.
- Manual testing after patch can be applied.
- Patch needss review

User interface changes

API changes

Data model changes

Comments

timmillwood created an issue. See original summary.

tetranz’s picture

Issue summary: View changes
tetranz’s picture

Issue summary: View changes

Confirmed. I'm going to look into it further.

I noticed what I think is another bug. When you give parents of root plus a real term, the drag handles disappear from the term list. That's correct and as expected but they don't return again if the term is then intentionally saved with another term as its only parent. The only way to make the drag handles return seems to be to save with root being the only parent.

I edited the description. Angle brackets around the word root was making it disappear as an unknown html tag. :)

tetranz’s picture

Status: Active » Needs review
StatusFileSize
new3.61 KB

Lets try this.

I added the TermStorage::hasRootAsParent method rather than change TermStorage::loadParents to include root because there is code that calls loadParents which expects to get only entities back.

timmillwood’s picture

Issue tags: +Needs tests

This needs tests, but so far looking awesome @tetranz! Thanks!

tetranz’s picture

Thanks @timmillwood. I'll add a test in the next day or so.

tetranz’s picture

StatusFileSize
new7.81 KB
new4.2 KB

Status: Needs review » Needs work

The last submitted patch, 7: terms-lose-root-2898903-7.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tetranz’s picture

Status: Needs work » Needs review
StatusFileSize
new7.84 KB
new1.38 KB

I guess we need to sort.

tetranz’s picture

Issue tags: -Needs tests
tetranz’s picture

I've created a related issue https://www.drupal.org/node/2900139. It's tempting to fix them both in one patch but I guess it's best to keep them separate. It will be easy to fix that one after this one is committed.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

Looking good, I think this is a pretty nice, simple fix.

catch’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/modules/taxonomy/src/TermStorage.php
diff --git a/core/modules/taxonomy/src/TermStorageInterface.php b/core/modules/taxonomy/src/TermStorageInterface.php
index 05d8d7f4c7..a4f386eec0 100644

index 05d8d7f4c7..a4f386eec0 100644
--- a/core/modules/taxonomy/src/TermStorageInterface.php

--- a/core/modules/taxonomy/src/TermStorageInterface.php
+++ b/core/modules/taxonomy/src/TermStorageInterface.php

+++ b/core/modules/taxonomy/src/TermStorageInterface.php
+++ b/core/modules/taxonomy/src/TermStorageInterface.php
@@ -118,4 +118,18 @@ public function resetWeights($vid);

TermStorage isn't a base class, so I'm not 100% sure if we can add a method to TermStorageInterface.

Has anyone checked whether this bug exists in 7.x? A quick review of the code suggests that it does. I can see this surviving for years given multiple parents usage is rare, and multiple parents with root as one of the parents likely rarer.

timmillwood’s picture

Status: Needs review » Needs work
Issue tags: +Workflow Initiative

Instead of adding a new interface for hasRootAsParent() we could add it as a protected method on TermForm?

I am assuming this is an issue in Drupal 7 too, but agree it's a very rare edge case.

Switching to "Needs work" to find a solution for not adding to TermStorageInterface.

tetranz’s picture

I don't think we'd want to add hasRootAsParent() to TermForm. That would mean injecting database into TermForm which doesn't feel good but I guess that would work.

I don't really understand what the problem is adding an additional method to TermStorageInterface. I guess I'm missing something because it seems that not being a base class, i.e., being taxonomy specific, is a good thing. I can understand that adding taxonomy specific code to its parent, ContentEntityStorageInterface would clearly be wrong.

timmillwood’s picture

@tetranz - The issue with adding a new method to the interface is that if any contrib (or custom) modules are implementing the interface they will need to also add the new method. If there was a base class then we could add the new method there and it would be assumed that any contrib/custom modules would be extending the base class and therefore get the new method automatically. We are only using the new asRootAsParent() method in TermForm, so although it's not ideal to inject the database there, it saves us from having to resolve and backwards compatibility issues.

tetranz’s picture

That makes sense. I see what you mean.

I'm happy to do another patch with that method in TermForm if you think that's the right thing to do I wonder if that causes a bigger issue. It would totally tie TermForm to SQL. I would have thought something like TermForm should be storage agnostic. That might be more a theory than reality but it seems to be way too far down the inheritance chain to be aware of storage implementation.

timmillwood’s picture

Yeh, that's a good point about making TermForm SQL dependent. My next suggestion then would be to add a new interface to TermStorage, then make TermForm handle the case of when the hasRootAsParent() method doesn't exist.

I'm sure @catch has a better idea though, he always does.

Ping @catch!

catch’s picture

+1 to #18. With no base class I don't see a way around the additional interface - we can have it extend TermStorageInterface and deprecate TermStorageInterface for 9.x

tetranz’s picture

ok sounds good.

So I should make a new interface extending TermStorageInterface. Any thoughts on what I should call the new interface?

catch’s picture

Could we do something like Drupal\taxonomy\Entity\TermStorageInterface? Usually we don't put the handlers inside the entity namespace but I don't really see why not? Or Drupal\taxonomy\Term\TermStorageInterface if not?

tetranz’s picture

Status: Needs work » Needs review
StatusFileSize
new8.64 KB
new2.91 KB

See how this goes. I went for Drupal\taxonomy\Entity\TermStorageInterface

I wasn't sure what, if any, comment I should put at the top of either the old or new interface.

I removed an unnecessary query for $parent.

michaellenahan’s picture

Issue tags: +Novice
michaellenahan’s picture

Issue tags: +Vienna2017
kork’s picture

working on it as part of the DrupalCon Vienna sprints

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Issue tags: +Bug Smash Initiative

I tested on Drupal 9.4.x and confirmed this is still a problem. I am postponing the related issue on this one.

mradcliffe’s picture

I added some remaining tasks and tags to clarify Novice tag. The issue summary also could be updated with @catch's proposed resolution in the proposed resolution section as well as filling out other sections based on the latest patch.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

immaculatexavier’s picture

Version: 9.5.x-dev » 9.4.x-dev
StatusFileSize
new7.95 KB
new3.1 KB

Rerolled patch for #22 against 9.4

ranjith_kumar_k_u’s picture

StatusFileSize
new0 bytes
new6.98 KB
ranjith_kumar_k_u’s picture

StatusFileSize
new7.81 KB
new1.93 KB
ranjith_kumar_k_u’s picture

immaculatexavier’s picture

StatusFileSize
new7 KB
new1.24 KB

Rerolled patch against #37 for 9.4.x. Fixed custom commands

rajandro’s picture

Couple of change suggestions:

  1.   public function hasRootAsParent($tid) {
        if (!isset($this->hasRootAsParent[$tid])) {
          $query = $this->database->select('taxonomy_term_hierarchy', 'h');
          $query
            ->condition('h.tid', $tid)
            ->condition('h.parent', 0);
    
          $count = $query->countQuery()->execute()->fetchCol()[0];
          $this->hasRootAsParent[$tid] = $count == 0 ? FALSE : TRUE;
        }
        return $this->hasRootAsParent[$tid];
      }
    

    As per 8.6.0 release note (https://www.drupal.org/node/2936675) taxonomy_term_hierarchy is deprecated, so we can change it to something like this...

      public function hasRootAsParent($tid) {
        if (!isset($this->hasRootAsParent[$tid])) {
          $query = $this->database->select('taxonomy_term__parent', 'ttp');
          $query
            ->condition('ttp.entity_id', $tid)
            ->condition('ttp.parent_target_id', 0);
          $count = $query->countQuery()->execute()->fetchCol()[0];
          $this->hasRootAsParent[$tid] = $count == 0 ? FALSE : TRUE;
        }
        return $this->hasRootAsParent[$tid];
      }
    
  2. Phpcs error need to be fix for core/modules/taxonomy/tests/src/Functional/TermTest.php on line 570
smustgrave’s picture

Status: Needs review » Needs work

Looks like patch #41 add some issues and #42 some suggestions that should be looked at.

Ratan Priya’s picture

Assigned: Unassigned » Ratan Priya
Ratan Priya’s picture

Assigned: Ratan Priya » Unassigned
Status: Needs work » Needs review
StatusFileSize
new0 bytes
new21.52 KB

@smustgrave,
I made the changes as required at comments #42
Needs review.

Ratan Priya’s picture

StatusFileSize
new6.88 KB
new21.51 KB

@smustgrave,
Sorry for the previous patch.
I made the changes as required in point 1 at comments #42
Needs review.

smustgrave’s picture

Status: Needs review » Needs work

Still looks like the patch is having issues

smustgrave’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll, -Needs issue summary update
StatusFileSize
new956 bytes
new6.62 KB

Fixed a syntax issue

Status: Needs review » Needs work

The last submitted patch, 48: 2898903-48.patch, failed testing. View results

smustgrave’s picture

Status: Needs work » Needs review
StatusFileSize
new2.73 KB
new6.65 KB

Fixing test case

abhijith s’s picture

StatusFileSize
new482.42 KB
new1019.41 KB

Applied patch #49 and it fixes the issue.Attaching screen recordings for reference.

smustgrave’s picture

If you feel the issue is fixed please move to RTBC

vinaymahale’s picture

Status: Needs review » Reviewed & tested by the community

I have tested the patch from comment #50 on 9.4.6-dev and 9.5.0-dev. Verified and the patch is applied successfully fixing the issue on both 9.4.6-dev and 9.5.0-dev.

Moving to RTBC
Looks good to Merge!

vinaymahale’s picture

Looks good to merge!

alexpott’s picture

Version: 9.4.x-dev » 9.5.x-dev
Status: Reviewed & tested by the community » Needs review
StatusFileSize
new6.18 KB
new4.4 KB

I don't think we need to add a new method using SQL to term storage for this. I think we can get the parents from the entity and be done. See attached patch.

alexpott’s picture

StatusFileSize
new4.64 KB
new4.69 KB
new5.99 KB

Whoops my test didn't fail against HEAD because of caching stuff and the test not testing what it says it was testing.

The last submitted patch, 56: 2898903-56.test-only.patch, failed testing. View results

prasanth_kp’s picture

StatusFileSize
new3.75 MB
new3.11 MB

#55 Patch Applied on 9.5.x-dev and it fixes the issue.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing

Also manually tested this and appears working.

Also from the videos in #58 think remvoing the manual testing is fine.

  • catch committed 675dbcd on 10.0.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...
  • catch committed 4b67540 on 10.1.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...

  • catch committed f035d25 on 9.5.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...

  • catch committed 7e655f9 on 9.4.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...
catch’s picture

Version: 10.1.x-dev » 9.4.x-dev
Status: Reviewed & tested by the community » Fixed

phpstan was complaining, fixed this on commit:

diff --git a/core/modules/taxonomy/tests/src/Functional/TermTest.php b/core/modules/taxonomy/tests/src/Functional/TermTest.php
index f87fd78d03..819578b227 100644
--- a/core/modules/taxonomy/tests/src/Functional/TermTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/TermTest.php
@@ -613,6 +613,7 @@ private function reloadTermByName(string $name): TermInterface {
    *   A sorted array of tids and 0 if the root is a parent.
    */
   private function getParentTids($term) {
+    $parent_tids = [];
     foreach ($term->get('parent') as $item) {
       $parent_tids[] = (int) $item->target_id;
     }

Committed to 10.1.x and cherry-picked back through to 9.4.x, thanks!

alexpott’s picture

Status: Fixed » Needs work

Reverting as this broke HEAD :(

  • alexpott committed dd0d85b on 10.1.x
    Revert "Issue #2898903 by tetranz, alexpott, smustgrave,...

  • alexpott committed 37e10af on 10.0.x
    Revert "Issue #2898903 by tetranz, alexpott, smustgrave,...

  • alexpott committed 003cfc7 on 9.5.x
    Revert "Issue #2898903 by tetranz, alexpott, smustgrave,...

  • alexpott committed feb5a55 on 9.4.x
    Revert "Issue #2898903 by tetranz, alexpott, smustgrave,...
alexpott’s picture

The patch in #56 is no longer good for 10.x - works on 9.5 and 9.4 though...

alexpott’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new1.69 KB
new6.49 KB

Fixed the test. This was caused by #2826592: No redirection to term view display page from term edit page. The new patch will work on Drupal 9 and 10 and as the changes are test only setting back to RTBC.

  • catch committed 3749e0f on 10.0.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...
  • catch committed 4592134 on 10.1.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...

  • catch committed 9d668e3 on 9.4.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...
  • catch committed 8aab026 on 9.5.x
    Issue #2898903 by tetranz, alexpott, smustgrave, immaculatexavier,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Whoops, thanks for fixing. Committed/pushed to 10.1.x and cherry-picked back to 9.4.x again.

Status: Fixed » Closed (fixed)

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

claudiu.cristea’s picture

This change broke the rdf_taxonomy sub-module of https://www.drupal.org/project/rdf_entity:

--- a/core/modules/taxonomy/src/TermForm.php
+++ b/core/modules/taxonomy/src/TermForm.php
@@ -23,7 +23,12 @@ public function form(array $form, FormStateInterface $form_state) {
     $taxonomy_storage = $this->entityTypeManager->getStorage('taxonomy_term');
     $vocabulary = $vocab_storage->load($term->bundle());
 
-    $parent = array_keys($taxonomy_storage->loadParents($term->id()));
+    $parent = [];
+    // Get the parent directly from the term as
+    // \Drupal\taxonomy\TermStorageInterface::loadParents() excludes the root.
+    foreach ($term->get('parent') as $item) {
+      $parent[] = (int) $item->target_id;
+    }
     $form_state->set(['taxonomy', 'parent'], $parent);
     $form_state->set(['taxonomy', 'vocabulary'], $vocabulary);

Specifically, we're swapping the storage of the taxonomy_term entity in order to store the terms in a triplestore backend. Along with this, the TIDs are strings (URIs), not integers. Casting here as integers is prohibiting us to use TermForm as it is.

At least, is it possible to move the parents computing in a protected method, so it would make easier to extend the class w/o copy/paste the whole TermForm::form() method?

claudiu.cristea’s picture