You are here

LayoutBuilder.php in Entity Usage 8.2

File

src/Plugin/EntityUsage/Track/LayoutBuilder.php
View source
<?php

namespace Drupal\entity_usage\Plugin\EntityUsage\Track;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\entity_usage\EntityUsage;
use Drupal\entity_usage\EntityUsageTrackBase;
use Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Tracks usage of entities related in Layout Builder layouts.
 *
 * @EntityUsageTrack(
 *   id = "layout_builder",
 *   label = @Translation("Layout builder"),
 *   description = @Translation("Tracks relationships in layout builder layouts."),
 *   field_types = {"layout_section"},
 * )
 */
class LayoutBuilder extends EntityUsageTrackBase {

  /**
   * Block manager.
   *
   * @var \Drupal\Core\Block\BlockManagerInterface
   */
  protected $blockManager;

  /**
   * Constructs a new LayoutBuilder plugin instance.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\entity_usage\EntityUsage $usage_service
   *   The usage tracking service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The EntityTypeManager service.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The EntityFieldManager service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The factory for configuration objects.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The EntityRepositoryInterface service.
   * @param \Drupal\Core\Block\BlockManagerInterface $blockManager
   *   Block manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityUsage $usage_service, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ConfigFactoryInterface $config_factory, EntityRepositoryInterface $entity_repository, BlockManagerInterface $blockManager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $usage_service, $entity_type_manager, $entity_field_manager, $config_factory, $entity_repository);
    $this->blockManager = $blockManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('entity_usage.usage'), $container
      ->get('entity_type.manager'), $container
      ->get('entity_field.manager'), $container
      ->get('config.factory'), $container
      ->get('entity.repository'), $container
      ->get('plugin.manager.block'));
  }

  /**
   * {@inheritdoc}
   */
  public function getTargetEntities(FieldItemInterface $item) {
    assert($item instanceof LayoutSectionItem);

    // We support both Content Blocks and Entity Browser Blocks.
    $blockContentRevisionIds = [];
    $ebbContentIds = [];
    $contentDependencyIds = [];

    /** @var \Drupal\layout_builder\Plugin\DataType\SectionData $value */
    foreach ($item as $value) {

      /** @var \Drupal\layout_builder\Section $section */
      $section = $value
        ->getValue();
      foreach ($section
        ->getComponents() as $component) {
        $configuration = $component
          ->toArray()['configuration'];
        try {
          $def = $this->blockManager
            ->getDefinition($component
            ->getPluginId());
        } catch (PluginNotFoundException $e) {

          // Block has since been removed, continue.
          continue;
        }
        if ($def['id'] === 'inline_block') {
          $blockContentRevisionIds[] = $configuration['block_revision_id'];
        }
        elseif ($def['id'] === 'entity_browser_block' && !empty($configuration['entity_ids'])) {
          $ebbContentIds = array_unique(array_merge($ebbContentIds, (array) $configuration['entity_ids']));
        }

        // Check the block plugin's content dependencies.

        /** @var \Drupal\Core\Block\BlockPluginInterface $plugin */
        $plugin = $component
          ->getPlugin();
        $dependencies = $plugin
          ->calculateDependencies();
        if (!empty($dependencies['content'])) {
          $contentDependencyIds = array_merge($contentDependencyIds, $dependencies['content']);
        }
      }
    }
    $target_entities = [];
    if (count($blockContentRevisionIds) > 0) {
      $target_entities = $this
        ->prepareBlockContentIds($blockContentRevisionIds);
    }
    if (count($ebbContentIds) > 0) {
      $target_entities = array_merge($target_entities, $this
        ->prepareEntityBrowserBlockIds($ebbContentIds));
    }
    if (count($contentDependencyIds) > 0) {
      $target_entities = array_merge($target_entities, $this
        ->prepareContentDependencyIds($contentDependencyIds));
    }
    return $target_entities;
  }

  /**
   * Prepare block content target entity values to be in the correct format.
   *
   * @param array $blockContentRevisionIds
   *   An array of block (content) revision IDs.
   *
   * @return array
   *   An array of the corresponding block IDs from the revision IDs passed in,
   *   each prefixed with the string "block_content|".
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private function prepareBlockContentIds(array $blockContentRevisionIds) {

    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $blockContentStorage */
    $blockContentStorage = $this->entityTypeManager
      ->getStorage('block_content');

    /** @var \Drupal\block_content\BlockContentInterface[] $blockContent */
    $ids = $blockContentStorage
      ->getQuery()
      ->condition($blockContentStorage
      ->getEntityType()
      ->getKey('revision'), $blockContentRevisionIds, 'IN')
      ->execute();
    return array_map(function (string $id) : string {
      return 'block_content|' . $id;
    }, $ids);
  }

  /**
   * Prepare Entity Browser Block IDs to be in the correct format.
   *
   * @param array $ebbContentIds
   *   An array of entity ID values as returned from the EBB configuration.
   *   (Each value is expected to be in the format "node:123", "media:42", etc).
   *
   * @return array
   *   The same array passed in, with the following modifications:
   *   - Non-loadable entities will be filtered out.
   *   - The ":" character will be replaced by the "|" character.
   */
  private function prepareEntityBrowserBlockIds(array $ebbContentIds) {

    // Only return loadable entities.
    $ids = array_filter($ebbContentIds, function ($item) {

      // Entity Browser Block stores each entity in "entity_ids" in the format:
      // "{$entity_type_id}:{$entity_id}".
      list($entity_type_id, $entity_id) = explode(":", $item);
      $storage = $this->entityTypeManager
        ->getStorage($entity_type_id);
      if (!$storage) {
        return FALSE;
      }
      $entity = $storage
        ->load($entity_id);
      if (!$entity) {
        return FALSE;
      }
      return TRUE;
    });
    if (empty($ids)) {
      return [];
    }

    // Return items in the expected format, separating type and id with a "|".
    return array_map(function (string $item) : string {
      return str_replace(":", "|", $item);
    }, $ids);
  }

  /**
   * Prepare plugin content dependency IDs to be in the correct format.
   *
   * @param array $ids
   *   An array of entity ID values as returned from the plugin dependency
   *   configuration. (Each value is expected to be in the format
   *   "media:image:4dd39aa2-068f-11ec-9a03-0242ac130003", etc).
   *
   * @return array
   *   The same array passed in, with the following modifications:
   *   - Non-loadable entities will be filtered out.
   *   - The bundle ID in the middle will be removed.
   *   - The UUID will be converted to a regular ID.
   *   - The ":" character will be replaced by the "|" character.
   */
  private function prepareContentDependencyIds(array $ids) {

    // Only return loadable entities.
    $ids = array_map(function ($item) {

      // Content dependencies are stored in the format:
      // "{$entity_type_id}:{$bundle_id}:{$entity_uuid}".
      list($entity_type_id, , $entity_uuid) = explode(':', $item);
      if ($entity = $this->entityRepository
        ->loadEntityByUuid($entity_type_id, $entity_uuid)) {
        return "{$entity_type_id}|{$entity->id()}";
      }
      return FALSE;
    }, $ids);
    return array_filter($ids);
  }

}

Classes

Namesort descending Description
LayoutBuilder Tracks usage of entities related in Layout Builder layouts.