upadted to 1.8

This commit is contained in:
Bachir Soussi Chiadmi
2013-09-26 15:49:26 +02:00
parent e0ae80791b
commit 128640cd15
52 changed files with 2604 additions and 1015 deletions

View File

@@ -1,5 +1,13 @@
<?php
/**
* @file
* Contains base definitions for data alterations.
*
* Contains the SearchApiAlterCallbackInterface interface and the
* SearchApiAbstractAlterCallback class.
*/
/**
* Interface representing a Search API data-alter callback.
*/
@@ -85,10 +93,15 @@ interface SearchApiAlterCallbackInterface {
public function alterItems(array &$items);
/**
* Declare the properties that are (or can be) added to items with this
* callback. If a property with this name already exists for an entity it
* will be overridden, so keep a clear namespace by prefixing the properties
* with the module name if this is not desired.
* Declare the properties that are added to items by this callback.
*
* If one of the specified properties already exists for an entity it will be
* overridden, so keep a clear namespace by prefixing the properties with the
* module name if this is not desired.
*
* CAUTION: Since this method is used when calling
* SearchApiIndex::getFields(), calling that method from inside propertyInfo()
* will lead to a recursion and should therefore be avoided.
*
* @see hook_entity_property_info()
*
@@ -101,8 +114,10 @@ interface SearchApiAlterCallbackInterface {
}
/**
* Abstract base class for data-alter callbacks, implementing most methods with
* sensible defaults.
* Abstract base class for data-alter callbacks.
*
* This class implements most methods with sensible defaults.
*
* Extending classes will at least have to implement the alterItems() method to
* make this work. If that method adds additional fields to the items,
* propertyInfo() has to be overridden, too.
@@ -124,12 +139,7 @@ abstract class SearchApiAbstractAlterCallback implements SearchApiAlterCallbackI
protected $options;
/**
* Construct a data-alter callback.
*
* @param SearchApiIndex $index
* The index whose items will be altered.
* @param array $options
* The callback options set for this index.
* Implements SearchApiAlterCallbackInterface::__construct().
*/
public function __construct(SearchApiIndex $index, array $options = array()) {
$this->index = $index;
@@ -137,64 +147,28 @@ abstract class SearchApiAbstractAlterCallback implements SearchApiAlterCallbackI
}
/**
* Check whether this data-alter callback is applicable for a certain index.
*
* This can be used for hiding the callback on the index's "Workflow" tab. To
* avoid confusion, you should only use criteria that are immutable, such as
* the index's entity type. Also, since this is only used for UI purposes, you
* should not completely rely on this to ensure certain index configurations
* and at least throw an exception with a descriptive error message if this is
* violated on runtime.
* Implements SearchApiAlterCallbackInterface::supportsIndex().
*
* The default implementation always returns TRUE.
*
* @param SearchApiIndex $index
* The index to check for.
*
* @return boolean
* TRUE if the callback can run on the given index; FALSE otherwise.
*/
public function supportsIndex(SearchApiIndex $index) {
return TRUE;
}
/**
* Display a form for configuring this callback.
*
* @return array
* A form array for configuring this callback, or FALSE if no configuration
* is possible.
* Implements SearchApiAlterCallbackInterface::configurationForm().
*/
public function configurationForm() {
return array();
}
/**
* Validation callback for the form returned by configurationForm().
*
* @param array $form
* The form returned by configurationForm().
* @param array $values
* The part of the $form_state['values'] array corresponding to this form.
* @param array $form_state
* The complete form state.
* Implements SearchApiAlterCallbackInterface::configurationFormValidate().
*/
public function configurationFormValidate(array $form, array &$values, array &$form_state) { }
/**
* Submit callback for the form returned by configurationForm().
*
* This method should both return the new options and set them internally.
*
* @param array $form
* The form returned by configurationForm().
* @param array $values
* The part of the $form_state['values'] array corresponding to this form.
* @param array $form_state
* The complete form state.
*
* @return array
* The new options array for this callback.
* Implements SearchApiAlterCallbackInterface::configurationFormSubmit().
*/
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
$this->options = $values;
@@ -202,16 +176,7 @@ abstract class SearchApiAbstractAlterCallback implements SearchApiAlterCallbackI
}
/**
* Declare the properties that are (or can be) added to items with this
* callback. If a property with this name already exists for an entity it
* will be overridden, so keep a clear namespace by prefixing the properties
* with the module name if this is not desired.
*
* @see hook_entity_property_info()
*
* @return array
* Information about all additional properties, as specified by
* hook_entity_property_info() (only the inner "properties" array).
* Implements SearchApiAlterCallbackInterface::propertyInfo().
*/
public function propertyInfo() {
return array();

View File

@@ -231,13 +231,19 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
}
return;
}
$v = $wrapper->value(array('identifier' => TRUE));
if ($v && !isset($values[$v])) {
$values[$v] = $v;
if (isset($wrapper->$property) && $wrapper->$property->value()) {
$this->extractHierarchy($wrapper->$property, $property, $values);
try {
$v = $wrapper->value(array('identifier' => TRUE));
if ($v && !isset($values[$v])) {
$values[$v] = $v;
if (isset($wrapper->$property) && $wrapper->value() && $wrapper->$property->value()) {
$this->extractHierarchy($wrapper->$property, $property, $values);
}
}
}
catch (EntityMetadataWrapperException $e) {
// Some properties like entity_metadata_book_get_properties() throw
// exceptions, so we catch them here and ignore the property.
}
}
}

View File

@@ -11,14 +11,16 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
* @see SearchApiAlterCallbackInterface::supportsIndex()
*/
public function supportsIndex(SearchApiIndex $index) {
return (bool) entity_get_info($index->item_type);
return (bool) $index->getEntityType();
}
public function configurationForm() {
$info = entity_get_info($this->index->item_type);
$view_modes = array();
foreach ($info['view modes'] as $key => $mode) {
$view_modes[$key] = $mode['label'];
if ($entity_type = $this->index->getEntityType()) {
$info = entity_get_info($entity_type);
foreach ($info['view modes'] as $key => $mode) {
$view_modes[$key] = $mode['label'];
}
}
$this->options += array('mode' => reset($view_modes));
if (count($view_modes) > 1) {
@@ -60,7 +62,7 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
$original_user = $GLOBALS['user'];
$GLOBALS['user'] = drupal_anonymous_user();
$type = $this->index->item_type;
$type = $this->index->getEntityType();
$mode = empty($this->options['mode']) ? 'full' : $this->options['mode'];
foreach ($items as $id => &$item) {
// Since we can't really know what happens in entity_view() and render(),

View File

@@ -7,11 +7,11 @@
class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
public function supportsIndex(SearchApiIndex $index) {
return ($info = entity_get_info($index->item_type)) && self::hasBundles($info);
return $index->getEntityType() && ($info = entity_get_info($index->getEntityType())) && self::hasBundles($info);
}
public function alterItems(array &$items) {
$info = entity_get_info($this->index->item_type);
$info = entity_get_info($this->index->getEntityType());
if (self::hasBundles($info) && isset($this->options['bundles'])) {
$bundles = array_flip($this->options['bundles']);
$default = (bool) $this->options['default'];
@@ -25,7 +25,7 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
}
public function configurationForm() {
$info = entity_get_info($this->index->item_type);
$info = entity_get_info($this->index->getEntityType());
if (self::hasBundles($info)) {
$options = array();
foreach ($info['bundles'] as $bundle => $bundle_info) {

View File

@@ -22,7 +22,7 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
*/
public function supportsIndex(SearchApiIndex $index) {
// Currently only node access is supported.
return $index->item_type === 'node';
return $index->getEntityType() === 'node';
}
/**

View File

@@ -22,7 +22,7 @@ class SearchApiAlterNodeStatus extends SearchApiAbstractAlterCallback {
* TRUE if the callback can run on the given index; FALSE otherwise.
*/
public function supportsIndex(SearchApiIndex $index) {
return $index->item_type === 'node';
return $index->getEntityType() === 'node';
}
/**

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains the SearchApiAlterRoleFilter class.
*/
/**
* Data alteration that filters out users based on their role.
*/
class SearchApiAlterRoleFilter extends SearchApiAbstractAlterCallback {
/**
* Overrides SearchApiAbstractAlterCallback::supportsIndex().
*
* This plugin only supports indexes containing users.
*/
public function supportsIndex(SearchApiIndex $index) {
return $index->getEntityType() == 'user';
}
/**
* Implements SearchApiAlterCallbackInterface::alterItems().
*/
public function alterItems(array &$items) {
$roles = $this->options['roles'];
$default = (bool) $this->options['default'];
foreach ($items as $id => $account) {
$role_match = (count(array_diff_key($account->roles, $roles)) !== count($account->roles));
if ($role_match === $default) {
unset($items[$id]);
}
}
}
/**
* Overrides SearchApiAbstractAlterCallback::configurationForm().
*
* Add option for the roles to include/exclude.
*/
public function configurationForm() {
$options = array_map('check_plain', user_roles());
$form = array(
'default' => array(
'#type' => 'radios',
'#title' => t('Which users should be indexed?'),
'#default_value' => isset($this->options['default']) ? $this->options['default'] : 1,
'#options' => array(
1 => t('All but those from one of the selected roles'),
0 => t('Only those from the selected roles'),
),
),
'roles' => array(
'#type' => 'select',
'#title' => t('Roles'),
'#default_value' => isset($this->options['roles']) ? $this->options['roles'] : array(),
'#options' => $options,
'#size' => min(4, count($options)),
'#multiple' => TRUE,
),
);
return $form;
}
}

89
includes/datasource.inc Executable file → Normal file
View File

@@ -12,6 +12,13 @@
* They are used for loading items, extracting item data, keeping track of the
* item status, etc.
*
* Modules providing implementations of this interface that use a different way
* (either different table or different method altogether) of keeping track of
* indexed/dirty items than SearchApiAbstractDataSourceController should be
* aware that indexes' numerical IDs can change due to feature reverts. It is
* therefore recommended to use search_api_index_update_datasource(), or similar
* code, in a hook_search_api_index_update() implementation.
*
* All methods of the data source may throw exceptions of type
* SearchApiDataSourceException if any exception or error state is encountered.
*/
@@ -237,6 +244,14 @@ interface SearchApiDataSourceControllerInterface {
*/
public function getIndexStatus(SearchApiIndex $index);
/**
* Get the entity type of items from this datasource.
*
* @return string|null
* An entity type string if the items provided by this datasource are
* entities; NULL otherwise.
*/
public function getEntityType();
}
/**
@@ -264,6 +279,15 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
*/
protected $type;
/**
* The entity type for this controller instance.
*
* @var string|null
*
* @see getEntityType()
*/
protected $entityType = NULL;
/**
* The info array for the item type, as specified via
* hook_search_api_item_type_info().
@@ -314,6 +338,21 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
public function __construct($type) {
$this->type = $type;
$this->info = search_api_get_item_type_info($type);
if (!empty($this->info['entity_type'])) {
$this->entityType = $this->info['entity_type'];
}
}
/**
* Get the entity type of items from this datasource.
*
* @return string|null
* An entity type string if the items provided by this datasource are
* entities; NULL otherwise.
*/
public function getEntityType() {
return $this->entityType;
}
/**
@@ -333,19 +372,55 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
*/
public function getMetadataWrapper($item = NULL, array $info = array()) {
$info += $this->getPropertyInfo();
return entity_metadata_wrapper($this->type, $item, $info);
return entity_metadata_wrapper($this->entityType ? $this->entityType : $this->type, $item, $info);
}
/**
* Helper method that can be used by subclasses to specify the property
* information to use when creating a metadata wrapper.
* Get the property info for this item type.
*
* This is a helper method for getMetadataWrapper() that can be used by
* subclasses to specify the property information to use when creating a
* metadata wrapper.
*
* The data structure uses largely the format specified in
* hook_entity_property_info(). However, the first level of keys (containing
* the entity types) is omitted, and the "property" key is called
* "property info" instead. So, an example return value would look like this:
*
* @code
* return array(
* 'property info' => array(
* 'foo' => array(
* 'label' => t('Foo'),
* 'type' => 'text',
* ),
* 'bar' => array(
* 'label' => t('Bar'),
* 'type' => 'list<integer>',
* ),
* ),
* );
* @endcode
*
* SearchApiExternalDataSourceController::getPropertyInfo() contains a working
* example of this method.
*
* If the item type is an entity type, no additional property information is
* required, the method will thus just return an empty array. You can still
* use this to append additional properties to the entities, or the like,
* though.
*
* @return array
* Property information as specified by hook_entity_property_info().
* Property information as specified by entity_metadata_wrapper().
*
* @see getMetadataWrapper()
* @see hook_entity_property_info()
*/
protected function getPropertyInfo() {
// If this is an entity type, no additional property info is needed.
if ($this->entityType) {
return array();
}
throw new SearchApiDataSourceException(t('No known property information for type @type.', array('@type' => $this->type)));
}
@@ -689,8 +764,10 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
if ($index->item_type != $this->type) {
$index_type = search_api_get_item_type_info($index->item_type);
$index_type = empty($index_type['name']) ? $index->item_type : $index_type['name'];
$msg = t('Invalid index @index of type @index_type passed to data source controller for type @this_type.',
array('@index' => $index->name, '@index_type' => $index_type, '@this_type' => $this->info['name']));
$msg = t(
'Invalid index @index of type @index_type passed to data source controller for type @this_type.',
array('@index' => $index->name, '@index_type' => $index_type, '@this_type' => $this->info['name'])
);
throw new SearchApiDataSourceException($msg);
}
}

View File

@@ -20,8 +20,8 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* search_api_field_types(). List types ("list<*>") are not allowed.
*/
public function getIdFieldInfo() {
$info = entity_get_info($this->type);
$properties = entity_get_property_info($this->type);
$info = entity_get_info($this->entityType);
$properties = entity_get_property_info($this->entityType);
if (empty($info['entity keys']['id'])) {
throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $info['label'])));
}
@@ -52,7 +52,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* The loaded items, keyed by ID.
*/
public function loadItems(array $ids) {
$items = entity_load($this->type, $ids);
$items = entity_load($this->entityType, $ids);
// If some items couldn't be loaded, remove them from tracking.
if (count($items) != count($ids)) {
$ids = array_flip($ids);
@@ -80,7 +80,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* @see entity_metadata_wrapper()
*/
public function getMetadataWrapper($item = NULL, array $info = array()) {
return entity_metadata_wrapper($this->type, $item, $info);
return entity_metadata_wrapper($this->entityType, $item, $info);
}
/**
@@ -93,7 +93,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* Either the unique ID of the item, or NULL if none is available.
*/
public function getItemId($item) {
$id = entity_id($this->type, $item);
$id = entity_id($this->entityType, $item);
return $id ? $id : NULL;
}
@@ -107,7 +107,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* Either a human-readable label for the item, or NULL if none is available.
*/
public function getItemLabel($item) {
$label = entity_label($this->type, $item);
$label = entity_label($this->entityType, $item);
return $label ? $label : NULL;
}
@@ -123,7 +123,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* item has no URL of its own.
*/
public function getItemUrl($item) {
if ($this->type == 'file') {
if ($this->entityType == 'file') {
return array(
'path' => file_create_url($item->uri),
'options' => array(
@@ -132,7 +132,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
),
);
}
$url = entity_uri($this->type, $item);
$url = entity_uri($this->entityType, $item);
return $url ? $url : NULL;
}
@@ -158,7 +158,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
// all items again without any key conflicts.
$this->stopTracking($indexes);
$entity_info = entity_get_info($this->type);
$entity_info = entity_get_info($this->entityType);
if (!empty($entity_info['base table'])) {
// Use a subselect, which will probably be much faster than entity_load().
@@ -200,7 +200,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
* An array containing all item IDs for this type.
*/
protected function getAllItemIds() {
return array_keys(entity_load($this->type));
return array_keys(entity_load($this->entityType));
}
}

View File

@@ -19,9 +19,7 @@
* will only have to specify some property information in getPropertyInfo(). If
* you have a custom service class which already returns the extracted fields
* with the search results, you will only have to provide a label and a type for
* each field. To make this use case easier, there is also a
* getFieldInformation() method which you can implement instead of directly
* implementing getPropertyInfo().
* each field.
*/
class SearchApiExternalDataSourceController extends SearchApiAbstractDataSourceController {
@@ -61,16 +59,9 @@ class SearchApiExternalDataSourceController extends SearchApiAbstractDataSourceC
}
/**
* Helper method that can be used by subclasses to specify the property
* information to use when creating a metadata wrapper.
* Overrides SearchApiAbstractDataSourceController::getPropertyInfo().
*
* For most use cases, you will have to override this method to provide the
* real property information for your item type.
*
* @return array
* Property information as specified by hook_entity_property_info().
*
* @see hook_entity_property_info()
* Only returns a single string ID field.
*/
protected function getPropertyInfo() {
$info['property info']['id'] = array(

View File

@@ -43,6 +43,15 @@ class SearchApiIndex extends Entity {
*/
protected $added_properties = NULL;
/**
* Static cache for the results of getFields().
*
* Can be accessed as follows: $this->fields[$only_indexed][$get_additional].
*
* @var array
*/
protected $fields = array();
/**
* An array containing two arrays.
*
@@ -192,7 +201,11 @@ class SearchApiIndex extends Entity {
if ($server->enabled) {
$server->removeIndex($this);
}
else {
// Once the index is deleted, servers won't be able to tell whether it was
// read-only. Therefore, we prefer to err on the safe side and don't call
// the server method at all if the index is read-only and the server
// currently disabled.
elseif (empty($this->read_only)) {
$tasks = variable_get('search_api_tasks', array());
$tasks[$server->machine_name][$this->machine_name] = array('remove');
variable_set('search_api_tasks', $tasks);
@@ -356,6 +369,17 @@ class SearchApiIndex extends Entity {
return $this->datasource;
}
/**
* Get the entity type of items in this index.
*
* @return string|null
* An entity type string if the items in this index are entities; NULL
* otherwise.
*/
public function getEntityType() {
return $this->datasource()->getEntityType();
}
/**
* Get the server this index lies on.
*
@@ -528,8 +552,8 @@ class SearchApiIndex extends Entity {
'options list' => 'entity_metadata_language_list',
),
);
// We use the reverse order here so the hierarchy for overwriting property infos is the same
// as for actually overwriting the properties.
// We use the reverse order here so the hierarchy for overwriting property
// infos is the same as for actually overwriting the properties.
foreach (array_reverse($this->getAlterCallbacks()) as $callback) {
$props = $callback->propertyInfo();
if ($props) {
@@ -543,16 +567,14 @@ class SearchApiIndex extends Entity {
return $property_info;
}
/**
* Fills the $processors array for use by the pre-/postprocessing functions.
/**
* Loads all enabled data alterations for this index in proper order.
*
* @return SearchApiIndex
* The called object.
* @return array
* All enabled callbacks for this index, as SearchApiAlterCallbackInterface
* objects.
*/
protected function getAlterCallbacks() {
public function getAlterCallbacks() {
if (isset($this->callbacks)) {
return $this->callbacks;
}
@@ -585,11 +607,13 @@ class SearchApiIndex extends Entity {
}
/**
* Loads all enabled processors for this index in proper order.
*
* @return array
* All enabled processors for this index, as SearchApiProcessorInterface
* objects.
*/
protected function getProcessors() {
public function getProcessors() {
if (isset($this->processors)) {
return $this->processors;
}
@@ -721,140 +745,160 @@ class SearchApiIndex extends Entity {
* "additional fields" key.
*/
public function getFields($only_indexed = TRUE, $get_additional = FALSE) {
$fields = empty($this->options['fields']) ? array() : $this->options['fields'];
$wrapper = $this->entityWrapper();
$additional = array();
$entity_types = entity_get_info();
$only_indexed = $only_indexed ? 1 : 0;
$get_additional = $get_additional ? 1 : 0;
// First we need all already added prefixes.
$added = ($only_indexed || empty($this->options['additional fields'])) ? array() : $this->options['additional fields'];
foreach (array_keys($fields) as $key) {
$len = strlen($key) + 1;
$pos = $len;
// The third parameter ($offset) to strrpos has rather weird behaviour,
// necessitating this rather awkward code. It will iterate over all
// prefixes of each field, beginning with the longest, adding all of them
// to $added until one is encountered that was already added (which means
// all shorter ones will have already been added, too).
while ($pos = strrpos($key, ':', $pos - $len)) {
$prefix = substr($key, 0, $pos);
if (isset($added[$prefix])) {
break;
}
$added[$prefix] = $prefix;
// First, try the static cache and the persistent cache bin.
if (empty($this->fields[$only_indexed][$get_additional])) {
$cid = $this->getCacheId() . "-$only_indexed-$get_additional";
$cache = cache_get($cid);
if ($cache) {
$this->fields[$only_indexed][$get_additional] = $cache->data;
}
}
// Then we walk through all properties and look if they are already
// contained in one of the arrays.
// Since this uses an iterative instead of a recursive approach, it is a bit
// complicated, with three arrays tracking the current depth.
// Otherwise, we have to compute the result.
if (empty($this->fields[$only_indexed][$get_additional])) {
$fields = empty($this->options['fields']) ? array() : $this->options['fields'];
$wrapper = $this->entityWrapper();
$additional = array();
$entity_types = entity_get_info();
// A wrapper for a specific field name prefix, e.g. 'user:' mapped to the user wrapper
$wrappers = array('' => $wrapper);
// Display names for the prefixes
$prefix_names = array('' => '');
// The list nesting level for entities with a certain prefix
$nesting_levels = array('' => 0);
$types = search_api_default_field_types();
$flat = array();
while ($wrappers) {
foreach ($wrappers as $prefix => $wrapper) {
$prefix_name = $prefix_names[$prefix];
// Deal with lists of entities.
$nesting_level = $nesting_levels[$prefix];
$type_prefix = str_repeat('list<', $nesting_level);
$type_suffix = str_repeat('>', $nesting_level);
if ($nesting_level) {
$info = $wrapper->info();
// The real nesting level of the wrapper, not the accumulated one.
$level = search_api_list_nesting_level($info['type']);
for ($i = 0; $i < $level; ++$i) {
$wrapper = $wrapper[0];
// First we need all already added prefixes.
$added = ($only_indexed || empty($this->options['additional fields'])) ? array() : $this->options['additional fields'];
foreach (array_keys($fields) as $key) {
$len = strlen($key) + 1;
$pos = $len;
// The third parameter ($offset) to strrpos has rather weird behaviour,
// necessitating this rather awkward code. It will iterate over all
// prefixes of each field, beginning with the longest, adding all of them
// to $added until one is encountered that was already added (which means
// all shorter ones will have already been added, too).
while ($pos = strrpos($key, ':', $pos - $len)) {
$prefix = substr($key, 0, $pos);
if (isset($added[$prefix])) {
break;
}
$added[$prefix] = $prefix;
}
// Now look at all properties.
foreach ($wrapper as $property => $value) {
$info = $value->info();
// We hide the complexity of multi-valued types from the user here.
$type = search_api_extract_inner_type($info['type']);
// Treat Entity API type "token" as our "string" type.
// Also let text fields with limited options be of type "string" by default.
if ($type == 'token' || ($type == 'text' && !empty($info['options list']))) {
// Inner type is changed to "string".
$type = 'string';
// Set the field type accordingly.
$info['type'] = search_api_nest_type('string', $info['type']);
}
$info['type'] = $type_prefix . $info['type'] . $type_suffix;
$key = $prefix . $property;
if ((isset($types[$type]) || isset($entity_types[$type])) && (!$only_indexed || !empty($fields[$key]))) {
if (!empty($fields[$key])) {
// This field is already known in the index configuration.
$flat[$key] = $fields[$key] + array(
'name' => $prefix_name . $info['label'],
'description' => empty($info['description']) ? NULL : $info['description'],
'boost' => '1.0',
'indexed' => TRUE,
);
// Update the type and its nesting level for non-entity properties.
if (!isset($entity_types[$type])) {
$flat[$key]['type'] = search_api_nest_type(search_api_extract_inner_type($flat[$key]['type']), $info['type']);
if (isset($flat[$key]['real_type'])) {
$real_type = search_api_extract_inner_type($flat[$key]['real_type']);
$flat[$key]['real_type'] = search_api_nest_type($real_type, $info['type']);
}
}
}
else {
$flat[$key] = array(
'name' => $prefix_name . $info['label'],
'description' => empty($info['description']) ? NULL : $info['description'],
'type' => $info['type'],
'boost' => '1.0',
'indexed' => FALSE,
);
}
if (isset($entity_types[$type])) {
$base_type = isset($entity_types[$type]['entity keys']['name']) ? 'string' : 'integer';
$flat[$key]['type'] = search_api_nest_type($base_type, $info['type']);
$flat[$key]['entity_type'] = $type;
}
}
if (empty($types[$type])) {
if (isset($added[$key])) {
// Visit this entity/struct in a later iteration.
$wrappers[$key . ':'] = $value;
$prefix_names[$key . ':'] = $prefix_name . $info['label'] . ' » ';
$nesting_levels[$key . ':'] = search_api_list_nesting_level($info['type']);
}
else {
$name = $prefix_name . $info['label'];
// Add machine names to discern fields with identical labels.
if (isset($used_names[$name])) {
if ($used_names[$name] !== FALSE) {
$additional[$used_names[$name]] .= ' [' . $used_names[$name] . ']';
$used_names[$name] = FALSE;
}
$name .= ' [' . $key . ']';
}
$additional[$key] = $name;
$used_names[$name] = $key;
}
}
}
unset($wrappers[$prefix]);
}
// Then we walk through all properties and look if they are already
// contained in one of the arrays.
// Since this uses an iterative instead of a recursive approach, it is a bit
// complicated, with three arrays tracking the current depth.
// A wrapper for a specific field name prefix, e.g. 'user:' mapped to the user wrapper
$wrappers = array('' => $wrapper);
// Display names for the prefixes
$prefix_names = array('' => '');
// The list nesting level for entities with a certain prefix
$nesting_levels = array('' => 0);
$types = search_api_default_field_types();
$flat = array();
while ($wrappers) {
foreach ($wrappers as $prefix => $wrapper) {
$prefix_name = $prefix_names[$prefix];
// Deal with lists of entities.
$nesting_level = $nesting_levels[$prefix];
$type_prefix = str_repeat('list<', $nesting_level);
$type_suffix = str_repeat('>', $nesting_level);
if ($nesting_level) {
$info = $wrapper->info();
// The real nesting level of the wrapper, not the accumulated one.
$level = search_api_list_nesting_level($info['type']);
for ($i = 0; $i < $level; ++$i) {
$wrapper = $wrapper[0];
}
}
// Now look at all properties.
foreach ($wrapper as $property => $value) {
$info = $value->info();
// We hide the complexity of multi-valued types from the user here.
$type = search_api_extract_inner_type($info['type']);
// Treat Entity API type "token" as our "string" type.
// Also let text fields with limited options be of type "string" by default.
if ($type == 'token' || ($type == 'text' && !empty($info['options list']))) {
// Inner type is changed to "string".
$type = 'string';
// Set the field type accordingly.
$info['type'] = search_api_nest_type('string', $info['type']);
}
$info['type'] = $type_prefix . $info['type'] . $type_suffix;
$key = $prefix . $property;
if ((isset($types[$type]) || isset($entity_types[$type])) && (!$only_indexed || !empty($fields[$key]))) {
if (!empty($fields[$key])) {
// This field is already known in the index configuration.
$flat[$key] = $fields[$key] + array(
'name' => $prefix_name . $info['label'],
'description' => empty($info['description']) ? NULL : $info['description'],
'boost' => '1.0',
'indexed' => TRUE,
);
// Update the type and its nesting level for non-entity properties.
if (!isset($entity_types[$type])) {
$flat[$key]['type'] = search_api_nest_type(search_api_extract_inner_type($flat[$key]['type']), $info['type']);
if (isset($flat[$key]['real_type'])) {
$real_type = search_api_extract_inner_type($flat[$key]['real_type']);
$flat[$key]['real_type'] = search_api_nest_type($real_type, $info['type']);
}
}
}
else {
$flat[$key] = array(
'name' => $prefix_name . $info['label'],
'description' => empty($info['description']) ? NULL : $info['description'],
'type' => $info['type'],
'boost' => '1.0',
'indexed' => FALSE,
);
}
if (isset($entity_types[$type])) {
$base_type = isset($entity_types[$type]['entity keys']['name']) ? 'string' : 'integer';
$flat[$key]['type'] = search_api_nest_type($base_type, $info['type']);
$flat[$key]['entity_type'] = $type;
}
}
if (empty($types[$type])) {
if (isset($added[$key])) {
// Visit this entity/struct in a later iteration.
$wrappers[$key . ':'] = $value;
$prefix_names[$key . ':'] = $prefix_name . $info['label'] . ' » ';
$nesting_levels[$key . ':'] = search_api_list_nesting_level($info['type']);
}
else {
$name = $prefix_name . $info['label'];
// Add machine names to discern fields with identical labels.
if (isset($used_names[$name])) {
if ($used_names[$name] !== FALSE) {
$additional[$used_names[$name]] .= ' [' . $used_names[$name] . ']';
$used_names[$name] = FALSE;
}
$name .= ' [' . $key . ']';
}
$additional[$key] = $name;
$used_names[$name] = $key;
}
}
}
unset($wrappers[$prefix]);
}
}
if (!$get_additional) {
$this->fields[$only_indexed][$get_additional] = $flat;
}
else {
$options = array();
$options['fields'] = $flat;
$options['additional fields'] = $additional;
$this->fields[$only_indexed][$get_additional] = $options;
}
cache_set($cid, $this->fields[$only_indexed][$get_additional]);
}
if (!$get_additional) {
return $flat;
}
$options = array();
$options['fields'] = $flat;
$options['additional fields'] = $additional;
return $options;
return $this->fields[$only_indexed][$get_additional];
}
/**
@@ -881,6 +925,19 @@ class SearchApiIndex extends Entity {
return $this->fulltext_fields[$i];
}
/**
* Get the cache ID prefix used for this index's caches.
*
* @param $type
* The type of cache. Currently only "fields" is used.
*
* @return
* The cache ID (prefix) for this index's caches.
*/
public function getCacheId($type = 'fields') {
return 'search_api:index-' . $this->machine_name . '--' . $type;
}
/**
* Helper function for creating an entity metadata wrapper appropriate for
* this index.
@@ -930,6 +987,7 @@ class SearchApiIndex extends Entity {
$this->callbacks = NULL;
$this->processors = NULL;
$this->added_properties = NULL;
$this->fields = array();
$this->fulltext_fields = array();
}

View File

@@ -187,7 +187,7 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
$fields = array_filter($values['fields']);
if ($fields) {
$fields = array_combine($fields, array_fill(0, count($fields), TRUE));
$fields = array_fill_keys($fields, TRUE);
}
$values['fields'] = $fields;
}
@@ -272,8 +272,14 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
$this->processFieldValue($value);
}
if (is_array($value)) {
$type = 'tokens';
$value = $this->normalizeTokens($value);
// Don't tokenize non-fulltext content!
if (in_array($type, array('text', 'tokens'))) {
$type = 'tokens';
$value = $this->normalizeTokens($value);
}
else {
$value = $this->implodeTokens($value);
}
}
}
@@ -305,6 +311,32 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
return $ret;
}
/**
* Internal helper function for imploding tokens into a single string.
*
* @param array $tokens
* The tokens array to implode.
*
* @return string
* The text data from the tokens concatenated into a single string.
*/
protected function implodeTokens(array $tokens) {
$ret = array();
foreach ($tokens as $token) {
if (empty($token['value']) && !is_numeric($token['value'])) {
// Filter out empty tokens.
continue;
}
if (is_array($token['value'])) {
$ret[] = $this->implodeTokens($token['value']);
}
else {
$ret[] = $token['value'];
}
}
return implode(' ', $ret);
}
/**
* Method for preprocessing search keys.
*/
@@ -329,10 +361,20 @@ abstract class SearchApiAbstractProcessor implements SearchApiProcessorInterface
*/
protected function processFilters(array &$filters) {
$fields = $this->index->options['fields'];
foreach ($filters as &$f) {
foreach ($filters as $key => &$f) {
if (is_array($f)) {
if (isset($fields[$f[0]]) && $this->testField($f[0], $fields[$f[0]])) {
// We want to allow processors also to easily remove complete filters.
// However, we can't use empty() or the like, as that would sort out
// filters for 0 or NULL. So we specifically check only for the empty
// string, and we also make sure the filter value was actually changed
// by storing whether it was empty before.
$empty_string = $f[1] === '';
$this->processFilterValue($f[1]);
if ($f[1] === '' && !$empty_string) {
unset($filters[$key]);
}
}
}
else {

View File

@@ -0,0 +1,401 @@
<?php
/**
* @file
* Contains the SearchApiHighlight class.
*/
/**
* Processor for highlighting search results.
*/
class SearchApiHighlight extends SearchApiAbstractProcessor {
/**
* PREG regular expression for a word boundary.
*
* We highlight around non-indexable or CJK characters.
*
* @var string
*/
protected static $boundary;
/**
* PREG regular expression for splitting words.
*
* We highlight around non-indexable or CJK characters.
*
* @var string
*/
protected static $split;
/**
* {@inheritdoc}
*/
public function __construct(SearchApiIndex $index, array $options = array()) {
parent::__construct($index, $options);
$cjk = '\x{1100}-\x{11FF}\x{3040}-\x{309F}\x{30A1}-\x{318E}' .
'\x{31A0}-\x{31B7}\x{31F0}-\x{31FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FCF}' .
'\x{A000}-\x{A48F}\x{A4D0}-\x{A4FD}\x{A960}-\x{A97F}\x{AC00}-\x{D7FF}' .
'\x{F900}-\x{FAFF}\x{FF21}-\x{FF3A}\x{FF41}-\x{FF5A}\x{FF66}-\x{FFDC}' .
'\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}';
self::$boundary = '(?:(?<=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . '])|(?=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . ']))';
self::$split = '/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . $cjk . ']+/iu';
}
/**
* {@inheritdoc}
*/
public function configurationForm() {
$this->options += array(
'prefix' => '<strong>',
'suffix' => '</strong>',
'excerpt' => TRUE,
'excerpt_length' => 256,
'highlight' => 'always',
);
$form['prefix'] = array(
'#type' => 'textfield',
'#title' => t('Highlighting prefix'),
'#description' => t('Text/HTML that will be prepended to all occurrences of search keywords in highlighted text.'),
'#default_value' => $this->options['prefix'],
);
$form['suffix'] = array(
'#type' => 'textfield',
'#title' => t('Highlighting suffix'),
'#description' => t('Text/HTML that will be appended to all occurrences of search keywords in highlighted text.'),
'#default_value' => $this->options['suffix'],
);
$form['excerpt'] = array(
'#type' => 'checkbox',
'#title' => t('Create excerpt'),
'#description' => t('When enabled, an excerpt will be created for searches with keywords, containing all occurrences of keywords in a fulltext field.'),
'#default_value' => $this->options['excerpt'],
);
$form['excerpt_length'] = array(
'#type' => 'textfield',
'#title' => t('Excerpt length'),
'#description' => t('The requested length of the excerpt, in characters.'),
'#default_value' => $this->options['excerpt_length'],
'#element_validate' => array('element_validate_integer_positive'),
'#states' => array(
'visible' => array(
'#edit-processors-search-api-highlighting-settings-excerpt' => array(
'checked' => TRUE,
),
),
),
);
$form['highlight'] = array(
'#type' => 'select',
'#title' => t('Highlight returned field data'),
'#description' => t('Select whether returned fields should be highlighted.'),
'#options' => array(
'always' => t('Always'),
'server' => t('If the server returns fields'),
'never' => t('Never'),
),
'#default_value' => $this->options['highlight'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
// Overridden so $form['fields'] is not checked.
}
/**
* {@inheritdoc}
*/
public function postprocessSearchResults(array &$response, SearchApiQuery $query) {
if (!$response['result count'] || !($keys = $this->getKeywords($query))) {
return;
}
foreach ($response['results'] as $id => &$result) {
if ($this->options['excerpt']) {
$text = array();
$fields = $this->getFulltextFields($response['results'], $id);
foreach ($fields as $data) {
if (is_array($data)) {
$text = array_merge($text, $data);
}
else {
$text[] = $data;
}
}
$result['excerpt'] = $this->createExcerpt(implode("\n\n", $text), $keys);
}
if ($this->options['highlight'] != 'never') {
$fields = $this->getFulltextFields($response['results'], $id, $this->options['highlight'] == 'always');
foreach ($fields as $field => $data) {
if (is_array($data)) {
foreach ($data as $i => $text) {
$result['fields'][$field][$i] = $this->highlightField($text, $keys);
}
}
else {
$result['fields'][$field] = $this->highlightField($data, $keys);
}
}
}
}
}
/**
* Retrieves the fulltext data of a result.
*
* @param array $result
* All results returned in the search.
* @param int|string $i
* The index in the results array of the result whose data should be
* returned.
* @param bool $load
* TRUE if the item should be loaded if necessary, FALSE if only fields
* already returned in the results should be used.
*
* @return array
* An array containing fulltext field names mapped to the text data
* contained in them for the given result.
*/
protected function getFulltextFields(array &$results, $i, $load = TRUE) {
$data = array();
// Act as if $load is TRUE if we have a loaded item.
$load |= !empty($result['entity']);
$result = &$results[$i];
$result += array('fields' => array());
$fulltext_fields = $this->index->getFulltextFields();
// We only need detailed fields data if $load is TRUE.
$fields = $load ? $this->index->getFields() : array();
$needs_extraction = array();
foreach ($fulltext_fields as $field) {
if (array_key_exists($field, $result['fields'])) {
$data[$field] = $result['fields'][$field];
}
elseif ($load) {
$needs_extraction[$field] = $fields[$field];
}
}
if (!$needs_extraction) {
return $data;
}
if (empty($result['entity'])) {
$items = $this->index->loadItems(array_keys($results));
foreach ($items as $id => $item) {
$results[$id]['entity'] = $item;
}
}
// If we still don't have a loaded item, we should stop trying.
if (empty($result['entity'])) {
return $data;
}
$wrapper = $this->index->entityWrapper($result['entity'], FALSE);
$extracted = search_api_extract_fields($wrapper, $needs_extraction);
foreach ($extracted as $field => $info) {
if (isset($info['value'])) {
$data[$field] = $info['value'];
}
}
return $data;
}
/**
* Extracts the positive keywords used in a search query.
*
* @param SearchApiQuery $query
* The query from which to extract the keywords.
*
* @return array
* An array of all unique positive keywords used in the query.
*/
protected function getKeywords(SearchApiQuery $query) {
$keys = $query->getKeys();
if (!$keys) {
return array();
}
if (is_array($keys)) {
return $this->flattenKeysArray($keys);
}
$keywords = preg_split(self::$split, $keys);
// Assure there are no duplicates. (This is actually faster than
// array_unique() by a factor of 3 to 4.)
$keywords = drupal_map_assoc(array_filter($keywords));
// Remove quotes from keywords.
foreach ($keywords as $key) {
$keywords[$key] = trim($key, "'\"");
}
return drupal_map_assoc(array_filter($keywords));
}
/**
* Extracts the positive keywords from a keys array.
*
* @param array $keys
* A search keys array, as specified by SearchApiQueryInterface::getKeys().
*
* @return array
* An array of all unique positive keywords contained in the keys.
*/
protected function flattenKeysArray(array $keys) {
if (!empty($keys['#negation'])) {
return array();
}
$keywords = array();
foreach ($keys as $i => $key) {
if (!element_child($i)) {
continue;
}
if (is_array($key)) {
$keywords += $this->flattenKeysArray($key);
}
else {
$keywords[$key] = $key;
}
}
return $keywords;
}
/**
* Returns snippets from a piece of text, with certain keywords highlighted.
*
* Largely copied from search_excerpt().
*
* @param string $text
* The text to extract fragments from.
* @param array $keys
* Search keywords entered by the user.
*
* @return string
* A string containing HTML for the excerpt.
*/
protected function createExcerpt($text, array $keys) {
// Prepare text by stripping HTML tags and decoding HTML entities.
$text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
$text = ' ' . decode_entities($text);
// Extract fragments around keywords.
// First we collect ranges of text around each keyword, starting/ending
// at spaces, trying to get to the requested length.
// If the sum of all fragments is too short, we look for second occurrences.
$ranges = array();
$included = array();
$foundkeys = array();
$length = 0;
$workkeys = $keys;
while ($length < $this->options['excerpt_length'] && count($workkeys)) {
foreach ($workkeys as $k => $key) {
if ($length >= $this->options['excerpt_length']) {
break;
}
// Remember occurrence of key so we can skip over it if more occurrences
// are desired.
if (!isset($included[$key])) {
$included[$key] = 0;
}
// Locate a keyword (position $p, always >0 because $text starts with a
// space).
$p = 0;
if (preg_match('/' . self::$boundary . $key . self::$boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
$p = $match[0][1];
}
// Now locate a space in front (position $q) and behind it (position $s),
// leaving about 60 characters extra before and after for context.
// Note that a space was added to the front and end of $text above.
if ($p) {
if (($q = strpos(' ' . $text, ' ', max(0, $p - 61))) !== FALSE) {
$end = substr($text . ' ', $p, 80);
if (($s = strrpos($end, ' ')) !== FALSE) {
// Account for the added spaces.
$q = max($q - 1, 0);
$s = min($s, strlen($end) - 1);
$ranges[$q] = $p + $s;
$length += $p + $s - $q;
$included[$key] = $p + 1;
}
else {
unset($workkeys[$k]);
}
}
else {
unset($workkeys[$k]);
}
}
else {
unset($workkeys[$k]);
}
}
}
if (count($ranges) == 0) {
// We didn't find any keyword matches, so just return NULL.
return NULL;
}
// Sort the text ranges by starting position.
ksort($ranges);
// Now we collapse overlapping text ranges into one. The sorting makes it O(n).
$newranges = array();
foreach ($ranges as $from2 => $to2) {
if (!isset($from1)) {
$from1 = $from2;
$to1 = $to2;
continue;
}
if ($from2 <= $to1) {
$to1 = max($to1, $to2);
}
else {
$newranges[$from1] = $to1;
$from1 = $from2;
$to1 = $to2;
}
}
$newranges[$from1] = $to1;
// Fetch text
$out = array();
foreach ($newranges as $from => $to) {
$out[] = substr($text, $from, $to - $from);
}
// Let translators have the ... separator text as one chunk.
$dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));
$text = (isset($newranges[0]) ? '' : $dots[0]) . implode($dots[1], $out) . $dots[2];
$text = check_plain($text);
return $this->highlightField($text, $keys);
}
/**
* Marks occurrences of the search keywords in a text field.
*
* @param string $text
* The text of the field.
* @param array $keys
* Search keywords entered by the user.
*
* @return string
* The field's text with all occurrences of search keywords highlighted.
*/
protected function highlightField($text, array $keys) {
$replace = $this->options['prefix'] . '\0' . $this->options['suffix'];
$text = preg_replace('/' . self::$boundary . '(' . implode('|', $keys) . ')' . self::$boundary . '/iu', $replace, ' ' . $text);
return substr($text, 1);
}
}

View File

@@ -6,7 +6,10 @@
class SearchApiIgnoreCase extends SearchApiAbstractProcessor {
protected function process(&$value) {
$value = drupal_strtolower($value);
// We don't touch integers, NULL values or the like.
if (is_string($value)) {
$value = drupal_strtolower($value);
}
}
}

View File

@@ -5,6 +5,13 @@
*/
class SearchApiStopWords extends SearchApiAbstractProcessor {
/**
* Holds all words ignored for the last query.
*
* @var array
*/
protected $ignored = array();
public function configurationForm() {
$form = parent::configurationForm();
@@ -50,7 +57,7 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
public function process(&$value) {
$stopwords = $this->getStopWords();
if (empty($stopwords)) {
if (empty($stopwords) && !is_string($value)) {
return;
}
$words = preg_split('/\s+/', $value);
@@ -63,12 +70,19 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
$value = implode(' ', $words);
}
public function preprocessSearchQuery(SearchApiQuery $query) {
$this->ignored = array();
parent::preprocessSearchQuery($query);
}
public function postprocessSearchResults(array &$response, SearchApiQuery $query) {
if (isset($this->ignored)) {
if ($this->ignored) {
if (isset($response['ignored'])) {
$response['ignored'] = array_merge($response['ignored'], $this->ignored);
}
else $response['ignored'] = $this->ignored;
else {
$response['ignored'] = $this->ignored;
}
}
}

View File

@@ -18,6 +18,17 @@ class SearchApiTokenizer extends SearchApiAbstractProcessor {
public function configurationForm() {
$form = parent::configurationForm();
// Only make fulltext fields available as options.
$fields = $this->index->getFields();
$field_options = array();
foreach ($fields as $name => $field) {
if (empty($field['real_type']) && search_api_is_text_type($field['type'])) {
$field_options[$name] = $field['name'];
}
}
$form['fields']['#options'] = $field_options;
$form += array(
'spaces' => array(
'#type' => 'textfield',
@@ -37,7 +48,7 @@ class SearchApiTokenizer extends SearchApiAbstractProcessor {
);
if (!empty($this->options)) {
$form['spaces']['#default_value'] = $this->options['spaces'];
$form['spaces']['#default_value'] = $this->options['spaces'];
$form['ignorable']['#default_value'] = $this->options['ignorable'];
}
@@ -76,12 +87,15 @@ class SearchApiTokenizer extends SearchApiAbstractProcessor {
}
protected function process(&$value) {
$this->prepare();
if ($this->ignorable) {
$value = preg_replace('/' . $this->ignorable . '+/u', '', $value);
}
if ($this->spaces) {
$value = preg_replace('/' . $this->spaces . '+/u', ' ', $value);
// We don't touch integers, NULL values or the like.
if (is_string($value)) {
$this->prepare();
if ($this->ignorable) {
$value = preg_replace('/' . $this->ignorable . '+/u', '', $value);
}
if ($this->spaces) {
$value = preg_replace('/' . $this->spaces . '+/u', ' ', $value);
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Processor for making searches insensitive to accents and other non-ASCII characters.
*/
class SearchApiTransliteration extends SearchApiAbstractProcessor {
protected function process(&$value) {
// We don't touch integers, NULL values or the like.
if (is_string($value)) {
$value = transliteration_get($value, '', language_default('language'));
}
}
}

View File

@@ -9,7 +9,7 @@
interface SearchApiQueryInterface {
/**
* Constructor used when creating SearchApiQueryInterface objects.
* Constructs a new search query.
*
* @param SearchApiIndex $index
* The index the query should be executed on.
@@ -46,6 +46,8 @@ interface SearchApiQueryInterface {
public function __construct(SearchApiIndex $index, array $options = array());
/**
* Retrieves the parse modes supported by this query class.
*
* @return array
* An associative array of parse modes recognized by objects of this class.
* The keys are the parse modes' ids, values are associative arrays
@@ -56,9 +58,9 @@ interface SearchApiQueryInterface {
public function parseModes();
/**
* Method for creating a filter to use with this query object.
* Creates a new filter to use with this query object.
*
* @param $conjunction
* @param string $conjunction
* The conjunction to use for the filter - either 'AND' or 'OR'.
*
* @return SearchApiQueryFilterInterface
@@ -67,10 +69,12 @@ interface SearchApiQueryInterface {
public function createFilter($conjunction = 'AND');
/**
* Sets the keys to search for. If this method is not called on the query
* before execution, this will be a filter-only query.
* Sets the keys to search for.
*
* @param $keys
* If this method is not called on the query before execution, this will be a
* filter-only query.
*
* @param array|string|null $keys
* A string with the unparsed search keys, or NULL to use no search keys.
*
* @return SearchApiQueryInterface
@@ -79,18 +83,20 @@ interface SearchApiQueryInterface {
public function keys($keys = NULL);
/**
* Sets the fields that will be searched for the search keys. If this is not
* called, all fulltext fields should be searched.
* Sets the fields that will be searched for the search keys.
*
* If this is not called, all fulltext fields will be searched.
*
* @param array $fields
* An array containing fulltext fields that should be searched.
*
* @throws SearchApiException
* If one of the fields isn't of type "text".
*
* @return SearchApiQueryInterface
* The called object.
*
* @throws SearchApiException
* If one of the fields isn't of type "text".
*/
// @todo Allow calling with NULL.
public function fields(array $fields);
/**
@@ -105,13 +111,13 @@ interface SearchApiQueryInterface {
public function filter(SearchApiQueryFilterInterface $filter);
/**
* Add a new ($field $operator $value) condition filter.
* Adds a new ($field $operator $value) condition filter.
*
* @param $field
* @param string $field
* The field to filter on, e.g. 'title'.
* @param $value
* @param mixed $value
* The value the field should have (or be related to by the operator).
* @param $operator
* @param string $operator
* The operator to use for checking the constraint. The following operators
* are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
* have the same semantics as the corresponding SQL operators.
@@ -127,31 +133,34 @@ interface SearchApiQueryInterface {
public function condition($field, $value, $operator = '=');
/**
* Add a sort directive to this search query. If no sort is manually set, the
* results will be sorted descending by relevance.
* Adds a sort directive to this search query.
*
* @param $field
* If no sort is manually set, the results will be sorted descending by
* relevance.
*
* @param string $field
* The field to sort by. The special fields 'search_api_relevance' (sort by
* relevance) and 'search_api_id' (sort by item id) may be used.
* @param $order
* @param string $order
* The order to sort items in - either 'ASC' or 'DESC'.
*
* @throws SearchApiException
* If the field is multi-valued or of a fulltext type.
*
* @return SearchApiQueryInterface
* The called object.
*
* @throws SearchApiException
* If the field is multi-valued or of a fulltext type.
*/
public function sort($field, $order = 'ASC');
/**
* Adds a range of results to return. This will be saved in the query's
* options. If called without parameters, this will remove all range
* restrictions previously set.
* Adds a range of results to return.
*
* @param $offset
* This will be saved in the query's options. If called without parameters,
* this will remove all range restrictions previously set.
*
* @param int|null $offset
* The zero-based offset of the first result returned.
* @param $limit
* @param int|null $limit
* The number of results to return.
*
* @return SearchApiQueryInterface
@@ -206,24 +215,29 @@ interface SearchApiQueryInterface {
public function preExecute();
/**
* Postprocess the search results before they are returned.
* Postprocesses the search results before they are returned.
*
* This method should always be called by execute() and contain all necessary
* operations after the results are returned from the server.
*
* @param array $results
* The results returned by the server, which may be altered.
* The results returned by the server, which may be altered. The data
* structure is the same as returned by execute().
*/
public function postExecute(array &$results);
/**
* Retrieves the index associated with this search.
*
* @return SearchApiIndex
* The search index this query should be executed on.
*/
public function getIndex();
/**
* @return
* Retrieves the search keys for this query.
*
* @return array|string|null
* This object's search keys - either a string or an array specifying a
* complex search expression.
* An array will contain a '#conjunction' key specifying the conjunction
@@ -236,41 +250,59 @@ interface SearchApiQueryInterface {
* the terms in the array should be excluded; with the "OR" conjunction and
* negation, all results containing one or more of the terms in the array
* should be excluded.
*
* @see keys()
*/
public function &getKeys();
/**
* @return
* Retrieves the unparsed search keys for this query as originally entered.
*
* @return array|string|null
* The unprocessed search keys, exactly as passed to this object. Has the
* same format as getKeys().
* same format as the return value of getKeys().
*
* @see keys()
*/
public function getOriginalKeys();
/**
* Retrieves the fulltext fields that will be searched for the search keys.
*
* @return array
* An array containing the fields that should be searched for the search
* keys.
*
* @see fields()
*/
public function &getFields();
/**
* Retrieves the filter object associated with this search query.
*
* @return SearchApiQueryFilterInterface
* This object's associated filter object.
*/
public function getFilter();
/**
* Retrieves the sorts set for this query.
*
* @return array
* An array specifying the sort order for this query. Array keys are the
* field names in order of importance, the values are the respective order
* in which to sort the results according to the field.
*
* @see sort()
*/
public function &getSort();
/**
* @param $name string
* Retrieves an option set on this search query.
*
* @param string $name
* The name of an option.
* @param $default mixed
* @param mixed $default
* The value to return if the specified option is not set.
*
* @return mixed
@@ -279,6 +311,8 @@ interface SearchApiQueryInterface {
public function getOption($name, $default = NULL);
/**
* Sets an option for this search query.
*
* @param string $name
* The name of an option.
* @param mixed $value
@@ -289,6 +323,11 @@ interface SearchApiQueryInterface {
public function setOption($name, $value);
/**
* Retrieves all options set for this search query.
*
* The return value is a reference to the options so they can also be altered
* this way.
*
* @return array
* An associative array of query options.
*/
@@ -297,7 +336,7 @@ interface SearchApiQueryInterface {
}
/**
* Standard implementation of SearchApiQueryInterface.
* Provides a standard implementation of the SearchApiQueryInterface.
*/
class SearchApiQuery implements SearchApiQueryInterface {
@@ -351,44 +390,14 @@ class SearchApiQuery implements SearchApiQueryInterface {
protected $options;
/**
* Count for providing a unique ID.
* Flag for whether preExecute() was already called for this query.
*
* @var bool
*/
protected static $count = 0;
protected $pre_execute = FALSE;
/**
* Constructor for SearchApiQuery objects.
*
* @param SearchApiIndex $index
* The index the query should be executed on.
* @param array $options
* Associative array of options configuring this query. Recognized options
* are:
* - conjunction: The type of conjunction to use for this query - either
* 'AND' or 'OR'. 'AND' by default. This only influences the search keys,
* filters will always use AND by default.
* - 'parse mode': The mode with which to parse the $keys variable, if it
* is set and not already an array. See SearchApiQuery::parseModes() for
* recognized parse modes.
* - languages: The languages to search for, as an array of language IDs.
* If not specified, all languages will be searched. Language-neutral
* content (LANGUAGE_NONE) is always searched.
* - offset: The position of the first returned search results relative to
* the whole result in the index.
* - limit: The maximum number of search results to return. -1 means no
* limit.
* - 'filter class': Can be used to change the SearchApiQueryFilterInterface
* implementation to use.
* - 'search id': A string that will be used as the identifier when storing
* this search in the Search API's static cache.
* - search_api_access_account: The account which will be used for entity
* access checks, if available and enabled for the index.
* - search_api_bypass_access: If set to TRUE, entity access checks will be
* skipped, even if enabled for the index.
* All options are optional. Third-party modules might define and use other
* options not listed here.
*
* @throws SearchApiException
* If a search on that index (or with those options) won't be possible.
* {@inheritdoc}
*/
public function __construct(SearchApiIndex $index, array $options = array()) {
if (empty($index->options['fields'])) {
@@ -415,12 +424,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* @return array
* An associative array of parse modes recognized by objects of this class.
* The keys are the parse modes' ids, values are associative arrays
* containing the following entries:
* - name: The translated name of the parse mode.
* - description: (optional) A translated text describing the parse mode.
* {@inheritdoc}
*/
public function parseModes() {
$modes['direct'] = array(
@@ -442,10 +446,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Parses the keys string according to the $mode parameter.
*
* @return
* The parsed keys. Either a string or an array.
* {@inheritdoc}
*/
protected function parseKeys($keys, $mode) {
if ($keys === NULL || is_array($keys)) {
@@ -460,7 +461,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
return array('#conjunction' => $this->options['conjunction'], $keys);
case 'terms':
$ret = explode(' ', $keys);
$ret = preg_split('/\s+/u', $keys);
$quoted = FALSE;
$str = '';
foreach ($ret as $k => $v) {
@@ -500,13 +501,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Method for creating a filter to use with this query object.
*
* @param $conjunction
* The conjunction to use for the filter - either 'AND' or 'OR'.
*
* @return SearchApiQueryFilterInterface
* A filter object that is set to use the specified conjunction.
* {@inheritdoc}
*/
public function createFilter($conjunction = 'AND') {
$filter_class = $this->options['filter class'];
@@ -514,14 +509,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Sets the keys to search for. If this method is not called on the query
* before execution, this will be a filter-only query.
*
* @param $keys
* A string with the unparsed search keys, or NULL to use no search keys.
*
* @return SearchApiQuery
* The called object.
* {@inheritdoc}
*/
public function keys($keys = NULL) {
$this->orig_keys = $keys;
@@ -534,17 +522,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
return $this;
}
/**
* Sets the fields that will be searched for the search keys. If this is not
* called, all fulltext fields should be searched.
*
* @param array $fields
* An array containing fulltext fields that should be searched.
*
* @throws SearchApiException
* If one of the fields isn't of type "text".
*
* @return SearchApiQueryInterface
* The called object.
* {@inheritdoc}
*/
public function fields(array $fields) {
$fulltext_fields = $this->index->getFulltextFields();
@@ -556,13 +534,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Adds a subfilter to this query's filter.
*
* @param SearchApiQueryFilterInterface $filter
* A SearchApiQueryFilter object that should be added as a subfilter.
*
* @return SearchApiQuery
* The called object.
* {@inheritdoc}
*/
public function filter(SearchApiQueryFilterInterface $filter) {
$this->filter->filter($filter);
@@ -570,24 +542,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Add a new ($field $operator $value) condition filter.
*
* @param $field
* The field to filter on, e.g. 'title'.
* @param $value
* The value the field should have (or be related to by the operator).
* @param $operator
* The operator to use for checking the constraint. The following operators
* are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
* have the same semantics as the corresponding SQL operators.
* If $field is a fulltext field, $operator can only be "=" or "<>", which
* are in this case interpreted as "contains" or "doesn't contain",
* respectively.
* If $value is NULL, $operator also can only be "=" or "<>", meaning the
* field must have no or some value, respectively.
*
* @return SearchApiQuery
* The called object.
* {@inheritdoc}
*/
public function condition($field, $value, $operator = '=') {
$this->filter->condition($field, $value, $operator);
@@ -595,20 +550,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Add a sort directive to this search query. If no sort is manually set, the
* results will be sorted descending by relevance.
*
* @param $field
* The field to sort by. The special fields 'search_api_relevance' (sort by
* relevance) and 'search_api_id' (sort by item id) may be used.
* @param $order
* The order to sort items in - either 'ASC' or 'DESC'.
*
* @throws SearchApiException
* If the field is multi-valued or of a fulltext type.
*
* @return SearchApiQuery
* The called object.
* {@inheritdoc}
*/
public function sort($field, $order = 'ASC') {
$fields = $this->index->options['fields'];
@@ -629,17 +571,7 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Adds a range of results to return. This will be saved in the query's
* options. If called without parameters, this will remove all range
* restrictions previously set.
*
* @param $offset
* The zero-based offset of the first result returned.
* @param $limit
* The number of results to return.
*
* @return SearchApiQueryInterface
* The called object.
* {@inheritdoc}
*/
public function range($offset = NULL, $limit = NULL) {
$this->options['offset'] = $offset;
@@ -649,42 +581,9 @@ class SearchApiQuery implements SearchApiQueryInterface {
/**
* Executes this search query.
*
* @return array
* An associative array containing the search results. The following keys
* are standardized:
* - 'result count': The overall number of results for this query, without
* range restrictions. Might be approximated, for large numbers.
* - results: An array of results, ordered as specified. The array keys are
* the items' IDs, values are arrays containing the following keys:
* - id: The item's ID.
* - score: A float measuring how well the item fits the search.
* - fields: (optional) If set, an array containing some field values
* already ready-to-use. This allows search engines (or postprocessors)
* to store extracted fields so other modules don't have to extract them
* again. This fields should always be checked by modules that want to
* use field contents of the result items.
* - entity: (optional) If set, the fully loaded result item. This field
* should always be used by modules using search results, to avoid
* duplicate item loads.
* - excerpt: (optional) If set, an HTML text containing highlighted
* portions of the fulltext that match the query.
* - warnings: A numeric array of translated warning messages that may be
* displayed to the user.
* - ignored: A numeric array of search keys that were ignored for this
* search (e.g., because of being too short or stop words).
* - performance: An associative array with the time taken (as floats, in
* seconds) for specific parts of the search execution:
* - complete: The complete runtime of the query.
* - hooks: Hook invocations and other client-side preprocessing.
* - preprocessing: Preprocessing of the service class.
* - execution: The actual query to the search server, in whatever form.
* - postprocessing: Preparing the results for returning.
* Additional metadata may be returned in other keys. Only 'result count'
* and 'result' always have to be set, all other entries are optional.
* {@inheritdoc}
*/
public final function execute() {
public function execute() {
$start = microtime(TRUE);
// Prepare the query for execution by the server.
@@ -711,7 +610,12 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Helper method for adding a language filter.
* Adds language filters for the query.
*
* Internal helper function.
*
* @param array $languages
* The languages for which results should be returned.
*/
protected function addLanguages(array $languages) {
if (array_search(LANGUAGE_NONE, $languages) === FALSE) {
@@ -755,37 +659,32 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* Prepares the query object for the search.
*
* This method should always be called by execute() and contain all necessary
* operations before the query is passed to the server's search() method.
* {@inheritdoc}
*/
public function preExecute() {
// Add filter for languages.
if (isset($this->options['languages'])) {
$this->addLanguages($this->options['languages']);
// Make sure to only execute this once per query.
if (!$this->pre_execute) {
$this->pre_execute = TRUE;
// Add filter for languages.
if (isset($this->options['languages'])) {
$this->addLanguages($this->options['languages']);
}
// Add fulltext fields, unless set
if ($this->fields === NULL) {
$this->fields = $this->index->getFulltextFields();
}
// Preprocess query.
$this->index->preprocessSearchQuery($this);
// Let modules alter the query.
drupal_alter('search_api_query', $this);
}
// Add fulltext fields, unless set
if ($this->fields === NULL) {
$this->fields = $this->index->getFulltextFields();
}
// Preprocess query.
$this->index->preprocessSearchQuery($this);
// Let modules alter the query.
drupal_alter('search_api_query', $this);
}
/**
* Postprocess the search results before they are returned.
*
* This method should always be called by execute() and contain all necessary
* operations after the results are returned from the server.
*
* @param array $results
* The results returned by the server, which may be altered.
* {@inheritdoc}
*/
public function postExecute(array &$results) {
// Postprocess results.
@@ -793,86 +692,56 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* @return SearchApiIndex
* The search index this query will be executed on.
* {@inheritdoc}
*/
public function getIndex() {
return $this->index;
}
/**
* @return
* This object's search keys - either a string or an array specifying a
* complex search expression.
* An array will contain a '#conjunction' key specifying the conjunction
* type, and search strings or nested expression arrays at numeric keys.
* Additionally, a '#negation' key might be present, which means unless it
* maps to a FALSE value that the search keys contained in that array
* should be negated, i.e. not be present in returned results. The negation
* works on the whole array, not on each contained term individually i.e.,
* with the "AND" conjunction and negation, only results that contain all
* the terms in the array should be excluded; with the "OR" conjunction and
* negation, all results containing one or more of the terms in the array
* should be excluded.
* {@inheritdoc}
*/
public function &getKeys() {
return $this->keys;
}
/**
* @return
* The unprocessed search keys, exactly as passed to this object. Has the
* same format as getKeys().
* {@inheritdoc}
*/
public function getOriginalKeys() {
return $this->orig_keys;
}
/**
* @return array
* An array containing the fields that should be searched for the search
* keys.
* {@inheritdoc}
*/
public function &getFields() {
return $this->fields;
}
/**
* @return SearchApiQueryFilterInterface
* This object's associated filter object.
* {@inheritdoc}
*/
public function getFilter() {
return $this->filter;
}
/**
* @return array
* An array specifying the sort order for this query. Array keys are the
* field names in order of importance, the values are the respective order
* in which to sort the results according to the field.
* {@inheritdoc}
*/
public function &getSort() {
return $this->sort;
}
/**
* @param $name string
* The name of an option.
*
* @return mixed
* The option with the specified name, if set, or NULL otherwise.
* {@inheritdoc}
*/
public function getOption($name, $default = NULL) {
return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
}
/**
* @param string $name
* The name of an option.
* @param mixed $value
* The new value of the option.
*
* @return The option's previous value.
* {@inheritdoc}
*/
public function setOption($name, $value) {
$old = $this->getOption($name);
@@ -881,28 +750,46 @@ class SearchApiQuery implements SearchApiQueryInterface {
}
/**
* @return array
* An associative array of query options.
* {@inheritdoc}
*/
public function &getOptions() {
return $this->options;
}
/**
* Implements the magic __sleep() method to avoid serializing the index.
*/
public function __sleep() {
$this->index_id = $this->index->machine_name;
$keys = get_object_vars($this);
unset($keys['index']);
return array_keys($keys);
}
/**
* Implements the magic __wakeup() method to reload the query's index.
*/
public function __wakeup() {
if (!isset($this->index) && !empty($this->index_id)) {
$this->index = search_api_index_load($this->index_id);
unset($this->index_id);
}
}
}
/**
* Interface representing a search query filter, that filters on one or more
* fields with a specific conjunction (AND or OR).
* Represents a filter on a search query.
*
* Methods not noting otherwise will return the object itself, so calls can be
* chained.
* Filters apply conditions on one or more fields with a specific conjunction
* (AND or OR) and may contain nested filters.
*/
interface SearchApiQueryFilterInterface {
/**
* Constructs a new filter that uses the specified conjunction.
*
* @param $conjunction
* @param string $conjunction
* The conjunction to use for this filter - either 'AND' or 'OR'.
*/
public function __construct($conjunction = 'AND');
@@ -910,7 +797,7 @@ interface SearchApiQueryFilterInterface {
/**
* Sets this filter's conjunction.
*
* @param $conjunction
* @param string $conjunction
* The conjunction to use for this filter - either 'AND' or 'OR'.
*
* @return SearchApiQueryFilterInterface
@@ -921,7 +808,7 @@ interface SearchApiQueryFilterInterface {
/**
* Adds a subfilter.
*
* @param $filter
* @param SearchApiQueryFilterInterface $filter
* A SearchApiQueryFilterInterface object that should be added as a
* subfilter.
*
@@ -931,13 +818,13 @@ interface SearchApiQueryFilterInterface {
public function filter(SearchApiQueryFilterInterface $filter);
/**
* Add a new ($field $operator $value) condition.
* Adds a new ($field $operator $value) condition.
*
* @param $field
* @param string $field
* The field to filter on, e.g. 'title'.
* @param $value
* @param mixed $value
* The value the field should have (or be related to by the operator).
* @param $operator
* @param string $operator
* The operator to use for checking the constraint. The following operators
* are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
* have the same semantics as the corresponding SQL operators.
@@ -953,12 +840,16 @@ interface SearchApiQueryFilterInterface {
public function condition($field, $value, $operator = '=');
/**
* @return
* Retrieves the conjunction used by this filter.
*
* @return string
* The conjunction used by this filter - either 'AND' or 'OR'.
*/
public function getConjunction();
/**
* Return all conditions and nested filters contained in this filter.
*
* @return array
* An array containing this filter's subfilters. Each of these is either an
* array (field, value, operator), or another SearchApiFilter object.
@@ -968,24 +859,29 @@ interface SearchApiQueryFilterInterface {
}
/**
* Standard implementation of SearchApiQueryFilterInterface.
* Provides a standard implementation of SearchApiQueryFilterInterface.
*/
class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
/**
* Array containing subfilters. Each of these is either an array
* (field, value, operator), or another SearchApiFilter object.
* Array containing subfilters.
*
* Each of these is either an array (field, value, operator), or another
* SearchApiFilter object.
*
* @var array
*/
protected $filters;
/** String specifying this filter's conjunction ('AND' or 'OR'). */
/**
* String specifying this filter's conjunction ('AND' or 'OR').
*
* @var string
*/
protected $conjunction;
/**
* Constructs a new filter that uses the specified conjunction.
*
* @param $conjunction
* The conjunction to use for this filter - either 'AND' or 'OR'.
* {@inheritdoc}
*/
public function __construct($conjunction = 'AND') {
$this->setConjunction($conjunction);
@@ -993,13 +889,7 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
}
/**
* Sets this filter's conjunction.
*
* @param $conjunction
* The conjunction to use for this filter - either 'AND' or 'OR'.
*
* @return SearchApiQueryFilter
* The called object.
* {@inheritdoc}
*/
public function setConjunction($conjunction) {
$this->conjunction = strtoupper(trim($conjunction)) == 'OR' ? 'OR' : 'AND';
@@ -1007,14 +897,7 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
}
/**
* Adds a subfilter.
*
* @param $filter
* A SearchApiQueryFilterInterface object that should be added as a
* subfilter.
*
* @return SearchApiQueryFilter
* The called object.
* {@inheritdoc}
*/
public function filter(SearchApiQueryFilterInterface $filter) {
$this->filters[] = $filter;
@@ -1022,24 +905,7 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
}
/**
* Add a new ($field $operator $value) condition.
*
* @param $field
* The field to filter on, e.g. 'title'.
* @param $value
* The value the field should have (or be related to by the operator).
* @param $operator
* The operator to use for checking the constraint. The following operators
* are supported for primitive types: "=", "<>", "<", "<=", ">=", ">". They
* have the same semantics as the corresponding SQL operators.
* If $field is a fulltext field, $operator can only be "=" or "<>", which
* are in this case interpreted as "contains" or "doesn't contain",
* respectively.
* If $value is NULL, $operator also can only be "=" or "<>", meaning the
* field must have no or some value, respectively.
*
* @return SearchApiQueryFilter
* The called object.
* {@inheritdoc}
*/
public function condition($field, $value, $operator = '=') {
$this->filters[] = array($field, $value, $operator);
@@ -1047,17 +913,14 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
}
/**
* @return
* The conjunction used by this filter - either 'AND' or 'OR'.
* {@inheritdoc}
*/
public function getConjunction() {
return $this->conjunction;
}
/**
* @return array
* An array containing this filter's subfilters. Each of these is either an
* array (field, value, operator), or another SearchApiFilter object.
* {@inheritdoc}
*/
public function &getFilters() {
return $this->filters;

View File

@@ -9,8 +9,9 @@
interface SearchApiServiceInterface {
/**
* Constructor for a service class, setting the server configuration used with
* this service.
* Constructs a service object.
*
* This will set the server configuration used with this service.
*
* @param SearchApiServer $server
* The server object for this service.
@@ -57,9 +58,10 @@ interface SearchApiServiceInterface {
public function configurationFormSubmit(array $form, array &$values, array &$form_state);
/**
* Determines whether this service class implementation supports a given
* feature. Features are optional extensions to Search API functionality and
* usually defined and used by third-party modules.
* Determines whether this service class supports a given feature.
*
* Features are optional extensions to Search API functionality and usually
* defined and used by third-party modules.
*
* There are currently three features defined directly in the Search API
* project:
@@ -72,7 +74,7 @@ interface SearchApiServiceInterface {
* @param string $feature
* The name of the optional feature.
*
* @return boolean
* @return bool
* TRUE if this service knows and supports the specified feature. FALSE
* otherwise.
*/
@@ -121,15 +123,15 @@ interface SearchApiServiceInterface {
public function addIndex(SearchApiIndex $index);
/**
* Notify the server that the indexed field settings for the index have
* changed.
* Notify the server that the field settings for the index have changed.
*
* If any user action is necessary as a result of this, the method should
* use drupal_set_message() to notify the user.
*
* @param SearchApiIndex $index
* The updated index.
*
* @return
* @return bool
* TRUE, if this change affected the server in any way that forces it to
* re-index the content. FALSE otherwise.
*/
@@ -144,6 +146,9 @@ interface SearchApiServiceInterface {
*
* If the index wasn't added to the server, the method call should be ignored.
*
* Implementations of this method should also check whether $index->read_only
* is set, and don't delete any indexed data if it is.
*
* @param $index
* Either an object representing the index to remove, or its machine name
* (if the index was completely deleted).
@@ -234,6 +239,10 @@ interface SearchApiServiceInterface {
/**
* Abstract class with generic implementation of most service methods.
*
* For creating your own service class extending this class, you only need to
* implement indexItems(), deleteItems() and search() from the
* SearchApiServiceInterface interface.
*/
abstract class SearchApiAbstractService implements SearchApiServiceInterface {
@@ -250,13 +259,9 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
protected $options = array();
/**
* Constructor for a service class, setting the server configuration used with
* this service.
* Implements SearchApiServiceInterface::__construct().
*
* The default implementation sets $this->server and $this->options.
*
* @param SearchApiServer $server
* The server object for this service.
*/
public function __construct(SearchApiServer $server) {
$this->server = $server;
@@ -264,46 +269,28 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
}
/**
* Form callback. Might be called on an uninitialized object - in this case,
* the form is for configuring a newly created server.
* Implements SearchApiServiceInterface::__construct().
*
* Returns an empty form by default.
*
* @return array
* A form array for setting service-specific options.
*/
public function configurationForm(array $form, array &$form_state) {
return array();
}
/**
* Validation callback for the form returned by configurationForm().
* Implements SearchApiServiceInterface::__construct().
*
* Does nothing by default.
*
* @param array $form
* The form returned by configurationForm().
* @param array $values
* The part of the $form_state['values'] array corresponding to this form.
* @param array $form_state
* The complete form state.
*/
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
return;
}
/**
* Submit callback for the form returned by configurationForm().
* Implements SearchApiServiceInterface::__construct().
*
* The default implementation just ensures that additional elements in
* $options, not present in the form, don't get lost at the update.
*
* @param array $form
* The form returned by configurationForm().
* @param array $values
* The part of the $form_state['values'] array corresponding to this form.
* @param array $form_state
* The complete form state.
*/
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
if (!empty($this->options)) {
@@ -313,32 +300,16 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
}
/**
* Determines whether this service class implementation supports a given
* feature. Features are optional extensions to Search API functionality and
* usually defined and used by third-party modules.
* Implements SearchApiServiceInterface::__construct().
*
* There are currently three features defined directly in the Search API
* project:
* - "search_api_facets", by the search_api_facetapi module.
* - "search_api_facets_operator_or", also by the search_api_facetapi module.
* - "search_api_mlt", by the search_api_views module.
* Other contrib modules might define additional features. These should always
* be properly documented in the module by which they are defined.
*
* @param string $feature
* The name of the optional feature.
*
* @return boolean
* TRUE if this service knows and supports the specified feature. FALSE
* otherwise.
* The default implementation always returns FALSE.
*/
public function supportsFeature($feature) {
return FALSE;
}
/**
* View this server's settings. Output can be HTML or a render array, a <dl>
* listing all relevant settings is preferred.
* Implements SearchApiServiceInterface::__construct().
*
* The default implementation does a crude output as a definition list, with
* option names taken from the configuration form.
@@ -364,8 +335,7 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
}
/**
* Called once, when the server is first created. Allows it to set up its
* necessary infrastructure.
* Implements SearchApiServiceInterface::__construct().
*
* Does nothing, by default.
*/
@@ -374,23 +344,16 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
}
/**
* Notifies this server that its fields are about to be updated. The server's
* $original property can be used to inspect the old property values.
* Implements SearchApiServiceInterface::__construct().
*
* @return
* TRUE, if the update requires reindexing of all content on the server.
* The default implementation always returns FALSE.
*/
public function postUpdate() {
return FALSE;
}
/**
* Notifies this server that it is about to be deleted from the database and
* should therefore clean up, if appropriate.
*
* Note that you shouldn't call the server's save() method, or any
* methods that might do that, from inside of this method as the server isn't
* present in the database anymore at this point.
* Implements SearchApiServiceInterface::__construct().
*
* By default, deletes all indexes from this server.
*/
@@ -402,65 +365,38 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
}
/**
* Add a new index to this server.
* Implements SearchApiServiceInterface::__construct().
*
* Does nothing, by default.
*
* @param SearchApiIndex $index
* The index to add.
*/
public function addIndex(SearchApiIndex $index) {
return;
}
/**
* Notify the server that the indexed field settings for the index have
* changed.
* If any user action is necessary as a result of this, the method should
* use drupal_set_message() to notify the user.
* Implements SearchApiServiceInterface::__construct().
*
* @param SearchApiIndex $index
* The updated index.
*
* @return
* TRUE, if this change affected the server in any way that forces it to
* re-index the content. FALSE otherwise.
* The default implementation always returns FALSE.
*/
public function fieldsUpdated(SearchApiIndex $index) {
return FALSE;
}
/**
* Remove an index from this server.
*
* This might mean that the index has been deleted, or reassigned to a
* different server. If you need to distinguish between these cases, inspect
* $index->server.
* Implements SearchApiServiceInterface::__construct().
*
* By default, removes all items from that index.
*
* @param $index
* Either an object representing the index to remove, or its machine name
* (if the index was completely deleted).
*/
public function removeIndex($index) {
$this->deleteItems('all', $index);
if (is_object($index) && empty($index->read_only)) {
$this->deleteItems('all', $index);
}
}
/**
* Create a query object for searching on an index lying on this server.
* Implements SearchApiServiceInterface::__construct().
*
* @param SearchApiIndex $index
* The index to search on.
* @param $options
* Associative array of options configuring this query. See
* SearchApiQueryInterface::__construct().
*
* @return SearchApiQueryInterface
* An object for searching the given index.
*
* @throws SearchApiException
* If the server is currently disabled.
* The default implementation returns a SearchApiQuery object.
*/
public function query(SearchApiIndex $index, $options = array()) {
return new SearchApiQuery($index, $options);