layout_builder_ipe-1.0.x-dev/src/Validation/Constraint/LayoutBuilderEntityChangedConstraintValidator.php
src/Validation/Constraint/LayoutBuilderEntityChangedConstraintValidator.php
<?php
namespace Drupal\layout_builder_ipe\Validation\Constraint;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint;
use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraintValidator;
use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage;
use Drupal\layout_builder_ipe\LayoutBuilderIpeService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the EntityChanged constraint.
*/
class LayoutBuilderEntityChangedConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* The layout builder ipe service.
*
* @var \Drupal\layout_builder_ipe\LayoutBuilderIpeService
*/
protected $layoutBuilderIpe;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Creates an LayoutBuilderEntityChangedConstraintValidator object.
*
* @param \Drupal\layout_builder_ipe\LayoutBuilderIpeService $layout_builder_ipe
* The layout builder IPE service.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack..
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(LayoutBuilderIpeService $layout_builder_ipe, DateFormatterInterface $date_formatter, RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager) {
$this->layoutBuilderIpe = $layout_builder_ipe;
$this->dateFormatter = $date_formatter;
$this->request = $request_stack->getCurrentRequest();
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('layout_builder_ipe'), $container->get('date.formatter'), $container->get('request_stack'), $container->get('entity_type.manager'));
}
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if (isset($entity)) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
if (!$entity->isNew()) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $saved_entity */
$saved_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
if ($this->layoutBuilderIpe->ipeEnabled($entity)) {
// This is an entity that uses IPE.
if ($this->layoutBuilderIpe->isLayoutBuilderIpeFormSubmission($entity)) {
// This is a form submission from the layout builder form. All that
// can be changed there is the layout, so all we need to check is if
// the layout has changed.
if ($this->hasLayoutChanges($saved_entity)) {
// The layout has changed, so we add a violation.
$placeholders = [
'@updated' => $this->dateFormatter->format($saved_entity->getChangedTime()),
];
$this->context->addViolation((string) (new FormattableMarkup($constraint->message, $placeholders)));
}
}
else {
// This is some other modification to the entity, presumably a
// submission from the node edit form.
if ($this->hasEntityChanges($saved_entity)) {
// The entity has changed, so we add a violation.
$entity_changed_constraint = new EntityChangedConstraint();
$this->context->addViolation($entity_changed_constraint->message);
}
}
}
else {
// This is an entity that doesn't use IPE, so we fall back to the
// original EntityChanged constraint.
$original_validator = new EntityChangedConstraintValidator();
$original_validator->initialize($this->context);
$original_validator->validate($entity, new EntityChangedConstraint());
}
}
}
}
/**
* See if the given entity has any layout changes.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*
* @return bool
* TRUE if the given entity has layout changes, FALSE otherwise.
*/
public function hasLayoutChanges(EntityInterface $entity) {
$original_hash = $this->request->get('layout_builder_ipe_layout_hash');
if (empty($original_hash)) {
return FALSE;
}
$section_storage = $this->layoutBuilderIpe->getSectionStorageForEntity($entity);
if ($section_storage instanceof DefaultsSectionStorage) {
// An edit to the default storage which will create an overridden storage
// for the first time is a layout change, but shouldn't count, otherwise
// layouts could never be customized.
return FALSE;
}
$current_hash = $this->layoutBuilderIpe->hashLayout($section_storage);
return $current_hash != $original_hash;
}
/**
* See if the given entity has any entity changes (no layout).
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
*
* @return bool
* TRUE if the given entity has entity changes, FALSE otherwise.
*/
public function hasEntityChanges(EntityInterface $entity) {
$original_hash = $this->request->get('layout_builder_ipe_entity_hash');
if (empty($original_hash)) {
return FALSE;
}
$current_hash = $this->layoutBuilderIpe->hashEntity($entity);
return $current_hash != $original_hash;
}
}
