content_translation.admin.inc 17.4 KB
Newer Older
1 2 3 4
<?php

/**
 * @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 25 26
 * @param string $element_name
 *   (optional) The element name, which is added to drupalSettings so that
 *   javascript can manipulate the form element.
27 28 29 30
 *
 * @return array
 *   A form element to configure field synchronization.
 */
31
function content_translation_field_sync_widget(FieldDefinitionInterface $field, $element_name = 'third_party_settings[content_translation][translation_sync]') {
32 33
  // No way to store field sync information on this field.
  if (!($field instanceof ThirdPartySettingsInterface)) {
34
    return [];
35
  }
36

37
  $element = [];
38 39
  $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
  $column_groups = $definition['column_groups'];
40
  if (!empty($column_groups) && count($column_groups) > 1) {
41 42 43
    $options = [];
    $default = [];
    $require_all_groups_for_translation = [];
44

45
    foreach ($column_groups as $group => $info) {
46 47
      $options[$group] = $info['label'];
      $default[$group] = !empty($info['translatable']) ? $group : FALSE;
48 49 50
      if (!empty($info['require_all_groups_for_translation'])) {
        $require_all_groups_for_translation[] = $group;
      }
51 52
    }

53
    $default = $field->getThirdPartySetting('content_translation', 'translation_sync', $default);
54

55
    $element = [
56 57 58
      '#type' => 'checkboxes',
      '#title' => t('Translatable elements'),
      '#options' => $options,
59
      '#default_value' => $default,
60
    ];
61 62 63 64 65 66 67

    if ($require_all_groups_for_translation) {
      // The actual checkboxes are sometimes rendered separately and the parent
      // element is ignored. Attach to the first option to ensure that this
      // does not get lost.
      $element[key($options)]['#attached']['drupalSettings']['contentTranslationDependentOptions'] = [
        'dependent_selectors' => [
68
          $element_name => $require_all_groups_for_translation,
69 70 71 72
        ],
      ];
      $element[key($options)]['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
    }
73 74 75 76 77 78 79
  }

  return $element;
}

/**
 * (proxied) Implements hook_form_FORM_ID_alter().
80
 */
81
function _content_translation_form_language_content_settings_form_alter(array &$form, FormStateInterface $form_state) {
82 83
  // Inject into the content language settings the translation settings if the
  // user has the required permission.
84
  if (!\Drupal::currentUser()->hasPermission('administer content translation')) {
85 86 87
    return;
  }

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 95
  }
  $form['entity_types']['#default_value'] = $default;

96
  $form['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
97

98 99 100
  $entity_type_manager = \Drupal::entityTypeManager();
  /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
  $entity_field_manager = \Drupal::service('entity_field.manager');
101
  $bundle_info_service = \Drupal::service('entity_type.bundle.info');
102
  foreach ($form['#labels'] as $entity_type_id => $label) {
103 104
    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
    $storage_definitions = $entity_type instanceof ContentEntityTypeInterface ? $entity_field_manager->getFieldStorageDefinitions($entity_type_id) : [];
105

106
    $entity_type_translatable = $content_translation_manager->isSupported($entity_type_id);
107
    foreach ($bundle_info_service->getBundleInfo($entity_type_id) as $bundle => $bundle_info) {
108 109
      // Here we do not want the widget to be altered and hold also the "Enable
      // translation" checkbox, which would be redundant. Hence we add this key
110 111
      // to be able to skip alterations. Alter the title and display the message
      // about UI integration.
112
      $form['settings'][$entity_type_id][$bundle]['settings']['language']['#content_translation_skip_alter'] = TRUE;
113
      if (!$entity_type_translatable) {
114
        $form['settings'][$entity_type_id]['#title'] = t('@label (Translation is not supported).', ['@label' => $entity_type->getLabel()]);
115 116
        continue;
      }
117

118 119 120
      // Displayed the "shared fields widgets" toggle.
      if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
        $settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
121
        $force_hidden = ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $bundle);
122 123 124
        $form['settings'][$entity_type_id][$bundle]['settings']['content_translation']['untranslatable_fields_hide'] = [
          '#type' => 'checkbox',
          '#title' => t('Hide non translatable fields on translation forms'),
125 126 127
          '#default_value' => $force_hidden || !empty($settings['untranslatable_fields_hide']),
          '#disabled' => $force_hidden,
          '#description' => $force_hidden ? t('Moderated content requires non-translatable fields to be edited in the original language form.') : '',
128 129 130 131 132 133 134 135 136 137
          '#states' => [
            'visible' => [
              ':input[name="settings[' . $entity_type_id . '][' . $bundle . '][translatable]"]' => [
                'checked' => TRUE,
              ],
            ],
          ],
        ];
      }

138
      $fields = $entity_field_manager->getFieldDefinitions($entity_type_id, $bundle);
139 140
      if ($fields) {
        foreach ($fields as $field_name => $definition) {
141
          if ($definition->isComputed() || (!empty($storage_definitions[$field_name]) && _content_translation_is_field_translatability_configurable($entity_type, $storage_definitions[$field_name]))) {
142
            $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = [
143 144 145
              '#label' => $definition->getLabel(),
              '#type' => 'checkbox',
              '#default_value' => $definition->isTranslatable(),
146
            ];
147
            // Display the column translatability configuration widget.
148
            $column_element = content_translation_field_sync_widget($definition, "settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]");
149 150 151 152 153 154 155 156
            if ($column_element) {
              $form['settings'][$entity_type_id][$bundle]['columns'][$field_name] = $column_element;
            }
          }
        }
        if (!empty($form['settings'][$entity_type_id][$bundle]['fields'])) {
          // Only show the checkbox to enable translation if the bundles in the
          // entity might have fields and if there are fields to translate.
157
          $form['settings'][$entity_type_id][$bundle]['translatable'] = [
158
            '#type' => 'checkbox',
159
            '#default_value' => $content_translation_manager->isEnabled($entity_type_id, $bundle),
160
          ];
161
        }
162 163 164
      }
    }
  }
165

166 167
  $form['#validate'][] = 'content_translation_form_language_content_settings_validate';
  $form['#submit'][] = 'content_translation_form_language_content_settings_submit';
168
}
169

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
/**
 * Checks whether translatability should be configurable for a field.
 *
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
 *   The entity type definition.
 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
 *   The field storage definition.
 *
 * @return bool
 *   TRUE if field translatability can be configured, FALSE otherwise.
 *
 * @internal
 */
function _content_translation_is_field_translatability_configurable(EntityTypeInterface $entity_type, FieldStorageDefinitionInterface $definition) {
  // Allow to configure only fields supporting multilingual storage. We skip our
  // own fields as they are always translatable. Additionally we skip a set of
  // well-known fields implementing entity system business logic.
187
  return $definition->isTranslatable() &&
188 189 190
    $definition->getProvider() != 'content_translation' &&
    !in_array($definition->getName(), [$entity_type->getKey('langcode'), $entity_type->getKey('default_langcode'), 'revision_translation_affected']);
}
191

192 193 194
/**
 * (proxied) Implements hook_preprocess_HOOK();
 */
195
function _content_translation_preprocess_language_content_settings_table(&$variables) {
196 197
  // Alter the 'build' variable injecting the translation settings if the user
  // has the required permission.
198
  if (!\Drupal::currentUser()->hasPermission('administer content translation')) {
199 200 201 202 203 204
    return;
  }

  $element = $variables['element'];
  $build = &$variables['build'];

205 206
  array_unshift($build['#header'], ['data' => t('Translatable'), 'class' => ['translatable']]);
  $rows = [];
207

208
  foreach (Element::children($element) as $bundle) {
209
    $field_names = !empty($element[$bundle]['fields']) ? Element::children($element[$bundle]['fields']) : [];
210 211 212
    if (!empty($element[$bundle]['translatable'])) {
      $checkbox_id = $element[$bundle]['translatable']['#id'];
    }
213 214
    $rows[$bundle] = $build['#rows'][$bundle];

215
    if (!empty($element[$bundle]['translatable'])) {
216
      $translatable = [
217
        'data' => $element[$bundle]['translatable'],
218 219
        'class' => ['translatable'],
      ];
220
      array_unshift($rows[$bundle]['data'], $translatable);
221

222 223 224
      $rows[$bundle]['data'][1]['data']['#prefix'] = '<label for="' . $checkbox_id . '">';
    }
    else {
225
      $translatable = [
226
        'data' => t('N/A'),
227 228
        'class' => ['untranslatable'],
      ];
229 230
      array_unshift($rows[$bundle]['data'], $translatable);
    }
231 232 233

    foreach ($field_names as $field_name) {
      $field_element = &$element[$bundle]['fields'][$field_name];
234 235 236
      $rows[] = [
        'data' => [
          [
237
            'data' => \Drupal::service('renderer')->render($field_element),
238 239 240 241
            'class' => ['translatable'],
          ],
          [
            'data' => [
242 243
              '#prefix' => '<label for="' . $field_element['#id'] . '">',
              '#suffix' => '</label>',
244
              'bundle' => [
245
                '#prefix' => '<span class="visually-hidden">',
246
                '#suffix' => '</span> ',
247
                '#plain_text' => $element[$bundle]['settings']['#label'],
248 249
              ],
              'field' => [
250
                '#plain_text' => $field_element['#label'],
251 252 253 254 255
              ],
            ],
            'class' => ['field'],
          ],
          [
256
            'data' => '',
257 258 259
            'class' => ['operations'],
          ],
        ],
260
        '#field_name' => $field_name,
261 262
        'class' => ['field-settings'],
      ];
263 264 265

      if (!empty($element[$bundle]['columns'][$field_name])) {
        $column_element = &$element[$bundle]['columns'][$field_name];
266
        foreach (Element::children($column_element) as $key) {
267 268
          $column_label = $column_element[$key]['#title'];
          unset($column_element[$key]['#title']);
269 270 271
          $rows[] = [
            'data' => [
              [
272
                'data' => \Drupal::service('renderer')->render($column_element[$key]),
273 274 275 276
                'class' => ['translatable'],
              ],
              [
                'data' => [
277 278
                  '#prefix' => '<label for="' . $column_element[$key]['#id'] . '">',
                  '#suffix' => '</label>',
279
                  'bundle' => [
280
                    '#prefix' => '<span class="visually-hidden">',
281
                    '#suffix' => '</span> ',
282
                    '#plain_text' => $element[$bundle]['settings']['#label'],
283 284
                  ],
                  'field' => [
285
                    '#prefix' => '<span class="visually-hidden">',
286
                    '#suffix' => '</span> ',
287
                    '#plain_text' => $field_element['#label'],
288 289
                  ],
                  'columns' => [
290
                    '#plain_text' => $column_label,
291 292 293 294 295
                  ],
                ],
                'class' => ['column'],
              ],
              [
296
                'data' => '',
297 298 299 300 301
                'class' => ['operations'],
              ],
            ],
            'class' => ['column-settings'],
          ];
302 303 304 305 306 307 308 309
        }
      }
    }
  }

  $build['#rows'] = $rows;
}

310
/**
311
 * Form validation handler for content_translation_admin_settings_form().
312
 *
313
 * @see content_translation_admin_settings_form_submit()
314
 */
315
function content_translation_form_language_content_settings_validate(array $form, FormStateInterface $form_state) {
316
  $settings = &$form_state->getValue('settings');
317 318 319 320 321 322 323
  foreach ($settings as $entity_type => $entity_settings) {
    foreach ($entity_settings as $bundle => $bundle_settings) {
      if (!empty($bundle_settings['translatable'])) {
        $name = "settings][$entity_type][$bundle][translatable";

        $translatable_fields = isset($settings[$entity_type][$bundle]['fields']) ? array_filter($settings[$entity_type][$bundle]['fields']) : FALSE;
        if (empty($translatable_fields)) {
324
          $t_args = ['%bundle' => $form['settings'][$entity_type][$bundle]['settings']['#label']];
325
          $form_state->setErrorByName($name, t('At least one field needs to be translatable to enable %bundle for translation.', $t_args));
326 327 328
        }

        $values = $bundle_settings['settings']['language'];
329
        if (empty($values['language_alterable']) && \Drupal::languageManager()->isLanguageLocked($values['langcode'])) {
330
          foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_LOCKED) as $language) {
331
            $locked_languages[] = $language->getName();
332
          }
333
          $form_state->setErrorByName($name, t('Translation is not supported if language is always one of: @locked_languages', ['@locked_languages' => implode(', ', $locked_languages)]));
334 335 336 337 338 339 340
        }
      }
    }
  }
}

/**
341
 * Form submission handler for content_translation_admin_settings_form().
342
 *
343
 * @see content_translation_admin_settings_form_validate()
344
 */
345
function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
346 347
  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
  $content_translation_manager = \Drupal::service('content_translation.manager');
348 349
  $entity_types = $form_state->getValue('entity_types');
  $settings = &$form_state->getValue('settings');
350 351 352 353

  // If an entity type is not translatable all its bundles and fields must be
  // marked as non-translatable. Similarly, if a bundle is made non-translatable
  // all of its fields will be not translatable.
354 355
  foreach ($settings as $entity_type_id => &$entity_settings) {
    foreach ($entity_settings as $bundle => &$bundle_settings) {
356
      $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle);
357
      if (!empty($bundle_settings['translatable'])) {
358
        $bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type_id];
359
      }
360 361
      if (!empty($bundle_settings['fields'])) {
        foreach ($bundle_settings['fields'] as $field_name => $translatable) {
362
          $translatable = $translatable && $bundle_settings['translatable'];
363 364 365
          // If we have column settings and no column is translatable, no point
          // in making the field translatable.
          if (isset($bundle_settings['columns'][$field_name]) && !array_filter($bundle_settings['columns'][$field_name])) {
366
            $translatable = FALSE;
367
          }
368 369 370 371 372
          $field_config = $fields[$field_name]->getConfig($bundle);
          if ($field_config->isTranslatable() != $translatable) {
            $field_config
              ->setTranslatable($translatable)
              ->save();
373
          }
374 375
        }
      }
376 377
      if (isset($bundle_settings['translatable'])) {
        // Store whether a bundle has translation enabled or not.
378 379 380 381 382 383
        $content_translation_manager->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);

        // Store any other bundle settings.
        if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
          $content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, $bundle_settings['settings']['content_translation']);
        }
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

        // Save translation_sync settings.
        if (!empty($bundle_settings['columns'])) {
          foreach ($bundle_settings['columns'] as $field_name => $column_settings) {
            $field_config = $fields[$field_name]->getConfig($bundle);
            if ($field_config->isTranslatable()) {
              $field_config->setThirdPartySetting('content_translation', 'translation_sync', $column_settings);
            }
            // If the field does not have translatable enabled we need to reset
            // the sync settings to their defaults.
            else {
              $field_config->unsetThirdPartySetting('content_translation', 'translation_sync');
            }
            $field_config->save();
          }
        }
      }
401 402
    }
  }
403

404
  // Ensure entity and menu router information are correctly rebuilt.
405
  \Drupal::entityTypeManager()->clearCachedDefinitions();
406
  \Drupal::service('router.builder')->setRebuildNeeded();
407
}