Vocabulary plugin stops outputting items in complex hierarchical vocab

Created on 11 August 2025, about 1 month ago

Problem/Motivation

The Sitemap module has a "Vocabulary" plugin type to display the terms in a Taxonomy vocab. If the terms in the vocab have a hierarchy, it will display the terms as a set of nested unordered lists.

However, if the hierarchy is sufficiently complex, it will eventually stop outputting terms.

After outputting a term, \Drupal\sitemap\Plugin\Sitemap\Vocabulary::buildList() checks if ($maxDepth >= $currentDepth), and if TRUE, it renders the descendant terms. However, $currentDepth is only ever incremented: it isn't decremented when going back up a level, so if the hierarchy is complex enough, $currentDepth will eventually become larger than $maxDepth, i.e.: and it will stop outputting terms at that point.

Note that the code to output top-level terms (i.e.: terms with a NULL parent) is different, so those will always be output. As a result, $currentDepth will reset when a new top-level term is encountered. This may be why we haven't noticed the problem since it was introduced.

Steps to reproduce

  1. Install DDEV
  2. Clone sitemap-8.x-2.x-dev:
    git clone https://git.drupalcode.org/project/sitemap.git ; cd sitemap
  3. Set up the project with the ddev/ddev-drupal-contrib add-on:
    ddev config --project-type=drupal --docroot=web --php-version=8.3 --corepack-enable ; ddev add-on get ddev/ddev-drupal-contrib ; ddev start ; ddev poser ; ddev symlink-project ; ddev config --update ; ddev restart
  4. Install Drupal with the Demo: Umami Food Magazine (Experimental) install profile:
    ddev drush -y si demo_umami
  5. Navigate to the web UI and log in:
    ddev launch $(ddev drush uli)
  6. Enable the sitemap module:
    ddev drush -y en sitemap
  7. Display the Tags vocabulary on the sitemap: go to /en/admin/config/search/sitemap, check Vocabulary: Tags, leave the configuration at their default values, and click the Save configuration button.
  8. Go to /en/admin/config/development/performance and click Clear all caches.
  9. View the sitemap by going to /en/sitemap, and compare it with the list of terms in the vocabulary at /en/admin/structure/taxonomy/manage/tags/overview
    • All 28 terms in the Tags vocabulary should be visible on both pages.
      (if you want, run document.querySelectorAll('.region-content a[href^="/en/tags/"]').length in the browser console to verify).
  10. Go to View the sitemap by going to /en/sitemap, and compare it with the list of terms in the vocabulary at /en/admin/structure/taxonomy/manage/tags/overview
    • All 28 terms in the Tags vocabulary should be visible on both pages.
  11. Run the following script with drush -y php:
    $tags = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['vid' => 'tags']);
    $firstTag = \reset($tags);
    $tagsSize = \count($tags);
    $previousTerm = NULL;
    for ($i = 1; $i <= $tagsSize; $i++) {
      $term = \array_shift($tags);
      if ($i === 1) {
        $previousTerm = $term;
        continue;
      }
      elseif (\in_array($i, \range(2, 16, 2), TRUE)) {
        $term->parent = [$firstTag];
        $previousTerm = $term;
      }
      else {
        $term->parent = [$previousTerm];
      }
      $term->save();
    }
        

    Or, manually rearrange the items as follows:

    • Alcohol free
      • Baked
        • Baking
      • Breakfast
        • Cake
      • Carrots
        • Chocolate
      • Cocktail party
        • Dairy-free
      • Dessert
        • Dinner party
      • Drinks
        • Egg
      • Grow your own
        • Healthy
      • Herbs
        • Learn to cook
        • Mushrooms
        • Oats
        • Party
        • Seasonal
        • Shopping
        • Soup
        • Supermarkets
        • Vegan
        • Vegetarian
  12. Go to /en/admin/config/development/performance and click Clear all caches.
  13. View the sitemap by going to /en/sitemap, and compare it with the list of terms in the vocabulary at /en/admin/structure/taxonomy/manage/tags/overview
    • Expected behavior:
      • 28 terms in the Tags vocabulary should be visible on both pages in the above arrangement.
    • Actual behavior:
      • 16 terms in the Tags vocabulary are visible on the Sitemap. The terms under "Herbs" (i.e.: "Learn to cook", "Mushrooms", "Oats", "Party", "Pasta", "Pastry", "Seasonal", "Shopping", "Soup", "Supermarkets", "Vegan", "Vegetarian") are no longer visible on the sitemap.
      • However, all 28 terms in the Tags vocabulary are visible on both pages in the above arrangement.

Note that if I modify the code with the following patch...

diff --git a/src/Plugin/Sitemap/Vocabulary.php b/src/Plugin/Sitemap/Vocabulary.php
index 8f27238..94c3db3 100644
--- a/src/Plugin/Sitemap/Vocabulary.php
+++ b/src/Plugin/Sitemap/Vocabulary.php
@@ -328,13 +328,13 @@ public function view() {
    * @return array|void
    *   Returns an array if the term display is TRUE.
    */
-  protected function buildSitemapTerm($term) {
+  protected function buildSitemapTerm($term, string $currentDepth = '') {
     $this->checkTermThreshold($term);

     if ($term->display) {
       return [
         '#theme' => 'sitemap_taxonomy_term',
-        '#name' => $term->name,
+        '#name' => \sprintf('%s [%s]', $term->name, $currentDepth),
         '#url' => $this->buildTermLink($term) ?: '',
         '#show_link' => $this->determineLinkVisibility($term),
         '#show_count' => $this->determineCountVisibility($term),
@@ -449,7 +449,7 @@ protected function buildList(array &$list, $object, $vid, &$currentDepth, $maxDe
     $children = $termStorage->loadTree($vid, $object->tid, 1);
     if (!$children) {
       $object->hasChildren = FALSE;
-      if ($element = $this->buildSitemapTerm($object)) {
+      if ($element = $this->buildSitemapTerm($object, $currentDepth)) {
         $list[$object->tid][] = $element;
       }
       return;
@@ -459,7 +459,7 @@ protected function buildList(array &$list, $object, $vid, &$currentDepth, $maxDe
       // @todo That's not entirely accurate...
       $object->display = TRUE;
       $object->hasChildren = TRUE;
-      $list[$object->tid][] = $this->buildSitemapTerm($object);
+      $list[$object->tid][] = $this->buildSitemapTerm($object, $currentDepth);
       $list[$object->tid]['children'] = [];
       $object_children = &$list[$object->tid]['children'];
     }

... , then I can see the current value of the $currentDepth variable, which helps explain the behavior.

Proposed resolution

Unknown at this time.

One possible way to fix this would be to find a way to $currentDepth-- when we go back up a level. However, I worry that the existing code in \Drupal\sitemap\Plugin\Sitemap\Vocabulary::view() may have yet-uncovered bugs.

I'd prefer to find a way to simplify the existing code (if possible) in \Drupal\sitemap\Plugin\Sitemap\Vocabulary::view()... in particular, if possible, it would be good to reuse/copy the logic for rendering the tree from the Taxonomy module, i.e.: the code used to display the "overview" list of terms in a vocab at /en/admin/structure/taxonomy/manage/tags/overview (i.e.: the code in \Drupal\taxonomy\Form\OverviewTerms::buildForm()).

Remaining tasks

  1. Write a test to reproduce the problem
  2. Write a patch
  3. Review and feedback
  4. RTBC and feedback
  5. Commit
  6. Release

User interface changes

To be determined.

API changes

To be determined.

Data model changes

To be determined; likely none.

🐛 Bug report
Status

Active

Version

2.0

Component

Code

Created by

🇨🇦Canada mparker17 UTC-4

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Production build 0.71.5 2024