summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityBase.php33
-rw-r--r--core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php19
-rw-r--r--core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php36
-rw-r--r--core/lib/Drupal/Core/Entity/EntityType.php9
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php20
-rw-r--r--core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php128
-rw-r--r--core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php9
-rw-r--r--core/modules/content_translation/config/schema/content_translation.schema.yml6
-rw-r--r--core/modules/content_translation/content_translation.admin.inc30
-rw-r--r--core/modules/content_translation/content_translation.module16
-rw-r--r--core/modules/content_translation/src/BundleTranslationSettingsInterface.php35
-rw-r--r--core/modules/content_translation/src/ContentTranslationHandler.php57
-rw-r--r--core/modules/content_translation/src/ContentTranslationManager.php19
-rw-r--r--core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php2
-rw-r--r--core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php119
-rw-r--r--core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php7
-rw-r--r--core/modules/system/tests/modules/entity_test/entity_test.module3
-rw-r--r--core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php3
-rw-r--r--core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php1
-rw-r--r--core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php61
-rw-r--r--core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php6
21 files changed, 580 insertions, 39 deletions
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 13c452c..ad6c62d 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -20,6 +20,10 @@ use Drupal\Core\TypedData\TypedDataInterface;
*/
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface {
+ use EntityChangesDetectionTrait {
+ getFieldsToSkipFromTranslationChangesCheck as traitGetFieldsToSkipFromTranslationChangesCheck;
+ }
+
/**
* The plain data values of the contained fields.
*
@@ -1373,17 +1377,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
* An array of field names.
*/
protected function getFieldsToSkipFromTranslationChangesCheck() {
- /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
- $entity_type = $this->getEntityType();
- // A list of known revision metadata fields which should be skipped from
- // the comparision.
- $fields = [
- $entity_type->getKey('revision'),
- 'revision_translation_affected',
- ];
- $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
-
- return $fields;
+ return $this->traitGetFieldsToSkipFromTranslationChangesCheck($this);
}
/**
@@ -1423,10 +1417,15 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
// The list of fields to skip from the comparision.
$skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck();
+ // We also check untranslatable fields, so that a change to those will mark
+ // all translations as affected, unless they are configured to only affect
+ // the default translation.
+ $skip_untranslatable_fields = !$this->isDefaultTranslation() && $this->isDefaultTranslationAffectedOnly();
+
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
// @todo Avoid special-casing the following fields. See
// https://www.drupal.org/node/2329253.
- if (in_array($field_name, $skip_fields, TRUE)) {
+ if (in_array($field_name, $skip_fields, TRUE) || ($skip_untranslatable_fields && !$definition->isTranslatable())) {
continue;
}
$field = $this->get($field_name);
@@ -1447,4 +1446,14 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
return FALSE;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function isDefaultTranslationAffectedOnly() {
+ $bundle_name = $this->bundle();
+ $bundle_info = \Drupal::service('entity_type.bundle.info')
+ ->getBundleInfo($this->getEntityTypeId());
+ return !empty($bundle_info[$bundle_name]['untranslatable_fields.default_translation_affected']);
+ }
+
}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index c85c725..b82af9b 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -227,13 +227,13 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
$active_langcode = $entity->language()->getId();
$skipped_field_names = array_flip($this->getRevisionTranslationMergeSkippedFieldNames());
- // Default to preserving the untranslatable field values in the default
- // revision, otherwise we may expose data that was not meant to be
- // accessible.
+ // By default we copy untranslatable field values from the default
+ // revision, unless they are configured to affect only the default
+ // translation. This way we can ensure we always have only one affected
+ // translation in pending revisions. This constraint is enforced by
+ // EntityUntranslatableFieldsConstraintValidator.
if (!isset($keep_untranslatable_fields)) {
- // @todo Implement a more complete default logic in
- // https://www.drupal.org/project/drupal/issues/2878556.
- $keep_untranslatable_fields = FALSE;
+ $keep_untranslatable_fields = $entity->isDefaultTranslation() && $entity->isDefaultTranslationAffectedOnly();
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
@@ -262,6 +262,13 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
// No need to copy untranslatable field values more than once.
$keep_untranslatable_fields = TRUE;
}
+
+ // The "original" property is used in various places to detect changes in
+ // field values with respect to the stored ones. If the property is not
+ // defined, the stored version is loaded explicitly. Since the merged
+ // revision generated here is not stored anywhere, we need to populate the
+ // "original" property manually, so that changes can be properly detected.
+ $new_revision->original = clone $new_revision;
}
// Eventually mark the new revision as such.
diff --git a/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
new file mode 100644
index 0000000..e598e8c
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides helper methods to detect changes in an entity object.
+ *
+ * @internal This may be replaced by a proper entity comparison handler.
+ */
+trait EntityChangesDetectionTrait {
+
+ /**
+ * Returns an array of field names to skip when checking for changes.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * A content entity object.
+ *
+ * @return string[]
+ * An array of field names.
+ */
+ protected function getFieldsToSkipFromTranslationChangesCheck(ContentEntityInterface $entity) {
+ /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+ $entity_type = $entity->getEntityType();
+
+ // A list of known revision metadata fields which should be skipped from
+ // the comparision.
+ $fields = [
+ $entity_type->getKey('revision'),
+ $entity_type->getKey('revision_translation_affected'),
+ ];
+ $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
+
+ return $fields;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 19a52ca..f5204f8 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -311,11 +311,16 @@ class EntityType extends PluginDefinition implements EntityTypeInterface {
$this->checkStorageClass($this->handlers['storage']);
}
- // Automatically add the EntityChanged constraint if the entity type tracks
- // the changed time.
+ // Automatically add the "EntityChanged" constraint if the entity type
+ // tracks the changed time.
if ($this->entityClassImplements(EntityChangedInterface::class)) {
$this->addConstraint('EntityChanged');
}
+ // Automatically add the "EntityUntranslatableFields" constraint if we have
+ // an entity type supporting translatable fields and pending revisions.
+ if ($this->entityClassImplements(ContentEntityInterface::class)) {
+ $this->addConstraint('EntityUntranslatableFields');
+ }
// Ensure a default list cache tag is set.
if (empty($this->list_cache_tags)) {
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php
new file mode 100644
index 0000000..0044b6b
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Validation constraint for the entity changed timestamp.
+ *
+ * @Constraint(
+ * id = "EntityUntranslatableFields",
+ * label = @Translation("Entity untranslatable fields", context = "Validation"),
+ * type = {"entity"}
+ * )
+ */
+class EntityUntranslatableFieldsConstraint extends Constraint {
+
+ public $message = 'Non translatable fields can only be changed when updating the current revision or the original language.';
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
new file mode 100644
index 0000000..6ac7d03
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangesDetectionTrait;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\ChangedFieldItemList;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the EntityChanged constraint.
+ */
+class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+ use EntityChangesDetectionTrait;
+
+ /**
+ * The entity type manager.
+ *
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ */
+ protected $entityTypeManager;
+
+ /**
+ * Constructs an EntityUntranslatableFieldsConstraintValidator object.
+ *
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager.
+ */
+ public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+ $this->entityTypeManager = $entity_type_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity_type.manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($entity, Constraint $constraint) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
+ // Untranslatable field restrictions apply only to pending revisions of
+ // multilingual entities.
+ if ($entity->isNew() || $entity->isDefaultRevision() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
+ return;
+ }
+
+ // To avoid unintentional reverts and data losses, we forbid changes to
+ // untranslatable fields in pending revisions for multilingual entities. The
+ // only case where changes in pending revisions are acceptable is when
+ // untranslatable fields affect only the default translation, in which case
+ // a pending revision contains only one affected translation. Even in this
+ // case, multiple translations would be affected in a single revision, if we
+ // allowed changes to untranslatable fields while editing non-default
+ // translations, so that is forbidden too.
+ if ($this->hasUntranslatableFieldsChanges($entity)) {
+ if ($entity->isDefaultTranslationAffectedOnly()) {
+ foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
+ if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
+ $this->context->addViolation($constraint->message);
+ break;
+ }
+ }
+ }
+ else {
+ $this->context->addViolation($constraint->message);
+ }
+ }
+ }
+
+ /**
+ * Checks whether an entity has untranslatable field changes.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * A content entity object.
+ *
+ * @return bool
+ * TRUE if untranslatable fields have changes, FALSE otherwise.
+ */
+ protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity) {
+ $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $original */
+ if (isset($entity->original)) {
+ $original = $entity->original;
+ }
+ else {
+ $original = $this->entityTypeManager
+ ->getStorage($entity->getEntityTypeId())
+ ->loadRevision($entity->getLoadedRevisionId());
+ }
+
+ foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
+ if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
+ continue;
+ }
+
+ // When saving entities in the user interface, the changed timestamp is
+ // automatically incremented by ContentEntityForm::submitForm() even if
+ // nothing was actually changed. Thus, the changed time needs to be
+ // ignored when determining whether there are any actual changes in the
+ // entity.
+ $field = $entity->get($field_name);
+ if ($field instanceof ChangedFieldItemList) {
+ continue;
+ }
+
+ $items = $field->filterEmptyItems();
+ $original_items = $original->get($field_name)->filterEmptyItems();
+ if (!$items->equals($original_items)) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php b/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
index e1bc1e3..7460eeb 100644
--- a/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
+++ b/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
@@ -71,4 +71,13 @@ interface TranslatableRevisionableInterface extends TranslatableInterface, Revis
*/
public function setRevisionTranslationAffectedEnforced($enforced);
+ /**
+ * Checks if untranslatable fields should affect only the default translation.
+ *
+ * @return bool
+ * TRUE if untranslatable fields should affect only the default translation,
+ * FALSE otherwise.
+ */
+ public function isDefaultTranslationAffectedOnly();
+
}
diff --git a/core/modules/content_translation/config/schema/content_translation.schema.yml b/core/modules/content_translation/config/schema/content_translation.schema.yml
index 808a6e7..7367672 100644
--- a/core/modules/content_translation/config/schema/content_translation.schema.yml
+++ b/core/modules/content_translation/config/schema/content_translation.schema.yml
@@ -18,3 +18,9 @@ language.content_settings.*.*.third_party.content_translation:
enabled:
type: boolean
label: 'Content translation enabled'
+ bundle_settings:
+ type: sequence
+ label: 'Content translation bundle settings'
+ sequence:
+ type: string
+ label: 'Bundle settings values'
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index bf33ba0..f157749 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -5,6 +5,7 @@
* The content translation administration forms.
*/
+use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeInterface;
@@ -83,6 +84,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
return;
}
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
$content_translation_manager = \Drupal::service('content_translation.manager');
$default = $form['entity_types']['#default_value'];
foreach ($default as $entity_type_id => $enabled) {
@@ -110,6 +112,23 @@ function _content_translation_form_language_content_settings_form_alter(array &$
continue;
}
+ // Displayed the "shared fields widgets" toggle.
+ if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+ $settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
+ $form['settings'][$entity_type_id][$bundle]['settings']['content_translation']['untranslatable_fields_hide'] = [
+ '#type' => 'checkbox',
+ '#title' => t('Hide non translatable fields on translation forms'),
+ '#default_value' => !empty($settings['untranslatable_fields_hide']),
+ '#states' => [
+ 'visible' => [
+ ':input[name="settings[' . $entity_type_id . '][' . $bundle . '][translatable]"]' => [
+ 'checked' => TRUE,
+ ],
+ ],
+ ],
+ ];
+ }
+
$fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
if ($fields) {
foreach ($fields as $field_name => $definition) {
@@ -317,6 +336,8 @@ function content_translation_form_language_content_settings_validate(array $form
* @see content_translation_admin_settings_form_validate()
*/
function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
+ $content_translation_manager = \Drupal::service('content_translation.manager');
$entity_types = $form_state->getValue('entity_types');
$settings = &$form_state->getValue('settings');
@@ -347,7 +368,12 @@ function content_translation_form_language_content_settings_submit(array $form,
}
if (isset($bundle_settings['translatable'])) {
// Store whether a bundle has translation enabled or not.
- \Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
+ $content_translation_manager->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
+
+ // Store any other bundle settings.
+ if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+ $content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, $bundle_settings['settings']['content_translation']);
+ }
// Save translation_sync settings.
if (!empty($bundle_settings['columns'])) {
@@ -367,8 +393,8 @@ function content_translation_form_language_content_settings_submit(array $form,
}
}
}
+
// Ensure entity and menu router information are correctly rebuilt.
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->setRebuildNeeded();
-
}
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 6a0c229..48feccc 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -5,6 +5,7 @@
* Allows entities to be translated into different languages.
*/
+use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
@@ -161,9 +162,15 @@ function content_translation_entity_type_alter(array &$entity_types) {
* Implements hook_entity_bundle_info_alter().
*/
function content_translation_entity_bundle_info_alter(&$bundles) {
- foreach ($bundles as $entity_type => &$info) {
+ /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
+ $content_translation_manager = \Drupal::service('content_translation.manager');
+ foreach ($bundles as $entity_type_id => &$info) {
foreach ($info as $bundle => &$bundle_info) {
- $bundle_info['translatable'] = \Drupal::service('content_translation.manager')->isEnabled($entity_type, $bundle);
+ $bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
+ if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+ $settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
+ $bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']);
+ }
}
}
}
@@ -319,6 +326,11 @@ function content_translation_form_alter(array &$form, FormStateInterface $form_s
}
}
+ // The footer region, if defined, may contain multilingual widgets so we
+ // need to always display it.
+ if (isset($form['footer'])) {
+ $form['footer']['#multilingual'] = TRUE;
+ }
}
}
diff --git a/core/modules/content_translation/src/BundleTranslationSettingsInterface.php b/core/modules/content_translation/src/BundleTranslationSettingsInterface.php
new file mode 100644
index 0000000..df7b64b
--- /dev/null
+++ b/core/modules/content_translation/src/BundleTranslationSettingsInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\content_translation;
+
+/**
+ * Interface providing support for content translation bundle settings.
+ */
+interface BundleTranslationSettingsInterface {
+
+ /**
+ * Returns translation settings for the specified bundle.
+ *
+ * @param string $entity_type_id
+ * The entity type identifier.
+ * @param string $bundle
+ * The bundle name.
+ *
+ * @return array
+ * An associative array of values keyed by setting name.
+ */
+ public function getBundleTranslationSettings($entity_type_id, $bundle);
+
+ /**
+ * Sets translation settings for the specified bundle.
+ *
+ * @param string $entity_type_id
+ * The entity type identifier.
+ * @param string $bundle
+ * The bundle name.
+ * @param array $settings
+ * An associative array of values keyed by setting name.
+ */
+ public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings);
+
+}
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 7e6a7ca..cb6776f 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -5,6 +5,7 @@ namespace Drupal\content_translation;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityChangesDetectionTrait;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
@@ -13,8 +14,10 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\Entity\User;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -25,7 +28,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* @ingroup entity_api
*/
class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
+
+ use EntityChangesDetectionTrait;
use DependencySerializationTrait;
+ use StringTranslationTrait;
/**
* The type of the entity being translated.
@@ -71,6 +77,13 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
protected $fieldStorageDefinitions;
/**
+ * The messenger service.
+ *
+ * @var \Drupal\Core\Messenger\MessengerInterface
+ */
+ protected $messenger;
+
+ /**
* Initializes an instance of the content translation controller.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
@@ -83,14 +96,17 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
* The entity manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
+ * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+ * The messenger service.
*/
- public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
+ public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, MessengerInterface $messenger) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->languageManager = $language_manager;
$this->manager = $manager;
$this->currentUser = $current_user;
$this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
+ $this->messenger = $messenger;
}
/**
@@ -102,7 +118,8 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
$container->get('language_manager'),
$container->get('content_translation.manager'),
$container->get('entity.manager'),
- $container->get('current_user')
+ $container->get('current_user'),
+ $container->get('messenger')
);
}
@@ -269,6 +286,8 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
$form_object = $form_state->getFormObject();
$form_langcode = $form_object->getFormLangcode($form_state);
$entity_langcode = $entity->getUntranslated()->language()->getId();
@@ -512,6 +531,20 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
$ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details']);
}
+ /** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
+ $form_object = $form_state->getFormObject();
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+ $entity = $form_object->getEntity();
+ $display_translatability_clue = !$entity->isDefaultTranslationAffectedOnly();
+ $hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly() && !$entity->isDefaultTranslation();
+ $translation_form = $form_state->get(['content_translation', 'translation_form']);
+ $display_warning = FALSE;
+
+ // We use field definitions to identify untranslatable field widgets to be
+ // hidden. Fields that are not involved in translation changes checks should
+ // not be affected by this logic (the "revision_log" field, for instance).
+ $field_definitions = array_diff_key($entity->getFieldDefinitions(), array_flip($this->getFieldsToSkipFromTranslationChangesCheck($entity)));
+
foreach (Element::children($element) as $key) {
if (!isset($element[$key]['#type'])) {
$this->entityFormSharedElements($element[$key], $form_state, $form);
@@ -524,10 +557,17 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
// Elements are considered to be non multilingual by default.
if (empty($element[$key]['#multilingual'])) {
// If we are displaying a multilingual entity form we need to provide
- // translatability clues, otherwise the shared form elements should be
- // hidden.
- if (!$form_state->get(['content_translation', 'translation_form'])) {
- $this->addTranslatabilityClue($element[$key]);
+ // translatability clues, otherwise the non-multilingual form elements
+ // should be hidden.
+ if (!$translation_form) {
+ if ($display_translatability_clue) {
+ $this->addTranslatabilityClue($element[$key]);
+ }
+ // Hide widgets for untranslatable fields.
+ if ($hide_untranslatable_fields && isset($field_definitions[$key])) {
+ $element[$key]['#access'] = FALSE;
+ $display_warning = TRUE;
+ }
}
else {
$element[$key]['#access'] = FALSE;
@@ -536,6 +576,11 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
}
}
+ if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
+ $url = $entity->getUntranslated()->toUrl('edit-form')->toString();
+ $this->messenger->addWarning($this->t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.', [':url' => $url]));
+ }
+
return $element;
}
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index da8bd1c..8b3831a 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityManagerInterface;
/**
* Provides common functionality for content translation.
*/
-class ContentTranslationManager implements ContentTranslationManagerInterface {
+class ContentTranslationManager implements ContentTranslationManagerInterface, BundleTranslationSettingsInterface {
/**
* The entity type manager.
@@ -106,6 +106,23 @@ class ContentTranslationManager implements ContentTranslationManagerInterface {
}
/**
+ * {@inheritdoc}
+ */
+ public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings) {
+ $config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
+ $config->setThirdPartySetting('content_translation', 'bundle_settings', $settings)
+ ->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBundleTranslationSettings($entity_type_id, $bundle) {
+ $config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
+ return $config->getThirdPartySetting('content_translation', 'bundle_settings', []);
+ }
+
+ /**
* Loads a content language config entity based on the entity type and bundle.
*
* @param string $entity_type_id
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
index ec7f87d..d3e81a6 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
@@ -138,7 +138,7 @@ abstract class ContentTranslationTestBase extends BrowserTestBase {
* Returns an array of permissions needed for the administrator.
*/
protected function getAdministratorPermissions() {
- return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer content translation']);
+ return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer languages', 'administer content translation']);
}
/**
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php
new file mode 100644
index 0000000..31431e6
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Functional;
+
+use Drupal\Core\Url;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests the untranslatable fields behaviors.
+ *
+ * @group content_translation
+ */
+class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = ['language', 'content_translation', 'entity_test'];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ // Configure one field as untranslatable.
+ $this->drupalLogin($this->administrator);
+ $edit = [
+ 'settings[' . $this->entityTypeId . '][' . $this->bundle . '][fields][' . $this->fieldName . ']' => 0,
+ ];
+ $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+
+ /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
+ $entity_field_manager = $this->container->get('entity_field.manager');
+ $entity_field_manager->clearCachedFieldDefinitions();
+ $definitions = $entity_field_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
+ $this->assertFalse($definitions[$this->fieldName]->isTranslatable());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getEditorPermissions() {
+ return array_merge(parent::getTranslatorPermissions(), ['administer entity_test content', 'view test entity']);
+ }
+
+ /**
+ * Tests that hiding untranslatable field widgets works correctly.
+ */
+ public function testHiddenWidgets() {
+ /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
+ $entity_type_manager = $this->container->get('entity_type.manager');
+ $id = $this->createEntity([], 'en');
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+ $entity = $entity_type_manager
+ ->getStorage($this->entityTypeId)
+ ->load($id);
+
+ // Check that the untranslatable field widget is displayed on the edit form
+ // and no translatability clue is displayed yet.
+ $this->drupalGet($entity->toUrl('edit-form'));
+ $field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
+ $this->assertNotEmpty($this->xpath($field_xpath));
+ $clue_xpath = '//label[@for="edit-' . strtr($this->fieldName, '_', '-') . '-0-value"]/span[text()="(all languages)"]';
+ $this->assertEmpty($this->xpath($clue_xpath));
+
+ // Add a translation and check that the untranslatable field widget is
+ // displayed on the translation and edit forms along with translatability
+ // clues.
+ $add_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", [
+ $entity->getEntityTypeId() => $entity->id(),
+ 'source' => 'en',
+ 'target' => 'it'
+ ]);
+ $this->drupalGet($add_url);
+ $this->assertNotEmpty($this->xpath($field_xpath));
+ $this->assertNotEmpty($this->xpath($clue_xpath));
+ $this->drupalPostForm(NULL, [], 'Save');
+
+ // Check that the widget is displayed along with its clue in the edit form
+ // for both languages.
+ $this->drupalGet($entity->toUrl('edit-form'));
+ $this->assertNotEmpty($this->xpath($field_xpath));
+ $this->assertNotEmpty($this->xpath($clue_xpath));
+ $it_language = ConfigurableLanguage::load('it');
+ $this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
+ $this->assertNotEmpty($this->xpath($field_xpath));
+ $this->assertNotEmpty($this->xpath($clue_xpath));
+
+ // Configure untranslatable field widgets to be hidden on non-default
+ // language edit forms.
+ $edit = [
+ 'settings[' . $this->entityTypeId . '][' . $this->bundle . '][settings][content_translation][untranslatable_fields_hide]' => 1,
+ ];
+ $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+
+ // Verify that the widget is displayed in the default language edit form,
+ // but no clue is displayed.
+ $this->drupalGet($entity->toUrl('edit-form'));
+ $field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
+ $this->assertNotEmpty($this->xpath($field_xpath));
+ $this->assertEmpty($this->xpath($clue_xpath));
+
+ // Verify no widget is displayed on the non-default language edit form.
+ $this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
+ $this->assertEmpty($this->xpath($field_xpath));
+ $this->assertEmpty($this->xpath($clue_xpath));
+
+ // Verify a warning is displayed.
+ $this->assertSession()->pageTextContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
+ $edit_path = $entity->toUrl('edit-form')->toString();
+ $link_xpath = '//a[@href=:edit_path and text()="Edit them on the original language form"]';
+ $elements = $this->xpath($link_xpath, [':edit_path' => $edit_path]);
+ $this->assertNotEmpty($elements);
+ }
+
+}
diff --git a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
index 0c68d8b..eb07b7a 100644
--- a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
+++ b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
@@ -88,10 +88,15 @@ class NodeRevisionRevertTranslationForm extends NodeRevisionRevertForm {
$this->langcode = $langcode;
$form = parent::buildForm($form, $form_state, $node_revision);
+ // Unless untranslatable fields are configured to affect only the default
+ // translation, we need to ask the user whether they should be included in
+ // the revert process.
+ $default_translation_affected = $this->revision->isDefaultTranslationAffectedOnly();
$form['revert_untranslated_fields'] = [
'#type' => 'checkbox',
'#title' => $this->t('Revert content shared among translations'),
- '#default_value' => FALSE,
+ '#default_value' => $default_translation_affected && $this->revision->getTranslation($this->langcode)->isDefaultTranslation(),
+ '#access' => !$default_translation_affected,
];
return $form;
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 7d4df70..a934ad6 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/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 724385f..b4493e3 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -58,7 +58,8 @@ class EntityTestMulRev extends EntityTestRev {
$fields['non_mul_field'] = BaseFieldDefinition::create('string')
->setLabel(t('Non translatable'))
- ->setDescription(t('A non-translatable string field'));
+ ->setDescription(t('A non-translatable string field'))
+ ->setRevisionable(TRUE);
return $fields;
}
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 3913075..13a169d 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 @@ class EntityTestMulRevChanged extends EntityTestMulChanged {
$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 213d190..ab01d91 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php
@@ -3,6 +3,7 @@
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\user\Entity\User;
@@ -216,12 +217,32 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
$sets['Default behavior - Untranslatable fields affect all revisions'] = [
[
['en', TRUE, TRUE],
+ ['it', FALSE, TRUE, FALSE],
+ ['en', FALSE, TRUE, FALSE],
+ ['en', TRUE, TRUE],
['it', TRUE, TRUE],
['en', FALSE],
['it', FALSE],
['en', TRUE],
['it', TRUE],
],
+ 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;
@@ -234,11 +255,20 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
* 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 ::createRevision
+ * @covers \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraintValidator::validate
*
* @dataProvider dataTestUntranslatableFields
*/
- public function testUntranslatableFields($sequence) {
+ 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 = EntityTestMulRev::create();
$entity->set('non_mul_field', 0);
@@ -289,14 +319,23 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
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] = EntityTestMulRev::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,
@@ -304,10 +343,12 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
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
@@ -332,10 +373,21 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
$this->assertEquals($previous_label, $entity->label(), $this->formatMessage('Loaded translatable field value does not match the previous one.'));
}
+ // Check that the previous untranslatable field value is loaded in the new
+ // revision as expected. When we are dealing with a non default translation
+ // the expected value is always the one stored in the default revision, as
+ // untranslatable fields can only be changed in the default translation or
+ // in the default revision, depending on the configured mode.
$value = $entity->get('non_mul_field')->value;
if (isset($previous_untranslatable_field_value)) {
$this->assertEquals($previous_untranslatable_field_value, $value, $this->formatMessage('Loaded untranslatable field value does not match the previous one.'));
}
+ elseif (!$entity->isDefaultTranslation()) {
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
+ $default_revision = $this->storage->loadUnchanged($entity->id());
+ $expected_value = $default_revision->get('non_mul_field')->value;
+ $this->assertEquals($expected_value, $value, $this->formatMessage('Loaded untranslatable field value does not match the previous one.'));
+ }
// Perform a change and store it.
$label = $this->generateNewEntityLabel($entity, $previous_revision_id, TRUE);
@@ -345,12 +397,13 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
// the new value, besides the upcoming revision ID. Useful to analyze test
// failures.
$prev = 0;
- if (isset($value)) {
- preg_match('/^\d+ -> (\d+)$/', $value, $matches);
+ if (isset($previous_untranslatable_field_value)) {
+ preg_match('/^\d+ -> (\d+)$/', $previous_untranslatable_field_value, $matches);
$prev = $matches[1];
}
$value = $prev . ' -> ' . ($entity->getLoadedRevisionId() + 1);
$entity->set('non_mul_field', $value);
+ $previous_untranslatable_field_value = $value;
}
$violations = $entity->validate();
@@ -378,7 +431,7 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
// 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) {
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
index a58b47c..cdf4b96 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
@@ -24,7 +24,11 @@ class EntityTypeConstraintsTest extends EntityKernelTestBase {
// Test reading the annotation. There should be two constraints, the defined
// constraint and the automatically added EntityChanged constraint.
$entity_type = $this->entityManager->getDefinition('entity_test_constraints');
- $default_constraints = ['NotNull' => [], 'EntityChanged' => NULL];
+ $default_constraints = [
+ 'NotNull' => [],
+ 'EntityChanged' => NULL,
+ 'EntityUntranslatableFields' => NULL,
+ ];
$this->assertEqual($default_constraints, $entity_type->getConstraints());
// Enable our test module and test extending constraints.