/** * {@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); } } } } }
/** * {@inheritdoc} */ public function getFields($extract = TRUE) { if ($extract && !$this->fieldsExtracted) { $data_type_fallback_mapping = Utility::getDataTypeFallbackMapping($this->index); foreach (array(NULL, $this->getDatasourceId()) as $datasource_id) { $fields_by_property_path = array(); 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]); } $fields_by_property_path[$field->getPropertyPath()] = $this->fields[$field_id]; } } if ($datasource_id && $fields_by_property_path) { try { Utility::extractFields($this->getOriginalObject(), $fields_by_property_path); } catch (SearchApiException $e) { // If we couldn't load the object, just log an error and fail // silently to set the values. watchdog_exception('search_api', $e); } } } $this->fieldsExtracted = TRUE; } return $this->fields; }
/** * Retrieves the fulltext fields of the given result items. * * @param \Drupal\search_api\Item\ItemInterface[] $result_items * The results for which fulltext data should be extracted, keyed by item * ID. * @param bool $load * (optional) If FALSE, only field values already present will be returned. * Otherwise, fields will be loaded if necessary. * * @return \Drupal\search_api\Item\FieldInterface[][] * An two-dimensional array of fulltext fields, keyed first by item ID and * then field ID. */ protected function getFulltextFields(array $result_items, $load = TRUE) { // @todo Add some caching, since this will sometimes be called twice for the // same result set. $items = array(); // All the index's fulltext fields, grouped by datasource. $fulltext_fields = array(); foreach ($this->index->getFields() as $field_id => $field) { if (Utility::isTextType($field->getType())) { $fulltext_fields[$field->getDatasourceId()][$field_id] = $field; } } $needs_extraction = array(); foreach ($result_items as $item_id => $result_item) { $datasource_id = $result_item->getDatasourceId(); // Make sure this datasource even has any indexed fulltext fields. if (empty($fulltext_fields[$datasource_id])) { continue; } /** @var \Drupal\search_api\Item\FieldInterface $field */ foreach ($fulltext_fields[$datasource_id] as $field_id => $field) { if ($result_item->getField($field_id, FALSE)) { $items[$item_id][$field_id] = $result_item->getField($field_id, FALSE); } elseif ($load) { $needs_extraction[$item_id][$field->getPropertyPath()] = clone $field; } } } $needs_load = array(); foreach ($needs_extraction as $item_id => $fields) { if (!$result_items[$item_id]->getOriginalObject(FALSE)) { $needs_load[$item_id] = $item_id; } } if ($needs_load) { foreach ($this->index->loadItemsMultiple($needs_load) as $item_id => $object) { $result_items[$item_id]->setOriginalObject($object); unset($needs_load[$item_id]); } } // Remove the fields for all items that couldn't be loaded. $needs_extraction = array_diff_key($needs_extraction, $needs_load); foreach ($needs_extraction as $item_id => $fields) { try { Utility::extractFields($result_items[$item_id]->getOriginalObject(), $fields); foreach ($fields as $field) { $field_id = $field->getFieldIdentifier(); $result_items[$item_id]->setField($field_id, $field); $items[$item_id][$field_id] = $field; } } catch (SearchApiException $e) { // Missing highlighting will be the least problem in this case – just // ignore it. } } return $items; }
/** * {@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); } } } } }