From 3da0c1595f8d1802f209ffe84c8b79496d28a81a Mon Sep 17 00:00:00 2001 From: Grevil <js@webks.de> Date: Fri, 14 Feb 2025 15:35:41 +0100 Subject: [PATCH] Add 'ConditionsFormTrait' --- .../Core/Condition/ConditionsFormTrait.php | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 core/lib/Drupal/Core/Condition/ConditionsFormTrait.php diff --git a/core/lib/Drupal/Core/Condition/ConditionsFormTrait.php b/core/lib/Drupal/Core/Condition/ConditionsFormTrait.php new file mode 100644 index 000000000000..b3907ff66843 --- /dev/null +++ b/core/lib/Drupal/Core/Condition/ConditionsFormTrait.php @@ -0,0 +1,220 @@ +<?php + +namespace Drupal\Core\Condition; + +use Drupal\Core\Form\SubformState; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Plugin\Context\ContextRepositoryInterface; + +/** + * Provides common functionality for creating a conditions form. + */ +trait ConditionsFormTrait { + + /** + * The key for the conditions form. + * + * @var string + */ + protected string $conditionsFormKey = 'conditions'; + + /** + * The condition manager. + * + * @var \Drupal\Core\Condition\ConditionManager + */ + protected ?ConditionManager $conditionManager = NULL; + + /** + * Repository for context data. + * + * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface + */ + protected ?ContextRepositoryInterface $contextRepository = NULL; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected ?LanguageManagerInterface $languageManager = NULL; + + /** + * Get the condition manager. + * + * @return \Drupal\Core\Condition\ConditionManager + * The condition manager. + */ + protected function getConditionManager(): ConditionManager { + if (!$this->conditionManager) { + $this->conditionManager = \Drupal::service('plugin.manager.condition'); + } + return $this->conditionManager; + } + + /** + * Get the context repository. + * + * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface + * The context repository. + */ + protected function getContextRepository(): ContextRepositoryInterface { + if (!$this->contextRepository) { + $this->contextRepository = \Drupal::service('context.repository'); + } + return $this->contextRepository; + } + + /** + * Get the language manager. + * + * @return \Drupal\Core\Language\LanguageManagerInterface + * The language manager. + */ + protected function getLanguageManager(): LanguageManagerInterface { + if (!$this->languageManager) { + $this->languageManager = \Drupal::service('language_manager'); + } + return $this->languageManager; + } + + /** + * Conditions form constructor. + * + * Typically implemented in the buildForm() method, as follows: + * `$form[$this->conditionsFormKey] = $this->buildConditionsForm( + * [], + * $form_state, + * 'my_module', + * $conditions + * );` + * + * @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 string $consumer + * A string identifying the consumer of the condition definitions (e.g. + * the module id). + * @param array $conditions + * The conditions. + * + * @return array + * The form array with the visibility UI added in. + */ + protected function buildConditionsForm(array $form, FormStateInterface $form_state, $consumer, $conditions): array { + // Store the gathered contexts in the form_state, so it will be magically + // submitted in our subforms: + $form_state->setTemporaryValue('gathered_contexts', $this->getContextRepository()->getAvailableContexts()); + + // Set conditions hierarchical, so form_state can properly read them. + // Otherwise no conditions can be validated or submitted: + $form['#tree'] = TRUE; + // Build the conditions tabs: + $form['condition_tabs'] = [ + '#type' => 'vertical_tabs', + '#title' => $this->t('Conditions'), + '#description_display' => 'before', + '#parents' => ['condition_tabs'], + ]; + + foreach ($this->getFilteredConditionDefinitions($consumer) as $conditionId => $filteredCondition) { + // Don't display the language condition until we have multiple languages. + if ($conditionId === 'language' && !$this->getLanguageManager()->isMultilingual()) { + continue; + } + + /** @var \Drupal\Core\Condition\ConditionInterface $condition */ + $condition = $this->getConditionManager()->createInstance($conditionId, $conditions[$conditionId] ?? []); + $form_state->set([$this->conditionsFormKey, $conditionId], $condition); + $conditionForm = $condition->buildConfigurationForm([], $form_state); + $conditionForm['#type'] = 'details'; + $conditionForm['#title'] = $condition->getPluginDefinition()['label']; + $conditionForm['#group'] = 'condition_tabs'; + $form[$conditionId] = $conditionForm; + } + return $form; + } + + /** + * Retrieves filtered condition definitions for a given consumer. + * + * @param string $consumer + * A string identifying the consumer of the condition definitions (e.g. + * the module id). + * + * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface[] + * An array of filtered condition definitions. + */ + protected function getFilteredConditionDefinitions($consumer): array { + return $this->getConditionManager()->getFilteredDefinitions( + $consumer, + $this->getContextRepository()->getAvailableContexts(), + ); + } + + /** + * Conditions form validation handler. + * + * Typically implemented in the validateForm() method as follows: + * `$this->validateConditionsForm($form, $form_state);` + * + * @param array $form + * A nested array form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function validateConditionsForm(array $form, FormStateInterface $form_state): void { + // Validate visibility condition settings. + foreach ($form_state->getValue($this->conditionsFormKey) as $conditionId => $values) { + $subform = &$form[$this->conditionsFormKey][$conditionId]; + // All condition plugins use 'negate' as a Boolean in their schema. + // However, certain form elements may return it as 0/1. Cast here to + // ensure the data is in the expected type: + if (array_key_exists('negate', $values)) { + $form_state->setValue([$this->conditionsFormKey, $conditionId, 'negate'], (bool) $values['negate']); + } + /** @var \Drupal\Core\Condition\ConditionInterface $condition */ + $condition = $form_state->get([$this->conditionsFormKey, $conditionId]); + $condition->validateConfigurationForm($subform, SubformState::createForSubform($subform, $form, $form_state)); + } + } + + /** + * Conditions Form submission handler. + * + * Typically implemented in the submitForm() method. E.g.: + * `$this + * ->config('my_module.settings') + * ->set( + * $this->conditionsFormKey, + * $this->submitConditionsForm($form, $form_state) + * );` + * + * @param array $form + * A nested array form elements comprising the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The conditions configuration. + */ + protected function submitConditionsForm(array $form, FormStateInterface $form_state): array { + $conditionsCollection = new ConditionPluginCollection($this->conditionManager, []); + foreach ($form_state->getValue($this->conditionsFormKey) as $condition_id => $values) { + $subform = &$form[$this->conditionsFormKey][$condition_id]; + // Allow the condition to submit the form. + $condition = $form_state->get([$this->conditionsFormKey, $condition_id]); + $condition->submitConfigurationForm($subform, SubformState::createForSubform($subform, $form, $form_state)); + + $conditionConfig = $condition->getConfiguration(); + // Cast the negate value to a boolean, for consistency: + $conditionConfig['negate'] = (bool) (array_key_exists('negate', $conditionConfig) ? $conditionConfig['negate'] : FALSE); + // Add the condition to the collection: + $conditionsCollection->addInstanceId($condition_id, $conditionConfig); + } + return $conditionsCollection->getConfiguration(); + } + +} -- GitLab