Added Entity and Entity Reference Revisions which got dropped somewhere along the...
[yaffs-website] / web / core / modules / config_translation / src / FormElement / FormElementBase.php
1 <?php
2
3 namespace Drupal\config_translation\FormElement;
4
5 use Drupal\Core\Config\Config;
6 use Drupal\Core\Language\LanguageInterface;
7 use Drupal\Core\StringTranslation\StringTranslationTrait;
8 use Drupal\Core\TypedData\TypedDataInterface;
9 use Drupal\language\Config\LanguageConfigOverride;
10
11 /**
12  * Provides a common base class for form elements.
13  */
14 abstract class FormElementBase implements ElementInterface {
15
16   use StringTranslationTrait;
17
18   /**
19    * The schema element this form is for.
20    *
21    * @var \Drupal\Core\TypedData\TypedDataInterface
22    */
23   protected $element;
24
25   /**
26    * The data definition of the element this form element is for.
27    *
28    * @var \Drupal\Core\TypedData\DataDefinitionInterface
29    */
30   protected $definition;
31
32   /**
33    * Constructs a FormElementBase.
34    *
35    * @param \Drupal\Core\TypedData\TypedDataInterface $element
36    *   The schema element this form element is for.
37    */
38   public function __construct(TypedDataInterface $element) {
39     $this->element = $element;
40     $this->definition = $element->getDataDefinition();
41   }
42
43   /**
44    * {@inheritdoc}
45    */
46   public static function create(TypedDataInterface $schema) {
47     return new static($schema);
48   }
49
50   /**
51    * {@inheritdoc}
52    */
53   public function getTranslationBuild(LanguageInterface $source_language, LanguageInterface $translation_language, $source_config, $translation_config, array $parents, $base_key = NULL) {
54     $build['#theme'] = 'config_translation_manage_form_element';
55
56     // For accessibility we make source and translation appear next to each
57     // other in the source for each element, which is why we utilize the
58     // 'source' and 'translation' sub-keys for the form. The form values,
59     // however, should mirror the configuration structure, so that we can
60     // traverse the configuration schema and still access the right
61     // configuration values in ConfigTranslationFormBase::setConfig().
62     // Therefore we make the 'source' and 'translation' keys the top-level
63     // keys in $form_state['values'].
64     $build['source'] = $this->getSourceElement($source_language, $source_config);
65     $build['translation'] = $this->getTranslationElement($translation_language, $source_config, $translation_config);
66
67     $build['source']['#parents'] = array_merge(['source'], $parents);
68     $build['translation']['#parents'] = array_merge(['translation'], $parents);
69     return $build;
70   }
71
72   /**
73    * Returns the source element for a given configuration definition.
74    *
75    * This can be either a render array that actually outputs the source values
76    * directly or a read-only form element with the source values depending on
77    * what is considered to provide a more intuitive user interface for the
78    * translator.
79    *
80    * @param \Drupal\Core\Language\LanguageInterface $source_language
81    *   Thee source language of the configuration object.
82    * @param mixed $source_config
83    *   The configuration value of the element in the source language.
84    *
85    * @return array
86    *   A render array for the source value.
87    */
88   protected function getSourceElement(LanguageInterface $source_language, $source_config) {
89     if ($source_config) {
90       $value = '<span lang="' . $source_language->getId() . '">' . nl2br($source_config) . '</span>';
91     }
92     else {
93       $value = $this->t('(Empty)');
94     }
95
96     return [
97       '#type' => 'item',
98       '#title' => $this->t('@label <span class="visually-hidden">(@source_language)</span>', [
99         // Labels originate from configuration schema and are translatable.
100         '@label' => $this->t($this->definition->getLabel()),
101         '@source_language' => $source_language->getName(),
102       ]),
103       '#markup' => $value,
104     ];
105   }
106
107   /**
108    * Returns the translation form element for a given configuration definition.
109    *
110    * For complex data structures (such as mappings) that are translatable
111    * wholesale but contain non-translatable properties, the form element is
112    * responsible for checking access to the source value of those properties. In
113    * case of formatted text, for example, access to the source text format must
114    * be checked. If the translator does not have access to the text format, the
115    * textarea must be disabled and the translator may not be able to translate
116    * this particular configuration element. If the translator does have access
117    * to the text format, the element must be locked down to that particular text
118    * format; in other words, the format may not be changed by the translator
119    * (because the text format property is not itself translatable).
120    *
121    * In addition, the form element is responsible for checking whether the
122    * value of such non-translatable properties in the translated configuration
123    * is equal to the corresponding source values. If not, that means that the
124    * source value has changed after the translation was added. In this case -
125    * again - the translation of this element must be disabled if the translator
126    * does not have access to the source value of the non-translatable property.
127    * For example, if a formatted text element, whose source format was plain
128    * text when it was first translated, gets changed to the Full HTML format,
129    * simply changing the format of the translation would lead to an XSS
130    * vulnerability as the translated text, that was intended to be escaped,
131    * would now be displayed unescaped. Thus, if the translator does not have
132    * access to the Full HTML format, the translation for this particular element
133    * may not be updated at all (the textarea must be disabled). Only if access
134    * to the Full HTML format is granted, an explicit translation taking into
135    * account the updated source value(s) may be submitted.
136    *
137    * In the specific case of formatted text this logic is implemented by
138    * utilizing a form element of type 'text_format' and its #format and
139    * #allowed_formats properties. The access logic explained above is then
140    * handled by the 'text_format' element itself, specifically by
141    * filter_process_format(). In case such a rich element is not available for
142    * translation of complex data, similar access logic must be implemented
143    * manually.
144    *
145    * @param \Drupal\Core\Language\LanguageInterface $translation_language
146    *   The language to display the translation form for.
147    * @param mixed $source_config
148    *   The configuration value of the element in the source language.
149    * @param mixed $translation_config
150    *   The configuration value of the element in the language to translate to.
151    *
152    * @return array
153    *   Form API array to represent the form element.
154    *
155    * @see \Drupal\config_translation\FormElement\TextFormat
156    * @see filter_process_format()
157    */
158   protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
159     // Add basic properties that apply to all form elements.
160     return [
161       '#title' => $this->t('@label <span class="visually-hidden">(@source_language)</span>', [
162         // Labels originate from configuration schema and are translatable.
163         '@label' => $this->t($this->definition->getLabel()),
164         '@source_language' => $translation_language->getName(),
165       ]),
166       '#default_value' => $translation_config,
167       '#attributes' => ['lang' => $translation_language->getId()],
168     ];
169   }
170
171   /**
172    * {@inheritdoc}
173    */
174   public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) {
175     // Save the configuration values, if they are different from the source
176     // values in the base configuration. Otherwise remove the override.
177     if ($base_config->get($base_key) !== $config_values) {
178       $config_translation->set($base_key, $config_values);
179     }
180     else {
181       $config_translation->clear($base_key);
182     }
183   }
184
185 }