From aa6e74e9afc805d2d8b54c506058ace1ab63ed91 Mon Sep 17 00:00:00 2001
From: Thomas Gauges <tgauges@dev-specialists.com>
Date: Wed, 17 Jan 2024 13:20:45 +0100
Subject: [PATCH] feat(#2960456): apply the adjusted patch for 8.x-1.x to 2.0.x

---
 src/Element/CheckboxTree.php                  | 48 ++++++++++++++++---
 .../Field/FieldWidget/TermReferenceTree.php   | 35 ++++++++++++++
 term_reference_tree.module                    | 35 ++++++++------
 3 files changed, 98 insertions(+), 20 deletions(-)

diff --git a/src/Element/CheckboxTree.php b/src/Element/CheckboxTree.php
index d79be89..e25cecf 100644
--- a/src/Element/CheckboxTree.php
+++ b/src/Element/CheckboxTree.php
@@ -5,6 +5,7 @@ namespace Drupal\term_reference_tree\Element;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element\CompositeFormElementTrait;
 use Drupal\Core\Render\Element\FormElement;
+use Drupal\term_reference_tree\Plugin\Field\FieldWidget\TermReferenceTree;
 
 /**
  * Provides a form element for term reference tree.
@@ -66,19 +67,54 @@ class CheckboxTree extends FormElement {
     if (empty($element['#options'])) {
       $options_tree = [];
       foreach ($element['#vocabularies'] as $vocabulary) {
-        $options = _term_reference_tree_get_term_hierarchy(0, $vocabulary->id(), $allowed, $filter, '', $value);
-        $options_tree = array_merge($options_tree, $options);
+        $options_tree[$vocabulary->id()] = [
+          'name' => $vocabulary->label(),
+          'terms' => _term_reference_tree_get_term_hierarchy(0, $vocabulary->id(), $allowed, $filter, '', $value),
+        ];
       }
       $element['#options_tree'] = $options_tree;
       $element['#options'] = _term_reference_tree_get_options($element['#options_tree'], $allowed, $filter);
     }
 
-    $terms = !empty($element['#options_tree']) ? $element['#options_tree'] : [];
+    if (count($element['#vocabularies']) > 1 && $element['#multiple_vocabularies'] === TermReferenceTree::MULTIPLE_VOCABULARIES_TREE_ROOT) {
+      $tree = new \stdClass();
+      $tree->children = [];
+      foreach ($options_tree as $vocabulary_id => $data) {
+        $root = new \stdClass();
+        $root->vid = $vocabulary_id;
+        $root->name = $data['name'];
+        $root->children = $data['terms'];
+        $tree->children[] = $root;
+        $element['#options'] = array_merge($element['#options'], _term_reference_tree_get_options($data['terms'], $allowed, NULL));
+      }
+      $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1);
+    }
+    elseif (count($element['#vocabularies']) > 1 && $element['#multiple_vocabularies'] === TermReferenceTree::MULTIPLE_VOCABULARIES_CLONED) {
+      foreach ($options_tree as $vocabulary_id => $data) {
+        $tree = new \stdClass();
+        $tree->vid = $vocabulary_id;
+        $tree->name = $data['name'];
+        $tree->children = $data['terms'];
+        $element[$vocabulary_id] = [
+          '#type' => 'details',
+          '#title' => $data['name'],
+          'tree' => _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1),
+        ];
+        $element['#options'] = array_merge($element['#options'], _term_reference_tree_get_options($data['terms'], $allowed, NULL));
+      }
+    }
+    // If there is only one vocabulary or the "merged" mode is selected.
+    else {
+      $tree = new \stdClass();
+      $tree->children = [];
+      foreach ($options_tree as $vocabulary_id => $data) {
+        $tree->children = array_merge($tree->children, $data['terms']);
+      }
+      $element['#options'] = _term_reference_tree_get_options($tree->children, $allowed, NULL);
+      $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1);
+    }
 
-    $tree = new \stdClass();
-    $tree->children = $terms;
     unset($element['#needs_validation']);
-    $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $element['#max_choices'], [], 1);
     return $element;
   }
 
diff --git a/src/Plugin/Field/FieldWidget/TermReferenceTree.php b/src/Plugin/Field/FieldWidget/TermReferenceTree.php
index 06cb955..830855c 100644
--- a/src/Plugin/Field/FieldWidget/TermReferenceTree.php
+++ b/src/Plugin/Field/FieldWidget/TermReferenceTree.php
@@ -28,6 +28,12 @@ class TermReferenceTree extends WidgetBase {
 
   const CASCADING_SELECTION_DESELECT = '3';
 
+  const MULTIPLE_VOCABULARIES_MERGED = 'merged';
+
+  const MULTIPLE_VOCABULARIES_TREE_ROOT = 'tree_root';
+
+  const MULTIPLE_VOCABULARIES_CLONED = 'cloned';
+
   /**
    * {@inheritdoc}
    */
@@ -37,6 +43,7 @@ class TermReferenceTree extends WidgetBase {
       'leaves_only' => FALSE,
       'select_parents' => FALSE,
       'cascading_selection' => self::CASCADING_SELECTION_NONE,
+      'multiple_vocabularies' => self::MULTIPLE_VOCABULARIES_MERGED,
       'max_depth' => 0,
     ] + parent::defaultSettings();
   }
@@ -94,6 +101,20 @@ class TermReferenceTree extends WidgetBase {
       $form['cascading_selection']['#description'] .= ' <em>' . $this->t("This option is only valid if an unlimited number of values can be selected.") . '</em>';
     }
 
+    if (count($this->fieldDefinition->getSettings()['handler_settings']['target_bundles']) > 1) {
+      $form['multiple_vocabularies'] = [
+        '#type' => 'select',
+        '#title' => $this->t('Multiple vocabularies handling'),
+        '#description' => $this->t('Select how terms from multiple vocabularies are displayed.'),
+        '#default_value' => $this->getSetting('multiple_vocabularies'),
+        '#options' => [
+          self::MULTIPLE_VOCABULARIES_MERGED => $this->t('Terms from all vocabularies are merged in the same tree'),
+          self::MULTIPLE_VOCABULARIES_TREE_ROOT => $this->t('Each vocabulary is a root node of the tree'),
+          self::MULTIPLE_VOCABULARIES_CLONED => $this->t('Each vocabulary has its own widget'),
+        ],
+      ];
+    }
+
     $form['max_depth'] = [
       '#type' => 'number',
       '#title' => $this->t('Maximum Depth'),
@@ -134,6 +155,19 @@ class TermReferenceTree extends WidgetBase {
       $summary[] = sprintf('%s (%s)', $this->t('Cascading selection'), $this->t('Only deselect'));
     }
 
+    switch ($this->getSetting('multiple_vocabularies')) {
+      case self::MULTIPLE_VOCABULARIES_TREE_ROOT:
+        $summary[] = $this->t('Each vocabulary is a root node of the tree');
+        break;
+      case self::MULTIPLE_VOCABULARIES_CLONED:
+        $summary[] = $this->t('Each vocabulary has its own widget');
+        break;
+      case self::MULTIPLE_VOCABULARIES_MERGED:
+      default:
+        $summary[] = $this->t('Terms from all vocabularies are merged in the same tree');
+        break;
+    }
+
     if ($this->getSetting('max_depth')) {
       $summary[] = $this->formatPlural($this->getSetting('max_depth'), 'Maximum Depth: @count level', 'Maximum Depth: @count levels');
     }
@@ -156,6 +190,7 @@ class TermReferenceTree extends WidgetBase {
     $element['#leaves_only'] = $this->getSetting('leaves_only');
     $element['#select_parents'] = $this->getSetting('select_parents');
     $element['#cascading_selection'] = $this->getSetting('cascading_selection');
+    $element['#multiple_vocabularies'] = $this->getSetting('multiple_vocabularies');
     $element['#value_key'] = 'target_id';
     $element['#max_depth'] = $this->getSetting('max_depth');
     $element['#start_minimized'] = $this->getSetting('start_minimized');
diff --git a/term_reference_tree.module b/term_reference_tree.module
index 9e5e870..9ac57c0 100644
--- a/term_reference_tree.module
+++ b/term_reference_tree.module
@@ -350,7 +350,7 @@ function _term_reference_tree_get_options(&$terms, &$allowed, $filter) {
 
   if (is_array($terms) && count($terms) > 0) {
     foreach ($terms as $term) {
-      if (!$filter || (is_array($allowed) && $allowed[$term->tid])) {
+      if (is_object($term) && (!$filter || (is_array($allowed) && $allowed[$term->tid]))) {
         $options[$term->tid] = $term->name;
         $options += _term_reference_tree_get_options($term->children, $allowed, $filter);
       }
@@ -377,10 +377,11 @@ function _term_reference_tree_build_level($element, $term, $form_state, $value,
     '#depth' => $depth,
   ];
 
-  $container['#level_start_minimized'] = $depth > 1 && $element['#start_minimized'] && !($term->children_selected);
+  $container['#level_start_minimized'] = $depth > 1 && $element['#start_minimized'] && empty($term->children_selected);
 
   foreach ($term->children as $child) {
-    $container[$child->tid] = _term_reference_tree_build_item($element, $child, $form_state, $value, $max_choices, $parent_tids, $container, $depth);
+    $key = _term_reference_tree_item_id($child);
+    $container[$key] = _term_reference_tree_build_item($element, $child, $form_state, $value, $max_choices, $parent_tids, $container, $depth);
   }
 
   return $container;
@@ -421,7 +422,7 @@ function _term_reference_tree_build_item($element, $term, $form_state, $value, $
     '#depth' => $depth,
   ];
 
-  if (!$element['#leaves_only'] || count($term->children) == 0) {
+  if ((!$element['#leaves_only'] || count($term->children) == 0) && !empty($term->tid)) {
     $e = [
       '#type' => ($max_choices == 1) ? 'radio' : 'checkbox',
       '#title' => $term_name,
@@ -434,12 +435,10 @@ function _term_reference_tree_build_item($element, $term, $form_state, $value, $
       '#ajax' => $element['#ajax'] ?? NULL,
     ];
 
-    if (is_array($e)) {
-      if ($e['#type'] == 'radio') {
-        $parents_for_id = array_merge($element['#parents'], [$term->tid]);
-        $e['#id'] = Html::getId('edit-' . implode('-', $parents_for_id));
-        $e['#parents'] = array_merge($element['#parents'], ['wiget']);
-      }
+    if ($e['#type'] == 'radio') {
+      $parents_for_id = array_merge($element['#parents'], [$term->tid]);
+      $e['#id'] = Html::getId('edit-' . implode('-', $parents_for_id));
+      $e['#parents'] = array_merge($element['#parents'], ['wiget']);
     }
   }
   else {
@@ -449,18 +448,26 @@ function _term_reference_tree_build_item($element, $term, $form_state, $value, $
     ];
   }
 
-  $container[$term->tid] = $e;
+  $key = _term_reference_tree_item_id($term);
+  $container[$key] = $e;
 
   if (($depth + 1 <= $element['#max_depth'] || !$element['#max_depth']) && property_exists($term, 'children') && count($term->children) > 0) {
     $parents = $parent_tids;
-    $parents[] = $term->tid;
-    $container[$term->tid . '-children'] = _term_reference_tree_build_level($element, $term, $form_state, $value, $max_choices, $parents, $depth + 1);
-    $container['#level_start_minimized'] = $container[$term->tid . '-children']['#level_start_minimized'];
+    $parents[] = $key;
+    $container[$key . '-children'] = _term_reference_tree_build_level($element, $term, $form_state, $value, $max_choices, $parents, $depth + 1);
+    $container['#level_start_minimized'] = $container[$key . '-children']['#level_start_minimized'];
   }
 
   return $container;
 }
 
+/**
+ *
+ */
+function _term_reference_tree_item_id($item) {
+  return !empty($item->tid) ? $item->tid : $item->vid;
+}
+
 /**
  * Implements hook_preprocess_HOOK() for term tree display templates.
  *
-- 
GitLab