You are here

public function SearchApiSolrService::searchMultiple in Search API Solr 7

Implements SearchApiMultiServiceInterface::searchMultiple().

File

includes/service.inc, line 2329

Class

SearchApiSolrService
Search service class using Solr server.

Code

public function searchMultiple(SearchApiMultiQueryInterface $query) {
  $time_method_called = microtime(TRUE);

  // Get field information
  $solr_fields = array(
    'search_api_id' => 'item_id',
    'search_api_relevance' => 'score',
    'search_api_multi_index' => 'index_id',
  );
  $fields = array(
    'search_api_multi_index' => array(
      'type' => 'string',
    ),
  );
  foreach ($query
    ->getIndexes() as $index) {
    if (empty($index->options['fields'])) {
      continue;
    }
    $prefix = $this
      ->getIndexId($index->machine_name) . ':';
    foreach ($this
      ->getFieldNames($index) as $field => $key) {
      if (!isset($solr_fields[$field])) {
        $solr_fields[$prefix . $field] = $key;
      }
    }
    foreach ($index->options['fields'] as $field => $info) {
      $fields[$prefix . $field] = $info;
    }
  }

  // Extract keys
  $keys = $query
    ->getKeys();
  if (is_array($keys)) {
    $keys = $this
      ->flattenKeys($keys);
  }

  // Set searched fields
  $search_fields = $query
    ->getFields();
  $qf = array();
  foreach ($search_fields as $f) {
    $boost = isset($fields[$f]['boost']) ? '^' . $fields[$f]['boost'] : '';
    $qf[] = $solr_fields[$f] . $boost;
  }

  // Extract filters
  $filter = $query
    ->getFilter();
  $fq = $this
    ->createFilterQueries($filter, $solr_fields, $fields);

  // Restrict search to searched indexes.
  $index_filter = array();
  $indexes = array();
  foreach ($query
    ->getIndexes() as $index) {
    $index_id = $this
      ->getIndexId($index->machine_name);
    $indexes[$index_id] = $index;
    $index_filter[] = 'index_id:' . call_user_func(array(
      $this
        ->getConnectionClass(),
      'phrase',
    ), $index_id);
  }
  $fq[] = implode(' OR ', $index_filter);
  if (!empty($this->options['site_hash'])) {

    // We don't need to escape the site hash, as that consists only of
    // alphanumeric characters.
    $fq[] = 'hash:' . search_api_solr_site_hash();
  }

  // Extract sort
  $sort = array();
  foreach ($query
    ->getSort() as $f => $order) {
    $f = $solr_fields[$f];
    if (substr($f, 0, 3) == 'ss_') {
      $f = 'sort_' . substr($f, 3);
    }
    $order = strtolower($order);
    $sort[] = "{$f} {$order}";
  }

  // Get facet fields
  $facets = $query
    ->getOption('search_api_facets') ? $query
    ->getOption('search_api_facets') : array();
  $facet_params = $this
    ->getFacetParams($facets, $solr_fields, $fq);

  // Handle highlighting.
  $highlight_params = $this
    ->getHighlightParams($query);

  // Set defaults
  if (!$keys) {
    $keys = NULL;
  }
  $options = $query
    ->getOptions();

  // Collect parameters
  $params = array(
    'fl' => 'item_id,index_id,score',
    'qf' => $qf,
    'fq' => $fq,
  );
  if (isset($options['offset'])) {
    $params['start'] = $options['offset'];
  }
  if (isset($options['limit'])) {
    $params['rows'] = $options['limit'];
  }
  if ($sort) {
    $params['sort'] = implode(', ', $sort);
  }
  if (!empty($facet_params['facet.field'])) {
    $params += $facet_params;
  }
  if (!empty($highlight_params)) {
    $params += $highlight_params;
  }
  if (!empty($this->options['retrieve_data'])) {
    $params['fl'] = '*,score';
  }

  // Retrieve http method from server options.
  $http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';

  // Send search request
  $time_processing_done = microtime(TRUE);
  $this
    ->connect();
  $call_args = array(
    'query' => &$keys,
    'params' => &$params,
    'http_method' => &$http_method,
  );
  drupal_alter('search_api_solr_multi_query', $call_args, $query);
  $response = $this->solr
    ->search($keys, $params, $http_method);
  $time_query_done = microtime(TRUE);

  // Extract results
  $results = array();
  $results['result count'] = $response->response->numFound;
  $results['results'] = array();
  $fulltext_fields_by_index = array();
  foreach ($search_fields as $field) {
    list($index_id, $field) = explode(':', $field, 2);
    $fulltext_fields_by_index[$index_id][] = $field;
  }
  foreach ($response->response->docs as $id => $doc) {
    $index_id = $doc->index_id;
    if (isset($indexes[$index_id])) {
      $index = $indexes[$index_id];
    }
    else {
      $index = new SearchApiIndex(array(
        'machine_name' => $index_id,
      ));
    }
    $fields = $this
      ->getFieldNames($index);
    $field_options = $index->options['fields'];
    $result = array(
      'id' => NULL,
      'index_id' => $index_id,
      'score' => NULL,
      'fields' => array(),
    );
    $solr_id = $this
      ->createId($index_id, $doc->item_id);
    foreach ($fields as $search_api_property => $solr_property) {
      if (isset($doc->{$solr_property})) {
        $value = $doc->{$solr_property};

        // Date fields need some special treatment to become valid date values
        // (i.e., timestamps) again.
        $first_value = $value;
        while (is_array($first_value)) {
          $first_value = reset($first_value);
        }
        if (isset($field_options[$search_api_property]['type']) && search_api_extract_inner_type($field_options[$search_api_property]['type']) === 'date' && preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$/', $first_value)) {
          $value = is_array($value) ? array_map('strtotime', $value) : strtotime($value);
        }
        $result['fields'][$search_api_property] = $value;
      }
    }
    $fulltext_fields = isset($fulltext_fields_by_index[$index_id]) ? $fulltext_fields_by_index[$index_id] : array();
    $excerpt = $this
      ->getExcerpt($response, $solr_id, $result['fields'], $fields, $fulltext_fields);
    if ($excerpt) {
      $result['excerpt'] = $excerpt;
    }

    // We can find the item id and score in the special 'search_api_*'
    // properties. Mappings are provided for these properties in
    // SearchApiSolrService::getFieldNames().
    $result['id'] = $result['fields']['search_api_id'];
    $result['score'] = $result['fields']['search_api_relevance'];
    $results['results'][$id] = $result;
  }

  // Extract facets
  if (isset($response->facet_counts->facet_fields)) {
    $results['search_api_facets'] = array();
    $facet_fields = $response->facet_counts->facet_fields;

    // The key for the "missing" facet (empty string in the JSON). This will
    // be either "" or "_empty_", depending on the PHP version.
    $empty_key = key((array) json_decode('{"":5}'));
    foreach ($facets as $delta => $info) {
      $field = $solr_fields[$info['field']];
      if (!empty($facet_fields->{$field})) {
        $min_count = $info['min_count'];
        $terms = $facet_fields->{$field};
        if ($info['missing']) {

          // We have to correctly incorporate the "missing" term ($empty_key).
          // This will ensure that the term with the least results is dropped,
          // if the limit would be exceeded.
          if (isset($terms->{$empty_key})) {
            if ($terms->{$empty_key} < $min_count) {
              unset($terms->{$empty_key});
            }
            else {
              $terms = (array) $terms;
              arsort($terms);
              if ($info['limit'] > 0 && count($terms) > $info['limit']) {
                array_pop($terms);
              }
            }
          }
        }
        elseif (isset($terms->{$empty_key})) {
          $terms = clone $terms;
          unset($terms->{$empty_key});
        }
        $type = isset($fields[$info['field']]['type']) ? search_api_extract_inner_type($fields[$info['field']]['type']) : 'string';
        foreach ($terms as $term => $count) {
          if ($count >= $min_count) {
            if ($term === $empty_key) {
              $term = '!';
            }
            elseif ($type == 'boolean') {
              if ($term == 'true') {
                $term = '"1"';
              }
              elseif ($term == 'false') {
                $term = '"0"';
              }
            }
            elseif ($type == 'date') {
              $term = $term ? '"' . strtotime($term) . '"' : NULL;
            }
            else {
              $term = "\"{$term}\"";
            }
            if ($term) {
              $results['search_api_facets'][$delta][] = array(
                'filter' => $term,
                'count' => $count,
              );
            }
          }
        }
        if (empty($results['search_api_facets'][$delta])) {
          unset($results['search_api_facets'][$delta]);
        }
      }
    }
  }
  drupal_alter('search_api_solr_multi_search_results', $results, $query, $response);

  // Compute performance
  $time_end = microtime(TRUE);
  $results['performance'] = array(
    'complete' => $time_end - $time_method_called,
    'preprocessing' => $time_processing_done - $time_method_called,
    'execution' => $time_query_done - $time_processing_done,
    'postprocessing' => $time_end - $time_query_done,
  );
  return $results;
}