sites_group_overrides-1.x-dev/src/FormDecorator/EntityOverrides.php
src/FormDecorator/EntityOverrides.php
<?php
declare(strict_types=1);
namespace Drupal\sites_group_overrides\FormDecorator;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\system\Controller\CsrfTokenController;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\group\Entity\GroupRelationshipInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\form_decorator\ContentEntityFormDecoratorBase;
use Drupal\sites_group_overrides\Event\FieldOverrideEvent;
use Drupal\sites\ContextProvider\CurrentSiteContextInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\sites_group_overrides\SitesGroupOverridesServiceInterface;
/**
* Implementation for all ContentEntityFormInterface forms.
*
* @FormDecorator()
*/
final class EntityOverrides extends ContentEntityFormDecoratorBase implements ContainerFactoryPluginInterface {
use StringTranslationTrait;
use DependencySerializationTrait;
/**
* The group relationship that corresponds to the entity.
*
* @var GroupRelationshipInterface
*/
protected GroupRelationshipInterface $relationship;
/**
* {@inheritdoc}
*
* @param \Drupal\sites\ContextProvider\CurrentSiteContextInterface $currentSiteContext
* The current site context.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrfToken
* The token generator.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected SitesGroupOverridesServiceInterface $sitesGroupOverridesService,
protected EventDispatcherInterface $eventDispatcher,
protected ConfigFactoryInterface $configfactory,
protected EntityTypeManagerInterface $entityTypeManager,
protected readonly CurrentSiteContextInterface $currentSiteContext,
protected readonly CsrfTokenGenerator $csrfToken,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $configuration, $plugin_id, $plugin_definition) {
return new self($configuration, $plugin_id, $plugin_definition,
$container->get('sites_group_overrides.service'),
$container->get('event_dispatcher'),
$container->get('config.factory'),
$container->get('entity_type.manager'),
$container->get('sites.current_site'),
$container->get('csrf_token'),
);
}
/**
* {@inheritdoc}
*/
public function applies(): bool {
if (!$this->inner instanceof ContentEntityFormInterface) {
return FALSE;
}
// For whatever reason some entites use the 'default' form operation (Term)
// And other have the 'edit' form opertaion (Nodes).
if (!in_array($this->inner->getOperation(), ['edit', 'default'])) {
return FALSE;
}
$relationship = $this->sitesGroupOverridesService->getRelationship($this->getEntity());
if (!$relationship instanceof GroupRelationshipInterface) {
return FALSE;
}
if (empty($this->sitesGroupOverridesService->getSynchronizableFields($relationship, $this->getEntity()))) {
return FALSE;
}
$this->relationship = $relationship;
return TRUE;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ...$args) {
$form = parent::buildForm($form, $form_state, ...$args);
$form['actions']['submit']['#value'] = $this->t('Save override');
$revert_url = Url::fromRoute(
'sites_group_overrides.revert_to_defaults',
[
'group' => $this->relationship->getGroupId(),
'group_relationship' => $this->relationship->id(),
],
['query' => ['destination' => Url::fromRoute('<current>')->toString()]],
);
if ($revert_url->access()) {
$form['actions']['revert'] = [
'#type' => 'link',
'#title' => $this->t('Revert to defaults'),
'#url' => $revert_url,
];
}
unset($form['actions']['delete']);
// Save the current site_id to the form to make sure the submission always
// gets processed in the right site context.
$form['site_id'] = [
'#type' => 'hidden',
'#value' => $this->currentSiteContext->getSiteFromContext()->getPluginId(),
];
// Add a SCRF Token so that we can evaluate if the site_id post param is legitimite.
$form['sites_csrf'] = [
'#type' => 'hidden',
'#value' => $this->csrfToken->get('sites_csrf'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$this->getEntity()->setSyncing(TRUE);
return $this->inner->validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->getEntity();
$link = $entity->toLink()->toString();
$original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->load($entity->id());
$langcode = $entity->language()->getId();
if ($original_entity->hasTranslation($langcode)) {
$original_entity = $original_entity->getTranslation($langcode);
}
// Then extract the values of fields that are not rendered through widgets,
// by simply copying from top-level form values. This leaves the fields
// that are not being edited within this form untouched.
$updated = FALSE;
// We need to "deep clone" the original values because field widgets might modify the original entity.
$original_values = [];
foreach ($original_entity->getFields() as $field_name => $field) {
$original_values[$field_name] = clone $field;
}
foreach ($this->sitesGroupOverridesService->getSynchronizableFields($this->relationship, $entity) as $entity_field_name => $relationship_field_value) {
$original_value = $original_values[$entity_field_name]->getValue();
$value = NULL;
// We want to override only if the given values differ from the original entity.
if ($entity->get($entity_field_name)->hasAffectingChanges($original_values[$entity_field_name], $langcode)) {
// Use the value that would be saved in the entity for the relationship.
$entity->get($entity_field_name)->preSave();
$event = new FieldOverrideEvent($original_entity, $entity, $entity_field_name);
$this->eventDispatcher->dispatch($event, FieldOverrideEvent::EVENT_NAME);
$value = $event->getValue();
}
$relationship_field_value->setValue($value);
// Reset the value of the entity to the original value.
$entity->set($entity_field_name, $original_value);
$updated = TRUE;
}
if ($updated) {
$this->relationship->save();
}
// Supress messages form entity forms - they contain confusing labels..
$messages = $this->messenger()->all();
$this->inner->save($form, $form_state);
$this->messenger()->deleteAll();
foreach ($messages as $type => $texts) {
foreach ($texts as $text) {
$this->messenger()->addMessage($text, $type);
}
}
$entity->setSyncing(false);
$this->messenger()->addStatus($this->t('%title has been saved.', ['%title' => $link]));
}
}
