Loading src/CshsOptionsFromHelper.php +62 −19 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -29,6 +47,7 @@ trait CshsOptionsFromHelper { 'force_deepest' => FALSE, 'save_lineage' => FALSE, 'hierarchy_depth' => 0, 'required_depth' => 0, ]; } Loading Loading @@ -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, ]); Loading @@ -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; } Loading @@ -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', Loading Loading @@ -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. * Loading @@ -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, ]; Loading src/Element/CshsElement.php +29 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading Loading @@ -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. Loading @@ -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'); } } Loading
src/CshsOptionsFromHelper.php +62 −19 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -29,6 +47,7 @@ trait CshsOptionsFromHelper { 'force_deepest' => FALSE, 'save_lineage' => FALSE, 'hierarchy_depth' => 0, 'required_depth' => 0, ]; } Loading Loading @@ -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, ]); Loading @@ -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; } Loading @@ -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', Loading Loading @@ -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. * Loading @@ -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, ]; Loading
src/Element/CshsElement.php +29 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading Loading @@ -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. Loading @@ -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'); } }