diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php index 7cbc51451808dcc80b0294f5545ec723eece802e..b906d49803981fe8e70b9eaaa1bcf42721580bf1 100644 --- a/core/modules/content_translation/src/Controller/ContentTranslationController.php +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php @@ -68,6 +68,33 @@ public static function create(ContainerInterface $container) { */ public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) { $source_langcode = $source->getId(); + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId()); + + // Once translations from the default revision are added, there may be + // additional draft translations that don't exist in the default revision. + // Add those translations too so that they aren't deleted when the new + // translation is saved. + /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */ + $default_revision = $storage->load($entity->id()); + // Check the entity isn't missing any translations. + $languages = $this->languageManager()->getLanguages(); + foreach ($languages as $language) { + $langcode = $language->getId(); + if ($entity->hasTranslation($langcode) || $target->getId() === $langcode) { + continue; + } + $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode); + if ($latest_revision_id) { + if ($default_revision->hasTranslation($langcode)) { + $existing_translation = $default_revision->getTranslation($langcode); + $existing_translation->setNewRevision(FALSE); + $existing_translation->isDefaultRevision(FALSE); + $existing_translation->setRevisionTranslationAffected(FALSE); + $entity->addTranslation($langcode, $existing_translation->toArray()); + } + } + } /** @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */ $source_translation = $entity->getTranslation($source_langcode); $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray()); diff --git a/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module b/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module index 50495a8c799b202413954134d27803e6f8fc89ca..99d14eb19f5de205dbe2bf1c27a1dcd8b5bb878a 100644 --- a/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module +++ b/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module @@ -68,3 +68,10 @@ function content_translation_test_form_node_form_alter(&$form, FormStateInterfac function content_translation_test_form_node_form_submit($form, FormStateInterface $form_state) { \Drupal::state()->set('test_field_only_en_fr', $form_state->getValue('test_field_only_en_fr')); } + +/** + * Implements hook_entity_translation_delete(). + */ +function content_translation_test_entity_translation_delete(EntityInterface $translation) { + \Drupal::state()->set('content_translation_test.translation_deleted', TRUE); +} diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e11056f56c56536e8fbb033f0820075287d9ff6f --- /dev/null +++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php @@ -0,0 +1,170 @@ +<?php + +namespace Drupal\Tests\content_translation\Functional; + +use Drupal\Core\Url; +use Drupal\language\Entity\ConfigurableLanguage; + +/** + * Tests that new translations do not delete existing ones. + * + * @group content_translation + */ +class ContentTranslationNewTranslationWithExistingRevisionsTest extends ContentTranslationPendingRevisionTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'content_moderation', + 'content_translation', + 'content_translation_test', + 'language', + 'node', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->enableContentModeration(); + } + + /** + * Tests a translation with a draft is not deleted. + */ + public function testDraftTranslationIsNotDeleted() { + $this->drupalLogin($this->translator); + + // Create a test node. + $values = [ + 'title' => "Test EN", + 'moderation_state' => 'published', + ]; + $id = $this->createEntity($values, 'en'); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->storage->load($id); + + // Add a published translation. + $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", + [ + $entity->getEntityTypeId() => $id, + 'source' => 'en', + 'target' => 'it', + ], + [ + 'language' => ConfigurableLanguage::load('it'), + 'absolute' => FALSE, + ] + ); + $this->drupalGet($add_translation_url); + $edit = [ + 'title[0][value]' => "Test IT", + 'moderation_state[0][state]' => 'published', + ]; + $this->submitForm($edit, 'Save (this translation)'); + $it_revision = $this->loadRevisionTranslation($entity, 'it'); + + // Add a draft translation. + $this->drupalGet($this->getEditUrl($it_revision)); + $edit = [ + 'title[0][value]' => "Test IT 2", + 'moderation_state[0][state]' => 'draft', + ]; + $this->submitForm($edit, 'Save (this translation)'); + + // Add a new draft translation. + $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", + [ + $entity->getEntityTypeId() => $id, + 'source' => 'en', + 'target' => 'fr', + ], + [ + 'language' => ConfigurableLanguage::load('fr'), + 'absolute' => FALSE, + ] + ); + $this->drupalGet($add_translation_url); + $edit = [ + 'title[0][value]' => "Test FR", + 'moderation_state[0][state]' => 'published', + ]; + $this->submitForm($edit, 'Save (this translation)'); + // Check the first translation still exists. + $entity = $this->storage->loadUnchanged($id); + $this->assertTrue($entity->hasTranslation('it')); + } + + /** + * Test translation delete hooks are not invoked. + */ + public function testCreatingNewDraftDoesNotInvokeDeleteHook() { + $this->drupalLogin($this->translator); + + // Create a test node. + $values = [ + 'title' => "Test EN", + 'moderation_state' => 'published', + ]; + $id = $this->createEntity($values, 'en'); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->storage->load($id); + + // Add a published translation. + $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", + [ + $entity->getEntityTypeId() => $id, + 'source' => 'en', + 'target' => 'it', + ], + [ + 'language' => ConfigurableLanguage::load('it'), + 'absolute' => FALSE, + ] + ); + $this->drupalGet($add_translation_url); + $edit = [ + 'title[0][value]' => "Test IT", + 'moderation_state[0][state]' => 'published', + ]; + $this->submitForm($edit, 'Save (this translation)'); + $it_revision = $this->loadRevisionTranslation($entity, 'it'); + + // Add a draft translation. + $this->drupalGet($this->getEditUrl($it_revision)); + $edit = [ + 'title[0][value]' => "Test IT 2", + 'moderation_state[0][state]' => 'draft', + ]; + $this->submitForm($edit, 'Save (this translation)'); + // Add a new draft translation. + $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", + [ + $entity->getEntityTypeId() => $id, + 'source' => 'en', + 'target' => 'fr', + ], + [ + 'language' => ConfigurableLanguage::load('fr'), + 'absolute' => FALSE, + ] + ); + $this->drupalGet($add_translation_url); + $edit = [ + 'title[0][value]' => "Test FR", + 'moderation_state[0][state]' => 'draft', + ]; + $this->submitForm($edit, 'Save (this translation)'); + // If the translation delete hook was incorrectly invoked, the state + // variable would be set. + $this->assertNull($this->container->get('state')->get('content_translation_test.translation_deleted')); + } + +}