diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index be72b6c6ba..b84190435f 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -370,11 +370,12 @@ public function createNewRevision($default_revision = TRUE, $copy_untranslatable $new_revision = $new_revision->hasTranslation($active_langcode) ? $new_revision->getTranslation($active_langcode) : $new_revision->addTranslation($active_langcode); - // We skip untranslatable fields unless we are dealing with the default - // translation, otherwise more than one language would be affected in this - // revision. + // We skip untranslatable fields unless they are configured to affect only + // the default translation, so that we can ensure we always have only one + // affected translation in pending revisions. This constraint is enforced + // by EntityUntranslatableFieldsConstraintValidator. if (!isset($copy_untranslatable_fields)) { - $copy_untranslatable_fields = $this->isDefaultTranslation(); + $copy_untranslatable_fields = $this->isDefaultTranslation() && $this->isDefaultTranslationAffectedOnly(); } foreach ($new_revision->getFieldDefinitions() as $field_name => $definition) { if ($copy_untranslatable_fields || $definition->isTranslatable()) { diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module index 7d4df70bae..a934ad65ac 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.module +++ b/core/modules/system/tests/modules/entity_test/entity_test.module @@ -217,6 +217,9 @@ function entity_test_entity_bundle_info_alter(&$bundles) { if ($state->get('entity_test.translation')) { foreach ($all_bundle_info as $bundle_name => &$bundle_info) { $bundle_info['translatable'] = TRUE; + if ($state->get('entity_test.untranslatable_fields.default_translation_affected')) { + $bundle_info['untranslatable_fields.default_translation_affected'] = TRUE; + } } } } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php index 391307576e..13a169df49 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php @@ -71,6 +71,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['name']->setRevisionable(TRUE); $fields['user_id']->setRevisionable(TRUE); $fields['changed']->setRevisionable(TRUE); + $fields['not_translatable']->setRevisionable(TRUE); return $fields; } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php index 380ecfe2f0..df41d76167 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\Query\Queries; +use Drupal\Core\Language\LanguageInterface; use Drupal\entity_test\Entity\EntityTestMulRevChanged; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\user\Entity\User; @@ -208,6 +209,75 @@ public function testDecoupledPendingRevisions($sequence) { $this->assertEquals(count($sequence), $revision_id); } + /** + * Data provider for ::testUntranslatableFields. + */ + public function dataTestUntranslatableFields() { + $sets = []; + + $sets['Default behavior - Only default revisions supported'] = [ + [ + ['en', TRUE, TRUE], + ['it', FALSE, TRUE, FALSE], + ['en', FALSE, TRUE, FALSE], + ['en', TRUE, TRUE], + ['it', TRUE, TRUE], + ['en', FALSE], + ['it', FALSE], + ], + FALSE, + ]; + + $sets['Alternative behavior - Untranslatable fields affect only default translation'] = [ + [ + ['en', TRUE, TRUE], + ['it', FALSE, TRUE, FALSE], + ['en', FALSE, TRUE], + ['it', FALSE], + ['it', TRUE], + ['en', TRUE, TRUE], + ['it', FALSE], + ['en', FALSE], + ['it', TRUE], + ['en', TRUE, TRUE], + ], + TRUE, + ]; + + return $sets; + } + + /** + * Tests that untranslatable fields are handled correctly. + * + * @param array[] $sequence + * An array with arrays of arguments for the ::doSaveNewRevision() method as + * values. Every child array corresponds to a method invocation. + * + * @param bool $default_translation_affected + * Whether untranslatable field changes affect all revisions or only the + * default revision. + * + * @covers ::createNewRevision + * @covers \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraintValidator::validate + * + * @dataProvider dataTestUntranslatableFields + */ + public function testUntranslatableFields($sequence, $default_translation_affected) { + // Configure the untranslatable fields edit mode. + $this->state->set('entity_test.untranslatable_fields.default_translation_affected', $default_translation_affected); + $this->bundleInfo->clearCachedBundles(); + + // Test that a new entity is always valid. + $entity = EntityTestMulRevChanged::create(); + $entity->set('not_translatable', 0); + $violations = $entity->validate(); + $this->assertEmpty($violations); + + // Test the specified sequence. + $this->doTestEditSequence($sequence); + } + /** * Actually tests an edit step sequence. * @@ -246,14 +316,23 @@ protected function doTestEditSequence($sequence) { protected function doEditStep($active_langcode, $default_revision, $untranslatable_update = FALSE, $valid = TRUE) { $this->stepInfo = [$active_langcode, $default_revision, $untranslatable_update, $valid]; + // If changes to untranslatable fields affect only the default translation, + // we can different values for untranslatable fields in the various + // revision translations, so we need to track their previous value per + // language. + $all_translations_affected = !$this->state->get('entity_test.untranslatable_fields.default_translation_affected'); + $previous_untranslatable_field_langcode = $all_translations_affected ? LanguageInterface::LANGCODE_DEFAULT : $active_langcode; + // Initialize previous data tracking. if (!isset($this->translations)) { $this->translations[$active_langcode] = EntityTestMulRevChanged::create(); $this->previousRevisionId[$active_langcode] = 0; + $this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode] = NULL; } if (!isset($this->translations[$active_langcode])) { $this->translations[$active_langcode] = reset($this->translations)->addTranslation($active_langcode); $this->previousRevisionId[$active_langcode] = 0; + $this->previousUntranslatableFieldValue[$active_langcode] = NULL; } // We want to update previous data only if we expect a valid result, @@ -261,10 +340,12 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab if ($valid) { $entity = &$this->translations[$active_langcode]; $previous_revision_id = &$this->previousRevisionId[$active_langcode]; + $previous_untranslatable_field_value = &$this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode]; } else { $entity = clone $this->translations[$active_langcode]; $previous_revision_id = $this->previousRevisionId[$active_langcode]; + $previous_untranslatable_field_value = $this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode]; } // Check that after instantiating a new revision for the specified @@ -328,7 +409,7 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab // translation was marked as affected. foreach ($entity->getTranslationLanguages() as $langcode => $language) { $translation = $entity->getTranslation($langcode); - $rta_expected = $langcode == $active_langcode || $untranslatable_update; + $rta_expected = $langcode == $active_langcode || ($untranslatable_update && $all_translations_affected); $this->assertEquals($rta_expected, $translation->isRevisionTranslationAffected(), $this->formatMessage("'$langcode' translation incorrectly affected")); $label_expected = $label; if ($langcode !== $active_langcode) {