/**
  * 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);
 }
示例#2
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 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;
 }
 /**
  * {@inheritdoc}
  */
 public function query()
 {
     // If we're not using Field API field rendering, just use the query()
     // implementation of the fallback handler.
     if (!$this->options['field_rendering']) {
         $this->fallbackHandler->query();
         return;
     }
     // If we do use Field API rendering, we need the entity object for the
     // parent property.
     $parent_path = $this->getParentPath();
     $property_path = $parent_path ? "{$parent_path}:_object" : '_object';
     $combined_property_path = Utility::createCombinedId($this->getDatasourceId(), $property_path);
     $this->addRetrievedProperty($combined_property_path);
 }
示例#4
0
文件: Database.php 项目: jkyto/agolf
  /**
   * Creates a database query condition for a given search filter.
   *
   * Used as a helper method in createDbQuery().
   *
   * @param \Drupal\search_api\Query\FilterInterface $filter
   *   The filter for which a condition should be created.
   * @param array $fields
   *   Internal information about the index's fields.
   * @param \Drupal\Core\Database\Query\SelectInterface $db_query
   *   The database query to which the condition will be added.
   * @param \Drupal\search_api\IndexInterface $index
   *   The index we're searching on.
   *
   * @return \Drupal\Core\Database\Query\ConditionInterface|null
   *   The condition to set on the query, or NULL if none is necessary.
   *
   * @throws \Drupal\search_api\SearchApiException
   *   Thrown if an unknown field was used in the filter.
   */
  protected function createFilterCondition(FilterInterface $filter, array $fields, SelectInterface $db_query, IndexInterface $index) {
    $cond =  new Condition($filter->getConjunction());
    $db_info = $this->getIndexDbInfo($index);

    $empty = TRUE;
    // Store whether a JOIN already occurred for a field, so we don't JOIN
    // repeatedly for OR filters.
    $first_join = array();
    // Store the table aliases for the fields in this condition group.
    $tables = array();
    foreach ($filter->getFilters() as $f) {
      if (is_object($f)) {
        $c = $this->createFilterCondition($f, $fields, $db_query, $index);
        if ($c) {
          $empty = FALSE;
          $cond->condition($c);
        }
      }
      else {
        $empty = FALSE;
        // We don't index the datasource explicitly, so this needs a bit of
        // magic.
        // @todo Index the datasource explicitly so this doesn't need magic.
        if ($f[0] === 'search_api_datasource') {
          $alias = $this->getTableAlias(array('table' => $db_info['index_table']), $db_query);
          // @todo Stop recognizing "!=" as an operator.
          $operator = ($f[2] == '<>' || $f[2] == '!=') ? 'NOT LIKE' : 'LIKE';
          $prefix = Utility::createCombinedId($f[1], '');
          $cond->condition($alias . '.item_id', $this->database->escapeLike($prefix) . '%', $operator);
          continue;
        }
        if (!isset($fields[$f[0]])) {
          throw new SearchApiException(new FormattableMarkup('Unknown field in filter clause: @field.', array('@field' => $f[0])));
        }
        $field = $fields[$f[0]];
        // Fields have their own table, so we have to check for NULL values in
        // a special way (i.e., check for missing entries in that table).
        // @todo This can probably always use the denormalized table.
        if ($f[1] === NULL) {
          $query = $this->database->select($field['table'], 't')
            ->fields('t', array('item_id'));
          $cond->condition('t.item_id', $query, $f[2] == '<>' || $f[2] == '!=' ? 'IN' : 'NOT IN');
          continue;
        }
        if (Utility::isTextType($field['type'])) {
          $keys = $this->prepareKeys($f[1]);
          $query = $this->createKeysQuery($keys, array($f[0] => $field), $fields, $index);
          // We don't need the score, so we remove it. The score might either be
          // an expression or a field.
          $query_expressions = &$query->getExpressions();
          if ($query_expressions) {
            $query_expressions = array();
          }
          else {
            $query_fields = &$query->getFields();
            unset($query_fields['score']);
            unset($query_fields);
          }
          unset($query_expressions);
          $cond->condition('t.item_id', $query, $f[2] == '<>' || $f[2] == '!=' ? 'NOT IN' : 'IN');
        }
        else {
          $new_join = ($filter->getConjunction() == 'AND' || empty($first_join[$f[0]]));
          if ($new_join || empty($tables[$f[0]])) {
            $tables[$f[0]] = $this->getTableAlias($field, $db_query, $new_join);
            $first_join[$f[0]] = TRUE;
          }
          $column = $tables[$f[0]] . '.' . 'value';
          if ($f[1] !== NULL) {
            $cond->condition($column, $f[1], $f[2]);
          }
          else {
            $method = ($f[2] == '=') ? 'isNull' : 'isNotNull';
            $cond->$method($column);
          }
        }
      }
    }
    return $empty ? NULL : $cond;
  }
示例#5
0
 /**
  * {@inheritdoc}
  */
 public function getCombinedPropertyPath()
 {
     return Utility::createCombinedId($this->getDatasourceId(), $this->getPropertyPath());
 }
示例#6
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;
 }
 /**
  * Creates an items list for the given properties.
  *
  * @param \Drupal\Core\TypedData\DataDefinitionInterface[] $properties
  *   The property definitions, keyed by their property names.
  * @param string $active_property_path
  *   The relative property path to the active property.
  * @param \Drupal\Core\Url $base_url
  *   The base URL to which property path parameters should be added for
  *   the navigation links.
  * @param string $parent_path
  *   (optional) The common property path prefix of the given properties.
  * @param string $label_prefix
  *   (optional) The prefix to use for the labels of created fields.
  *
  * @return array
  *   A render array representing the given properties and, possibly, nested
  *   properties.
  */
 protected function getPropertiesList(array $properties, $active_property_path, Url $base_url, $parent_path = '', $label_prefix = '')
 {
     $list = array('#theme' => 'search_api_form_item_list');
     $active_item = '';
     if ($active_property_path) {
         list($active_item, $active_property_path) = explode(':', $active_property_path, 2) + array(1 => '');
     }
     $type_mapping = Utility::getFieldTypeMapping();
     $query_base = $base_url->getOption('query');
     foreach ($properties as $key => $property) {
         $this_path = $parent_path ? $parent_path . ':' : '';
         $this_path .= $key;
         $label = $property->getLabel();
         $property = Utility::getInnerProperty($property);
         $can_be_indexed = TRUE;
         $nested_properties = array();
         $parent_child_type = NULL;
         if ($property instanceof ComplexDataDefinitionInterface) {
             $can_be_indexed = FALSE;
             $nested_properties = $property->getPropertyDefinitions();
             $main_property = $property->getMainPropertyName();
             if ($main_property && isset($nested_properties[$main_property])) {
                 $parent_child_type = $property->getDataType() . '.';
                 $property = $nested_properties[$main_property];
                 $parent_child_type .= $property->getDataType();
                 unset($nested_properties[$main_property]);
                 $can_be_indexed = TRUE;
             }
             // Don't add the additional 'entity' property for entity reference
             // fields which don't target a content entity type.
             if ($property instanceof FieldItemDataDefinition && in_array($property->getDataType(), array('field_item:entity_reference', 'field_item:image', 'field_item:file'))) {
                 $entity_type = $this->getEntityTypeManager()->getDefinition($property->getSetting('target_type'));
                 if (!$entity_type->isSubclassOf('Drupal\\Core\\Entity\\ContentEntityInterface')) {
                     unset($nested_properties['entity']);
                 }
             }
         }
         // Don't allow indexing of properties with unmapped types. Also, prefer
         // a "parent.child" type mapping (taking into account the parent property
         // for, e.g., text fields).
         $type = $property->getDataType();
         if ($parent_child_type && !empty($type_mapping[$parent_child_type])) {
             $type = $parent_child_type;
         } elseif (empty($type_mapping[$type])) {
             // Remember the type only if it was not explicitly mapped to FALSE.
             if (!isset($type_mapping[$type])) {
                 $this->unmappedFields[$type][] = $label_prefix . $label;
             }
             $can_be_indexed = FALSE;
         }
         // If the property can neither be expanded nor indexed, just skip it.
         if (!($nested_properties || $can_be_indexed)) {
             continue;
         }
         $nested_list = array();
         $expand_link = array();
         if ($nested_properties) {
             if ($key == $active_item) {
                 $link_url = clone $base_url;
                 $query_base['property_path'] = $parent_path;
                 $link_url->setOption('query', $query_base);
                 $expand_link = array('#type' => 'link', '#title' => '(-) ', '#url' => $link_url);
                 $nested_list = $this->getPropertiesList($nested_properties, $active_property_path, $base_url, $this_path, $label_prefix . $label . ' » ');
             } else {
                 $link_url = clone $base_url;
                 $query_base['property_path'] = $this_path;
                 $link_url->setOption('query', $query_base);
                 $expand_link = array('#type' => 'link', '#title' => '(+) ', '#url' => $link_url);
             }
         }
         $item = array('#type' => 'container', '#attributes' => array('class' => array('container-inline')));
         if ($expand_link) {
             $item['expand_link'] = $expand_link;
         }
         $item['label']['#markup'] = Html::escape($label) . ' ';
         if ($can_be_indexed) {
             $item['add'] = array('#type' => 'submit', '#name' => Utility::createCombinedId($this->getParameter('datasource') ?: NULL, $this_path), '#value' => $this->t('Add'), '#submit' => array('::addField', '::save'), '#property' => $property, '#prefixed_label' => $label_prefix . $label, '#data_type' => $type_mapping[$type]);
         }
         if ($nested_list) {
             $item['properties'] = $nested_list;
         }
         $list[] = $item;
     }
     return $list;
 }
示例#8
0
 /**
  * Retrieve all properties available on the index.
  *
  * The properties will be keyed by combined ID, which is a combination of the
  * datasource ID and the property path. This is used internally in this class
  * to easily identify any property on the index.
  *
  * @param bool $alter
  *   (optional) Whether to pass the property definitions to the index's
  *   enabled processors for altering before returning them. Must be set to
  *   FALSE when called from within alterProperties(), for obvious reasons.
  *
  * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
  *   All the properties available on the index, keyed by combined ID.
  *
  * @see \Drupal\search_api\Utility::createCombinedId()
  */
 protected function getAvailableProperties($alter = TRUE)
 {
     $properties = array();
     $datasource_ids = $this->index->getDatasourceIds();
     $datasource_ids[] = NULL;
     foreach ($datasource_ids as $datasource_id) {
         foreach ($this->index->getPropertyDefinitions($datasource_id, $alter) as $property_path => $property) {
             $properties[Utility::createCombinedId($datasource_id, $property_path)] = $property;
         }
     }
     return $properties;
 }
示例#9
0
  /**
   * Generates some test items.
   *
   * @param array[] $items
   *   Array of items to be transformed into proper search item objects. Each
   *   item in this array is an associative array with the following keys:
   *   - datasource: The datasource plugin ID.
   *   - item: The item object to be indexed.
   *   - item_id: The datasource-specific raw item ID.
   *   - *: Any other keys will be treated as property paths, and their values
   *     as a single value for a field with that property path.
   *
   * @return \Drupal\search_api\Item\ItemInterface[]
   *   The generated test items.
   */
  public function generateItems(array $items) {
    /** @var \Drupal\search_api\Item\ItemInterface[] $extracted_items */
    $extracted_items = array();
    foreach ($items as $item) {
      $id = Utility::createCombinedId($item['datasource'], $item['item_id']);
      $extracted_items[$id] = Utility::createItemFromObject($this->index, $item['item'], $id);
      foreach (array(NULL, $item['datasource']) as $datasource_id) {
        foreach ($this->index->getFieldsByDatasource($datasource_id) as $key => $field) {
          /** @var \Drupal\search_api\Item\FieldInterface $field */
          $field = clone $field;
          if (isset($item[$field->getPropertyPath()])) {
            $field->addValue($item[$field->getPropertyPath()]);
          }
          $extracted_items[$id]->setField($key, $field);
        }
      }
    }

    return $extracted_items;
  }
 /**
  * Asserts that the search results contain the expected IDs.
  *
  * @param ResultSetInterface $result
  *   The search results.
  * @param int[][] $ids
  *   The expected entity IDs, grouped by entity type and with their indexes in
  *   this object's respective array properties as the values.
  */
 protected function assertResults(ResultSetInterface $result, array $expected)
 {
     $results = array_keys($result->getResultItems());
     sort($results);
     $ids = array();
     foreach ($expected as $entity_type => $items) {
         $datasource_id = "entity:{$entity_type}";
         foreach ($items as $i) {
             if ($entity_type == 'user') {
                 $id = $i . ':en';
             } else {
                 $id = $this->{"{$entity_type}s"}[$i]->id() . ':en';
             }
             $ids[] = Utility::createCombinedId($datasource_id, $id);
         }
     }
     sort($ids);
     $this->assertEqual($results, $ids);
 }
 /**
  * Adds a node access filter to a search query, if applicable.
  *
  * @param \Drupal\search_api\Query\QueryInterface $query
  *   The query to which a node access filter should be added, if applicable.
  * @param \Drupal\Core\Session\AccountInterface $account
  *   The user for whom the search is executed.
  *
  * @throws \Drupal\search_api\SearchApiException
  *   Thrown if not all necessary fields are indexed on the index.
  */
 protected function addNodeAccess(QueryInterface $query, AccountInterface $account)
 {
     // Don't do anything if the user can access all content.
     if ($account->hasPermission('bypass node access')) {
         return;
     }
     // Gather the affected datasources, grouped by entity type, as well as the
     // unaffected ones.
     $affected_datasources = array();
     $unaffected_datasources = array();
     foreach ($this->index->getDatasources() as $datasource_id => $datasource) {
         $entity_type = $datasource->getEntityTypeId();
         if (in_array($entity_type, array('node', 'comment'))) {
             $affected_datasources[$entity_type][] = $datasource_id;
         } else {
             $unaffected_datasources[] = $datasource_id;
         }
     }
     // The filter structure we want looks like this:
     //   [belongs to other datasource]
     //   OR
     //   (
     //     [is enabled (or was created by the user, if applicable)]
     //     AND
     //     [grants view access to one of the user's gid/realm combinations]
     //   )
     // If there are no "other" datasources, we don't need the nested OR,
     // however, and can add the "ADD"
     // @todo Add a filter tag, once they are implemented.
     if ($unaffected_datasources) {
         $outer_filter = $query->createFilter('OR');
         $query->filter($outer_filter);
         foreach ($unaffected_datasources as $datasource_id) {
             $outer_filter->condition('search_api_datasource', $datasource_id);
         }
         $access_filter = $query->createFilter('AND');
         $outer_filter->filter($access_filter);
     } else {
         $access_filter = $query;
     }
     if (!$account->hasPermission('access content')) {
         unset($affected_datasources['node']);
     }
     if (!$account->hasPermission('access comments')) {
         unset($affected_datasources['comment']);
     }
     // If the user does not have the permission to see any content at all, deny
     // access to all items from affected datasources.
     if (!$affected_datasources) {
         // If there were "other" datasources, the existing filter will already
         // remove all results of node or comment datasources. Otherwise, we should
         // not return any results at all.
         if (!$unaffected_datasources) {
             // @todo More elegant way to return no results?
             $query->condition('search_api_language', '');
         }
         return;
     }
     // Collect all the required fields that need to be part of the index.
     $unpublished_own = $account->hasPermission('view own unpublished content');
     $enabled_filter = $query->createFilter('OR');
     foreach ($affected_datasources as $entity_type => $datasources) {
         $published = $entity_type == 'node' ? NODE_PUBLISHED : Comment::PUBLISHED;
         foreach ($datasources as $datasource_id) {
             // If this is a comment datasource, or users cannot view their own
             // unpublished nodes, a simple filter on "status" is enough. Otherwise,
             // it's a bit more complicated.
             $status_field = Utility::createCombinedId($datasource_id, 'status');
             $enabled_filter->condition($status_field, $published);
             if ($entity_type == 'node' && $unpublished_own) {
                 $author_field = Utility::createCombinedId($datasource_id, 'uid');
                 $enabled_filter->condition($author_field, $account->id());
             }
         }
     }
     $access_filter->filter($enabled_filter);
     // Filter by the user's node access grants.
     $grants_filter = $query->createFilter('OR');
     $grants = node_access_grants('view', $account);
     foreach ($grants as $realm => $gids) {
         foreach ($gids as $gid) {
             $grants_filter->condition('search_api_node_grants', "node_access_{$realm}:{$gid}");
         }
     }
     // Also add items that are accessible for everyone by checking the "access
     // all" pseudo grant.
     $grants_filter->condition('search_api_node_grants', 'node_access__all');
     $access_filter->filter($grants_filter);
 }
示例#12
0
 /**
  * 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;
 }
示例#13
0
  /**
   * Tests field highlighting and excerpts with two items.
   */
  public function testPostprocessSearchResultsWithTwoItems() {
    $body_field_id = Utility::createCombinedId('entity:node', 'body');

    $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_field_id);
    $body_field->setType('text');

    $index->expects($this->atLeastOnce())
      ->method('getFields')
      ->will($this->returnValue(array($body_field_id => $body_field)));

    $this->processor->setIndex($index);

    $body_values = array('Some foo value', 'foo bar');
    $fields = array(
      $body_field_id => array(
        'type' => 'text',
        'values' => $body_values,
      ),
    );

    $items = $this->createItems($index, 2, $fields);

    $items[$this->itemIds[1]]->getField($body_field_id)->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_field_id][0], 'Highlighting is correctly applied to first body field value.');
    $this->assertEquals('<strong>foo</strong> bar', $output[$this->itemIds[0]][$body_field_id][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_field_id][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.');
  }
示例#14
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.');
 }
示例#15
0
 /**
  * Creates a database query condition for a given search filter.
  *
  * Used as a helper method in createDbQuery().
  *
  * @param \Drupal\search_api\Query\ConditionGroupInterface $conditions
  *   The conditions for which a condition should be created.
  * @param array $fields
  *   Internal information about the index's fields.
  * @param \Drupal\Core\Database\Query\SelectInterface $db_query
  *   The database query to which the condition will be added.
  * @param \Drupal\search_api\IndexInterface $index
  *   The index we're searching on.
  *
  * @return \Drupal\Core\Database\Query\ConditionInterface|null
  *   The condition to set on the query, or NULL if none is necessary.
  *
  * @throws \Drupal\search_api\SearchApiException
  *   Thrown if an unknown field was used in the filter.
  */
 protected function createDbCondition(ConditionGroupInterface $conditions, array $fields, SelectInterface $db_query, IndexInterface $index)
 {
     $db_condition = new Condition($conditions->getConjunction());
     $db_info = $this->getIndexDbInfo($index);
     // Store whether a JOIN already occurred for a field, so we don't JOIN
     // repeatedly for OR filters.
     $first_join = array();
     // Store the table aliases for the fields in this condition group.
     $tables = array();
     foreach ($conditions->getConditions() as $condition) {
         if ($condition instanceof ConditionGroupInterface) {
             $sub_condition = $this->createDbCondition($condition, $fields, $db_query, $index);
             if ($sub_condition) {
                 $db_condition->condition($sub_condition);
             }
         } else {
             $field = $condition->getField();
             $operator = $condition->getOperator();
             $value = $condition->getValue();
             $not_equals = $operator == '<>' || $operator == '!=';
             // We don't index the datasource explicitly, so this needs a bit of
             // magic.
             // @todo Index the datasource explicitly so this doesn't need magic.
             if ($field === 'search_api_datasource') {
                 if (empty($tables[NULL])) {
                     $table = array('table' => $db_info['index_table']);
                     $tables[NULL] = $this->getTableAlias($table, $db_query);
                 }
                 $operator = $not_equals ? 'NOT LIKE' : 'LIKE';
                 $prefix = Utility::createCombinedId($value, '');
                 $db_condition->condition($tables[NULL] . '.item_id', $this->database->escapeLike($prefix) . '%', $operator);
                 continue;
             }
             if (!isset($fields[$field])) {
                 throw new SearchApiException(new FormattableMarkup('Unknown field in filter clause: @field.', array('@field' => $field)));
             }
             $field_info = $fields[$field];
             // For NULL values, we can just use the single-values table, since we
             // only need to know if there's any value at all for that field.
             if ($value === NULL || empty($field_info['multi-valued'])) {
                 if (empty($tables[NULL])) {
                     $table = array('table' => $db_info['index_table']);
                     $tables[NULL] = $this->getTableAlias($table, $db_query);
                 }
                 $column = $tables[NULL] . '.' . $field_info['column'];
                 if ($value === NULL) {
                     $method = $not_equals ? 'isNotNull' : 'isNull';
                     $db_condition->{$method}($column);
                 } else {
                     $db_condition->condition($column, $value, $operator);
                 }
                 continue;
             }
             if (Utility::isTextType($field_info['type'])) {
                 $keys = $this->prepareKeys($value);
                 if (!isset($keys)) {
                     continue;
                 }
                 $query = $this->createKeysQuery($keys, array($field => $field_info), $fields, $index);
                 // We only want the item IDs, so we use the keys query as a nested
                 // query.
                 $query = $this->database->select($query, 't')->fields('t', array('item_id'));
                 $db_condition->condition('t.item_id', $query, $not_equals ? 'NOT IN' : 'IN');
             } else {
                 $new_join = $conditions->getConjunction() == 'AND' || empty($first_join[$field]);
                 if ($new_join || empty($tables[$field])) {
                     $tables[$field] = $this->getTableAlias($field_info, $db_query, $new_join);
                     $first_join[$field] = TRUE;
                 }
                 $column = $tables[$field] . '.' . 'value';
                 if ($not_equals) {
                     // The situation is more complicated for multi-valued fields, since
                     // we must make sure that results are excluded if ANY of the field's
                     // values equals the one given in this condition.
                     $query = $this->database->select($field_info['table'], 't')->fields('t', array('item_id'))->condition('value', $value);
                     $db_condition->condition('t.item_id', $query, 'NOT IN');
                 } else {
                     $db_condition->condition($column, $value, $operator);
                 }
             }
         }
     }
     return $db_condition->count() ? $db_condition : NULL;
 }
示例#16
0
 /**
  * {@inheritdoc}
  */
 public function trackItemsDeleted($datasource_id, array $ids)
 {
     if ($this->hasValidTracker() && $this->status()) {
         $item_ids = array();
         foreach ($ids as $id) {
             $item_ids[] = Utility::createCombinedId($datasource_id, $id);
         }
         $this->getTrackerInstance()->trackItemsDeleted($item_ids);
         if (!$this->isReadOnly() && $this->isServerEnabled()) {
             $this->getServerInstance()->deleteItems($this, $item_ids);
         }
     }
 }
示例#17
0
 /**
  * {@inheritdoc}
  */
 public function addItemsOnce(IndexInterface $index)
 {
     $index_state = $this->getIndexState($index);
     if (!($index_state['status'] && $index_state['pages'])) {
         return NULL;
     }
     if (!$index->hasValidTracker()) {
         return 0;
     }
     // Use this method to automatically circle through the datasources, adding
     // items from each of them in turn.
     $page = reset($index_state['pages']);
     $datasource_id = key($index_state['pages']);
     unset($index_state['pages'][$datasource_id]);
     $added = 0;
     if ($index->isValidDatasource($datasource_id)) {
         $raw_ids = $index->getDatasource($datasource_id)->getItemIds($page);
         if ($raw_ids !== NULL) {
             $index_state['pages'][$datasource_id] = ++$page;
             if ($raw_ids) {
                 $item_ids = array();
                 foreach ($raw_ids as $raw_id) {
                     $item_ids[] = Utility::createCombinedId($datasource_id, $raw_id);
                 }
                 $added = count($item_ids);
                 $index->getTrackerInstance()->trackItemsInserted($item_ids);
             }
         }
     }
     if (empty($index_state['pages'])) {
         $this->state->delete($this->getIndexStateKey($index));
         return NULL;
     }
     $this->state->set($this->getIndexStateKey($index), $index_state);
     return $added;
 }
 /**
  * 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;
 }
示例#19
0
 /**
  * Adds a field for a specific property to the index.
  *
  * @param string|null $datasource_id
  *   The property's datasource's ID, or NULL if it is a datasource-independent
  *   property.
  * @param string $property_path
  *   The property path.
  * @param string|null $label
  *   (optional) If given, the label to check for in the success message.
  */
 protected function addField($datasource_id, $property_path, $label = NULL)
 {
     $path = $this->getIndexPath('fields/add');
     $url_options = array('query' => array('datasource' => $datasource_id));
     if ($this->getUrl() === $this->buildUrl($path, $url_options)) {
         $path = NULL;
     }
     // Unfortunately it doesn't seem possible to specify the clicked button by
     // anything other than label, so we have to pass it as extra POST data.
     $combined_property_path = Utility::createCombinedId($datasource_id, $property_path);
     $post = '&' . $this->serializePostValues(array($combined_property_path => $this->t('Add')));
     $this->drupalPostForm($path, array(), NULL, $url_options, array(), NULL, $post);
     if ($label) {
         $args['%label'] = $label;
         $this->assertRaw($this->t('Field %label was added to the index.', $args));
     }
 }
示例#20
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.');
  }
 /**
  * Expands the properties to retrieve for this field.
  *
  * The properties are taken from this object's $retrievedProperties property,
  * with all their ancestors also added to the array, with the ancestor
  * properties always ordered before their descendants.
  *
  * This will ensure, when dealing with these properties sequentially, that
  * the parent object necessary to load the "child" property is always already
  * loaded.
  *
  * @return string[][]
  *   The combined property paths to retrieve, keyed by their datasource ID and
  *   property path.
  */
 protected function expandRequiredProperties()
 {
     $required_properties = array();
     foreach ($this->retrievedProperties as $datasource_id => $properties) {
         foreach (array_keys($properties) as $property_path) {
             $path_to_add = '';
             foreach (explode(':', $property_path) as $component) {
                 $path_to_add .= ($path_to_add ? ':' : '') . $component;
                 if (!isset($required_properties[$path_to_add])) {
                     $required_properties[$datasource_id][$path_to_add] = Utility::createCombinedId($datasource_id, $path_to_add);
                 }
             }
         }
     }
     return $required_properties;
 }
示例#22
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.');
 }
 /**
  * Ensures that a field with certain properties is indexed on the index.
  *
  * Can be used as a helper method in preIndexSave().
  *
  * @param string|null $datasource_id
  *   The ID of the field's datasource, or NULL for a datasource-independent
  *   field.
  * @param string $property_path
  *   The field's property path on the datasource.
  * @param string|null $type
  *   (optional) If set, the field should have this type.
  *
  * @return \Drupal\search_api\Item\FieldInterface
  *   A field on the index, possibly newly added, with the specified
  *   properties.
  *
  * @throws \Drupal\search_api\SearchApiException
  *   Thrown if there is no property with the specified path, or no type is
  *   given and no default could be determined for the property.
  */
 protected function ensureField($datasource_id, $property_path, $type = NULL)
 {
     $field = $this->findField($datasource_id, $property_path, $type);
     if (!$field) {
         $property = Utility::retrieveNestedProperty($this->index->getPropertyDefinitions($datasource_id), $property_path);
         if (!$property) {
             $args['%property'] = Utility::createCombinedId($datasource_id, $property_path);
             $args['%processor'] = $this->label();
             $message = new FormattableMarkup('Could not find property %property which is required by the %processor processor.', $args);
             throw new SearchApiException($message);
         }
         $field = Utility::createFieldFromProperty($this->index, $property, $datasource_id, $property_path, NULL, $type);
     }
     $field->setIndexedLocked();
     if (isset($type)) {
         $field->setTypeLocked();
     }
     $this->index->addField($field);
     return $field;
 }