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