toolshed-8.x-1.x-dev/modules/toolshed_search/src/Plugin/search_api/processor/EntityPropertiesProcessor.php
modules/toolshed_search/src/Plugin/search_api/processor/EntityPropertiesProcessor.php
<?php
namespace Drupal\toolshed_search\Plugin\search_api\processor;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Item\ItemInterface;
use Drupal\search_api\Plugin\search_api\data_type\value\TextValue;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Processor\ProcessorProperty;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Adds the common entity properties to the indexed data.
*
* Doing this allows creating views and filters that can be applied across
* multiple entity types using singular search fields.
*
* @SearchApiProcessor(
* id = "toolshed_search_entity_properties",
* label = @Translation("Entity properties"),
* description = @Translation("Create for common entity properties into the global datasource."),
* stages = {
* "add_properties" = 0,
* },
* locked = false,
* hidden = false,
* )
*/
class EntityPropertiesProcessor extends ProcessorPluginBase {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
/**
* Mapping Search API property paths to property ID keys for quick lookups.
*
* @var mixed[]
*/
protected array $propMappings = [];
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$processor->setEntityTypeManager($container->get('entity_type.manager'));
return $processor;
}
/**
* Retrieves the entity type manager.
*
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
* The entity type manager.
*/
public function getEntityTypeManager(): EntityTypeManagerInterface {
return $this->entityTypeManager;
}
/**
* Sets the entity type manager.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*
* @return $this
*/
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager): self {
$this->entityTypeManager = $entity_type_manager;
return $this;
}
/**
* {@inheritdoc}
*/
public static function supportsIndex(IndexInterface $index): bool {
foreach ($index->getDatasources() as $datasource) {
if ($entityTypeId = $datasource->getEntityTypeId()) {
$entityType = \Drupal::entityTypeManager()
->getDefinition($entityTypeId);
if ($entityType && $entityType->entityClassImplements(ContentEntityInterface::class)) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Get the $item fields relevant to this processor keyed by property path.
*
* @param \Drupal\search_api\Item\ItemInterface $item
* The Search API item whose fields are being populated.
*
* @return \Drupal\search_api\Item\FieldInterface[][]
* An array of fields from the Search API $item keyed by the property
* paths defined in ::getPropertyDefintions().
*/
protected function getFieldsByProperty(ItemInterface $item): array {
$indexId = $item->getIndex()->id();
$srcFields = $item->getFields();
// Create a mapping from field IDs to the property path the field has data
// for supports. This should not change per index, and could speed up
// successive calls to ::addFieldValues() for this search index.
if (!isset($this->propMappings[$indexId])) {
$mapping = [];
$properties = $this->getPropertyDefinitions();
foreach ($srcFields as $key => $field) {
$propPath = $field->getPropertyPath();
if ($field->getDatasourceId() === NULL && isset($properties[$propPath])) {
$mapping[$propPath][$key] = $key;
}
}
$this->propMappings[$indexId] = $mapping;
}
$fields = [];
foreach ($this->propMappings[$indexId] as $prop => $keys) {
foreach ($keys as $key) {
if (isset($srcFields[$key])) {
$fields[$prop][$key] = $srcFields[$key];
}
}
}
return $fields;
}
/**
* Determine the status of a content entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to determine the status of.
*
* @return bool|null
* Returns the status value of an entity if
*/
protected function getEntityStatus(ContentEntityInterface $entity): ?bool {
if ($entity instanceof EntityPublishedInterface) {
return $entity->isPublished();
}
if ($entity instanceof UserInterface) {
return $entity->isActive();
}
$entityType = $entity->getEntityType();
$statusKey = $entityType->getKey('status');
if ($statusKey && $entity->hasField($statusKey)) {
return (bool) $entity->get($statusKey)->value;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getPropertyDefinitions(?DatasourceInterface $datasource = NULL): array {
$properties = [];
// Add the properties only to the global Search API index, so skip adding
// to any specific datasource (content, taxonomy, etc...).
if (!$datasource) {
$properties['entity_label'] = new ProcessorProperty([
'label' => $this->t('Entity label'),
'description' => $this->t('The value of the label property of the entity.'),
'type' => 'search_api_text',
'is_list' => FALSE,
'processor_id' => $this->getPluginId(),
]);
$properties['entity_bundle'] = new ProcessorProperty([
'label' => $this->t('Entity bundle'),
'description' => $this->t('Canonical entity bundle name that includes the entity type and bundle together with a colon separator.'),
'type' => 'string',
'is_list' => FALSE,
'processor_id' => $this->getPluginId(),
]);
$properties['entity_status'] = new ProcessorProperty([
'label' => $this->t('Entity status'),
'description' => $this->t('Exposes a boolean status for entity types that support status. Consider using the "Entity Status" processor if looking to only filter unpublished content.'),
'type' => 'boolean',
'is_list' => FALSE,
'processor_id' => $this->getPluginId(),
]);
$properties['entity_created'] = new ProcessorProperty([
'label' => $this->t('Entity created date'),
'description' => $this->t('Entity created date if available from entity.'),
'type' => 'timestamp',
'is_list' => FALSE,
'processor_id' => $this->getPluginId(),
]);
$properties['entity_changed'] = new ProcessorProperty([
'label' => $this->t('Entity changed'),
'description' => $this->t('The entity last changed timestamp if supported by the entity type.'),
'type' => 'timestamp',
'is_list' => FALSE,
'processor_id' => $this->getPluginId(),
]);
}
return $properties;
}
/**
* {@inheritdoc}
*/
public function addFieldValues(ItemInterface $item): void {
$entity = $item->getOriginalObject()->getValue();
// Only add property information for entities.
if (!($entity instanceof ContentEntityInterface)) {
return;
}
$fields = $this->getFieldsByProperty($item);
// Add the entity label to the search index.
if (!empty($fields['entity_label'])) {
$text = new TextValue($entity->label());
foreach ($fields['entity_label'] as $field) {
$field->setValues([$text]);
}
}
// Add the entity type and bundle for single field accross entity types.
// The bundle is stored as "ENTITY_TYPE:BUNDLE_LABEL".
if (!empty($fields['entity_bundle'])) {
foreach ($fields['entity_bundle'] as $field) {
$field->setValues([$entity->getEntityTypeId() . ':' . $entity->bundle()]);
}
}
if (!empty($fields['entity_status'])) {
$statusValue = $this->getEntityStatus($entity);
foreach ($fields['entity_status'] as $field) {
$field->setValues([$statusValue]);
}
}
if ($entity->hasField('created') && !empty($fields['entity_created'])) {
$createdField = $entity->get('created');
$fieldType = $createdField->getFieldDefinition()->getType();
if (in_array($fieldType, ['timestamp', 'created'])) {
foreach ($fields['entity_created'] as $field) {
$field->setValues([$createdField->value]);
}
}
}
// Only entities that implement the EntityChangedInterface have a
// consistent way of finding the changed time.
if ($entity instanceof EntityChangedInterface && !empty($fields['entity_changed'])) {
foreach ($fields['entity_changed'] as $field) {
$field->setValues([$entity->getChangedTime()]);
}
}
}
}
