Unverified Commit 537e7fef authored by Sergii Bondarenko's avatar Sergii Bondarenko Committed by Sergii Bondarenko
Browse files

Issue #3076080 by Daniel Kulbe, zuernBernhard, nishantghetiya, ksenzee,...

Issue #3076080 by Daniel Kulbe, zuernBernhard, nishantghetiya, ksenzee, BR0kEN: Configurable required level of selection
parent db6d24a3
Loading
Loading
Loading
Loading
+62 −19
Original line number Diff line number Diff line
@@ -9,6 +9,24 @@ use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\Html;
use Drupal\taxonomy\VocabularyInterface;

const HIERARCHY_OPTIONS = [
  'hierarchy_depth' => [
    'Hierarchy depth',
    [
      'Limits the nesting level. Use 0 to display all values. For the hierarchy like',
      '"a" -> "b" -> "c" the selection of 2 will result in "b" being the deepest option.',
    ],
  ],
  'required_depth' => [
    'Required depth',
    [
      'Requires item selection at the given nesting level. Use 0 to not impose the',
      'requirement. For the hierarchy like "a" -> "b" -> "c" the selection of 2 will',
      'obey a user to select at least "a" and "b".',
    ],
  ],
];

/**
 * Defines a class for getting options for a cshs form element from vocabulary.
 */
@@ -29,6 +47,7 @@ trait CshsOptionsFromHelper {
      'force_deepest' => FALSE,
      'save_lineage' => FALSE,
      'hierarchy_depth' => 0,
      'required_depth' => 0,
    ];
  }

@@ -68,10 +87,20 @@ trait CshsOptionsFromHelper {
  public function settingsSummary(): array {
    $settings = $this->getSettings();
    $summary = [];
    $deepest = $this->t('Deepest');
    $none = $this->t('None');
    $yes = $this->t('Yes');
    $no = $this->t('No');

    foreach (HIERARCHY_OPTIONS as $option_name => [$label]) {
      /** @noinspection NestedTernaryOperatorInspection */
      $summary[] = $this->t("$label: @$option_name", [
        "@$option_name" => empty($settings['force_deepest'])
          ? (empty($settings[$option_name]) ? $none : $settings[$option_name])
          : $deepest,
      ]);
    }

    $summary[] = $this->t('Force deepest: @force_deepest', [
      '@force_deepest' => empty($settings['force_deepest']) ? $no : $yes,
    ]);
@@ -88,10 +117,6 @@ trait CshsOptionsFromHelper {
      '@level_labels' => empty($settings['level_labels']) ? $none : $this->getTranslatedLevelLabels(),
    ]);

    $summary[] = $this->t('Hierarchy depth: @hierarchy_depth', [
      '@hierarchy_depth' => empty($settings['hierarchy_depth']) ? $none : $this->getSetting('hierarchy_depth'),
    ]);

    return $summary;
  }

@@ -116,22 +141,21 @@ trait CshsOptionsFromHelper {
      $options[$key] = $value['name'];
    }

    $element['hierarchy_depth'] = [
    foreach (HIERARCHY_OPTIONS as $option_name => [$title, $description]) {
      $description[] = '<i>Ignored when the deepest selection is enforced.</i>';
      $element[$option_name] = [
        '#min' => 0,
        '#type' => 'number',
      '#title' => $this->t('Hierarchy depth'),
      '#default_value' => $this->getSetting('hierarchy_depth'),
      '#description' => $this->t(\implode(' ', [
        'Limits the nesting level. Use 0 to display all values. For the hierarchy like',
        '"a" -> "b" -> "c" the selection of 2 will result in "b" being the deepest option.',
        '<i>Ignored when the deepest selection is enforced.</i>',
      ])),
        '#title' => $title,
        '#description' => $this->t(\implode(' ', $description)),
        '#default_value' => $this->getSetting($option_name),
        '#states' => [
          'disabled' => [
            ':input[name*="force_deepest"]' => ['checked' => TRUE],
          ],
        ],
      ];
    }

    $element['force_deepest'] = [
      '#type' => 'checkbox',
@@ -172,11 +196,29 @@ trait CshsOptionsFromHelper {
      '#default_value' => $this->getTranslatedLevelLabels(),
    ];

    $element['#element_validate'][] = [$this, 'validateSettingsForm'];

    $form_state->set('vocabulary', $vocabulary);

    return $element;
  }

  /**
   * Validates the settings form.
   *
   * @param array $element
   *   The element's form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function validateSettingsForm(array &$element, FormStateInterface $form_state): void {
    $settings = $form_state->getValue($element['#parents']);

    if ($settings['required_depth'] > $settings['hierarchy_depth']) {
      $form_state->setError($element['required_depth'], $this->t('The required depth cannot be greater than the hierarchy depth.'));
    }
  }

  /**
   * Returns the form for a single widget.
   *
@@ -200,6 +242,7 @@ trait CshsOptionsFromHelper {
      '#vocabulary' => $vocabulary,
      '#none_value' => CSHS_DEFAULT_NONE_VALUE,
      '#force_deepest' => $settings['force_deepest'],
      '#required_depth' => $settings['required_depth'],
      '#multiple' => $settings['save_lineage'],
      '#default_value' => CSHS_DEFAULT_NONE_VALUE,
    ];
+29 −11
Original line number Diff line number Diff line
@@ -4,7 +4,6 @@ namespace Drupal\cshs\Element;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Select;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy\TermStorageInterface;
use Drupal\views\Form\ViewsExposedForm;

@@ -25,6 +24,7 @@ class CshsElement extends Select {
    $info['#labels'] = [];
    $info['#parent'] = 0;
    $info['#force_deepest'] = FALSE;
    $info['#required_depth'] = 0;
    $info['#none_value'] = CSHS_DEFAULT_NONE_VALUE;
    // @codingStandardsIgnoreStart
    $info['#none_label'] = $this->t(CSHS_DEFAULT_NONE_LABEL);
@@ -62,14 +62,14 @@ class CshsElement extends Select {
   * {@inheritdoc}
   */
  public static function validateElement(array &$element, FormStateInterface $form_state): void {
    $value = $element['#value'];
    $term_id = $element['#value'];

    if (\is_array($value)) {
      $value = \end($value);
    if (\is_array($term_id)) {
      $term_id = \end($term_id);
    }

    // The value is not selected.
    if (empty($value) || $value == $element['#none_value']) {
    if (empty($term_id) || $term_id == $element['#none_value']) {
      // Element must have its "none" value when nothing selected. This will
      // let it function correctly, for instance with views. Otherwise it could
      // lead to illegal choice selection error.
@@ -85,24 +85,42 @@ class CshsElement extends Select {
    }
    // Do we want to force the user to select terms from the deepest level?
    elseif ($element['#force_deepest']) {
      $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
      \assert($storage instanceof TermStorageInterface);
      $term = $storage->load($value);
      \assert($term === NULL || $term instanceof TermInterface);
      $storage = static::getTermStorage();

      if ($term === NULL) {
      if (!$storage->load($term_id)) {
        $form_state->setError($element, \t('Unable to load a term (ID: @id) for the @label field.', [
          '@id' => $element['#value'],
          '@label' => $element['#label'],
        ]));
      }
      // Set an error if term has children.
      elseif (!empty($storage->loadChildren($term->id(), $term->bundle()))) {
      elseif (!empty($storage->loadChildren($term_id))) {
        $form_state->setError($element, \t('You need to select a term from the deepest level in @label field.', [
          '@label' => $element['#label'],
        ]));
      }
    }
    // Do we want to force a user to select terms from at least a certain level?
    elseif ($element['#required_depth'] > 0) {
      $storage = static::getTermStorage();

      if (\count($storage->loadAllParents($term_id)) < $element['#required_depth']) {
        $form_state->setError($element, \t('The field @label requires you to select at least @level levels of hierarchy.', [
          '@label' => $element['#label'],
          '@level' => $element['#required_depth'],
        ]));
      }
    }
  }

  /**
   * Returns the "taxonomy_term" entities storage.
   *
   * @return \Drupal\taxonomy\TermStorageInterface
   *   The storage.
   */
  protected static function getTermStorage(): TermStorageInterface {
    return \Drupal::entityTypeManager()->getStorage('taxonomy_term');
  }

}