Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / modules / content_translation / src / Controller / ContentTranslationController.php
1 <?php
2
3 namespace Drupal\content_translation\Controller;
4
5 use Drupal\content_translation\ContentTranslationManager;
6 use Drupal\content_translation\ContentTranslationManagerInterface;
7 use Drupal\Core\Cache\CacheableMetadata;
8 use Drupal\Core\Controller\ControllerBase;
9 use Drupal\Core\Entity\ContentEntityInterface;
10 use Drupal\Core\Language\LanguageInterface;
11 use Drupal\Core\Routing\RouteMatchInterface;
12 use Drupal\Core\Url;
13 use Symfony\Component\DependencyInjection\ContainerInterface;
14
15 /**
16  * Base class for entity translation controllers.
17  */
18 class ContentTranslationController extends ControllerBase {
19
20   /**
21    * The content translation manager.
22    *
23    * @var \Drupal\content_translation\ContentTranslationManagerInterface
24    */
25   protected $manager;
26
27   /**
28    * Initializes a content translation controller.
29    *
30    * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
31    *   A content translation manager instance.
32    */
33   public function __construct(ContentTranslationManagerInterface $manager) {
34     $this->manager = $manager;
35   }
36
37   /**
38    * {@inheritdoc}
39    */
40   public static function create(ContainerInterface $container) {
41     return new static($container->get('content_translation.manager'));
42   }
43
44   /**
45    * Populates target values with the source values.
46    *
47    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
48    *   The entity being translated.
49    * @param \Drupal\Core\Language\LanguageInterface $source
50    *   The language to be used as source.
51    * @param \Drupal\Core\Language\LanguageInterface $target
52    *   The language to be used as target.
53    */
54   public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) {
55     /* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */
56     $source_translation = $entity->getTranslation($source->getId());
57     $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray());
58
59     // Make sure we do not inherit the affected status from the source values.
60     if ($entity->getEntityType()->isRevisionable()) {
61       $target_translation->setRevisionTranslationAffected(NULL);
62     }
63
64     /** @var \Drupal\user\UserInterface $user */
65     $user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id());
66     $metadata = $this->manager->getTranslationMetadata($target_translation);
67
68     // Update the translation author to current user, as well the translation
69     // creation time.
70     $metadata->setAuthor($user);
71     $metadata->setCreatedTime(REQUEST_TIME);
72   }
73
74   /**
75    * Builds the translations overview page.
76    *
77    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
78    *   The route match.
79    * @param string $entity_type_id
80    *   (optional) The entity type ID.
81    * @return array
82    *   Array of page elements to render.
83    */
84   public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL) {
85     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
86     $entity = $route_match->getParameter($entity_type_id);
87     $account = $this->currentUser();
88     $handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
89     $manager = $this->manager;
90     $entity_type = $entity->getEntityType();
91     $use_latest_revisions = $entity_type->isRevisionable() && ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle());
92
93     // Start collecting the cacheability metadata, starting with the entity and
94     // later merge in the access result cacheability metadata.
95     $cacheability = CacheableMetadata::createFromObject($entity);
96
97     $languages = $this->languageManager()->getLanguages();
98     $original = $entity->getUntranslated()->language()->getId();
99     $translations = $entity->getTranslationLanguages();
100     $field_ui = $this->moduleHandler()->moduleExists('field_ui') && $account->hasPermission('administer ' . $entity_type_id . ' fields');
101
102     $rows = [];
103     $show_source_column = FALSE;
104     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
105     $storage = $this->entityTypeManager()->getStorage($entity_type_id);
106     $default_revision = $storage->load($entity->id());
107
108     if ($this->languageManager()->isMultilingual()) {
109       // Determine whether the current entity is translatable.
110       $translatable = FALSE;
111       foreach ($this->entityManager->getFieldDefinitions($entity_type_id, $entity->bundle()) as $instance) {
112         if ($instance->isTranslatable()) {
113           $translatable = TRUE;
114           break;
115         }
116       }
117
118       // Show source-language column if there are non-original source langcodes.
119       $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use ($entity, $original, $manager) {
120         $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource();
121         return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED;
122       });
123       $show_source_column = !empty($additional_source_langcodes);
124
125       foreach ($languages as $language) {
126         $language_name = $language->getName();
127         $langcode = $language->getId();
128
129         // If the entity type is revisionable, we may have pending revisions
130         // with translations not available yet in the default revision. Thus we
131         // need to load the latest translation-affecting revision for each
132         // language to be sure we are listing all available translations.
133         if ($use_latest_revisions) {
134           $entity = $default_revision;
135           $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
136           if ($latest_revision_id) {
137             /** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
138             $latest_revision = $storage->loadRevision($latest_revision_id);
139             // Make sure we do not list removed translations, i.e. translations
140             // that have been part of a default revision but no longer are.
141             if (!$latest_revision->wasDefaultRevision() || $default_revision->hasTranslation($langcode)) {
142               $entity = $latest_revision;
143             }
144           }
145           $translations = $entity->getTranslationLanguages();
146         }
147
148         $options = ['language' => $language];
149         $add_url = $entity->toUrl('drupal:content-translation-add', $options)
150           ->setRouteParameter('source', $original)
151           ->setRouteParameter('target', $language->getId());
152         $edit_url = $entity->toUrl('drupal:content-translation-edit', $options)
153           ->setRouteParameter('language', $language->getId());
154         $delete_url = $entity->toUrl('drupal:content-translation-delete', $options)
155           ->setRouteParameter('language', $language->getId());
156         $operations = [
157           'data' => [
158             '#type' => 'operations',
159             '#links' => [],
160           ],
161         ];
162
163         $links = &$operations['data']['#links'];
164         if (array_key_exists($langcode, $translations)) {
165           // Existing translation in the translation set: display status.
166           $translation = $entity->getTranslation($langcode);
167           $metadata = $manager->getTranslationMetadata($translation);
168           $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED;
169           $is_original = $langcode == $original;
170           $label = $entity->getTranslation($langcode)->label();
171           $link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : ['url' => $entity->urlInfo()];
172           if (!empty($link['url'])) {
173             $link['url']->setOption('language', $language);
174             $row_title = $this->l($label, $link['url']);
175           }
176
177           if (empty($link['url'])) {
178             $row_title = $is_original ? $label : $this->t('n/a');
179           }
180
181           // If the user is allowed to edit the entity we point the edit link to
182           // the entity form, otherwise if we are not dealing with the original
183           // language we point the link to the translation form.
184           $update_access = $entity->access('update', NULL, TRUE);
185           $translation_access = $handler->getTranslationAccess($entity, 'update');
186           $cacheability = $cacheability
187             ->merge(CacheableMetadata::createFromObject($update_access))
188             ->merge(CacheableMetadata::createFromObject($translation_access));
189           if ($update_access->isAllowed() && $entity_type->hasLinkTemplate('edit-form')) {
190             $links['edit']['url'] = $entity->urlInfo('edit-form');
191             $links['edit']['language'] = $language;
192           }
193           elseif (!$is_original && $translation_access->isAllowed()) {
194             $links['edit']['url'] = $edit_url;
195           }
196
197           if (isset($links['edit'])) {
198             $links['edit']['title'] = $this->t('Edit');
199           }
200           $status = [
201             'data' => [
202               '#type' => 'inline_template',
203               '#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
204               '#context' => [
205                 'status' => $metadata->isPublished(),
206                 'outdated' => $metadata->isOutdated(),
207               ],
208             ],
209           ];
210
211           if ($is_original) {
212             $language_name = $this->t('<strong>@language_name (Original language)</strong>', ['@language_name' => $language_name]);
213             $source_name = $this->t('n/a');
214           }
215           else {
216             /** @var \Drupal\Core\Access\AccessResultInterface $delete_route_access */
217             $delete_route_access = \Drupal::service('content_translation.delete_access')->checkAccess($translation);
218             $cacheability->addCacheableDependency($delete_route_access);
219
220             if ($delete_route_access->isAllowed()) {
221               $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
222               $delete_access = $entity->access('delete', NULL, TRUE);
223               $translation_access = $handler->getTranslationAccess($entity, 'delete');
224               $cacheability
225                 ->addCacheableDependency($delete_access)
226                 ->addCacheableDependency($translation_access);
227
228               if ($delete_access->isAllowed() && $entity_type->hasLinkTemplate('delete-form')) {
229                 $links['delete'] = [
230                   'title' => $this->t('Delete'),
231                   'url' => $entity->urlInfo('delete-form'),
232                   'language' => $language,
233                 ];
234               }
235               elseif ($translation_access->isAllowed()) {
236                 $links['delete'] = [
237                   'title' => $this->t('Delete'),
238                   'url' => $delete_url,
239                 ];
240               }
241             }
242             else {
243               $this->messenger()->addWarning($this->t('The "Delete translation" action is only available for published translations.'), FALSE);
244             }
245           }
246         }
247         else {
248           // No such translation in the set yet: help user to create it.
249           $row_title = $source_name = $this->t('n/a');
250           $source = $entity->language()->getId();
251
252           $create_translation_access = $handler->getTranslationAccess($entity, 'create');
253           $cacheability = $cacheability
254             ->merge(CacheableMetadata::createFromObject($create_translation_access));
255           if ($source != $langcode && $create_translation_access->isAllowed()) {
256             if ($translatable) {
257               $links['add'] = [
258                 'title' => $this->t('Add'),
259                 'url' => $add_url,
260               ];
261             }
262             elseif ($field_ui) {
263               $url = new Url('language.content_settings_page');
264
265               // Link directly to the fields tab to make it easier to find the
266               // setting to enable translation on fields.
267               $links['nofields'] = [
268                 'title' => $this->t('No translatable fields'),
269                 'url' => $url,
270               ];
271             }
272           }
273
274           $status = $this->t('Not translated');
275         }
276         if ($show_source_column) {
277           $rows[] = [
278             $language_name,
279             $row_title,
280             $source_name,
281             $status,
282             $operations,
283           ];
284         }
285         else {
286           $rows[] = [$language_name, $row_title, $status, $operations];
287         }
288       }
289     }
290     if ($show_source_column) {
291       $header = [
292         $this->t('Language'),
293         $this->t('Translation'),
294         $this->t('Source language'),
295         $this->t('Status'),
296         $this->t('Operations'),
297       ];
298     }
299     else {
300       $header = [
301         $this->t('Language'),
302         $this->t('Translation'),
303         $this->t('Status'),
304         $this->t('Operations'),
305       ];
306     }
307
308     $build['#title'] = $this->t('Translations of %label', ['%label' => $entity->label()]);
309
310     // Add metadata to the build render array to let other modules know about
311     // which entity this is.
312     $build['#entity'] = $entity;
313     $cacheability
314       ->addCacheTags($entity->getCacheTags())
315       ->applyTo($build);
316
317     $build['content_translation_overview'] = [
318       '#theme' => 'table',
319       '#header' => $header,
320       '#rows' => $rows,
321     ];
322
323     return $build;
324   }
325
326   /**
327    * Builds an add translation page.
328    *
329    * @param \Drupal\Core\Language\LanguageInterface $source
330    *   The language of the values being translated. Defaults to the entity
331    *   language.
332    * @param \Drupal\Core\Language\LanguageInterface $target
333    *   The language of the translated values. Defaults to the current content
334    *   language.
335    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
336    *   The route match object from which to extract the entity type.
337    * @param string $entity_type_id
338    *   (optional) The entity type ID.
339    *
340    * @return array
341    *   A processed form array ready to be rendered.
342    */
343   public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
344     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
345     $entity = $route_match->getParameter($entity_type_id);
346
347     // In case of a pending revision, make sure we load the latest
348     // translation-affecting revision for the source language, otherwise the
349     // initial form values may not be up-to-date.
350     if (!$entity->isDefaultRevision() && ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle())) {
351       /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
352       $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
353       $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $source->getId());
354       if ($revision_id != $entity->getRevisionId()) {
355         $entity = $storage->loadRevision($revision_id);
356       }
357     }
358
359     // @todo Exploit the upcoming hook_entity_prepare() when available.
360     // See https://www.drupal.org/node/1810394.
361     $this->prepareTranslation($entity, $source, $target);
362
363     // @todo Provide a way to figure out the default form operation. Maybe like
364     //   $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
365     //   See https://www.drupal.org/node/2006348.
366
367     // Use the add form handler, if available, otherwise default.
368     $operation = $entity->getEntityType()->hasHandlerClass('form', 'add') ? 'add' : 'default';
369
370     $form_state_additions = [];
371     $form_state_additions['langcode'] = $target->getId();
372     $form_state_additions['content_translation']['source'] = $source;
373     $form_state_additions['content_translation']['target'] = $target;
374     $form_state_additions['content_translation']['translation_form'] = !$entity->access('update');
375
376     return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);
377   }
378
379   /**
380    * Builds the edit translation page.
381    *
382    * @param \Drupal\Core\Language\LanguageInterface $language
383    *   The language of the translated values. Defaults to the current content
384    *   language.
385    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
386    *   The route match object from which to extract the entity type.
387    * @param string $entity_type_id
388    *   (optional) The entity type ID.
389    *
390    * @return array
391    *   A processed form array ready to be rendered.
392    */
393   public function edit(LanguageInterface $language, RouteMatchInterface $route_match, $entity_type_id = NULL) {
394     $entity = $route_match->getParameter($entity_type_id);
395
396     // @todo Provide a way to figure out the default form operation. Maybe like
397     //   $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
398     //   See https://www.drupal.org/node/2006348.
399
400     // Use the edit form handler, if available, otherwise default.
401     $operation = $entity->getEntityType()->hasHandlerClass('form', 'edit') ? 'edit' : 'default';
402
403     $form_state_additions = [];
404     $form_state_additions['langcode'] = $language->getId();
405     $form_state_additions['content_translation']['translation_form'] = TRUE;
406
407     return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);
408   }
409
410 }