search_api-8.x-1.15/src/Item/Item.php
src/Item/Item.php
<?php namespace Drupal\search_api\Item; use Drupal\Core\Access\AccessResult; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\search_api\Datasource\DatasourceInterface; use Drupal\search_api\IndexInterface; use Drupal\search_api\LoggerTrait; use Drupal\search_api\Processor\ProcessorInterface; use Drupal\search_api\Processor\ProcessorPropertyInterface; use Drupal\search_api\SearchApiException; use Drupal\search_api\Utility\Utility; /** * Provides a default implementation for a search item. */ class Item implements \IteratorAggregate, ItemInterface { use LoggerTrait; /** * The search index with which this item is associated. * * @var \Drupal\search_api\IndexInterface */ protected $index; /** * The ID of this item. * * @var string */ protected $itemId; /** * The complex data item this Search API item is based on. * * @var \Drupal\Core\TypedData\ComplexDataInterface */ protected $originalObject; /** * The ID of this item's datasource. * * @var string */ protected $datasourceId; /** * The datasource of this item. * * @var \Drupal\search_api\Datasource\DatasourceInterface */ protected $datasource; /** * The language code of this item. * * @var string */ protected $language; /** * The extracted fields of this item. * * @var \Drupal\search_api\Item\FieldInterface[] */ protected $fields = []; /** * Whether the fields were already extracted for this item. * * @var bool */ protected $fieldsExtracted = FALSE; /** * The HTML text with highlighted text-parts that match the query. * * @var string */ protected $excerpt; /** * The score this item had as a result in a corresponding search query. * * @var float */ protected $score = 1.0; /** * The boost of this item at indexing time. * * @var float */ protected $boost = 1.0; /** * Extra data set on this item. * * @var array */ protected $extraData = []; /** * Cached access results for the item, keyed by user ID. * * @var \Drupal\Core\Access\AccessResultInterface[] * * @see getAccessResult() */ protected $accessResults = []; /** * Constructs an Item object. * * @param \Drupal\search_api\IndexInterface $index * The item's search index. * @param string $id * The ID of this item. * @param \Drupal\search_api\Datasource\DatasourceInterface|null $datasource * (optional) The datasource of this item. If not set, it will be determined * from the ID and loaded from the index. */ public function __construct(IndexInterface $index, $id, DatasourceInterface $datasource = NULL) { $this->index = $index; $this->itemId = $id; if ($datasource) { $this->datasource = $datasource; $this->datasourceId = $datasource->getPluginId(); } } /** * {@inheritdoc} */ public function getDatasourceId() { if (!isset($this->datasourceId)) { list($this->datasourceId) = Utility::splitCombinedId($this->itemId); } return $this->datasourceId; } /** * {@inheritdoc} */ public function getDatasource() { if (!isset($this->datasource)) { $this->datasource = $this->index->getDatasource($this->getDatasourceId()); } return $this->datasource; } /** * {@inheritdoc} */ public function getIndex() { return $this->index; } /** * {@inheritdoc} */ public function getLanguage() { if (!isset($this->language)) { $this->language = $this->getDatasource()->getItemLanguage($this->getOriginalObject()); } return $this->language; } /** * {@inheritdoc} */ public function setLanguage($language) { $this->language = $language; return $this; } /** * {@inheritdoc} */ public function getId() { return $this->itemId; } /** * {@inheritdoc} */ public function getOriginalObject($load = TRUE) { if (!isset($this->originalObject) && $load) { $this->originalObject = $this->index->loadItem($this->itemId); if (!$this->originalObject) { throw new SearchApiException('Failed to load original object ' . $this->itemId); } } return $this->originalObject; } /** * {@inheritdoc} */ public function setOriginalObject(ComplexDataInterface $original_object) { $this->originalObject = $original_object; return $this; } /** * {@inheritdoc} */ public function getField($field_id, $extract = TRUE) { if (isset($this->fields[$field_id])) { return $this->fields[$field_id]; } $fields = $this->getFields($extract); return isset($fields[$field_id]) ? $fields[$field_id] : NULL; } /** * {@inheritdoc} */ public function getFields($extract = TRUE) { if ($extract && !$this->fieldsExtracted) { $data_type_fallback_mapping = \Drupal::getContainer() ->get('search_api.data_type_helper') ->getDataTypeFallbackMapping($this->index); foreach ([NULL, $this->getDatasourceId()] as $datasource_id) { $fields_by_property_path = []; $processors_with_fields = []; $properties = $this->index->getPropertyDefinitions($datasource_id); foreach ($this->index->getFieldsByDatasource($datasource_id) as $field_id => $field) { // Don't overwrite fields that were previously set. if (empty($this->fields[$field_id])) { $this->fields[$field_id] = clone $field; $field_data_type = $this->fields[$field_id]->getType(); // If the field data type is in the fallback mapping list, then use // the fallback type as field type. if (isset($data_type_fallback_mapping[$field_data_type])) { $this->fields[$field_id]->setType($data_type_fallback_mapping[$field_data_type]); } // For determining whether the field is provided via a processor, we // need to check using the first part of its property path (in other // words, the property that's directly on the result item, not // nested), since only direct properties of the item can be added by // the processor. $property = NULL; $property_name = Utility::splitPropertyPath($field->getPropertyPath(), FALSE)[0]; if (isset($properties[$property_name])) { $property = $properties[$property_name]; } if ($property instanceof ProcessorPropertyInterface) { $processors_with_fields[$property->getProcessorId()] = TRUE; } elseif ($datasource_id) { $fields_by_property_path[$field->getPropertyPath()][] = $this->fields[$field_id]; } } } try { if ($fields_by_property_path) { \Drupal::getContainer() ->get('search_api.fields_helper') ->extractFields($this->getOriginalObject(), $fields_by_property_path, $this->getLanguage()); } if ($processors_with_fields) { $processors = $this->index->getProcessorsByStage(ProcessorInterface::STAGE_ADD_PROPERTIES); foreach ($processors as $processor_id => $processor) { if (isset($processors_with_fields[$processor_id])) { $processor->addFieldValues($this); } } } } catch (SearchApiException $e) { // If we couldn't load the object, just log an error and fail // silently to set the values. $this->logException($e); } } $this->fieldsExtracted = TRUE; } return $this->fields; } /** * {@inheritdoc} */ public function setField($field_id, FieldInterface $field = NULL) { if ($field) { if ($field->getFieldIdentifier() !== $field_id) { throw new \InvalidArgumentException('The field identifier passed must be consistent with the identifier set on the field object.'); } // Make sure that the field has the same index object set as we. This // might otherwise cause impossibly hard-to-detect bugs. $field->setIndex($this->index); $this->fields[$field_id] = $field; } else { unset($this->fields[$field_id]); } return $this; } /** * {@inheritdoc} */ public function setFields(array $fields) { // Make sure that all fields have the same index object set as we. This // might otherwise cause impossibly hard-to-detect bugs. /** @var \Drupal\search_api\Item\FieldInterface $field */ foreach ($fields as $field) { $field->setIndex($this->index); } $this->fields = $fields; return $this; } /** * {@inheritdoc} */ public function isFieldsExtracted() { return $this->fieldsExtracted; } /** * {@inheritdoc} */ public function setFieldsExtracted($fields_extracted) { $this->fieldsExtracted = $fields_extracted; return $this; } /** * {@inheritdoc} */ public function getScore() { return $this->score; } /** * {@inheritdoc} */ public function setScore($score) { $this->score = $score; return $this; } /** * {@inheritdoc} */ public function getBoost() { return $this->boost; } /** * {@inheritdoc} */ public function setBoost($boost) { $this->boost = $boost; return $this; } /** * {@inheritdoc} */ public function getExcerpt() { return $this->excerpt; } /** * {@inheritdoc} */ public function setExcerpt($excerpt) { $this->excerpt = $excerpt; return $this; } /** * {@inheritdoc} */ public function hasExtraData($key) { return array_key_exists($key, $this->extraData); } /** * {@inheritdoc} */ public function getExtraData($key, $default = NULL) { return array_key_exists($key, $this->extraData) ? $this->extraData[$key] : $default; } /** * {@inheritdoc} */ public function getAllExtraData() { return $this->extraData; } /** * {@inheritdoc} */ public function setExtraData($key, $data = NULL) { if (isset($data)) { $this->extraData[$key] = $data; } else { unset($this->extraData[$key]); } return $this; } /** * {@inheritdoc} */ public function checkAccess(AccountInterface $account = NULL) { @trigger_error('\Drupal\search_api\Item\ItemInterface::checkAccess() is deprecated in search_api:8.x-1.14 and is removed from search_api:9.x-1.0. Use getAccessResult() instead. See https://www.drupal.org/node/3051902', E_USER_DEPRECATED); return $this->getAccessResult($account)->isAllowed(); } /** * {@inheritdoc} */ public function getAccessResult(AccountInterface $account = NULL) { if (!$account) { $account = \Drupal::currentUser(); } $uid = $account->id(); if (empty($this->accessResults[$uid])) { try { $this->accessResults[$uid] = $this->getDatasource() ->getItemAccessResult($this->getOriginalObject(), $account); } catch (SearchApiException $e) { $this->accessResults[$uid] = AccessResult::neutral('Item could not be loaded, so cannot check access'); } } return $this->accessResults[$uid]; } /** * {@inheritdoc} */ public function getIterator() { return new \ArrayIterator($this->getFields()); } /** * Implements the magic __clone() method to implement a deep clone. */ public function __clone() { // The fields definitely need to be cloned. For the extra data its hard (or, // rather, impossible) to tell, but we opt for cloning objects there, too, // to be on the (hopefully) safer side. (Ideas for later: introduce an // interface that tells us to not clone the data object; or check whether // its an entity; or introduce some other system to override this default.) foreach ($this->fields as $field_id => $field) { $this->fields[$field_id] = clone $field; } foreach ($this->extraData as $key => $data) { if (is_object($data)) { $this->extraData[$key] = clone $data; } } } /** * Implements the magic __toString() method to simplify debugging. */ public function __toString() { $out = 'Item ' . $this->getId(); if ($this->getScore() != 1) { $out .= "\nScore: " . $this->getScore(); } if ($this->getBoost() != 1) { $out .= "\nBoost: " . $this->getBoost(); } if ($this->getExcerpt()) { $excerpt = str_replace("\n", "\n ", $this->getExcerpt()); $out .= "\nExcerpt: $excerpt"; } if ($this->getFields(FALSE)) { $out .= "\nFields:"; foreach ($this->getFields(FALSE) as $field) { $field = str_replace("\n", "\n ", "$field"); $out .= "\n- " . $field; } } if ($this->getAllExtraData()) { $data = str_replace("\n", "\n ", print_r($this->getAllExtraData(), TRUE)); $out .= "\nExtra data: " . $data; } return $out; } }