annotate 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
rev   line source
Chris@17 1 <?php
Chris@17 2
Chris@17 3 namespace Drupal\layout_builder;
Chris@17 4
Chris@17 5 use Drupal\Core\Database\Connection;
Chris@17 6 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
Chris@17 7 use Drupal\Core\Entity\EntityInterface;
Chris@17 8 use Drupal\Core\Entity\EntityTypeManagerInterface;
Chris@17 9 use Drupal\Core\Entity\RevisionableInterface;
Chris@17 10 use Drupal\layout_builder\Plugin\Block\InlineBlock;
Chris@17 11 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@17 12
Chris@17 13 /**
Chris@17 14 * Defines a class for reacting to entity events related to Inline Blocks.
Chris@17 15 *
Chris@17 16 * @internal
Chris@17 17 */
Chris@17 18 class InlineBlockEntityOperations implements ContainerInjectionInterface {
Chris@17 19
Chris@17 20 use LayoutEntityHelperTrait;
Chris@17 21
Chris@17 22 /**
Chris@17 23 * Inline block usage tracking service.
Chris@17 24 *
Chris@17 25 * @var \Drupal\layout_builder\InlineBlockUsage
Chris@17 26 */
Chris@17 27 protected $usage;
Chris@17 28
Chris@17 29 /**
Chris@17 30 * The block content storage.
Chris@17 31 *
Chris@17 32 * @var \Drupal\Core\Entity\EntityStorageInterface
Chris@17 33 */
Chris@17 34 protected $blockContentStorage;
Chris@17 35
Chris@17 36 /**
Chris@17 37 * The entity type manager.
Chris@17 38 *
Chris@17 39 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
Chris@17 40 */
Chris@17 41 protected $entityTypeManager;
Chris@17 42
Chris@17 43 /**
Chris@17 44 * Constructs a new EntityOperations object.
Chris@17 45 *
Chris@17 46 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
Chris@17 47 * The entity type manager service.
Chris@17 48 * @param \Drupal\layout_builder\InlineBlockUsage $usage
Chris@17 49 * Inline block usage tracking service.
Chris@17 50 * @param \Drupal\Core\Database\Connection $database
Chris@17 51 * The database connection.
Chris@17 52 */
Chris@17 53 public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsage $usage, Connection $database) {
Chris@17 54 $this->entityTypeManager = $entityTypeManager;
Chris@17 55 $this->blockContentStorage = $entityTypeManager->getStorage('block_content');
Chris@17 56 $this->usage = $usage;
Chris@17 57 }
Chris@17 58
Chris@17 59 /**
Chris@17 60 * {@inheritdoc}
Chris@17 61 */
Chris@17 62 public static function create(ContainerInterface $container) {
Chris@17 63 return new static(
Chris@17 64 $container->get('entity_type.manager'),
Chris@17 65 $container->get('inline_block.usage'),
Chris@17 66 $container->get('database')
Chris@17 67 );
Chris@17 68 }
Chris@17 69
Chris@17 70 /**
Chris@17 71 * Remove all unused inline blocks on save.
Chris@17 72 *
Chris@17 73 * Entities that were used in prevision revisions will be removed if not
Chris@17 74 * saving a new revision.
Chris@17 75 *
Chris@17 76 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@17 77 * The parent entity.
Chris@17 78 */
Chris@17 79 protected function removeUnusedForEntityOnSave(EntityInterface $entity) {
Chris@17 80 // If the entity is new or '$entity->original' is not set then there will
Chris@17 81 // not be any unused inline blocks to remove.
Chris@17 82 // If this is a revisionable entity then do not remove inline blocks. They
Chris@17 83 // could be referenced in previous revisions even if this is not a new
Chris@17 84 // revision.
Chris@17 85 if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) {
Chris@17 86 return;
Chris@17 87 }
Chris@17 88 $sections = $this->getEntitySections($entity);
Chris@17 89 // If this is a layout override and there are no sections then it is a new
Chris@17 90 // override.
Chris@17 91 if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) {
Chris@17 92 return;
Chris@17 93 }
Chris@17 94
Chris@17 95 // Delete and remove the usage for inline blocks that were removed.
Chris@17 96 if ($removed_block_ids = $this->getRemovedBlockIds($entity)) {
Chris@17 97 $this->deleteBlocksAndUsage($removed_block_ids);
Chris@17 98 }
Chris@17 99 }
Chris@17 100
Chris@17 101 /**
Chris@17 102 * Gets the IDs of the inline blocks that were removed.
Chris@17 103 *
Chris@17 104 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@17 105 * The layout entity.
Chris@17 106 *
Chris@17 107 * @return int[]
Chris@17 108 * The block content IDs that were removed.
Chris@17 109 */
Chris@17 110 protected function getRemovedBlockIds(EntityInterface $entity) {
Chris@17 111 $original_sections = $this->getEntitySections($entity->original);
Chris@17 112 $current_sections = $this->getEntitySections($entity);
Chris@17 113 // Avoid un-needed conversion from revision IDs to block content IDs by
Chris@17 114 // first determining if there are any revisions in the original that are not
Chris@17 115 // also in the current sections.
Chris@17 116 $current_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($current_sections);
Chris@17 117 $original_block_content_revision_ids = $this->getInlineBlockRevisionIdsInSections($original_sections);
Chris@17 118 if ($unused_original_revision_ids = array_diff($original_block_content_revision_ids, $current_block_content_revision_ids)) {
Chris@17 119 // If there are any revisions in the original that aren't in the current
Chris@17 120 // there may some blocks that need to be removed.
Chris@17 121 $current_block_content_ids = $this->getBlockIdsForRevisionIds($current_block_content_revision_ids);
Chris@17 122 $unused_original_block_content_ids = $this->getBlockIdsForRevisionIds($unused_original_revision_ids);
Chris@17 123 return array_diff($unused_original_block_content_ids, $current_block_content_ids);
Chris@17 124 }
Chris@17 125 return [];
Chris@17 126 }
Chris@17 127
Chris@17 128 /**
Chris@17 129 * Handles entity tracking on deleting a parent entity.
Chris@17 130 *
Chris@17 131 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@17 132 * The parent entity.
Chris@17 133 */
Chris@17 134 public function handleEntityDelete(EntityInterface $entity) {
Chris@17 135 if ($this->isLayoutCompatibleEntity($entity)) {
Chris@17 136 $this->usage->removeByLayoutEntity($entity);
Chris@17 137 }
Chris@17 138 }
Chris@17 139
Chris@17 140 /**
Chris@17 141 * Handles saving a parent entity.
Chris@17 142 *
Chris@17 143 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@17 144 * The parent entity.
Chris@17 145 */
Chris@17 146 public function handlePreSave(EntityInterface $entity) {
Chris@17 147 if (!$this->isLayoutCompatibleEntity($entity)) {
Chris@17 148 return;
Chris@17 149 }
Chris@17 150 $duplicate_blocks = FALSE;
Chris@17 151
Chris@17 152 if ($sections = $this->getEntitySections($entity)) {
Chris@17 153 if ($this->isEntityUsingFieldOverride($entity)) {
Chris@17 154 if (!$entity->isNew() && isset($entity->original)) {
Chris@17 155 if (empty($this->getEntitySections($entity->original))) {
Chris@17 156 // If there were no sections in the original entity then this is a
Chris@17 157 // new override from a default and the blocks need to be duplicated.
Chris@17 158 $duplicate_blocks = TRUE;
Chris@17 159 }
Chris@17 160 }
Chris@17 161 }
Chris@17 162 $new_revision = FALSE;
Chris@17 163 if ($entity instanceof RevisionableInterface) {
Chris@17 164 // If the parent entity will have a new revision create a new revision
Chris@17 165 // of the block.
Chris@17 166 // @todo Currently revisions are never created for the parent entity.
Chris@17 167 // This will be fixed in https://www.drupal.org/node/2937199.
Chris@17 168 // To work around this always make a revision when the parent entity
Chris@17 169 // is an instance of RevisionableInterface. After the issue is fixed
Chris@17 170 // only create a new revision if '$entity->isNewRevision()'.
Chris@17 171 $new_revision = TRUE;
Chris@17 172 }
Chris@17 173
Chris@17 174 foreach ($this->getInlineBlockComponents($sections) as $component) {
Chris@17 175 $this->saveInlineBlockComponent($entity, $component, $new_revision, $duplicate_blocks);
Chris@17 176 }
Chris@17 177 }
Chris@17 178 $this->removeUnusedForEntityOnSave($entity);
Chris@17 179 }
Chris@17 180
Chris@17 181 /**
Chris@17 182 * Gets a block ID for an inline block plugin.
Chris@17 183 *
Chris@17 184 * @param \Drupal\layout_builder\Plugin\Block\InlineBlock $block_plugin
Chris@17 185 * The inline block plugin.
Chris@17 186 *
Chris@17 187 * @return int
Chris@17 188 * The block content ID or null none available.
Chris@17 189 */
Chris@17 190 protected function getPluginBlockId(InlineBlock $block_plugin) {
Chris@17 191 $configuration = $block_plugin->getConfiguration();
Chris@17 192 if (!empty($configuration['block_revision_id'])) {
Chris@17 193 $revision_ids = $this->getBlockIdsForRevisionIds([$configuration['block_revision_id']]);
Chris@17 194 return array_pop($revision_ids);
Chris@17 195 }
Chris@17 196 return NULL;
Chris@17 197 }
Chris@17 198
Chris@17 199 /**
Chris@17 200 * Delete the inline blocks and the usage records.
Chris@17 201 *
Chris@17 202 * @param int[] $block_content_ids
Chris@17 203 * The block content entity IDs.
Chris@17 204 */
Chris@17 205 protected function deleteBlocksAndUsage(array $block_content_ids) {
Chris@17 206 foreach ($block_content_ids as $block_content_id) {
Chris@17 207 if ($block = $this->blockContentStorage->load($block_content_id)) {
Chris@17 208 $block->delete();
Chris@17 209 }
Chris@17 210 }
Chris@17 211 $this->usage->deleteUsage($block_content_ids);
Chris@17 212 }
Chris@17 213
Chris@17 214 /**
Chris@17 215 * Removes unused inline blocks.
Chris@17 216 *
Chris@17 217 * @param int $limit
Chris@17 218 * The maximum number of inline blocks to remove.
Chris@17 219 */
Chris@17 220 public function removeUnused($limit = 100) {
Chris@17 221 $this->deleteBlocksAndUsage($this->usage->getUnused($limit));
Chris@17 222 }
Chris@17 223
Chris@17 224 /**
Chris@17 225 * Gets blocks IDs for an array of revision IDs.
Chris@17 226 *
Chris@17 227 * @param int[] $revision_ids
Chris@17 228 * The revision IDs.
Chris@17 229 *
Chris@17 230 * @return int[]
Chris@17 231 * The block IDs.
Chris@17 232 */
Chris@17 233 protected function getBlockIdsForRevisionIds(array $revision_ids) {
Chris@17 234 if ($revision_ids) {
Chris@17 235 $query = $this->blockContentStorage->getQuery();
Chris@17 236 $query->condition('revision_id', $revision_ids, 'IN');
Chris@17 237 $block_ids = $query->execute();
Chris@17 238 return $block_ids;
Chris@17 239 }
Chris@17 240 return [];
Chris@17 241 }
Chris@17 242
Chris@17 243 /**
Chris@17 244 * Saves an inline block component.
Chris@17 245 *
Chris@17 246 * @param \Drupal\Core\Entity\EntityInterface $entity
Chris@17 247 * The entity with the layout.
Chris@17 248 * @param \Drupal\layout_builder\SectionComponent $component
Chris@17 249 * The section component with an inline block.
Chris@17 250 * @param bool $new_revision
Chris@17 251 * Whether a new revision of the block should be created.
Chris@17 252 * @param bool $duplicate_blocks
Chris@17 253 * Whether the blocks should be duplicated.
Chris@17 254 */
Chris@17 255 protected function saveInlineBlockComponent(EntityInterface $entity, SectionComponent $component, $new_revision, $duplicate_blocks) {
Chris@17 256 /** @var \Drupal\layout_builder\Plugin\Block\InlineBlock $plugin */
Chris@17 257 $plugin = $component->getPlugin();
Chris@17 258 $pre_save_configuration = $plugin->getConfiguration();
Chris@17 259 $plugin->saveBlockContent($new_revision, $duplicate_blocks);
Chris@17 260 $post_save_configuration = $plugin->getConfiguration();
Chris@17 261 if ($duplicate_blocks || (empty($pre_save_configuration['block_revision_id']) && !empty($post_save_configuration['block_revision_id']))) {
Chris@17 262 $this->usage->addUsage($this->getPluginBlockId($plugin), $entity);
Chris@17 263 }
Chris@17 264 $component->setConfiguration($post_save_configuration);
Chris@17 265 }
Chris@17 266
Chris@17 267 }