annotate core/modules/layout_builder/src/InlineBlockEntityOperations.php @ 19:fa3358dc1485 tip

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