annotate core/modules/layout_builder/src/InlineBlockEntityOperations.php @ 5:12f9dff5fda9 tip

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