/**
  * {@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);
                 }
             }
         }
     }
 }
Example #2
0
 /**
  * {@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;
 }
Example #3
0
 /**
  * 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);
                 }
             }
         }
     }
 }