diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module index 43c59a2..ef3d9de 100644 --- a/core/modules/config_translation/config_translation.module +++ b/core/modules/config_translation/config_translation.module @@ -188,6 +188,8 @@ function config_translation_config_schema_info_alter(&$definitions) { 'text' => '\Drupal\config_translation\FormElement\Textarea', 'date_format' => '\Drupal\config_translation\FormElement\DateFormat', 'text_format' => '\Drupal\config_translation\FormElement\TextFormat', + 'mapping' => '\Drupal\config_translation\FormElement\ListElement', + 'sequence' => '\Drupal\config_translation\FormElement\ListElement', ); // Enhance the text and date type definitions with classes to generate proper diff --git a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php index 121ca1e..522493e 100644 --- a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php +++ b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php @@ -12,6 +12,7 @@ use Drupal\Core\Config\Schema\ArrayElement; use Drupal\Core\Config\Schema\Element; use Drupal\Core\Config\TypedConfigManagerInterface; +use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\BaseFormIdInterface; use Drupal\Core\Form\FormBase; @@ -253,83 +254,44 @@ public function submitForm(array &$form, FormStateInterface $form_state) { */ protected function buildConfigForm($name, ArrayElement $schema, $source_config, $translation_config, $open = TRUE, $base_key = NULL) { $build = array(); - foreach ($schema as $key => $element) { - // Make the specific element key, "$base_key.$key". - $element_key = implode('.', array_filter(array($base_key, $key))); - $definition = $element->getDataDefinition(); - if (!$definition->getLabel()) { - $definition->setLabel($this->t('N/A')); - } - // If this element is translatable, show a translation form. This may be a - // simple element, as is the case for unformatted text or date formats, or - // an array element, as is the case for formatted text. In the latter case - // the form element class is responsible for handling input for all - // configuration values contained in the element. - if (!empty($definition['translatable'])) { - if (!isset($definition['form_element_class'])) { - $this->logger('config_translation')->notice("No form element class exists for config schema definition '@name'", array('@name', $definition->getLabel())); - continue; - } - /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */ - $form_element = new $definition['form_element_class']($definition); - - $build[$element_key] = array( - '#theme' => 'config_translation_manage_form_element', - ); - $build[$element_key]['source'] = $form_element->getSourceElement($this->sourceLanguage, $source_config[$key]); - $build[$element_key]['translation'] = $form_element->getTranslationElement($this->language, $source_config[$key], $translation_config[$key]); - // For accessibility we make source and translation appear next to each - // other in the source for each element, which is why we utilize the - // 'source' and 'translation' sub-keys for the form. The form values, - // however, should mirror the configuration structure, so that we can - // traverse the configuration schema and still access the right - // configuration values in ConfigTranslationFormBase::setConfig(). - // Therefore we make the 'source' and 'translation' keys the top-level - // keys in $form_state['values']. - $parents = array_merge(array('config_names', $name), explode('.', $element_key)); - $build[$element_key]['source']['#parents'] = array_merge(array('source'), $parents); - $build[$element_key]['translation']['#parents'] = array_merge(array('translation'), $parents); + if ($form_element = $this->createFormElement($schema)) { + $parents = array_merge(array('config_names', $name)); + $key = ''; + $build = $form_element->getElements($this->sourceLanguage, $this->language, $source_config, $translation_config, $key, $parents); + } + else { + $definition = $schema->getDefinition(); + $this->logger('config_translation')->notice("No form element class exists for config schema definition '@name'", array('@name', $definition->getLabel())); + } + if ($build) { + $build[$key]['#open'] = TRUE; + } + + return $build; + } + + /** + * Create form element builder. + * + * @param \Drupal\Core\TypedData\TypedDataInterface $schema + * Schema definition of configuration. + * + * @return \Drupal\config_translation\FormElement\ElementInterface|NULL + * The element builder object if possible. + */ + public static function createFormElement(TypedDataInterface $schema) { + $definition = $schema->getDataDefinition(); + if (!empty($definition['translatable']) || $schema instanceof ArrayElement) { + if (!$definition->getLabel()) { + $definition->setLabel(t('N/A')); } - // If this is a non-translatable array element, traverse the schema - // further. - elseif ($element instanceof ArrayElement) { - // Build sub-structure and include it with a wrapper in the form - // if there are any translatable elements there. - $sub_build = $this->buildConfigForm($name, $element, $source_config[$key], $translation_config[$key], FALSE, $element_key); - if (!empty($sub_build)) { - // For some configuration elements the same element structure can - // repeat multiple times, (like views displays, filters, etc.). - // So try to find a more usable title for the details summary. First - // check if there is an element which is called title or label, then - // check if there is an element which contains these words. - $title = ''; - if (isset($sub_build['title']['source'])) { - $title = $sub_build['title']['source']['#markup']; - } - elseif (isset($sub_build['label']['source'])) { - $title = $sub_build['label']['source']['#markup']; - } - else { - foreach (array_keys($sub_build) as $title_key) { - if (isset($sub_build[$title_key]['source']) && (strpos($title_key, 'title') !== FALSE || strpos($title_key, 'label') !== FALSE)) { - $title = $sub_build[$title_key]['source']['#markup']; - break; - } - } - } - $build[$key] = array( - '#type' => 'details', - '#title' => (!empty($title) ? (strip_tags($title) . ' ') : '') . $this->t($definition['label']), - '#open' => $open, - ) + $sub_build; - } + if (isset($definition['form_element_class'])) { + /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */ + return new $definition['form_element_class']($schema); } - // If this is a simple, non-translatable element, simply continue with the - // next element. } - return $build; } /** diff --git a/core/modules/config_translation/src/FormElement/ElementInterface.php b/core/modules/config_translation/src/FormElement/ElementInterface.php index 2448daa..3d629f6 100644 --- a/core/modules/config_translation/src/FormElement/ElementInterface.php +++ b/core/modules/config_translation/src/FormElement/ElementInterface.php @@ -16,6 +16,28 @@ interface ElementInterface { /** + * Returns the source and translation elements for a given configuration + * definition. + * + * @param \Drupal\Core\Language\LanguageInterface $source_language + * Thee source language of the configuration object. + * @param \Drupal\Core\Language\LanguageInterface $translation_language + * The language to display the translation form for. + * @param mixed $source_config + * The configuration value of the element in the source language. + * @param mixed $translation_config + * The configuration value of the element in the language to translate to. + * @param string $base_key + * Base key to be used for the elements in the form. + * @param array $parents + * Parents array for the element in the form. + * + * @return array + * A render array for the source value. + */ + public function getElements(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, $base_key, $parents); + + /** * Returns the source element for a given configuration definition. * * This can be either a render array that actually outputs the source values diff --git a/core/modules/config_translation/src/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php index 5a1bab0..57e8c4c 100644 --- a/core/modules/config_translation/src/FormElement/FormElementBase.php +++ b/core/modules/config_translation/src/FormElement/FormElementBase.php @@ -10,6 +10,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\TypedData\DataDefinitionInterface; +use Drupal\Core\TypedData\TypedDataInterface; /** * Provides a common base class for form elements. @@ -30,8 +31,31 @@ * * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition */ - public function __construct(DataDefinitionInterface $definition) { - $this->definition = $definition; + public function __construct(TypedDataInterface $element) { + $this->element = $element; + $this->definition = $element->getDataDefinition(); + } + + /** + * {@inheritdoc} + */ + public function getElements(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, $base_key, $parents) { + $build[$base_key]['#theme'] = 'config_translation_manage_form_element'; + + // For accessibility we make source and translation appear next to each + // other in the source for each element, which is why we utilize the + // 'source' and 'translation' sub-keys for the form. The form values, + // however, should mirror the configuration structure, so that we can + // traverse the configuration schema and still access the right + // configuration values in ConfigTranslationFormBase::setConfig(). + // Therefore we make the 'source' and 'translation' keys the top-level + // keys in $form_state['values']. + $build[$base_key]['source'] = $this->getSourceElement($source_language, $source_config); + $build[$base_key]['translation'] = $this->getTranslationElement($translation_language, $source_config, $translation_config); + + $build[$base_key]['source']['#parents'] = array_merge(array('source'), $parents); + $build[$base_key]['translation']['#parents'] = array_merge(array('translation'), $parents); + return $build; } /** diff --git a/core/modules/config_translation/src/FormElement/ListElement.php b/core/modules/config_translation/src/FormElement/ListElement.php new file mode 100644 index 0000000..2e6b3df --- /dev/null +++ b/core/modules/config_translation/src/FormElement/ListElement.php @@ -0,0 +1,88 @@ +element as $key => $element) { + $sub_build = array(); + // Make the specific element key, "$base_key.$key". + $element_key = implode('.', array_filter(array($base_key, $key))); + $definition = $element->getDataDefinition(); + + // If this element is translatable, show a translation form. This may be a + // simple element, as is the case for unformatted text or date formats, or + // an array element, as is the case for formatted text. In the latter case + // the form element class is responsible for handling input for all + // configuration values contained in the element. + + // If this is a non-translatable array element, traverse the schema + // further. + + if ($form_element = ConfigTranslationFormBase::createFormElement($element)) { + // For accessibility we make source and translation appear next to each + // other in the source for each element, which is why we utilize the + // 'source' and 'translation' sub-keys for the form. The form values, + // however, should mirror the configuration structure, so that we can + // traverse the configuration schema and still access the right + // configuration values in ConfigTranslationFormBase::setConfig(). + // Therefore we make the 'source' and 'translation' keys the top-level + // keys in $form_state['values']. + $element_parents = array_merge($parents, array($key)); + $sub_build += $form_element->getElements($source_language, $translation_language, $source_config[$key], $translation_config[$key], $element_key, $element_parents); + // Build sub-structure and include it with a wrapper in the form + // if there are any translatable elements there. + if (!empty($sub_build) && $element instanceof ArrayElement) { + // For some configuration elements the same element structure can + // repeat multiple times, (like views displays, filters, etc.). + // So try to find a more usable title for the details summary. First + // check if there is an element which is called title or label, then + // check if there is an element which contains these words. + $title = ''; + if (isset($sub_build['title']['source'])) { + $title = $sub_build['title']['source']['#markup']; + } + elseif (isset($sub_build['label']['source'])) { + $title = $sub_build['label']['source']['#markup']; + } + else { + foreach (array_keys($sub_build) as $title_key) { + if (isset($sub_build[$title_key]['source']) && (strpos($title_key, 'title') !== FALSE || strpos($title_key, 'label') !== FALSE)) { + $title = $sub_build[$title_key]['source']['#markup']; + break; + } + } + } + $build[$base_key] = array( + '#type' => 'details', + '#title' => (!empty($title) ? (strip_tags($title) . ' ') : '') . $this->t($definition['label']), + '#open' => FALSE, + ) + $sub_build; + } + else { + $build += $sub_build; + } + } + } + + return $build; + } + +}