diff --git a/core/modules/config_translation/src/FormElement/ElementInterface.php b/core/modules/config_translation/src/FormElement/ElementInterface.php index 4f3db25..a3cc1d6 100644 --- a/core/modules/config_translation/src/FormElement/ElementInterface.php +++ b/core/modules/config_translation/src/FormElement/ElementInterface.php @@ -40,94 +40,14 @@ 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 - * directly or a read-only form element with the source values depending on - * what is considered to provide a more intuitive user interface for the - * translator. - * - * @param \Drupal\Core\Language\LanguageInterface $source_language - * Thee source language of the configuration object. - * @param mixed $source_config - * The configuration value of the element in the source language. - * - * @return array - * A render array for the source value. - */ - public function getSourceElement(LanguageInterface $source_language, $source_config); - - /** - * Returns the translation form element for a given configuration definition. - * - * For complex data structures (such as mappings) that are translatable - * wholesale but contain non-translatable properties, the form element is - * responsible for checking access to the source value of those properties. In - * case of formatted text, for example, access to the source text format must - * be checked. If the translator does not have access to the text format, the - * textarea must be disabled and the translator may not be able to translate - * this particular configuration element. If the translator does have access - * to the text format, the element must be locked down to that particular text - * format; in other words, the format may not be changed by the translator - * (because the text format property is not itself translatable). - * - * In addition, the form element is responsible for checking whether the - * value of such non-translatable properties in the translated configuration - * is equal to the corresponding source values. If not, that means that the - * source value has changed after the translation was added. In this case - - * again - the translation of this element must be disabled if the translator - * does not have access to the source value of the non-translatable property. - * For example, if a formatted text element, whose source format was plain - * text when it was first translated, gets changed to the full HTML format, - * simply changing the format of the translation would lead to an XSS - * vulnerability as the translated text, that was intended to be escaped, - * would now be displayed unescaped. Thus, if the translator does not have - * access to the Full HTML format, the translation for this particular element - * may not be updated at all (the textarea must be disabled). Only if access - * to the Full HTML format is granted, an explicit translation taking into - * account the updated source value(s) may be submitted. - * - * In the specific case of formatted text this logic is implemented by - * utilizing a form element of type 'text_format' and its #format and - * #allowed_formats properties. The access logic explained above is then - * handled by the 'text_format' element itself, specifically by - * filter_process_format(). In case such a rich element is not available for - * translation of complex data, similar access logic must be implemented - * manually. - * - * @param \Drupal\Core\Language\LanguageInterface $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. - * - * @return array - * Form API array to represent the form element. - * - * @see \Drupal\config_translation\FormElement\TextFormat - * @see filter_process_format() - */ - public function getTranslationElement(LanguageInterface $language, $source_config, $translation_config); - - /** * Sets configuration based on a nested form value array. * * @param \Drupal\Core\Config\Config $base_config * Base configuration values, in the source language. * @param \Drupal\language\Config\LanguageConfigOverride $config_translation * Translation configuration override data. - * @param array $config_values - * A simple one dimensional or recursive array: - * - simple: - * array(name => array('translation' => 'French site name')); - * - recursive: - * cancel_confirm => array( - * cancel_confirm.subject => array('translation' => 'Subject'), - * cancel_confirm.body => array('translation' => 'Body content'), - * ); - * Either format is used, the nested arrays are just containers and not - * needed for saving the data. + * @param mixed $config_values + * The configuration value of the element taken from the form values. * @param string|null $base_key * (optional) The base key that the schema and the configuration values * belong to. This should be NULL for the top-level configuration object and @@ -137,6 +57,6 @@ public function getTranslationElement(LanguageInterface $language, $source_confi * @return array * Translation configuration override data. */ - public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, array $config_values, $base_key = NULL); + public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL); } diff --git a/core/modules/config_translation/src/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php index 86e4c0e..ec9a023 100644 --- a/core/modules/config_translation/src/FormElement/FormElementBase.php +++ b/core/modules/config_translation/src/FormElement/FormElementBase.php @@ -23,6 +23,13 @@ use StringTranslationTrait; /** + * The schema element this form is for. + * + * @var \Drupal\Core\TypedData\TypedDataInterface + */ + protected $element; + + /** * The data definition of the element this form element is for. * * @var \Drupal\Core\TypedData\DataDefinitionInterface @@ -32,7 +39,8 @@ /** * Constructs a FormElementBase. * - * @param TypedDataInterface $element + * @param \Drupal\Core\TypedData\TypedDataInterface $element + * The schema element this form element is for. */ public function __construct(TypedDataInterface $element) { $this->element = $element; @@ -62,7 +70,20 @@ public function getElements(LanguageInterface $source_language, LanguageInterfac } /** - * {@inheritdoc} + * Returns the source element for a given configuration definition. + * + * This can be either a render array that actually outputs the source values + * directly or a read-only form element with the source values depending on + * what is considered to provide a more intuitive user interface for the + * translator. + * + * @param \Drupal\Core\Language\LanguageInterface $source_language + * Thee source language of the configuration object. + * @param mixed $source_config + * The configuration value of the element in the source language. + * + * @return array + * A render array for the source value. */ public function getSourceElement(LanguageInterface $source_language, $source_config) { if ($source_config) { @@ -82,9 +103,56 @@ public function getSourceElement(LanguageInterface $source_language, $source_con ); } - /** - * {@inheritdoc} + * Returns the translation form element for a given configuration definition. + * + * For complex data structures (such as mappings) that are translatable + * wholesale but contain non-translatable properties, the form element is + * responsible for checking access to the source value of those properties. In + * case of formatted text, for example, access to the source text format must + * be checked. If the translator does not have access to the text format, the + * textarea must be disabled and the translator may not be able to translate + * this particular configuration element. If the translator does have access + * to the text format, the element must be locked down to that particular text + * format; in other words, the format may not be changed by the translator + * (because the text format property is not itself translatable). + * + * In addition, the form element is responsible for checking whether the + * value of such non-translatable properties in the translated configuration + * is equal to the corresponding source values. If not, that means that the + * source value has changed after the translation was added. In this case - + * again - the translation of this element must be disabled if the translator + * does not have access to the source value of the non-translatable property. + * For example, if a formatted text element, whose source format was plain + * text when it was first translated, gets changed to the full HTML format, + * simply changing the format of the translation would lead to an XSS + * vulnerability as the translated text, that was intended to be escaped, + * would now be displayed unescaped. Thus, if the translator does not have + * access to the Full HTML format, the translation for this particular element + * may not be updated at all (the textarea must be disabled). Only if access + * to the Full HTML format is granted, an explicit translation taking into + * account the updated source value(s) may be submitted. + * + * In the specific case of formatted text this logic is implemented by + * utilizing a form element of type 'text_format' and its #format and + * #allowed_formats properties. The access logic explained above is then + * handled by the 'text_format' element itself, specifically by + * filter_process_format(). In case such a rich element is not available for + * translation of complex data, similar access logic must be implemented + * manually. + * + * @param \Drupal\Core\Language\LanguageInterface $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. + * + * @return array + * Form API array to represent the form element. + * + * @see \Drupal\config_translation\FormElement\TextFormat + * @see filter_process_format() */ public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) { // Add basic properties that apply to all form elements. @@ -101,34 +169,15 @@ public function getTranslationElement(LanguageInterface $translation_language, $ /** * {@inheritdoc} */ - public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, array $config_values, $base_key = NULL) { - foreach ($this->element as $key => $element) { - if (!isset($config_values[$key])) { - continue; - } - $value = $config_values[$key]; - - $element_key = implode('.', array_filter(array($base_key, $key))); - $definition = $element->getDataDefinition(); - // While the 'form_element_class' key is used for form building, the - // 'translatable' key is used for the setting of configuration values. - // This allows the form to contain values which will not be set, such as - // the TextFormat element's 'format' value. - if ($element instanceof Element && empty($definition['translatable'])) { - // Traverse into this level in the configuration. - $this->setConfig($base_config, $config_translation, $value, $element_key); + public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) { + // Save value, if different from the source value in the base + // configuration. If same as original configuration, remove override. + if ($base_config->get($base_key) !== $config_values) { + $config_translation->set($base_key, $config_values); } else { - // Save value, if different from the source value in the base - // configuration. If same as original configuration, remove override. - if ($base_config->get($element_key) !== $value) { - $config_translation->set($element_key, $value); - } - else { - $config_translation->clear($element_key); - } + $config_translation->clear($base_key); } } - } } diff --git a/core/modules/config_translation/src/FormElement/ListElement.php b/core/modules/config_translation/src/FormElement/ListElement.php index 4230789..0314a3c 100644 --- a/core/modules/config_translation/src/FormElement/ListElement.php +++ b/core/modules/config_translation/src/FormElement/ListElement.php @@ -12,6 +12,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Config\Schema\ArrayElement; use Drupal\config_translation\Form\ConfigTranslationFormBase; +use Drupal\Core\TypedData\TypedDataInterface; use Drupal\language\Config\LanguageConfigOverride; /** @@ -20,6 +21,23 @@ class ListElement extends FormElementBase { /** + * The schema element this form is for. + * + * @var \Drupal\Core\Config\Schema\ArrayElement + */ + protected $element; + + /** + * Constructs a FormElementBase. + * + * @param \Drupal\Core\Config\Schema\ArrayElement $element + * The schema element this form element is for. + */ + public function __construct(ArrayElement $element) { + parent::__construct($element); + } + + /** * {@inheritdoc} */ public function getElements(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, $base_key, $parents) { @@ -78,14 +96,34 @@ public function getElements(LanguageInterface $source_language, LanguageInterfac '#title' => (!empty($title) ? (strip_tags($title) . ' ') : '') . $this->t($definition['label']), '#open' => FALSE, ) + $sub_build; - } - else { - $build += $sub_build; - } - } + } + else { + $build += $sub_build; + } + } } return $build; } + /** + * {@inheritdoc} + */ + public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) { + foreach ($this->element as $key => $element) { + // Do not bother traversing schema elements for which no values have been + // submitted. + if (!isset($config_values[$key])) { + continue; + } + $value = $config_values[$key]; + + $element_key = implode('.', array_filter(array($base_key, $key))); + if ($form_element = ConfigTranslationFormBase::createFormElement($element)) { + // Traverse into the next level of the configuration. + $form_element->setConfig($base_config, $config_translation, $value, $element_key); + } + } + } + } diff --git a/core/modules/config_translation/src/FormElement/TextFormat.php b/core/modules/config_translation/src/FormElement/TextFormat.php index 1f8f3e7..5948085 100644 --- a/core/modules/config_translation/src/FormElement/TextFormat.php +++ b/core/modules/config_translation/src/FormElement/TextFormat.php @@ -37,9 +37,9 @@ public function getTranslationElement(LanguageInterface $translation_language, $ // Override the #default_value property from the parent class. '#default_value' => $translation_config['value'], '#format' => $translation_config['format'], + // @see \Drupal\config_translation\Element\ElementInterface::getTranslationElement() '#allowed_formats' => array($source_config['format']), ) + parent::getTranslationElement($translation_language, $source_config, $translation_config); } - } diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php index f516ca3..6c29709 100644 --- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php +++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php @@ -100,7 +100,7 @@ protected function setUp() { /** * Tests the site information translation interface. */ - public function _testSiteInformationTranslationUi() { + public function testSiteInformationTranslationUi() { $this->drupalLogin($this->admin_user); $site_name = 'Site name for testing configuration translation'; @@ -166,7 +166,7 @@ public function _testSiteInformationTranslationUi() { /** * Tests the site information translation interface. */ - public function _testSourceValueDuplicateSave() { + public function testSourceValueDuplicateSave() { $this->drupalLogin($this->admin_user); $site_name = 'Site name for testing configuration translation'; @@ -243,7 +243,7 @@ public function _testSourceValueDuplicateSave() { /** * Tests the contact form translation. */ - public function _testContactConfigEntityTranslation() { + public function testContactConfigEntityTranslation() { $this->drupalLogin($this->admin_user); $this->drupalGet('admin/structure/contact'); @@ -374,7 +374,7 @@ public function _testContactConfigEntityTranslation() { /** * Tests date format translation. */ - public function _testDateFormatTranslation() { + public function testDateFormatTranslation() { $this->drupalLogin($this->admin_user); $this->drupalGet('admin/config/regional/date-time'); @@ -439,7 +439,7 @@ public function _testDateFormatTranslation() { * names involved building up one configuration translation form. Test that * the translations are saved for all configuration names properly. */ - public function _testAccountSettingsConfigurationTranslation() { + public function testAccountSettingsConfigurationTranslation() { $this->drupalLogin($this->admin_user); $this->drupalGet('admin/config/people/accounts/translate'); @@ -469,7 +469,7 @@ public function _testAccountSettingsConfigurationTranslation() { /** * Tests source and target language edge cases. */ - public function _testSourceAndTargetLanguage() { + public function testSourceAndTargetLanguage() { $this->drupalLogin($this->admin_user); // Loading translation page for not-specified language (und) @@ -517,7 +517,7 @@ public function _testSourceAndTargetLanguage() { /** * Tests the views translation interface. */ - public function _testViewsTranslationUI() { + public function testViewsTranslationUI() { $this->drupalLogin($this->admin_user); // Assert contextual link related to views. @@ -569,7 +569,7 @@ public function _testViewsTranslationUI() { /** * Test translation storage in locale storage. */ - public function _testLocaleDBStorage() { + public function testLocaleDBStorage() { // Enable import of translations. By default this is disabled for automated // tests. \Drupal::config('locale.settings') @@ -617,7 +617,7 @@ public function _testLocaleDBStorage() { /** * Tests the single language existing. */ - public function _testSingleLanguageUI() { + public function testSingleLanguageUI() { $this->drupalLogin($this->admin_user); // Delete French language @@ -644,7 +644,7 @@ public function _testSingleLanguageUI() { /** * Tests the config_translation_info_alter() hook. */ - public function _testAlterInfo() { + public function testAlterInfo() { $this->drupalLogin($this->admin_user); $this->container->get('state')->set('config_translation_test_config_translation_info_alter', TRUE); diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config/install/config_translation_test.content.yml b/core/modules/config_translation/tests/modules/config_translation_test/config/install/config_translation_test.content.yml index 55aa430..5a4d4bb 100644 --- a/core/modules/config_translation/tests/modules/config_translation_test/config/install/config_translation_test.content.yml +++ b/core/modules/config_translation/tests/modules/config_translation_test/config/install/config_translation_test.content.yml @@ -2,5 +2,5 @@ id: test label: 'Test' langcode: en content: - value: "Hello World" + value: "

Hello World

" format: plain_text diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.links.task.yml b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.links.task.yml new file mode 100644 index 0000000..92581b4 --- /dev/null +++ b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.links.task.yml @@ -0,0 +1,7 @@ +# Add a default local task for the file system settings page, so that the local +# task added by Configuration Translation becomes visible. This facilitates +# manual testing. +system.file_system_settings: + route_name: system.file_system_settings + title: Settings + base_route: system.file_system_settings diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.local_tasks.yml b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.local_tasks.yml deleted file mode 100644 index 92581b4..0000000 --- a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.local_tasks.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Add a default local task for the file system settings page, so that the local -# task added by Configuration Translation becomes visible. This facilitates -# manual testing. -system.file_system_settings: - route_name: system.file_system_settings - title: Settings - base_route: system.file_system_settings