Issue #2878556 by plach, matsbla, vijaycs85, Gábor Hojtsy, catch, gabesullice, effulgentsia, hchonov, hass, amateescu, xjm: Ensure that changes to untranslatable fields affect only one translation in pending revisions
parent
4e66ea208f
commit
27c3b40e39
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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.';
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -70,6 +76,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.
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -105,6 +105,23 @@ class ContentTranslationManager implements ContentTranslationManagerInterface {
|
|||
return $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
*
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -215,6 +216,9 @@ 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],
|
||||
|
@ -222,6 +226,23 @@ class EntityDecoupledTranslationRevisionsTest extends EntityKernelTestBase {
|
|||
['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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue