You are here

class InlineBlockEntityOperations in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/layout_builder/src/InlineBlockEntityOperations.php \Drupal\layout_builder\InlineBlockEntityOperations

Defines a class for reacting to entity events related to Inline Blocks.

@internal This is an internal utility class wrapping hook implementations.

Hierarchy

Expanded class hierarchy of InlineBlockEntityOperations

1 file declares its use of InlineBlockEntityOperations
layout_builder.module in core/modules/layout_builder/layout_builder.module
Provides hook implementations for Layout Builder.

File

core/modules/layout_builder/src/InlineBlockEntityOperations.php, line 19

Namespace

Drupal\layout_builder
View source
class InlineBlockEntityOperations implements ContainerInjectionInterface {
  use LayoutEntityHelperTrait;

  /**
   * Inline block usage tracking service.
   *
   * @var \Drupal\layout_builder\InlineBlockUsageInterface
   */
  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.
   *
   * @todo This constructor has one optional parameter, $section_storage_manager
   *    and one totally unused $database parameter. Deprecate the current
   *    constructor signature in https://www.drupal.org/node/3031492 after the
   *    general policy for constructor backwards compatibility is determined in
   *    https://www.drupal.org/node/3030640.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service.
   * @param \Drupal\layout_builder\InlineBlockUsageInterface $usage
   *   Inline block usage tracking service.
   * @param \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface $section_storage_manager
   *   (optional) The section storage manager.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, InlineBlockUsageInterface $usage, SectionStorageManagerInterface $section_storage_manager) {
    $this->entityTypeManager = $entityTypeManager;
    $this->blockContentStorage = $entityTypeManager
      ->getStorage('block_content');
    $this->usage = $usage;
    $this->sectionStorageManager = $section_storage_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('entity_type.manager'), $container
      ->get('inline_block.usage'), $container
      ->get('plugin.manager.layout_builder.section_storage'));
  }

  /**
   * 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;
    }

    // If the original entity used the default storage then we cannot remove
    // unused inline blocks because they will still be referenced in the
    // defaults.
    if ($this
      ->originalEntityUsesDefaultStorage($entity)) {
      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) {

    // @todo In https://www.drupal.org/node/3008943 call
    //   \Drupal\layout_builder\LayoutEntityHelperTrait::isLayoutCompatibleEntity().
    $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
        ->originalEntityUsesDefaultStorage($entity)) {

        // This is a new override from a default and the blocks need to be
        // duplicated.
        $duplicate_blocks = TRUE;
      }

      // Since multiple parent entity revisions may reference common block
      // revisions, when a block is modified, it must always result in the
      // creation of a new block revision.
      $new_revision = $entity instanceof RevisionableInterface;
      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()
        ->accessCheck(FALSE);
      $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 when modified.
   * @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);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
InlineBlockEntityOperations::$blockContentStorage protected property The block content storage.
InlineBlockEntityOperations::$entityTypeManager protected property The entity type manager.
InlineBlockEntityOperations::$usage protected property Inline block usage tracking service.
InlineBlockEntityOperations::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create
InlineBlockEntityOperations::deleteBlocksAndUsage protected function Delete the inline blocks and the usage records.
InlineBlockEntityOperations::getBlockIdsForRevisionIds protected function Gets blocks IDs for an array of revision IDs.
InlineBlockEntityOperations::getPluginBlockId protected function Gets a block ID for an inline block plugin.
InlineBlockEntityOperations::getRemovedBlockIds protected function Gets the IDs of the inline blocks that were removed.
InlineBlockEntityOperations::handleEntityDelete public function Handles entity tracking on deleting a parent entity.
InlineBlockEntityOperations::handlePreSave public function Handles saving a parent entity.
InlineBlockEntityOperations::removeUnused public function Removes unused inline blocks.
InlineBlockEntityOperations::removeUnusedForEntityOnSave protected function Remove all unused inline blocks on save.
InlineBlockEntityOperations::saveInlineBlockComponent protected function Saves an inline block component.
InlineBlockEntityOperations::__construct public function Constructs a new EntityOperations object.
LayoutEntityHelperTrait::$sectionStorageManager protected property The section storage manager. 1
LayoutEntityHelperTrait::getEntitySections protected function Gets the sections for an entity if any.
LayoutEntityHelperTrait::getInlineBlockComponents protected function Gets components that have Inline Block plugins.
LayoutEntityHelperTrait::getInlineBlockRevisionIdsInSections protected function Gets revision IDs for layout sections.
LayoutEntityHelperTrait::getSectionStorageForEntity protected function Gets the section storage for an entity.
LayoutEntityHelperTrait::isLayoutCompatibleEntity protected function Determines if an entity can have a layout.
LayoutEntityHelperTrait::originalEntityUsesDefaultStorage protected function Determines if the original entity used the default section storage.
LayoutEntityHelperTrait::sectionStorageManager private function Gets the section storage manager. 1