"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/modules/content_translation/src/Plugin/Validation/Constraint/ContentTranslationSynchronizedFieldsConstraintValidator.php" (26 Nov 2020, 9100 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ContentTranslationSynchronizedFieldsConstraintValidator.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\content_translation\Plugin\Validation\Constraint;
    4 
    5 use Drupal\content_translation\ContentTranslationManagerInterface;
    6 use Drupal\content_translation\FieldTranslationSynchronizerInterface;
    7 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
    8 use Drupal\Core\Entity\ContentEntityInterface;
    9 use Drupal\Core\Entity\EntityTypeManagerInterface;
   10 use Drupal\Core\Field\FieldDefinitionInterface;
   11 use Symfony\Component\DependencyInjection\ContainerInterface;
   12 use Symfony\Component\Validator\Constraint;
   13 use Symfony\Component\Validator\ConstraintValidator;
   14 
   15 /**
   16  * Checks that synchronized fields are handled correctly in pending revisions.
   17  *
   18  * As for untranslatable fields, two modes are supported:
   19  * - When changes to untranslatable fields are configured to affect all revision
   20  *   translations, synchronized field properties can be changed only in default
   21  *   revisions.
   22  * - When changes to untranslatable fields affect are configured to affect only
   23  *   the revision's default translation, synchronized field properties can be
   24  *   changed only when editing the default translation. This may lead to
   25  *   temporarily desynchronized values, when saving a pending revision for the
   26  *   default translation that changes a synchronized property. These are
   27  *   actually synchronized when saving changes to the default translation as a
   28  *   new default revision.
   29  *
   30  * @see \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraint
   31  * @see \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraintValidator
   32  *
   33  * @internal
   34  */
   35 class ContentTranslationSynchronizedFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
   36 
   37   /**
   38    * The entity type manager.
   39    *
   40    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   41    */
   42   protected $entityTypeManager;
   43 
   44   /**
   45    * The content translation manager.
   46    *
   47    * @var \Drupal\content_translation\ContentTranslationManagerInterface
   48    */
   49   protected $contentTranslationManager;
   50 
   51   /**
   52    * The field translation synchronizer.
   53    *
   54    * @var \Drupal\content_translation\FieldTranslationSynchronizerInterface
   55    */
   56   protected $synchronizer;
   57 
   58   /**
   59    * ContentTranslationSynchronizedFieldsConstraintValidator constructor.
   60    *
   61    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   62    *   The entity type manager.
   63    * @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
   64    *   The content translation manager.
   65    * @param \Drupal\content_translation\FieldTranslationSynchronizerInterface $synchronizer
   66    *   The field translation synchronizer.
   67    */
   68   public function __construct(EntityTypeManagerInterface $entity_type_manager, ContentTranslationManagerInterface $content_translation_manager, FieldTranslationSynchronizerInterface $synchronizer) {
   69     $this->entityTypeManager = $entity_type_manager;
   70     $this->contentTranslationManager = $content_translation_manager;
   71     $this->synchronizer = $synchronizer;
   72   }
   73 
   74   /**
   75    * {@inheritdoc}
   76    */
   77   public static function create(ContainerInterface $container) {
   78     return new static(
   79       $container->get('entity_type.manager'),
   80       $container->get('content_translation.manager'),
   81       $container->get('content_translation.synchronizer')
   82     );
   83   }
   84 
   85   /**
   86    * {@inheritdoc}
   87    */
   88   public function validate($value, Constraint $constraint) {
   89     /** @var \Drupal\content_translation\Plugin\Validation\Constraint\ContentTranslationSynchronizedFieldsConstraint $constraint */
   90     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
   91     $entity = $value;
   92     if ($entity->isNew() || !$entity->getEntityType()->isRevisionable()) {
   93       return;
   94     }
   95     // When changes to untranslatable fields are configured to affect all
   96     // revision translations, we always allow changes in default revisions.
   97     if ($entity->isDefaultRevision() && !$entity->isDefaultTranslationAffectedOnly()) {
   98       return;
   99     }
  100     $entity_type_id = $entity->getEntityTypeId();
  101     if (!$this->contentTranslationManager->isEnabled($entity_type_id, $entity->bundle())) {
  102       return;
  103     }
  104     $synchronized_properties = $this->getSynchronizedPropertiesByField($entity->getFieldDefinitions());
  105     if (!$synchronized_properties) {
  106       return;
  107     }
  108 
  109     /** @var \Drupal\Core\Entity\ContentEntityInterface $original */
  110     $original = $this->getOriginalEntity($entity);
  111     $original_translation = $this->getOriginalTranslation($entity, $original);
  112     if ($this->hasSynchronizedPropertyChanges($entity, $original_translation, $synchronized_properties)) {
  113       if ($entity->isDefaultTranslationAffectedOnly()) {
  114         foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
  115           if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
  116             $this->context->addViolation($constraint->defaultTranslationMessage);
  117             break;
  118           }
  119         }
  120       }
  121       else {
  122         $this->context->addViolation($constraint->defaultRevisionMessage);
  123       }
  124     }
  125   }
  126 
  127   /**
  128    * Checks whether any synchronized property has changes.
  129    *
  130    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  131    *   The entity being validated.
  132    * @param \Drupal\Core\Entity\ContentEntityInterface $original
  133    *   The original unchanged entity.
  134    * @param string[][] $synchronized_properties
  135    *   An associative array of arrays of synchronized field properties keyed by
  136    *   field name.
  137    *
  138    * @return bool
  139    *   TRUE if changes in synchronized properties were detected, FALSE
  140    *   otherwise.
  141    */
  142   protected function hasSynchronizedPropertyChanges(ContentEntityInterface $entity, ContentEntityInterface $original, array $synchronized_properties) {
  143     foreach ($synchronized_properties as $field_name => $properties) {
  144       foreach ($properties as $property) {
  145         $items = $entity->get($field_name)->getValue();
  146         $original_items = $original->get($field_name)->getValue();
  147         if (count($items) !== count($original_items)) {
  148           return TRUE;
  149         }
  150         foreach ($items as $delta => $item) {
  151           // @todo This loose comparison is not fully reliable. Revisit this
  152           //   after https://www.drupal.org/project/drupal/issues/2941092.
  153           if ($items[$delta][$property] != $original_items[$delta][$property]) {
  154             return TRUE;
  155           }
  156         }
  157       }
  158     }
  159     return FALSE;
  160   }
  161 
  162   /**
  163    * Returns the original unchanged entity to be used to detect changes.
  164    *
  165    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  166    *   The entity being changed.
  167    *
  168    * @return \Drupal\Core\Entity\ContentEntityInterface
  169    *   The unchanged entity.
  170    */
  171   protected function getOriginalEntity(ContentEntityInterface $entity) {
  172     if (!isset($entity->original)) {
  173       $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
  174       $original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId());
  175     }
  176     else {
  177       $original = $entity->original;
  178     }
  179     return $original;
  180   }
  181 
  182   /**
  183    * Returns the original translation.
  184    *
  185    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  186    *   The entity being validated.
  187    * @param \Drupal\Core\Entity\ContentEntityInterface $original
  188    *   The original entity.
  189    *
  190    * @return \Drupal\Core\Entity\ContentEntityInterface
  191    *   The original entity translation object.
  192    */
  193   protected function getOriginalTranslation(ContentEntityInterface $entity, ContentEntityInterface $original) {
  194     // If the language of the default translation is changing, the original
  195     // translation will be the same as the original entity, but they won't
  196     // necessarily have the same langcode.
  197     if ($entity->isDefaultTranslation() && $original->language()->getId() !== $entity->language()->getId()) {
  198       return $original;
  199     }
  200     $langcode = $entity->language()->getId();
  201     if ($original->hasTranslation($langcode)) {
  202       $original_langcode = $langcode;
  203     }
  204     else {
  205       $metadata = $this->contentTranslationManager->getTranslationMetadata($entity);
  206       $original_langcode = $metadata->getSource();
  207     }
  208     return $original->getTranslation($original_langcode);
  209   }
  210 
  211   /**
  212    * Returns the synchronized properties for every specified field.
  213    *
  214    * @param \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions
  215    *   An array of field definitions.
  216    *
  217    * @return string[][]
  218    *   An associative array of arrays of field property names keyed by field
  219    *   name.
  220    */
  221   public function getSynchronizedPropertiesByField(array $field_definitions) {
  222     $synchronizer = $this->synchronizer;
  223     $synchronized_properties = array_filter(array_map(
  224       function (FieldDefinitionInterface $field_definition) use ($synchronizer) {
  225         return $synchronizer->getFieldSynchronizedProperties($field_definition);
  226       },
  227       $field_definitions
  228     ));
  229     return $synchronized_properties;
  230   }
  231 
  232 }