/** * Tests field highlighting and excerpts with two items. */ public function testPostprocessSearchResultsWithTwoItems() { $query = $this->getMock('Drupal\\search_api\\Query\\QueryInterface'); $query->expects($this->atLeastOnce())->method('getKeys')->will($this->returnValue(array('#conjunction' => 'OR', 'foo'))); /** @var \Drupal\search_api\Query\QueryInterface $query */ /** @var \Drupal\search_api\IndexInterface|\PHPUnit_Framework_MockObject_MockObject $index */ $index = $this->getMock('Drupal\\search_api\\IndexInterface'); $body_field = Utility::createField($index, 'body'); $body_field->setType('text'); $body_field->setDatasourceId('entity:node'); $body_field->setPropertyPath('body'); $index->expects($this->atLeastOnce())->method('getFields')->will($this->returnValue(array('body' => $body_field))); $this->processor->setIndex($index); $body_values = array('Some foo value', 'foo bar'); $fields = array('entity:node/body' => array('type' => 'text', 'values' => $body_values)); $items = $this->createItems($index, 2, $fields); $items[$this->itemIds[1]]->getField('body')->setValues(array('The second item also contains foo in its body.')); $results = Utility::createSearchResultSet($query); $results->setResultItems($items); $results->setResultCount(1); $this->processor->postprocessSearchResults($results); $output = $results->getExtraData('highlighted_fields'); $this->assertEquals('Some <strong>foo</strong> value', $output[$this->itemIds[0]]['body'][0], 'Highlighting is correctly applied to first body field value.'); $this->assertEquals('<strong>foo</strong> bar', $output[$this->itemIds[0]]['body'][1], 'Highlighting is correctly applied to second body field value.'); $this->assertEquals('The second item also contains <strong>foo</strong> in its body.', $output[$this->itemIds[1]]['body'][0], 'Highlighting is correctly applied to second item.'); $excerpt1 = '… Some <strong>foo</strong> value … <strong>foo</strong> bar …'; $excerpt2 = '… The second item also contains <strong>foo</strong> in its body. …'; $this->assertEquals($excerpt1, $items[$this->itemIds[0]]->getExcerpt(), 'Correct excerpt created from two text fields.'); $this->assertEquals($excerpt2, $items[$this->itemIds[1]]->getExcerpt(), 'Correct excerpt created for second item.'); }
/** * Adds a field to a search index. * * The index will not be saved automatically. * * @param \Drupal\search_api\IndexInterface $index * The search index. * @param string $property_name * The property's name. * @param string $type * (optional) The field type. */ protected function addField(IndexInterface $index, $property_name, $type = 'text') { $field_info = array('label' => $property_name, 'type' => $type, 'datasource_id' => 'entity:entity_test', 'property_path' => $property_name); $field = Utility::createField($index, $property_name, $field_info); $index->addField($field); $index->save(); }
/** * {@inheritdoc} */ public function preprocessIndexItems(array &$items) { if (!$items || empty($this->configuration['fields'])) { return; } $label_not_empty = function (array $field_definition) { return !empty($field_definition['label']); }; $aggregated_fields = array_filter($this->configuration['fields'], $label_not_empty); if (!$aggregated_fields) { return; } $required_properties_by_datasource = array_fill_keys($this->index->getDatasourceIds(), array()); $required_properties_by_datasource[NULL] = array(); foreach ($aggregated_fields as $field_definition) { foreach ($field_definition['fields'] as $combined_id) { list($datasource_id, $property_path) = Utility::splitCombinedId($combined_id); $required_properties_by_datasource[$datasource_id][$property_path] = $combined_id; } } /** @var \Drupal\search_api\Item\ItemInterface[] $items */ foreach ($items as $item) { // Extract the required properties. $property_values = array(); /** @var \Drupal\search_api\Item\FieldInterface[] $missing_fields */ $missing_fields = array(); foreach (array(NULL, $item->getDatasourceId()) as $datasource_id) { foreach ($required_properties_by_datasource[$datasource_id] as $property_path => $combined_id) { // If a field with the right property path is already set on the item, // use it. This might actually make problems in case the values have // already been processed in some way, or use a data type that // transformed their original value – but on the other hand, it's // (currently – see #2575003) the only way to include computed // (processor-added) properties here, so it seems like a fair // trade-off. foreach ($this->filterForPropertyPath($item->getFields(FALSE), $property_path) as $field) { if ($field->getDatasourceId() === $datasource_id) { $property_values[$combined_id] = $field->getValues(); continue 2; } } // If the field is not already on the item, we need to extract it. We // set our own combined ID as the field identifier as kind of a hack, // to easily be able to add the field values to $property_values // afterwards. if ($datasource_id) { $missing_fields[$property_path] = Utility::createField($this->index, $combined_id); } else { // Extracting properties without a datasource is pointless. $property_values[$combined_id] = array(); } } } if ($missing_fields) { Utility::extractFields($item->getOriginalObject(), $missing_fields); foreach ($missing_fields as $field) { $property_values[$field->getFieldIdentifier()] = $field->getValues(); } } foreach ($this->configuration['fields'] as $aggregated_field_id => $aggregated_field) { $values = array(); foreach ($aggregated_field['fields'] as $combined_id) { if (!empty($property_values[$combined_id])) { $values = array_merge($values, $property_values[$combined_id]); } } switch ($aggregated_field['type']) { case 'concat': $values = array(implode("\n\n", $values)); break; case 'sum': $values = array(array_sum($values)); break; case 'count': $values = array(count($values)); break; case 'max': $values = array(max($values)); break; case 'min': $values = array(min($values)); break; case 'first': if ($values) { $values = array(reset($values)); } break; } if ($values) { foreach ($this->filterForPropertyPath($item->getFields(), $aggregated_field_id) as $field) { $field->setValues($values); } } } } }
/** * Creates a certain number of test items. * * @param \Drupal\search_api\IndexInterface $index * The index that should be used for the items. * @param int $count * The number of items to create. * @param array[] $fields * The fields to create on the items, with keys being field IDs and values * being arrays with the following information: * - type: The type to set for the field. * - values: (optional) The values to set for the field. * @param \Drupal\Core\TypedData\ComplexDataInterface|null $object * (optional) The object to set on each item as the "original object". * @param array|null $datasource_ids * (optional) An array of datasource IDs to use for the items, in that order * (starting again from the front if necessary). Defaults to using * "entity:node" for all items. * * @return \Drupal\search_api\Item\ItemInterface[] * An array containing the requested test items. */ public function createItems(IndexInterface $index, $count, array $fields, ComplexDataInterface $object = NULL, array $datasource_ids = NULL) { if (!isset($datasource_ids)) { $datasource_ids = array('entity:node'); } $datasource_count = count($datasource_ids); $items = array(); for ($i = 0; $i < $count; ++$i) { $datasource_id = $datasource_ids[$i % $datasource_count]; $this->itemIds[$i] = $item_id = Utility::createCombinedId($datasource_id, $i + 1 . ':en'); $item = Utility::createItem($index, $item_id); if (isset($object)) { $item->setOriginalObject($object); } foreach ($fields as $field_id => $field_info) { // Only add fields of the right datasource. list($field_datasource_id) = Utility::splitCombinedId($field_id); if (isset($field_datasource_id) && $field_datasource_id != $datasource_id) { continue; } $field = Utility::createField($index, $field_id)->setType($field_info['type']); if (isset($field_info['values'])) { $field->setValues($field_info['values']); } $item->setField($field_id, $field); } $item->setFieldsExtracted(TRUE); $items[$item_id] = $item; } return $items; }
/** * Converts an array of property definitions into Search API field objects. * * Stores the resulting values in $this->fields, to be returned by subsequent * getFields() calls. * * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties * An array of properties on some complex data object. * @param string|null $datasource_id * (optional) The ID of the datasource to which these properties belong. * @param string $prefix * Internal use only. A prefix to use for the generated field names in this * method. * @param string $label_prefix * Internal use only. A prefix to use for the generated field labels in this * method. * * @throws \Drupal\search_api\SearchApiException * Thrown if $datasource_id is not valid datasource for this index. */ protected function convertPropertyDefinitionsToFields(array $properties, $datasource_id = NULL, $prefix = '', $label_prefix = '') { $type_mapping = Utility::getFieldTypeMapping(); $field_options = isset($this->options['fields']) ? $this->options['fields'] : array(); $enabled_additional_fields = isset($this->options['additional fields']) ? $this->options['additional fields'] : array(); // All field identifiers should start with the datasource ID. if (!$prefix && $datasource_id) { $prefix = $datasource_id . self::DATASOURCE_ID_SEPARATOR; } $datasource_label = $datasource_id ? $this->getDatasource($datasource_id)->label() . ' » ' : ''; // Loop over all properties and handle them accordingly. $recurse = array(); foreach ($properties as $property_path => $property) { $original_property = $property; if ($property instanceof PropertyInterface) { $property = $property->getWrappedProperty(); } $key = "$prefix$property_path"; $label = $label_prefix . $property->getLabel(); $description = $property->getDescription(); while ($property instanceof ListDataDefinitionInterface) { $property = $property->getItemDefinition(); } while ($property instanceof DataReferenceDefinitionInterface) { $property = $property->getTargetDefinition(); } if ($property instanceof ComplexDataDefinitionInterface) { $main_property = $property->getMainPropertyName(); $nested_properties = $property->getPropertyDefinitions(); // Don't add the additional 'entity' property for entity reference // fields which don't target a content entity type. $referenced_entity_type_label = NULL; if ($property instanceof FieldItemDataDefinition && in_array($property->getDataType(), array('field_item:entity_reference', 'field_item:image', 'field_item:file'))) { $entity_type = $this->entityManager()->getDefinition($property->getSetting('target_type')); if (!$entity_type->isSubclassOf('Drupal\Core\Entity\ContentEntityInterface')) { unset($nested_properties['entity']); } else { $referenced_entity_type_label = $entity_type->getLabel(); } } $additional = count($nested_properties) > 1; if (!empty($enabled_additional_fields[$key]) && $nested_properties) { // We allow the main property to be indexed directly, so we don't // have to add it again for the nested fields. if ($main_property) { unset($nested_properties[$main_property]); } if ($nested_properties) { $additional = TRUE; $recurse[] = array($nested_properties, $datasource_id, "$key:", "$label » "); } } if ($additional) { $additional_field = Utility::createAdditionalField($this, $key); $additional_field->setLabel("$label [$key]"); $additional_field->setDescription($description); $additional_field->setEnabled(!empty($enabled_additional_fields[$key])); $additional_field->setLocked(FALSE); if ($original_property instanceof PropertyInterface) { $additional_field->setHidden($original_property->isHidden()); } $this->fields[0]['additional fields'][$key] = $additional_field; if ($additional_field->isEnabled()) { while ($pos = strrpos($property_path, ':')) { $property_path = substr($property_path, 0, $pos); /** @var \Drupal\search_api\Item\AdditionalFieldInterface $additional_field */ $additional_field = $this->fields[0]['additional fields'][$property_path]; $additional_field->setEnabled(TRUE); $additional_field->setLocked(); } } } // If the complex data type has a main property, we can index that // directly here. Otherwise, we don't add it and continue with the next // property. if (!$main_property) { continue; } $parent_type = $property->getDataType(); $property = $property->getPropertyDefinition($main_property); if (!$property) { continue; } // If there are additional properties, add the label for the main // property to make it clear what it refers to. if ($additional) { $nested_label = $property->getLabel(); if ($referenced_entity_type_label) { $nested_label = str_replace('@label', $referenced_entity_type_label, $nested_label); } $label .= ' » ' . $nested_label; } } $type = $property->getDataType(); // Try to see if there's a mapping for a parent.child data type. if (isset($parent_type) && isset($type_mapping[$parent_type . '.' . $type])) { $field_type = $type_mapping[$parent_type . '.' . $type]; } elseif (!empty($type_mapping[$type])) { $field_type = $type_mapping[$type]; } else { // Failed to map this type, skip. if (!isset($type_mapping[$type])) { $this->unmappedFields[$type][$key] = $key; } continue; } $field = Utility::createField($this, $key); $field->setType($field_type); $field->setLabel($label); $field->setLabelPrefix($datasource_label); $field->setDescription($description); $field->setIndexed(FALSE); // To make it possible to lock fields that are, technically, nested, use // the original $property for this check. if ($original_property instanceof PropertyInterface) { $field->setIndexedLocked($original_property->isIndexedLocked()); $field->setTypeLocked($original_property->isTypeLocked()); $field->setHidden($original_property->isHidden()); } $this->fields[0]['fields'][$key] = $field; if (isset($field_options[$key]) || $field->isIndexedLocked()) { $field->setIndexed(TRUE); if (isset($field_options[$key])) { $field->setType($field_options[$key]['type']); if (isset($field_options[$key]['boost'])) { $field->setBoost($field_options[$key]['boost']); } } $this->fields[1]['fields'][$key] = $field; } } foreach ($recurse as $arguments) { call_user_func_array(array($this, 'convertPropertyDefinitionsToFields'), $arguments); } // Order unindexed fields alphabetically. $sort_by_label = function(GenericFieldInterface $field1, GenericFieldInterface $field2) { return strnatcasecmp($field1->getLabel(), $field2->getLabel()); }; uasort($this->fields[0]['fields'], $sort_by_label); uasort($this->fields[0]['additional fields'], $sort_by_label); }
/** * {@inheritdoc} */ public function getFields() { // ::$fieldInstances is already filled with fields, so keep on using those. if (!is_null($this->fieldInstances)) { return $this->fieldInstances; } $fields = array(); foreach ($this->field_settings as $key => $field_info) { $fields[$key] = Utility::createField($this, $key, $field_info); } $this->fieldInstances = $fields; return $fields; }
/** * Creates a certain number of test items. * * @param \Drupal\search_api\IndexInterface $index * The index that should be used for the items. * @param int $count * The number of items to create. * @param array[] $fields * The fields to create on the items, with keys being combined property * paths and values being arrays with properties to set on the field. * @param \Drupal\Core\TypedData\ComplexDataInterface|null $object * (optional) The object to set on each item as the "original object". * @param array|null $datasource_ids * (optional) An array of datasource IDs to use for the items, in that order * (starting again from the front if necessary). * * @return \Drupal\search_api\Item\ItemInterface[] * An array containing the requested test items. */ public function createItems(IndexInterface $index, $count, array $fields, ComplexDataInterface $object = NULL, array $datasource_ids = array('entity:node')) { $datasource_count = count($datasource_ids); $items = array(); for ($i = 0; $i < $count; ++$i) { $datasource_id = $datasource_ids[$i % $datasource_count]; $this->itemIds[$i] = $item_id = Utility::createCombinedId($datasource_id, $i + 1 . ':en'); $item = Utility::createItem($index, $item_id); if (isset($object)) { $item->setOriginalObject($object); } foreach ($fields as $combined_property_path => $field_info) { list($field_info['datasource_id'], $field_info['property_path']) = Utility::splitCombinedId($combined_property_path); // Only add fields of the right datasource. if (isset($field_info['datasource_id']) && $field_info['datasource_id'] != $datasource_id) { continue; } $field_id = Utility::getNewFieldId($index, $field_info['property_path']); $field = Utility::createField($index, $field_id, $field_info); $item->setField($field_id, $field); } $item->setFieldsExtracted(TRUE); $items[$item_id] = $item; } return $items; }
/** * Retrieves this field's label. * * The field's label, contrary to the label returned by the field's data * definition, contains a human-readable representation of the full property * path. The datasource label is not included, though – use getPrefixedLabel() * for that. * * @return string * A human-readable label representing this field's property path. * * @see \Drupal\search_api\Item\GenericFieldInterface::getLabel() */ public function getLabel() { if (!isset($this->label)) { $label = ''; try { $label = $this->getDataDefinition()->getLabel(); } catch (SearchApiException $e) { watchdog_exception('search_api', $e); } $pos = strrpos($this->propertyPath, ':'); if ($pos) { $parent_id = substr($this->propertyPath, 0, $pos); if ($this->datasourceId) { $parent_id = Utility::createCombinedId($this->datasourceId, $parent_id); } $label = Utility::createField($this->index, $parent_id)->getLabel() . ' » ' . $label; } $this->label = $label; } return $this->label; }
/** * Tests whether the properties are correctly altered. * * @see \Drupal\search_api\Plugin\search_api\processor\AggregatedField::alterPropertyDefinitions() */ public function testAlterPropertyDefinitions() { $fields = array( 'entity:test1/foo', 'entity:test1/foo:bar', 'entity:test2/foobaz:bla', ); $index_fields = array(); foreach ($fields as $field_id) { $field_object = Utility::createField($this->index, $field_id); list($prefix, $label) = str_replace(':', ' » ', Utility::splitCombinedId($field_id)); $field_object->setLabelPrefix($prefix . ' » '); $field_object->setLabel($label); $index_fields[$field_id] = $field_object; } $this->index->expects($this->any()) ->method('getFields') ->will($this->returnValue($index_fields)); $configuration['fields'] = array( 'search_api_aggregation_1' => array( 'label' => 'Field 1', 'type' => 'union', 'fields' => array( 'entity:test1/foo', 'entity:test1/foo:bar', 'entity:test2/foobaz:bla', ), ), 'search_api_aggregation_2' => array( 'label' => 'Field 2', 'type' => 'max', 'fields' => array( 'entity:test1/foo:bar', ), ), ); $this->processor->setConfiguration($configuration); /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ $translation = $this->getStringTranslationStub(); $this->processor->setStringTranslation($translation); // Check for modified properties when no data source is given. $properties = array(); $this->processor->alterPropertyDefinitions($properties, NULL); $property_added = array_key_exists('search_api_aggregation_1', $properties); $this->assertTrue($property_added, 'The "search_api_aggregation_1" property was added to the properties.'); if ($property_added) { $this->assertInstanceOf('Drupal\Core\TypedData\DataDefinitionInterface', $properties['search_api_aggregation_1'], 'The "search_api_aggregation_1" property contains a valid data definition.'); if ($properties['search_api_aggregation_1'] instanceof DataDefinitionInterface) { $this->assertEquals('string', $properties['search_api_aggregation_1']->getDataType(), 'Correct data type set in the data definition.'); $this->assertEquals('Field 1', $properties['search_api_aggregation_1']->getLabel(), 'Correct label set in the data definition.'); $description = $translation->translate('A @type aggregation of the following fields: @fields.', array('@type' => 'Union', '@fields' => 'entity » test1 » foo, entity » test1 » foo » bar, entity » test2 » foobaz » bla'));; $this->assertEquals($description, $properties['search_api_aggregation_1']->getDescription(), 'Correct description set in the data definition.'); } } $property_added = array_key_exists('search_api_aggregation_2', $properties); $this->assertTrue($property_added, 'The "search_api_aggregation_2" property was added to the properties.'); if ($property_added) { $this->assertInstanceOf('Drupal\Core\TypedData\DataDefinitionInterface', $properties['search_api_aggregation_2'], 'The "search_api_aggregation_2" property contains a valid data definition.'); if ($properties['search_api_aggregation_2'] instanceof DataDefinitionInterface) { $this->assertEquals('integer', $properties['search_api_aggregation_2']->getDataType(), 'Correct data type set in the data definition.'); $this->assertEquals('Field 2', $properties['search_api_aggregation_2']->getLabel(), 'Correct label set in the data definition.'); $description = $translation->translate('A @type aggregation of the following fields: @fields.', array('@type' => 'Maximum', '@fields' => 'entity » test1 » foo » bar'));; $this->assertEquals($description, $properties['search_api_aggregation_2']->getDescription(), 'Correct description set in the data definition.'); } } // Test whether the properties of specific datasources stay untouched. $properties = array(); /** @var \Drupal\search_api\Datasource\DatasourceInterface $datasource */ $datasource = $this->getMock('Drupal\search_api\Datasource\DatasourceInterface'); $this->processor->alterPropertyDefinitions($properties, $datasource); $this->assertEmpty($properties, 'Datasource-specific properties did not get changed.'); }
/** * {@inheritdoc} */ public function preprocessIndexItems(array &$items) { if (!$items) { return; } if (isset($this->configuration['fields'])) { /** @var \Drupal\search_api\Item\ItemInterface[] $items */ foreach ($items as $item) { foreach ($this->configuration['fields'] as $aggregated_field_id => $aggregated_field) { if ($aggregated_field['label']) { if (!$item->getField($aggregated_field_id, FALSE)) { continue; } // Extract the selected fields to aggregate from the settings. $required_fields = array(); // @todo Don't do this once for every item, compute fields per // datasource right away. foreach ($aggregated_field['fields'] as $field_id_to_aggregate) { // Only include valid and selected fields to aggregate. if (!isset($required_fields[$field_id_to_aggregate]) && !empty($field_id_to_aggregate)) { // Make sure we only get fields from the datasource of the // current item. list($datasource_id) = Utility::splitCombinedId($field_id_to_aggregate); if (!$datasource_id || $datasource_id == $item->getDatasourceId()) { $required_fields[$field_id_to_aggregate] = $field_id_to_aggregate; } } } // Get all the available field values. $given_fields = array(); foreach ($required_fields as $required_field_id) { $field = $item->getField($required_field_id); if ($field && $field->getValues()) { $given_fields[$required_field_id] = $field; unset($required_fields[$required_field_id]); } } $missing_fields = array(); // Get all the missing field values. foreach ($required_fields as $required_field_id) { $field = Utility::createField($this->index, $required_field_id); $missing_fields[$field->getPropertyPath()] = $field; } // Get the value from the original objects in to the fields if ($missing_fields) { Utility::extractFields($item->getOriginalObject(), $missing_fields); } $fields = array_merge($given_fields, $missing_fields); $values = array(); /** @var \Drupal\search_api\Item\FieldInterface[] $fields */ foreach ($fields as $field) { $values = array_merge($values, $field->getValues()); } switch ($aggregated_field['type']) { case 'concat': $values = array(implode("\n\n", $values)); break; case 'sum': $values = array(array_sum($values)); break; case 'count': $values = array(count($values)); break; case 'max': $values = array(max($values)); break; case 'min': $values = array(min($values)); break; case 'first': if ($values) { $values = array(reset($values)); } break; } $item->getField($aggregated_field_id)->setValues($values); } } } } }
/** * Extract results from a Solr response. * * @param \Drupal\search_api\Query\QueryInterface $query * The Search API query object. * @param \Solarium\Core\Query\Result\ResultInterface $result * A Solarium select response object. * * @return \Drupal\search_api\Query\ResultSetInterface * A result set object. */ protected function extractResults(QueryInterface $query, ResultInterface $result) { $index = $query->getIndex(); $field_names = $this->getFieldNames($index); $fields = $index->getFields(); $site_hash = SearchApiSolrUtility::getSiteHash(); // We can find the item ID and the score in the special 'search_api_*' // properties. Mappings are provided for these properties in // SearchApiSolrBackend::getFieldNames(). $id_field = $field_names['search_api_id']; $score_field = $field_names['search_api_relevance']; // Set up the results array. $result_set = SearchApiUtility::createSearchResultSet($query); $result_set->setExtraData('search_api_solr_response', $result->getData()); // In some rare cases (e.g., MLT query with nonexistent ID) the response // will be NULL. $is_grouping = $result instanceof Result && $result->getGrouping(); if (!$result->getResponse() && !$is_grouping) { $result_set->setResultCount(0); return $result_set; } // If field collapsing has been enabled for this query, we need to process // the results differently. $grouping = $query->getOption('search_api_grouping'); $docs = array(); if (!empty($grouping['use_grouping']) && $is_grouping) { // $docs = array(); // $result_set['result count'] = 0; // foreach ($grouping['fields'] as $field) { // if (!empty($response->grouped->{$fields[$field]})) { // $result_set['result count'] += $response->grouped->{$fields[$field]}->ngroups; // foreach ($response->grouped->{$fields[$field]}->groups as $group) { // foreach ($group->doclist->docs as $doc) { // $docs[] = $doc; // } // } // } // } } else { $result_set->setResultCount($result->getNumFound()); $docs = $result->getDocuments(); } // Add each search result to the results array. /** @var \Solarium\QueryType\Select\Result\Document $doc */ foreach ($docs as $doc) { $doc_fields = $doc->getFields(); $item_id = $doc_fields[$id_field]; // For items coming from a different site, we need to adapt the item ID. if (!$this->configuration['site_hash'] && $doc_fields['hash'] != $site_hash) { $item_id = $doc_fields['hash'] . '--' . $item_id; } $result_item = SearchApiUtility::createItem($index, $item_id); $result_item->setScore($doc_fields[$score_field]); unset($doc_fields[$id_field], $doc_fields[$score_field]); // Extract properties from the Solr document, translating from Solr to // Search API property names. This reverses the mapping in // SearchApiSolrBackend::getFieldNames(). foreach ($field_names as $search_api_property => $solr_property) { if (isset($doc_fields[$solr_property])) { // Date fields need some special treatment to become valid date values // (i.e., timestamps) again. if (isset($fields[$search_api_property]) && $fields[$search_api_property]->getType() == 'date' && preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/', $doc_fields[$solr_property][0])) { $doc_fields[$solr_property][0] = strtotime($doc_fields[$solr_property][0]); } $field = SearchApiUtility::createField($index, $search_api_property); $field->setValues($doc_fields[$solr_property]); $result_item->setField($search_api_property, $field); } } $index_id = $this->getIndexId($index->id()); $solr_id = $this->createId($index_id, $result_item->getId()); $item_fields = $result_item->getFields(); $excerpt = $this->getSolrHelper()->getExcerpt($result->getData(), $solr_id, $item_fields, $field_names); if ($excerpt) { $result_item->setExcerpt($excerpt); } $result_set->addResultItem($result_item); } // Check for spellcheck suggestions. /*if (module_exists('search_api_spellcheck') && $query->getOption('search_api_spellcheck')) { $result_set->setExtraData('search_api_spellcheck', new SearchApiSpellcheckSolr($result)); }*/ return $result_set; }