/**
  * 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.');
 }
Exemple #2
0
 /**
  * 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;
 }
Exemple #5
0
  /**
   * 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);
  }
Exemple #6
0
 /**
  * {@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;
 }
Exemple #9
0
  /**
   * 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;
 }