updated to 7.x-1.11
This commit is contained in:
parent
a30917d1d2
commit
cf03e9ca52
@ -1,5 +1,88 @@
|
||||
Search API 1.x, dev (xx/xx/xxxx):
|
||||
---------------------------------
|
||||
Search API 1.11 (12/25/2013):
|
||||
-----------------------------
|
||||
- #1879196 by drunken monkey: Fixed invalid old indexes causing errors.
|
||||
- #2155127 by drunken monkey: Clarified the scope of the "Node access" and
|
||||
"Exclude unpublished nodes" data alterations.
|
||||
- #2155575 by drunken monkey: Fixed incorrect "Server index status" warnings.
|
||||
- #2159011 by idebr, drunken monkey: Fixed highlighting of keywords with PCRE
|
||||
special characters.
|
||||
- #2155721 by rjacobs, drunken monkey: Added support for Views' get_total_rows
|
||||
property.
|
||||
- #2158873 by drumm, drunken monkey: Fixed "all of" operator of Views entity
|
||||
filter handler.
|
||||
- #2156021 by jgullstr: Fixed confirm message when disabling servers.
|
||||
- #2146435 by timkang: Fixed Views paging with custom pager add-ons.
|
||||
- #2150347 by drunken monkey: Added access callbacks for indexes and servers.
|
||||
|
||||
Search API 1.10 (12/09/2013):
|
||||
-----------------------------
|
||||
- #2130819 by drunken monkey, Bojhan: Added UI improvements for the "View" tabs.
|
||||
- #2152327 by sirtet, miro_dietiker: Fixed typo in help text for drush sapi-c.
|
||||
- #2144531 by drunken monkey: Fixed cloning of queries to clone filters, too.
|
||||
- #2100671 by drunken monkey: Fixed stopwords processor to ignore missing
|
||||
stopwords.
|
||||
- #2139239 by drunken monkey: Fixed highlighting for the last word of a field.
|
||||
- #1925114 by azinck: Fixed Views Facet Block integration with Panels.
|
||||
- #2139215 by drunken monkey: Fixed $context parameter of batch callback.
|
||||
- #2143659 by khiminrm: Fixed typo in update function 7116.
|
||||
- #2134509 by kscheirer, drunken monkey: Removed unused variables and
|
||||
parameters.
|
||||
- #2136019 by drunken monkey: Fixed mapping callback for taxonomy term facets.
|
||||
- #2128001 by drunken monkey: Fixed the logic of the "contains none of these
|
||||
words" fulltext operator.
|
||||
- #2128947 by stBorchert, drunken monkey: Fixed facet handling for multiple
|
||||
searches on a page.
|
||||
- #2128529 by Frando, drunken monkey: Added a way for facet query type plugins
|
||||
to pass options to the search query.
|
||||
- #1551302 by drunken monkey: Fixed the server tasks system.
|
||||
- #2135363 by drumm, drunken monkey: Added support for Views' use_count_query()
|
||||
method.
|
||||
- #1390598 by Damien Tournoud, drunken monkey: Added the concept of query filter
|
||||
tags.
|
||||
- #2135255 by dww: Fixed missing pager on first page of search results.
|
||||
- #1832334 by Damien Tournoud, drunken monkey: Fixed performance issues of
|
||||
Views options filter handler for huge options lists.
|
||||
- #2118589 by mxr576, drunken monkey: Added node access for comment indexes.
|
||||
- #1961120 by drunken monkey: Fixed Views handling of short fulltext keywords.
|
||||
- #2100231 by drunken monkey: Renamed "Workflow" tab to "Filters".
|
||||
- #2100193 by drunken monkey: Turned operations in overview into D8 dropbuttons.
|
||||
- #2100199 by drunken monkey: Merged index tabs for a cleaner look.
|
||||
- #2115127 by drunken monkey: Fixed cron indexing logic to keep the right order.
|
||||
- #1750144 by jsacksick, drunken monkey: Fixed missing Boost option for custom
|
||||
fulltext field types.
|
||||
- #1956650 by drunken monkey, wwhurley: Fixed trackItemChange not checking for
|
||||
empty $item_ids.
|
||||
- #2100191 by drunken monkey, Bojhan: Added an admin description to the Search
|
||||
API landing page.
|
||||
|
||||
Search API 1.9 (10/23/2013):
|
||||
----------------------------
|
||||
- #2113277 by moonray, drunken monkey: Fixed date facet count for active item.
|
||||
- #2086783 by drunken monkey: Removed Views field handlers for "virtual" fields.
|
||||
- #2114593 by drunken monkey: Added list of floats to test module.
|
||||
- #2109247 by mmikitka, drunken monkey: Exposed the status and module
|
||||
properties to Entity API.
|
||||
- #2091499 by sammys, drunken monkey: Added Views contextual filter handler for
|
||||
dates.
|
||||
- #2109537 by hefox, drunken monkey: Added alter hooks for workflow plugin
|
||||
definitions.
|
||||
- #2102111 by sergei_brill: Added hook_search_api_views_query_alter().
|
||||
- #2110315 by drumm, drunken monkey: Added specialized Views filters for users
|
||||
and terms.
|
||||
- #2111273 by drunken monkey: Fixed Javascript states for exposed filter
|
||||
operator.
|
||||
- #2102353 by aaronbauman: Fixed "smaller than" to read "less than".
|
||||
- #2097559 by thijsvdanker: Fixed the language of created search excerpts.
|
||||
- #2096275 by andrewbelcher: Fixed calling of Views pager pre/post execute
|
||||
callbacks.
|
||||
- #2093023 by maciej.zgadzaj: Added Drush commands to enable and disable
|
||||
indexes.
|
||||
- #2088905 by queenvictoria, drunken monkey: Fixed handling of Views
|
||||
override_path option.
|
||||
- #2083481 by drunken monkey, nickgs: Added "exclude" option for facets.
|
||||
- #2084953 by Yaron Tal: Fixed issue with theme initialization.
|
||||
- #2075839 by leeomara, drunken monkey: Added descriptions to field lists for
|
||||
'Aggregated Fields'.
|
||||
|
||||
Search API 1.8 (09/01/2013):
|
||||
----------------------------
|
||||
|
12
README.txt
12
README.txt
@ -90,7 +90,7 @@ IMPORTANT: Access checks
|
||||
results are displayed – either by only indexing such items, or by filtering
|
||||
appropriately at search time.
|
||||
For search on general site content (item type "Node"), this is already
|
||||
supported by the Search API. To enable this, go to the index's "Workflow" tab
|
||||
supported by the Search API. To enable this, go to the index's "Filters" tab
|
||||
and activate the "Node access" data alteration. This will add the necessary
|
||||
field, "Node access information", to the index (which you have to leave as
|
||||
"indexed"). If both this field and "Published" are set to be indexed, access
|
||||
@ -171,8 +171,8 @@ form at the bottom of the page. For instance, you might want to index the
|
||||
author's username to the indexed data of a node, and you need to add the "Body"
|
||||
entity to the node when you want to index the actual text it contains.
|
||||
|
||||
- Index workflow
|
||||
(Configuration > Search API > [Index name] > Workflow)
|
||||
- Indexing workflow
|
||||
(Configuration > Search API > [Index name] > Filters)
|
||||
|
||||
This page lets you customize how the created index works, and what metadata will
|
||||
be available, by selecting data alterations and processors (see the glossary for
|
||||
@ -210,12 +210,6 @@ search_api_index_worker_callback_runtime:
|
||||
API will spend indexing (for all indexes combined) in each cron run. The
|
||||
default is 15 seconds.
|
||||
|
||||
search_api_batch_per_cron:
|
||||
By changing this variable, you can define how many batch items are created on
|
||||
a single cron run. The value is per index, so on a site with 5 indexes with a
|
||||
cron limit of 100 each, the default value of 10 will load and queue up to 5000
|
||||
search items in up to 50 batch items.
|
||||
|
||||
|
||||
Information for developers
|
||||
--------------------------
|
||||
|
39
boosts-and-queryconditon.patch
Normal file
39
boosts-and-queryconditon.patch
Normal file
@ -0,0 +1,39 @@
|
||||
diff --git a/search_api.admin.inc b/search_api.admin.inc
|
||||
index 5fbc8d8..9a5122e 100644
|
||||
--- a/search_api.admin.inc
|
||||
+++ b/search_api.admin.inc
|
||||
@@ -1480,8 +1480,8 @@ function search_api_admin_index_fields(array $form, array &$form_state, SearchAp
|
||||
$fulltext_type = array(0 => 'text');
|
||||
$entity_types = entity_get_info();
|
||||
$default_types = search_api_default_field_types();
|
||||
- $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0'));
|
||||
-
|
||||
+ // $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0', '5000', '10000', '20000', '40000', '80000', '160000', '320000'));
|
||||
+ $boosts = drupal_map_assoc(array('0.1', '0.2', '0.3', '0.5', '0.8', '1.0', '2.0', '3.0', '5.0', '8.0', '13.0', '21.0', '100', '1000', '1010', '1020', '1030', '1040', '1050', '1060'));
|
||||
$form_state['index'] = $index;
|
||||
$form['#theme'] = 'search_api_admin_fields_table';
|
||||
$form['#tree'] = TRUE;
|
||||
diff --git a/search_api.module b/search_api.module
|
||||
index bba0681..ba27465 100644
|
||||
--- a/search_api.module
|
||||
+++ b/search_api.module
|
||||
@@ -1444,7 +1444,7 @@ function _search_api_query_add_node_access($account, SearchApiQueryInterface $qu
|
||||
$query->filter($filter);
|
||||
}
|
||||
else {
|
||||
- $query->condition('status', NODE_PUBLISHED);
|
||||
+ // $query->condition('status', NODE_PUBLISHED);
|
||||
}
|
||||
// Filter by node access grants.
|
||||
$filter = $query->createFilter('OR');
|
||||
@@ -1636,6 +1636,10 @@ function search_api_extract_fields(EntityMetadataWrapper $wrapper, array $fields
|
||||
foreach ($nested as $prefix => $nested_fields) {
|
||||
if (isset($wrapper->$prefix)) {
|
||||
$nested_fields = search_api_extract_fields($wrapper->$prefix, $nested_fields, $value_options);
|
||||
+ # http://drupal.org/node/1873910#comment-6876200
|
||||
+ // $subwrapper = $wrapper->$prefix;
|
||||
+ // $subwrapper->language( $wrapper->language->value() );
|
||||
+ // $nested_fields = search_api_extract_fields($subwrapper, $nested_fields, $value_options);
|
||||
foreach ($nested_fields as $field => $info) {
|
||||
$fields["$prefix:$field"] = $info;
|
||||
}
|
@ -109,7 +109,12 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
public function addFacet(array $facet, SearchApiQueryInterface $query) {
|
||||
if (isset($this->fields[$facet['name']])) {
|
||||
$options = &$query->getOptions();
|
||||
$options['search_api_facets'][$facet['name']] = $this->fields[$facet['name']];
|
||||
$facet_info = $this->fields[$facet['name']];
|
||||
if (!empty($facet['query_options'])) {
|
||||
// Let facet-specific query options override the set options.
|
||||
$facet_info = $facet['query_options'] + $facet_info;
|
||||
}
|
||||
$options['search_api_facets'][$facet['name']] = $facet_info;
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +144,7 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
// I suspect that http://drupal.org/node/593658 would help.
|
||||
// For now, just taking the first current search for this index. :-/
|
||||
foreach (search_api_current_search() as $search) {
|
||||
list($query, $results) = $search;
|
||||
list($query) = $search;
|
||||
if ($query->getIndex()->machine_name == $index_id) {
|
||||
$this->current_search = $search;
|
||||
}
|
||||
@ -196,7 +201,6 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
*/
|
||||
public function settingsForm(&$form, &$form_state) {
|
||||
$facet = $form['#facetapi']['facet'];
|
||||
$realm = $form['#facetapi']['realm'];
|
||||
$facet_settings = $this->getFacet($facet)->getSettings();
|
||||
$options = $facet_settings->settings;
|
||||
$search_ids = variable_get('search_api_facets_search_ids', array());
|
||||
@ -205,6 +209,7 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
$form['global']['default_true'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Display for searches'),
|
||||
'#prefix' => '<div class="facetapi-global-setting">',
|
||||
'#options' => array(
|
||||
TRUE => t('For all except the selected'),
|
||||
FALSE => t('Only for the selected'),
|
||||
@ -214,6 +219,7 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
$form['global']['facet_search_ids'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Search IDs'),
|
||||
'#suffix' => '</div>',
|
||||
'#options' => $search_ids,
|
||||
'#size' => min(4, count($search_ids)),
|
||||
'#multiple' => TRUE,
|
||||
@ -246,9 +252,25 @@ class SearchApiFacetapiAdapter extends FacetapiAdapter {
|
||||
'#type' => 'select',
|
||||
'#title' => t('Granularity'),
|
||||
'#description' => t('Determine the maximum drill-down level'),
|
||||
'#prefix' => '<div class="facetapi-global-setting">',
|
||||
'#suffix' => '</div>',
|
||||
'#options' => $granularity_options,
|
||||
'#default_value' => isset($options['date_granularity']) ? $options['date_granularity'] : FACETAPI_DATE_MINUTE,
|
||||
);
|
||||
}
|
||||
|
||||
// Add an "Exclude" option for terms.
|
||||
if(!empty($facet['query types']) && in_array('term', $facet['query types'])) {
|
||||
$form['global']['operator']['#weight'] = -2;
|
||||
unset($form['global']['operator']['#suffix']);
|
||||
$form['global']['exclude'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Exclude'),
|
||||
'#description' => t('Make the search exclude selected facets, instead of restricting it to them.'),
|
||||
'#suffix' => '</div>',
|
||||
'#weight' => -1,
|
||||
'#default_value' => !empty($options['exclude']),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,17 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
public function execute($query) {
|
||||
// Return terms for this facet.
|
||||
$this->adapter->addFacet($this->facet, $query);
|
||||
|
||||
$settings = $this->adapter->getFacet($this->facet)->getSettings()->settings;
|
||||
|
||||
// First check if the facet is enabled for this search.
|
||||
$default_true = isset($settings['default_true']) ? $settings['default_true'] : TRUE;
|
||||
$facet_search_ids = isset($settings['facet_search_ids']) ? $settings['facet_search_ids'] : array();
|
||||
if ($default_true != empty($facet_search_ids[$query->getOption('search id')])) {
|
||||
// Facet is not enabled for this search ID.
|
||||
return;
|
||||
}
|
||||
|
||||
// Change limit to "unlimited" (-1).
|
||||
$options = &$query->getOptions();
|
||||
if (!empty($options['search_api_facets'][$this->facet['name']])) {
|
||||
@ -121,7 +132,8 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
|
||||
// Gets active facets, starts building hierarchy.
|
||||
$parent = $gap = NULL;
|
||||
foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) {
|
||||
$active_items = $this->adapter->getActiveItems($this->facet);
|
||||
foreach ($active_items as $value => $item) {
|
||||
// If the item is active, the count is the result set count.
|
||||
$build[$value] = array('#count' => $total);
|
||||
|
||||
@ -199,7 +211,9 @@ class SearchApiFacetapiDate extends SearchApiFacetapiTerm implements FacetapiQue
|
||||
if (!isset($build[$new_value])) {
|
||||
$build[$new_value] = array('#count' => $count);
|
||||
}
|
||||
else {
|
||||
// Active items already have their value set because it's the current
|
||||
// result count.
|
||||
elseif (!isset($active_items[$new_value])) {
|
||||
$build[$new_value]['#count'] += $count;
|
||||
}
|
||||
|
||||
|
@ -30,53 +30,66 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
|
||||
// Return terms for this facet.
|
||||
$this->adapter->addFacet($this->facet, $query);
|
||||
|
||||
$settings = $this->adapter->getFacet($this->facet)->getSettings();
|
||||
// Adds the operator parameter.
|
||||
$operator = $settings->settings['operator'];
|
||||
$settings = $this->adapter->getFacet($this->facet)->getSettings()->settings;
|
||||
|
||||
// Add active facet filters.
|
||||
// First check if the facet is enabled for this search.
|
||||
$default_true = isset($settings['default_true']) ? $settings['default_true'] : TRUE;
|
||||
$facet_search_ids = isset($settings['facet_search_ids']) ? $settings['facet_search_ids'] : array();
|
||||
if ($default_true != empty($facet_search_ids[$query->getOption('search id')])) {
|
||||
// Facet is not enabled for this search ID.
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the active facet filters.
|
||||
$active = $this->adapter->getActiveItems($this->facet);
|
||||
if (empty($active)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FACETAPI_OPERATOR_OR == $operator) {
|
||||
// If we're dealing with an OR facet, we need to use a nested filter.
|
||||
$facet_filter = $query->createFilter('OR');
|
||||
// Create the facet filter, and add a tag to it so that it can be easily
|
||||
// identified down the line by services when they need to exclude facets.
|
||||
$operator = $settings['operator'];
|
||||
if ($operator == FACETAPI_OPERATOR_AND) {
|
||||
$conjunction = 'AND';
|
||||
}
|
||||
elseif ($operator == FACETAPI_OPERATOR_OR) {
|
||||
$conjunction = 'OR';
|
||||
}
|
||||
else {
|
||||
// Otherwise we set the conditions directly on the query.
|
||||
$facet_filter = $query;
|
||||
throw new SearchApiException(t('Unknown facet operator %operator.', array('%operator' => $operator)));
|
||||
}
|
||||
$tags = array('facet:' . $this->facet['field']);
|
||||
$facet_filter = $query->createFilter($conjunction, $tags);
|
||||
|
||||
foreach ($active as $filter => $filter_array) {
|
||||
$field = $this->facet['field'];
|
||||
$this->addFacetFilter($facet_filter, $field, $filter);
|
||||
}
|
||||
|
||||
// For OR facets, we now have to add the filter to the query.
|
||||
if (FACETAPI_OPERATOR_OR == $operator) {
|
||||
$query->filter($facet_filter);
|
||||
}
|
||||
// Now add the filter to the query.
|
||||
$query->filter($facet_filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for setting a facet filter on a query or query filter object.
|
||||
*/
|
||||
protected function addFacetFilter($query_filter, $field, $filter) {
|
||||
// Test if this filter should be negated.
|
||||
$settings = $this->adapter->getFacet($this->facet)->getSettings();
|
||||
$exclude = !empty($settings->settings['exclude']);
|
||||
// Integer (or other nun-string) filters might mess up some of the following
|
||||
// comparison expressions.
|
||||
$filter = (string) $filter;
|
||||
if ($filter == '!') {
|
||||
$query_filter->condition($field, NULL);
|
||||
$query_filter->condition($field, NULL, $exclude ? '<>' : '=');
|
||||
}
|
||||
elseif ($filter[0] == '[' && $filter[strlen($filter) - 1] == ']' && ($pos = strpos($filter, ' TO '))) {
|
||||
$lower = trim(substr($filter, 1, $pos));
|
||||
$upper = trim(substr($filter, $pos + 4, -1));
|
||||
if ($lower == '*' && $upper == '*') {
|
||||
$query_filter->condition($field, NULL, '<>');
|
||||
$query_filter->condition($field, NULL, $exclude ? '=' : '<>');
|
||||
}
|
||||
else {
|
||||
elseif (!$exclude) {
|
||||
if ($lower != '*') {
|
||||
// Iff we have a range with two finite boundaries, we set two
|
||||
// conditions (larger than the lower bound and less than the upper
|
||||
@ -92,9 +105,22 @@ class SearchApiFacetapiTerm extends FacetapiQueryType implements FacetapiQueryTy
|
||||
$query_filter->condition($field, $upper, '<=');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Same as above, but with inverted logic.
|
||||
if ($lower != '*') {
|
||||
if ($upper != '*' && ($query_filter instanceof SearchApiQueryInterface || $query_filter->getConjunction() === 'AND')) {
|
||||
$original_query_filter = $query_filter;
|
||||
$query_filter = new SearchApiQueryFilter('OR');
|
||||
}
|
||||
$query_filter->condition($field, $lower, '<');
|
||||
}
|
||||
if ($upper != '*') {
|
||||
$query_filter->condition($field, $upper, '>');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$query_filter->condition($field, $filter);
|
||||
$query_filter->condition($field, $filter, $exclude ? '<>' : '=');
|
||||
}
|
||||
if (isset($original_query_filter)) {
|
||||
$original_query_filter->filter($query_filter);
|
||||
|
@ -9,9 +9,9 @@ files[] = plugins/facetapi/adapter.inc
|
||||
files[] = plugins/facetapi/query_type_term.inc
|
||||
files[] = plugins/facetapi/query_type_date.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-09-01
|
||||
version = "7.x-1.8"
|
||||
; Information added by Drupal.org packaging script on 2013-12-25
|
||||
version = "7.x-1.11"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1378025826"
|
||||
datestamp = "1387965506"
|
||||
|
||||
|
@ -92,7 +92,7 @@ function search_api_facetapi_facetapi_facet_info(array $searcher_info) {
|
||||
// other modules.
|
||||
$type_settings = array(
|
||||
'taxonomy_term' => array(
|
||||
'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy',
|
||||
'hierarchy callback' => 'search_api_facetapi_get_taxonomy_hierarchy',
|
||||
),
|
||||
'date' => array(
|
||||
'query type' => 'date',
|
||||
@ -226,6 +226,26 @@ function search_api_facetapi_settings($realm_name, SearchApiIndex $index) {
|
||||
return drupal_get_form('facetapi_realm_settings_form', $searcher_name, $realm_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets hierarchy information for taxonomy terms.
|
||||
*
|
||||
* Used as a hierarchy callback in search_api_facetapi_facetapi_facet_info().
|
||||
*
|
||||
* Internally just uses facetapi_get_taxonomy_hierarchy(), but makes sure that
|
||||
* our special "!" value is not passed.
|
||||
*
|
||||
* @param array $values
|
||||
* An array containing the term IDs.
|
||||
*
|
||||
* @return array
|
||||
* An associative array mapping term IDs to parent IDs (where parents could be
|
||||
* found).
|
||||
*/
|
||||
function search_api_facetapi_get_taxonomy_hierarchy(array $values) {
|
||||
$values = array_filter($values, 'is_numeric');
|
||||
return $values ? facetapi_get_taxonomy_hierarchy($values) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map callback for all search_api facet fields.
|
||||
*
|
||||
|
@ -151,11 +151,9 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
|
||||
}
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
|
||||
form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
|
||||
return NULL;
|
||||
}
|
||||
public function query(){
|
||||
parent::query();
|
||||
|
||||
$facet_field = $this->get_option('facet_field');
|
||||
if (!$facet_field) {
|
||||
return NULL;
|
||||
@ -165,7 +163,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
|
||||
if (!$base_path) {
|
||||
$base_path = $_GET['q'];
|
||||
}
|
||||
$this->view->build();
|
||||
|
||||
$limit = empty($this->view->query->pager->options['items_per_page']) ? 10 : $this->view->query->pager->options['items_per_page'];
|
||||
$query_options = &$this->view->query->getOptions();
|
||||
if (!$this->get_option('hide_block')) {
|
||||
@ -179,6 +177,17 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
|
||||
}
|
||||
$query_options['search_api_base_path'] = $base_path;
|
||||
$this->view->query->range(0, 0);
|
||||
}
|
||||
|
||||
public function render() {
|
||||
if (substr($this->view->base_table, 0, 17) != 'search_api_index_') {
|
||||
form_set_error('', t('The "Facets block" display can only be used with base tables based on Search API indexes.'));
|
||||
return NULL;
|
||||
}
|
||||
$facet_field = $this->get_option('facet_field');
|
||||
if (!$facet_field) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$this->view->execute();
|
||||
|
||||
@ -229,7 +238,7 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
|
||||
// Initializes variables passed to theme hook.
|
||||
$variables = array(
|
||||
'text' => $name,
|
||||
'path' => $base_path,
|
||||
'path' => $this->view->query->getOption('search_api_base_path'),
|
||||
'count' => $term['count'],
|
||||
'options' => array(
|
||||
'attributes' => array('class' => 'facetapi-inactive'),
|
||||
@ -249,10 +258,16 @@ class SearchApiViewsFacetsBlockDisplay extends views_plugin_display_block {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$info['content']['facets'] = array(
|
||||
return array(
|
||||
'facets' => array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $facets,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(){
|
||||
$info['content'] = $this->render();
|
||||
$info['content']['more'] = $this->render_more_link();
|
||||
$info['subject'] = filter_xss_admin($this->view->get_title());
|
||||
return $info;
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerArgument.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views argument handler class for handling all non-fulltext types.
|
||||
*/
|
||||
|
161
contrib/search_api_views/includes/handler_argument_date.inc
Normal file
161
contrib/search_api_views/includes/handler_argument_date.inc
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiViewsHandlerArgumentDate class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a contextual filter searching for a date or date range.
|
||||
*/
|
||||
class SearchApiViewsHandlerArgumentDate extends SearchApiViewsHandlerArgument {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query($group_by = FALSE) {
|
||||
if (empty($this->value)) {
|
||||
$this->fillValue();
|
||||
if ($this->value === FALSE) {
|
||||
$this->abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$outer_conjunction = strtoupper($this->operator);
|
||||
|
||||
if (empty($this->options['not'])) {
|
||||
$operator = '=';
|
||||
$inner_conjunction = 'OR';
|
||||
}
|
||||
else {
|
||||
$operator = '<>';
|
||||
$inner_conjunction = 'AND';
|
||||
}
|
||||
|
||||
if (!empty($this->value)) {
|
||||
if (!empty($this->value)) {
|
||||
$outer_filter = $this->query->createFilter($outer_conjunction);
|
||||
foreach ($this->value as $value) {
|
||||
$value_filter = $this->query->createFilter($inner_conjunction);
|
||||
$values = explode(';', $value);
|
||||
$values = array_map(array($this, 'getTimestamp'), $values);
|
||||
if (in_array(FALSE, $values, TRUE)) {
|
||||
$this->abort();
|
||||
return;
|
||||
}
|
||||
$is_range = (count($values) > 1);
|
||||
|
||||
$inner_filter = ($is_range ? $this->query->createFilter('AND') : $value_filter);
|
||||
$range_op = (empty($this->options['not']) ? '>=' : '<');
|
||||
$inner_filter->condition($this->real_field, $values[0], $is_range ? $range_op : $operator);
|
||||
if ($is_range) {
|
||||
$range_op = (empty($this->options['not']) ? '<=' : '>');
|
||||
$inner_filter->condition($this->real_field, $values[1], $range_op);
|
||||
$value_filter->filter($inner_filter);
|
||||
}
|
||||
$outer_filter->filter($value_filter);
|
||||
}
|
||||
|
||||
$this->query->filter($outer_filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to a timestamp, if it isn't one already.
|
||||
*
|
||||
* @param string|int $value
|
||||
* The value to convert. Either a timestamp, or a date/time string as
|
||||
* recognized by strtotime().
|
||||
*
|
||||
* @return int|false
|
||||
* The parsed timestamp, or FALSE if an illegal string was passed.
|
||||
*/
|
||||
public function getTimestamp($value) {
|
||||
if (is_numeric($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return strtotime($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills $this->value with data from the argument.
|
||||
*/
|
||||
protected function fillValue() {
|
||||
if (!empty($this->options['break_phrase'])) {
|
||||
// Set up defaults:
|
||||
if (!isset($this->value)) {
|
||||
$this->value = array();
|
||||
}
|
||||
|
||||
if (!isset($this->operator)) {
|
||||
$this->operator = 'OR';
|
||||
}
|
||||
|
||||
if (empty($this->argument)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match('/^([-\d;:\s]+\+)*[-\d;:\s]+$/', $this->argument)) {
|
||||
// The '+' character in a query string may be parsed as ' '.
|
||||
$this->value = explode('+', $this->argument);
|
||||
}
|
||||
elseif (preg_match('/^([-\d;:\s]+,)*[-\d;:\s]+$/', $this->argument)) {
|
||||
$this->operator = 'AND';
|
||||
$this->value = explode(',', $this->argument);
|
||||
}
|
||||
|
||||
// Keep an 'error' value if invalid strings were given.
|
||||
if (!empty($this->argument) && (empty($this->value) || !is_array($this->value))) {
|
||||
$this->value = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->value = array($this->argument);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the associated query due to an illegal argument.
|
||||
*/
|
||||
protected function abort() {
|
||||
$variables['!field'] = $this->definition['group'] . ': ' . $this->definition['title'];
|
||||
$this->query->abort(t('Illegal argument passed to !field contextual filter.', $variables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the title this argument will assign the view, given the argument.
|
||||
*
|
||||
* @return string
|
||||
* A title fitting for the passed argument.
|
||||
*/
|
||||
public function title() {
|
||||
if (!empty($this->argument)) {
|
||||
if (empty($this->value)) {
|
||||
$this->fillValue();
|
||||
}
|
||||
$dates = array();
|
||||
foreach ($this->value as $date) {
|
||||
$date_parts = explode(';', $date);
|
||||
|
||||
$ts = $this->getTimestamp($date_parts[0]);
|
||||
$datestr = format_date($ts, 'short');
|
||||
if (count($date_parts) > 1) {
|
||||
$ts = $this->getTimestamp($date_parts[1]);
|
||||
$datestr .= ' - ' . format_date($ts, 'short');
|
||||
}
|
||||
|
||||
if ($datestr) {
|
||||
$dates[] = $datestr;
|
||||
}
|
||||
}
|
||||
|
||||
return $dates ? implode(', ', $dates) : check_plain($this->argument);
|
||||
}
|
||||
|
||||
return check_plain($this->argument);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerArgumentFulltext.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views argument handler class for handling fulltext fields.
|
||||
*/
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerArgumentMoreLikeThis.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views argument handler providing a list of related items for search servers
|
||||
* supporting the "search_api_mlt" feature.
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerArgumentString.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views argument handler class for handling string fields.
|
||||
*/
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler base class for handling all "normal" cases.
|
||||
*/
|
||||
@ -31,8 +36,8 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
|
||||
*/
|
||||
public function operator_options() {
|
||||
return array(
|
||||
'<' => t('Is smaller than'),
|
||||
'<=' => t('Is smaller than or equal to'),
|
||||
'<' => t('Is less than'),
|
||||
'<=' => t('Is less than or equal to'),
|
||||
'=' => t('Is equal to'),
|
||||
'<>' => t('Is not equal to'),
|
||||
'>=' => t('Is greater than or equal to'),
|
||||
@ -46,8 +51,8 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
|
||||
* Provide a form for setting the filter value.
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
while (is_array($this->value)) {
|
||||
$this->value = $this->value ? array_shift($this->value) : NULL;
|
||||
while (is_array($this->value) && count($this->value) < 2) {
|
||||
$this->value = $this->value ? reset($this->value) : NULL;
|
||||
}
|
||||
$form['value'] = array(
|
||||
'#type' => 'textfield',
|
||||
@ -58,10 +63,19 @@ class SearchApiViewsHandlerFilter extends views_handler_filter {
|
||||
|
||||
// Hide the value box if the operator is 'empty' or 'not empty'.
|
||||
// Radios share the same selector so we have to add some dummy selector.
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
if (empty($form_state['exposed'])) {
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
}
|
||||
elseif (!empty($this->options['expose']['use_operator'])) {
|
||||
$name = $this->options['expose']['operator_id'];
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="' . $name . '"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="' . $name . '"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterBoolean.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling fulltext fields.
|
||||
*/
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterDate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler base class for handling all "normal" cases.
|
||||
*/
|
||||
|
211
contrib/search_api_views/includes/handler_filter_entity.inc
Normal file
211
contrib/search_api_views/includes/handler_filter_entity.inc
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterEntity.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for entities.
|
||||
*
|
||||
* Should be extended for specific entity types, such as
|
||||
* SearchApiViewsHandlerFilterUser and SearchApiViewsHandlerFilterTaxonomyTerm.
|
||||
*
|
||||
* Based on views_handler_filter_term_node_tid.
|
||||
*/
|
||||
abstract class SearchApiViewsHandlerFilterEntity extends SearchApiViewsHandlerFilter {
|
||||
|
||||
/**
|
||||
* If exposed form input was successfully validated, the entered entity IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $validated_exposed_input;
|
||||
|
||||
/**
|
||||
* Validates entered entity labels and converts them to entity IDs.
|
||||
*
|
||||
* Since this can come from either the form or the exposed filter, this is
|
||||
* abstracted out a bit so it can handle the multiple input sources.
|
||||
*
|
||||
* @param array $form
|
||||
* The form or form element for which any errors should be set.
|
||||
* @param array $values
|
||||
* The entered user names to validate.
|
||||
*
|
||||
* @return array
|
||||
* The entity IDs corresponding to all entities that could be found.
|
||||
*/
|
||||
abstract protected function validate_entity_strings(array &$form, array $values);
|
||||
|
||||
/**
|
||||
* Transforms an array of entity IDs into a comma-separated list of labels.
|
||||
*
|
||||
* @param array $ids
|
||||
* The entity IDs to transform.
|
||||
*
|
||||
* @return string
|
||||
* A string containing the labels corresponding to the IDs, separated by
|
||||
* commas.
|
||||
*/
|
||||
abstract protected function ids_to_strings(array $ids);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function operator_options() {
|
||||
$operators = array(
|
||||
'=' => $this->isMultiValued() ? t('Is one of') : t('Is'),
|
||||
'all of' => t('Is all of'),
|
||||
'<>' => $this->isMultiValued() ? t('Is not one of') : t('Is not'),
|
||||
'empty' => t('Is empty'),
|
||||
'not empty' => t('Is not empty'),
|
||||
);
|
||||
if (!$this->isMultiValued()) {
|
||||
unset($operators['all of']);
|
||||
}
|
||||
return $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['expose']['multiple']['default'] = TRUE;
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
parent::value_form($form, $form_state);
|
||||
|
||||
if (!is_array($this->value)) {
|
||||
$this->value = $this->value ? array($this->value) : array();
|
||||
}
|
||||
|
||||
// Set the correct default value in case the admin-set value is used (and a
|
||||
// value is present). The value is used if the form is either not exposed,
|
||||
// or the exposed form wasn't submitted yet (there is
|
||||
if ($this->value && (empty($form_state['input']) || !empty($form_state['input']['live_preview']))) {
|
||||
$form['value']['#default_value'] = $this->ids_to_strings($this->value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function value_validate($form, &$form_state) {
|
||||
if (!empty($form['value'])) {
|
||||
$value = &$form_state['values']['options']['value'];
|
||||
$values = $this->isMultiValued($form_state['values']['options']) ? drupal_explode_tags($value) : array($value);
|
||||
$ids = $this->validate_entity_strings($form['value'], $values);
|
||||
|
||||
if ($ids) {
|
||||
$value = $ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function accept_exposed_input($input) {
|
||||
$rc = parent::accept_exposed_input($input);
|
||||
|
||||
if ($rc) {
|
||||
// If we have previously validated input, override.
|
||||
if ($this->validated_exposed_input) {
|
||||
$this->value = $this->validated_exposed_input;
|
||||
}
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exposed_validate(&$form, &$form_state) {
|
||||
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
$input = $form_state['values'][$identifier];
|
||||
|
||||
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
|
||||
$this->operator = $this->options['group_info']['group_items'][$input]['operator'];
|
||||
$input = $this->options['group_info']['group_items'][$input]['value'];
|
||||
}
|
||||
|
||||
$values = $this->isMultiValued() ? drupal_explode_tags($input) : array($input);
|
||||
|
||||
if (!$this->options['is_grouped'] || ($this->options['is_grouped'] && ($input != 'All'))) {
|
||||
$this->validated_exposed_input = $this->validate_entity_strings($form[$identifier], $values);
|
||||
}
|
||||
else {
|
||||
$this->validated_exposed_input = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether multiple user names can be entered into this filter.
|
||||
*
|
||||
* This is either the case if the form isn't exposed, or if the " Allow
|
||||
* multiple selections" option is enabled.
|
||||
*
|
||||
* @param array $options
|
||||
* (optional) The options array to use. If not supplied, the options set on
|
||||
* this filter will be used.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if multiple values can be entered for this filter, FALSE otherwise.
|
||||
*/
|
||||
protected function isMultiValued(array $options = array()) {
|
||||
$options = $options ? $options : $this->options;
|
||||
return empty($options['exposed']) || !empty($options['expose']['multiple']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function admin_summary() {
|
||||
$value = $this->value;
|
||||
$this->value = empty($value) ? '' : $this->ids_to_strings($value);
|
||||
$ret = parent::admin_summary();
|
||||
$this->value = $value;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
if ($this->operator === 'empty') {
|
||||
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
|
||||
}
|
||||
elseif ($this->operator === 'not empty') {
|
||||
$this->query->condition($this->real_field, NULL, '<>', $this->options['group']);
|
||||
}
|
||||
elseif (is_array($this->value)) {
|
||||
$all_of = $this->operator === 'all of';
|
||||
$operator = $all_of ? '=' : $this->operator;
|
||||
if (count($this->value) == 1) {
|
||||
$this->query->condition($this->real_field, reset($this->value), $operator, $this->options['group']);
|
||||
}
|
||||
else {
|
||||
$filter = $this->query->createFilter($operator === '<>' || $all_of ? 'AND' : 'OR');
|
||||
foreach ($this->value as $value) {
|
||||
$filter->condition($this->real_field, $value, $operator);
|
||||
}
|
||||
$this->query->filter($filter, $this->options['group']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterFulltext.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling fulltext fields.
|
||||
*/
|
||||
@ -33,6 +38,7 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
|
||||
$options['operator']['default'] = 'AND';
|
||||
|
||||
$options['mode'] = array('default' => 'keys');
|
||||
$options['min_length'] = array('default' => '');
|
||||
$options['fields'] = array('default' => array());
|
||||
|
||||
return $options;
|
||||
@ -75,6 +81,55 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
|
||||
if (isset($form['expose'])) {
|
||||
$form['expose']['#weight'] = -5;
|
||||
}
|
||||
|
||||
$form['min_length'] = array(
|
||||
'#title' => t('Minimum keyword length'),
|
||||
'#description' => t('Minimum length of each word in the search keys. Leave empty to allow all words.'),
|
||||
'#type' => 'textfield',
|
||||
'#element_validate' => array('element_validate_integer_positive'),
|
||||
'#default_value' => $this->options['min_length'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exposed_validate(&$form, &$form_state) {
|
||||
// Only validate exposed input.
|
||||
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only need to validate if there is a minimum word length set.
|
||||
if ($this->options['min_length'] < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
$input = &$form_state['values'][$identifier];
|
||||
|
||||
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
|
||||
$this->operator = $this->options['group_info']['group_items'][$input]['operator'];
|
||||
$input = &$this->options['group_info']['group_items'][$input]['value'];
|
||||
}
|
||||
|
||||
// If there is no input, we're fine.
|
||||
if (!trim($input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$words = preg_split('/\s+/', $input);
|
||||
foreach ($words as $i => $word) {
|
||||
if (drupal_strlen($word) < $this->options['min_length']) {
|
||||
unset($words[$i]);
|
||||
}
|
||||
}
|
||||
if (!$words) {
|
||||
$vars['@count'] = $this->options['min_length'];
|
||||
$msg = t('You must include at least one positive keyword with @count characters or more.', $vars);
|
||||
form_error($form[$identifier], $msg);
|
||||
}
|
||||
$input = implode(' ', $words);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,9 +163,9 @@ class SearchApiViewsHandlerFilterFulltext extends SearchApiViewsHandlerFilterTex
|
||||
return;
|
||||
}
|
||||
|
||||
// If the operator was set to OR, set it as the conjunction. (AND is set by
|
||||
// default.)
|
||||
if ($this->operator === 'OR') {
|
||||
// If the operator was set to OR or NOT, set OR as the conjunction. (It is
|
||||
// also set for NOT since otherwise it would be "not all of these words".)
|
||||
if ($this->operator != 'AND') {
|
||||
$this->query->setOption('conjunction', $this->operator);
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,10 @@ class SearchApiViewsHandlerFilterLanguage extends SearchApiViewsHandlerFilterOpt
|
||||
*/
|
||||
public function query() {
|
||||
global $language_content;
|
||||
|
||||
if (!is_array($this->value)) {
|
||||
$this->value = $this->value ? array($this->value) : array();
|
||||
}
|
||||
foreach ($this->value as $i => $v) {
|
||||
if ($v == 'current') {
|
||||
$this->value[$i] = $language_content->language;
|
||||
|
@ -1,16 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling fields with a limited set of possible
|
||||
* values.
|
||||
*
|
||||
* Definition items:
|
||||
* - options: An array of possible values for this field.
|
||||
* @file
|
||||
* Contains the SearchApiViewsHandlerFilterOptions class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler for fields with a limited set of possible values.
|
||||
*/
|
||||
class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
|
||||
/**
|
||||
* Stores the values which are available on the form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $value_options = NULL;
|
||||
|
||||
/**
|
||||
* The type of form element used to display the options.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $value_form_type = 'checkboxes';
|
||||
|
||||
/**
|
||||
* Retrieves a wrapper for this filter's field.
|
||||
*
|
||||
* @return EntityMetadataWrapper|null
|
||||
* A wrapper for the field which this filter uses.
|
||||
*/
|
||||
protected function get_wrapper() {
|
||||
if ($this->query) {
|
||||
$index = $this->query->getIndex();
|
||||
}
|
||||
elseif (substr($this->view->base_table, 0, 17) == 'search_api_index_') {
|
||||
$index = search_api_index_load(substr($this->view->base_table, 17));
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
$wrapper = $index->entityWrapper(NULL, TRUE);
|
||||
$parts = explode(':', $this->real_field);
|
||||
foreach ($parts as $i => $part) {
|
||||
if (!isset($wrapper->$part)) {
|
||||
return NULL;
|
||||
}
|
||||
$wrapper = $wrapper->$part;
|
||||
$info = $wrapper->info();
|
||||
if ($i < count($parts) - 1) {
|
||||
// Unwrap lists.
|
||||
$level = search_api_list_nesting_level($info['type']);
|
||||
for ($j = 0; $j < $level; ++$j) {
|
||||
$wrapper = $wrapper[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the value_options property with all possible options.
|
||||
*/
|
||||
protected function get_value_options() {
|
||||
if (isset($this->value_options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wrapper = $this->get_wrapper();
|
||||
if ($wrapper) {
|
||||
$this->value_options = $wrapper->optionsList('view');
|
||||
}
|
||||
else {
|
||||
$this->value_options = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of options for the operator form.
|
||||
*/
|
||||
@ -63,13 +129,12 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
* Reduce the options according to the selection.
|
||||
*/
|
||||
protected function reduce_value_options() {
|
||||
$options = array();
|
||||
foreach ($this->definition['options'] as $id => $option) {
|
||||
if (isset($this->options['value'][$id])) {
|
||||
$options[$id] = $option;
|
||||
foreach ($this->value_options as $id => $option) {
|
||||
if (!isset($this->options['value'][$id])) {
|
||||
unset($this->value_options[$id]);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
return $this->value_options;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,27 +157,38 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
* Provide a form for setting options.
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
$options = array();
|
||||
$this->get_value_options();
|
||||
if (!empty($this->options['expose']['reduce']) && !empty($form_state['exposed'])) {
|
||||
$options += $this->reduce_value_options($form_state);
|
||||
$options = $this->reduce_value_options();
|
||||
}
|
||||
else {
|
||||
$options += $this->definition['options'];
|
||||
$options = $this->value_options;
|
||||
}
|
||||
|
||||
$form['value'] = array(
|
||||
'#type' => $this->value_form_type,
|
||||
'#title' => empty($form_state['exposed']) ? t('Value') : '',
|
||||
'#options' => $options,
|
||||
'#multiple' => TRUE,
|
||||
'#size' => min(4, count($this->definition['options'])),
|
||||
'#size' => min(4, count($options)),
|
||||
'#default_value' => is_array($this->value) ? $this->value : array(),
|
||||
);
|
||||
// Hide the value box if operator is 'empty' or 'not empty'.
|
||||
|
||||
// Hide the value box if the operator is 'empty' or 'not empty'.
|
||||
// Radios share the same selector so we have to add some dummy selector.
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
if (empty($form_state['exposed'])) {
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="options[operator]"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="options[operator]"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
}
|
||||
elseif (!empty($this->options['expose']['use_operator'])) {
|
||||
$name = $this->options['expose']['operator_id'];
|
||||
$form['value']['#states']['visible'] = array(
|
||||
':input[name="' . $name . '"],dummy-empty' => array('!value' => 'empty'),
|
||||
':input[name="' . $name . '"],dummy-not-empty' => array('!value' => 'not empty'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,8 +215,9 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
$values = '';
|
||||
|
||||
// Remove every element which is not known.
|
||||
$this->get_value_options();
|
||||
foreach ($this->value as $i => $value) {
|
||||
if (!isset($this->definition['options'][$value])) {
|
||||
if (!isset($this->value_options[$value])) {
|
||||
unset($this->value[$i]);
|
||||
}
|
||||
}
|
||||
@ -161,7 +238,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
}
|
||||
// If there is only a single value, use just the plain operator, = or <>.
|
||||
$operator = check_plain($operator);
|
||||
$values = check_plain($this->definition['options'][reset($this->value)]);
|
||||
$values = check_plain($this->value_options[reset($this->value)]);
|
||||
}
|
||||
else {
|
||||
foreach ($this->value as $value) {
|
||||
@ -172,7 +249,7 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
$values .= '…';
|
||||
break;
|
||||
}
|
||||
$values .= check_plain($this->definition['options'][$value]);
|
||||
$values .= check_plain($this->value_options[$value]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,28 +274,24 @@ class SearchApiViewsHandlerFilterOptions extends SearchApiViewsHandlerFilter {
|
||||
$this->value = reset($this->value);
|
||||
}
|
||||
|
||||
// Determine operator and conjunction.
|
||||
// Determine operator and conjunction. The defaults are already right for
|
||||
// "all of".
|
||||
$operator = '=';
|
||||
$conjunction = 'AND';
|
||||
switch ($this->operator) {
|
||||
case '=':
|
||||
$operator = '=';
|
||||
$conjunction = 'OR';
|
||||
break;
|
||||
|
||||
case 'all of':
|
||||
$operator = '=';
|
||||
$conjunction = 'AND';
|
||||
break;
|
||||
|
||||
case '<>':
|
||||
$operator = '<>';
|
||||
$conjunction = 'AND';
|
||||
break;
|
||||
}
|
||||
|
||||
// If the value is an empty array, we either want no filter at all (for
|
||||
// "is none of", or want to find only items with no value for the field.
|
||||
// "is none of"), or want to find only items with no value for the field.
|
||||
if ($this->value === array()) {
|
||||
if ($this->operator != '<>') {
|
||||
if ($operator != '<>') {
|
||||
$this->query->condition($this->real_field, NULL, '=', $this->options['group']);
|
||||
}
|
||||
return;
|
||||
|
@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterTaxonomyTerm.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for taxonomy term entities.
|
||||
*
|
||||
* Based on views_handler_filter_term_node_tid.
|
||||
*/
|
||||
class SearchApiViewsHandlerFilterTaxonomyTerm extends SearchApiViewsHandlerFilterEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has_extra_options() {
|
||||
return !empty($this->definition['vocabulary']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
|
||||
$options['type'] = array('default' => !empty($this->definition['vocabulary']) ? 'textfield' : 'select');
|
||||
$options['hierarchy'] = array('default' => 0);
|
||||
$options['error_message'] = array('default' => TRUE, 'bool' => TRUE);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extra_options_form(&$form, &$form_state) {
|
||||
$form['type'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Selection type'),
|
||||
'#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')),
|
||||
'#default_value' => $this->options['type'],
|
||||
);
|
||||
|
||||
$form['hierarchy'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Show hierarchy in dropdown'),
|
||||
'#default_value' => !empty($this->options['hierarchy']),
|
||||
);
|
||||
$form['hierarchy']['#states']['visible'][':input[name="options[type]"]']['value'] = 'select';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
parent::value_form($form, $form_state);
|
||||
|
||||
if (!empty($this->definition['vocabulary'])) {
|
||||
$vocabulary = taxonomy_vocabulary_machine_name_load($this->definition['vocabulary']);
|
||||
$title = t('Select terms from vocabulary @voc', array('@voc' => $vocabulary->name));
|
||||
}
|
||||
else {
|
||||
$vocabulary = FALSE;
|
||||
$title = t('Select terms');
|
||||
}
|
||||
$form['value']['#title'] = $title;
|
||||
|
||||
if ($vocabulary && $this->options['type'] == 'textfield') {
|
||||
$form['value']['#autocomplete_path'] = 'admin/views/ajax/autocomplete/taxonomy/' . $vocabulary->vid;
|
||||
}
|
||||
else {
|
||||
if ($vocabulary && !empty($this->options['hierarchy'])) {
|
||||
$tree = taxonomy_get_tree($vocabulary->vid);
|
||||
$options = array();
|
||||
|
||||
if ($tree) {
|
||||
foreach ($tree as $term) {
|
||||
$choice = new stdClass();
|
||||
$choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
|
||||
$options[] = $choice;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$options = array();
|
||||
$query = db_select('taxonomy_term_data', 'td');
|
||||
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
|
||||
$query->fields('td');
|
||||
$query->orderby('tv.weight');
|
||||
$query->orderby('tv.name');
|
||||
$query->orderby('td.weight');
|
||||
$query->orderby('td.name');
|
||||
$query->addTag('term_access');
|
||||
if ($vocabulary) {
|
||||
$query->condition('tv.machine_name', $vocabulary->machine_name);
|
||||
}
|
||||
$result = $query->execute();
|
||||
foreach ($result as $term) {
|
||||
$options[$term->tid] = $term->name;
|
||||
}
|
||||
}
|
||||
|
||||
$default_value = (array) $this->value;
|
||||
|
||||
if (!empty($form_state['exposed'])) {
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
|
||||
if (!empty($this->options['expose']['reduce'])) {
|
||||
$options = $this->reduce_value_options($options);
|
||||
|
||||
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
|
||||
$default_value = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->options['expose']['multiple'])) {
|
||||
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
|
||||
$default_value = 'All';
|
||||
}
|
||||
elseif (empty($default_value)) {
|
||||
$keys = array_keys($options);
|
||||
$default_value = array_shift($keys);
|
||||
}
|
||||
// Due to #1464174 there is a chance that array('') was saved in the
|
||||
// admin ui. Let's choose a safe default value.
|
||||
elseif ($default_value == array('')) {
|
||||
$default_value = 'All';
|
||||
}
|
||||
else {
|
||||
$copy = $default_value;
|
||||
$default_value = array_shift($copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
$form['value']['#type'] = 'select';
|
||||
$form['value']['#multiple'] = TRUE;
|
||||
$form['value']['#options'] = $options;
|
||||
$form['value']['#size'] = min(9, count($options));
|
||||
$form['value']['#default_value'] = $default_value;
|
||||
|
||||
if (!empty($form_state['exposed']) && isset($identifier) && !isset($form_state['input'][$identifier])) {
|
||||
$form_state['input'][$identifier] = $default_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the available exposed options according to the selection.
|
||||
*/
|
||||
protected function reduce_value_options(array $options) {
|
||||
foreach ($options as $id => $option) {
|
||||
if (empty($this->options['value'][$id])) {
|
||||
unset($options[$id]);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function value_validate($form, &$form_state) {
|
||||
// We only validate if they've chosen the text field style.
|
||||
if ($this->options['type'] != 'textfield') {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::value_validate($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function accept_exposed_input($input) {
|
||||
if (empty($this->options['exposed'])) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// If view is an attachment and is inheriting exposed filters, then assume
|
||||
// exposed input has already been validated.
|
||||
if (!empty($this->view->is_attachment) && $this->view->display_handler->uses_exposed()) {
|
||||
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
|
||||
}
|
||||
|
||||
// If it's non-required and there's no value don't bother filtering.
|
||||
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return parent::accept_exposed_input($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exposed_validate(&$form, &$form_state) {
|
||||
if (empty($this->options['exposed']) || empty($this->options['expose']['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only validate if they've chosen the text field style.
|
||||
if ($this->options['type'] != 'textfield') {
|
||||
$input = $form_state['values'][$this->options['expose']['identifier']];
|
||||
if ($this->options['is_grouped'] && isset($this->options['group_info']['group_items'][$input])) {
|
||||
$input = $this->options['group_info']['group_items'][$input]['value'];
|
||||
}
|
||||
|
||||
if ($input != 'All') {
|
||||
$this->validated_exposed_input = (array) $input;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parent::exposed_validate($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate_entity_strings(array &$form, array $values) {
|
||||
if (empty($values)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$tids = array();
|
||||
$names = array();
|
||||
$missing = array();
|
||||
foreach ($values as $value) {
|
||||
$missing[strtolower($value)] = TRUE;
|
||||
$names[] = $value;
|
||||
}
|
||||
|
||||
if (!$names) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$query = db_select('taxonomy_term_data', 'td');
|
||||
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
|
||||
$query->fields('td');
|
||||
$query->condition('td.name', $names);
|
||||
if (!empty($this->definition['vocabulary'])) {
|
||||
$query->condition('tv.machine_name', $this->definition['vocabulary']);
|
||||
}
|
||||
$query->addTag('term_access');
|
||||
$result = $query->execute();
|
||||
foreach ($result as $term) {
|
||||
unset($missing[strtolower($term->name)]);
|
||||
$tids[] = $term->tid;
|
||||
}
|
||||
|
||||
if ($missing) {
|
||||
if (!empty($this->options['error_message'])) {
|
||||
form_error($form, format_plural(count($missing), 'Unable to find term: @terms', 'Unable to find terms: @terms', array('@terms' => implode(', ', array_keys($missing)))));
|
||||
}
|
||||
else {
|
||||
// Add a bogus TID which will show an empty result for a positive filter
|
||||
// and be ignored for an excluding one.
|
||||
$tids[] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $tids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expose_form(&$form, &$form_state) {
|
||||
parent::expose_form($form, $form_state);
|
||||
if ($this->options['type'] != 'select') {
|
||||
unset($form['expose']['reduce']);
|
||||
}
|
||||
$form['error_message'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Display error message'),
|
||||
'#description' => t('Display an error message if one of the entered terms could not be found.'),
|
||||
'#default_value' => !empty($this->options['error_message']),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function ids_to_strings(array $ids) {
|
||||
return implode(', ', db_select('taxonomy_term_data', 'td')
|
||||
->fields('td', array('name'))
|
||||
->condition('td.tid', array_filter($ids))
|
||||
->execute()
|
||||
->fetchCol());
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterText.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling fulltext fields.
|
||||
*/
|
||||
|
77
contrib/search_api_views/includes/handler_filter_user.inc
Normal file
77
contrib/search_api_views/includes/handler_filter_user.inc
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerFilterUser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views filter handler class for handling user entities.
|
||||
*
|
||||
* Based on views_handler_filter_user_name.
|
||||
*/
|
||||
class SearchApiViewsHandlerFilterUser extends SearchApiViewsHandlerFilterEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function value_form(&$form, &$form_state) {
|
||||
parent::value_form($form, $form_state);
|
||||
|
||||
// Set autocompletion.
|
||||
$path = $this->isMultiValued() ? 'admin/views/ajax/autocomplete/user' : 'user/autocomplete';
|
||||
$form['value']['#autocomplete_path'] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function ids_to_strings(array $ids) {
|
||||
$names = array();
|
||||
$args[':uids'] = array_filter($ids);
|
||||
$result = db_query("SELECT uid, name FROM {users} u WHERE uid IN (:uids)", $args);
|
||||
$result = $result->fetchAllKeyed();
|
||||
foreach ($ids as $uid) {
|
||||
if (!$uid) {
|
||||
$names[] = variable_get('anonymous', t('Anonymous'));
|
||||
}
|
||||
elseif (isset($result[$uid])) {
|
||||
$names[] = $result[$uid];
|
||||
}
|
||||
}
|
||||
return implode(', ', $names);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate_entity_strings(array &$form, array $values) {
|
||||
$uids = array();
|
||||
$missing = array();
|
||||
foreach ($values as $value) {
|
||||
if (drupal_strtolower($value) === drupal_strtolower(variable_get('anonymous', t('Anonymous')))) {
|
||||
$uids[] = 0;
|
||||
}
|
||||
else {
|
||||
$missing[strtolower($value)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missing) {
|
||||
return $uids;
|
||||
}
|
||||
|
||||
$result = db_query("SELECT * FROM {users} WHERE name IN (:names)", array(':names' => array_values($missing)));
|
||||
foreach ($result as $account) {
|
||||
unset($missing[strtolower($account->name)]);
|
||||
$uids[] = $account->uid;
|
||||
}
|
||||
|
||||
if ($missing) {
|
||||
form_error($form, format_plural(count($missing), 'Unable to find user: @users', 'Unable to find users: @users', array('@users' => implode(', ', $missing))));
|
||||
}
|
||||
|
||||
return $uids;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsHandlerSort.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for sorting results according to a specified field.
|
||||
*/
|
||||
|
@ -98,7 +98,7 @@ class SearchApiViewsCache extends views_plugin_cache_time {
|
||||
// other parameters used in the parent method are already reflected in the
|
||||
// Search API query object we use.
|
||||
if (isset($_GET['exposed_info'])) {
|
||||
$key_data[$key] = $_GET[$key];
|
||||
$key_data['exposed_info'] = $_GET['exposed_info'];
|
||||
}
|
||||
|
||||
$this->_results_key = $this->view->name . ':' . $this->display->id . ':results:' . md5(serialize($key_data));
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiViewsQuery.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Views query class using a Search API index as the data source.
|
||||
*/
|
||||
@ -180,7 +185,6 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
'#options' => array(),
|
||||
'#default_value' => $this->options['parse_mode'],
|
||||
);
|
||||
$modes = array();
|
||||
foreach ($this->query->parseModes() as $key => $mode) {
|
||||
$form['parse_mode']['#options'][$key] = $mode['name'];
|
||||
if (!empty($mode['description'])) {
|
||||
@ -243,16 +247,6 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
$view->init_pager();
|
||||
$this->pager->query();
|
||||
|
||||
// Views passes sometimes NULL and sometimes the integer 0 for "All" in a
|
||||
// pager. If set to 0 items, a string "0" is passed. Therefore, we unset
|
||||
// the limit if an empty value OTHER than a string "0" was passed.
|
||||
if (!$this->limit && $this->limit !== '0') {
|
||||
$this->limit = NULL;
|
||||
}
|
||||
// Set the range. (We always set this, as there might even be an offset if
|
||||
// all items are shown.)
|
||||
$this->query->range($this->offset, $this->limit);
|
||||
|
||||
// Set the search ID, if it was not already set.
|
||||
if ($this->query->getOption('search id') == get_class($this->query)) {
|
||||
$this->query->setOption('search id', 'search_api_views:' . $view->name . ':' . $view->current_display);
|
||||
@ -262,6 +256,20 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
if (!empty($this->options['search_api_bypass_access'])) {
|
||||
$this->query->setOption('search_api_bypass_access', TRUE);
|
||||
}
|
||||
|
||||
// If the View and the Panel conspire to provide an overridden path then
|
||||
// pass that through as the base path.
|
||||
if (!empty($this->view->override_path) && strpos(current_path(), $this->view->override_path) !== 0) {
|
||||
$this->query->setOption('search_api_base_path', $this->view->override_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alter(&$view) {
|
||||
parent::alter($view);
|
||||
drupal_alter('search_api_views_query', $view, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,7 +292,28 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the "skip result count" option, if it wasn't already set to
|
||||
// FALSE.
|
||||
$skip_result_count = $this->query->getOption('skip result count', TRUE);
|
||||
if ($skip_result_count) {
|
||||
$skip_result_count = !$this->pager->use_count_query() && empty($view->get_total_rows);
|
||||
$this->query->setOption('skip result count', $skip_result_count);
|
||||
}
|
||||
|
||||
try {
|
||||
// Trigger pager pre_execute().
|
||||
$this->pager->pre_execute($this->query);
|
||||
|
||||
// Views passes sometimes NULL and sometimes the integer 0 for "All" in a
|
||||
// pager. If set to 0 items, a string "0" is passed. Therefore, we unset
|
||||
// the limit if an empty value OTHER than a string "0" was passed.
|
||||
if (!$this->limit && $this->limit !== '0') {
|
||||
$this->limit = NULL;
|
||||
}
|
||||
// Set the range. (We always set this, as there might even be an offset if
|
||||
// all items are shown.)
|
||||
$this->query->range($this->offset, $this->limit);
|
||||
|
||||
$start = microtime(TRUE);
|
||||
|
||||
// Execute the search.
|
||||
@ -292,11 +321,13 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
$this->search_api_results = $results;
|
||||
|
||||
// Store the results.
|
||||
$this->pager->total_items = $view->total_rows = $results['result count'];
|
||||
if (!empty($this->pager->options['offset'])) {
|
||||
$this->pager->total_items -= $this->pager->options['offset'];
|
||||
if (!$skip_result_count) {
|
||||
$this->pager->total_items = $view->total_rows = $results['result count'];
|
||||
if (!empty($this->pager->options['offset'])) {
|
||||
$this->pager->total_items -= $this->pager->options['offset'];
|
||||
}
|
||||
$this->pager->update_page_info();
|
||||
}
|
||||
$this->pager->update_page_info();
|
||||
$view->result = array();
|
||||
if (!empty($results['results'])) {
|
||||
$this->addResults($results['results'], $view);
|
||||
@ -304,6 +335,9 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
// We shouldn't use $results['performance']['complete'] here, since
|
||||
// extracting the results probably takes considerable time as well.
|
||||
$view->execute_time = microtime(TRUE) - $start;
|
||||
|
||||
// Trigger pager post_execute().
|
||||
$this->pager->post_execute($view->result);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->errors[] = $e->getMessage();
|
||||
@ -317,8 +351,14 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
*
|
||||
* Used by handlers to flag a fatal error which shouldn't be displayed but
|
||||
* still lead to the view returning empty and the search not being executed.
|
||||
*
|
||||
* @param string|null $msg
|
||||
* Optionally, a translated, unescaped error message to display.
|
||||
*/
|
||||
public function abort() {
|
||||
public function abort($msg = NULL) {
|
||||
if ($msg) {
|
||||
$this->errors[] = $msg;
|
||||
}
|
||||
$this->abort = TRUE;
|
||||
}
|
||||
|
||||
@ -520,9 +560,9 @@ class SearchApiViewsQuery extends views_plugin_query {
|
||||
// Query interface methods (proxy to $this->query)
|
||||
//
|
||||
|
||||
public function createFilter($conjunction = 'AND') {
|
||||
public function createFilter($conjunction = 'AND', $tags = array()) {
|
||||
if (!$this->errors) {
|
||||
return $this->query->createFilter($conjunction);
|
||||
return $this->query->createFilter($conjunction, $tags);
|
||||
}
|
||||
}
|
||||
|
||||
|
34
contrib/search_api_views/search_api_views.api.php
Normal file
34
contrib/search_api_views/search_api_views.api.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Search Views module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter the query before executing the query.
|
||||
*
|
||||
* @param view $view
|
||||
* The view object about to be processed.
|
||||
* @param SearchApiViewsQuery $query
|
||||
* The Search API Views query to be altered.
|
||||
*
|
||||
* @see hook_views_query_alter()
|
||||
*/
|
||||
function hook_search_api_views_query_alter(view &$view, SearchApiViewsQuery &$query) {
|
||||
// (Example assuming a view with an exposed filter on node title.)
|
||||
// If the input for the title filter is a positive integer, filter against
|
||||
// node ID instead of node title.
|
||||
if ($view->name == 'my_view' && is_numeric($view->exposed_raw_input['title']) && $view->exposed_raw_input['title'] > 0) {
|
||||
// Traverse through the 'where' part of the query.
|
||||
foreach ($query->where as &$condition_group) {
|
||||
foreach ($condition_group['conditions'] as &$condition) {
|
||||
// If this is the part of the query filtering on title, chang the
|
||||
// condition to filter on node ID.
|
||||
if (reset($condition) == 'node.title') {
|
||||
$condition = array('node.nid', $view->exposed_raw_input['title'],'=');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
name = Search views
|
||||
description = Integrates the Search API with Views, enabling users to create views with searches as filters or arguments.
|
||||
dependencies[] = search_api
|
||||
@ -12,21 +11,25 @@ files[] = includes/handler_argument.inc
|
||||
files[] = includes/handler_argument_fulltext.inc
|
||||
files[] = includes/handler_argument_more_like_this.inc
|
||||
files[] = includes/handler_argument_string.inc
|
||||
files[] = includes/handler_argument_date.inc
|
||||
files[] = includes/handler_argument_taxonomy_term.inc
|
||||
files[] = includes/handler_filter.inc
|
||||
files[] = includes/handler_filter_boolean.inc
|
||||
files[] = includes/handler_filter_date.inc
|
||||
files[] = includes/handler_filter_entity.inc
|
||||
files[] = includes/handler_filter_fulltext.inc
|
||||
files[] = includes/handler_filter_language.inc
|
||||
files[] = includes/handler_filter_options.inc
|
||||
files[] = includes/handler_filter_taxonomy_term.inc
|
||||
files[] = includes/handler_filter_text.inc
|
||||
files[] = includes/handler_filter_user.inc
|
||||
files[] = includes/handler_sort.inc
|
||||
files[] = includes/plugin_cache.inc
|
||||
files[] = includes/query.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-09-01
|
||||
version = "7.x-1.8"
|
||||
; Information added by Drupal.org packaging script on 2013-12-25
|
||||
version = "7.x-1.11"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1378025826"
|
||||
datestamp = "1387965506"
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the search_api_views module.
|
||||
@ -24,7 +25,7 @@ function search_api_views_update_7101() {
|
||||
if (!$table_fields) {
|
||||
return;
|
||||
}
|
||||
foreach (views_get_all_views() as $name => $view) {
|
||||
foreach (views_get_all_views() as $view) {
|
||||
if (empty($view->base_table) || empty($table_fields[$view->base_table])) {
|
||||
continue;
|
||||
}
|
||||
@ -32,7 +33,7 @@ function search_api_views_update_7101() {
|
||||
$fields = $table_fields[$view->base_table];
|
||||
$change |= _search_api_views_update_7101_helper($view->base_field, $fields);
|
||||
if (!empty($view->display)) {
|
||||
foreach ($view->display as $key => &$display) {
|
||||
foreach ($view->display as &$display) {
|
||||
$options = &$display->display_options;
|
||||
if (isset($options['style_options']['grouping'])) {
|
||||
$change |= _search_api_views_update_7101_helper($options['style_options']['grouping'], $fields);
|
||||
@ -66,8 +67,15 @@ function search_api_views_update_7101() {
|
||||
/**
|
||||
* Helper function for replacing field identifiers.
|
||||
*
|
||||
* @return
|
||||
* TRUE iff the identifier was changed.
|
||||
* @param $field
|
||||
* Some data to be searched for field names that should be altered. Passed by
|
||||
* reference.
|
||||
* @param array $fields
|
||||
* An array mapping Search API field identifiers (as previously used by Views)
|
||||
* to the new, sanitized Views field identifiers.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if any data was changed, FALSE otherwise.
|
||||
*/
|
||||
function _search_api_views_update_7101_helper(&$field, array $fields) {
|
||||
if (is_array($field)) {
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Integrates the Search API with Views.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_views_api().
|
||||
*/
|
||||
@ -12,7 +17,7 @@ function search_api_views_views_api() {
|
||||
/**
|
||||
* Implements hook_search_api_index_insert().
|
||||
*/
|
||||
function search_api_views_search_api_index_insert(SearchApiIndex $index) {
|
||||
function search_api_views_search_api_index_insert() {
|
||||
// Make the new index available for views.
|
||||
views_invalidate_cache();
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Views hook implementations for the Search API Views module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_views_data().
|
||||
*/
|
||||
@ -28,7 +33,7 @@ function search_api_views_views_data() {
|
||||
}
|
||||
|
||||
try {
|
||||
$wrapper = $index->entityWrapper(NULL, TRUE);
|
||||
$wrapper = $index->entityWrapper(NULL, FALSE);
|
||||
}
|
||||
catch (EntityMetadataWrapperException $e) {
|
||||
watchdog_exception('search_api_views', $e, "%type while retrieving metadata for index %index: !message in %function (line %line of %file).", array('%index' => $index->name), WATCHDOG_WARNING);
|
||||
@ -43,6 +48,14 @@ function search_api_views_views_data() {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$wrapper = $index->entityWrapper(NULL);
|
||||
}
|
||||
catch (EntityMetadataWrapperException $e) {
|
||||
watchdog_exception('search_api_views', $e, "%type while retrieving metadata for index %index: !message in %function (line %line of %file).", array('%index' => $index->name), WATCHDOG_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add handlers for all indexed fields.
|
||||
foreach ($index->getFields() as $key => $field) {
|
||||
$tmp = $wrapper;
|
||||
@ -69,7 +82,7 @@ function search_api_views_views_data() {
|
||||
if ($group) {
|
||||
// @todo Entity type label instead of $group?
|
||||
$table[$id]['group'] = $group;
|
||||
$name = t('@field (indexed)', array('@field' => $name));
|
||||
$name = t('!field (indexed)', array('!field' => $name));
|
||||
}
|
||||
$table[$id]['title'] = $name;
|
||||
$table[$id]['help'] = empty($info['description']) ? t('(No information available)') : $info['description'];
|
||||
@ -145,8 +158,18 @@ function search_api_views_views_data() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns an array of handler definitions to add to a
|
||||
* views field definition.
|
||||
* Adds handler definitions for a field to a Views data table definition.
|
||||
*
|
||||
* Helper method for search_api_views_views_data().
|
||||
*
|
||||
* @param $id
|
||||
* The internal identifier of the field.
|
||||
* @param array $field
|
||||
* Information about the field.
|
||||
* @param EntityMetadataWrapper $wrapper
|
||||
* A wrapper providing further metadata about the field.
|
||||
* @param array $table
|
||||
* The existing Views data table definition, as a reference.
|
||||
*/
|
||||
function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper $wrapper, array &$table) {
|
||||
$type = $field['type'];
|
||||
@ -170,9 +193,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
|
||||
return;
|
||||
}
|
||||
|
||||
if ($options = $wrapper->optionsList('view')) {
|
||||
$info = $wrapper->info();
|
||||
if (isset($info['options list']) && is_callable($info['options list'])) {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterOptions';
|
||||
$table[$id]['filter']['options'] = $options;
|
||||
$table[$id]['filter']['multi-valued'] = search_api_is_list_type($type);
|
||||
}
|
||||
elseif ($inner_type == 'boolean') {
|
||||
@ -181,6 +204,27 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
|
||||
elseif ($inner_type == 'date') {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterDate';
|
||||
}
|
||||
elseif (isset($field['entity_type']) && $field['entity_type'] === 'user') {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterUser';
|
||||
}
|
||||
elseif (isset($field['entity_type']) && $field['entity_type'] === 'taxonomy_term') {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilterTaxonomyTerm';
|
||||
$info = $wrapper->info();
|
||||
$field_info = field_info_field($info['name']);
|
||||
// For the "Parent terms" and "All parent terms" properties, we can
|
||||
// extrapolate the vocabulary from the parent in the selector. (E.g.,
|
||||
// for "field_tags:parent" we can use the information of "field_tags".)
|
||||
// Otherwise, we can't include any vocabulary information.
|
||||
if (!$field_info && ($info['name'] == 'parent' || $info['name'] == 'parents_all')) {
|
||||
if (!empty($table[$id]['real field'])) {
|
||||
$parts = explode(':', $table[$id]['real field']);
|
||||
$field_info = field_info_field($parts[count($parts) - 2]);
|
||||
}
|
||||
}
|
||||
if (isset($field_info['settings']['allowed_values'][0]['vocabulary'])) {
|
||||
$table[$id]['filter']['vocabulary'] = $field_info['settings']['allowed_values'][0]['vocabulary'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$table[$id]['filter']['handler'] = 'SearchApiViewsHandlerFilter';
|
||||
}
|
||||
@ -188,6 +232,9 @@ function _search_api_views_add_handlers($id, array $field, EntityMetadataWrapper
|
||||
if ($inner_type == 'string' || $inner_type == 'uri') {
|
||||
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentString';
|
||||
}
|
||||
elseif ($inner_type == 'date') {
|
||||
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgumentDate';
|
||||
}
|
||||
else {
|
||||
$table[$id]['argument']['handler'] = 'SearchApiViewsHandlerArgument';
|
||||
}
|
||||
|
BIN
disabled.png
BIN
disabled.png
Binary file not shown.
Before Width: | Height: | Size: 384 B |
BIN
enabled.png
BIN
enabled.png
Binary file not shown.
Before Width: | Height: | Size: 383 B |
@ -26,7 +26,7 @@ interface SearchApiAlterCallbackInterface {
|
||||
/**
|
||||
* 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
|
||||
* This can be used for hiding the callback on the index's "Filters" 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
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterAddAggregation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that adds an URL field for all items.
|
||||
*/
|
||||
@ -11,7 +16,11 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
$fields = $this->index->getFields(FALSE);
|
||||
$field_options = array();
|
||||
foreach ($fields as $name => $field) {
|
||||
$field_options[$name] = $field['name'];
|
||||
$field_options[$name] = check_plain($field['name']);
|
||||
$field_properties[$name] = array(
|
||||
'#attributes' => array('title' => $name),
|
||||
'#description' => check_plain($field['description']),
|
||||
);
|
||||
}
|
||||
$additional = empty($this->options['fields']) ? array() : $this->options['fields'];
|
||||
|
||||
@ -63,14 +72,14 @@ class SearchApiAlterAddAggregation extends SearchApiAbstractAlterCallback {
|
||||
foreach (array_keys($types) as $type) {
|
||||
$form['fields'][$name]['type_descriptions'][$type]['#states']['visible'][':input[name="callbacks[search_api_alter_add_aggregation][settings][fields][' . $name . '][type]"]']['value'] = $type;
|
||||
}
|
||||
$form['fields'][$name]['fields'] = array(
|
||||
$form['fields'][$name]['fields'] = array_merge($field_properties, array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Contained fields'),
|
||||
'#options' => $field_options,
|
||||
'#default_value' => drupal_map_assoc($field['fields']),
|
||||
'#attributes' => array('class' => array('search-api-alter-add-aggregation-fields')),
|
||||
'#required' => TRUE,
|
||||
);
|
||||
));
|
||||
$form['fields'][$name]['actions'] = array(
|
||||
'#type' => 'actions',
|
||||
'remove' => array(
|
||||
|
@ -1,7 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that adds an URL field for all items.
|
||||
* @file
|
||||
* Contains SearchApiAlterAddHierarchy.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds all ancestors for hierarchical fields.
|
||||
*/
|
||||
class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
|
||||
@ -15,24 +20,16 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
protected $field_options;
|
||||
|
||||
/**
|
||||
* Enable this data alteration only if any hierarchical fields are available.
|
||||
* Overrides SearchApiAbstractAlterCallback::supportsIndex().
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index to check for.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the callback can run on the given index; FALSE otherwise.
|
||||
* Returns TRUE only if any hierarchical fields are available.
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return (bool) $this->getHierarchicalFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form for configuring this callback.
|
||||
*
|
||||
* @return array
|
||||
* A form array for configuring this callback, or FALSE if no configuration
|
||||
* is possible.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm() {
|
||||
$options = $this->getHierarchicalFields();
|
||||
@ -54,19 +51,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
// Change the saved type of fields in the index, if necessary.
|
||||
@ -74,7 +59,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
$fields = &$this->index->options['fields'];
|
||||
$previous = drupal_map_assoc($this->options['fields']);
|
||||
foreach ($values['fields'] as $field) {
|
||||
list($key, $prop) = explode(':', $field);
|
||||
list($key) = explode(':', $field);
|
||||
if (empty($previous[$field]) && isset($fields[$key]['type'])) {
|
||||
$fields[$key]['type'] = 'list<' . search_api_extract_inner_type($fields[$key]['type']) . '>';
|
||||
$change = TRUE;
|
||||
@ -82,7 +67,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
$new = drupal_map_assoc($values['fields']);
|
||||
foreach ($previous as $field) {
|
||||
list($key, $prop) = explode(':', $field);
|
||||
list($key) = explode(':', $field);
|
||||
if (empty($new[$field]) && isset($fields[$key]['type'])) {
|
||||
$w = $this->index->entityWrapper(NULL, FALSE);
|
||||
if (isset($w->$key)) {
|
||||
@ -102,19 +87,11 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter items before indexing.
|
||||
*
|
||||
* Items which are removed from the array won't be indexed, but will be marked
|
||||
* as clean for future indexing. This could for instance be used to implement
|
||||
* some sort of access filter for security purposes (e.g., don't index
|
||||
* unpublished nodes or comments).
|
||||
*
|
||||
* @param array $items
|
||||
* An array of items to be altered, keyed by item IDs.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
if (empty($this->options['fields'])) {
|
||||
return array();
|
||||
return;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$wrapper = $this->index->entityWrapper($item, FALSE);
|
||||
@ -137,16 +114,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function propertyInfo() {
|
||||
if (empty($this->options['fields'])) {
|
||||
@ -188,7 +156,7 @@ class SearchApiAlterAddHierarchy extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for finding all hierarchical fields of an index's type.
|
||||
* Finds all hierarchical fields for the current index.
|
||||
*
|
||||
* @return array
|
||||
* An array containing all hierarchical fields of the index, structured as
|
||||
|
@ -1,12 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterAddUrl.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that adds an URL field for all items.
|
||||
*/
|
||||
class SearchApiAlterAddUrl extends SearchApiAbstractAlterCallback {
|
||||
|
||||
public function alterItems(array &$items) {
|
||||
foreach ($items as $id => &$item) {
|
||||
foreach ($items as &$item) {
|
||||
$url = $this->index->datasource()->getItemUrl($item);
|
||||
if (!$url) {
|
||||
$item->search_api_url = NULL;
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterAddViewedEntity.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that adds an URL field for all items.
|
||||
*/
|
||||
@ -64,7 +69,7 @@ class SearchApiAlterAddViewedEntity extends SearchApiAbstractAlterCallback {
|
||||
|
||||
$type = $this->index->getEntityType();
|
||||
$mode = empty($this->options['mode']) ? 'full' : $this->options['mode'];
|
||||
foreach ($items as $id => &$item) {
|
||||
foreach ($items as &$item) {
|
||||
// Since we can't really know what happens in entity_view() and render(),
|
||||
// we use try/catch. This will at least prevent some errors, even though
|
||||
// it's no protection against fatal errors and the like.
|
||||
|
@ -1,15 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that filters out items based on their
|
||||
* bundle.
|
||||
* @file
|
||||
* Contains SearchApiAlterBundleFilter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a data alteration that restricts entity indexes to some bundles.
|
||||
*/
|
||||
class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return $index->getEntityType() && ($info = entity_get_info($index->getEntityType())) && self::hasBundles($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
if (self::hasBundles($info) && isset($this->options['bundles'])) {
|
||||
@ -24,6 +34,9 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm() {
|
||||
$info = entity_get_info($this->index->getEntityType());
|
||||
if (self::hasBundles($info)) {
|
||||
@ -62,8 +75,13 @@ class SearchApiAlterBundleFilter extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for figuring out if the entities with the given entity info
|
||||
* can be filtered by bundle.
|
||||
* Determines whether a certain entity type has any bundles.
|
||||
*
|
||||
* @param array $entity_info
|
||||
* The entity type's entity_get_info() array.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity type has bundles, FASLE otherwise.
|
||||
*/
|
||||
protected static function hasBundles(array $entity_info) {
|
||||
return !empty($entity_info['entity keys']['bundle']) && !empty($entity_info['bundles']);
|
||||
|
46
includes/callback_comment_access.inc
Normal file
46
includes/callback_comment_access.inc
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains the SearchApiAlterCommentAccess class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds node access information to comment indexes.
|
||||
*/
|
||||
class SearchApiAlterCommentAccess extends SearchApiAlterNodeAccess {
|
||||
|
||||
/**
|
||||
* Overrides SearchApiAlterNodeAccess::supportsIndex().
|
||||
*
|
||||
* Returns TRUE only for indexes on comments.
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
return $index->getEntityType() === 'comment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides SearchApiAlterNodeAccess::getNode().
|
||||
*
|
||||
* Returns the comment's node, instead of the item (i.e., the comment) itself.
|
||||
*/
|
||||
protected function getNode($item) {
|
||||
return node_load($item->nid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides SearchApiAlterNodeAccess::configurationFormSubmit().
|
||||
*
|
||||
* Doesn't index the comment's "Author".
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
$old_status = !empty($form_state['index']->options['data_alter_callbacks']['search_api_alter_comment_access']['status']);
|
||||
$new_status = !empty($form_state['values']['callbacks']['search_api_alter_comment_access']['status']);
|
||||
|
||||
if (!$old_status && $new_status) {
|
||||
$form_state['index']->options['fields']['status']['type'] = 'boolean';
|
||||
}
|
||||
|
||||
return parent::configurationFormSubmit($form, $values, $form_state);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiAlterLanguageControl.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search API data alteration callback that filters out items based on their
|
||||
* bundle.
|
||||
@ -7,12 +12,7 @@
|
||||
class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(SearchApiIndex $index, array $options = array()) {
|
||||
$options += array(
|
||||
@ -23,16 +23,10 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this data-alter callback is applicable for a certain index.
|
||||
* Overrides SearchApiAbstractAlterCallback::supportsIndex().
|
||||
*
|
||||
* Only returns TRUE if the system is multilingual.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index to check for.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if the callback can run on the given index; FALSE otherwise.
|
||||
*
|
||||
* @see drupal_multilingual()
|
||||
*/
|
||||
public function supportsIndex(SearchApiIndex $index) {
|
||||
@ -40,10 +34,7 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a form for configuring this data alteration.
|
||||
*
|
||||
* @return array
|
||||
* A form array for configuring this data alteration.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationForm() {
|
||||
$form = array();
|
||||
@ -98,19 +89,7 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
$values['languages'] = array_filter($values['languages']);
|
||||
@ -118,15 +97,7 @@ class SearchApiAlterLanguageControl extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter items before indexing.
|
||||
*
|
||||
* Items which are removed from the array won't be indexed, but will be marked
|
||||
* as clean for future indexing. This could for instance be used to implement
|
||||
* some sort of access filter for security purposes (e.g., don't index
|
||||
* unpublished nodes or comments).
|
||||
*
|
||||
* @param array $items
|
||||
* An array of items to be altered, keyed by item IDs.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
foreach ($items as $i => &$item) {
|
||||
|
@ -10,15 +10,9 @@
|
||||
class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
|
||||
|
||||
/**
|
||||
* Check whether this data-alter callback is applicable for a certain index.
|
||||
* Overrides SearchApiAbstractAlterCallback::supportsIndex().
|
||||
*
|
||||
* Returns TRUE only for indexes on nodes.
|
||||
*
|
||||
* @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) {
|
||||
// Currently only node access is supported.
|
||||
@ -26,15 +20,9 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the properties that are (or can be) added to items with this callback.
|
||||
* Overrides SearchApiAbstractAlterCallback::propertyInfo().
|
||||
*
|
||||
* Adds the "search_api_access_node" property.
|
||||
*
|
||||
* @see hook_entity_property_info()
|
||||
*
|
||||
* @return array
|
||||
* Information about all additional properties, as specified by
|
||||
* hook_entity_property_info() (only the inner "properties" array).
|
||||
*/
|
||||
public function propertyInfo() {
|
||||
return array(
|
||||
@ -47,15 +35,7 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter items before indexing.
|
||||
*
|
||||
* Items which are removed from the array won't be indexed, but will be marked
|
||||
* as clean for future indexing. This could for instance be used to implement
|
||||
* some sort of access filter for security purposes (e.g., don't index
|
||||
* unpublished nodes or comments).
|
||||
*
|
||||
* @param array $items
|
||||
* An array of items to be altered, keyed by item IDs.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function alterItems(array &$items) {
|
||||
static $account;
|
||||
@ -65,30 +45,39 @@ class SearchApiAlterNodeAccess extends SearchApiAbstractAlterCallback {
|
||||
$account = drupal_anonymous_user();
|
||||
}
|
||||
|
||||
foreach ($items as $nid => &$item) {
|
||||
foreach ($items as $id => $item) {
|
||||
$node = $this->getNode($item);
|
||||
// Check whether all users have access to the node.
|
||||
if (!node_access('view', $item, $account)) {
|
||||
if (!node_access('view', $node, $account)) {
|
||||
// Get node access grants.
|
||||
$result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $item->nid));
|
||||
$result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = :nid) AND grant_view = 1', array(':nid' => $node->nid));
|
||||
|
||||
// Store all grants together with it's realms in the item.
|
||||
// Store all grants together with their realms in the item.
|
||||
foreach ($result as $grant) {
|
||||
if (!isset($items[$nid]->search_api_access_node)) {
|
||||
$items[$nid]->search_api_access_node = array();
|
||||
}
|
||||
$items[$nid]->search_api_access_node[] = "node_access_$grant->realm:$grant->gid";
|
||||
$items[$id]->search_api_access_node[] = "node_access_{$grant->realm}:{$grant->gid}";
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Add the generic view grant if we are not using node access or the
|
||||
// node is viewable by anonymous users.
|
||||
$items[$nid]->search_api_access_node = array('node_access__all');
|
||||
$items[$id]->search_api_access_node = array('node_access__all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for the configuration form.
|
||||
* Retrieves the node related to a search item.
|
||||
*
|
||||
* In the default implementation for nodes, the item is already the node.
|
||||
* Subclasses may override this to easily provide node access checks for
|
||||
* items related to nodes.
|
||||
*/
|
||||
protected function getNode($item) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides SearchApiAbstractAlterCallback::configurationFormSubmit().
|
||||
*
|
||||
* If the data alteration is being enabled, set "Published" and "Author" to
|
||||
* "indexed", because both are needed for the node access filter.
|
||||
|
@ -18,46 +18,49 @@
|
||||
* 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.
|
||||
*/
|
||||
interface SearchApiDataSourceControllerInterface {
|
||||
|
||||
/**
|
||||
* Constructor for a data source controller.
|
||||
* Constructs a new data source controller.
|
||||
*
|
||||
* @param $type
|
||||
* @param string $type
|
||||
* The item type for which this controller is created.
|
||||
*/
|
||||
public function __construct($type);
|
||||
|
||||
/**
|
||||
* Return information on the ID field for this controller's type.
|
||||
* Returns information on the ID field for this controller's type.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the following keys:
|
||||
* - key: The property key for the ID field, as used in the item wrapper.
|
||||
* - type: The type of the ID field. Has to be one of the types from
|
||||
* search_api_field_types(). List types ("list<*>") are not allowed.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getIdFieldInfo();
|
||||
|
||||
/**
|
||||
* Load items of the type of this data source controller.
|
||||
* Loads items of the type of this data source controller.
|
||||
*
|
||||
* @param array $ids
|
||||
* The IDs of the items to laod.
|
||||
*
|
||||
* @return array
|
||||
* The loaded items, keyed by ID.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function loadItems(array $ids);
|
||||
|
||||
/**
|
||||
* Get a metadata wrapper for the item type of this data source controller.
|
||||
* Creates a metadata wrapper for this datasource controller's type.
|
||||
*
|
||||
* @param $item
|
||||
* @param mixed $item
|
||||
* Unless NULL, an item of the item type for this controller to be wrapped.
|
||||
* @param array $info
|
||||
* Optionally, additional information that should be used for creating the
|
||||
@ -67,151 +70,170 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* A wrapper for the item type of this data source controller, according to
|
||||
* the info array, and optionally loaded with the given data.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*
|
||||
* @see entity_metadata_wrapper()
|
||||
*/
|
||||
public function getMetadataWrapper($item = NULL, array $info = array());
|
||||
|
||||
/**
|
||||
* Get the unique ID of an item.
|
||||
* Retrieves the unique ID of an item.
|
||||
*
|
||||
* @param $item
|
||||
* @param mixed $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* @return mixed
|
||||
* Either the unique ID of the item, or NULL if none is available.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getItemId($item);
|
||||
|
||||
/**
|
||||
* Get a human-readable label for an item.
|
||||
* Retrieves a human-readable label for an item.
|
||||
*
|
||||
* @param $item
|
||||
* @param mixed $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* @return string|null
|
||||
* Either a human-readable label for the item, or NULL if none is available.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getItemLabel($item);
|
||||
|
||||
/**
|
||||
* Get a URL at which the item can be viewed on the web.
|
||||
* Retrieves a URL at which the item can be viewed on the web.
|
||||
*
|
||||
* @param $item
|
||||
* @param mixed $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* @return array|null
|
||||
* Either an array containing the 'path' and 'options' keys used to build
|
||||
* the URL of the item, and matching the signature of url(), or NULL if the
|
||||
* item has no URL of its own.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getItemUrl($item);
|
||||
|
||||
/**
|
||||
* Initialize tracking of the index status of items for the given indexes.
|
||||
* Initializes tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* All currently known items of this data source's type should be inserted
|
||||
* into the tracking table for the given indexes, with status "changed". If
|
||||
* items were already present, these should also be set to "changed" and not
|
||||
* be inserted again.
|
||||
*
|
||||
* @param array $indexes
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The SearchApiIndex objects for which item tracking should be initialized.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function startTracking(array $indexes);
|
||||
|
||||
/**
|
||||
* Stop tracking of the index status of items for the given indexes.
|
||||
* Stops tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* The tracking tables of the given indexes should be completely cleared.
|
||||
*
|
||||
* @param array $indexes
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The SearchApiIndex objects for which item tracking should be stopped.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function stopTracking(array $indexes);
|
||||
|
||||
/**
|
||||
* Start tracking the index status for the given items on the given indexes.
|
||||
* Starts tracking the index status for the given items on the given indexes.
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of new items to track.
|
||||
* @param array $indexes
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which items should be tracked.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function trackItemInsert(array $item_ids, array $indexes);
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "changed"/"dirty".
|
||||
* Sets the tracking status of the given items to "changed"/"dirty".
|
||||
*
|
||||
* Unless $dequeue is set to TRUE, this operation is ignored for items whose
|
||||
* status is not "indexed".
|
||||
*
|
||||
* @param $item_ids
|
||||
* @param array|false $item_ids
|
||||
* Either an array with the IDs of the changed items. Or FALSE to mark all
|
||||
* items as changed for the given indexes.
|
||||
* @param array $indexes
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which the change should be tracked.
|
||||
* @param $dequeue
|
||||
* If set to TRUE, also change the status of queued items.
|
||||
* @param bool $dequeue
|
||||
* (deprecated) If set to TRUE, also change the status of queued items.
|
||||
* The concept of queued items will be removed in the Drupal 8 version of
|
||||
* this module.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE);
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "queued".
|
||||
* Sets the tracking status of the given items to "queued".
|
||||
*
|
||||
* Queued items are not marked as "dirty" even when they are changed, and they
|
||||
* are not returned by the getChangedItems() method.
|
||||
*
|
||||
* @param $item_ids
|
||||
* @param array|false $item_ids
|
||||
* Either an array with the IDs of the queued items. Or FALSE to mark all
|
||||
* items as queued for the given indexes.
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which the items were queued.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*
|
||||
* @deprecated
|
||||
* As of Search API 1.10, the cron queue is not used for indexing anymore,
|
||||
* therefore this method has become useless. It will be removed in the
|
||||
* Drupal 8 version of this module.
|
||||
*/
|
||||
public function trackItemQueued($item_ids, SearchApiIndex $index);
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "indexed".
|
||||
* Sets the tracking status of the given items to "indexed".
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of the indexed items.
|
||||
* @param SearchApiIndex $indexes
|
||||
* @param SearchApiIndex $index
|
||||
* The index on which the items were indexed.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If the index doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function trackItemIndexed(array $item_ids, SearchApiIndex $index);
|
||||
|
||||
/**
|
||||
* Stop tracking the index status for the given items on the given indexes.
|
||||
* Stops tracking the index status for the given items on the given indexes.
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of the removed items.
|
||||
* @param array $indexes
|
||||
* @param SearchApiIndex[] $indexes
|
||||
* The indexes for which the deletions should be tracked.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function trackItemDelete(array $item_ids, array $indexes);
|
||||
|
||||
/**
|
||||
* Get a list of items that need to be indexed.
|
||||
* Retrieves a list of items that need to be indexed.
|
||||
*
|
||||
* If possible, completely unindexed items should be returned before items
|
||||
* that were indexed but later changed. Also, items that were changed longer
|
||||
@ -219,16 +241,19 @@ interface SearchApiDataSourceControllerInterface {
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which changed items should be returned.
|
||||
* @param $limit
|
||||
* @param int $limit
|
||||
* The maximum number of items to return. Negative values mean "unlimited".
|
||||
*
|
||||
* @return array
|
||||
* The IDs of items that need to be indexed for the given index.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getChangedItems(SearchApiIndex $index, $limit = -1);
|
||||
|
||||
/**
|
||||
* Get information on how many items have been indexed for a certain index.
|
||||
* Retrieves information on how many items have been indexed for a certain index.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose index status should be returned.
|
||||
@ -240,22 +265,26 @@ interface SearchApiDataSourceControllerInterface {
|
||||
* index.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If the index doesn't use the same item type as this controller.
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getIndexStatus(SearchApiIndex $index);
|
||||
|
||||
/**
|
||||
* Get the entity type of items from this datasource.
|
||||
* Retrieves 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.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
public function getEntityType();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default base class for the SearchApiDataSourceControllerInterface.
|
||||
* Provides a default base class for datasource controllers.
|
||||
*
|
||||
* Contains default implementations for a number of methods which will be
|
||||
* similar for most data sources. Concrete data sources can decide to extend
|
||||
@ -330,10 +359,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
protected $changedColumn = 'changed';
|
||||
|
||||
/**
|
||||
* Constructor for a data source controller.
|
||||
*
|
||||
* @param $type
|
||||
* The item type for which this controller is created.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($type) {
|
||||
$this->type = $type;
|
||||
@ -345,30 +371,14 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityType() {
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a metadata wrapper for the item type of this data source controller.
|
||||
*
|
||||
* @param $item
|
||||
* Unless NULL, an item of the item type for this controller to be wrapped.
|
||||
* @param array $info
|
||||
* Optionally, additional information that should be used for creating the
|
||||
* wrapper. Uses the same format as entity_metadata_wrapper().
|
||||
*
|
||||
* @return EntityMetadataWrapper
|
||||
* A wrapper for the item type of this data source controller, according to
|
||||
* the info array, and optionally loaded with the given data.
|
||||
*
|
||||
* @see entity_metadata_wrapper()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadataWrapper($item = NULL, array $info = array()) {
|
||||
$info += $this->getPropertyInfo();
|
||||
@ -376,7 +386,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property info for this item type.
|
||||
* Retrieves 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
|
||||
@ -384,7 +394,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
*
|
||||
* 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
|
||||
* the entity types) is omitted, and the "properties" key is called
|
||||
* "property info" instead. So, an example return value would look like this:
|
||||
*
|
||||
* @code
|
||||
@ -413,6 +423,9 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
* @return array
|
||||
* Property information as specified by entity_metadata_wrapper().
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*
|
||||
* @see getMetadataWrapper()
|
||||
* @see hook_entity_property_info()
|
||||
*/
|
||||
@ -425,13 +438,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID of an item.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either the unique ID of the item, or NULL if none is available.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemId($item) {
|
||||
$id_info = $this->getIdFieldInfo();
|
||||
@ -445,13 +452,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable label for an item.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either a human-readable label for the item, or NULL if none is available.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemLabel($item) {
|
||||
$label = $this->getMetadataWrapper($item)->label();
|
||||
@ -459,33 +460,14 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL at which the item can be viewed on the web.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either an array containing the 'path' and 'options' keys used to build
|
||||
* the URL of the item, and matching the signature of url(), or NULL if the
|
||||
* item has no URL of its own.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemUrl($item) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* All currently known items of this data source's type should be inserted
|
||||
* into the tracking table for the given indexes, with status "changed". If
|
||||
* items were already present, these should also be set to "changed" and not
|
||||
* be inserted again.
|
||||
*
|
||||
* @param array $indexes
|
||||
* The SearchApiIndex objects for which item tracking should be initialized.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function startTracking(array $indexes) {
|
||||
if (!$this->table) {
|
||||
@ -499,27 +481,23 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that can be used by subclasses instead of implementing startTracking().
|
||||
*
|
||||
* Returns the IDs of all items that are known for this controller's type.
|
||||
*
|
||||
* Helper method that can be used by subclasses instead of implementing
|
||||
* startTracking().
|
||||
*
|
||||
* @return array
|
||||
* An array containing all item IDs for this type.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any error state was encountered.
|
||||
*/
|
||||
protected function getAllItemIds() {
|
||||
throw new SearchApiDataSourceException(t('Items not known for type @type.', array('@type' => $this->type)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* The tracking tables of the given indexes should be completely cleared.
|
||||
*
|
||||
* @param array $indexes
|
||||
* The SearchApiIndex objects for which item tracking should be stopped.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stopTracking(array $indexes) {
|
||||
if (!$this->table) {
|
||||
@ -529,22 +507,14 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
// will mostly be called with only one index.
|
||||
foreach ($indexes as $index) {
|
||||
$this->checkIndex($index);
|
||||
$query = db_delete($this->table)
|
||||
db_delete($this->table)
|
||||
->condition($this->indexIdColumn, $index->id)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking the index status for the given items on the given indexes.
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of new items to track.
|
||||
* @param array $indexes
|
||||
* The indexes for which items should be tracked.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemInsert(array $item_ids, array $indexes) {
|
||||
if (!$this->table) {
|
||||
@ -571,21 +541,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "changed"/"dirty".
|
||||
*
|
||||
* Unless $dequeue is set to TRUE, this operation is ignored for items whose
|
||||
* status is not "indexed".
|
||||
*
|
||||
* @param $item_ids
|
||||
* Either an array with the IDs of the changed items. Or FALSE to mark all
|
||||
* items as changed for the given indexes.
|
||||
* @param array $indexes
|
||||
* The indexes for which the change should be tracked.
|
||||
* @param $dequeue
|
||||
* If set to TRUE, also change the status of queued items.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemChange($item_ids, array $indexes, $dequeue = FALSE) {
|
||||
if (!$this->table) {
|
||||
@ -609,21 +565,10 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "queued".
|
||||
*
|
||||
* Queued items are not marked as "dirty" even when they are changed, and they
|
||||
* are not returned by the getChangedItems() method.
|
||||
*
|
||||
* @param $item_ids
|
||||
* Either an array with the IDs of the queued items. Or FALSE to mark all
|
||||
* items as queued for the given indexes.
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which the items were queued.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemQueued($item_ids, SearchApiIndex $index) {
|
||||
$this->checkIndex($index);
|
||||
if (!$this->table) {
|
||||
return;
|
||||
}
|
||||
@ -639,15 +584,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tracking status of the given items to "indexed".
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of the indexed items.
|
||||
* @param SearchApiIndex $indexes
|
||||
* The index on which the items were indexed.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If the index doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemIndexed(array $item_ids, SearchApiIndex $index) {
|
||||
if (!$this->table) {
|
||||
@ -664,15 +601,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking the index status for the given items on the given indexes.
|
||||
*
|
||||
* @param array $item_ids
|
||||
* The IDs of the removed items.
|
||||
* @param array $indexes
|
||||
* The indexes for which the deletions should be tracked.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trackItemDelete(array $item_ids, array $indexes) {
|
||||
if (!$this->table) {
|
||||
@ -690,19 +619,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of items that need to be indexed.
|
||||
*
|
||||
* If possible, completely unindexed items should be returned before items
|
||||
* that were indexed but later changed. Also, items that were changed longer
|
||||
* ago should be favored.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index for which changed items should be returned.
|
||||
* @param $limit
|
||||
* The maximum number of items to return. Negative values mean "unlimited".
|
||||
*
|
||||
* @return array
|
||||
* The IDs of items that need to be indexed for the given index.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChangedItems(SearchApiIndex $index, $limit = -1) {
|
||||
if ($limit == 0) {
|
||||
@ -721,16 +638,7 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information on how many items have been indexed for a certain index.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index whose index status should be returned.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing two keys (in this order):
|
||||
* - indexed: The number of items already indexed in their latest version.
|
||||
* - total: The total number of items that have to be indexed for this
|
||||
* index.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIndexStatus(SearchApiIndex $index) {
|
||||
if (!$this->table) {
|
||||
@ -752,13 +660,16 @@ abstract class SearchApiAbstractDataSourceController implements SearchApiDataSou
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for ensuring that an index uses the same item type as this controller.
|
||||
* Checks whether the given index is valid for this datasource controller.
|
||||
*
|
||||
* Helper method used by various methods in this class. By default only checks
|
||||
* whether the types match.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index to check.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If the index doesn't use the same type as this controller.
|
||||
* If the index doesn't fit to this datasource controller.
|
||||
*/
|
||||
protected function checkIndex(SearchApiIndex $index) {
|
||||
if ($index->item_type != $this->type) {
|
||||
|
@ -6,18 +6,12 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data source for all entities known to the Entity API.
|
||||
* Represents a datasource for all entities known to the Entity API.
|
||||
*/
|
||||
class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceController {
|
||||
|
||||
/**
|
||||
* Return information on the ID field for this controller's type.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the following keys:
|
||||
* - key: The property key for the ID field, as used in the item wrapper.
|
||||
* - type: The type of the ID field. Has to be one of the types from
|
||||
* search_api_field_types(). List types ("list<*>") are not allowed.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdFieldInfo() {
|
||||
$info = entity_get_info($this->entityType);
|
||||
@ -43,13 +37,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Load items of the type of this data source controller.
|
||||
*
|
||||
* @param array $ids
|
||||
* The IDs of the items to laod.
|
||||
*
|
||||
* @return array
|
||||
* The loaded items, keyed by ID.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadItems(array $ids) {
|
||||
$items = entity_load($this->entityType, $ids);
|
||||
@ -65,32 +53,14 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a metadata wrapper for the item type of this data source controller.
|
||||
*
|
||||
* @param $item
|
||||
* Unless NULL, an item of the item type for this controller to be wrapped.
|
||||
* @param array $info
|
||||
* Optionally, additional information that should be used for creating the
|
||||
* wrapper. Uses the same format as entity_metadata_wrapper().
|
||||
*
|
||||
* @return EntityMetadataWrapper
|
||||
* A wrapper for the item type of this data source controller, according to
|
||||
* the info array, and optionally loaded with the given data.
|
||||
*
|
||||
* @see entity_metadata_wrapper()
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadataWrapper($item = NULL, array $info = array()) {
|
||||
return entity_metadata_wrapper($this->entityType, $item, $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID of an item.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either the unique ID of the item, or NULL if none is available.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemId($item) {
|
||||
$id = entity_id($this->entityType, $item);
|
||||
@ -98,13 +68,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable label for an item.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either a human-readable label for the item, or NULL if none is available.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemLabel($item) {
|
||||
$label = entity_label($this->entityType, $item);
|
||||
@ -112,15 +76,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL at which the item can be viewed on the web.
|
||||
*
|
||||
* @param $item
|
||||
* An item of this controller's type.
|
||||
*
|
||||
* @return
|
||||
* Either an array containing the 'path' and 'options' keys used to build
|
||||
* the URL of the item, and matching the signature of url(), or NULL if the
|
||||
* item has no URL of its own.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemUrl($item) {
|
||||
if ($this->entityType == 'file') {
|
||||
@ -137,18 +93,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tracking of the index status of items for the given indexes.
|
||||
*
|
||||
* All currently known items of this data source's type should be inserted
|
||||
* into the tracking table for the given indexes, with status "changed". If
|
||||
* items were already present, these should also be set to "changed" and not
|
||||
* be inserted again.
|
||||
*
|
||||
* @param array $indexes
|
||||
* The SearchApiIndex objects for which item tracking should be initialized.
|
||||
*
|
||||
* @throws SearchApiDataSourceException
|
||||
* If any of the indexes doesn't use the same item type as this controller.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function startTracking(array $indexes) {
|
||||
if (!$this->table) {
|
||||
@ -190,14 +135,7 @@ class SearchApiEntityDataSourceController extends SearchApiAbstractDataSourceCon
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that can be used by subclasses instead of implementing startTracking().
|
||||
*
|
||||
* Returns the IDs of all items that are known for this controller's type.
|
||||
*
|
||||
* Will be used when the entity type doesn't specify a "base table".
|
||||
*
|
||||
* @return array
|
||||
* An array containing all item IDs for this type.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAllItemIds() {
|
||||
return array_keys(entity_load($this->entityType));
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiException.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an exception or error that occurred in some part of the Search API
|
||||
* framework.
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiIndex.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a search index.
|
||||
*/
|
||||
@ -178,18 +183,9 @@ class SearchApiIndex extends Entity {
|
||||
if ($this->enabled) {
|
||||
$this->queueItems();
|
||||
}
|
||||
$server = $this->server();
|
||||
if ($server) {
|
||||
if ($server = $this->server()) {
|
||||
// Tell the server about the new index.
|
||||
if ($server->enabled) {
|
||||
$server->addIndex($this);
|
||||
}
|
||||
else {
|
||||
$tasks = variable_get('search_api_tasks', array());
|
||||
// When we add or remove an index, we can ignore all other tasks.
|
||||
$tasks[$server->machine_name][$this->machine_name] = array('add');
|
||||
variable_set('search_api_tasks', $tasks);
|
||||
}
|
||||
$server->addIndex($this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,18 +194,7 @@ class SearchApiIndex extends Entity {
|
||||
*/
|
||||
public function postDelete() {
|
||||
if ($server = $this->server()) {
|
||||
if ($server->enabled) {
|
||||
$server->removeIndex($this);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
$server->removeIndex($this);
|
||||
}
|
||||
|
||||
// Stop tracking entities for indexing.
|
||||
@ -230,14 +215,14 @@ class SearchApiIndex extends Entity {
|
||||
*/
|
||||
public function dequeueItems() {
|
||||
$this->datasource()->stopTracking(array($this));
|
||||
_search_api_empty_cron_queue($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this index to the database, either creating a new record or updating
|
||||
* an existing one.
|
||||
* Saves this index to the database.
|
||||
*
|
||||
* @return
|
||||
* Either creates a new record or updates the existing one with the same ID.
|
||||
*
|
||||
* @return int|false
|
||||
* Failure to save the index will return FALSE. Otherwise, SAVED_NEW or
|
||||
* SAVED_UPDATED is returned depending on the operation performed. $this->id
|
||||
* will be set if a new index was inserted.
|
||||
@ -253,6 +238,7 @@ class SearchApiIndex extends Entity {
|
||||
// This will also throw an exception if the server doesn't exist – which is good.
|
||||
elseif (!$this->server(TRUE)->enabled) {
|
||||
$this->enabled = FALSE;
|
||||
$this->server = NULL;
|
||||
}
|
||||
|
||||
return parent::save();
|
||||
@ -267,7 +253,7 @@ class SearchApiIndex extends Entity {
|
||||
* @param array $fields
|
||||
* The new field values.
|
||||
*
|
||||
* @return
|
||||
* @return int|false
|
||||
* SAVE_UPDATED on success, FALSE on failure, 0 if the fields already had
|
||||
* the specified values.
|
||||
*/
|
||||
@ -296,7 +282,7 @@ class SearchApiIndex extends Entity {
|
||||
/**
|
||||
* Schedules this search index for re-indexing.
|
||||
*
|
||||
* @return
|
||||
* @return bool
|
||||
* TRUE on success, FALSE on failure.
|
||||
*/
|
||||
public function reindex() {
|
||||
@ -311,7 +297,7 @@ class SearchApiIndex extends Entity {
|
||||
/**
|
||||
* Clears this search index and schedules all of its items for re-indexing.
|
||||
*
|
||||
* @return
|
||||
* @return bool
|
||||
* TRUE on success, FALSE on failure.
|
||||
*/
|
||||
public function clear() {
|
||||
@ -319,20 +305,7 @@ class SearchApiIndex extends Entity {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$server = $this->server();
|
||||
if ($server->enabled) {
|
||||
$server->deleteItems('all', $this);
|
||||
}
|
||||
else {
|
||||
$tasks = variable_get('search_api_tasks', array());
|
||||
// If the index was cleared or newly added since the server was last enabled, we don't need to do anything.
|
||||
if (!isset($tasks[$server->machine_name][$this->machine_name])
|
||||
|| (array_search('add', $tasks[$server->machine_name][$this->machine_name]) === FALSE
|
||||
&& array_search('clear', $tasks[$server->machine_name][$this->machine_name]) === FALSE)) {
|
||||
$tasks[$server->machine_name][$this->machine_name][] = 'clear';
|
||||
variable_set('search_api_tasks', $tasks);
|
||||
}
|
||||
}
|
||||
$this->server()->deleteItems('all', $this);
|
||||
|
||||
_search_api_index_reindex($this);
|
||||
module_invoke_all('search_api_index_reindex', $this, TRUE);
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiProcessorInterface and SearchApiAbstractProcessor.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface representing a Search API pre- and/or post-processor.
|
||||
*
|
||||
@ -27,7 +32,7 @@ interface SearchApiProcessorInterface {
|
||||
/**
|
||||
* Check whether this processor is applicable for a certain index.
|
||||
*
|
||||
* This can be used for hiding the processor on the index's "Workflow" tab. To
|
||||
* This can be used for hiding the processor on the index's "Filters" tab. To
|
||||
* avoid confusion, you should only use criteria that are immutable, such as
|
||||
* the index's item type. Also, since this is only used for UI purposes, you
|
||||
* should not completely rely on this to ensure certain index configurations
|
||||
|
@ -150,8 +150,8 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
/**
|
||||
* Retrieves the fulltext data of a result.
|
||||
*
|
||||
* @param array $result
|
||||
* All results returned in the search.
|
||||
* @param array $results
|
||||
* All results returned in the search, by reference.
|
||||
* @param int|string $i
|
||||
* The index in the results array of the result whose data should be
|
||||
* returned.
|
||||
@ -164,11 +164,12 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
* contained in them for the given result.
|
||||
*/
|
||||
protected function getFulltextFields(array &$results, $i, $load = TRUE) {
|
||||
global $language;
|
||||
$data = array();
|
||||
// Act as if $load is TRUE if we have a loaded item.
|
||||
$load |= !empty($result['entity']);
|
||||
|
||||
$result = &$results[$i];
|
||||
// Act as if $load is TRUE if we have a loaded item.
|
||||
$load |= !empty($result['entity']);
|
||||
$result += array('fields' => array());
|
||||
$fulltext_fields = $this->index->getFulltextFields();
|
||||
// We only need detailed fields data if $load is TRUE.
|
||||
@ -198,6 +199,7 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
return $data;
|
||||
}
|
||||
$wrapper = $this->index->entityWrapper($result['entity'], FALSE);
|
||||
$wrapper->language($language->language);
|
||||
$extracted = search_api_extract_fields($wrapper, $needs_extraction);
|
||||
|
||||
foreach ($extracted as $field => $info) {
|
||||
@ -292,7 +294,6 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
// 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)) {
|
||||
@ -394,8 +395,9 @@ class SearchApiHighlight extends SearchApiAbstractProcessor {
|
||||
*/
|
||||
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);
|
||||
$keys = implode('|', array_map('preg_quote', $keys));
|
||||
$text = preg_replace('/' . self::$boundary . '(' . $keys . ')' . self::$boundary . '/iu', $replace, ' ' . $text . ' ');
|
||||
return substr($text, 1, -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiHtmlFilter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processor for stripping HTML from indexed fulltext data. Supports assigning
|
||||
* custom boosts for any HTML element.
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiIgnoreCase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processor for making searches case-insensitive.
|
||||
*/
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiStopWords.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processor for removing stopwords from index and search terms.
|
||||
*/
|
||||
@ -21,8 +26,7 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
|
||||
),
|
||||
'file' => array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Stopwords file URI'),
|
||||
'#title' => t('Enter the URI of your stopwords.txt file'),
|
||||
'#title' => t('Stopwords file'),
|
||||
'#description' => t('This must be a stream-type description like <code>public://stopwords/stopwords.txt</code> or <code>http://example.com/stopwords.txt</code> or <code>private://stopwords.txt</code>.'),
|
||||
),
|
||||
'stopwords' => array(
|
||||
@ -43,13 +47,8 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
|
||||
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
|
||||
parent::configurationFormValidate($form, $values, $form_state);
|
||||
|
||||
$stopwords = trim($values['stopwords']);
|
||||
$uri = $values['file'];
|
||||
if (empty($stopwords) && empty($uri)) {
|
||||
$el = $form['file'];
|
||||
form_error($el, $el['#title'] . ': ' . t('At stopwords file or words are required.'));
|
||||
}
|
||||
if (!empty($uri) && !file_get_contents($uri)) {
|
||||
if (!empty($uri) && !@file_get_contents($uri)) {
|
||||
$el = $form['file'];
|
||||
form_error($el, t('Stopwords file') . ': ' . t('The file %uri is not readable or does not exist.', array('%uri' => $uri)));
|
||||
}
|
||||
@ -57,7 +56,7 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
|
||||
|
||||
public function process(&$value) {
|
||||
$stopwords = $this->getStopWords();
|
||||
if (empty($stopwords) && !is_string($value)) {
|
||||
if (empty($stopwords) || !is_string($value)) {
|
||||
return;
|
||||
}
|
||||
$words = preg_split('/\s+/', $value);
|
||||
@ -105,4 +104,4 @@ class SearchApiStopWords extends SearchApiAbstractProcessor {
|
||||
$this->stopwords = array_flip(array_merge($file_words, $form_words));
|
||||
return $this->stopwords;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiTokenizer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processor for tokenizing fulltext data by replacing (configurable)
|
||||
* non-letters with spaces.
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiTransliteration.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processor for making searches insensitive to accents and other non-ASCII characters.
|
||||
*/
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiQueryInterface and SearchApiQuery.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface representing a search query on an Search API index.
|
||||
*
|
||||
@ -33,6 +38,10 @@ interface SearchApiQueryInterface {
|
||||
* 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.
|
||||
* - 'skip result count': If present and set to TRUE, the result's
|
||||
* "result count" key will not be needed. Service classes can check for
|
||||
* this option to possibly avoid executing expensive operations to compute
|
||||
* the result count in cases where it is not needed.
|
||||
* - 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
|
||||
@ -62,11 +71,15 @@ interface SearchApiQueryInterface {
|
||||
*
|
||||
* @param string $conjunction
|
||||
* The conjunction to use for the filter - either 'AND' or 'OR'.
|
||||
* @param $tags
|
||||
* (Optional) An arbitrary set of tags. Can be used to identify this filter
|
||||
* down the line if necessary. This is primarily used by the facet system
|
||||
* to support OR facet queries.
|
||||
*
|
||||
* @return SearchApiQueryFilterInterface
|
||||
* A filter object that is set to use the specified conjunction.
|
||||
*/
|
||||
public function createFilter($conjunction = 'AND');
|
||||
public function createFilter($conjunction = 'AND', $tags = array());
|
||||
|
||||
/**
|
||||
* Sets the keys to search for.
|
||||
@ -175,7 +188,9 @@ interface SearchApiQueryInterface {
|
||||
* 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.
|
||||
* range restrictions. Might be approximated, for large numbers, or
|
||||
* skipped entirely if the "skip result count" option was set on this
|
||||
* query.
|
||||
* - 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.
|
||||
@ -318,7 +333,8 @@ interface SearchApiQueryInterface {
|
||||
* @param mixed $value
|
||||
* The new value of the option.
|
||||
*
|
||||
* @return The option's previous value.
|
||||
* @return mixed
|
||||
* The option's previous value.
|
||||
*/
|
||||
public function setOption($name, $value);
|
||||
|
||||
@ -341,12 +357,21 @@ interface SearchApiQueryInterface {
|
||||
class SearchApiQuery implements SearchApiQueryInterface {
|
||||
|
||||
/**
|
||||
* The index.
|
||||
* The index this query will use.
|
||||
*
|
||||
* @var SearchApiIndex
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* The index's machine name.
|
||||
*
|
||||
* used during serialization to avoid serializing the whole index object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $index_id;
|
||||
|
||||
/**
|
||||
* The search keys. If NULL, this will be a filter-only search.
|
||||
*
|
||||
@ -503,9 +528,9 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createFilter($conjunction = 'AND') {
|
||||
public function createFilter($conjunction = 'AND', $tags = array()) {
|
||||
$filter_class = $this->options['filter class'];
|
||||
return new $filter_class($conjunction);
|
||||
return new $filter_class($conjunction, $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -616,6 +641,9 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
*
|
||||
* @param array $languages
|
||||
* The languages for which results should be returned.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If there was a logical error in the combination of filters and languages.
|
||||
*/
|
||||
protected function addLanguages(array $languages) {
|
||||
if (array_search(LANGUAGE_NONE, $languages) === FALSE) {
|
||||
@ -776,6 +804,13 @@ class SearchApiQuery implements SearchApiQueryInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __clone() method to clone the filter, too.
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->filter = clone $this->filter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -790,9 +825,13 @@ interface SearchApiQueryFilterInterface {
|
||||
* Constructs a new filter that uses the specified conjunction.
|
||||
*
|
||||
* @param string $conjunction
|
||||
* The conjunction to use for this filter - either 'AND' or 'OR'.
|
||||
* (optional) The conjunction to use for this filter - either 'AND' or 'OR'.
|
||||
* @param array $tags
|
||||
* (optional) An arbitrary set of tags. Can be used to identify this filter
|
||||
* down the line if necessary. This is primarily used by the facet system
|
||||
* to support OR facet queries.
|
||||
*/
|
||||
public function __construct($conjunction = 'AND');
|
||||
public function __construct($conjunction = 'AND', array $tags = array());
|
||||
|
||||
/**
|
||||
* Sets this filter's conjunction.
|
||||
@ -856,6 +895,25 @@ interface SearchApiQueryFilterInterface {
|
||||
*/
|
||||
public function &getFilters();
|
||||
|
||||
/**
|
||||
* Checks whether a certain tag was set on this filter.
|
||||
*
|
||||
* @param string $tag
|
||||
* A tag to check for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the tag was set for this filter, FALSE otherwise.
|
||||
*/
|
||||
public function hasTag($tag);
|
||||
|
||||
/**
|
||||
* Retrieves the tags set on this filter.
|
||||
*
|
||||
* @return array
|
||||
* The tags associated with this filter, as both the array keys and values.
|
||||
*/
|
||||
public function &getTags();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -883,9 +941,10 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($conjunction = 'AND') {
|
||||
public function __construct($conjunction = 'AND', array $tags = array()) {
|
||||
$this->setConjunction($conjunction);
|
||||
$this->filters = array();
|
||||
$this->tags = drupal_map_assoc($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -926,4 +985,29 @@ class SearchApiQueryFilter implements SearchApiQueryFilterInterface {
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasTag($tag) {
|
||||
return isset($this->tags[$tag]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function &getTags() {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __clone() method to clone nested filters, too.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->filters as $i => $filter) {
|
||||
if (is_object($filter)) {
|
||||
$this->filters[$i] = clone $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiServer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class representing a search server.
|
||||
*
|
||||
@ -82,7 +87,7 @@ class SearchApiServer extends Entity {
|
||||
* @param array $fields
|
||||
* The new field values.
|
||||
*
|
||||
* @return
|
||||
* @return int|false
|
||||
* SAVE_UPDATED on success, FALSE on failure, 0 if the fields already had
|
||||
* the specified values.
|
||||
*/
|
||||
@ -136,6 +141,8 @@ class SearchApiServer extends Entity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts to calls of undefined methods on this object.
|
||||
*
|
||||
* If the service class defines additional methods, not specified in the
|
||||
* SearchApiServiceInterface interface, then they are called via this magic
|
||||
* method.
|
||||
@ -148,81 +155,242 @@ class SearchApiServer extends Entity {
|
||||
// Proxy methods
|
||||
|
||||
// For increased clarity, and since some parameters are passed by reference,
|
||||
// we don't use the __call() magic method for those.
|
||||
// we don't use the __call() magic method for those. This also gives us the
|
||||
// opportunity to do additional error handling.
|
||||
|
||||
/**
|
||||
* Form constructor for the server configuration form.
|
||||
*
|
||||
* @see SearchApiServiceInterface::configurationForm()
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->configurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation callback for the form returned by configurationForm().
|
||||
*
|
||||
* @see SearchApiServiceInterface::configurationFormValidate()
|
||||
*/
|
||||
public function configurationFormValidate(array $form, array &$values, array &$form_state) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->configurationFormValidate($form, $values, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit callback for the form returned by configurationForm().
|
||||
*
|
||||
* @see SearchApiServiceInterface::configurationFormSubmit()
|
||||
*/
|
||||
public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->configurationFormSubmit($form, $values, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this service class supports a given feature.
|
||||
*
|
||||
* @see SearchApiServiceInterface::supportsFeature()
|
||||
*/
|
||||
public function supportsFeature($feature) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->supportsFeature($feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays this server's settings.
|
||||
*
|
||||
* @see SearchApiServiceInterface::viewSettings()
|
||||
*/
|
||||
public function viewSettings() {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->viewSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts to the server's creation.
|
||||
*
|
||||
* @see SearchApiServiceInterface::postCreate()
|
||||
*/
|
||||
public function postCreate() {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->postCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this server that its fields are about to be updated.
|
||||
*
|
||||
* @see SearchApiServiceInterface::postUpdate()
|
||||
*/
|
||||
public function postUpdate() {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->postUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this server that it is about to be deleted from the database.
|
||||
*
|
||||
* @see SearchApiServiceInterface::preDelete()
|
||||
*/
|
||||
public function preDelete() {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->preDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new index to this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::addIndex()
|
||||
* @see search_api_server_tasks_add()
|
||||
*/
|
||||
public function addIndex(SearchApiIndex $index) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->addIndex($index);
|
||||
try {
|
||||
$this->proxy->addIndex($index);
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$vars = array(
|
||||
'%server' => $this->name,
|
||||
'%index' => $index->name,
|
||||
);
|
||||
watchdog_exception('search_api', $e, '%type while adding index %index to server %server: !message in %function (line %line of %file).', $vars);
|
||||
search_api_server_tasks_add($this, __FUNCTION__, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the server that the field settings for the index have changed.
|
||||
*
|
||||
* If the service class implementation of the method returns TRUE, this will
|
||||
* automatically take care of marking the items on the index for re-indexing.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::fieldsUpdated()
|
||||
* @see search_api_server_tasks_add()
|
||||
*/
|
||||
public function fieldsUpdated(SearchApiIndex $index) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->fieldsUpdated($index);
|
||||
try {
|
||||
if ($this->proxy->fieldsUpdated($index)) {
|
||||
_search_api_index_reindex($index);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$vars = array(
|
||||
'%server' => $this->name,
|
||||
'%index' => $index->name,
|
||||
);
|
||||
watchdog_exception('search_api', $e, '%type while updating the fields of index %index on server %server: !message in %function (line %line of %file).', $vars);
|
||||
search_api_server_tasks_add($this, __FUNCTION__, $index, isset($index->original) ? $index->original : NULL);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an index from this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::removeIndex()
|
||||
* @see search_api_server_tasks_add()
|
||||
*/
|
||||
public function removeIndex($index) {
|
||||
// When removing an index from a server, it doesn't make any sense anymore to
|
||||
// delete items from it, or react to other changes.
|
||||
search_api_server_tasks_delete(NULL, $this, $index);
|
||||
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->removeIndex($index);
|
||||
try {
|
||||
$this->proxy->removeIndex($index);
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$vars = array(
|
||||
'%server' => $this->name,
|
||||
'%index' => is_object($index) ? $index->name : $index,
|
||||
);
|
||||
watchdog_exception('search_api', $e, '%type while removing index %index from server %server: !message in %function (line %line of %file).', $vars);
|
||||
search_api_server_tasks_add($this, __FUNCTION__, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes the specified items.
|
||||
*
|
||||
* @see SearchApiServiceInterface::indexItems()
|
||||
*/
|
||||
public function indexItems(SearchApiIndex $index, array $items) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->indexItems($index, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes indexed items from this server.
|
||||
*
|
||||
* If an exception in the service class implementation of this method occcurs,
|
||||
* it will be caught and the operation saved as an pending server task.
|
||||
*
|
||||
* @see SearchApiServiceInterface::deleteItems()
|
||||
* @see search_api_server_tasks_add()
|
||||
*/
|
||||
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->deleteItems($ids, $index);
|
||||
try {
|
||||
$this->proxy->deleteItems($ids, $index);
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$vars = array(
|
||||
'%server' => $this->name,
|
||||
);
|
||||
watchdog_exception('search_api', $e, '%type while deleting items from server %server: !message in %function (line %line of %file).', $vars);
|
||||
search_api_server_tasks_add($this, __FUNCTION__, $index, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query object for searching on an index lying on this server.
|
||||
*
|
||||
* @see SearchApiServiceInterface::query()
|
||||
*/
|
||||
public function query(SearchApiIndex $index, $options = array()) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->query($index, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a search on the server represented by this object.
|
||||
*
|
||||
* @see SearchApiServiceInterface::search()
|
||||
*/
|
||||
public function search(SearchApiQueryInterface $query) {
|
||||
$this->ensureProxy();
|
||||
return $this->proxy->search($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves additional information for the server, if available.
|
||||
*
|
||||
* Retrieving such information is only supported if the service class supports
|
||||
* the "search_api_service_extra" feature.
|
||||
*
|
||||
* @return array
|
||||
* An array containing additional, service class-specific information about
|
||||
* the server.
|
||||
*
|
||||
* @see SearchApiAbstractService::getExtraInformation()
|
||||
*/
|
||||
public function getExtraInformation() {
|
||||
if ($this->proxy->supportsFeature('search_api_service_extra')) {
|
||||
return $this->proxy->getExtraInformation();
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains SearchApiServiceInterface and SearchApiAbstractService.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface defining the methods search services have to implement.
|
||||
*
|
||||
* Before a service object is used, the corresponding server's data will be read
|
||||
* from the database (see SearchApiAbstractService for a list of fields).
|
||||
*
|
||||
* Most methods in this interface (where any change in data occurs) can throw a
|
||||
* SearchApiException. The server entity class SearchApiServer catches these
|
||||
* exceptions and uses the server tasks system to assure that the action is
|
||||
* later retried.
|
||||
*/
|
||||
interface SearchApiServiceInterface {
|
||||
|
||||
@ -19,8 +29,15 @@ interface SearchApiServiceInterface {
|
||||
public function __construct(SearchApiServer $server);
|
||||
|
||||
/**
|
||||
* Form callback. Might be called on an uninitialized object - in this case,
|
||||
* the form is for configuring a newly created server.
|
||||
* Form constructor for the server configuration form.
|
||||
*
|
||||
* Might be called with an incomplete server (no ID). In this case, the form
|
||||
* is displayed for the initial creation of the server.
|
||||
*
|
||||
* @param array $form
|
||||
* The server options part of the form.
|
||||
* @param array $form_state
|
||||
* The current form state.
|
||||
*
|
||||
* @return array
|
||||
* A form array for setting service-specific options.
|
||||
@ -81,29 +98,36 @@ interface SearchApiServiceInterface {
|
||||
public function supportsFeature($feature);
|
||||
|
||||
/**
|
||||
* View this server's settings. Output can be HTML or a render array, a <dl>
|
||||
* listing all relevant settings is preferred.
|
||||
* Displays this server's settings.
|
||||
*
|
||||
* Output can be HTML or a render array, a <dl> listing all relevant settings
|
||||
* is preferred.
|
||||
*/
|
||||
public function viewSettings();
|
||||
|
||||
/**
|
||||
* Reacts to the server's creation.
|
||||
*
|
||||
* Called once, when the server is first created. Allows it to set up its
|
||||
* necessary infrastructure.
|
||||
*/
|
||||
public function postCreate();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Notifies this server that its fields are about to be updated.
|
||||
*
|
||||
* @return
|
||||
* The server's $original property can be used to inspect the old property
|
||||
* values.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE, if the update requires reindexing of all content on the server.
|
||||
*/
|
||||
public function postUpdate();
|
||||
|
||||
/**
|
||||
* Notifies this server that it is about to be deleted from the database and
|
||||
* should therefore clean up, if appropriate.
|
||||
* Notifies this server that it is about to be deleted from the database.
|
||||
*
|
||||
* This should execute any necessary cleanup operations.
|
||||
*
|
||||
* 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
|
||||
@ -112,18 +136,21 @@ interface SearchApiServiceInterface {
|
||||
public function preDelete();
|
||||
|
||||
/**
|
||||
* Add a new index to this server.
|
||||
* Adds a new index to this server.
|
||||
*
|
||||
* If the index was already added to the server, the object should treat this
|
||||
* as if removeIndex() and then addIndex() were called.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index to add.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If an error occurred while adding the index.
|
||||
*/
|
||||
public function addIndex(SearchApiIndex $index);
|
||||
|
||||
/**
|
||||
* Notify the server that the field settings for the index have changed.
|
||||
* Notifies 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.
|
||||
@ -134,11 +161,14 @@ interface SearchApiServiceInterface {
|
||||
* @return bool
|
||||
* TRUE, if this change affected the server in any way that forces it to
|
||||
* re-index the content. FALSE otherwise.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If an error occurred while reacting to the change of fields.
|
||||
*/
|
||||
public function fieldsUpdated(SearchApiIndex $index);
|
||||
|
||||
/**
|
||||
* Remove an index from this server.
|
||||
* Removes 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
|
||||
@ -152,11 +182,14 @@ interface SearchApiServiceInterface {
|
||||
* @param $index
|
||||
* Either an object representing the index to remove, or its machine name
|
||||
* (if the index was completely deleted).
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If an error occurred while removing the index.
|
||||
*/
|
||||
public function removeIndex($index);
|
||||
|
||||
/**
|
||||
* Index the specified items.
|
||||
* Indexes the specified items.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The search index for which items should be indexed.
|
||||
@ -187,7 +220,7 @@ interface SearchApiServiceInterface {
|
||||
public function indexItems(SearchApiIndex $index, array $items);
|
||||
|
||||
/**
|
||||
* Delete items from an index on this server.
|
||||
* Deletes indexed items from this server.
|
||||
*
|
||||
* Might be either used to delete some items (given by their ids) from a
|
||||
* specified index, or all items from that index, or all items from all
|
||||
@ -200,11 +233,14 @@ interface SearchApiServiceInterface {
|
||||
* @param SearchApiIndex $index
|
||||
* The index from which items should be deleted, or NULL if all indexes on
|
||||
* this server should be cleared (then, $ids has to be 'all').
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If an error occurred while trying to delete the items.
|
||||
*/
|
||||
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL);
|
||||
|
||||
/**
|
||||
* Create a query object for searching on an index lying on this server.
|
||||
* Creates a query object for searching on an index lying on this server.
|
||||
*
|
||||
* @param SearchApiIndex $index
|
||||
* The index to search on.
|
||||
@ -334,6 +370,30 @@ abstract class SearchApiAbstractService implements SearchApiServiceInterface {
|
||||
return $output ? "<dl>\n$output</dl>" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional, service-specific information about this server.
|
||||
*
|
||||
* If a service class implements this method and supports the
|
||||
* "search_api_service_extra" option, this method will be used to add extra
|
||||
* information to the server's "View" tab.
|
||||
*
|
||||
* In the default theme implementation this data will be output in a table
|
||||
* with two columns along with other, generic information about the server.
|
||||
*
|
||||
* @return array
|
||||
* An array of additional server information, with each piece of information
|
||||
* being an associative array with the following keys:
|
||||
* - label: The human-readable label for this data.
|
||||
* - info: The information, as HTML.
|
||||
* - status: (optional) The status associated with this information. One of
|
||||
* "info", "ok", "warning" or "error". Defaults to "info".
|
||||
*
|
||||
* @see supportsFeature()
|
||||
*/
|
||||
public function getExtraInformation() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements SearchApiServiceInterface::__construct().
|
||||
*
|
||||
|
@ -1,44 +1,229 @@
|
||||
/**
|
||||
* @file
|
||||
* Styles for Search API admin pages.
|
||||
*/
|
||||
|
||||
td.search-api-status {
|
||||
/*
|
||||
* OVERVIEW
|
||||
*/
|
||||
|
||||
.search-api-overview td.search-api-status {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.search-api-edit-menu {
|
||||
.search-api-overview td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/*
|
||||
* VIEW SERVER
|
||||
*/
|
||||
|
||||
.search-api-server-summary ul.inline {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-api-server-summary ul.inline li {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* VIEW INDEX
|
||||
*/
|
||||
.search-api-limit,
|
||||
.search-api-batch-size {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-api-index-status .progress .filled {
|
||||
background: #0074BD none;
|
||||
}
|
||||
|
||||
/*
|
||||
* DROPBUTTONS
|
||||
*
|
||||
* (Largely copied from D8's dropbutton.css.)
|
||||
*/
|
||||
|
||||
/**
|
||||
* When a dropbutton has only one option, it is simply a button.
|
||||
*/
|
||||
.dropbutton-wrapper,
|
||||
.dropbutton-wrapper div {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.js .dropbutton-wrapper {
|
||||
display: block;
|
||||
min-height: 2em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.js .dropbutton-wrapper,
|
||||
.js .dropbutton-widget {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.js .dropbutton-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.js .dropbutton-widget {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
color: black;
|
||||
z-index: 999;
|
||||
border: 1px solid black;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
-khtml-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
div.search-api-edit-menu ul {
|
||||
margin: 0 0.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.search-api-edit-menu ul li {
|
||||
padding: 0;
|
||||
/* UL styles are over-scoped in core, so this selector needs weight parity. */
|
||||
.js .dropbutton-widget .dropbutton {
|
||||
list-style-image: none;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.js .dropbutton li,
|
||||
.js .dropbutton a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.search-api-edit-menu.collapsed {
|
||||
/**
|
||||
* The dropbutton styling.
|
||||
*
|
||||
* A dropbutton is a widget that displays a list of action links as a button
|
||||
* with a primary action. Secondary actions are hidden behind a click on a
|
||||
* twisty arrow.
|
||||
*
|
||||
* The arrow is created using border on a zero-width, zero-height span.
|
||||
* The arrow inherits the link color, but can be overridden with border colors.
|
||||
*/
|
||||
.js .dropbutton-multiple .dropbutton-widget {
|
||||
padding-right: 2em; /* LTR */
|
||||
}
|
||||
|
||||
.js[dir="rtl"] .dropbutton-multiple .dropbutton-widget {
|
||||
padding-left: 2em;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.dropbutton-multiple.open,
|
||||
.dropbutton-multiple.open .dropbutton-widget {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.dropbutton-multiple.open {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.dropbutton-multiple .dropbutton .secondary-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropbutton-multiple.open .dropbutton .secondary-action {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropbutton-toggle {
|
||||
bottom: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0; /* LTR */
|
||||
text-indent: 110%;
|
||||
top: 0;
|
||||
white-space: nowrap;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
[dir="rtl"] .dropbutton-toggle {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.dropbutton-toggle button {
|
||||
background: none;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropbutton-arrow {
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 0.3333em 0.3333em 0;
|
||||
display: block;
|
||||
height: 0;
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
right: 40%; /* 0.6667em; */
|
||||
/* LTR */
|
||||
top: 50%;
|
||||
margin-top: -0.1666em;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[dir="rtl"] .dropbutton-arrow {
|
||||
left: 0.6667em;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.dropbutton-multiple.open .dropbutton-arrow {
|
||||
border-bottom: 0.3333em solid;
|
||||
border-top-color: transparent;
|
||||
top: 0.6667em;
|
||||
}
|
||||
|
||||
.js .dropbutton-widget {
|
||||
background-color: white;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
|
||||
.js .dropbutton-widget:hover {
|
||||
border-color: #B8B8B8;
|
||||
}
|
||||
|
||||
.dropbutton .dropbutton-action > * {
|
||||
padding: 0.1em 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropbutton .secondary-action {
|
||||
border-top: 1px solid #E8E8E8;
|
||||
}
|
||||
|
||||
.dropbutton-multiple .dropbutton {
|
||||
border-right: 1px solid #E8E8E8; /* LTR */
|
||||
}
|
||||
|
||||
[dir="rtl"] .dropbutton-multiple .dropbutton {
|
||||
border-left: 1px solid #E8E8E8;
|
||||
border-right: 0 none;
|
||||
}
|
||||
|
||||
.dropbutton-multiple .dropbutton .dropbutton-action > * {
|
||||
margin-right: 0.25em; /* LTR */
|
||||
}
|
||||
|
||||
[dir="rtl"] .dropbutton-multiple .dropbutton .dropbutton-action > * {
|
||||
margin-left: 0.25em;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* MISC
|
||||
*/
|
||||
|
||||
.search-api-alter-add-aggregation-fields,
|
||||
.search-api-checkboxes-list {
|
||||
max-height: 12em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Workaround for http://drupal.org/node/1015798 */
|
||||
.vertical-tabs fieldset div.fieldset-wrapper fieldset legend {
|
||||
display: block;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,14 @@
|
||||
/**
|
||||
* @file
|
||||
* Javascript enhancements for the Search API admin pages.
|
||||
*/
|
||||
|
||||
// Copied from filter.admin.js
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Allows the re-ordering of enabled data alterations and processors.
|
||||
*/
|
||||
// Copied from filter.admin.js
|
||||
Drupal.behaviors.searchApiStatus = {
|
||||
attach: function (context, settings) {
|
||||
$('.search-api-status-wrapper input.form-checkbox', context).once('search-api-status', function () {
|
||||
@ -43,19 +50,158 @@ Drupal.behaviors.searchApiStatus = {
|
||||
}
|
||||
};
|
||||
|
||||
Drupal.behaviors.searchApiEditMenu = {
|
||||
/**
|
||||
* Processes elements with the .dropbutton class on page load.
|
||||
*/
|
||||
Drupal.behaviors.searchApiDropButton = {
|
||||
attach: function (context, settings) {
|
||||
$('.search-api-edit-menu-toggle', context).click(function (e) {
|
||||
$menu = $(this).parent().find('.search-api-edit-menu');
|
||||
if ($menu.is('.collapsed')) {
|
||||
$menu.removeClass('collapsed');
|
||||
var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');
|
||||
if ($dropbuttons.length) {
|
||||
//$('.dropbutton-toggle', $dropbuttons).click(dropbuttonClickHandler);
|
||||
// Initialize all buttons.
|
||||
for (var i = 0, il = $dropbuttons.length; i < il; i++) {
|
||||
DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));
|
||||
}
|
||||
else {
|
||||
$menu.addClass('collapsed');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// Adds the delegated handler that will toggle dropdowns on click.
|
||||
$('.dropbutton-toggle', $dropbuttons).click(dropbuttonClickHandler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegated callback for opening and closing dropbutton secondary actions.
|
||||
*/
|
||||
function dropbuttonClickHandler(e) {
|
||||
e.preventDefault();
|
||||
$(e.target).closest('.dropbutton-wrapper').toggleClass('open');
|
||||
}
|
||||
|
||||
/**
|
||||
* A DropButton presents an HTML list as a button with a primary action.
|
||||
*
|
||||
* All secondary actions beyond the first in the list are presented in a
|
||||
* dropdown list accessible through a toggle arrow associated with the button.
|
||||
*
|
||||
* @param {jQuery} dropbutton
|
||||
* A jQuery element.
|
||||
*
|
||||
* @param {Object} settings
|
||||
* A list of options including:
|
||||
* - {String} title: The text inside the toggle link element. This text is
|
||||
* hidden from visual UAs.
|
||||
*/
|
||||
function DropButton(dropbutton, settings) {
|
||||
// Merge defaults with settings.
|
||||
var options = $.extend({'title': Drupal.t('List additional actions')}, settings);
|
||||
var $dropbutton = $(dropbutton);
|
||||
this.$dropbutton = $dropbutton;
|
||||
this.$list = $dropbutton.find('.dropbutton');
|
||||
// Find actions and mark them.
|
||||
this.$actions = this.$list.find('li').addClass('dropbutton-action');
|
||||
|
||||
// Add the special dropdown only if there are hidden actions.
|
||||
if (this.$actions.length > 1) {
|
||||
// Identify the first element of the collection.
|
||||
var $primary = this.$actions.slice(0, 1);
|
||||
// Identify the secondary actions.
|
||||
var $secondary = this.$actions.slice(1);
|
||||
$secondary.addClass('secondary-action');
|
||||
// Add toggle link.
|
||||
$primary.after(Drupal.theme('dropbuttonToggle', options));
|
||||
// Bind mouse events.
|
||||
this.$dropbutton
|
||||
.addClass('dropbutton-multiple')
|
||||
/**
|
||||
* Adds a timeout to close the dropdown on mouseleave.
|
||||
*/
|
||||
.bind('mouseleave.dropbutton', $.proxy(this.hoverOut, this))
|
||||
/**
|
||||
* Clears timeout when mouseout of the dropdown.
|
||||
*/
|
||||
.bind('mouseenter.dropbutton', $.proxy(this.hoverIn, this))
|
||||
/**
|
||||
* Similar to mouseleave/mouseenter, but for keyboard navigation.
|
||||
*/
|
||||
.bind('focusout.dropbutton', $.proxy(this.focusOut, this))
|
||||
.bind('focusin.dropbutton', $.proxy(this.focusIn, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the DropButton constructor.
|
||||
*/
|
||||
$.extend(DropButton, {
|
||||
/**
|
||||
* Store all processed DropButtons.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
dropbuttons: []
|
||||
});
|
||||
|
||||
/**
|
||||
* Extend the DropButton prototype.
|
||||
*/
|
||||
$.extend(DropButton.prototype, {
|
||||
/**
|
||||
* Toggle the dropbutton open and closed.
|
||||
*
|
||||
* @param {Boolean} show
|
||||
* (optional) Force the dropbutton to open by passing true or to close by
|
||||
* passing false.
|
||||
*/
|
||||
toggle: function (show) {
|
||||
var isBool = typeof show === 'boolean';
|
||||
show = isBool ? show : !this.$dropbutton.hasClass('open');
|
||||
this.$dropbutton.toggleClass('open', show);
|
||||
},
|
||||
|
||||
hoverIn: function () {
|
||||
// Clear any previous timer we were using.
|
||||
if (this.timerID) {
|
||||
window.clearTimeout(this.timerID);
|
||||
}
|
||||
},
|
||||
|
||||
hoverOut: function () {
|
||||
// Wait half a second before closing.
|
||||
this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
|
||||
},
|
||||
|
||||
open: function () {
|
||||
this.toggle(true);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this.toggle(false);
|
||||
},
|
||||
|
||||
focusOut: function (e) {
|
||||
this.hoverOut.call(this, e);
|
||||
},
|
||||
|
||||
focusIn: function (e) {
|
||||
this.hoverIn.call(this, e);
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(Drupal.theme, {
|
||||
/**
|
||||
* A toggle is an interactive element often bound to a click handler.
|
||||
*
|
||||
* @param {Object} options
|
||||
* - {String} title: (optional) The HTML anchor title attribute and
|
||||
* text for the inner span element.
|
||||
*
|
||||
* @return {String}
|
||||
* A string representing a DOM fragment.
|
||||
*/
|
||||
dropbuttonToggle: function (options) {
|
||||
return '<li class="dropbutton-toggle"><button type="button" role="button"><span class="dropbutton-arrow"><span class="visually-hidden">' + options.title + '</span></span></button></li>';
|
||||
}
|
||||
});
|
||||
|
||||
// Expose constructor in the public space.
|
||||
Drupal.DropButton = DropButton;
|
||||
|
||||
})(jQuery);
|
||||
|
@ -22,7 +22,8 @@
|
||||
* - description: A translated string to be shown to administrators when
|
||||
* selecting a service class. Should contain all peculiarities of the
|
||||
* service class, like field type support, supported features (like facets),
|
||||
* the "direct" parse mode and other specific things to keep in mind.
|
||||
* the "direct" parse mode and other specific things to keep in mind. The
|
||||
* text can contain HTML.
|
||||
* - class: The service class, which has to implement the
|
||||
* SearchApiServiceInterface interface.
|
||||
*
|
||||
@ -192,6 +193,8 @@ function hook_search_api_data_type_info_alter(array &$infos) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Define available data alterations.
|
||||
*
|
||||
* Registers one or more callbacks that can be called at index time to add
|
||||
* additional data to the indexed items (e.g. comments or attachments to nodes),
|
||||
* alter the data in other forms or remove items from the array.
|
||||
@ -226,6 +229,21 @@ function hook_search_api_alter_callback_info() {
|
||||
return $callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the available data alterations.
|
||||
*
|
||||
* @param array $callbacks
|
||||
* The callback information to be altered, keyed by callback IDs.
|
||||
*
|
||||
* @see hook_search_api_alter_callback_info()
|
||||
*/
|
||||
function hook_search_api_alter_callback_info_alter(array &$callbacks) {
|
||||
if (!empty($callbacks['example_random_alter'])) {
|
||||
$callbacks['example_random_alter']['name'] = t('Even more random alteration');
|
||||
$callbacks['example_random_alter']['class'] = 'ExampleUltraRandomAlter';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers one or more processors. These are classes implementing the
|
||||
* SearchApiProcessorInterface interface which can be used at index and search
|
||||
@ -261,6 +279,20 @@ function hook_search_api_processor_info() {
|
||||
return $callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the available processors.
|
||||
*
|
||||
* @param array $processors
|
||||
* The processor information to be altered, keyed by processor IDs.
|
||||
*
|
||||
* @see hook_search_api_processor_info()
|
||||
*/
|
||||
function hook_search_api_processor_info_alter(array &$processors) {
|
||||
if (!empty($processors['example_processor'])) {
|
||||
$processors['example_processor']['weight'] = -20;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to log or alter the items that are indexed.
|
||||
*
|
||||
|
@ -22,6 +22,32 @@ function search_api_drush_command() {
|
||||
'aliases' => array('sapi-l'),
|
||||
);
|
||||
|
||||
$items['search-api-enable'] = array(
|
||||
'description' => 'Enable one or all disabled search_api indexes.',
|
||||
'examples' => array(
|
||||
'drush searchapi-enable' => dt('Enable all disabled indexes.'),
|
||||
'drush sapi-en' => dt('Alias to enable all disabled indexes.'),
|
||||
'drush sapi-en 1' => dt('Enable index with the ID !id.', array('!id' => 1)),
|
||||
),
|
||||
'arguments' => array(
|
||||
'index_id' => dt('The numeric ID or machine name of an index to enable.'),
|
||||
),
|
||||
'aliases' => array('sapi-en'),
|
||||
);
|
||||
|
||||
$items['search-api-disable'] = array(
|
||||
'description' => 'Disable one or all enabled search_api indexes.',
|
||||
'examples' => array(
|
||||
'drush searchapi-disable' => dt('Disable all enabled indexes.'),
|
||||
'drush sapi-dis' => dt('Alias to disable all enabled indexes.'),
|
||||
'drush sapi-dis 1' => dt('Disable index with the ID !id.', array('!id' => 1)),
|
||||
),
|
||||
'arguments' => array(
|
||||
'index_id' => dt('The numeric ID or machine name of an index to disable.'),
|
||||
),
|
||||
'aliases' => array('sapi-dis'),
|
||||
);
|
||||
|
||||
$items['search-api-status'] = array(
|
||||
'description' => 'Show the status of one or all search indexes.',
|
||||
'examples' => array(
|
||||
@ -73,8 +99,8 @@ function search_api_drush_command() {
|
||||
'examples' => array(
|
||||
'drush searchapi-clear' => dt('Clear all search indexes.'),
|
||||
'drush sapi-c' => dt('Alias to clear all search indexes.'),
|
||||
'drush sapi-r 1' => dt('Clear the search index with the ID !id.', array('!id' => 1)),
|
||||
'drush sapi-r default_node_index' => dt('Clear the search index with the machine name !name.', array('!name' => 'default_node_index')),
|
||||
'drush sapi-c 1' => dt('Clear the search index with the ID !id.', array('!id' => 1)),
|
||||
'drush sapi-c default_node_index' => dt('Clear the search index with the machine name !name.', array('!name' => 'default_node_index')),
|
||||
),
|
||||
'arguments' => array(
|
||||
'index_id' => dt('The numeric ID or machine name of an index.'),
|
||||
@ -127,6 +153,65 @@ function drush_search_api_list() {
|
||||
drush_print_table($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable index(es).
|
||||
*
|
||||
* @param string|integer $index_id
|
||||
* The index name or id which should be enabled.
|
||||
*/
|
||||
function drush_search_api_enable($index_id = NULL) {
|
||||
if (search_api_drush_static(__FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
$indexes = search_api_drush_get_index($index_id);
|
||||
if (empty($indexes)) {
|
||||
return;
|
||||
}
|
||||
foreach ($indexes as $index) {
|
||||
if (!$index->enabled) {
|
||||
drush_log(dt("Enabling index !index and queueing items for indexing.", array('!index' => $index->name)), 'notice');
|
||||
if (search_api_index_enable($index->id)) {
|
||||
drush_log(dt("The index !index was successfully enabled.", array('!index' => $index->name)), 'ok');
|
||||
}
|
||||
else {
|
||||
drush_log(dt("Error enabling index !index.", array('!index' => $index->name)), 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt("The index !index is already enabled.", array('!index' => $index->name)), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable index(es).
|
||||
*
|
||||
* @param string|integer $index_id
|
||||
* The index name or id which should be disabled.
|
||||
*/
|
||||
function drush_search_api_disable($index_id = NULL) {
|
||||
if (search_api_drush_static(__FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
$indexes = search_api_drush_get_index($index_id);
|
||||
if (empty($indexes)) {
|
||||
return;
|
||||
}
|
||||
foreach ($indexes as $index) {
|
||||
if ($index->enabled) {
|
||||
if (search_api_index_disable($index->id)) {
|
||||
drush_log(dt("The index !index was successfully disabled.", array('!index' => $index->name)), 'ok');
|
||||
}
|
||||
else {
|
||||
drush_log(dt("Error disabling index !index.", array('!index' => $index->name)), 'error');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt("The index !index is already disabled.", array('!index' => $index->name)), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display index status.
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@ files[] = includes/callback_add_hierarchy.inc
|
||||
files[] = includes/callback_add_url.inc
|
||||
files[] = includes/callback_add_viewed_entity.inc
|
||||
files[] = includes/callback_bundle_filter.inc
|
||||
files[] = includes/callback_comment_access.inc
|
||||
files[] = includes/callback_language_control.inc
|
||||
files[] = includes/callback_node_access.inc
|
||||
files[] = includes/callback_node_status.inc
|
||||
@ -33,9 +34,9 @@ files[] = includes/service.inc
|
||||
|
||||
configure = admin/config/search/search_api
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-09-01
|
||||
version = "7.x-1.8"
|
||||
; Information added by Drupal.org packaging script on 2013-12-25
|
||||
version = "7.x-1.11"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1378025826"
|
||||
datestamp = "1387965506"
|
||||
|
||||
|
@ -191,6 +191,47 @@ function search_api_schema() {
|
||||
'primary key' => array('item_id', 'index_id'),
|
||||
);
|
||||
|
||||
$schema['search_api_task'] = array(
|
||||
'description' => 'Stores pending tasks for servers.',
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'description' => 'An integer identifying this task.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'server_id' => array(
|
||||
'description' => 'The {search_api_server}.machine_name for which this task should be executed.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'type' => array(
|
||||
'description' => 'A keyword identifying the type of task that should be executed.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'index_id' => array(
|
||||
'description' => 'The {search_api_index}.machine_name to which this task pertains, if applicable for this type.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'data' => array(
|
||||
'description' => 'Some data needed for the task, might be optional depending on the type.',
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'serialize' => TRUE,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'server' => array('server_id'),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
@ -330,14 +371,12 @@ function search_api_disable() {
|
||||
// Modules defining entity or item types might have been disabled. Ignore.
|
||||
}
|
||||
}
|
||||
DrupalQueue::get('search_api_indexing_queue')->deleteQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function search_api_uninstall() {
|
||||
variable_del('search_api_tasks');
|
||||
variable_del('search_api_index_worker_callback_runtime');
|
||||
}
|
||||
|
||||
@ -612,7 +651,7 @@ function search_api_update_7106() {
|
||||
$callbacks['search_api_alter_add_aggregation'] = $callbacks['search_api_alter_add_fulltext'];
|
||||
unset($callbacks['search_api_alter_add_fulltext']);
|
||||
if (!empty($callbacks['search_api_alter_add_aggregation']['settings']['fields'])) {
|
||||
foreach ($callbacks['search_api_alter_add_aggregation']['settings']['fields'] as $field => &$info) {
|
||||
foreach ($callbacks['search_api_alter_add_aggregation']['settings']['fields'] as &$info) {
|
||||
if (!isset($info['type'])) {
|
||||
$info['type'] = 'fulltext';
|
||||
}
|
||||
@ -813,3 +852,143 @@ function search_api_update_7114() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to indexing without the use of a cron queue.
|
||||
*/
|
||||
function search_api_update_7115() {
|
||||
variable_del('search_api_batch_per_cron');
|
||||
DrupalQueue::get('search_api_indexing_queue')->deleteQueue();
|
||||
db_update('search_api_item')
|
||||
->fields(array(
|
||||
'changed' => 1,
|
||||
))
|
||||
->condition('changed', 0, '<')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the tasks for disabled servers to a separate database table.
|
||||
*/
|
||||
function search_api_update_7116() {
|
||||
// Create table.
|
||||
$table = array(
|
||||
'description' => 'Stores pending tasks for servers.',
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'description' => 'An integer identifying this task.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'server_id' => array(
|
||||
'description' => 'The {search_api_server}.machine_name for which this task should be executed.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'type' => array(
|
||||
'description' => 'A keyword identifying the type of task that should be executed.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'index_id' => array(
|
||||
'description' => 'The {search_api_index}.machine_name to which this task pertains, if applicable for this type.',
|
||||
'type' => 'varchar',
|
||||
'length' => 50,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'data' => array(
|
||||
'description' => 'Some data needed for the task, might be optional depending on the type.',
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'serialize' => TRUE,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'server' => array('server_id'),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
);
|
||||
db_create_table('search_api_task', $table);
|
||||
|
||||
// Collect old tasks.
|
||||
$tasks = array();
|
||||
foreach (variable_get('search_api_tasks', array()) as $server => $indexes) {
|
||||
foreach ($indexes as $index => $old_tasks) {
|
||||
if (in_array('clear all', $old_tasks)) {
|
||||
$tasks[] = array(
|
||||
'server_id' => $server,
|
||||
'type' => 'deleteItems',
|
||||
);
|
||||
}
|
||||
if (in_array('remove', $old_tasks)) {
|
||||
$tasks[] = array(
|
||||
'server_id' => $server,
|
||||
'type' => 'removeIndex',
|
||||
'index_id' => $index,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
variable_del('search_api_tasks');
|
||||
|
||||
$select = db_select('search_api_index', 'i')
|
||||
->fields('i', array('machine_name', 'server'));
|
||||
$select->innerJoin('search_api_server', 's', 'i.server = s.machine_name AND s.enabled = 0');
|
||||
$index_ids = array();
|
||||
foreach ($select->execute() as $index) {
|
||||
$index_ids[] = $index->machine_name;
|
||||
$tasks[] = array(
|
||||
'server_id' => $index->server,
|
||||
'type' => 'removeIndex',
|
||||
'index_id' => $index->machine_name,
|
||||
);
|
||||
}
|
||||
if ($index_ids) {
|
||||
db_update('search_api_index')
|
||||
->fields(array(
|
||||
'enabled' => 0,
|
||||
'server' => NULL,
|
||||
))
|
||||
->condition('machine_name', $index_ids)
|
||||
->execute();
|
||||
}
|
||||
|
||||
if ($tasks) {
|
||||
$insert = db_insert('search_api_task')
|
||||
->fields(array('server_id', 'type', 'index_id', 'data'));
|
||||
foreach ($tasks as $task) {
|
||||
$insert->values($task);
|
||||
}
|
||||
$insert->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the database for illegal {search_api_index}.server values.
|
||||
*/
|
||||
function search_api_update_7117() {
|
||||
$servers = db_select('search_api_server', 's')
|
||||
->fields('s', array('machine_name'))
|
||||
->condition('enabled', 1);
|
||||
$indexes = db_select('search_api_index', 'i')
|
||||
->fields('i', array('id'))
|
||||
->condition('server', $servers, 'NOT IN')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
if ($indexes) {
|
||||
db_delete('search_api_item')
|
||||
->condition('index_id', $indexes)
|
||||
->execute();
|
||||
db_update('search_api_index')
|
||||
->fields(array(
|
||||
'server' => NULL,
|
||||
'enabled' => 0,
|
||||
))
|
||||
->condition('id', $indexes)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
1004
search_api.module
1004
search_api.module
File diff suppressed because it is too large
Load Diff
595
search_api.test
595
search_api.test
@ -1,29 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for testing Search API web functionality.
|
||||
* @file
|
||||
* Contains the SearchApiWebTest and the SearchApiUnitTest classes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for testing Search API functionality via the UI.
|
||||
*/
|
||||
class SearchApiWebTest extends DrupalWebTestCase {
|
||||
|
||||
/**
|
||||
* The machine name of the created test server.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $server_id;
|
||||
|
||||
/**
|
||||
* The machine name of the created test index.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $index_id;
|
||||
|
||||
/**
|
||||
* Overrides DrupalWebTestCase::assertText().
|
||||
*
|
||||
* Changes the default message to be just the text checked for.
|
||||
*/
|
||||
protected function assertText($text, $message = '', $group = 'Other') {
|
||||
return parent::assertText($text, $message ? $message : $text, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalWebTestCase::drupalGet().
|
||||
*
|
||||
* Additionally asserts that the HTTP request returned a 200 status code.
|
||||
*/
|
||||
protected function drupalGet($path, array $options = array(), array $headers = array()) {
|
||||
$ret = parent::drupalGet($path, $options, $headers);
|
||||
$this->assertResponse(200, 'HTTP code 200 returned.');
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalWebTestCase::drupalPost().
|
||||
*
|
||||
* Additionally asserts that the HTTP request returned a 200 status code.
|
||||
*/
|
||||
protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
|
||||
$ret = parent::drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post);
|
||||
$this->assertResponse(200, 'HTTP code 200 returned.');
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this test case.
|
||||
*
|
||||
* @return array
|
||||
* An array with information about this test case.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Test search API framework',
|
||||
@ -32,24 +69,34 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp('entity', 'search_api', 'search_api_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests correct admin UI, indexing and search behavior.
|
||||
*
|
||||
* We only use a single test method to avoid wasting ressources on setting up
|
||||
* the test environment multiple times. This will be the only method called
|
||||
* by the Simpletest framework (since the method name starts with "test"). It
|
||||
* in turn calls other methdos that set up the environment in a certain way
|
||||
* and then run tests on it.
|
||||
*/
|
||||
public function testFramework() {
|
||||
$this->drupalLogin($this->drupalCreateUser(array('administer search_api')));
|
||||
// @todo Why is there no default index?
|
||||
//$this->deleteDefaultIndex();
|
||||
$this->insertItems();
|
||||
$this->checkOverview1();
|
||||
$this->createIndex();
|
||||
$this->insertItems(5);
|
||||
$this->insertItems();
|
||||
$this->createServer();
|
||||
$this->checkOverview2();
|
||||
$this->checkOverview();
|
||||
$this->enableIndex();
|
||||
$this->searchNoResults();
|
||||
$this->indexItems();
|
||||
$this->searchSuccess();
|
||||
$this->checkIndexingOrder();
|
||||
$this->editServer();
|
||||
$this->clearIndex();
|
||||
$this->searchNoResults();
|
||||
@ -57,57 +104,64 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->disableModules();
|
||||
}
|
||||
|
||||
protected function deleteDefaultIndex() {
|
||||
$this->drupalPost('admin/config/search/search_api/index/default_node_index/delete', array(), t('Confirm'));
|
||||
/**
|
||||
* Returns the test server in use by this test case.
|
||||
*
|
||||
* @return SearchApiServer
|
||||
* The test server.
|
||||
*/
|
||||
protected function server() {
|
||||
return search_api_server_load($this->server_id, TRUE);
|
||||
}
|
||||
|
||||
protected function insertItems($offset = 0) {
|
||||
/**
|
||||
* Returns the test index in use by this test case.
|
||||
*
|
||||
* @return SearchApiIndex
|
||||
* The test index.
|
||||
*/
|
||||
protected function index() {
|
||||
return search_api_index_load($this->index_id, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts some test items into the database, via the test module.
|
||||
*
|
||||
* @param int $number
|
||||
* The number of items to insert.
|
||||
*
|
||||
* @see insertItem()
|
||||
*/
|
||||
protected function insertItems($number = 5) {
|
||||
$count = db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField();
|
||||
$this->insertItem(array(
|
||||
'id' => $offset + 1,
|
||||
'title' => 'Title 1',
|
||||
'body' => 'Body text 1.',
|
||||
'type' => 'Item',
|
||||
));
|
||||
$this->insertItem(array(
|
||||
'id' => $offset + 2,
|
||||
'title' => 'Title 2',
|
||||
'body' => 'Body text 2.',
|
||||
'type' => 'Item',
|
||||
));
|
||||
$this->insertItem(array(
|
||||
'id' => $offset + 3,
|
||||
'title' => 'Title 3',
|
||||
'body' => 'Body text 3.',
|
||||
'type' => 'Item',
|
||||
));
|
||||
$this->insertItem(array(
|
||||
'id' => $offset + 4,
|
||||
'title' => 'Title 4',
|
||||
'body' => 'Body text 4.',
|
||||
'type' => 'Page',
|
||||
));
|
||||
$this->insertItem(array(
|
||||
'id' => $offset + 5,
|
||||
'title' => 'Title 5',
|
||||
'body' => 'Body text 5.',
|
||||
'type' => 'Page',
|
||||
));
|
||||
for ($i = 1; $i <= $number; ++$i) {
|
||||
$id = $count + $i;
|
||||
$this->insertItem(array(
|
||||
'id' => $id,
|
||||
'title' => "Title $id",
|
||||
'body' => "Body text $id.",
|
||||
'type' => 'Item',
|
||||
));
|
||||
}
|
||||
$count = db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField() - $count;
|
||||
$this->assertEqual($count, 5, '5 items successfully inserted.');
|
||||
$this->assertEqual($count, $number, "$number items successfully inserted.");
|
||||
}
|
||||
|
||||
protected function insertItem($values) {
|
||||
/**
|
||||
* Helper function for inserting a single test item.
|
||||
*
|
||||
* @param array $values
|
||||
* The property values of the test item.
|
||||
*
|
||||
* @see search_api_test_insert_item()
|
||||
*/
|
||||
protected function insertItem(array $values) {
|
||||
$this->drupalPost('search_api_test/insert', $values, t('Save'));
|
||||
}
|
||||
|
||||
protected function checkOverview1() {
|
||||
// This test fails for no apparent reason for drupal.org test bots.
|
||||
// Commenting them out for now.
|
||||
//$this->drupalGet('admin/config/search/search_api');
|
||||
//$this->assertText(t('There are no search servers or indexes defined yet.'), '"No servers" message is displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test index via the UI and tests whether this works correctly.
|
||||
*/
|
||||
protected function createIndex() {
|
||||
$values = array(
|
||||
'name' => '',
|
||||
@ -136,7 +190,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText(t('The index was successfully created. Please set up its indexed fields now.'), 'The index was successfully created.');
|
||||
$found = strpos($this->getUrl(), 'admin/config/search/search_api/index/' . $id) !== FALSE;
|
||||
$this->assertTrue($found, 'Correct redirect.');
|
||||
$index = search_api_index_load($id, TRUE);
|
||||
$index = $this->index();
|
||||
$this->assertEqual($index->name, $values['name'], 'Name correctly inserted.');
|
||||
$this->assertEqual($index->item_type, $values['item_type'], 'Index item type correctly inserted.');
|
||||
$this->assertFalse($index->enabled, 'Status correctly inserted.');
|
||||
@ -210,18 +264,18 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
'callbacks[search_api_alter_add_aggregation][settings][fields][search_api_aggregation_1][fields][parent:body]' => 1,
|
||||
);
|
||||
$this->drupalPost(NULL, $values, t('Save configuration'));
|
||||
$this->assertText(t("The search index' workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."), 'Workflow successfully edited.');
|
||||
$this->assertText(t("The indexing workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."), 'Workflow successfully edited.');
|
||||
|
||||
$this->drupalGet("admin/config/search/search_api/index/$id");
|
||||
$this->assertTitle('Search API test index | Drupal', 'Correct title when viewing index.');
|
||||
$this->assertText('An index used for testing.', 'Description displayed.');
|
||||
$this->assertText('Search API test entity', 'Item type displayed.');
|
||||
$this->assertText(format_plural(1, '1 item per cron batch.', '@count items per cron batch.'), 'Cron batch size displayed.');
|
||||
|
||||
$this->drupalGet("admin/config/search/search_api/index/$id/status");
|
||||
$this->assertText(t('The index is currently disabled.'), '"Disabled" status displayed.');
|
||||
$this->assertText(t('disabled'), '"Disabled" status displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test server via the UI and tests whether this works correctly.
|
||||
*/
|
||||
protected function createServer() {
|
||||
$values = array(
|
||||
'name' => '',
|
||||
@ -251,7 +305,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText(t('The server was successfully created.'));
|
||||
$found = strpos($this->getUrl(), 'admin/config/search/search_api/server/' . $id) !== FALSE;
|
||||
$this->assertTrue($found, 'Correct redirect.');
|
||||
$server = search_api_server_load($id, TRUE);
|
||||
$server = $this->server();
|
||||
$this->assertEqual($server->name, $values['name'], 'Name correctly inserted.');
|
||||
$this->assertTrue($server->enabled, 'Status correctly inserted.');
|
||||
$this->assertEqual($server->description, $values['description'], 'Description correctly inserted.');
|
||||
@ -260,17 +314,22 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertTitle('Search API test server | Drupal', 'Correct title when viewing server.');
|
||||
$this->assertText('A server used for testing.', 'Description displayed.');
|
||||
$this->assertText('search_api_test_service', 'Service name displayed.');
|
||||
$this->assertText('search_api_test_service description', 'Service description displayed.');
|
||||
$this->assertText('search_api_test foo bar', 'Service options displayed.');
|
||||
}
|
||||
|
||||
protected function checkOverview2() {
|
||||
/**
|
||||
* Checks whether the server and index are correctly listed in the overview.
|
||||
*/
|
||||
protected function checkOverview() {
|
||||
$this->drupalGet('admin/config/search/search_api');
|
||||
$this->assertText('Search API test server', 'Server displayed.');
|
||||
$this->assertText('Search API test index', 'Index displayed.');
|
||||
$this->assertNoText(t('There are no search servers or indexes defined yet.'), '"No servers" message not displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the index onto the server and enables it.
|
||||
*/
|
||||
protected function enableIndex() {
|
||||
$values = array(
|
||||
'server' => $this->server_id,
|
||||
@ -283,24 +342,60 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText(t('The index was successfully enabled.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a search on the index works but yields no results.
|
||||
*
|
||||
* This is the case since no items should have been indexed yet.
|
||||
*/
|
||||
protected function searchNoResults() {
|
||||
$this->drupalGet('search_api_test/query/' . $this->index_id);
|
||||
$this->assertText('result count = 0', 'No search results returned without indexing.');
|
||||
$this->assertText('results = ()', 'No search results returned without indexing.');
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 0, 'No search results returned without indexing.');
|
||||
$this->assertEqual(array_keys($results['results']), array(), 'No search results returned without indexing.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a search on the test index.
|
||||
*
|
||||
* Helper method used for testing search results.
|
||||
*
|
||||
* @param int|null $offset
|
||||
* (optional) The offset for the returned results.
|
||||
* @param int|null $limit
|
||||
* (optional) The limit for the returned results.
|
||||
*
|
||||
* @return array
|
||||
* Search results as specified by SearchApiQueryInterface::execute().
|
||||
*/
|
||||
protected function doSearch($offset = NULL, $limit = NULL) {
|
||||
// Since we change server and index settings via the UI (and, therefore, in
|
||||
// different page requests), the static cache in this page request
|
||||
// (executing the tests) will get stale. Therefore, we clear it before
|
||||
// executing the search.
|
||||
$this->index();
|
||||
$this->server();
|
||||
|
||||
$query = search_api_query($this->index_id);
|
||||
if ($offset || $limit) {
|
||||
$query->range($offset, $limit);
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests indexing via the UI "Index now" functionality.
|
||||
*
|
||||
* Asserts that errors during indexing are handled properly and that the
|
||||
* status readings work.
|
||||
*/
|
||||
protected function indexItems() {
|
||||
$this->drupalGet("admin/config/search/search_api/index/{$this->index_id}/status");
|
||||
$this->assertText(t('The index is currently enabled.'), '"Enabled" status displayed.');
|
||||
$this->assertText(t('All items still need to be indexed (@total total).', array('@total' => 10)), 'Correct index status displayed.');
|
||||
$this->assertText(t('Index now'), '"Index now" button found.');
|
||||
$this->assertText(t('Clear index'), '"Clear index" button found.');
|
||||
$this->assertNoText(t('Re-index content'), '"Re-index" button not found.');
|
||||
$this->checkIndexStatus();
|
||||
|
||||
// Here we test the indexing + the warning message when some items
|
||||
// can not be indexed.
|
||||
// The server refuses (for test purpose) to index items with IDs that are
|
||||
// multiples of 8 unless the "search_api_test_index_all" variable is set.
|
||||
// cannot be indexed.
|
||||
// The server refuses (for test purpose) to index the item that has the same
|
||||
// ID as the "search_api_test_indexing_break" variable (default: 8).
|
||||
// Therefore, if we try to index 8 items, only the first seven will be
|
||||
// successfully indexed and a warning should be displayed.
|
||||
$values = array(
|
||||
'limit' => 8,
|
||||
);
|
||||
@ -308,11 +403,14 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText(t('Successfully indexed @count items.', array('@count' => 7)));
|
||||
$this->assertText(t('1 item could not be indexed. Check the logs for details.'), 'Index errors warning is displayed.');
|
||||
$this->assertNoText(t("Couldn't index items. Check the logs for details."), "Index error isn't displayed.");
|
||||
$this->assertText(t('About @percentage% of all items have been indexed in their latest version (@indexed / @total).', array('@indexed' => 7, '@total' => 10, '@percentage' => 70)), 'Correct index status displayed.');
|
||||
$this->assertText(t('Re-indexing'), '"Re-index" button found.');
|
||||
$this->checkIndexStatus(7);
|
||||
|
||||
// Here we're testing the error message when no item could be indexed.
|
||||
// The item with ID 8 is still not indexed.
|
||||
// The item with ID 8 is still not indexed, but it will be the first to be
|
||||
// indexed now. Therefore, if we try to index a single items, only item 8
|
||||
// will be passed to the server, which will reject it and no items will be
|
||||
// indexed. Since normally this signifies a more serious error than when
|
||||
// only some items couldn't be indexed, this is handled differently.
|
||||
$values = array(
|
||||
'limit' => 1,
|
||||
);
|
||||
@ -321,8 +419,10 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertNoText(t('1 item could not be indexed. Check the logs for details.'), "Index errors warning isn't displayed.");
|
||||
$this->assertText(t("Couldn't index items. Check the logs for details."), 'Index error is displayed.');
|
||||
|
||||
// Here we test the indexing of all the remaining items.
|
||||
variable_set('search_api_test_index_all', TRUE);
|
||||
// No we set the "search_api_test_indexing_break" variable to 0, so all
|
||||
// items will be indexed. The remaining items (8, 9, 10) should therefore
|
||||
// be successfully indexed and no warning should show.
|
||||
variable_set('search_api_test_indexing_break', 0);
|
||||
$values = array(
|
||||
'limit' => -1,
|
||||
);
|
||||
@ -330,20 +430,249 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText(t('Successfully indexed @count items.', array('@count' => 3)));
|
||||
$this->assertNoText(t("Some items couldn't be indexed. Check the logs for details."), "Index errors warning isn't displayed.");
|
||||
$this->assertNoText(t("Couldn't index items. Check the logs for details."), "Index error isn't displayed.");
|
||||
$this->assertText(t('All items have been indexed (@indexed / @total).', array('@indexed' => 10, '@total' => 10)), 'Correct index status displayed.');
|
||||
$this->assertNoText(t('Index now'), '"Index now" button no longer displayed.');
|
||||
$this->checkIndexStatus(10);
|
||||
|
||||
// Reset the static cache for the server.
|
||||
$this->server();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the index's "Status" tab shows the correct values.
|
||||
*
|
||||
* Helper method used by indexItems() and others.
|
||||
*
|
||||
* The internal browser will point to the index's "Status" tab after this
|
||||
* method is called.
|
||||
*
|
||||
* @param int $indexed
|
||||
* (optional) The number of items that should be indexed at the moment.
|
||||
* Defaults to 0.
|
||||
* @param int $total
|
||||
* (optional) The (correct) total number of items. Defaults to 10.
|
||||
* @param bool $check_buttons
|
||||
* (optional) Whether to check for the correct presence/absence of buttons.
|
||||
* Defaults to TRUE.
|
||||
* @param int|null $on_server
|
||||
* (optional) The number of items actually on the server. Defaults to
|
||||
* $indexed.
|
||||
*/
|
||||
protected function checkIndexStatus($indexed = 0, $total = 10, $check_buttons = TRUE, $on_server = NULL) {
|
||||
$url = "admin/config/search/search_api/index/{$this->index_id}";
|
||||
if (strpos($this->url, $url) === FALSE) {
|
||||
$this->drupalGet($url);
|
||||
}
|
||||
|
||||
$index_status = t('@indexed/@total indexed', array('@indexed' => $indexed, '@total' => $total));
|
||||
$this->assertText($index_status, 'Correct index status displayed.');
|
||||
|
||||
if (!isset($on_server)) {
|
||||
$on_server = $indexed;
|
||||
}
|
||||
$info = format_plural($on_server, 'There is 1 item indexed on the server for this index.', 'There are @count items indexed on the server for this index.');
|
||||
$this->assertText(t('Server index status'), 'Server index status displayed.');
|
||||
$this->assertText($info, 'Correct server index status displayed.');
|
||||
|
||||
if (!$check_buttons) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertText(t('enabled'), '"Enabled" status displayed.');
|
||||
if ($indexed == $total) {
|
||||
$this->assertRaw('disabled="disabled"', '"Index now" form disabled.');
|
||||
}
|
||||
else {
|
||||
$this->assertNoRaw('disabled="disabled"', '"Index now" form enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether searches yield the right results after indexing.
|
||||
*
|
||||
* The test server only implements range functionality, no kind of fulltext
|
||||
* search capabilities, so we can only test for that.
|
||||
*/
|
||||
protected function searchSuccess() {
|
||||
$this->drupalGet('search_api_test/query/' . $this->index_id);
|
||||
$this->assertText('result count = 10', 'Correct search result count returned after indexing.');
|
||||
$this->assertText('results = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)', 'Correct search results returned after indexing.');
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 10, 'Correct search result count returned after indexing.');
|
||||
$this->assertEqual(array_keys($results['results']), array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 'Correct search results returned after indexing.');
|
||||
|
||||
$this->drupalGet('search_api_test/query/' . $this->index_id . '/foo/2/4');
|
||||
$this->assertText('result count = 10', 'Correct search result count with ranged query.');
|
||||
$this->assertText('results = (3, 4, 5, 6)', 'Correct search results with ranged query.');
|
||||
$results = $this->doSearch(2, 4);
|
||||
$this->assertEqual($results['result count'], 10, 'Correct search result count with ranged query.');
|
||||
$this->assertEqual(array_keys($results['results']), array(3, 4, 5, 6), 'Correct search results with ranged query.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether items are indexed in the right order.
|
||||
*
|
||||
* The indexing order should always be that new items are indexed before
|
||||
* changed ones, and only then the changed items in the order of their change.
|
||||
*
|
||||
* This method also assures that this behavior is even observed when indexing
|
||||
* temporarily fails.
|
||||
*
|
||||
* @see https://drupal.org/node/2115127
|
||||
*/
|
||||
protected function checkIndexingOrder() {
|
||||
// Set cron batch size to 1 so not all items will get indexed right away.
|
||||
// This also ensures that later, when indexing of a single item will be
|
||||
// rejected by using the "search_api_test_indexing_break" variable, this
|
||||
// will have the effect of rejecting "all" items of a batch (since that
|
||||
// batch only consists of a single item).
|
||||
$values = array(
|
||||
'options[cron_limit]' => 1,
|
||||
);
|
||||
$this->drupalPost("admin/config/search/search_api/index/{$this->index_id}/edit", $values, t('Save settings'));
|
||||
$this->assertText(t('The search index was successfully edited.'));
|
||||
|
||||
// Manually clear the server's item storage – that way, the items will still
|
||||
// count as indexed for the Search API, but won't be returned in searches.
|
||||
// We do this so we have finer-grained control over the order in which items
|
||||
// are indexed.
|
||||
$this->server()->deleteItems();
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 0, 'Indexed items were successfully deleted from the server.');
|
||||
$this->assertEqual(array_keys($results['results']), array(), 'Indexed items were successfully deleted from the server.');
|
||||
|
||||
// Now insert some new items, and mark others as changed. Make sure that
|
||||
// each action has a unique timestamp, so the order will be correct.
|
||||
$this->drupalGet('search_api_test/touch/8');
|
||||
$this->insertItems(1);// item 11
|
||||
sleep(1);
|
||||
$this->drupalGet('search_api_test/touch/2');
|
||||
$this->insertItems(1);// item 12
|
||||
sleep(1);
|
||||
$this->drupalGet('search_api_test/touch/5');
|
||||
$this->insertItems(1);// item 13
|
||||
sleep(1);
|
||||
$this->drupalGet('search_api_test/touch/8');
|
||||
$this->insertItems(1); // item 14
|
||||
|
||||
// Check whether the status display is right.
|
||||
$this->checkIndexStatus(7, 14, FALSE, 0);
|
||||
|
||||
// Indexing order should now be: 11, 12, 13, 14, 8, 2, 4. Let's try it out!
|
||||
// First manually index one item, and see if it's 11.
|
||||
$values = array(
|
||||
'limit' => 1,
|
||||
);
|
||||
$this->drupalPost(NULL, $values, t('Index now'));
|
||||
$this->assertText(t('Successfully indexed @count item.', array('@count' => 1)));
|
||||
$this->assertNoText(t("Some items couldn't be indexed. Check the logs for details."), "Index errors warning isn't displayed.");
|
||||
$this->assertNoText(t("Couldn't index items. Check the logs for details."), "Index error isn't displayed.");
|
||||
$this->checkIndexStatus(8, 14, FALSE, 1);
|
||||
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 1, 'Indexing order test 1: correct result count.');
|
||||
$this->assertEqual(array_keys($results['results']), array(11), 'Indexing order test 1: correct results.');
|
||||
|
||||
// Now index with a cron run, but stop at item 8.
|
||||
variable_set('search_api_test_indexing_break', 8);
|
||||
$this->cronRun();
|
||||
// Now just the four new items should have been indexed.
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 4, 'Indexing order test 2: correct result count.');
|
||||
$this->assertEqual(array_keys($results['results']), array(11, 12, 13, 14), 'Indexing order test 2: correct results.');
|
||||
|
||||
// This time stop at item 5 (should be the last one).
|
||||
variable_set('search_api_test_indexing_break', 5);
|
||||
$this->cronRun();
|
||||
// Now all new and changed items should have been indexed, except item 5.
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 6, 'Indexing order test 3: correct result count.');
|
||||
$this->assertEqual(array_keys($results['results']), array(2, 8, 11, 12, 13, 14), 'Indexing order test 3: correct results.');
|
||||
|
||||
// Index the remaining item.
|
||||
variable_set('search_api_test_indexing_break', 0);
|
||||
$this->cronRun();
|
||||
// Now all new and changed items should have been indexed.
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual($results['result count'], 7, 'Indexing order test 4: correct result count.');
|
||||
$this->assertEqual(array_keys($results['results']), array(2, 5, 8, 11, 12, 13, 14), 'Indexing order test 4: correct results.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the server tasks system works correctly.
|
||||
*
|
||||
* Uses the "search_api_test_error_state" variable to trigger exceptions in
|
||||
* the test service class and asserts that the Search API reacts correctly and
|
||||
* re-attempts the operation on the next cron run.
|
||||
*/
|
||||
protected function checkServerTasks() {
|
||||
// Make sure none of the previous operations added any tasks.
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 0, 'No server tasks were previously saved.');
|
||||
|
||||
// Set error state for test service, so all operations will fail.
|
||||
variable_set('search_api_test_error_state', TRUE);
|
||||
|
||||
// Delete some items.
|
||||
$this->drupalGet('search_api_test/delete/8');
|
||||
$this->drupalGet('search_api_test/delete/12');
|
||||
|
||||
// Assert that the indexed items haven't changed yet.
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual(array_keys($results['results']), array(2, 5, 8, 11, 12, 13, 14), 'During error state, no indexed items were deleted.');
|
||||
|
||||
// Check that tasks were correctly inserted.
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 2, 'Server tasks for deleted items were saved.');
|
||||
|
||||
// Now reset the error state variable and run cron to delete the items.
|
||||
variable_set('search_api_test_error_state', FALSE);
|
||||
$this->cronRun();
|
||||
|
||||
// Assert that the indexed items were indeed deleted from the server.
|
||||
$results = $this->doSearch();
|
||||
$this->assertEqual(array_keys($results['results']), array(2, 5, 11, 13, 14), 'Pending "delete item" server tasks were correctly executed during the cron run.');
|
||||
|
||||
// Check that the tasks were correctly deleted.
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 0, 'Server tasks were correctly deleted after being executed.');
|
||||
|
||||
// Now we first delete more items, then disable the server (thereby removing
|
||||
// the index from it) – all while in error state.
|
||||
variable_set('search_api_test_error_state', TRUE);
|
||||
$this->drupalGet('search_api_test/delete/14');
|
||||
$this->drupalGet('search_api_test/delete/2');
|
||||
$settings['enabled'] = 0;
|
||||
$this->drupalPost("admin/config/search/search_api/server/{$this->server_id}/edit", $settings, t('Save settings'));
|
||||
|
||||
// Check whether the index was correctly removed from the server.
|
||||
$this->assertEqual($this->index()->server(), NULL, 'The index was successfully set to have no server.');
|
||||
$exception = FALSE;
|
||||
try {
|
||||
$this->doSearch();
|
||||
}
|
||||
catch (SearchApiException $e) {
|
||||
$exception = TRUE;
|
||||
}
|
||||
$this->assertTrue($exception, 'Searching on the index failed with an exception.');
|
||||
|
||||
// Check that only one task – to remove the index from the server – is now
|
||||
// present in the tasks table.
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 1, 'Only the "remove index" task is present in the server tasks.');
|
||||
|
||||
// Reset the error state variable, re-enable the server.
|
||||
variable_set('search_api_test_error_state', FALSE);
|
||||
$settings['enabled'] = 1;
|
||||
$this->drupalPost("admin/config/search/search_api/server/{$this->server_id}/edit", $settings, t('Save settings'));
|
||||
|
||||
// Check whether the index was really removed from the server now.
|
||||
$server = $this->server();
|
||||
$this->assertTrue(empty($server->options['indexes'][$this->index_id]), 'The index was removed from the server after cron ran.');
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 0, 'Server tasks were correctly deleted after being executed.');
|
||||
|
||||
// Put the index back on the server and index some items for the next tests.
|
||||
$settings = array('server' => $this->server_id);
|
||||
$this->drupalPost("admin/config/search/search_api/index/{$this->index_id}/edit", $settings, t('Save settings'));
|
||||
$this->cronRun();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether editing the server works correctly.
|
||||
*/
|
||||
protected function editServer() {
|
||||
$values = array(
|
||||
'name' => 'test-name-foo',
|
||||
@ -357,19 +686,49 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertText('test-test-baz', 'Service options changed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether clearing the index works correctly.
|
||||
*/
|
||||
protected function clearIndex() {
|
||||
$this->drupalPost("admin/config/search/search_api/index/{$this->index_id}/status", array(), t('Clear index'));
|
||||
$this->drupalPost("admin/config/search/search_api/index/{$this->index_id}", array(), t('Clear all indexed data'));
|
||||
$this->drupalPost(NULL, array(), t('Confirm'));
|
||||
$this->assertText(t('The index was successfully cleared.'));
|
||||
$this->assertText(t('All items still need to be indexed (@total total).', array('@total' => 10)), 'Correct index status displayed.');
|
||||
$this->assertText(t('@indexed/@total indexed', array('@indexed' => 0, '@total' => 14)), 'Correct index status displayed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether deleting the server works correctly.
|
||||
*
|
||||
* The index still lying on the server should be disabled and removed from it.
|
||||
* Also, any tasks with that server's ID should be deleted.
|
||||
*/
|
||||
protected function deleteServer() {
|
||||
// Insert some dummy tasks to check for.
|
||||
$server = $this->server();
|
||||
search_api_server_tasks_add($server, 'foo');
|
||||
search_api_server_tasks_add($server, 'bar', $this->index());
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 2, 'Dummy tasks were added.');
|
||||
|
||||
// Delete the server.
|
||||
$this->drupalPost("admin/config/search/search_api/server/{$this->server_id}/delete", array(), t('Confirm'));
|
||||
$this->assertNoText('test-name-foo', 'Server no longer listed.');
|
||||
$this->drupalGet("admin/config/search/search_api/index/{$this->index_id}/status");
|
||||
$this->assertText(t('The index is currently disabled.'), 'The index was disabled and removed from the server.');
|
||||
$this->drupalGet("admin/config/search/search_api/index/{$this->index_id}");
|
||||
$this->assertNoText(t('Server'), 'The index was removed from the server.');
|
||||
$this->assertText(t('disabled'), 'The index was disabled.');
|
||||
|
||||
// Check whether the tasks were correctly deleted.
|
||||
$task_count = db_query('SELECT COUNT(id) FROM {search_api_task}')->fetchField();
|
||||
$this->assertEqual($task_count, 0, 'Remaining server tasks were correctly deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether disabling and uninstalling the modules works correctly.
|
||||
*
|
||||
* This will disable and uninstall both the test module and the Search API. It
|
||||
* asserts that this works correctly (since the server has been deleted in
|
||||
* deleteServer()) and that all associated tables and variables are removed.
|
||||
*/
|
||||
protected function disableModules() {
|
||||
module_disable(array('search_api_test'), FALSE);
|
||||
$this->assertFalse(module_exists('search_api_test'), 'Test module was successfully disabled.');
|
||||
@ -384,7 +743,7 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
$this->assertFalse(db_table_exists('search_api_server'), 'Search server table was successfully removed.');
|
||||
$this->assertFalse(db_table_exists('search_api_index'), 'Search index table was successfully removed.');
|
||||
$this->assertFalse(db_table_exists('search_api_item'), 'Index items table was successfully removed.');
|
||||
$this->assertNull(variable_get('search_api_tasks'), 'Tasks variable was correctly removed.');
|
||||
$this->assertFalse(db_table_exists('search_api_task'), 'Server tasks table was successfully removed.');
|
||||
$this->assertNull(variable_get('search_api_index_worker_callback_runtime'), 'Worker runtime variable was correctly removed.');
|
||||
}
|
||||
|
||||
@ -398,8 +757,19 @@ class SearchApiWebTest extends DrupalWebTestCase {
|
||||
*/
|
||||
class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
|
||||
/**
|
||||
* The index used by these tests.
|
||||
*
|
||||
* @var SearchApIindex
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* Overrides DrupalTestCase::assertEqual().
|
||||
*
|
||||
* For arrays, checks whether all array keys are mapped the same in both
|
||||
* arrays recursively, while ignoring their order.
|
||||
*/
|
||||
protected function assertEqual($first, $second, $message = '', $group = 'Other') {
|
||||
if (is_array($first) && is_array($second)) {
|
||||
return $this->assertTrue($this->deepEquals($first, $second), $message, $group);
|
||||
@ -409,6 +779,20 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether two values are equal.
|
||||
*
|
||||
* For arrays, this is done by comparing the key/value pairs recursively
|
||||
* instead of checking for simple equality.
|
||||
*
|
||||
* @param mixed $first
|
||||
* The first value.
|
||||
* @param mixed $second
|
||||
* The second value.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the two values are equal, FALSE otherwise.
|
||||
*/
|
||||
protected function deepEquals($first, $second) {
|
||||
if (!is_array($first) || !is_array($second)) {
|
||||
return $first == $second;
|
||||
@ -424,6 +808,12 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
return empty($second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about this test case.
|
||||
*
|
||||
* @return array
|
||||
* An array with information about this test case.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Test search API components',
|
||||
@ -432,6 +822,9 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp('entity', 'search_api');
|
||||
$this->index = entity_create('search_api_index', array(
|
||||
@ -455,6 +848,12 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the functionality of several components of the module.
|
||||
*
|
||||
* This is the single test method called by the Simpletest framework. It in
|
||||
* turn calls other helper methods to test specific functionality.
|
||||
*/
|
||||
public function testUnits() {
|
||||
$this->checkQueryParseKeys();
|
||||
$this->checkIgnoreCaseProcessor();
|
||||
@ -462,11 +861,13 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
$this->checkHtmlFilter();
|
||||
}
|
||||
|
||||
public function checkQueryParseKeys() {
|
||||
/**
|
||||
* Checks whether the keys are parsed correctly by the query class.
|
||||
*/
|
||||
protected function checkQueryParseKeys() {
|
||||
$options['parse mode'] = 'direct';
|
||||
$mode = &$options['parse mode'];
|
||||
$query = new SearchApiQuery($this->index, $options);
|
||||
$modes = $query->parseModes();
|
||||
|
||||
$query->keys('foo');
|
||||
$this->assertEqual($query->getKeys(), 'foo', '"Direct query" parse mode, test 1.');
|
||||
@ -499,8 +900,10 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
$this->assertEqual($query->getKeys(), array('#conjunction' => 'AND', 'Münster'), '"Multiple terms" parse mode, test 4.');
|
||||
}
|
||||
|
||||
public function checkIgnoreCaseProcessor() {
|
||||
$types = search_api_field_types();
|
||||
/**
|
||||
* Tests the functionality of the "Ignore case" processor.
|
||||
*/
|
||||
protected function checkIgnoreCaseProcessor() {
|
||||
$orig = 'Foo bar BaZ, ÄÖÜÀÁ<>»«.';
|
||||
$processed = drupal_strtolower($orig);
|
||||
$items = array(
|
||||
@ -566,7 +969,10 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
$this->assertEqual($query->getFilter()->getFilters(), $filters2, 'Filters were processed correctly.');
|
||||
}
|
||||
|
||||
public function checkTokenizer() {
|
||||
/**
|
||||
* Tests the functionality of the "Tokenizer" processor.
|
||||
*/
|
||||
protected function checkTokenizer() {
|
||||
$orig = 'Foo bar1 BaZ, La-la-la.';
|
||||
$processed1 = array(
|
||||
array(
|
||||
@ -648,7 +1054,10 @@ class SearchApiUnitTest extends DrupalWebTestCase {
|
||||
$this->assertEqual($query->getKeys(), 'foo"b r b z"foob r1', 'Search keys were processed correctly.');
|
||||
}
|
||||
|
||||
public function checkHtmlFilter() {
|
||||
/**
|
||||
* Tests the functionality of the "HTML filter" processor.
|
||||
*/
|
||||
protected function checkHtmlFilter() {
|
||||
$orig = <<<END
|
||||
This is <em lang="en" title =
|
||||
"something">a test</em>.
|
||||
|
@ -10,9 +10,9 @@ files[] = search_api_test.module
|
||||
|
||||
hidden = TRUE
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-09-01
|
||||
version = "7.x-1.8"
|
||||
; Information added by Drupal.org packaging script on 2013-12-25
|
||||
version = "7.x-1.11"
|
||||
core = "7.x"
|
||||
project = "search_api"
|
||||
datestamp = "1378025826"
|
||||
datestamp = "1387965506"
|
||||
|
||||
|
@ -39,7 +39,13 @@ function search_api_test_schema() {
|
||||
'description' => 'A comma separated list of keywords.',
|
||||
'type' => 'varchar',
|
||||
'length' => 200,
|
||||
'not null' => FALSE,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
'prices' => array(
|
||||
'description' => 'A comma separated list of prices.',
|
||||
'type' => 'varchar',
|
||||
'length' => 200,
|
||||
'not null' => FALSE,
|
||||
),
|
||||
),
|
||||
'primary key' => array('id'),
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test functions and classes for testing the Search API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
@ -11,15 +16,21 @@ function search_api_test_menu() {
|
||||
'page arguments' => array('search_api_test_insert_item'),
|
||||
'access callback' => TRUE,
|
||||
),
|
||||
'search_api_test/%search_api_test' => array(
|
||||
'search_api_test/view/%search_api_test' => array(
|
||||
'title' => 'View item',
|
||||
'page callback' => 'search_api_test_view',
|
||||
'page arguments' => array(1),
|
||||
'page arguments' => array(2),
|
||||
'access callback' => TRUE,
|
||||
),
|
||||
'search_api_test/query/%search_api_index' => array(
|
||||
'title' => 'Search query',
|
||||
'page callback' => 'search_api_test_query',
|
||||
'search_api_test/touch/%search_api_test' => array(
|
||||
'title' => 'Mark item as changed',
|
||||
'page callback' => 'search_api_test_touch',
|
||||
'page arguments' => array(2),
|
||||
'access callback' => TRUE,
|
||||
),
|
||||
'search_api_test/delete/%search_api_test' => array(
|
||||
'title' => 'Delete items',
|
||||
'page callback' => 'search_api_test_delete',
|
||||
'page arguments' => array(2),
|
||||
'access callback' => TRUE,
|
||||
),
|
||||
@ -46,6 +57,9 @@ function search_api_test_insert_item(array $form, array &$form_state) {
|
||||
'keywords' => array(
|
||||
'#type' => 'textfield',
|
||||
),
|
||||
'prices' => array(
|
||||
'#type' => 'textfield',
|
||||
),
|
||||
'submit' => array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
@ -74,42 +88,22 @@ function search_api_test_load($id) {
|
||||
* Menu callback for displaying search_api_test entities.
|
||||
*/
|
||||
function search_api_test_view($entity) {
|
||||
return array('text' => nl2br(check_plain(print_r($entity, TRUE))));
|
||||
return nl2br(check_plain(print_r($entity, TRUE)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback for executing a search.
|
||||
* Menu callback for marking a "search_api_test" entity as changed.
|
||||
*/
|
||||
function search_api_test_query(SearchApiIndex $index, $keys = 'foo bar', $offset = 0, $limit = 10, $fields = NULL, $sort = NULL, $filters = NULL) {
|
||||
$query = $index->query()
|
||||
->keys($keys ? $keys : NULL)
|
||||
->range($offset, $limit);
|
||||
if ($fields) {
|
||||
$query->fields(explode(',', $fields));
|
||||
}
|
||||
if ($sort) {
|
||||
$sort = explode(',', $sort);
|
||||
$query->sort($sort[0], $sort[1]);
|
||||
}
|
||||
else {
|
||||
$query->sort('search_api_id', 'ASC');
|
||||
}
|
||||
if ($filters) {
|
||||
$filters = explode(',', $filters);
|
||||
foreach ($filters as $filter) {
|
||||
$filter = explode('=', $filter);
|
||||
$query->condition($filter[0], $filter[1]);
|
||||
}
|
||||
}
|
||||
$result = $query->execute();
|
||||
function search_api_test_touch($entity) {
|
||||
module_invoke_all('entity_update', $entity, 'search_api_test');
|
||||
}
|
||||
|
||||
$ret = '';
|
||||
$ret .= 'result count = ' . (int) $result['result count'] . '<br/>';
|
||||
$ret .= 'results = (' . (empty($result['results']) ? '' : implode(', ', array_keys($result['results']))) . ')<br/>';
|
||||
$ret .= 'warnings = (' . (empty($result['warnings']) ? '' : '"' . implode('", "', $result['warnings']) . '"') . ')<br/>';
|
||||
$ret .= 'ignored = (' . (empty($result['ignored']) ? '' : implode(', ', $result['ignored'])) . ')<br/>';
|
||||
$ret .= nl2br(check_plain(print_r($result['performance'], TRUE)));
|
||||
return $ret;
|
||||
/**
|
||||
* Menu callback for marking a "search_api_test" entity as changed.
|
||||
*/
|
||||
function search_api_test_delete($entity) {
|
||||
db_delete('search_api_test')->condition('id', $entity->id)->execute();
|
||||
module_invoke_all('entity_delete', $entity, 'search_api_test');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,6 +163,12 @@ function search_api_test_entity_property_info() {
|
||||
'description' => 'An optional collection of keywords describing the item.',
|
||||
'getter callback' => 'search_api_test_list_callback',
|
||||
),
|
||||
'prices' => array(
|
||||
'label' => 'Prices',
|
||||
'type' => 'list<decimal>',
|
||||
'description' => 'An optional list of prices.',
|
||||
'getter callback' => 'search_api_test_list_callback',
|
||||
),
|
||||
);
|
||||
|
||||
return $info;
|
||||
@ -193,13 +193,17 @@ function search_api_test_parent($entity) {
|
||||
/**
|
||||
* List callback.
|
||||
*/
|
||||
function search_api_test_list_callback($data) {
|
||||
//return is_array($entity->keywords) ? $entity->keywords : explode(',', $entity->keywords);
|
||||
function search_api_test_list_callback($data, array $options, $name) {
|
||||
if (is_array($data)) {
|
||||
$res = is_array($data['keywords']) ? $data['keywords'] : explode(',', $data['keywords']);
|
||||
$res = is_array($data[$name]) ? $data[$name] : explode(',', $data[$name]);
|
||||
}
|
||||
else {
|
||||
$res = is_array($data->keywords) ? $data->keywords : explode(',', $data->keywords);
|
||||
$res = is_array($data->$name) ? $data->$name : explode(',', $data->$name);
|
||||
}
|
||||
if ($name == 'prices') {
|
||||
foreach ($res as &$x) {
|
||||
$x = (float) $x;
|
||||
}
|
||||
}
|
||||
return array_filter($res);
|
||||
}
|
||||
@ -221,6 +225,11 @@ function search_api_test_search_api_service_info() {
|
||||
*/
|
||||
class SearchApiTestService extends SearchApiAbstractService {
|
||||
|
||||
/**
|
||||
* Overrides SearchApiAbstractService::configurationForm().
|
||||
*
|
||||
* Returns a single text field for testing purposes.
|
||||
*/
|
||||
public function configurationForm(array $form, array &$form_state) {
|
||||
$form = array(
|
||||
'test' => array(
|
||||
@ -236,38 +245,72 @@ class SearchApiTestService extends SearchApiAbstractService {
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function indexItems(SearchApiIndex $index, array $items) {
|
||||
// Refuse to index items with IDs that are multiples of 8 unless the
|
||||
// "search_api_test_index_all" variable is set.
|
||||
if (variable_get('search_api_test_index_all', FALSE)) {
|
||||
return $this->index($index, array_keys($items));
|
||||
}
|
||||
$ret = array();
|
||||
foreach ($items as $id => $item) {
|
||||
if ($id % 8) {
|
||||
$ret[] = $id;
|
||||
}
|
||||
}
|
||||
return $this->index($index, $ret);
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addIndex(SearchApiIndex $index) {
|
||||
$this->checkErrorState();
|
||||
}
|
||||
|
||||
protected function index(SearchApiIndex $index, array $ids) {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fieldsUpdated(SearchApiIndex $index) {
|
||||
$this->checkErrorState();
|
||||
return db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeIndex($index) {
|
||||
$this->checkErrorState();
|
||||
parent::removeIndex($index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements SearchApiServiceInterface::indexItems().
|
||||
*
|
||||
* Indexes items by storing their IDs in the server's options.
|
||||
*
|
||||
* If the "search_api_test_indexing_break" variable is set, the item with
|
||||
* that ID will not be indexed.
|
||||
*/
|
||||
public function indexItems(SearchApiIndex $index, array $items) {
|
||||
$this->checkErrorState();
|
||||
// Refuse to index the item with the same ID as the
|
||||
// "search_api_test_indexing_break" variable, if it is set.
|
||||
$exclude = variable_get('search_api_test_indexing_break', 8);
|
||||
foreach ($items as $id => $item) {
|
||||
if ($id == $exclude) {
|
||||
unset($items[$id]);
|
||||
}
|
||||
}
|
||||
$ids = array_keys($items);
|
||||
|
||||
$this->options += array('indexes' => array());
|
||||
$this->options['indexes'] += array($index->machine_name => array());
|
||||
$this->options['indexes'][$index->machine_name] += drupal_map_assoc($ids);
|
||||
sort($this->options['indexes'][$index->machine_name]);
|
||||
asort($this->options['indexes'][$index->machine_name]);
|
||||
$this->server->save();
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override so deleteItems() isn't called which would otherwise lead to the
|
||||
* Overrides SearchApiAbstractService::preDelete().
|
||||
*
|
||||
* Overridden so deleteItems() isn't called which would otherwise lead to the
|
||||
* server being updated and, eventually, to a notice because there is no
|
||||
* server to be updated anymore.
|
||||
*/
|
||||
public function preDelete() {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems($ids = 'all', SearchApiIndex $index = NULL) {
|
||||
$this->checkErrorState();
|
||||
if ($ids == 'all') {
|
||||
if ($index) {
|
||||
$this->options['indexes'][$index->machine_name] = array();
|
||||
@ -284,6 +327,12 @@ class SearchApiTestService extends SearchApiAbstractService {
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements SearchApiServiceInterface::indexItems().
|
||||
*
|
||||
* Will ignore all query settings except the range, as only the item IDs are
|
||||
* indexed.
|
||||
*/
|
||||
public function search(SearchApiQueryInterface $query) {
|
||||
$options = $query->getOptions();
|
||||
$ret = array();
|
||||
@ -315,8 +364,16 @@ class SearchApiTestService extends SearchApiAbstractService {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function fieldsUpdated(SearchApiIndex $index) {
|
||||
return db_query('SELECT COUNT(*) FROM {search_api_test}')->fetchField() > 0;
|
||||
/**
|
||||
* Throws an exception if the "search_api_test_error_state" variable is set.
|
||||
*
|
||||
* @throws SearchApiException
|
||||
* If the "search_api_test_error_state" variable is set.
|
||||
*/
|
||||
protected function checkErrorState() {
|
||||
if (variable_get('search_api_test_error_state', FALSE)) {
|
||||
throw new SearchApiException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user