upadted to 1.8
This commit is contained in:
@@ -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();
|
||||
|
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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(),
|
||||
|
@@ -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) {
|
||||
|
@@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
65
includes/callback_role_filter.inc
Normal file
65
includes/callback_role_filter.inc
Normal 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
89
includes/datasource.inc
Executable file → Normal 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);
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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(
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
401
includes/processor_highlight.inc
Normal file
401
includes/processor_highlight.inc
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
15
includes/processor_transliteration.inc
Normal file
15
includes/processor_transliteration.inc
Normal 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'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user