You are here

class AddHierarchy in Search API 8

Adds all ancestors' IDs to a hierarchical field.

Plugin annotation


@SearchApiProcessor(
  id = "hierarchy",
  label = @Translation("Index hierarchy"),
  description = @Translation("Allows the indexing of values along with all their ancestors for hierarchical fields (like taxonomy term references)"),
  stages = {
    "preprocess_index" = -45
  }
)

Hierarchy

Expanded class hierarchy of AddHierarchy

File

src/Plugin/search_api/processor/AddHierarchy.php, line 32

Namespace

Drupal\search_api\Plugin\search_api\processor
View source
class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface {
  use PluginFormTrait;

  /**
   * Static cache for getHierarchyFields() return values, keyed by index ID.
   *
   * @var string[][][]
   *
   * @see \Drupal\search_api\Plugin\search_api\processor\AddHierarchy::getHierarchyFields()
   */
  protected static $indexHierarchyFields = [];

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
   */
  protected $entityTypeManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {

    /** @var static $processor */
    $processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $processor
      ->setEntityTypeManager($container
      ->get('entity_type.manager'));
    return $processor;
  }

  /**
   * Retrieves the entity type manager service.
   *
   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
   *   The entity type manager service.
   */
  public function getEntityTypeManager() {
    return $this->entityTypeManager ?: \Drupal::entityTypeManager();
  }

  /**
   * Sets the entity type manager service.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   *
   * @return $this
   */
  public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public static function supportsIndex(IndexInterface $index) {
    $processor = new static([
      '#index' => $index,
    ], 'hierarchy', []);
    return (bool) $processor
      ->getHierarchyFields();
  }

  /**
   * Finds all (potentially) hierarchical fields for this processor's index.
   *
   * Fields are returned if:
   * - they point to an entity type; and
   * - that entity type contains a property referencing the same type of entity
   *   (so that a hierarchy could be built from that nested property).
   *
   * @return string[][]
   *   An array containing all fields of the index for which hierarchical data
   *   might be retrievable. The keys are those field's IDs, the values are
   *   associative arrays containing the nested properties of those fields from
   *   which a hierarchy might be constructed, with the property paths as the
   *   keys and labels as the values.
   */
  protected function getHierarchyFields() {
    if (!isset(static::$indexHierarchyFields[$this->index
      ->id()])) {
      $field_options = [];
      foreach ($this->index
        ->getFields() as $field_id => $field) {
        try {
          $definition = $field
            ->getDataDefinition();
        } catch (SearchApiException $e) {
          $vars = [
            '%index' => $this->index
              ->label(),
          ];
          watchdog_exception('search_api', $e, '%type while trying to retrieve a list of hierarchical fields on index %index: @message in %function (line %line of %file).', $vars);
          continue;
        }
        if ($definition instanceof ComplexDataDefinitionInterface) {
          $properties = $this
            ->getFieldsHelper()
            ->getNestedProperties($definition);

          // The property might be an entity data definition itself.
          $properties[''] = $definition;
          foreach ($properties as $property) {
            $property_label = $property
              ->getLabel();
            $property = $this
              ->getFieldsHelper()
              ->getInnerProperty($property);
            if ($property instanceof EntityDataDefinitionInterface) {
              $options = static::findHierarchicalProperties($property, $property_label);
              if ($options) {
                $field_options += [
                  $field_id => [],
                ];
                $field_options[$field_id] += $options;
              }
            }
          }
        }
      }
      static::$indexHierarchyFields[$this->index
        ->id()] = $field_options;
    }
    return static::$indexHierarchyFields[$this->index
      ->id()];
  }

  /**
   * Finds all hierarchical properties nested on an entity-typed property.
   *
   * @param \Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface $property
   *   The property to be searched for hierarchical nested properties.
   * @param string $property_label
   *   The property's label.
   *
   * @return string[]
   *   An options list of hierarchical properties, keyed by the parent
   *   property's entity type ID and the nested properties identifier,
   *   concatenated with a dash (-).
   */
  protected function findHierarchicalProperties(EntityDataDefinitionInterface $property, $property_label) {
    $entity_type_id = $property
      ->getEntityTypeId();
    $property_label = Utility::escapeHtml($property_label);
    $options = [];

    // Check properties for potential hierarchy. Check two levels down, since
    // Core's entity references all have an additional "entity" sub-property for
    // accessing the actual entity reference, which we'd otherwise miss.
    foreach ($this
      ->getFieldsHelper()
      ->getNestedProperties($property) as $name_2 => $property_2) {
      $property_2_label = $property_2
        ->getLabel();
      $property_2 = $this
        ->getFieldsHelper()
        ->getInnerProperty($property_2);
      $is_reference = FALSE;
      if ($property_2 instanceof EntityDataDefinitionInterface) {
        if ($property_2
          ->getEntityTypeId() == $entity_type_id) {
          $is_reference = TRUE;
        }
      }
      elseif ($property_2 instanceof ComplexDataDefinitionInterface) {
        foreach ($property_2
          ->getPropertyDefinitions() as $property_3) {
          $property_3 = $this
            ->getFieldsHelper()
            ->getInnerProperty($property_3);
          if ($property_3 instanceof EntityDataDefinitionInterface) {
            if ($property_3
              ->getEntityTypeId() == $entity_type_id) {
              $is_reference = TRUE;
              break;
            }
          }
        }
      }
      if ($is_reference) {
        $property_2_label = Utility::escapeHtml($property_2_label);
        $options["{$entity_type_id}-{$name_2}"] = $property_label . ' » ' . $property_2_label;
      }
    }
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'fields' => [],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $formState) {
    $form['#description'] = $this
      ->t('Select the fields to which hierarchical data should be added.');
    foreach ($this
      ->getHierarchyFields() as $field_id => $options) {
      $enabled = !empty($this->configuration['fields'][$field_id]);
      $form['fields'][$field_id]['status'] = [
        '#type' => 'checkbox',
        '#title' => $this->index
          ->getField($field_id)
          ->getLabel(),
        '#default_value' => $enabled,
      ];
      reset($options);
      $form['fields'][$field_id]['property'] = [
        '#type' => 'radios',
        '#title' => $this
          ->t('Hierarchy property to use'),
        '#description' => $this
          ->t("This field has several nested properties which look like they might contain hierarchy data for the field. Please pick the one that should be used."),
        '#options' => $options,
        '#default_value' => $enabled ? $this->configuration['fields'][$field_id] : key($options),
        '#access' => count($options) > 1,
        '#states' => [
          'visible' => [
            // @todo This shouldn't be dependent on the form array structure.
            //   Use the '#process' trick instead.
            ":input[name=\"processors[hierarchy][settings][fields][{$field_id}][status]\"]" => [
              'checked' => TRUE,
            ],
          ],
        ],
      ];
    }
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $formState) {
    $fields = [];
    foreach ($formState
      ->getValue('fields', []) as $field_id => $values) {
      if (!empty($values['status'])) {
        if (empty($values['property'])) {
          $formState
            ->setError($form['fields'][$field_id]['property'], $this
            ->t('You need to select a nested property to use for the hierarchy data.'));
        }
        else {
          $fields[$field_id] = $values['property'];
        }
      }
    }
    $formState
      ->setValue('fields', $fields);
    if (!$fields) {
      $formState
        ->setError($form['fields'], $this
        ->t('You need to select at least one field for which to add hierarchy data.'));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function preprocessIndexItems(array $items) {

    /** @var \Drupal\search_api\Item\ItemInterface $item */
    foreach ($items as $item) {
      foreach ($this->configuration['fields'] as $field_id => $property_specifier) {
        $field = $item
          ->getField($field_id);
        if (!$field) {
          continue;
        }
        list($entity_type_id, $property) = explode('-', $property_specifier);
        foreach ($field
          ->getValues() as $entity_id) {
          if ($entity_id instanceof TextValue) {
            $entity_id = $entity_id
              ->getOriginalText();
          }
          if (is_scalar($entity_id)) {
            try {
              $this
                ->addHierarchyValues($entity_type_id, $entity_id, $property, $field);
            } catch (\Exception $e) {
              $vars = [
                '%index' => $this->index
                  ->label(),
                '%field' => $field
                  ->getLabel(),
                '%field_id' => $field
                  ->getFieldIdentifier(),
              ];
              watchdog_exception('search_api', $e, '%type while trying to add hierarchy values to field %field (%field_id) on index %index: @message in %function (line %line of %file).', $vars);
              continue;
            }
          }
        }
      }
    }
  }

  /**
   * Adds all ancestors' IDs of the given entity to the given field.
   *
   * @param string $entityTypeId
   *   The entity type ID.
   * @param mixed $entityId
   *   The ID of the entity for which ancestors should be found.
   * @param string $property
   *   The name of the property on the entity type which contains the references
   *   to the parent entities.
   * @param \Drupal\search_api\Item\FieldInterface $field
   *   The field to which values should be added.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *   Thrown if a referenced entity type does not exist.
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   *   Thrown if a referenced entity's storage handler couldn't be loaded.
   */
  protected function addHierarchyValues($entityTypeId, $entityId, $property, FieldInterface $field) {
    if ("{$entityTypeId}-{$property}" == 'taxonomy_term-parent') {

      /** @var \Drupal\taxonomy\TermStorageInterface $entity_storage */
      $entity_storage = $this
        ->getEntityTypeManager()
        ->getStorage('taxonomy_term');
      $parents = [];
      foreach ($entity_storage
        ->loadParents($entityId) as $term) {
        $parents[] = $term
          ->id();
      }
    }
    else {
      $entity = $this
        ->getEntityTypeManager()
        ->getStorage($entityTypeId)
        ->load($entityId);
      $parents = [];
      if ($entity instanceof ContentEntityInterface) {
        try {
          foreach ($entity
            ->get($property) as $data) {
            $values = static::getFieldsHelper()
              ->extractFieldValues($data);
            $parents = array_merge($parents, $values);
          }
        } catch (\InvalidArgumentException $e) {

          // Might happen, for example, if the property only exists on a certain
          // bundle, and this entity has the wrong one.
        }
      }
    }
    foreach ($parents as $parent) {
      if (!in_array($parent, $field
        ->getValues())) {
        $field
          ->addValue($parent);
        $this
          ->addHierarchyValues($entityTypeId, $parent, $property, $field);
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AddHierarchy::$entityTypeManager protected property The entity type manager.
AddHierarchy::$indexHierarchyFields protected static property Static cache for getHierarchyFields() return values, keyed by index ID.
AddHierarchy::addHierarchyValues protected function Adds all ancestors' IDs of the given entity to the given field.
AddHierarchy::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm
AddHierarchy::create public static function Creates an instance of the plugin. Overrides ProcessorPluginBase::create
AddHierarchy::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurablePluginBase::defaultConfiguration
AddHierarchy::findHierarchicalProperties protected function Finds all hierarchical properties nested on an entity-typed property.
AddHierarchy::getEntityTypeManager public function Retrieves the entity type manager service.
AddHierarchy::getHierarchyFields protected function Finds all (potentially) hierarchical fields for this processor's index.
AddHierarchy::preprocessIndexItems public function Preprocesses search items for indexing. Overrides ProcessorPluginBase::preprocessIndexItems
AddHierarchy::setEntityTypeManager public function Sets the entity type manager service.
AddHierarchy::supportsIndex public static function Checks whether this processor is applicable for a certain index. Overrides ProcessorPluginBase::supportsIndex
AddHierarchy::validateConfigurationForm public function Form validation handler. Overrides PluginFormTrait::validateConfigurationForm
ConfigurablePluginBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies 6
ConfigurablePluginBase::calculatePluginDependencies Deprecated protected function Calculates and adds dependencies of a specific plugin instance.
ConfigurablePluginBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
ConfigurablePluginBase::getDescription public function Returns the plugin's description. Overrides ConfigurablePluginInterface::getDescription
ConfigurablePluginBase::getPluginDependencies Deprecated protected function Calculates and returns dependencies of a specific plugin instance.
ConfigurablePluginBase::label public function Returns the label for use on the administration pages. Overrides ConfigurablePluginInterface::label
ConfigurablePluginBase::moduleHandler Deprecated protected function Wraps the module handler.
ConfigurablePluginBase::onDependencyRemoval public function Informs the plugin that some of its dependencies are being removed. Overrides ConfigurablePluginInterface::onDependencyRemoval 5
ConfigurablePluginBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration 3
ConfigurablePluginBase::themeHandler Deprecated protected function Wraps the theme handler.
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
DependencyTrait::$dependencies protected property The object's dependencies.
DependencyTrait::addDependencies protected function Adds multiple dependencies.
DependencyTrait::addDependency protected function Adds a dependency.
IndexPluginBase::$index protected property The index this processor is configured for.
IndexPluginBase::getIndex public function Retrieves the index this plugin is configured for. Overrides IndexPluginInterface::getIndex
IndexPluginBase::setIndex public function Sets the index this plugin is configured for. Overrides IndexPluginInterface::setIndex
IndexPluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. Overrides ConfigurablePluginBase::__construct 2
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginDependencyTrait::calculatePluginDependencies protected function Calculates and adds dependencies of a specific plugin instance. Aliased as: traitCalculatePluginDependencies 1
PluginDependencyTrait::getPluginDependencies protected function Calculates and returns dependencies of a specific plugin instance. Aliased as: traitGetPluginDependencies
PluginDependencyTrait::moduleHandler protected function Wraps the module handler. Aliased as: traitModuleHandler 1
PluginDependencyTrait::themeHandler protected function Wraps the theme handler. Aliased as: traitThemeHandler 1
PluginFormTrait::submitConfigurationForm public function Form submission handler. 7
ProcessorInterface::STAGE_ADD_PROPERTIES constant Processing stage: add properties.
ProcessorInterface::STAGE_ALTER_ITEMS constant Processing stage: alter indexed items.
ProcessorInterface::STAGE_POSTPROCESS_QUERY constant Processing stage: postprocess query.
ProcessorInterface::STAGE_PREPROCESS_INDEX constant Processing stage: preprocess index.
ProcessorInterface::STAGE_PREPROCESS_QUERY constant Processing stage: preprocess query.
ProcessorInterface::STAGE_PRE_INDEX_SAVE constant Processing stage: preprocess index.
ProcessorPluginBase::$fieldsHelper protected property The fields helper. 1
ProcessorPluginBase::addFieldValues public function Adds the values of properties defined by this processor to the item. Overrides ProcessorInterface::addFieldValues 8
ProcessorPluginBase::alterIndexedItems public function Alter the items to be indexed. Overrides ProcessorInterface::alterIndexedItems 3
ProcessorPluginBase::ensureField protected function Ensures that a field with certain properties is indexed on the index.
ProcessorPluginBase::findField protected function Finds a certain field in the index.
ProcessorPluginBase::getFieldsHelper public function Retrieves the fields helper. 1
ProcessorPluginBase::getPropertyDefinitions public function Retrieves the properties this processor defines for the given datasource. Overrides ProcessorInterface::getPropertyDefinitions 8
ProcessorPluginBase::getWeight public function Returns the weight for a specific processing stage. Overrides ProcessorInterface::getWeight
ProcessorPluginBase::isHidden public function Determines whether this plugin should be hidden in the UI. Overrides HideablePluginBase::isHidden
ProcessorPluginBase::isLocked public function Determines whether this processor should always be enabled. Overrides ProcessorInterface::isLocked
ProcessorPluginBase::postprocessSearchResults public function Postprocess search results before they are returned by the query. Overrides ProcessorInterface::postprocessSearchResults 2
ProcessorPluginBase::preIndexSave public function Preprocesses the search index entity before it is saved. Overrides ProcessorInterface::preIndexSave 5
ProcessorPluginBase::preprocessSearchQuery public function Preprocesses a search query. Overrides ProcessorInterface::preprocessSearchQuery 4
ProcessorPluginBase::requiresReindexing public function Determines whether re-indexing is required after a settings change. Overrides ProcessorInterface::requiresReindexing
ProcessorPluginBase::setFieldsHelper public function Sets the fields helper. 1
ProcessorPluginBase::setWeight public function Sets the weight for a specific processing stage. Overrides ProcessorInterface::setWeight
ProcessorPluginBase::supportsStage public function Checks whether this processor implements a particular stage. Overrides ProcessorInterface::supportsStage 2
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.