Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / content_translation / content_translation.admin.inc
1 <?php
2
3 /**
4  * @file
5  * The content translation administration forms.
6  */
7
8 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
9 use Drupal\Core\Entity\ContentEntityTypeInterface;
10 use Drupal\Core\Entity\EntityTypeInterface;
11 use Drupal\Core\Field\FieldDefinitionInterface;
12 use Drupal\Core\Field\FieldStorageDefinitionInterface;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Language\LanguageInterface;
15 use Drupal\Core\Render\Element;
16
17 /**
18  * Returns a form element to configure field synchronization.
19  *
20  * @param \Drupal\Core\Field\FieldDefinitionInterface $field
21  *   A field definition object.
22  * @param string $element_name
23  *   (optional) The element name, which is added to drupalSettings so that
24  *   javascript can manipulate the form element.
25  *
26  * @return array
27  *   A form element to configure field synchronization.
28  */
29 function content_translation_field_sync_widget(FieldDefinitionInterface $field, $element_name = 'third_party_settings[content_translation][translation_sync]') {
30   // No way to store field sync information on this field.
31   if (!($field instanceof ThirdPartySettingsInterface)) {
32     return [];
33   }
34
35   $element = [];
36   $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
37   $column_groups = $definition['column_groups'];
38   if (!empty($column_groups) && count($column_groups) > 1) {
39     $options = [];
40     $default = [];
41     $require_all_groups_for_translation = [];
42
43     foreach ($column_groups as $group => $info) {
44       $options[$group] = $info['label'];
45       $default[$group] = !empty($info['translatable']) ? $group : FALSE;
46       if (!empty($info['require_all_groups_for_translation'])) {
47         $require_all_groups_for_translation[] = $group;
48       }
49     }
50
51     $default = $field->getThirdPartySetting('content_translation', 'translation_sync', $default);
52
53     $element = [
54       '#type' => 'checkboxes',
55       '#title' => t('Translatable elements'),
56       '#options' => $options,
57       '#default_value' => $default,
58     ];
59
60     if ($require_all_groups_for_translation) {
61       // The actual checkboxes are sometimes rendered separately and the parent
62       // element is ignored. Attach to the first option to ensure that this
63       // does not get lost.
64       $element[key($options)]['#attached']['drupalSettings']['contentTranslationDependentOptions'] = [
65         'dependent_selectors' => [
66           $element_name => $require_all_groups_for_translation
67         ],
68       ];
69       $element[key($options)]['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
70     }
71   }
72
73   return $element;
74 }
75
76 /**
77  * (proxied) Implements hook_form_FORM_ID_alter().
78  */
79 function _content_translation_form_language_content_settings_form_alter(array &$form, FormStateInterface $form_state) {
80   // Inject into the content language settings the translation settings if the
81   // user has the required permission.
82   if (!\Drupal::currentUser()->hasPermission('administer content translation')) {
83     return;
84   }
85
86   $content_translation_manager = \Drupal::service('content_translation.manager');
87   $default = $form['entity_types']['#default_value'];
88   foreach ($default as $entity_type_id => $enabled) {
89     $default[$entity_type_id] = $enabled || $content_translation_manager->isEnabled($entity_type_id) ? $entity_type_id : FALSE;
90   }
91   $form['entity_types']['#default_value'] = $default;
92
93   $form['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
94
95   $entity_manager = Drupal::entityManager();
96   $bundle_info_service = \Drupal::service('entity_type.bundle.info');
97   foreach ($form['#labels'] as $entity_type_id => $label) {
98     $entity_type = $entity_manager->getDefinition($entity_type_id);
99     $storage_definitions = $entity_type instanceof ContentEntityTypeInterface ? $entity_manager->getFieldStorageDefinitions($entity_type_id) : [];
100
101     $entity_type_translatable = $content_translation_manager->isSupported($entity_type_id);
102     foreach ($bundle_info_service->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
103       // Here we do not want the widget to be altered and hold also the "Enable
104       // translation" checkbox, which would be redundant. Hence we add this key
105       // to be able to skip alterations. Alter the title and display the message
106       // about UI integration.
107       $form['settings'][$entity_type_id][$bundle]['settings']['language']['#content_translation_skip_alter'] = TRUE;
108       if (!$entity_type_translatable) {
109         $form['settings'][$entity_type_id]['#title'] = t('@label (Translation is not supported).', ['@label' => $entity_type->getLabel()]);
110         continue;
111       }
112
113       $fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
114       if ($fields) {
115         foreach ($fields as $field_name => $definition) {
116           if ($definition->isComputed() || (!empty($storage_definitions[$field_name]) && _content_translation_is_field_translatability_configurable($entity_type, $storage_definitions[$field_name]))) {
117             $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = [
118               '#label' => $definition->getLabel(),
119               '#type' => 'checkbox',
120               '#default_value' => $definition->isTranslatable(),
121             ];
122             // Display the column translatability configuration widget.
123             $column_element = content_translation_field_sync_widget($definition, "settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]");
124             if ($column_element) {
125               $form['settings'][$entity_type_id][$bundle]['columns'][$field_name] = $column_element;
126             }
127           }
128         }
129         if (!empty($form['settings'][$entity_type_id][$bundle]['fields'])) {
130           // Only show the checkbox to enable translation if the bundles in the
131           // entity might have fields and if there are fields to translate.
132           $form['settings'][$entity_type_id][$bundle]['translatable'] = [
133             '#type' => 'checkbox',
134             '#default_value' => $content_translation_manager->isEnabled($entity_type_id, $bundle),
135           ];
136         }
137       }
138     }
139   }
140
141   $form['#validate'][] = 'content_translation_form_language_content_settings_validate';
142   $form['#submit'][] = 'content_translation_form_language_content_settings_submit';
143 }
144 /**
145  * Checks whether translatability should be configurable for a field.
146  *
147  * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
148  *   The entity type definition.
149  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
150  *   The field storage definition.
151  *
152  * @return bool
153  *   TRUE if field translatability can be configured, FALSE otherwise.
154  *
155  * @internal
156  */
157 function _content_translation_is_field_translatability_configurable(EntityTypeInterface $entity_type, FieldStorageDefinitionInterface $definition) {
158   // Allow to configure only fields supporting multilingual storage. We skip our
159   // own fields as they are always translatable. Additionally we skip a set of
160   // well-known fields implementing entity system business logic.
161   return
162     $definition->isTranslatable() &&
163     $definition->getProvider() != 'content_translation' &&
164     !in_array($definition->getName(), [$entity_type->getKey('langcode'), $entity_type->getKey('default_langcode'), 'revision_translation_affected']);
165 }
166
167 /**
168  * (proxied) Implements hook_preprocess_HOOK();
169  */
170 function _content_translation_preprocess_language_content_settings_table(&$variables) {
171   // Alter the 'build' variable injecting the translation settings if the user
172   // has the required permission.
173   if (!\Drupal::currentUser()->hasPermission('administer content translation')) {
174     return;
175   }
176
177   $element = $variables['element'];
178   $build = &$variables['build'];
179
180   array_unshift($build['#header'], ['data' => t('Translatable'), 'class' => ['translatable']]);
181   $rows = [];
182
183   foreach (Element::children($element) as $bundle) {
184     $field_names = !empty($element[$bundle]['fields']) ? Element::children($element[$bundle]['fields']) : [];
185     if (!empty($element[$bundle]['translatable'])) {
186       $checkbox_id = $element[$bundle]['translatable']['#id'];
187     }
188     $rows[$bundle] = $build['#rows'][$bundle];
189
190     if (!empty($element[$bundle]['translatable'])) {
191       $translatable = [
192         'data' => $element[$bundle]['translatable'],
193         'class' => ['translatable'],
194       ];
195       array_unshift($rows[$bundle]['data'], $translatable);
196
197       $rows[$bundle]['data'][1]['data']['#prefix'] = '<label for="' . $checkbox_id . '">';
198     }
199     else {
200       $translatable = [
201         'data' => t('N/A'),
202         'class' => ['untranslatable'],
203       ];
204       array_unshift($rows[$bundle]['data'], $translatable);
205     }
206
207     foreach ($field_names as $field_name) {
208       $field_element = &$element[$bundle]['fields'][$field_name];
209       $rows[] = [
210         'data' => [
211           [
212             'data' => drupal_render($field_element),
213             'class' => ['translatable'],
214           ],
215           [
216             'data' => [
217               '#prefix' => '<label for="' . $field_element['#id'] . '">',
218               '#suffix' => '</label>',
219               'bundle' => [
220                 '#prefix' => '<span class="visually-hidden">',
221                 '#suffix' => '</span> ',
222                 '#plain_text' => $element[$bundle]['settings']['#label'],
223               ],
224               'field' => [
225                 '#plain_text' => $field_element['#label'],
226               ],
227             ],
228             'class' => ['field'],
229           ],
230           [
231             'data' => '',
232             'class' => ['operations'],
233           ],
234         ],
235         'class' => ['field-settings'],
236       ];
237
238       if (!empty($element[$bundle]['columns'][$field_name])) {
239         $column_element = &$element[$bundle]['columns'][$field_name];
240         foreach (Element::children($column_element) as $key) {
241           $column_label = $column_element[$key]['#title'];
242           unset($column_element[$key]['#title']);
243           $rows[] = [
244             'data' => [
245               [
246                 'data' => drupal_render($column_element[$key]),
247                 'class' => ['translatable'],
248               ],
249               [
250                 'data' => [
251                   '#prefix' => '<label for="' . $column_element[$key]['#id'] . '">',
252                   '#suffix' => '</label>',
253                   'bundle' => [
254                     '#prefix' => '<span class="visually-hidden">',
255                     '#suffix' => '</span> ',
256                     '#plain_text' => $element[$bundle]['settings']['#label'],
257                   ],
258                   'field' => [
259                     '#prefix' => '<span class="visually-hidden">',
260                     '#suffix' => '</span> ',
261                     '#plain_text' => $field_element['#label'],
262                   ],
263                   'columns' => [
264                     '#plain_text' => $column_label,
265                   ],
266                 ],
267                 'class' => ['column'],
268               ],
269               [
270                 'data' => '',
271                 'class' => ['operations'],
272               ],
273             ],
274             'class' => ['column-settings'],
275           ];
276         }
277       }
278     }
279   }
280
281   $build['#rows'] = $rows;
282 }
283
284 /**
285  * Form validation handler for content_translation_admin_settings_form().
286  *
287  * @see content_translation_admin_settings_form_submit()
288  */
289 function content_translation_form_language_content_settings_validate(array $form, FormStateInterface $form_state) {
290   $settings = &$form_state->getValue('settings');
291   foreach ($settings as $entity_type => $entity_settings) {
292     foreach ($entity_settings as $bundle => $bundle_settings) {
293       if (!empty($bundle_settings['translatable'])) {
294         $name = "settings][$entity_type][$bundle][translatable";
295
296         $translatable_fields = isset($settings[$entity_type][$bundle]['fields']) ? array_filter($settings[$entity_type][$bundle]['fields']) : FALSE;
297         if (empty($translatable_fields)) {
298           $t_args = ['%bundle' => $form['settings'][$entity_type][$bundle]['settings']['#label']];
299           $form_state->setErrorByName($name, t('At least one field needs to be translatable to enable %bundle for translation.', $t_args));
300         }
301
302         $values = $bundle_settings['settings']['language'];
303         if (empty($values['language_alterable']) && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
304           foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $language) {
305             $locked_languages[] = $language->getName();
306           }
307           $form_state->setErrorByName($name, t('Translation is not supported if language is always one of: @locked_languages', ['@locked_languages' => implode(', ', $locked_languages)]));
308         }
309       }
310     }
311   }
312 }
313
314 /**
315  * Form submission handler for content_translation_admin_settings_form().
316  *
317  * @see content_translation_admin_settings_form_validate()
318  */
319 function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
320   $entity_types = $form_state->getValue('entity_types');
321   $settings = &$form_state->getValue('settings');
322
323   // If an entity type is not translatable all its bundles and fields must be
324   // marked as non-translatable. Similarly, if a bundle is made non-translatable
325   // all of its fields will be not translatable.
326   foreach ($settings as $entity_type_id => &$entity_settings) {
327     foreach ($entity_settings as $bundle => &$bundle_settings) {
328       $fields = \Drupal::entityManager()->getFieldDefinitions($entity_type_id, $bundle);
329       if (!empty($bundle_settings['translatable'])) {
330         $bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type_id];
331       }
332       if (!empty($bundle_settings['fields'])) {
333         foreach ($bundle_settings['fields'] as $field_name => $translatable) {
334           $translatable = $translatable && $bundle_settings['translatable'];
335           // If we have column settings and no column is translatable, no point
336           // in making the field translatable.
337           if (isset($bundle_settings['columns'][$field_name]) && !array_filter($bundle_settings['columns'][$field_name])) {
338             $translatable = FALSE;
339           }
340           $field_config = $fields[$field_name]->getConfig($bundle);
341           if ($field_config->isTranslatable() != $translatable) {
342             $field_config
343               ->setTranslatable($translatable)
344               ->save();
345           }
346         }
347       }
348       if (isset($bundle_settings['translatable'])) {
349         // Store whether a bundle has translation enabled or not.
350         \Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
351
352         // Save translation_sync settings.
353         if (!empty($bundle_settings['columns'])) {
354           foreach ($bundle_settings['columns'] as $field_name => $column_settings) {
355             $field_config = $fields[$field_name]->getConfig($bundle);
356             if ($field_config->isTranslatable()) {
357               $field_config->setThirdPartySetting('content_translation', 'translation_sync', $column_settings);
358             }
359             // If the field does not have translatable enabled we need to reset
360             // the sync settings to their defaults.
361             else {
362               $field_config->unsetThirdPartySetting('content_translation', 'translation_sync');
363             }
364             $field_config->save();
365           }
366         }
367       }
368     }
369   }
370   // Ensure entity and menu router information are correctly rebuilt.
371   \Drupal::entityManager()->clearCachedDefinitions();
372   \Drupal::service('router.builder')->setRebuildNeeded();
373
374 }