From 2a1c593260745daf64e3120763d23f312ea7aa9c Mon Sep 17 00:00:00 2001
From: Gabor Hojtsy <gabor@hojtsy.hu>
Date: Tue, 6 Mar 2018 12:57:25 +0100
Subject: [PATCH] =?UTF-8?q?Issue=20#2940890=20by=20plach,=20Wim=20Leers,?=
 =?UTF-8?q?=20effulgentsia,=20matsbla,=20timmillwood:=20Don=E2=80=99t=20al?=
 =?UTF-8?q?low=20deleting=20revision=20translations=20in=20pending=20revis?=
 =?UTF-8?q?ions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../content_translation.services.yml          |   6 +
 .../Access/ContentTranslationDeleteAccess.php | 124 ++++++++++
 .../ContentTranslationManageAccessCheck.php   |  50 +++-
 .../src/ContentTranslationHandler.php         |   7 +-
 .../ContentTranslationController.php          |  57 +++--
 .../ContentTranslationRouteSubscriber.php     |   8 +
 ...tentTranslationPendingRevisionTestBase.php |  59 +++++
 ...slationRevisionTranslationDeletionTest.php | 214 ++++++++++++++++++
 8 files changed, 493 insertions(+), 32 deletions(-)
 create mode 100644 core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
 create mode 100644 core/modules/content_translation/tests/src/Functional/ContentTranslationRevisionTranslationDeletionTest.php

diff --git a/core/modules/content_translation/content_translation.services.yml b/core/modules/content_translation/content_translation.services.yml
index 066142fac3fd..9a3ba9c04222 100644
--- a/core/modules/content_translation/content_translation.services.yml
+++ b/core/modules/content_translation/content_translation.services.yml
@@ -9,6 +9,12 @@ services:
     tags:
       - { name: event_subscriber }
 
+  content_translation.delete_access:
+    class: Drupal\content_translation\Access\ContentTranslationDeleteAccess
+    arguments: ['@entity_type.manager', '@content_translation.manager']
+    tags:
+      - { name: access_check, applies_to: _access_content_translation_delete }
+
   content_translation.overview_access:
     class: Drupal\content_translation\Access\ContentTranslationOverviewAccess
     arguments: ['@entity.manager']
diff --git a/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
new file mode 100644
index 000000000000..5f1933714bca
--- /dev/null
+++ b/core/modules/content_translation/src/Access/ContentTranslationDeleteAccess.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\content_translation\Access;
+
+use Drupal\content_translation\ContentTranslationManager;
+use Drupal\content_translation\ContentTranslationManagerInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\language\Entity\ContentLanguageSettings;
+use Drupal\workflows\Entity\Workflow;
+
+/**
+ * Access check for entity translation deletion.
+ *
+ * @internal This additional access checker only aims to prevent deletions in
+ *   pending revisions until we are able to flag revision translations as
+ *   deleted.
+ *
+ * @todo Remove this in https://www.drupal.org/node/2945956.
+ */
+class ContentTranslationDeleteAccess implements AccessInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The content translation manager.
+   *
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $contentTranslationManager;
+
+  /**
+   * Constructs a ContentTranslationDeleteAccess object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
+   *   The entity type manager.
+   * @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
+   *   The content translation manager.
+   */
+  public function __construct(EntityTypeManagerInterface $manager, ContentTranslationManagerInterface $content_translation_manager) {
+    $this->entityTypeManager = $manager;
+    $this->contentTranslationManager = $content_translation_manager;
+  }
+
+  /**
+   * Checks access to translation deletion for the specified route match.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The parameterized route.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(RouteMatchInterface $route_match, AccountInterface $account) {
+    $requirement = $route_match->getRouteObject()->getRequirement('_access_content_translation_delete');
+    $entity_type_id = current(explode('.', $requirement));
+    $entity = $route_match->getParameter($entity_type_id);
+    return $this->checkAccess($entity);
+  }
+
+  /**
+   * Checks access to translation deletion for the specified entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity translation to be deleted.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function checkAccess(ContentEntityInterface $entity) {
+    $result = AccessResult::allowed();
+
+    $entity_type_id = $entity->getEntityTypeId();
+    $result->addCacheableDependency($entity);
+    // Add the cache dependencies used by
+    // ContentTranslationManager::isPendingRevisionSupportEnabled().
+    if (\Drupal::moduleHandler()->moduleExists('content_moderation')) {
+      foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
+        $result->addCacheableDependency($workflow);
+      }
+    }
+    if (!ContentTranslationManager::isPendingRevisionSupportEnabled($entity_type_id, $entity->bundle())) {
+      return $result;
+    }
+
+    if ($entity->isDefaultTranslation()) {
+      return $result;
+    }
+
+    $config = ContentLanguageSettings::load($entity_type_id . '.' . $entity->bundle());
+    $result->addCacheableDependency($config);
+    if (!$this->contentTranslationManager->isEnabled($entity_type_id, $entity->bundle())) {
+      return $result;
+    }
+
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage($entity_type_id);
+    $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
+    if (!$revision_id) {
+      return $result;
+    }
+
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
+    $revision = $storage->loadRevision($revision_id);
+    if ($revision->wasDefaultRevision()) {
+      return $result;
+    }
+
+    $result = $result->andIf(AccessResult::forbidden());
+    return $result;
+  }
+
+}
diff --git a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
index 6a4be1eb11fe..bbf0540e90ce 100644
--- a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
+++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php
@@ -3,6 +3,7 @@
 namespace Drupal\content_translation\Access;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
@@ -90,15 +91,12 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
         return AccessResult::allowed()->cachePerPermissions();
       }
 
-      /* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
-      $handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
-
-      // Load translation.
-      $translations = $entity->getTranslationLanguages();
-      $languages = $this->languageManager->getLanguages();
-
       switch ($operation) {
         case 'create':
+          /* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
+          $handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
+          $translations = $entity->getTranslationLanguages();
+          $languages = $this->languageManager->getLanguages();
           $source_language = $this->languageManager->getLanguage($source) ?: $entity->language();
           $target_language = $this->languageManager->getLanguage($target) ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
           $is_new_translation = ($source_language->getId() != $target_language->getId()
@@ -109,12 +107,14 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
             ->andIf($handler->getTranslationAccess($entity, $operation));
 
         case 'delete':
+          // @todo Remove this in https://www.drupal.org/node/2945956.
+          /** @var \Drupal\Core\Access\AccessResultInterface $delete_access */
+          $delete_access = \Drupal::service('content_translation.delete_access')->checkAccess($entity);
+          $access = $this->checkAccess($entity, $language, $operation);
+          return $delete_access->andIf($access);
+
         case 'update':
-          $has_translation = isset($languages[$language->getId()])
-            && $language->getId() != $entity->getUntranslated()->language()->getId()
-            && isset($translations[$language->getId()]);
-          return AccessResult::allowedIf($has_translation)->cachePerPermissions()->addCacheableDependency($entity)
-            ->andIf($handler->getTranslationAccess($entity, $operation));
+          return $this->checkAccess($entity, $language, $operation);
       }
     }
 
@@ -122,4 +122,30 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
     return AccessResult::neutral();
   }
 
+  /**
+   * Performs access checks for the specified operation.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity being checked.
+   * @param \Drupal\Core\Language\LanguageInterface $language
+   *   For an update or delete operation, the language code of the translation
+   *   being updated or deleted.
+   * @param string $operation
+   *   The operation to be checked.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   An access result object.
+   */
+  protected function checkAccess(ContentEntityInterface $entity, LanguageInterface $language, $operation) {
+    /* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
+    $handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation');
+    $translations = $entity->getTranslationLanguages();
+    $languages = $this->languageManager->getLanguages();
+    $has_translation = isset($languages[$language->getId()])
+      && $language->getId() != $entity->getUntranslated()->language()->getId()
+      && isset($translations[$language->getId()]);
+    return AccessResult::allowedIf($has_translation)->cachePerPermissions()->addCacheableDependency($entity)
+      ->andIf($handler->getTranslationAccess($entity, $operation));
+  }
+
 }
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 9bbfb4c84894..bcf66bd53b0f 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -390,7 +390,12 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
             break;
           }
         }
-        $access = $this->getTranslationAccess($entity, 'delete')->isAllowed() || ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form'));
+        /** @var \Drupal\Core\Access\AccessResultInterface $delete_access */
+        $delete_access = \Drupal::service('content_translation.delete_access')->checkAccess($entity);
+        $access = $delete_access->isAllowed() && (
+          $this->getTranslationAccess($entity, 'delete')->isAllowed() ||
+          ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form'))
+        );
         $form['actions']['delete_translation'] = [
           '#type' => 'submit',
           '#value' => t('Delete translation'),
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index 2c9a1036437a..3f5891b73a85 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -101,9 +101,9 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
 
     $rows = [];
     $show_source_column = FALSE;
-    $default_revision = $entity;
     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
     $storage = $this->entityTypeManager()->getStorage($entity_type_id);
+    $default_revision = $storage->load($entity->id());
 
     if ($this->languageManager()->isMultilingual()) {
       // Determine whether the current entity is translatable.
@@ -131,8 +131,17 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
         // need to load the latest translation-affecting revision for each
         // language to be sure we are listing all available translations.
         if ($use_latest_revisions) {
+          $entity = $default_revision;
           $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
-          $entity = $latest_revision_id ? $storage->loadRevision($latest_revision_id) : $default_revision;
+          if ($latest_revision_id) {
+            /** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
+            $latest_revision = $storage->loadRevision($latest_revision_id);
+            // Make sure we do not list removed translations, i.e. translations
+            // that have been part of a default revision but no longer are.
+            if (!$latest_revision->wasDefaultRevision() || $default_revision->hasTranslation($langcode)) {
+              $entity = $latest_revision;
+            }
+          }
           $translations = $entity->getTranslationLanguages();
         }
 
@@ -227,24 +236,34 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
             $source_name = $this->t('n/a');
           }
           else {
-            $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
-            $delete_access = $entity->access('delete', NULL, TRUE);
-            $translation_access = $handler->getTranslationAccess($entity, 'delete');
-            $cacheability = $cacheability
-              ->merge(CacheableMetadata::createFromObject($delete_access))
-              ->merge(CacheableMetadata::createFromObject($translation_access));
-            if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) {
-              $links['delete'] = [
-                'title' => $this->t('Delete'),
-                'url' => $entity->urlInfo('delete-form'),
-                'language' => $language,
-              ];
+            /** @var \Drupal\Core\Access\AccessResultInterface $delete_route_access */
+            $delete_route_access = \Drupal::service('content_translation.delete_access')->checkAccess($translation);
+            $cacheability->addCacheableDependency($delete_route_access);
+
+            if ($delete_route_access->isAllowed()) {
+              $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a');
+              $delete_access = $entity->access('delete', NULL, TRUE);
+              $translation_access = $handler->getTranslationAccess($entity, 'delete');
+              $cacheability
+                ->addCacheableDependency($delete_access)
+                ->addCacheableDependency($translation_access);
+
+              if ($delete_access->isAllowed() && $entity_type->hasLinkTemplate('delete-form')) {
+                $links['delete'] = [
+                  'title' => $this->t('Delete'),
+                  'url' => $entity->urlInfo('delete-form'),
+                  'language' => $language,
+                ];
+              }
+              elseif ($translation_access->isAllowed()) {
+                $links['delete'] = [
+                  'title' => $this->t('Delete'),
+                  'url' => $delete_url,
+                ];
+              }
             }
-            elseif ($translation_access->isAllowed()) {
-              $links['delete'] = [
-                'title' => $this->t('Delete'),
-                'url' => $delete_url,
-              ];
+            else {
+              $this->messenger()->addWarning($this->t('The "Delete translation" action is only available for published translations.'), FALSE);
             }
           }
         }
diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
index 1947ef0771fa..fa1ac3bbf902 100644
--- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
+++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
@@ -164,6 +164,14 @@ protected function alterRoutes(RouteCollection $collection) {
         ]
       );
       $collection->add("entity.$entity_type_id.content_translation_delete", $route);
+
+      // Add our custom translation deletion access checker.
+      if ($load_latest_revision) {
+        $entity_delete_route = $collection->get("entity.$entity_type_id.delete_form");
+        if ($entity_delete_route) {
+          $entity_delete_route->addRequirements(['_access_content_translation_delete' => "$entity_type_id.delete"]);
+        }
+      }
     }
   }
 
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionTestBase.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionTestBase.php
index ccbe854d4f4a..3a3a058962a5 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionTestBase.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationPendingRevisionTestBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\content_translation\Functional;
 
+use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
 
 /**
@@ -111,4 +112,62 @@ protected function setupBundle() {
     $this->createContentType(['type' => $this->bundle]);
   }
 
+  /**
+   * Loads the active revision translation for the specified entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity being edited.
+   * @param string $langcode
+   *   The translation language code.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface|null
+   *   The active revision translation or NULL if none could be identified.
+   */
+  protected function loadRevisionTranslation(ContentEntityInterface $entity, $langcode) {
+    $revision_id = $this->storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
+    $revision = $revision_id ? $this->storage->loadRevision($revision_id) : NULL;
+    return $revision && $revision->hasTranslation($langcode) ? $revision->getTranslation($langcode) : NULL;
+  }
+
+  /**
+   * Returns the edit URL for the specified entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity being edited.
+   *
+   * @return \Drupal\Core\Url
+   *   The edit URL.
+   */
+  protected function getEditUrl(ContentEntityInterface $entity) {
+    if ($entity->access('update', $this->loggedInUser)) {
+      $url = $entity->toUrl('edit-form');
+    }
+    else {
+      $url = $entity->toUrl('drupal:content-translation-edit');
+      $url->setRouteParameter('language', $entity->language()->getId());
+    }
+    return $url;
+  }
+
+  /**
+   * Returns the delete translation URL for the specified entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity being edited.
+   *
+   * @return \Drupal\Core\Url
+   *   The delete translation URL.
+   */
+  protected function getDeleteUrl(ContentEntityInterface $entity) {
+    if ($entity->access('delete', $this->loggedInUser)) {
+      $url = $entity->toUrl('delete-form');
+    }
+    else {
+      $url = $entity->toUrl('drupal:content-translation-delete');
+      $url->setRouteParameter('language', $entity->language()->getId());
+    }
+    return $url;
+  }
+
 }
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationRevisionTranslationDeletionTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationRevisionTranslationDeletionTest.php
new file mode 100644
index 000000000000..842d7deda8c4
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationRevisionTranslationDeletionTest.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Functional;
+
+use Drupal\Core\Url;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests that revision translation deletion is handled correctly.
+ *
+ * @group content_translation
+ */
+class ContentTranslationRevisionTranslationDeletionTest extends ContentTranslationPendingRevisionTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->enableContentModeration();
+  }
+
+  /**
+   * Tests that translation overview handles pending revisions correctly.
+   */
+  public function testOverview() {
+    $index = 1;
+    $accounts = [
+      $this->rootUser,
+      $this->editor,
+      $this->translator,
+    ];
+    foreach ($accounts as $account) {
+      $this->currentAccount = $account;
+      $this->doTestOverview($index++);
+    }
+  }
+
+  /**
+   * Performs a test run.
+   *
+   * @param int $index
+   *   The test run index.
+   */
+  public function doTestOverview($index) {
+    $this->drupalLogin($this->currentAccount);
+
+    // Create a test node.
+    $values = [
+      'title' => "Test $index.1 EN",
+      'moderation_state' => 'published',
+    ];
+    $id = $this->createEntity($values, 'en');
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->storage->load($id);
+
+    // Add a draft translation and check that it is available only in the latest
+    // revision.
+    $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", [
+        $entity->getEntityTypeId() => $id,
+        'source' => 'en',
+        'target' => 'it',
+      ],
+      [
+        'language' => ConfigurableLanguage::load('it'),
+        'absolute' => FALSE,
+      ]
+    );
+    $add_translation_href = $add_translation_url->toString();
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test $index.2 IT",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertFalse($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->hasTranslation('it'));
+
+    // Check that translations cannot be deleted in drafts.
+    $overview_url = $entity->toUrl('drupal:content-translation-overview');
+    $this->drupalGet($overview_url);
+    $it_delete_url = $this->getDeleteUrl($it_revision);
+    $it_delete_href = $it_delete_url->toString();
+    $this->assertSession()->linkByHrefNotExists($it_delete_href);
+    $warning = 'The "Delete translation" action is only available for published translations.';
+    $this->assertSession()->pageTextContains($warning);
+    $this->drupalGet($this->getEditUrl($it_revision));
+    $this->assertSession()->buttonNotExists('Delete translation');
+
+    // Publish the translation and verify it can be deleted.
+    $edit = [
+      'title[0][value]' => "Test $index.3 IT",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertTrue($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->hasTranslation('it'));
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefExists($it_delete_href);
+    $this->assertSession()->pageTextNotContains($warning);
+    $this->drupalGet($this->getEditUrl($it_revision));
+    $this->assertSession()->buttonExists('Delete translation');
+
+    // Create an English draft and verify the published translation was
+    // preserved.
+    $this->drupalLogin($this->editor);
+    $en_revision = $this->loadRevisionTranslation($entity, 'en');
+    $this->drupalGet($this->getEditUrl($en_revision));
+    $edit = [
+      'title[0][value]' => "Test $index.4 EN",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertTrue($entity->hasTranslation('it'));
+    $en_revision = $this->loadRevisionTranslation($entity, 'en');
+    $this->assertTrue($en_revision->hasTranslation('it'));
+    $this->drupalLogin($this->currentAccount);
+
+    // Delete the translation and verify that it is actually gone and that it is
+    // possible to create it again.
+    $this->drupalGet($it_delete_url);
+    $this->drupalPostForm(NULL, [], 'Delete Italian translation');
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertFalse($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->wasDefaultRevision());
+    $this->assertTrue($it_revision->hasTranslation('it'));
+    $this->assertTrue($it_revision->getRevisionId() < $entity->getRevisionId());
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefNotExists($this->getEditUrl($it_revision)->toString());
+    $this->assertSession()->linkByHrefExists($add_translation_href);
+
+    // Publish the English draft and verify the translation is not accidentally
+    // restored.
+    $this->drupalLogin($this->editor);
+    $en_revision = $this->loadRevisionTranslation($entity, 'en');
+    $this->drupalGet($this->getEditUrl($en_revision));
+    $edit = [
+      'title[0][value]' => "Test $index.6 EN",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertFalse($entity->hasTranslation('it'));
+    $this->drupalLogin($this->currentAccount);
+
+    // Create a published translation again and verify it could be deleted.
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test $index.7 IT",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertTrue($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->hasTranslation('it'));
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefExists($it_delete_href);
+
+    // Create a translation draft again and verify it cannot be deleted.
+    $this->drupalGet($this->getEditUrl($it_revision));
+    $edit = [
+      'title[0][value]' => "Test $index.8 IT",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertTrue($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->hasTranslation('it'));
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefNotExists($it_delete_href);
+
+    // Delete the translation draft and verify the translation can be deleted
+    // again, since the active revision is now a default revision.
+    $this->drupalLogin($this->editor);
+    $this->drupalGet($it_revision->toUrl('version-history'));
+    $revision_deletion_url = Url::fromRoute('node.revision_delete_confirm', [
+        'node' => $id,
+        'node_revision' => $it_revision->getRevisionId(),
+      ],
+      [
+        'language' => ConfigurableLanguage::load('it'),
+        'absolute' => FALSE,
+      ]
+    );
+    $revision_deletion_href = $revision_deletion_url->toString();
+    $this->getSession()->getDriver()->click("//a[@href='$revision_deletion_href']");
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $this->drupalLogin($this->currentAccount);
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefExists($it_delete_href);
+
+    // Verify that now the translation can be deleted.
+    $this->drupalGet($it_delete_url);
+    $this->drupalPostForm(NULL, [], 'Delete Italian translation');
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertFalse($entity->hasTranslation('it'));
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+    $this->assertTrue($it_revision->wasDefaultRevision());
+    $this->assertTrue($it_revision->hasTranslation('it'));
+    $this->assertTrue($it_revision->getRevisionId() < $entity->getRevisionId());
+    $this->drupalGet($overview_url);
+    $this->assertSession()->linkByHrefNotExists($this->getEditUrl($it_revision)->toString());
+    $this->assertSession()->linkByHrefExists($add_translation_href);
+  }
+
+}
-- 
GitLab