entity_value_inheritance-1.3.0/src/Services/InheritanceUpdater.php
src/Services/InheritanceUpdater.php
<?php
namespace Drupal\entity_value_inheritance\Services;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\entity_value_inheritance\Entity\InheritanceInterface;
use Drupal\entity_value_inheritance\EntityValueInheritanceUpdaterPluginInterface;
use Drupal\entity_value_inheritance\EntityValueInheritanceUpdaterPluginManager;
use Drupal\entity_value_inheritance\Event\InheritanceAlterUpdateListEvent;
use Drupal\entity_value_inheritance\Event\InheritanceEvents;
use Drupal\entity_value_inheritance\Event\InheritancePostUpdateEvent;
use Drupal\entity_value_inheritance\Event\InheritancePreUpdateEvent;
use Drupal\entity_value_inheritance\Event\InheritanceSaveEntityEvent;
/**
* Inheritance Updater.
*/
final class InheritanceUpdater {
use StringTranslationTrait;
/**
* Array of plugins used.
*/
protected \SplObjectStorage $plugins;
/**
* Constructs a new \Drupal\entity_value_inheritance\Services\InheritanceUpdater object.
*
* @param \Drupal\entity_value_inheritance\EntityValueInheritanceUpdaterPluginManager $pluginManager
* Entity Value Inheritance Updater Plugin Manager Service.
* @param \Drupal\entity_value_inheritance\Services\Helper $helper
* Helper Class Service.
*/
public function __construct(protected EntityValueInheritanceUpdaterPluginManager $pluginManager, protected Helper $helper) {
$this->plugins = new \SplObjectStorage();
}
/**
* Modify the form for the destination entity.
*
* @param array $form
* Form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state to use.
*
* @return array
* Form element.
*/
public function alterForm(array &$form, FormStateInterface $form_state): array {
// Do not continue if the form_state is not an EntityForm.
if (!($form_state->getFormObject() instanceof EntityFormInterface)) {
return $form;
}
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $form_state->getFormObject()->getEntity();
// If there are no active records exit.
if (!$this->helper->hasActiveRecords($entity->getEntityTypeId())) {
return $form;
}
/** @var string[] $fields */
$fields = Element::getVisibleChildren($form);
// Loop through all the fields on the form.
foreach ($fields as $field) {
$inheritances = $this->helper->getDestinationsByField(
$entity->getEntityTypeId(),
$entity->bundle(),
$field
);
// Return the field.
$field_definition = $this->helper->getEntityBundleField($entity->getEntityTypeId(), $entity->bundle(), $field);
if ($field_definition === NULL) {
continue;
}
foreach ($inheritances as $inheritance) {
try {
$updaterPlugin = $this->createInstance($inheritance);
$form = $updaterPlugin->alterForm($form, $form_state, $entity, $field_definition);
$form[$field] = $updaterPlugin->alterFormField($form[$field], $form_state, $entity, $field_definition);
}
catch (PluginException | PluginNotFoundException $pluginNotFoundException) {
// If there is some sort of error log it.
$this->helper->logger()->error('Error trying to alter form field. <pre>@message</pre>', ['@message' => $pluginNotFoundException->getMessage()]);
}
}
}
return $form;
}
/**
* Insert the new destination entity.
*
* @param \Drupal\Core\Entity\EntityInterface $destinationEntity
* Destination being created.
*/
public function insertDestination(EntityInterface $destinationEntity): void {
$inheritanceGroup = $this->helper->groupByProperty(
$this->helper->getInheritanceItemsByDestination($destinationEntity),
'destination_entity_referencing_field'
);
foreach ($inheritanceGroup as $referenceField => $inheritances) {
// Get referenced entities for field.
$sourceEntities = $destinationEntity
->get($referenceField)
->referencedEntities();
foreach ($sourceEntities as $sourceEntity) {
foreach ($inheritances as $inheritance) {
$this->updateEntity($inheritance, $sourceEntity, $destinationEntity);
}
}
$this->helper->dispatchEvent(
new InheritanceSaveEntityEvent($inheritances, [$destinationEntity]),
InheritanceEvents::SAVE_ENTITIES
);
}
}
/**
* Update the destination entity types.
*
* @param \Drupal\Core\Entity\EntityInterface $sourceEntity
* Source entity to run updates on.
*
* @see entity_value_inheritance_entity_update
*/
public function updateDestination(EntityInterface $sourceEntity): void {
$inheritanceGroup = $this->helper->groupByProperty(
$this->helper->getInheritanceItemsBySource($sourceEntity),
'destination_entity_type'
);
foreach ($inheritanceGroup as $entityType => $inheritances) {
$destinationEntities = $this->getListOfDestinationEntities(
$sourceEntity,
$entityType,
$inheritances
);
foreach ($destinationEntities as $destinationEntity) {
foreach ($inheritances as $inheritance) {
$this->updateEntity($inheritance, $sourceEntity, $destinationEntity);
}
}
$this->helper->dispatchEvent(
new InheritanceSaveEntityEvent($inheritances, $destinationEntities),
InheritanceEvents::SAVE_ENTITIES
);
}
}
/**
* Try to update the provided entity.
*
* @param \Drupal\entity_value_inheritance\Entity\InheritanceInterface $inheritance
* Inheritance configuration.
* @param \Drupal\Core\Entity\EntityInterface $sourceEntity
* Entity providing the information.
* @param \Drupal\Core\Entity\EntityInterface $destinationEntity
* Entity being updated.
*
* @return bool
* Return TRUE to continue update.
*/
protected function updateEntity(InheritanceInterface $inheritance, EntityInterface $sourceEntity, EntityInterface $destinationEntity): bool {
// Check if the update process can continue.
/** @var \Drupal\entity_value_inheritance\Event\InheritancePreUpdateEvent $dispatchedEvent */
$dispatchedEvent = ($this->helper->dispatchEvent(
new InheritancePreUpdateEvent($inheritance, $sourceEntity, $destinationEntity),
InheritanceEvents::PRE_UPDATE
));
if (!$dispatchedEvent->canContinue()) {
return FALSE;
}
$this->tryUpdating($inheritance, $sourceEntity, $destinationEntity);
// Dispatch that the following items have completed update.
$this->helper->dispatchEvent(
new InheritancePostUpdateEvent($inheritance, $sourceEntity, $destinationEntity),
InheritanceEvents::POST_UPDATE
);
return TRUE;
}
/**
* Get the list of entities that need to be updated.
*
* @param \Drupal\Core\Entity\EntityInterface $sourceEntity
* Source entity to search for.
* @param string $destinationEntityType
* Destination Entity Type.
* @param \Drupal\entity_value_inheritance\Entity\InheritanceInterface[] $inheritances
* Inheritance entity to get criteria for.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* List of entities that need to be processed.
*/
protected function getListOfDestinationEntities(EntityInterface $sourceEntity, string $destinationEntityType, array $inheritances): array {
$updateEntities = $this->helper->queryEntities($destinationEntityType, $inheritances, $sourceEntity);
/** @var \Drupal\entity_value_inheritance\Event\InheritanceAlterUpdateListEvent $event */
$event = $this->helper->dispatchEvent(
new InheritanceAlterUpdateListEvent($sourceEntity, $updateEntities),
InheritanceEvents::ALTER_UPDATE_LIST
);
return $event->getList();
}
/**
* Try to update the destination using the source entity.
*
* @param \Drupal\entity_value_inheritance\Entity\InheritanceInterface $inheritance
* Inheritance configuration.
* @param \Drupal\Core\Entity\EntityInterface $sourceEntity
* Entity providing the information.
* @param \Drupal\Core\Entity\EntityInterface $destinationEntity
* Entity being updated.
*
* @return \Drupal\Core\Entity\EntityInterface
* Return the entity being updated.
*/
protected function tryUpdating(InheritanceInterface $inheritance, EntityInterface $sourceEntity, EntityInterface $destinationEntity): EntityInterface {
try {
$plugin = $this->createInstance($inheritance);
// Update the destination.
$altered = $plugin->updateDestination($sourceEntity, $destinationEntity);
// Mark if the entity is altered.
$destinationEntity->altered =
isset($destinationEntity->altered) &&
$destinationEntity->altered === TRUE ||
$altered;
}
catch (PluginNotFoundException | PluginException $pluginNotFoundException) {
// Add attribute that entity was altered.
$destinationEntity->altered = FALSE;
// If there is an issue log the message.
$this->helper->logger()->error(
implode('</br>', [
'Instance encountered an error.',
'Inheritance: @instance_id',
'Updater Plugin ID: @plugin_id',
'Source Entity ID: @source_entity_id',
'Destination Entity ID: @destination_entity_id',
'Error Message: <pre>@message</pre>',
]),
[
'@instanced_id' => $inheritance->id(),
'@plugin' => $inheritance->getStrategy(),
'@source_entity_id' => $sourceEntity->id(),
'@destination_entity_id' => $destinationEntity->id(),
'@message' => $pluginNotFoundException->getMessage(),
]
);
}
return $destinationEntity;
}
/**
* Hook to modify or work with entities before saving.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity to alter before saving.
*
* @see hook_entity_presave()
*/
public function preSaveEntity(EntityInterface $entity): void {
$list = array_merge(
$this->helper->getInheritanceItemsBySource($entity),
$this->helper->getInheritanceItemsByDestination($entity)
);
foreach ($list as $inheritance) {
$plugin = $this->createInstance($inheritance);
$plugin->preSaveEntity($entity);
}
}
/**
* Hook to modify or work with entities before saving.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity to alter before saving.
*
* @see hook_entity_delete()
*/
public function deleteEntity(EntityInterface $entity): void {
$list = array_merge(
$this->helper->getInheritanceItemsBySource($entity),
$this->helper->getInheritanceItemsByDestination($entity)
);
foreach ($list as $inheritance) {
$plugin = $this->createInstance($inheritance);
$plugin->deleteEntity($entity);
}
}
/**
* Modify or alter any of the entities that are being loaded.
*
* @param \Drupal\Core\Entity\EntityInterface[] $entities
* Entities to alter during the load process.
* @param string $entity_type_id
* Entity Type being loaded.
*
* @see hook_entity_load()
*/
public function entityLoad(array $entities, string $entity_type_id): void {
// Group the entities by entity type then bundle.
$bundles = [];
foreach ($entities as $entity) {
$bundles[$entity->bundle()][] = $entity;
}
// Loop through the group.
foreach ($bundles as $bundle_type => $entities) {
$list = array_merge(
$this->helper->getSourceItems($entity_type_id, $bundle_type),
$this->helper->getDestinationItems($entity_type_id, $bundle_type)
);
foreach ($list as $inheritance) {
$plugin = $this->createInstance($inheritance);
$plugin->entityLoadProperties($entity_type_id, $entities);
foreach ($entities as $entity) {
$plugin->entityLoad($entity);
}
}
}
}
/**
* Alter the processed build of groups.
*
* @param array $element
* The element being processed.
* @param object $group
* The group info.
* @param object $complete_form
* The complete form.
*
* @see hook_field_group_form_process_alter()
*/
public function fieldGroupFormProcessAlter(array &$element, object &$group, array &$complete_form) {
$fields = $group->children;
$entityType = $group->entity_type;
$bundleType = $group->bundle;
foreach ($fields as $field) {
$inheritances = $this->helper->getDestinationsByField($entityType, $bundleType, $field);
if (!empty($inheritances)) {
$inheritance = array_pop($inheritances);
$plugin = $this->createInstance($inheritance);
$plugin->fieldGroupFormProcessAlter($element, $group, $complete_form);
}
}
}
/**
* Helper Method for creating inheritance methods and storing a version of it.
*
* @param \Drupal\entity_value_inheritance\Entity\InheritanceInterface $inheritance
* Inheritance to create a plugin for.
*
* @return \Drupal\entity_value_inheritance\EntityValueInheritanceUpdaterPluginInterface
* Plugin Instance.
*
* @throws \Drupal\Component\Plugin\Exception
* Throws exception if can't find or create plugin.
*/
protected function createInstance(InheritanceInterface $inheritance): EntityValueInheritanceUpdaterPluginInterface {
if (!$this->plugins->contains($inheritance)) {
$this->plugins->offsetSet($inheritance, ($this->pluginManager->createInstance($inheritance->getStrategy(), $inheritance->getSettings()))->setInheritance($inheritance));
}
return $this->plugins->offsetGet($inheritance);
}
}
