3 namespace Drupal\content_translation\Controller;
5 use Drupal\content_translation\ContentTranslationManagerInterface;
6 use Drupal\Core\Cache\CacheableMetadata;
7 use Drupal\Core\Controller\ControllerBase;
8 use Drupal\Core\Entity\ContentEntityInterface;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\Routing\RouteMatchInterface;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
15 * Base class for entity translation controllers.
17 class ContentTranslationController extends ControllerBase {
20 * The content translation manager.
22 * @var \Drupal\content_translation\ContentTranslationManagerInterface
27 * Initializes a content translation controller.
29 * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
30 * A content translation manager instance.
32 public function __construct(ContentTranslationManagerInterface $manager) {
33 $this->manager = $manager;
39 public static function create(ContainerInterface $container) {
40 return new static($container->get('content_translation.manager'));
44 * Populates target values with the source values.
46 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
47 * The entity being translated.
48 * @param \Drupal\Core\Language\LanguageInterface $source
49 * The language to be used as source.
50 * @param \Drupal\Core\Language\LanguageInterface $target
51 * The language to be used as target.
53 public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) {
54 /* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */
55 $source_translation = $entity->getTranslation($source->getId());
56 $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray());
58 // Make sure we do not inherit the affected status from the source values.
59 if ($entity->getEntityType()->isRevisionable()) {
60 $target_translation->setRevisionTranslationAffected(NULL);
63 /** @var \Drupal\user\UserInterface $user */
64 $user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id());
65 $metadata = $this->manager->getTranslationMetadata($target_translation);
67 // Update the translation author to current user, as well the translation
69 $metadata->setAuthor($user);
70 $metadata->setCreatedTime(REQUEST_TIME);
74 * Builds the translations overview page.
76 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
78 * @param string $entity_type_id
79 * (optional) The entity type ID.
81 * Array of page elements to render.
83 public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL) {
84 /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
85 $entity = $route_match->getParameter($entity_type_id);
86 $account = $this->currentUser();
87 $handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
88 $manager = $this->manager;
89 $entity_type = $entity->getEntityType();
91 // Start collecting the cacheability metadata, starting with the entity and
92 // later merge in the access result cacheability metadata.
93 $cacheability = CacheableMetadata::createFromObject($entity);
95 $languages = $this->languageManager()->getLanguages();
96 $original = $entity->getUntranslated()->language()->getId();
97 $translations = $entity->getTranslationLanguages();
98 $field_ui = $this->moduleHandler()->moduleExists('field_ui') && $account->hasPermission('administer ' . $entity_type_id . ' fields');
101 $show_source_column = FALSE;
103 if ($this->languageManager()->isMultilingual()) {
104 // Determine whether the current entity is translatable.
105 $translatable = FALSE;
106 foreach ($this->entityManager->getFieldDefinitions($entity_type_id, $entity->bundle()) as $instance) {
107 if ($instance->isTranslatable()) {
108 $translatable = TRUE;
113 // Show source-language column if there are non-original source langcodes.
114 $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use ($entity, $original, $manager) {
115 $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource();
116 return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED;
118 $show_source_column = !empty($additional_source_langcodes);
120 foreach ($languages as $language) {
121 $language_name = $language->getName();
122 $langcode = $language->getId();
125 "entity.$entity_type_id.content_translation_add",
127 'source' => $original,
128 'target' => $language->getId(),
129 $entity_type_id => $entity->id(),
132 'language' => $language,
136 "entity.$entity_type_id.content_translation_edit",
138 'language' => $language->getId(),
139 $entity_type_id => $entity->id(),
142 'language' => $language,
145 $delete_url = new Url(
146 "entity.$entity_type_id.content_translation_delete",
148 'language' => $language->getId(),
149 $entity_type_id => $entity->id(),
152 'language' => $language,
157 '#type' => 'operations',
162 $links = &$operations['data']['#links'];
163 if (array_key_exists($langcode, $translations)) {
164 // Existing translation in the translation set: display status.
165 $translation = $entity->getTranslation($langcode);
166 $metadata = $manager->getTranslationMetadata($translation);
167 $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED;
168 $is_original = $langcode == $original;
169 $label = $entity->getTranslation($langcode)->label();
170 $link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : ['url' => $entity->urlInfo()];
171 if (!empty($link['url'])) {
172 $link['url']->setOption('language', $language);
173 $row_title = $this->l($label, $link['url']);
176 if (empty($link['url'])) {
177 $row_title = $is_original ? $label : $this->t('n/a');
180 // If the user is allowed to edit the entity we point the edit link to
181 // the entity form, otherwise if we are not dealing with the original
182 // language we point the link to the translation form.
183 $update_access = $entity->access('update', NULL, TRUE);
184 $translation_access = $handler->getTranslationAccess($entity, 'update');
185 $cacheability = $cacheability
186 ->merge(CacheableMetadata::createFromObject($update_access))
187 ->merge(CacheableMetadata::createFromObject($translation_access));
188 if ($update_access->isAllowed() && $entity_type->hasLinkTemplate('edit-form')) {
189 $links['edit']['url'] = $entity->urlInfo('edit-form');
190 $links['edit']['language'] = $language;
192 elseif (!$is_original && $translation_access->isAllowed()) {
193 $links['edit']['url'] = $edit_url;
196 if (isset($links['edit'])) {
197 $links['edit']['title'] = $this->t('Edit');
199 $status = ['data' => [
200 '#type' => 'inline_template',
201 '#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
203 'status' => $metadata->isPublished(),
204 'outdated' => $metadata->isOutdated(),
209 $language_name = $this->t('<strong>@language_name (Original language)</strong>', ['@language_name' => $language_name]);
210 $source_name = $this->t('n/a');
213 $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
214 $delete_access = $entity->access('delete', NULL, TRUE);
215 $translation_access = $handler->getTranslationAccess($entity, 'delete');
216 $cacheability = $cacheability
217 ->merge(CacheableMetadata::createFromObject($delete_access))
218 ->merge(CacheableMetadata::createFromObject($translation_access));
219 if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) {
221 'title' => $this->t('Delete'),
222 'url' => $entity->urlInfo('delete-form'),
223 'language' => $language,
226 elseif ($translation_access->isAllowed()) {
228 'title' => $this->t('Delete'),
229 'url' => $delete_url,
235 // No such translation in the set yet: help user to create it.
236 $row_title = $source_name = $this->t('n/a');
237 $source = $entity->language()->getId();
239 $create_translation_access = $handler->getTranslationAccess($entity, 'create');
240 $cacheability = $cacheability
241 ->merge(CacheableMetadata::createFromObject($create_translation_access));
242 if ($source != $langcode && $create_translation_access->isAllowed()) {
245 'title' => $this->t('Add'),
250 $url = new Url('language.content_settings_page');
252 // Link directly to the fields tab to make it easier to find the
253 // setting to enable translation on fields.
254 $links['nofields'] = [
255 'title' => $this->t('No translatable fields'),
261 $status = $this->t('Not translated');
263 if ($show_source_column) {
273 $rows[] = [$language_name, $row_title, $status, $operations];
277 if ($show_source_column) {
279 $this->t('Language'),
280 $this->t('Translation'),
281 $this->t('Source language'),
283 $this->t('Operations'),
288 $this->t('Language'),
289 $this->t('Translation'),
291 $this->t('Operations'),
295 $build['#title'] = $this->t('Translations of %label', ['%label' => $entity->label()]);
297 // Add metadata to the build render array to let other modules know about
298 // which entity this is.
299 $build['#entity'] = $entity;
301 ->addCacheTags($entity->getCacheTags())
304 $build['content_translation_overview'] = [
306 '#header' => $header,
314 * Builds an add translation page.
316 * @param \Drupal\Core\Language\LanguageInterface $source
317 * The language of the values being translated. Defaults to the entity
319 * @param \Drupal\Core\Language\LanguageInterface $target
320 * The language of the translated values. Defaults to the current content
322 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
323 * The route match object from which to extract the entity type.
324 * @param string $entity_type_id
325 * (optional) The entity type ID.
328 * A processed form array ready to be rendered.
330 public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
331 $entity = $route_match->getParameter($entity_type_id);
333 // @todo Exploit the upcoming hook_entity_prepare() when available.
334 // See https://www.drupal.org/node/1810394.
335 $this->prepareTranslation($entity, $source, $target);
337 // @todo Provide a way to figure out the default form operation. Maybe like
338 // $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
339 // See https://www.drupal.org/node/2006348.
341 // Use the add form handler, if available, otherwise default.
342 $operation = $entity->getEntityType()->hasHandlerClass('form', 'add') ? 'add' : 'default';
344 $form_state_additions = [];
345 $form_state_additions['langcode'] = $target->getId();
346 $form_state_additions['content_translation']['source'] = $source;
347 $form_state_additions['content_translation']['target'] = $target;
348 $form_state_additions['content_translation']['translation_form'] = !$entity->access('update');
350 return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);
354 * Builds the edit translation page.
356 * @param \Drupal\Core\Language\LanguageInterface $language
357 * The language of the translated values. Defaults to the current content
359 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
360 * The route match object from which to extract the entity type.
361 * @param string $entity_type_id
362 * (optional) The entity type ID.
365 * A processed form array ready to be rendered.
367 public function edit(LanguageInterface $language, RouteMatchInterface $route_match, $entity_type_id = NULL) {
368 $entity = $route_match->getParameter($entity_type_id);
370 // @todo Provide a way to figure out the default form operation. Maybe like
371 // $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
372 // See https://www.drupal.org/node/2006348.
374 // Use the edit form handler, if available, otherwise default.
375 $operation = $entity->getEntityType()->hasHandlerClass('form', 'edit') ? 'edit' : 'default';
377 $form_state_additions = [];
378 $form_state_additions['langcode'] = $language->getId();
379 $form_state_additions['content_translation']['translation_form'] = TRUE;
381 return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);