Exemplo n.º 1
0
  /**
   * {@inheritdoc}
   */
  public function setUp() {
    parent::setUp();

    $this->installSchema('search_api', array('search_api_item', 'search_api_task'));

    // Create a test server.
    $this->server = Server::create(array(
      'name' => $this->randomString(),
      'id' => $this->randomMachineName(),
      'status' => 1,
      'backend' => 'search_api_test_backend',
    ));
    $this->server->save();

    // Create a test index.
    $this->index = Index::create(array(
      'name' => $this->randomString(),
      'id' => $this->randomMachineName(),
      'status' => 1,
      'datasources' => array('entity:' . $this->testEntityTypeId),
      'tracker' => 'default',
      'server' => $this->server->id(),
      'options' => array('index_directly' => FALSE),
    ));
    $this->index->save();

    Utility::getIndexTaskManager()->addItemsAll($this->index);
  }
Exemplo n.º 2
0
  /**
   * {@inheritdoc}
   */
  public function setUp() {
    parent::setUp();

    $this->setUpExampleStructure();

    Utility::getIndexTaskManager()->addItemsAll(Index::load($this->indexId));
  }
Exemplo n.º 3
0
 /**
  * Returns the item IDs for the given entity IDs.
  *
  * @param array $entity_ids
  *   An array of entity IDs.
  *
  * @return string[]
  *   An array of item IDs.
  */
 protected function getItemIds(array $entity_ids)
 {
     $translate_ids = function ($entity_id) {
         return Utility::createCombinedId('entity:entity_test', $entity_id . ':en');
     };
     return array_map($translate_ids, $entity_ids);
 }
Exemplo n.º 4
0
  /**
   * {@inheritdoc}
   */
  public function setUp() {
    parent::setUp();

    $this->installSchema('search_api', array('search_api_item', 'search_api_task'));
    $this->installSchema('system', array('router'));
    $this->installSchema('user', array('users_data'));

    $this->setUpExampleStructure();

    $this->installConfig(array('search_api_test_db'));

    Utility::getIndexTaskManager()->addItemsAll($this->getIndex());
  }
Exemplo n.º 5
0
  /**
   * Tests custom data types integration.
   */
  public function testCustomDataTypes() {
    $original_value = $this->entities[1]->get('name')->value;
    $original_type = $this->index->getFields()['entity:entity_test/name']->getType();

    $item = $this->index->loadItem('entity:entity_test/1:en');
    $item = Utility::createItemFromObject($this->index, $item, 'entity:entity_test/1:en');
    $name_field = $item->getField('entity:entity_test/name');

    $processed_value = $name_field->getValues()[0];
    $processed_type = $name_field->getType();

    $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value');
    $this->assertEqual($processed_type, $original_type, 'The processed type matches the original type.');

    // Reset the fields on the item and change to the supported data type.
    $item->setFieldsExtracted(FALSE);
    $item->setFields(array());
    $this->index->getFields()['entity:entity_test/name']->setType('search_api_test_data_type');
    $name_field = $item->getField('entity:entity_test/name');

    $processed_value = $name_field->getValues()[0];
    $processed_type = $name_field->getType();

    $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value');
    $this->assertEqual($processed_type, 'search_api_test_data_type', 'The processed type matches the new type.');

    // Reset the fields on the item and change to the non-supported data type.
    $item->setFieldsExtracted(FALSE);
    $item->setFields(array());
    $this->index->getFields()['entity:entity_test/name']->setType('search_api_unsupported_test_data_type');
    $name_field = $item->getField('entity:entity_test/name');

    $processed_value = $name_field->getValues()[0];
    $processed_type = $name_field->getType();

    $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value');
    $this->assertEqual($processed_type, 'integer', 'The processed type matches the fallback type.');

    // Reset the fields on the item and change to the data altering data type.
    $item->setFieldsExtracted(FALSE);
    $item->setFields(array());
    $this->index->getFields()['entity:entity_test/name']->setType('search_api_altering_test_data_type');
    $name_field = $item->getField('entity:entity_test/name');

    $processed_value = $name_field->getValues()[0];
    $processed_type = $name_field->getType();

    $this->assertEqual($processed_value, strlen($original_value), 'The processed value matches the altered original value');
    $this->assertEqual($processed_type, 'search_api_altering_test_data_type', 'The processed type matches the defined type.');
  }
Exemplo n.º 6
0
 /**
  * Tests the processor's preprocessSearchQuery() method.
  */
 public function testPreprocessSearchQuery()
 {
     $index = $this->getMock('Drupal\\search_api\\IndexInterface');
     $index->expects($this->any())->method('status')->will($this->returnValue(TRUE));
     /** @var \Drupal\search_api\IndexInterface $index */
     $this->processor->setIndex($index);
     $query = Utility::createQuery($index);
     $keys = array('#conjunction' => 'AND', 'foo', 'bar', 'bar foo');
     $query->keys($keys);
     $this->processor->setConfiguration(array('stopwords' => array('foobar', 'bar', 'barfoo')));
     $this->processor->preprocessSearchQuery($query);
     unset($keys[1]);
     $this->assertEquals($keys, $query->getKeys());
     $results = Utility::createSearchResultSet($query);
     $this->processor->postprocessSearchResults($results);
     $this->assertEquals(array('bar'), $results->getIgnoredSearchKeys());
 }
Exemplo n.º 7
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();
 }
Exemplo n.º 8
0
  /**
   * Tests whether indexed items are correctly preprocessed.
   */
  public function testProcessIndexItems() {
    /** @var \Drupal\node\Entity\Node $node */
    $node = $this->getMockBuilder('Drupal\node\Entity\Node')
      ->disableOriginalConstructor()
      ->getMock();

    $body_value = array('Some text value');
    $body_field_id = Utility::createCombinedId('entity:node', 'body');
    $fields = array(
      'search_api_url' => array(
        'type' => 'string'
      ),
      $body_field_id => array(
        'type' => 'text',
        'values' => $body_value,
      ),
    );
    $items = $this->createItems($this->index, 2, $fields, EntityAdapter::createFromEntity($node));

    // Process the items.
    $this->processor->preprocessIndexItems($items);

    // Check the valid item.
    $field = $items[$this->itemIds[0]]->getField('search_api_url');
    $this->assertEquals(array('http://www.example.com/node/example'), $field->getValues(), 'Valid URL added as value to the field.');

    // Check that no other fields were changed.
    $field = $items[$this->itemIds[0]]->getField($body_field_id);
    $this->assertEquals($body_value, $field->getValues(), 'Body field was not changed.');

    // Check the second item to be sure that all are processed.
    $field = $items[$this->itemIds[1]]->getField('search_api_url');
    $this->assertEquals(array('http://www.example.com/node/example'), $field->getValues(), 'Valid URL added as value to the field in the second item.');
  }
Exemplo n.º 9
0
 /**
  * {@inheritdoc}
  */
 protected function testType($type)
 {
     return Utility::isTextType($type, array('text', 'tokenized_text'));
 }
Exemplo n.º 10
0
 /**
  * Tokenizes an HTML string according to the HTML elements.
  *
  * Assigns boost values to the elements' contents accordingly.
  *
  * @param string $text
  *   The HTML string to parse, passed by reference. After the method call, the
  *   variable will contain the portion of the string after the current
  *   element, or an empty string (if there is no current element).
  * @param string|null $active_tag
  *   (optional) The currently active tag, for which a closing tag has to be
  *   found. Internal use only.
  * @param float $boost
  *   (optional) The currently active boost value. Internal use only.
  *
  * @return array
  *   Tokenized text with appropriate scores.
  */
 protected function parseHtml(&$text, $active_tag = NULL, $boost = 1.0) {
   $ret = array();
   while (($pos = strpos($text, '<')) !== FALSE) {
     if ($boost && $pos > 0) {
       $value = $this->normalizeText(substr($text, 0, $pos));
       if ($value !== '') {
         $ret[] = Utility::createTextToken($value, $boost);
       }
     }
     $text = substr($text, $pos + 1);
     preg_match('#^(/?)([-:_a-zA-Z0-9]+)#', $text, $m);
     $pos = strpos($text, '>');
     $empty_tag = $text[$pos - 1] == '/';
     $text = substr($text, $pos + 1);
     if ($m[1]) {
       // Closing tag.
       if ($active_tag && $m[2] == $active_tag) {
         return $ret;
       }
     }
     elseif (!$empty_tag) {
       // Opening tag => recursive call.
       $inner_boost = $boost * (isset($this->configuration['tags'][$m[2]]) ? $this->configuration['tags'][$m[2]] : 1);
       $ret = array_merge($ret, $this->parseHtml($text, $m[2], $inner_boost));
     }
   }
   if ($text) {
     $value = $this->normalizeText($text);
     if ($value !== '') {
       $ret[] = Utility::createTextToken($value, $boost);
     }
     $text = '';
   }
   return $ret;
 }
Exemplo n.º 11
0
 /**
  * Implements the magic __toString() method to simplify debugging.
  */
 public function __toString()
 {
     $out = $this->getLabel() . ' [' . $this->getFieldIdentifier() . ']: ';
     if ($this->isIndexed()) {
         $out .= 'indexed as type ' . $this->getType();
         if (Utility::isTextType($this->getType())) {
             $out .= ' (boost ' . $this->getBoost() . ')';
         }
     } else {
         $out .= 'not indexed';
     }
     if ($this->getValues()) {
         $out .= "\nValues:";
         foreach ($this->getValues() as $value) {
             $value = str_replace("\n", "\n  ", "{$value}");
             $out .= "\n- " . $value;
         }
     }
     return $out;
 }
Exemplo n.º 12
0
 /**
  * Checks whether the index switched tracker plugin and reacts accordingly.
  *
  * Used as a helper method in postSave(). Should only be called when the index
  * was enabled before the change and remained so.
  *
  * @param \Drupal\search_api\IndexInterface $original
  *   The previous version of the index.
  */
 protected function reactToTrackerSwitch(IndexInterface $original) {
   if ($this->tracker != $original->getTrackerId()) {
     $index_task_manager = Utility::getIndexTaskManager();
     if ($original->hasValidTracker()) {
       $index_task_manager->stopTracking($this);
     }
     if ($this->hasValidTracker()) {
       $index_task_manager->startTracking($this);
     }
   }
 }
Exemplo n.º 13
0
 /**
  * Retrieves the property path of the parent property.
  *
  * @return string|null
  *   The property path of the parent property.
  */
 protected function getParentPath()
 {
     if (!isset($this->parentPath)) {
         $combined_property_path = $this->getCombinedPropertyPath();
         list(, $property_path) = Utility::splitCombinedId($combined_property_path);
         list($this->parentPath) = Utility::splitPropertyPath($property_path);
     }
     return $this->parentPath;
 }
Exemplo n.º 14
0
 /**
  * Makes sure that locked processors and fields are actually enabled/indexed.
  *
  * @return bool
  *   TRUE if any changes were made to the index as a result of this operation;
  *   FALSE otherwise.
  */
 public function applyLockedConfiguration()
 {
     $change = FALSE;
     // Obviously, we should first check for locked processors, because they
     // might add new locked properties.
     foreach ($this->getProcessors(FALSE) as $processor_id => $processor) {
         if ($processor->isLocked() && !isset($this->options['processors'][$processor_id])) {
             $this->options['processors'][$processor_id] = array('processor_id' => $processor_id, 'weights' => array(), 'settings' => array());
             $change = TRUE;
         }
     }
     if ($change) {
         $this->resetCaches(FALSE);
     }
     $datasource_ids = array_merge(array(NULL), $this->getDatasourceIds());
     foreach ($datasource_ids as $datasource_id) {
         foreach ($this->getPropertyDefinitions($datasource_id) as $key => $property) {
             if ($property instanceof PropertyInterface && $property->isLocked()) {
                 $settings = $property->getFieldSettings();
                 if (empty($settings['type'])) {
                     $mapping = Utility::getFieldTypeMapping();
                     $type = $property->getDataType();
                     $settings['type'] = !empty($mapping[$type]) ? $mapping[$type] : 'string';
                 }
                 $this->options['fields'][$key] = $settings;
                 $change = TRUE;
             }
         }
     }
     return $change;
 }
Exemplo n.º 15
0
  /**
   * Implements SearchApiAutocompleteInterface::getAutocompleteSuggestions().
   */
  public function getAutocompleteSuggestions(QueryInterface $query, SearchApiAutocompleteSearch $search, $incomplete_key, $user_input) {
    $settings = isset($this->configuration['autocomplete']) ? $this->configuration['autocomplete'] : array();
    $settings += array(
      'suggest_suffix' => TRUE,
      'suggest_words' => TRUE,
    );
    // If none of these options is checked, the user apparently chose a very
    // roundabout way of telling us he doesn't want autocompletion.
    if (!array_filter($settings)) {
      return array();
    }

    $index = $query->getIndex();
    $db_info = $this->getIndexDbInfo($index);
    if (empty($db_info['field_tables'])) {
      throw new SearchApiException(new FormattableMarkup('Unknown index @id.', array('@id' => $index->id())));
    }
    $fields = $this->getFieldInfo($index);

    $suggestions = array();
    $passes = array();
    $incomplete_like = NULL;

    // Make the input lowercase as the indexed data is (usually) also all
    // lowercase.
    $incomplete_key = Unicode::strtolower($incomplete_key);
    $user_input = Unicode::strtolower($user_input);

    // Decide which methods we want to use.
    if ($incomplete_key && $settings['suggest_suffix']) {
      $passes[] = 1;
      $incomplete_like = $this->database->escapeLike($incomplete_key) . '%';
    }
    if ($settings['suggest_words'] && (!$incomplete_key || strlen($incomplete_key) >= $this->configuration['min_chars'])) {
      $passes[] = 2;
    }

    if (!$passes) {
      return array();
    }

    // We want about half of the suggestions from each enabled method.
    $limit = $query->getOption('limit', 10);
    $limit /= count($passes);
    $limit = ceil($limit);

    // Also collect all keywords already contained in the query so we don't
    // suggest them.
    $keys = preg_split('/[^\p{L}\p{N}]+/u', $user_input, -1, PREG_SPLIT_NO_EMPTY);
    $keys = array_combine($keys, $keys);
    if ($incomplete_key) {
      $keys[$incomplete_key] = $incomplete_key;
    }

    foreach ($passes as $pass) {
      if ($pass == 2 && $incomplete_key) {
        $query->keys($user_input);
      }
      // To avoid suggesting incomplete words, we have to temporarily disable
      // the "partial_matches" option. (There should be no way we'll save the
      // server during the createDbQuery() call, so this should be safe.)
      $options = $this->options;
      $this->options['partial_matches'] = FALSE;
      $db_query = $this->createDbQuery($query, $fields);
      $this->options = $options;

      // We need a list of all current results to match the suggestions against.
      // However, since MySQL doesn't allow using a temporary table multiple
      // times in one query, we regrettably have to do it this way.
      $fulltext_fields = $this->getQueryFulltextFields($query);
      if (count($fulltext_fields) > 1) {
        $all_results = $db_query->execute()->fetchCol();
        // Compute the total number of results so we can later sort out matches
        // that occur too often.
        $total = count($all_results);
      }
      else {
        $table = $this->getTemporaryResultsTable($db_query);
        if (!$table) {
          return array();
        }
        $all_results = $this->database->select($table, 't')
          ->fields('t', array('item_id'));
        $total = $this->database->query("SELECT COUNT(item_id) FROM {{$table}}")->fetchField();
      }
      $max_occurrences = $this->getConfigFactory()->get('search_api_db.settings')->get('autocomplete_max_occurrences');
      $max_occurrences = max(1, floor($total * $max_occurrences));

      if (!$total) {
        if ($pass == 1) {
          return NULL;
        }
        continue;
      }

      /** @var \Drupal\Core\Database\Query\SelectInterface|null $word_query */
      $word_query = NULL;
      foreach ($fulltext_fields as $field) {
        if (!isset($fields[$field]) || !Utility::isTextType($fields[$field]['type'])) {
          continue;
        }
        $field_query = $this->database->select($fields[$field]['table'], 't');
        $field_query->fields('t', array('word', 'item_id'))
          ->condition('item_id', $all_results, 'IN');
        if ($pass == 1) {
          $field_query->condition('word', $incomplete_like, 'LIKE')
            ->condition('word', $keys, 'NOT IN');
        }
        if (!isset($word_query)) {
          $word_query = $field_query;
        }
        else {
          $word_query->union($field_query);
        }
      }
      if (!$word_query) {
        return array();
      }
      $db_query = $this->database->select($word_query, 't');
      $db_query->addExpression('COUNT(DISTINCT item_id)', 'results');
      $db_query->fields('t', array('word'))
        ->groupBy('word')
        ->having('results <= :max', array(':max' => $max_occurrences))
        ->orderBy('results', 'DESC')
        ->range(0, $limit);
      $incomp_len = strlen($incomplete_key);
      foreach ($db_query->execute() as $row) {
        $suffix = ($pass == 1) ? substr($row->word, $incomp_len) : ' ' . $row->word;
        $suggestions[] = array(
          'suggestion_suffix' => $suffix,
          'results' => $row->results,
        );
      }
    }

    return $suggestions;
  }
Exemplo n.º 16
0
 /**
  * Tests whether the rendered_item field is correctly filled by the processor.
  */
 public function testPreprocessIndexItems()
 {
     $items = array();
     foreach ($this->nodes as $node) {
         $items[] = array('datasource' => 'entity:node', 'item' => $node->getTypedData(), 'item_id' => $node->id(), 'text' => 'node text' . $node->id());
     }
     $items = $this->generateItems($items);
     $this->processor->preprocessIndexItems($items);
     foreach ($items as $key => $item) {
         list(, $nid) = Utility::splitCombinedId($key);
         $field = $item->getField('rendered_item');
         $this->assertEquals('text', $field->getType(), 'Node item ' . $nid . ' rendered value is identified as text.');
         $values = $field->getValues();
         // Test that the value is a string (not, e.g., a SafeString object).
         $this->assertTrue(is_string($values[0]), 'Node item ' . $nid . ' rendered value is a string.');
         $this->assertEquals(1, count($values), 'Node item ' . $nid . ' rendered value is a single value.');
         // These tests rely on the template not changing. However, if we'd only
         // check whether the field values themselves are included, there could
         // easier be false positives. For example the title text was present even
         // when the processor was broken, because the schema metadata was also
         // adding it to the output.
         $this->assertTrue(substr_count($values[0], 'view-mode-full') > 0, 'Node item ' . $nid . ' rendered in view-mode "full".');
         $this->assertTrue(substr_count($values[0], 'field--name-title') > 0, 'Node item ' . $nid . ' has a rendered title field.');
         $this->assertTrue(substr_count($values[0], '>' . $this->nodes[$nid]->label() . '<') > 0, 'Node item ' . $nid . ' has a rendered title inside HTML-Tags.');
         $this->assertTrue(substr_count($values[0], '>Member for<') > 0, 'Node item ' . $nid . ' has rendered member information HTML-Tags.');
         $this->assertTrue(substr_count($values[0], '>' . $this->nodes[$nid]->get('body')->getValue()[0]['value'] . '<') > 0, 'Node item ' . $nid . ' has rendered content inside HTML-Tags.');
     }
 }
Exemplo n.º 17
0
 /**
  * Form submission handler for adding a new field to the index.
  *
  * @param array $form
  *   An associative array containing the structure of the form.
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form.
  */
 public function addField(array $form, FormStateInterface $form_state)
 {
     $button = $form_state->getTriggeringElement();
     if (!$button) {
         return;
     }
     /** @var \Drupal\Core\TypedData\DataDefinitionInterface $property */
     $property = $button['#property'];
     list($datasource_id, $property_path) = Utility::splitCombinedId($button['#name']);
     $field = Utility::createFieldFromProperty($this->entity, $property, $datasource_id, $property_path, NULL, $button['#data_type']);
     $field->setLabel($button['#prefixed_label']);
     $this->entity->addField($field);
     $args['%label'] = $field->getLabel();
     drupal_set_message($this->t('Field %label was added to the index.', $args));
 }
Exemplo n.º 18
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;
 }
Exemplo n.º 19
0
 /**
  * {@inheritdoc}
  */
 public function trackItemsInserted(array $ids)
 {
     $transaction = $this->getDatabaseConnection()->startTransaction();
     try {
         $index_id = $this->getIndex()->id();
         // Process the IDs in chunks so we don't create an overly large INSERT
         // statement.
         foreach (array_chunk($ids, 1000) as $ids_chunk) {
             $insert = $this->createInsertStatement();
             foreach ($ids_chunk as $item_id) {
                 list($datasource_id) = Utility::splitCombinedId($item_id);
                 $insert->values(array('index_id' => $index_id, 'datasource' => $datasource_id, 'item_id' => $item_id, 'changed' => REQUEST_TIME, 'status' => $this::STATUS_NOT_INDEXED));
             }
             $insert->execute();
         }
     } catch (\Exception $e) {
         watchdog_exception('search_api', $e);
         $transaction->rollback();
     }
 }
Exemplo n.º 20
0
 /**
  * Tests preprocessing search items with an exclusive filter.
  */
 public function testFilterExclusive()
 {
     $configuration['roles'] = array('editor' => 'editor');
     $configuration['default'] = 1;
     $this->processor->setConfiguration($configuration);
     $this->processor->preprocessIndexItems($this->items);
     $this->assertTrue(empty($this->items[Utility::createCombinedId('entity:user', '1:en')]), 'User with editor role was successfully removed.');
     $this->assertTrue(!empty($this->items[Utility::createCombinedId('entity:user', '2:en')]), 'User without the editor role was not removed.');
     $this->assertTrue(!empty($this->items[Utility::createCombinedId('entity:node', '1:en')]), 'Node item was not removed.');
 }
Exemplo n.º 21
0
  /**
   * Tests whether overriding processFilterValue() works correctly.
   */
  public function testProcessFilterValueOverride() {
    $override = function (&$value) {
      if (isset($value)) {
        $value = '';
      }
    };
    $this->processor->setMethodOverride('processFilterValue', $override);

    $query = Utility::createQuery($this->index);
    $query->condition('text_field', 'foo');
    $query->condition('string_field', NULL, '<>');
    $query->condition('integer_field', 'bar');

    $this->processor->preprocessSearchQuery($query);

    $expected = array(
      array('string_field', NULL, '<>'),
      array('integer_field', 'bar', '='),
    );
    $this->assertEquals($expected, array_merge($query->getFilter()->getFilters()), 'Filters were preprocessed correctly.');
  }
Exemplo n.º 22
0
 /**
  * {@inheritdoc}
  */
 public function query(array $options = array())
 {
     if (!$this->status()) {
         throw new SearchApiException('Cannot search on a disabled index.');
     }
     return Utility::createQuery($this, $options);
 }
Exemplo n.º 23
0
 /**
  * Adds a property to be retrieved.
  *
  * Currently doesn't serve any purpose, but might be added to the search query
  * in the future to help backends that support returning fields determine
  * which of the fields should actually be returned.
  *
  * @param string $combined_property_path
  *   The combined property path of the property that should be retrieved.
  *
  * @return $this
  */
 public function addRetrievedProperty($combined_property_path)
 {
     list($datasource_id, $property_path) = Utility::splitCombinedId($combined_property_path);
     $this->retrievedProperties[$datasource_id][$property_path] = $combined_property_path;
     return $this;
 }
Exemplo n.º 24
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;
 }
Exemplo n.º 25
0
 /**
  * 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.');
 }
Exemplo n.º 26
0
 /**
  * {@inheritdoc}
  */
 protected function processFieldValue(&$value, &$type)
 {
     $this->prepare();
     $type = 'tokenized_text';
     $text = $this->simplifyText($value);
     // Split on spaces. The configured (or default) delimiters have been
     // replaced by those already in simplifyText().
     $arr = explode(' ', $text);
     $value = array();
     foreach ($arr as $token) {
         if (is_numeric($token) || Unicode::strlen($token) >= $this->configuration['minimum_word_size']) {
             $value[] = Utility::createTextToken($token);
         }
     }
 }
Exemplo n.º 27
0
 /**
  * {@inheritdoc}
  */
 public function search(QueryInterface $query)
 {
     $this->checkError(__FUNCTION__);
     $results = Utility::createSearchResultSet($query);
     $result_items = array();
     $datasources = $query->getIndex()->getDatasources();
     /** @var \Drupal\search_api\Datasource\DatasourceInterface $datasource */
     $datasource = reset($datasources);
     $datasource_id = $datasource->getPluginId();
     if ($query->getKeys() && $query->getKeys()[0] == 'test') {
         $item_id = Utility::createCombinedId($datasource_id, '1');
         $item = Utility::createItem($query->getIndex(), $item_id, $datasource);
         $item->setScore(2);
         $item->setExcerpt('test');
         $result_items[$item_id] = $item;
     } elseif ($query->getOption('search_api_mlt')) {
         $item_id = Utility::createCombinedId($datasource_id, '2');
         $item = Utility::createItem($query->getIndex(), $item_id, $datasource);
         $item->setScore(2);
         $item->setExcerpt('test test');
         $result_items[$item_id] = $item;
     } else {
         $item_id = Utility::createCombinedId($datasource_id, '1');
         $item = Utility::createItem($query->getIndex(), $item_id, $datasource);
         $item->setScore(1);
         $result_items[$item_id] = $item;
         $item_id = Utility::createCombinedId($datasource_id, '2');
         $item = Utility::createItem($query->getIndex(), $item_id, $datasource);
         $item->setScore(1);
         $result_items[$item_id] = $item;
     }
     $results->setResultCount(count($result_items));
     return $results;
 }
Exemplo n.º 28
0
 /**
  * Creates a description for an aggregated field.
  *
  * @param array $field_definition
  *   The settings of the aggregated field.
  * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties
  *   All available properties on the index, keyed by combined ID.
  * @param string[] $datasource_label_prefixes
  *   The label prefixes for all datasources.
  *
  * @return string
  *   A description for the given aggregated field.
  */
 protected function fieldDescription(array $field_definition, array $properties, array $datasource_label_prefixes)
 {
     $fields = array();
     foreach ($field_definition['fields'] as $combined_id) {
         list($datasource_id, $property_path) = Utility::splitCombinedId($combined_id);
         $label = $property_path;
         if (isset($properties[$combined_id])) {
             $label = $properties[$combined_id]->getLabel();
         }
         $fields[] = $datasource_label_prefixes[$datasource_id] . $label;
     }
     $type = $this->getTypes()[$field_definition['type']];
     return $this->t('A @type aggregation of the following fields: @fields.', array('@type' => $type, '@fields' => implode(', ', $fields)));
 }
Exemplo n.º 29
0
 /**
  * Tests if unpublished nodes are removed from the items list.
  */
 public function testNodeStatus()
 {
     $this->assertCount(2, $this->items, '2 nodes in the index.');
     $this->processor->preprocessIndexItems($this->items);
     $this->assertCount(1, $this->items, 'An item was removed from the items list.');
     $published_nid = Utility::createCombinedId('entity:node', '2:en');
     $this->assertTrue(isset($this->items[$published_nid]), 'Correct item was removed.');
 }
Exemplo n.º 30
0
 /**
  * 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;
 }