Mercurial > hg > isophonics-drupal-site
diff core/modules/layout_builder/src/InlineBlockEntityOperations.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children | af1871eacc83 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php Thu Feb 28 13:21:36 2019 +0000 @@ -0,0 +1,267 @@ +<?php + +namespace Drupal\layout_builder; + +use Drupal\Core\Database\Connection; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\RevisionableInterface; +use Drupal\layout_builder\Plugin\Block\InlineBlock; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Defines a class for reacting to entity events related to Inline Blocks. + * + * @internal + */ +class InlineBlockEntityOperations implements ContainerInjectionInterface { + + use LayoutEntityHelperTrait; + + /** + * Inline block usage tracking service. + * + * @var \Drupal\layout_builder\InlineBlockUsage + */ + protected $usage; + + /** + * The block content storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $blockContentStorage; + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * Constructs a new EntityOperations object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager service. + * @param \Drupal\layout_builder\InlineBlockUsage $usage + * Inline block usage tracking service. + * @param \Drupal\Core\Database\Connection $database + * The database connection. + */ + public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database) { + $this->entityTypeManager = $entityTypeManager; + $this->blockContentStorage = $entityTypeManager->getStorage('block_content'); + $this->usage = $usage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('inline_block.usage'), + $container->get('database') + ); + } + + /** + * Remove all unused inline blocks on save. + * + * Entities that were used in prevision revisions will be removed if not + * saving a new revision. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The parent entity. + */ + protected function removeUnusedForEntityOnSave(EntityInterface $entity) { + // If the entity is new or '$entity->original' is not set then there will + // not be any unused inline blocks to remove. + // If this is a revisionable entity then do not remove inline blocks. They + // could be referenced in previous revisions even if this is not a new + // revision. + if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) { + return; + } + $sections = $this->getEntitySections($entity); + // If this is a layout override and there are no sections then it is a new + // override. + if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) { + return; + } + + // Delete and remove the usage for inline blocks that were removed. + if ($removed_block_ids = $this->getRemovedBlockIds($entity)) { + $this->deleteBlocksAndUsage($removed_block_ids); + } + } + + /** + * Gets the IDs of the inline blocks that were removed. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The layout entity. + * + * @return int[] + * The block content IDs that were removed. + */ + protected function getRemovedBlockIds(EntityInterface $entity) { + $original_sections = $this->getEntitySections($entity->original); + $current_sections = $this->getEntitySections($entity); + // Avoid un-needed conversion from revision IDs to block content IDs by + // first determining if there are any revisions in the original that are not + // also in the current sections. + $current_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($current_sections); + $original_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($original_sections); + if ($unused_original_revision_ids = array_diff($original_block_content_revision_ids, $current_block_content_revision_ids)) { + // If there are any revisions in the original that aren't in the current + // there may some blocks that need to be removed. + $current_block_content_ids = $this->getBlockIdsForRevisionIds($current_block_content_revision_ids); + $unused_original_block_content_ids = $this->getBlockIdsForRevisionIds($unused_original_revision_ids); + return array_diff($unused_original_block_content_ids, $current_block_content_ids); + } + return []; + } + + /** + * Handles entity tracking on deleting a parent entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The parent entity. + */ + public function handleEntityDelete(EntityInterface $entity) { + if ($this->isLayoutCompatibleEntity($entity)) { + $this->usage->removeByLayoutEntity($entity); + } + } + + /** + * Handles saving a parent entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The parent entity. + */ + public function handlePreSave(EntityInterface $entity) { + if (!$this->isLayoutCompatibleEntity($entity)) { + return; + } + $duplicate_blocks = FALSE; + + if ($sections = $this->getEntitySections($entity)) { + if ($this->isEntityUsingFieldOverride($entity)) { + if (!$entity->isNew() && isset($entity->original)) { + if (empty($this->getEntitySections($entity->original))) { + // If there were no sections in the original entity then this is a + // new override from a default and the blocks need to be duplicated. + $duplicate_blocks = TRUE; + } + } + } + $new_revision = FALSE; + if ($entity instanceof RevisionableInterface) { + // If the parent entity will have a new revision create a new revision + // of the block. + // @todo Currently revisions are never created for the parent entity. + // This will be fixed in https://www.drupal.org/node/2937199. + // To work around this always make a revision when the parent entity + // is an instance of RevisionableInterface. After the issue is fixed + // only create a new revision if '$entity->isNewRevision()'. + $new_revision = TRUE; + } + + foreach ($this->getInlineBlockComponents($sections) as $component) { + $this->saveInlineBlockComponent($entity, $component, $new_revision, $duplicate_blocks); + } + } + $this->removeUnusedForEntityOnSave($entity); + } + + /** + * Gets a block ID for an inline block plugin. + * + * @param \Drupal\layout_builder\Plugin\Block\InlineBlock $block_plugin + * The inline block plugin. + * + * @return int + * The block content ID or null none available. + */ + protected function getPluginBlockId(InlineBlock $block_plugin) { + $configuration = $block_plugin->getConfiguration(); + if (!empty($configuration['block_revision_id'])) { + $revision_ids = $this->getBlockIdsForRevisionIds([$configuration['block_revision_id']]); + return array_pop($revision_ids); + } + return NULL; + } + + /** + * Delete the inline blocks and the usage records. + * + * @param int[] $block_content_ids + * The block content entity IDs. + */ + protected function deleteBlocksAndUsage(array $block_content_ids) { + foreach ($block_content_ids as $block_content_id) { + if ($block = $this->blockContentStorage->load($block_content_id)) { + $block->delete(); + } + } + $this->usage->deleteUsage($block_content_ids); + } + + /** + * Removes unused inline blocks. + * + * @param int $limit + * The maximum number of inline blocks to remove. + */ + public function removeUnused($limit = 100) { + $this->deleteBlocksAndUsage($this->usage->getUnused($limit)); + } + + /** + * Gets blocks IDs for an array of revision IDs. + * + * @param int[] $revision_ids + * The revision IDs. + * + * @return int[] + * The block IDs. + */ + protected function getBlockIdsForRevisionIds(array $revision_ids) { + if ($revision_ids) { + $query = $this->blockContentStorage->getQuery(); + $query->condition('revision_id', $revision_ids, 'IN'); + $block_ids = $query->execute(); + return $block_ids; + } + return []; + } + + /** + * Saves an inline block component. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity with the layout. + * @param \Drupal\layout_builder\SectionComponent $component + * The section component with an inline block. + * @param bool $new_revision + * Whether a new revision of the block should be created. + * @param bool $duplicate_blocks + * Whether the blocks should be duplicated. + */ + protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision, $duplicate_blocks) { + /** @var \Drupal\layout_builder\Plugin\Block\InlineBlock $plugin */ + $plugin = $component->getPlugin(); + $pre_save_configuration = $plugin->getConfiguration(); + $plugin->saveBlockContent($new_revision, $duplicate_blocks); + $post_save_configuration = $plugin->getConfiguration(); + if ($duplicate_blocks || (empty($pre_save_configuration['block_revision_id']) && !empty($post_save_configuration['block_revision_id']))) { + $this->usage->addUsage($this->getPluginBlockId($plugin), $entity); + } + $component->setConfiguration($post_save_configuration); + } + +}