Backup of db before drupal security update
[yaffs-website] / web / core / modules / content_translation / src / ContentTranslationHandler.php
1 <?php
2
3 namespace Drupal\content_translation;
4
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
7 use Drupal\Core\Entity\EntityChangedInterface;
8 use Drupal\Core\Entity\EntityHandlerInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\EntityManagerInterface;
11 use Drupal\Core\Entity\EntityTypeInterface;
12 use Drupal\Core\Field\BaseFieldDefinition;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Language\LanguageInterface;
15 use Drupal\Core\Language\LanguageManagerInterface;
16 use Drupal\Core\Render\Element;
17 use Drupal\Core\Session\AccountInterface;
18 use Drupal\user\Entity\User;
19 use Drupal\user\EntityOwnerInterface;
20 use Symfony\Component\DependencyInjection\ContainerInterface;
21
22 /**
23  * Base class for content translation handlers.
24  *
25  * @ingroup entity_api
26  */
27 class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
28   use DependencySerializationTrait;
29
30   /**
31    * The type of the entity being translated.
32    *
33    * @var string
34    */
35   protected $entityTypeId;
36
37   /**
38    * Information about the entity type.
39    *
40    * @var \Drupal\Core\Entity\EntityTypeInterface
41    */
42   protected $entityType;
43
44   /**
45    * The language manager.
46    *
47    * @var \Drupal\Core\Language\LanguageManagerInterface
48    */
49   protected $languageManager;
50
51   /**
52    * The content translation manager.
53    *
54    * @var \Drupal\content_translation\ContentTranslationManagerInterface
55    */
56   protected $manager;
57
58   /**
59    * The current user.
60    *
61    * @var \Drupal\Core\Session\AccountInterface
62    */
63   protected $currentUser;
64
65   /**
66    * The array of installed field storage definitions for the entity type, keyed
67    * by field name.
68    *
69    * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
70    */
71   protected $fieldStorageDefinitions;
72
73   /**
74    * Initializes an instance of the content translation controller.
75    *
76    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
77    *   The info array of the given entity type.
78    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
79    *   The language manager.
80    * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
81    *   The content translation manager service.
82    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
83    *   The entity manager.
84    * @param \Drupal\Core\Session\AccountInterface $current_user
85    *   The current user.
86    */
87   public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
88     $this->entityTypeId = $entity_type->id();
89     $this->entityType = $entity_type;
90     $this->languageManager = $language_manager;
91     $this->manager = $manager;
92     $this->currentUser = $current_user;
93     $this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
94   }
95
96   /**
97    * {@inheritdoc}
98    */
99   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
100     return new static(
101       $entity_type,
102       $container->get('language_manager'),
103       $container->get('content_translation.manager'),
104       $container->get('entity.manager'),
105       $container->get('current_user')
106     );
107   }
108
109   /**
110    * {@inheritdoc}
111    */
112   public function getFieldDefinitions() {
113     $definitions = [];
114
115     $definitions['content_translation_source'] = BaseFieldDefinition::create('language')
116       ->setLabel(t('Translation source'))
117       ->setDescription(t('The source language from which this translation was created.'))
118       ->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
119       ->setRevisionable(TRUE)
120       ->setTranslatable(TRUE);
121
122     $definitions['content_translation_outdated'] = BaseFieldDefinition::create('boolean')
123       ->setLabel(t('Translation outdated'))
124       ->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
125       ->setDefaultValue(FALSE)
126       ->setRevisionable(TRUE)
127       ->setTranslatable(TRUE);
128
129     if (!$this->hasAuthor()) {
130       $definitions['content_translation_uid'] = BaseFieldDefinition::create('entity_reference')
131         ->setLabel(t('Translation author'))
132         ->setDescription(t('The author of this translation.'))
133         ->setSetting('target_type', 'user')
134         ->setSetting('handler', 'default')
135         ->setRevisionable(TRUE)
136         ->setDefaultValueCallback(get_class($this) . '::getDefaultOwnerId')
137         ->setTranslatable(TRUE);
138     }
139
140     if (!$this->hasPublishedStatus()) {
141       $definitions['content_translation_status'] = BaseFieldDefinition::create('boolean')
142         ->setLabel(t('Translation status'))
143         ->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
144         ->setDefaultValue(TRUE)
145         ->setRevisionable(TRUE)
146         ->setTranslatable(TRUE);
147     }
148
149     if (!$this->hasCreatedTime()) {
150       $definitions['content_translation_created'] = BaseFieldDefinition::create('created')
151         ->setLabel(t('Translation created time'))
152         ->setDescription(t('The Unix timestamp when the translation was created.'))
153         ->setRevisionable(TRUE)
154         ->setTranslatable(TRUE);
155     }
156
157     if (!$this->hasChangedTime()) {
158       $definitions['content_translation_changed'] = BaseFieldDefinition::create('changed')
159         ->setLabel(t('Translation changed time'))
160         ->setDescription(t('The Unix timestamp when the translation was most recently saved.'))
161         ->setRevisionable(TRUE)
162         ->setTranslatable(TRUE);
163     }
164
165     return $definitions;
166   }
167
168   /**
169    * Checks whether the entity type supports author natively.
170    *
171    * @return bool
172    *   TRUE if metadata is natively supported, FALSE otherwise.
173    */
174   protected function hasAuthor() {
175     // Check for field named uid, but only in case the entity implements the
176     // EntityOwnerInterface. This helps to exclude cases, where the uid is
177     // defined as field name, but is not meant to be an owner field; for
178     // instance, the User entity.
179     return $this->entityType->entityClassImplements(EntityOwnerInterface::class) && $this->checkFieldStorageDefinitionTranslatability('uid');
180   }
181
182   /**
183    * Checks whether the entity type supports published status natively.
184    *
185    * @return bool
186    *   TRUE if metadata is natively supported, FALSE otherwise.
187    */
188   protected function hasPublishedStatus() {
189     return $this->checkFieldStorageDefinitionTranslatability('status');
190   }
191
192   /**
193    * Checks whether the entity type supports modification time natively.
194    *
195    * @return bool
196    *   TRUE if metadata is natively supported, FALSE otherwise.
197    */
198   protected function hasChangedTime() {
199     return $this->entityType->entityClassImplements(EntityChangedInterface::class) && $this->checkFieldStorageDefinitionTranslatability('changed');
200   }
201
202   /**
203    * Checks whether the entity type supports creation time natively.
204    *
205    * @return bool
206    *   TRUE if metadata is natively supported, FALSE otherwise.
207    */
208   protected function hasCreatedTime() {
209     return $this->checkFieldStorageDefinitionTranslatability('created');
210   }
211
212   /**
213    * Checks the field storage definition for translatability support.
214    *
215    * Checks whether the given field is defined in the field storage definitions
216    * and if its definition specifies it as translatable.
217    *
218    * @param string $field_name
219    *   The name of the field.
220    *
221    * @return bool
222    *   TRUE if translatable field storage definition exists, FALSE otherwise.
223    */
224   protected function checkFieldStorageDefinitionTranslatability($field_name) {
225     return array_key_exists($field_name, $this->fieldStorageDefinitions) && $this->fieldStorageDefinitions[$field_name]->isTranslatable();
226   }
227
228   /**
229    * {@inheritdoc}
230    */
231   public function retranslate(EntityInterface $entity, $langcode = NULL) {
232     $updated_langcode = !empty($langcode) ? $langcode : $entity->language()->getId();
233     foreach ($entity->getTranslationLanguages() as $langcode => $language) {
234       $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
235         ->setOutdated($langcode != $updated_langcode);
236     }
237   }
238
239   /**
240    * {@inheritdoc}
241    */
242   public function getTranslationAccess(EntityInterface $entity, $op) {
243     // @todo Move this logic into a translation access control handler checking also
244     //   the translation language and the given account.
245     $entity_type = $entity->getEntityType();
246     $translate_permission = TRUE;
247     // If no permission granularity is defined this entity type does not need an
248     // explicit translate permission.
249     if (!$this->currentUser->hasPermission('translate any entity') && $permission_granularity = $entity_type->getPermissionGranularity()) {
250       $translate_permission = $this->currentUser->hasPermission($permission_granularity == 'bundle' ? "translate {$entity->bundle()} {$entity->getEntityTypeId()}" : "translate {$entity->getEntityTypeId()}");
251     }
252     return AccessResult::allowedIf($translate_permission && $this->currentUser->hasPermission("$op content translations"))->cachePerPermissions();
253   }
254
255   /**
256    * {@inheritdoc}
257    */
258   public function getSourceLangcode(FormStateInterface $form_state) {
259     if ($source = $form_state->get(['content_translation', 'source'])) {
260       return $source->getId();
261     }
262     return FALSE;
263   }
264
265   /**
266    * {@inheritdoc}
267    */
268   public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
269     $form_object = $form_state->getFormObject();
270     $form_langcode = $form_object->getFormLangcode($form_state);
271     $entity_langcode = $entity->getUntranslated()->language()->getId();
272     $source_langcode = $this->getSourceLangcode($form_state);
273
274     $new_translation = !empty($source_langcode);
275     $translations = $entity->getTranslationLanguages();
276     if ($new_translation) {
277       // Make sure a new translation does not appear as existing yet.
278       unset($translations[$form_langcode]);
279     }
280     $is_translation = !$form_object->isDefaultFormLangcode($form_state);
281     $has_translations = count($translations) > 1;
282
283     // Adjust page title to specify the current language being edited, if we
284     // have at least one translation.
285     $languages = $this->languageManager->getLanguages();
286     if (isset($languages[$form_langcode]) && ($has_translations || $new_translation)) {
287       $title = $this->entityFormTitle($entity);
288       // When editing the original values display just the entity label.
289       if ($is_translation) {
290         $t_args = ['%language' => $languages[$form_langcode]->getName(), '%title' => $entity->label(), '@title' => $title];
291         $title = empty($source_langcode) ? t('@title [%language translation]', $t_args) : t('Create %language translation of %title', $t_args);
292       }
293       $form['#title'] = $title;
294     }
295
296     // Display source language selector only if we are creating a new
297     // translation and there are at least two translations available.
298     if ($has_translations && $new_translation) {
299       $form['source_langcode'] = [
300         '#type' => 'details',
301         '#title' => t('Source language: @language', ['@language' => $languages[$source_langcode]->getName()]),
302         '#tree' => TRUE,
303         '#weight' => -100,
304         '#multilingual' => TRUE,
305         'source' => [
306           '#title' => t('Select source language'),
307           '#title_display' => 'invisible',
308           '#type' => 'select',
309           '#default_value' => $source_langcode,
310           '#options' => [],
311         ],
312         'submit' => [
313           '#type' => 'submit',
314           '#value' => t('Change'),
315           '#submit' => [[$this, 'entityFormSourceChange']],
316         ],
317       ];
318       foreach ($this->languageManager->getLanguages() as $language) {
319         if (isset($translations[$language->getId()])) {
320           $form['source_langcode']['source']['#options'][$language->getId()] = $language->getName();
321         }
322       }
323     }
324
325     // Locate the language widget.
326     $langcode_key = $this->entityType->getKey('langcode');
327     if (isset($form[$langcode_key])) {
328       $language_widget = &$form[$langcode_key];
329     }
330
331     // If we are editing the source entity, limit the list of languages so that
332     // it is not possible to switch to a language for which a translation
333     // already exists. Note that this will only work if the widget is structured
334     // like \Drupal\Core\Field\Plugin\Field\FieldWidget\LanguageSelectWidget.
335     if (isset($language_widget['widget'][0]['value']) && !$is_translation && $has_translations) {
336       $language_select = &$language_widget['widget'][0]['value'];
337       if ($language_select['#type'] == 'language_select') {
338         $options = [];
339         foreach ($this->languageManager->getLanguages() as $language) {
340           // Show the current language, and the languages for which no
341           // translation already exists.
342           if (empty($translations[$language->getId()]) || $language->getId() == $entity_langcode) {
343             $options[$language->getId()] = $language->getName();
344           }
345         }
346         $language_select['#options'] = $options;
347       }
348     }
349     if ($is_translation) {
350       if (isset($language_widget)) {
351         $language_widget['widget']['#access'] = FALSE;
352       }
353
354       // Replace the delete button with the delete translation one.
355       if (!$new_translation) {
356         $weight = 100;
357         foreach (['delete', 'submit'] as $key) {
358           if (isset($form['actions'][$key]['weight'])) {
359             $weight = $form['actions'][$key]['weight'];
360             break;
361           }
362         }
363         $access = $this->getTranslationAccess($entity, 'delete')->isAllowed() || ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form'));
364         $form['actions']['delete_translation'] = [
365           '#type' => 'submit',
366           '#value' => t('Delete translation'),
367           '#weight' => $weight,
368           '#submit' => [[$this, 'entityFormDeleteTranslation']],
369           '#access' => $access,
370         ];
371       }
372
373       // Always remove the delete button on translation forms.
374       unset($form['actions']['delete']);
375     }
376
377     // We need to display the translation tab only when there is at least one
378     // translation available or a new one is about to be created.
379     if ($new_translation || $has_translations) {
380       $form['content_translation'] = [
381         '#type' => 'details',
382         '#title' => t('Translation'),
383         '#tree' => TRUE,
384         '#weight' => 10,
385         '#access' => $this->getTranslationAccess($entity, $source_langcode ? 'create' : 'update')->isAllowed(),
386         '#multilingual' => TRUE,
387       ];
388
389       if (isset($form['advanced'])) {
390         $form['content_translation'] += [
391           '#group' => 'advanced',
392           '#weight' => 100,
393           '#attributes' => [
394             'class' => ['entity-translation-options'],
395           ],
396         ];
397       }
398
399       // A new translation is enabled by default.
400       $metadata = $this->manager->getTranslationMetadata($entity);
401       $status = $new_translation || $metadata->isPublished();
402       // If there is only one published translation we cannot unpublish it,
403       // since there would be nothing left to display.
404       $enabled = TRUE;
405       if ($status) {
406         $published = 0;
407         foreach ($entity->getTranslationLanguages() as $langcode => $language) {
408           $published += $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
409             ->isPublished();
410         }
411         $enabled = $published > 1;
412       }
413       $description = $enabled ?
414         t('An unpublished translation will not be visible without translation permissions.') :
415         t('Only this translation is published. You must publish at least one more translation to unpublish this one.');
416
417       $form['content_translation']['status'] = [
418         '#type' => 'checkbox',
419         '#title' => t('This translation is published'),
420         '#default_value' => $status,
421         '#description' => $description,
422         '#disabled' => !$enabled,
423       ];
424
425       $translate = !$new_translation && $metadata->isOutdated();
426       if (!$translate) {
427         $form['content_translation']['retranslate'] = [
428           '#type' => 'checkbox',
429           '#title' => t('Flag other translations as outdated'),
430           '#default_value' => FALSE,
431           '#description' => t('If you made a significant change, which means the other translations should be updated, you can flag all translations of this content as outdated. This will not change any other property of them, like whether they are published or not.'),
432         ];
433       }
434       else {
435         $form['content_translation']['outdated'] = [
436           '#type' => 'checkbox',
437           '#title' => t('This translation needs to be updated'),
438           '#default_value' => $translate,
439           '#description' => t('When this option is checked, this translation needs to be updated. Uncheck when the translation is up to date again.'),
440         ];
441         $form['content_translation']['#open'] = TRUE;
442       }
443
444       // Default to the anonymous user.
445       $uid = 0;
446       if ($new_translation) {
447         $uid = $this->currentUser->id();
448       }
449       elseif (($account = $metadata->getAuthor()) && $account->id()) {
450         $uid = $account->id();
451       }
452       $form['content_translation']['uid'] = [
453         '#type' => 'entity_autocomplete',
454         '#title' => t('Authored by'),
455         '#target_type' => 'user',
456         '#default_value' => User::load($uid),
457         // Validation is done by static::entityFormValidate().
458         '#validate_reference' => FALSE,
459         '#maxlength' => 60,
460         '#description' => t('Leave blank for %anonymous.', ['%anonymous' => \Drupal::config('user.settings')->get('anonymous')]),
461       ];
462
463       $date = $new_translation ? REQUEST_TIME : $metadata->getCreatedTime();
464       $form['content_translation']['created'] = [
465         '#type' => 'textfield',
466         '#title' => t('Authored on'),
467         '#maxlength' => 25,
468         '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', ['%time' => format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date(REQUEST_TIME, 'custom', 'O')]),
469         '#default_value' => $new_translation || !$date ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
470       ];
471
472       if (isset($language_widget)) {
473         $language_widget['#multilingual'] = TRUE;
474       }
475
476       $form['#process'][] = [$this, 'entityFormSharedElements'];
477     }
478
479     // Process the submitted values before they are stored.
480     $form['#entity_builders'][] = [$this, 'entityFormEntityBuild'];
481
482     // Handle entity validation.
483     $form['#validate'][] = [$this, 'entityFormValidate'];
484
485     // Handle entity deletion.
486     if (isset($form['actions']['delete'])) {
487       $form['actions']['delete']['#submit'][] = [$this, 'entityFormDelete'];
488     }
489
490     // Handle entity form submission before the entity has been saved.
491     foreach (Element::children($form['actions']) as $action) {
492       if (isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] == 'submit') {
493         array_unshift($form['actions'][$action]['#submit'], [$this, 'entityFormSubmit']);
494       }
495     }
496   }
497
498   /**
499    * Process callback: determines which elements get clue in the form.
500    *
501    * @see \Drupal\content_translation\ContentTranslationHandler::entityFormAlter()
502    */
503   public function entityFormSharedElements($element, FormStateInterface $form_state, $form) {
504     static $ignored_types;
505
506     // @todo Find a more reliable way to determine if a form element concerns a
507     //   multilingual value.
508     if (!isset($ignored_types)) {
509       $ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details']);
510     }
511
512     foreach (Element::children($element) as $key) {
513       if (!isset($element[$key]['#type'])) {
514         $this->entityFormSharedElements($element[$key], $form_state, $form);
515       }
516       else {
517         // Ignore non-widget form elements.
518         if (isset($ignored_types[$element[$key]['#type']])) {
519           continue;
520         }
521         // Elements are considered to be non multilingual by default.
522         if (empty($element[$key]['#multilingual'])) {
523           // If we are displaying a multilingual entity form we need to provide
524           // translatability clues, otherwise the shared form elements should be
525           // hidden.
526           if (!$form_state->get(['content_translation', 'translation_form'])) {
527             $this->addTranslatabilityClue($element[$key]);
528           }
529           else {
530             $element[$key]['#access'] = FALSE;
531           }
532         }
533       }
534     }
535
536     return $element;
537   }
538
539   /**
540    * Adds a clue about the form element translatability.
541    *
542    * If the given element does not have a #title attribute, the function is
543    * recursively applied to child elements.
544    *
545    * @param array $element
546    *   A form element array.
547    */
548   protected function addTranslatabilityClue(&$element) {
549     static $suffix, $fapi_title_elements;
550
551     // Elements which can have a #title attribute according to FAPI Reference.
552     if (!isset($suffix)) {
553       $suffix = ' <span class="translation-entity-all-languages">(' . t('all languages') . ')</span>';
554       $fapi_title_elements = array_flip(['checkbox', 'checkboxes', 'date', 'details', 'fieldset', 'file', 'item', 'password', 'password_confirm', 'radio', 'radios', 'select', 'text_format', 'textarea', 'textfield', 'weight']);
555     }
556
557     // Update #title attribute for all elements that are allowed to have a
558     // #title attribute according to the Form API Reference. The reason for this
559     // check is because some elements have a #title attribute even though it is
560     // not rendered; for instance, field containers.
561     if (isset($element['#type']) && isset($fapi_title_elements[$element['#type']]) && isset($element['#title'])) {
562       $element['#title'] .= $suffix;
563     }
564     // If the current element does not have a (valid) title, try child elements.
565     elseif ($children = Element::children($element)) {
566       foreach ($children as $delta) {
567         $this->addTranslatabilityClue($element[$delta], $suffix);
568       }
569     }
570     // If there are no children, fall back to the current #title attribute if it
571     // exists.
572     elseif (isset($element['#title'])) {
573       $element['#title'] .= $suffix;
574     }
575   }
576
577   /**
578    * Entity builder method.
579    *
580    * @param string $entity_type
581    *   The type of the entity.
582    * @param \Drupal\Core\Entity\EntityInterface $entity
583    *   The entity whose form is being built.
584    *
585    * @see \Drupal\content_translation\ContentTranslationHandler::entityFormAlter()
586    */
587   public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
588     $form_object = $form_state->getFormObject();
589     $form_langcode = $form_object->getFormLangcode($form_state);
590     $values = &$form_state->getValue('content_translation', []);
591
592     $metadata = $this->manager->getTranslationMetadata($entity);
593     $metadata->setAuthor(!empty($values['uid']) ? User::load($values['uid']) : User::load(0));
594     $metadata->setPublished(!empty($values['status']));
595     $metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME);
596
597     $source_langcode = $this->getSourceLangcode($form_state);
598     if ($source_langcode) {
599       $metadata->setSource($source_langcode);
600     }
601
602     $metadata->setOutdated(!empty($values['outdated']));
603     if (!empty($values['retranslate'])) {
604       $this->retranslate($entity, $form_langcode);
605     }
606   }
607
608   /**
609    * Form validation handler for ContentTranslationHandler::entityFormAlter().
610    *
611    * Validates the submitted content translation metadata.
612    */
613   public function entityFormValidate($form, FormStateInterface $form_state) {
614     if (!$form_state->isValueEmpty('content_translation')) {
615       $translation = $form_state->getValue('content_translation');
616       // Validate the "authored by" field.
617       if (!empty($translation['uid']) && !($account = User::load($translation['uid']))) {
618         $form_state->setErrorByName('content_translation][uid', t('The translation authoring username %name does not exist.', ['%name' => $account->getUsername()]));
619       }
620       // Validate the "authored on" field.
621       if (!empty($translation['created']) && strtotime($translation['created']) === FALSE) {
622         $form_state->setErrorByName('content_translation][created', t('You have to specify a valid translation authoring date.'));
623       }
624     }
625   }
626
627   /**
628    * Form submission handler for ContentTranslationHandler::entityFormAlter().
629    *
630    * Updates metadata fields, which should be updated only after the validation
631    * has run and before the entity is saved.
632    */
633   public function entityFormSubmit($form, FormStateInterface $form_state) {
634     /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
635     $form_object = $form_state->getFormObject();
636     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
637     $entity = $form_object->getEntity();
638
639     // ContentEntityForm::submit will update the changed timestamp on submit
640     // after the entity has been validated, so that it does not break the
641     // EntityChanged constraint validator. The content translation metadata
642     // field for the changed timestamp  does not have such a constraint defined
643     // at the moment, but it is correct to update it's value in a submission
644     // handler as well and have the same logic like in the Form API.
645     if ($entity->hasField('content_translation_changed')) {
646       $metadata = $this->manager->getTranslationMetadata($entity);
647       $metadata->setChangedTime(REQUEST_TIME);
648     }
649   }
650
651   /**
652    * Form submission handler for ContentTranslationHandler::entityFormAlter().
653    *
654    * Takes care of the source language change.
655    */
656   public function entityFormSourceChange($form, FormStateInterface $form_state) {
657     $form_object = $form_state->getFormObject();
658     $entity = $form_object->getEntity();
659     $source = $form_state->getValue(['source_langcode', 'source']);
660
661     $entity_type_id = $entity->getEntityTypeId();
662     $form_state->setRedirect("entity.$entity_type_id.content_translation_add", [
663       $entity_type_id => $entity->id(),
664       'source' => $source,
665       'target' => $form_object->getFormLangcode($form_state),
666     ]);
667     $languages = $this->languageManager->getLanguages();
668     drupal_set_message(t('Source language set to: %language', ['%language' => $languages[$source]->getName()]));
669   }
670
671   /**
672    * Form submission handler for ContentTranslationHandler::entityFormAlter().
673    *
674    * Takes care of entity deletion.
675    */
676   public function entityFormDelete($form, FormStateInterface $form_state) {
677     $form_object = $form_state->getFormObject()->getEntity();
678     $entity = $form_object->getEntity();
679     if (count($entity->getTranslationLanguages()) > 1) {
680       drupal_set_message(t('This will delete all the translations of %label.', ['%label' => $entity->label()]), 'warning');
681     }
682   }
683
684   /**
685    * Form submission handler for ContentTranslationHandler::entityFormAlter().
686    *
687    * Takes care of content translation deletion.
688    */
689   public function entityFormDeleteTranslation($form, FormStateInterface $form_state) {
690     /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
691     $form_object = $form_state->getFormObject();
692     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
693     $entity = $form_object->getEntity();
694     $entity_type_id = $entity->getEntityTypeId();
695     if ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form')) {
696       $form_state->setRedirectUrl($entity->urlInfo('delete-form'));
697     }
698     else {
699       $form_state->setRedirect("entity.$entity_type_id.content_translation_delete", [
700         $entity_type_id => $entity->id(),
701         'language' => $form_object->getFormLangcode($form_state),
702       ]);
703     }
704   }
705
706   /**
707    * Returns the title to be used for the entity form page.
708    *
709    * @param \Drupal\Core\Entity\EntityInterface $entity
710    *   The entity whose form is being altered.
711    *
712    * @return string|null
713    *   The label of the entity, or NULL if there is no label defined.
714    */
715   protected function entityFormTitle(EntityInterface $entity) {
716     return $entity->label();
717   }
718
719   /**
720    * Default value callback for the owner base field definition.
721    *
722    * @return int
723    *   The user ID.
724    */
725   public static function getDefaultOwnerId() {
726     return \Drupal::currentUser()->id();
727   }
728
729 }