annotate core/modules/content_translation/content_translation.admin.inc @ 5:12f9dff5fda9 tip

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