EntityUntranslatableFieldsConstraintValidator.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <?php
  2. namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
  3. use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  4. use Drupal\Core\Entity\ContentEntityInterface;
  5. use Drupal\Core\Entity\EntityChangesDetectionTrait;
  6. use Drupal\Core\Entity\EntityTypeManagerInterface;
  7. use Symfony\Component\DependencyInjection\ContainerInterface;
  8. use Symfony\Component\Validator\Constraint;
  9. use Symfony\Component\Validator\ConstraintValidator;
  10. /**
  11. * Validates the EntityChanged constraint.
  12. */
  13. class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
  14. use EntityChangesDetectionTrait;
  15. /**
  16. * The entity type manager.
  17. *
  18. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  19. */
  20. protected $entityTypeManager;
  21. /**
  22. * Constructs an EntityUntranslatableFieldsConstraintValidator object.
  23. *
  24. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  25. * The entity type manager.
  26. */
  27. public function __construct(EntityTypeManagerInterface $entity_type_manager) {
  28. $this->entityTypeManager = $entity_type_manager;
  29. }
  30. /**
  31. * {@inheritdoc}
  32. */
  33. public static function create(ContainerInterface $container) {
  34. return new static(
  35. $container->get('entity_type.manager')
  36. );
  37. }
  38. /**
  39. * {@inheritdoc}
  40. */
  41. public function validate($entity, Constraint $constraint) {
  42. /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  43. /** @var \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraint $constraint */
  44. // Untranslatable field restrictions apply only to revisions of multilingual
  45. // entities.
  46. if ($entity->isNew() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
  47. return;
  48. }
  49. if ($entity->isDefaultRevision() && !$entity->isDefaultTranslationAffectedOnly()) {
  50. return;
  51. }
  52. // To avoid unintentional reverts and data losses, we forbid changes to
  53. // untranslatable fields in pending revisions for multilingual entities. The
  54. // only case where changes in pending revisions are acceptable is when
  55. // untranslatable fields affect only the default translation, in which case
  56. // a pending revision contains only one affected translation. Even in this
  57. // case, multiple translations would be affected in a single revision, if we
  58. // allowed changes to untranslatable fields while editing non-default
  59. // translations, so that is forbidden too. For the same reason, when changes
  60. // to untranslatable fields affect all translations, we can only allow them
  61. // in default revisions.
  62. if ($this->hasUntranslatableFieldsChanges($entity)) {
  63. if ($entity->isDefaultTranslationAffectedOnly()) {
  64. foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
  65. if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
  66. $this->context->addViolation($constraint->defaultTranslationMessage);
  67. break;
  68. }
  69. }
  70. }
  71. else {
  72. $this->context->addViolation($constraint->defaultRevisionMessage);
  73. }
  74. }
  75. }
  76. /**
  77. * Checks whether an entity has untranslatable field changes.
  78. *
  79. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  80. * A content entity object.
  81. *
  82. * @return bool
  83. * TRUE if untranslatable fields have changes, FALSE otherwise.
  84. */
  85. protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity) {
  86. $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
  87. /** @var \Drupal\Core\Entity\ContentEntityInterface $original */
  88. if (isset($entity->original)) {
  89. $original = $entity->original;
  90. }
  91. else {
  92. $original = $this->entityTypeManager
  93. ->getStorage($entity->getEntityTypeId())
  94. ->loadRevision($entity->getLoadedRevisionId());
  95. }
  96. foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
  97. if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
  98. continue;
  99. }
  100. $items = $entity->get($field_name)->filterEmptyItems();
  101. $original_items = $original->get($field_name)->filterEmptyItems();
  102. if ($items->hasAffectingChanges($original_items, $entity->getUntranslated()->language()->getId())) {
  103. return TRUE;
  104. }
  105. }
  106. return FALSE;
  107. }
  108. }