Example #1
0
  /**
   * {@inheritdoc}
   */
  public function validateArgument($argument) {
    if ($this->options['transform']) {
      $argument = str_replace('-', '_', $argument);
    }
    $properties = array('machine_name' => $argument);
    if ($bundles = array_filter($this->options['bundles'])) {
      $properties['vid'] = $bundles;
    }
    $terms = $this->termStorage->loadByProperties($properties);

    if (!$terms) {
      // Returned empty array no terms with the name.
      return FALSE;
    }

    // Not knowing which term will be used if more than one is returned check
    // each one.
    /** @var Term $term */
    foreach ($terms as $term) {
      if (!$this->validateEntity($term)) {
        return FALSE;
      }
    }

    $this->argument->realField = 'tid';
    $this->argument->argument = $term->id();

    // Property created dynamically.
    if (!$this->argument->validated_title = $term->getName()) {
      $this->argument->validated_title = $this->t('No name');
    }

    return TRUE;
  }
 /**
  * {@inheritdoc}
  */
 public function build(RouteMatchInterface $route_match)
 {
     $breadcrumb = new Breadcrumb();
     $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
     $term = $route_match->getParameter('taxonomy_term');
     // Breadcrumb needs to have terms cacheable metadata as a cacheable
     // dependency even though it is not shown in the breadcrumb because e.g. its
     // parent might have changed.
     $breadcrumb->addCacheableDependency($term);
     // @todo This overrides any other possible breadcrumb and is a pure
     //   hard-coded presumption. Make this behavior configurable per
     //   vocabulary or term.
     $parents = $this->termStorage->loadAllParents($term->id());
     // Remove current term being accessed.
     array_shift($parents);
     foreach (array_reverse($parents) as $term) {
         $term = $this->entityManager->getTranslationFromContext($term);
         $breadcrumb->addCacheableDependency($term);
         $breadcrumb->addLink(Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id())));
     }
     // This breadcrumb builder is based on a route parameter, and hence it
     // depends on the 'route' cache context.
     $breadcrumb->addCacheContexts(['route']);
     return $breadcrumb;
 }
Example #3
0
 /**
  * {@inheritdoc}
  */
 public function save(array $form, array &$form_state)
 {
     $this->termStorage->resetWeights($this->entity->id());
     drupal_set_message($this->t('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label())));
     watchdog('taxonomy', 'Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label()), WATCHDOG_NOTICE);
     $form_state['redirect_route'] = $this->entity->urlInfo('edit-form');
 }
 /**
  * {@inheritdoc}
  */
 public function save(array $form, FormStateInterface $form_state)
 {
     $this->termStorage->resetWeights($this->entity->id());
     drupal_set_message($this->t('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label())));
     $this->logger('taxonomy')->notice('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label()));
     $form_state->setRedirectUrl($this->entity->urlInfo('edit-form'));
 }
Example #5
0
 /**
  * {@inheritdoc}
  */
 public function submitForm(array &$form, FormStateInterface $form_state)
 {
     parent::submitForm($form, $form_state);
     $this->termStorage->resetWeights($this->entity->id());
     drupal_set_message($this->t('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label())));
     $this->logger('taxonomy')->notice('Reset vocabulary %name to alphabetical order.', array('%name' => $this->entity->label()));
     $form_state->setRedirectUrl($this->getCancelUrl());
 }
 /**
  * {@inheritdoc}
  */
 public function build(RouteMatchInterface $route_match)
 {
     $breadcrumb = new Breadcrumb();
     $breadcrumb->addCacheContexts(['route']);
     // Add all parent forums to breadcrumbs.
     /** @var Node $node */
     $node = $route_match->getParameter('node');
     $breadcrumb->addCacheableDependency($node);
     // Add all parent forums to breadcrumbs.
     /** @var \Drupal\Taxonomy\TermInterface $term */
     $term = !empty($node->field_channel) ? $node->field_channel->entity : '';
     $links = [];
     if ($term) {
         $breadcrumb->addCacheableDependency($term);
         $channels = $this->termStorage->loadAllParents($term->id());
         foreach (array_reverse($channels) as $term) {
             $term = $this->entityManager->getTranslationFromContext($term);
             $breadcrumb->addCacheableDependency($term);
             $links[] = Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id()));
         }
     }
     if (!$links || '/' . $links[0]->getUrl()->getInternalPath() != $this->configFactory->get('system.site')->get('page.front')) {
         array_unshift($links, Link::createFromRoute($this->t('Home'), '<front>'));
     }
     return $breadcrumb->setLinks($links);
 }
Example #7
0
 /**
  * {@inheritdoc}
  */
 public function calculateDependencies()
 {
     $dependencies = parent::calculateDependencies();
     $vocabulary = $this->vocabularyStorage->load($this->options['vid']);
     $dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
     foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) {
         $dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
     }
     return $dependencies;
 }
Example #8
0
 /**
  * {@inheritdoc}
  */
 public function validateArgument($argument)
 {
     if ($this->options['transform']) {
         $argument = str_replace('-', ' ', $argument);
     }
     $terms = $this->termStorage->loadByProperties(array('name' => $argument));
     if (!$terms) {
         // Returned empty array no terms with the name.
         return FALSE;
     }
     // Not knowing which term will be used if more than one is returned check
     // each one.
     foreach ($terms as $term) {
         if (!$this->validateEntity($term)) {
             return FALSE;
         }
     }
     return TRUE;
 }
Example #9
0
 /**
  * {@inheritdoc}
  */
 public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL, $selected_terms = array())
 {
     if (empty($selected_terms)) {
         $form['info'] = array('#markup' => $this->t('Please select the terms you want to export.'));
         return $form;
     }
     // Cache form state so that we keep the parents in the modal dialog.
     $form_state->setCached(TRUE);
     $form['voc'] = array('#type' => 'value', '#value' => $taxonomy_vocabulary);
     $form['selected_terms']['#tree'] = TRUE;
     $items = array();
     foreach ($selected_terms as $t) {
         $term = $this->termStorage->load($t);
         $items[] = $term->getName();
         $form['selected_terms'][$t] = array('#type' => 'value', '#value' => $t);
     }
     $form['terms'] = array('#theme' => 'item_list', '#items' => $items, '#title' => $this->t('Selected terms for export:'));
     $form['download_csv'] = array('#type' => 'submit', '#value' => $this->t('Download CSV'));
     $form['export'] = array('#type' => 'submit', '#value' => $this->t('Export'));
     return $form;
 }
 public function getOptions(FieldDefinitionInterface $field, $component)
 {
     $fs = $field->getFieldStorageDefinition()->getSettings();
     $options = $fs[$component . '_options'];
     foreach ($options as $index => $opt) {
         if (preg_match('/^\\[vocabulary:([0-9a-z\\_]{1,})\\]/', trim($opt), $matches)) {
             unset($options[$index]);
             if ($this->termStorage && $this->vocabularyStorage) {
                 $vocabulary = $this->vocabularyStorage->load($matches[1]);
                 if ($vocabulary) {
                     $max_length = isset($fs['max_length'][$component]) ? $fs['max_length'][$component] : 255;
                     foreach ($this->termStorage->loadTree($vocabulary->id()) as $term) {
                         if (Unicode::strlen($term->name) <= $max_length) {
                             $options[] = $term->name;
                         }
                     }
                 }
             }
         }
     }
     // Options could come from multiple sources, filter duplicates.
     $options = array_unique($options);
     if (isset($fs['sort_options']) && !empty($fs['sort_options'][$component])) {
         natcasesort($options);
     }
     $default = FALSE;
     foreach ($options as $index => $opt) {
         if (strpos($opt, '--') === 0) {
             unset($options[$index]);
             $default = trim(Unicode::substr($opt, 2));
         }
     }
     $options = array_map('trim', $options);
     $options = array_combine($options, $options);
     if ($default !== FALSE) {
         $options = array('' => $default) + $options;
     }
     return $options;
 }
Example #11
0
 public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL, $selected_terms = array())
 {
     if (empty($selected_terms)) {
         $form['info'] = array('#markup' => $this->t('Please select the terms you want to move.'));
         return $form;
     }
     // Cache form state so that we keep the parents in the modal dialog.
     $form_state->setCached(TRUE);
     $form['voc'] = array('#type' => 'value', '#value' => $taxonomy_vocabulary);
     $form['selected_terms']['#tree'] = TRUE;
     $items = array();
     foreach ($selected_terms as $t) {
         $term = $this->termStorage->load($t);
         $items[] = $term->getName();
         $form['selected_terms'][$t] = array('#type' => 'value', '#value' => $t);
     }
     $form['terms'] = array('#theme' => 'item_list', '#items' => $items, '#title' => $this->t('Selected terms to move:'));
     // @todo Add autocomplete to select/add parent term.
     $form['keep_old_parents'] = array('#type' => 'checkbox', '#title' => $this->t('Keep old parents and add new ones (multi-parent). Otherwise old parents get replaced.'));
     $form['delete'] = array('#type' => 'submit', '#value' => $this->t('Move'));
     return $form;
 }
Example #12
0
 /**
  * Returns add container entity form.
  *
  * @return array
  *   Render array for the add form.
  */
 public function addContainer()
 {
     $vid = $this->config('forum.settings')->get('vocabulary');
     $taxonomy_term = $this->termStorage->create(array('vid' => $vid, 'forum_container' => 1));
     return $this->entityFormBuilder()->getForm($taxonomy_term, 'container');
 }
Example #13
0
 /**
  * Form submission handler.
  *
  * Rather than using a textfield or weight field, this form depends entirely
  * upon the order of form elements on the page to determine new weights.
  *
  * Because there might be hundreds or thousands of taxonomy terms that need to
  * be ordered, terms are weighted from 0 to the number of terms in the
  * vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
  * lowest to highest, but are not necessarily sequential. Numbers may be
  * skipped when a term has children so that reordering is minimal when a child
  * is added or removed from a term.
  *
  * @param array $form
  *   An associative array containing the structure of the form.
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form.
  */
 public function submitForm(array &$form, FormStateInterface $form_state)
 {
     // Sort term order based on weight.
     uasort($form_state->getValue('terms'), array('Drupal\\Component\\Utility\\SortArray', 'sortByWeightElement'));
     $vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
     // Update the current hierarchy type as we go.
     $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
     $changed_terms = array();
     $tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE);
     if (empty($tree)) {
         return;
     }
     // Build a list of all terms that need to be updated on previous pages.
     $weight = 0;
     $term = $tree[0];
     while ($term->id() != $form['#first_tid']) {
         if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
             $term->setWeight($weight);
             $changed_terms[$term->id()] = $term;
         }
         $weight++;
         $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
         $term = $tree[$weight];
     }
     // Renumber the current page weights and assign any new parents.
     $level_weights = array();
     foreach ($form_state->getValue('terms') as $tid => $values) {
         if (isset($form['terms'][$tid]['#term'])) {
             $term = $form['terms'][$tid]['#term'];
             // Give terms at the root level a weight in sequence with terms on previous pages.
             if ($values['term']['parent'] == 0 && $term->getWeight() != $weight) {
                 $term->setWeight($weight);
                 $changed_terms[$term->id()] = $term;
             } elseif ($values['term']['parent'] > 0) {
                 $level_weights[$values['term']['parent']] = isset($level_weights[$values['term']['parent']]) ? $level_weights[$values['term']['parent']] + 1 : 0;
                 if ($level_weights[$values['term']['parent']] != $term->getWeight()) {
                     $term->setWeight($level_weights[$values['term']['parent']]);
                     $changed_terms[$term->id()] = $term;
                 }
             }
             // Update any changed parents.
             if ($values['term']['parent'] != $term->parents[0]) {
                 $term->parent->target_id = $values['term']['parent'];
                 $changed_terms[$term->id()] = $term;
             }
             $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
             $weight++;
         }
     }
     // Build a list of all terms that need to be updated on following pages.
     for ($weight; $weight < count($tree); $weight++) {
         $term = $tree[$weight];
         if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
             $term->parent->target_id = $term->parents[0];
             $term->setWeight($weight);
             $changed_terms[$term->id()] = $term;
         }
         $hierarchy = $term->parents[0] != 0 ? TAXONOMY_HIERARCHY_SINGLE : $hierarchy;
     }
     // Save all updated terms.
     foreach ($changed_terms as $term) {
         $term->save();
     }
     // Update the vocabulary hierarchy to flat or single hierarchy.
     if ($vocabulary->getHierarchy() != $hierarchy) {
         $vocabulary->setHierarchy($hierarchy);
         $vocabulary->save();
     }
     drupal_set_message($this->t('The configuration options have been saved.'));
 }
 /**
  * Form constructor.
  *
  * Display a tree of all the terms in a vocabulary, with options to edit
  * each one. The form is made drag and drop by the theme function.
  *
  * @param array $form
  *   An associative array containing the structure of the form.
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form.
  * @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
  *   The vocabulary to display the overview form for.
  *
  * @return array
  *   The form structure.
  */
 public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL)
 {
     // @todo Remove global variables when http://drupal.org/node/2044435 is in.
     global $pager_page_array, $pager_total, $pager_total_items;
     $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary);
     $parent_fields = FALSE;
     $page = $this->getRequest()->query->get('page') ?: 0;
     // Number of terms per page.
     $page_increment = $this->config('taxonomy.settings')->get('terms_per_page_admin');
     // Elements shown on this page.
     $page_entries = 0;
     // Elements at the root level before this page.
     $before_entries = 0;
     // Elements at the root level after this page.
     $after_entries = 0;
     // Elements at the root level on this page.
     $root_entries = 0;
     // Terms from previous and next pages are shown if the term tree would have
     // been cut in the middle. Keep track of how many extra terms we show on
     // each page of terms.
     $back_step = NULL;
     $forward_step = 0;
     // An array of the terms to be displayed on this page.
     $current_page = array();
     $delta = 0;
     $term_deltas = array();
     $tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE);
     $tree_index = 0;
     do {
         // In case this tree is completely empty.
         if (empty($tree[$tree_index])) {
             break;
         }
         $delta++;
         // Count entries before the current page.
         if ($page && $page * $page_increment > $before_entries && !isset($back_step)) {
             $before_entries++;
             continue;
         } elseif ($page_entries > $page_increment && isset($complete_tree)) {
             $after_entries++;
             continue;
         }
         // Do not let a term start the page that is not at the root.
         $term = $tree[$tree_index];
         if (isset($term->depth) && $term->depth > 0 && !isset($back_step)) {
             $back_step = 0;
             while ($pterm = $tree[--$tree_index]) {
                 $before_entries--;
                 $back_step++;
                 if ($pterm->depth == 0) {
                     $tree_index--;
                     // Jump back to the start of the root level parent.
                     continue 2;
                 }
             }
         }
         $back_step = isset($back_step) ? $back_step : 0;
         // Continue rendering the tree until we reach the a new root item.
         if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
             $complete_tree = TRUE;
             // This new item at the root level is the first item on the next page.
             $after_entries++;
             continue;
         }
         if ($page_entries >= $page_increment + $back_step) {
             $forward_step++;
         }
         // Finally, if we've gotten down this far, we're rendering a term on this
         // page.
         $page_entries++;
         $term_deltas[$term->id()] = isset($term_deltas[$term->id()]) ? $term_deltas[$term->id()] + 1 : 0;
         $key = 'tid:' . $term->id() . ':' . $term_deltas[$term->id()];
         // Keep track of the first term displayed on this page.
         if ($page_entries == 1) {
             $form['#first_tid'] = $term->id();
         }
         // Keep a variable to make sure at least 2 root elements are displayed.
         if ($term->parents[0] == 0) {
             $root_entries++;
         }
         $current_page[$key] = $term;
     } while (isset($tree[++$tree_index]));
     // Because we didn't use a pager query, set the necessary pager variables.
     $total_entries = $before_entries + $page_entries + $after_entries;
     $pager_total_items[0] = $total_entries;
     $pager_page_array[0] = $page;
     $pager_total[0] = ceil($total_entries / $page_increment);
     // If this form was already submitted once, it's probably hit a validation
     // error. Ensure the form is rebuilt in the same order as the user
     // submitted.
     $user_input = $form_state->getUserInput();
     if (!empty($user_input)) {
         // Get the POST order.
         $order = array_flip(array_keys($user_input['terms']));
         // Update our form with the new order.
         $current_page = array_merge($order, $current_page);
         foreach ($current_page as $key => $term) {
             // Verify this is a term for the current page and set at the current
             // depth.
             if (is_array($user_input['terms'][$key]) && is_numeric($user_input['terms'][$key]['term']['tid'])) {
                 $current_page[$key]->depth = $user_input['terms'][$key]['term']['depth'];
             } else {
                 unset($current_page[$key]);
             }
         }
     }
     $errors = $form_state->getErrors();
     $destination = drupal_get_destination();
     $row_position = 0;
     // Build the actual form.
     $form['terms'] = array('#type' => 'table', '#header' => array($this->t('Name'), $this->t('Weight'), $this->t('Operations')), '#empty' => $this->t('No terms available. <a href="@link">Add term</a>.', array('@link' => $this->url('entity.taxonomy_term.add_form', array('taxonomy_vocabulary' => $taxonomy_vocabulary->id())))), '#attributes' => array('id' => 'taxonomy'));
     foreach ($current_page as $key => $term) {
         /** @var $term \Drupal\Core\Entity\EntityInterface */
         $form['terms'][$key]['#term'] = $term;
         $indentation = array();
         if (isset($term->depth) && $term->depth > 0) {
             $indentation = array('#theme' => 'indentation', '#size' => $term->depth);
         }
         $form['terms'][$key]['term'] = array('#prefix' => !empty($indentation) ? drupal_render($indentation) : '', '#type' => 'link', '#title' => $term->getName(), '#url' => $term->urlInfo());
         if ($taxonomy_vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) {
             $parent_fields = TRUE;
             $form['terms'][$key]['term']['tid'] = array('#type' => 'hidden', '#value' => $term->id(), '#attributes' => array('class' => array('term-id')));
             $form['terms'][$key]['term']['parent'] = array('#type' => 'hidden', '#default_value' => $term->parents[0], '#attributes' => array('class' => array('term-parent')));
             $form['terms'][$key]['term']['depth'] = array('#type' => 'hidden', '#default_value' => $term->depth, '#attributes' => array('class' => array('term-depth')));
         }
         $form['terms'][$key]['weight'] = array('#type' => 'weight', '#delta' => $delta, '#title' => $this->t('Weight for added term'), '#title_display' => 'invisible', '#default_value' => $term->getWeight(), '#attributes' => array('class' => array('term-weight')));
         $operations = array('edit' => array('title' => $this->t('Edit'), 'query' => $destination, 'url' => $term->urlInfo('edit-form')), 'delete' => array('title' => $this->t('Delete'), 'query' => $destination, 'url' => $term->urlInfo('delete-form')));
         if ($this->moduleHandler->moduleExists('content_translation') && content_translation_translate_access($term)->isAllowed()) {
             $operations['translate'] = array('title' => $this->t('Translate'), 'query' => $destination, 'url' => $term->urlInfo('drupal:content-translation-overview'));
         }
         $form['terms'][$key]['operations'] = array('#type' => 'operations', '#links' => $operations);
         $form['terms'][$key]['#attributes']['class'] = array();
         if ($parent_fields) {
             $form['terms'][$key]['#attributes']['class'][] = 'draggable';
         }
         // Add classes that mark which terms belong to previous and next pages.
         if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
             $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-preview';
         }
         if ($row_position !== 0 && $row_position !== count($tree) - 1) {
             if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
                 $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-top';
             } elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
                 $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-bottom';
             }
         }
         // Add an error class if this row contains a form error.
         foreach ($errors as $error_key => $error) {
             if (strpos($error_key, $key) === 0) {
                 $form['terms'][$key]['#attributes']['class'][] = 'error';
             }
         }
         $row_position++;
     }
     if ($parent_fields) {
         $form['terms']['#tabledrag'][] = array('action' => 'match', 'relationship' => 'parent', 'group' => 'term-parent', 'subgroup' => 'term-parent', 'source' => 'term-id', 'hidden' => FALSE);
         $form['terms']['#tabledrag'][] = array('action' => 'depth', 'relationship' => 'group', 'group' => 'term-depth', 'hidden' => FALSE);
         $form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy';
         $form['terms']['#attached']['js'][] = array('data' => array('taxonomy' => array('backStep' => $back_step, 'forwardStep' => $forward_step)), 'type' => 'setting');
     }
     $form['terms']['#tabledrag'][] = array('action' => 'order', 'relationship' => 'sibling', 'group' => 'term-weight');
     if ($taxonomy_vocabulary->hierarchy != TAXONOMY_HIERARCHY_MULTIPLE && count($tree) > 1) {
         $form['actions'] = array('#type' => 'actions', '#tree' => FALSE);
         $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Save'), '#button_type' => 'primary');
         $form['actions']['reset_alphabetical'] = array('#type' => 'submit', '#submit' => array('::submitReset'), '#value' => $this->t('Reset to alphabetical'));
     }
     return $form;
 }
 /**
  * Return render array of indented taxonomy items.
  *
  * @param $vid: The vocabulary $vid (machine name).
  * @param $tid: Optional term tid to use as the parent for the tree.
  *
  * @return $items: A render array with the requested taxonomy terms.
  */
 public function getIndentedItems($vid, $tid)
 {
     $items = [];
     // The loadTree method has an option to return loaded entities instead of stdClass
     // objects, but that can exhaust all available memory for large vocabularies, so we
     // won't use it.
     $tree = $this->TermStorage->loadTree($vid, $tid);
     // The $term returned by loadTree() is not an entity, it is a simple stdClass object
     // with some of the most commonly-used term values, so we find values at
     // $term->depth, not $term->get('depth')->value.
     $term = current($tree);
     // If there are no terms, don't do anything.
     if (empty($term)) {
         return $items;
     } else {
         $base_depth = $term->depth;
     }
     $c = 0;
     $parent_array = [];
     // Iterate through the tree, setting up item links for each term.
     do {
         $depth = $term->depth - $base_depth;
         $item = $this->getTermItem($term);
         if (!isset($previous_depth)) {
             $previous_depth = $depth;
         }
         if ($depth > $previous_depth) {
             // Add a new level to the parent array.
             $parent_array[] = $c;
             $c = 0;
         } elseif ($depth < $previous_depth) {
             // Move the parent array back up a level.
             $c = array_pop($parent_array);
             // Render the previous group as a sub-list of items.
             // Pull out the whole sub-group that was at the previous level.
             $item_parent = $parent_array;
             $item_parent[] = $c;
             $child_items = NestedArray::getValue($items, $item_parent);
             // Pull out the parent from the children.
             $child_array = [];
             $child_array['#type'] = $child_items['#type'];
             $child_array['#title'] = $child_items['#title'];
             $child_array['#url'] = $child_items['#url'];
             unset($child_items['#type'], $child_items['#title'], $child_items['#url']);
             // Add the children as a sub item list.
             $child_array[0]['#theme'] = 'item_list';
             $child_array[0]['#items'] = $child_items;
             NestedArray::setValue($items, $item_parent, $child_array);
             // Start a new index at this level.
             $c++;
         } else {
             // No change in level, just increment the index.
             $c++;
         }
         // Set this item value using the parent array plus the current index.
         $item_parent = $parent_array;
         $item_parent[] = $c;
         NestedArray::setValue($items, $item_parent, $item);
         // Update the previous depth.
         $previous_depth = $depth;
     } while ($term = next($tree));
     // If there was a nested subgroup at the end of the list, render it now.
     $c = array_pop($parent_array);
     $item_parent = $parent_array;
     $item_parent[] = $c;
     $child_items = NestedArray::getValue($items, $item_parent);
     if (!empty($child_items)) {
         $child_array['#type'] = $child_items['#type'];
         $child_array['#title'] = $child_items['#title'];
         $child_array['#url'] = $child_items['#url'];
         $child_array[0]['#theme'] = 'item_list';
         unset($child_items['#type'], $child_items['#title'], $child_items['#url']);
         $child_array[0]['#items'] = $child_items;
         NestedArray::setValue($items, $item_parent, $child_array);
     }
     return $items;
 }