Security update for Core, with self-updated composer
[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\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;
11 use Drupal\Core\Url;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13
14 /**
15  * Base class for entity translation controllers.
16  */
17 class ContentTranslationController extends ControllerBase {
18
19   /**
20    * The content translation manager.
21    *
22    * @var \Drupal\content_translation\ContentTranslationManagerInterface
23    */
24   protected $manager;
25
26   /**
27    * Initializes a content translation controller.
28    *
29    * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
30    *   A content translation manager instance.
31    */
32   public function __construct(ContentTranslationManagerInterface $manager) {
33     $this->manager = $manager;
34   }
35
36   /**
37    * {@inheritdoc}
38    */
39   public static function create(ContainerInterface $container) {
40     return new static($container->get('content_translation.manager'));
41   }
42
43   /**
44    * Populates target values with the source values.
45    *
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.
52    */
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());
57
58     // Make sure we do not inherit the affected status from the source values.
59     if ($entity->getEntityType()->isRevisionable()) {
60       $target_translation->setRevisionTranslationAffected(NULL);
61     }
62
63     /** @var \Drupal\user\UserInterface $user */
64     $user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id());
65     $metadata = $this->manager->getTranslationMetadata($target_translation);
66
67     // Update the translation author to current user, as well the translation
68     // creation time.
69     $metadata->setAuthor($user);
70     $metadata->setCreatedTime(REQUEST_TIME);
71   }
72
73   /**
74    * Builds the translations overview page.
75    *
76    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
77    *   The route match.
78    * @param string $entity_type_id
79    *   (optional) The entity type ID.
80    * @return array
81    *   Array of page elements to render.
82    */
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();
90
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);
94
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');
99
100     $rows = [];
101     $show_source_column = FALSE;
102
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;
109           break;
110         }
111       }
112
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;
117       });
118       $show_source_column = !empty($additional_source_langcodes);
119
120       foreach ($languages as $language) {
121         $language_name = $language->getName();
122         $langcode = $language->getId();
123
124         $add_url = new Url(
125           "entity.$entity_type_id.content_translation_add",
126           [
127             'source' => $original,
128             'target' => $language->getId(),
129             $entity_type_id => $entity->id(),
130           ],
131           [
132             'language' => $language,
133           ]
134         );
135         $edit_url = new Url(
136           "entity.$entity_type_id.content_translation_edit",
137           [
138             'language' => $language->getId(),
139             $entity_type_id => $entity->id(),
140           ],
141           [
142             'language' => $language,
143           ]
144         );
145         $delete_url = new Url(
146           "entity.$entity_type_id.content_translation_delete",
147           [
148             'language' => $language->getId(),
149             $entity_type_id => $entity->id(),
150           ],
151           [
152             'language' => $language,
153           ]
154         );
155         $operations = [
156           'data' => [
157             '#type' => 'operations',
158             '#links' => [],
159           ],
160         ];
161
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']);
174           }
175
176           if (empty($link['url'])) {
177             $row_title = $is_original ? $label : $this->t('n/a');
178           }
179
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;
191           }
192           elseif (!$is_original && $translation_access->isAllowed()) {
193             $links['edit']['url'] = $edit_url;
194           }
195
196           if (isset($links['edit'])) {
197             $links['edit']['title'] = $this->t('Edit');
198           }
199           $status = [
200             'data' => [
201               '#type' => 'inline_template',
202               '#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
203               '#context' => [
204                 'status' => $metadata->isPublished(),
205                 'outdated' => $metadata->isOutdated(),
206               ],
207             ],
208           ];
209
210           if ($is_original) {
211             $language_name = $this->t('<strong>@language_name (Original language)</strong>', ['@language_name' => $language_name]);
212             $source_name = $this->t('n/a');
213           }
214           else {
215             $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
216             $delete_access = $entity->access('delete', NULL, TRUE);
217             $translation_access = $handler->getTranslationAccess($entity, 'delete');
218             $cacheability = $cacheability
219               ->merge(CacheableMetadata::createFromObject($delete_access))
220               ->merge(CacheableMetadata::createFromObject($translation_access));
221             if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) {
222               $links['delete'] = [
223                 'title' => $this->t('Delete'),
224                 'url' => $entity->urlInfo('delete-form'),
225                 'language' => $language,
226               ];
227             }
228             elseif ($translation_access->isAllowed()) {
229               $links['delete'] = [
230                 'title' => $this->t('Delete'),
231                 'url' => $delete_url,
232               ];
233             }
234           }
235         }
236         else {
237           // No such translation in the set yet: help user to create it.
238           $row_title = $source_name = $this->t('n/a');
239           $source = $entity->language()->getId();
240
241           $create_translation_access = $handler->getTranslationAccess($entity, 'create');
242           $cacheability = $cacheability
243             ->merge(CacheableMetadata::createFromObject($create_translation_access));
244           if ($source != $langcode && $create_translation_access->isAllowed()) {
245             if ($translatable) {
246               $links['add'] = [
247                 'title' => $this->t('Add'),
248                 'url' => $add_url,
249               ];
250             }
251             elseif ($field_ui) {
252               $url = new Url('language.content_settings_page');
253
254               // Link directly to the fields tab to make it easier to find the
255               // setting to enable translation on fields.
256               $links['nofields'] = [
257                 'title' => $this->t('No translatable fields'),
258                 'url' => $url,
259               ];
260             }
261           }
262
263           $status = $this->t('Not translated');
264         }
265         if ($show_source_column) {
266           $rows[] = [
267             $language_name,
268             $row_title,
269             $source_name,
270             $status,
271             $operations,
272           ];
273         }
274         else {
275           $rows[] = [$language_name, $row_title, $status, $operations];
276         }
277       }
278     }
279     if ($show_source_column) {
280       $header = [
281         $this->t('Language'),
282         $this->t('Translation'),
283         $this->t('Source language'),
284         $this->t('Status'),
285         $this->t('Operations'),
286       ];
287     }
288     else {
289       $header = [
290         $this->t('Language'),
291         $this->t('Translation'),
292         $this->t('Status'),
293         $this->t('Operations'),
294       ];
295     }
296
297     $build['#title'] = $this->t('Translations of %label', ['%label' => $entity->label()]);
298
299     // Add metadata to the build render array to let other modules know about
300     // which entity this is.
301     $build['#entity'] = $entity;
302     $cacheability
303       ->addCacheTags($entity->getCacheTags())
304       ->applyTo($build);
305
306     $build['content_translation_overview'] = [
307       '#theme' => 'table',
308       '#header' => $header,
309       '#rows' => $rows,
310     ];
311
312     return $build;
313   }
314
315   /**
316    * Builds an add translation page.
317    *
318    * @param \Drupal\Core\Language\LanguageInterface $source
319    *   The language of the values being translated. Defaults to the entity
320    *   language.
321    * @param \Drupal\Core\Language\LanguageInterface $target
322    *   The language of the translated values. Defaults to the current content
323    *   language.
324    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
325    *   The route match object from which to extract the entity type.
326    * @param string $entity_type_id
327    *   (optional) The entity type ID.
328    *
329    * @return array
330    *   A processed form array ready to be rendered.
331    */
332   public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
333     $entity = $route_match->getParameter($entity_type_id);
334
335     // @todo Exploit the upcoming hook_entity_prepare() when available.
336     // See https://www.drupal.org/node/1810394.
337     $this->prepareTranslation($entity, $source, $target);
338
339     // @todo Provide a way to figure out the default form operation. Maybe like
340     //   $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
341     //   See https://www.drupal.org/node/2006348.
342
343     // Use the add form handler, if available, otherwise default.
344     $operation = $entity->getEntityType()->hasHandlerClass('form', 'add') ? 'add' : 'default';
345
346     $form_state_additions = [];
347     $form_state_additions['langcode'] = $target->getId();
348     $form_state_additions['content_translation']['source'] = $source;
349     $form_state_additions['content_translation']['target'] = $target;
350     $form_state_additions['content_translation']['translation_form'] = !$entity->access('update');
351
352     return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);
353   }
354
355   /**
356    * Builds the edit translation page.
357    *
358    * @param \Drupal\Core\Language\LanguageInterface $language
359    *   The language of the translated values. Defaults to the current content
360    *   language.
361    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
362    *   The route match object from which to extract the entity type.
363    * @param string $entity_type_id
364    *   (optional) The entity type ID.
365    *
366    * @return array
367    *   A processed form array ready to be rendered.
368    */
369   public function edit(LanguageInterface $language, RouteMatchInterface $route_match, $entity_type_id = NULL) {
370     $entity = $route_match->getParameter($entity_type_id);
371
372     // @todo Provide a way to figure out the default form operation. Maybe like
373     //   $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default';
374     //   See https://www.drupal.org/node/2006348.
375
376     // Use the edit form handler, if available, otherwise default.
377     $operation = $entity->getEntityType()->hasHandlerClass('form', 'edit') ? 'edit' : 'default';
378
379     $form_state_additions = [];
380     $form_state_additions['langcode'] = $language->getId();
381     $form_state_additions['content_translation']['translation_form'] = TRUE;
382
383     return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions);
384   }
385
386 }