Drupal Search API - Hierarchical Entity Facet - Custom Plugin

By admin, 17 October, 2023
<?php
namespace Drupal\custom_module\Plugin\facets\hierarchy;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Condition\Annotation\Condition;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\facets\Hierarchy\HierarchyPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
 * Provides the Content hierarchy class.
 *
 * @FacetsHierarchy(
 *   id = "content",
 *   label = @Translation("Content hierarchy"),
 *   description = @Translation("Hierarchy structure provided by the Content module.")
 * )
 */
class Content extends HierarchyPluginBase {
  /**
   * Static cache for the nested children.
   *
   * @var array
   */
  protected $nestedChildren = [];
  /**
   * Static cache for the article parents.
   *
   * @var array
   */
  protected $contentParents = [];
  /**
   * The article storage.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $contentStorage;
  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;
  /**
   * Constructs a Drupal\Component\Plugin\PluginBase object.
   *
   * @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\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entity_type_manager;
  }
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager')
    );
  }
  /**
   * Returns the content storage.
   *
   * @return \Drupal\core\Entity\EntityInterface
   *   The content storage.
   */
  public function getContentStorage() {
    if (!isset($this->contentStorage)) {
      $this->contentStorage = $this->entityTypeManager->getStorage('node');
    }
    return $this->contentStorage;
  }
  /**
   * {@inheritdoc}
   */
  public function getParentIds($id) {
    $current_nid = $id;
    if ($current_nid != NULL) {
      while ($parent = $this->contentGetParent($current_nid)) {
        $current_nid = $parent;
        $parents[$id][] = $parent;
      }
      return $parents[$id] ?? [];
    }
  }
  /**
   * {@inheritdoc}
   */
  public function getNestedChildIds($id) {
    if (isset($this->nestedChildren[$id])) {
      return $this->nestedChildren[$id];
    }
    $parent = $this->getContentStorage()->load($id);
    $storage = $this->getContentStorage();
    $children = \Drupal::entityQuery('node')
    ->accessCheck(TRUE)
    ->condition('type', 'article')
    ->condition('field_parent_article', $parent->id())
    ->execute();
    $children = $storage->loadMultiple($children);
    $children = array_filter(array_values(array_map(function ($it) {
      return $it->id();
    }, $children)));
    $subchilds = [];
    foreach ($children as $child) {
      $subchilds = array_merge($subchilds, $this->getNestedChildIds($child));
    }
    return $this->nestedChildren[$id] = array_merge($children, $subchilds);
  }
  /**
   * {@inheritdoc}
   */
  public function getChildIds(array $ids) {
    $parents = [];
    foreach ($ids as $id) {
      $parent = $this->getContentStorage()->load($id);
      $storage = $this->getContentStorage();
      $children = \Drupal::entityQuery('node')
      ->accessCheck(TRUE)
      ->condition('type', 'article')
      ->condition('field_parent_article', $parent->id())
      ->execute();
      $children = $storage->loadMultiple($children);
      $parents[$id] = array_filter(array_values(array_map(function ($it) {
        return $it->id();
      }, $children)));
    }
    $parents = array_filter($parents);
    return $parents;
  }
  /**
   * {@inheritdoc}
   */
  public function getSiblingIds(array $ids, array $activeIds = [], bool $parentSiblings = TRUE) {
    if (empty($ids)) {
      return [];
    }
    $parentIds = [];
    $topLevelTerms = [];
    foreach ($ids as $key => $id) {
      if (!$activeIds || in_array($id, $activeIds)) {
        $currentParentIds = $this->getParentIds($id);
        if (!$currentParentIds) {
          if (!$topLevelTerms) {
            /** @var \Drupal\core\Entity\EntityInterface $entity */
            $entity = $this->getContentStorage()->load($id);
            // Issue #3260603:
            // Due to a bug in core
            // https://www.drupal.org/project/drupal/issues/2723323
            // it may happen that a content is still referenced in a
            // field, even though the term has been deleted. Not checking the
            // term is empty produces a fatal error. The same could happen if
            // someone manipulates the query parameter.
            if (!$entity instanceof EntityInterface) {
              unset($ids[$key]);
              continue;
            }
            $topLevelTerms = array_map(function ($entity) {
              return $entity->id;
            }, $this->getContentStorage()->loadByProperties(['type' => $entity->bundle()]));
          }
        } else {
          $parentIds[] = $currentParentIds;
        }
      }
    }
    $parentIds = array_unique(array_merge([], ...$parentIds));
    $childIds = array_merge([], ...$this->getChildIds($parentIds));
    return array_diff(
      array_merge(
        $childIds,
        $topLevelTerms,
        (!$topLevelTerms && $parentSiblings) ? $this->getSiblingIds($ids, $parentIds) : []
      ),
      $ids
    );
  }
  /**
   * Returns the parent article for a given article, or false if no parent exists.
   *
   * @param int $nid
   *   A content node id.
   *
   * @return int|false
   *   Returns FALSE if no parent is found, else parent nid.
   */
  protected function contentGetParent($nid) {
    if ($nid) {
      if (isset($this->contentParents[$nid])) {
        return $this->contentParents[$nid];
      }
    }
    $child = $this->getContentStorage()->load($nid);
    $parents = $child->get('field_parent_article')->value;
    if (empty($parents)) {
      return FALSE;
    }
    return $this->contentParents[$nid] = reset($parents)->id();
  }
  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    return Cache::mergeTags(parent::getCacheTags(), ['article_list']);
  }
  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return Cache::PERMANENT;
  }
}

References: https://www.drupal.org/docs/contributed-modules/facets/hierarchical-date-granularity-facet.

References from the contrib module Facets:

  1. web/modules/contrib/facets/src/Plugin/facets/hierarchy/DateItems.php
  2. web/modules/contrib/facets/src/Plugin/facets/hierarchy/Taxonomy.php

Pre-requisites: Drupal Framework Knowledge

Audience: Drupal Developers

Stars
No votes yet