+ $form['use_more']['#description'] = t('This will add a more link to the bottom of this view, which will link to the base path for the facet links.');
+ foreach ($index->getFields() as $key => $field) {
+ $fields[$key] = $field['name'];
+ }
+ }
+ if (!empty($fields)) {
+ $form['fields'] = array(
+ '#type' => 'select',
+ '#title' => t('Fields for Similarity'),
+ '#description' => t('Select the fields that will be used for finding similar content. If no fields are selected, all available fields will be used.'),
+ '#options' => $fields,
+ '#size' => min(8, count($fields)),
+ '#multiple' => TRUE,
+ '#default_value' => $this->options['fields'],
+ );
+ }
+ else {
+ $form['fields'] = array(
+ '#type' => 'value',
+ '#value' => array(),
+ );
+ }
+ }
+
+ /**
+ * Set up the query for this argument.
+ *
+ * The argument sent may be found at $this->argument.
+ */
+ public function query($group_by = FALSE) {
+ $server = $this->query->getIndex()->server();
+ if (!$server->supportsFeature('search_api_mlt')) {
+ public function options_form(&$form, &$form_state) {
+ parent::options_form($form, $form_state);
+
+ $form['mode'] = array(
+ '#title' => t('Use as'),
+ '#type' => 'radios',
+ '#options' => array(
+ 'keys' => t('Search keys – multiple words will be split and the filter will influence relevance. You can change how search keys are parsed under "Advanced" > "Query settings".'),
+ 'filter' => t("Search filter – use as a single phrase that restricts the result set but doesn't influence relevance."),
+ ),
+ '#default_value' => $this->options['mode'],
+ );
+
+ $fields = $this->getFulltextFields();
+ if (!empty($fields)) {
+ $form['fields'] = array(
+ '#type' => 'select',
+ '#title' => t('Searched fields'),
+ '#description' => t('Select the fields that will be searched. If no fields are selected, all available fulltext fields will be searched.'),
+ '#options' => $fields,
+ '#size' => min(4, count($fields)),
+ '#multiple' => TRUE,
+ '#default_value' => $this->options['fields'],
+ );
+ }
+ else {
+ $form['fields'] = array(
+ '#type' => 'value',
+ '#value' => array(),
+ );
+ }
+ 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.'),
+ '#title' => t('Additional access checks on result entities'),
+ '#description' => t("Execute an access check for all result entities. This prevents users from seeing inappropriate content when the index contains stale data, or doesn't provide access checks. However, result counts, paging and other things won't work correctly if results are eliminated in this way, so only use this as a last ressort (and in addition to other checks, if possible)."),
+ watchdog('search_api_views', 'The search index returned a reference to an entity with ID @id, which does not exist in the database. Your index may be out of sync and should be rebuilt.', array('@id' => $id), WATCHDOG_ERROR);
+ }
+ }
+ catch (EntityMetadataWrapperException $e) {
+ watchdog_exception('search_api_views', $e, "%type while trying to load search result entity with ID @id: !message in %function (line %line of %file).", array('@id' => $id), WATCHDOG_ERROR);
+ }
+ }
+ return array($type, $return);
+ }
+
+ /**
+ * Returns the according metadata wrappers for the given query results.
+ *
+ * This is necessary to support generic entity handlers and plugins with this
+ * query backend.
+ */
+ public function get_result_wrappers($results, $relationship = NULL, $field = NULL) {
+ $entity_type = $this->index->getEntityType();
+ $wrappers = array();
+ $load_entities = array();
+ foreach ($results as $row_index => $row) {
+ if ($entity_type && isset($row->entity)) {
+ // If this entity isn't load, register it for pre-loading.
+ foreach (views_get_all_views() as $name => $view) {
+ if (empty($view->disabled) && $view->base_table == $table) {
+ $names[] = $name;
+ // @todo: if ($index_deleted) $view->delete()?
+ }
+ }
+ if ($names) {
+ views_invalidate_cache();
+ drupal_set_message(t('The following views were using the index %name: @views. You should disable or delete them.', array('%name' => $index->name, '@views' => implode(', ', $names))), 'warning');
+ 'help' => t('Use the %name search index for filtering and retrieving data.', array('%name' => $index->name)),
+ 'query class' => 'search_api_views_query',
+ );
+ if (isset($entity_types[$index->getEntityType()])) {
+ $table['table'] += array(
+ 'entity type' => $index->getEntityType(),
+ 'skip entity load' => TRUE,
+ );
+ }
+
+ try {
+ $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);
+ continue;
+ }
+
+ // Add field handlers and relationships provided by the Entity API.
+ 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) {
+ $form['fields']['#prefix'] .= '<div class="messages warning">All changes in the form will not be saved until the <em>Save configuration</em> button at the form bottom is clicked.</div>';
+ form_error($form['fields'][$name]['fields'], t('You have to select at least one field to aggregate. If you want to remove an aggregated field, please delete its name.'));
+ }
+ }
+ }
+
+ public function configurationFormSubmit(array $form, array &$values, array &$form_state) {
+ if (empty($values['fields'])) {
+ return array();
+ }
+ $index_fields = $this->index->getFields(FALSE);
+ foreach ($values['fields'] as $name => $field) {
+ return t('A @type aggregation of the following fields: @fields.', array('@type' => $type, '@fields' => implode(', ', $fields)));
+ }
+
+ /**
+ * Helper method for getting all available aggregation types.
+ *
+ * @param $info (optional)
+ * One of "name", "type" or "description", to indicate what values should be
+ * returned for the types. Defaults to "name".
+ *
+ */
+ protected function getTypes($info = 'name') {
+ switch ($info) {
+ case 'name':
+ return array(
+ 'fulltext' => t('Fulltext'),
+ 'sum' => t('Sum'),
+ 'count' => t('Count'),
+ 'max' => t('Maximum'),
+ 'min' => t('Minimum'),
+ 'first' => t('First'),
+ );
+ case 'type':
+ return array(
+ 'fulltext' => 'text',
+ 'sum' => 'integer',
+ 'count' => 'integer',
+ 'max' => 'integer',
+ 'min' => 'integer',
+ 'first' => 'string',
+ );
+ case 'description':
+ return array(
+ 'fulltext' => t('The Fulltext aggregation concatenates the text data of all contained fields.'),
+ 'sum' => t('The Sum aggregation adds the values of all contained fields numerically.'),
+ 'count' => t('The Count aggregation takes the total number of contained field values as the aggregated field value.'),
+ 'max' => t('The Maximum aggregation computes the numerically largest contained field value.'),
+ 'min' => t('The Minimum aggregation computes the numerically smallest contained field value.'),
+ 'first' => t('The First aggregation will simply keep the first encountered field value. This is helpful foremost when you know that a list field will only have a single value.'),
+ );
+ }
+ }
+
+ /**
+ * Submit helper callback for buttons in the callback's configuration form.
+ */
+ public function formButtonSubmit(array $form, array &$form_state) {
+ throw new SearchApiDataSourceException(t("Entity type @type doesn't specify an ID key.", array('@type' => $info['label'])));
+ }
+ $field = $info['entity keys']['id'];
+ if (empty($properties['properties'][$field]['type'])) {
+ throw new SearchApiDataSourceException(t("Entity type @type doesn't specify a type for the @prop property.", array('@type' => $info['label'], '@prop' => $field)));
+ throw new SearchApiDataSourceException(t("Entity type @type uses list field @prop as its ID.", array('@type' => $info['label'], '@prop' => $field)));
+ }
+ if ($type == 'token') {
+ $type = 'string';
+ }
+ return array(
+ 'key' => $field,
+ 'type' => $type,
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function loadItems(array $ids) {
+ $items = entity_load($this->entityType, $ids);
+ // If some items couldn't be loaded, remove them from tracking.
+ throw new SearchApiException(t('Unknown server @server specified for index @name.', array('@server' => $this->server, '@name' => $this->machine_name)));
+ '#description' => t('Text/HTML that will be prepended to all occurrences of search keywords in highlighted text.'),
+ '#default_value' => $this->options['prefix'],
+ );
+ $form['suffix'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Highlighting suffix'),
+ '#description' => t('Text/HTML that will be appended to all occurrences of search keywords in highlighted text.'),
+ '#default_value' => $this->options['suffix'],
+ );
+ $form['excerpt'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Create excerpt'),
+ '#description' => t('When enabled, an excerpt will be created for searches with keywords, containing all occurrences of keywords in a fulltext field.'),
+ '#default_value' => $this->options['excerpt'],
+ );
+ $form['excerpt_length'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Excerpt length'),
+ '#description' => t('The requested length of the excerpt, in characters.'),
+ '#markup' => '<p>' . t('Provide a stopwords file or enter the words in this form. If you do both, both will be used. Read about !stopwords.', array('!stopwords' => l(t('stop words'), "http://en.wikipedia.org/wiki/Stop_words"))) . '</p>',
+ ),
+ 'file' => array(
+ '#type' => 'textfield',
+ '#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(
+ '#type' => 'textarea',
+ '#title' => t('Stopwords'),
+ '#description' => t('Enter a space and/or linebreak separated list of stopwords that will be removed from content before it is indexed and from search terms before searching.'),
+ '#default_value' => t("but\ndid\nthe this that those\netc"),
+ '#description' => t('Specify characters which should be removed from fulltext fields and search strings (e.g., "-"). The same format as above is used.'),
+ * 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) {
+ $languages[] = LANGUAGE_NONE;
+ }
+
+ $languages = drupal_map_assoc($languages);
+ $langs_to_add = $languages;
+ $filters = $this->filter->getFilters();
+ while ($filters && $langs_to_add) {
+ $filter = array_shift($filters);
+ if (is_array($filter)) {
+ if ($filter[0] == 'search_api_language' && $filter[2] == '=') {
+ $lang = $filter[1];
+ if (isset($languages[$lang])) {
+ unset($langs_to_add[$lang]);
+ }
+ else {
+ throw new SearchApiException(t('Impossible combination of filters and languages. There is a filter for "@language", but allowed languages are: "@languages".', array('@language' => $lang, '@languages' => implode('", "', $languages))));
+ if (!($this->proxy instanceof SearchApiServiceInterface)) {
+ throw new SearchApiException(t('Search server with machine name @name specifies illegal service class @class.', array('@name' => $this->machine_name, '@class' => $this->class)));
+ }
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public function __call($name, $arguments = array()) {
+ public function fieldsUpdated(SearchApiIndex $index) {
+ $this->ensureProxy();
+ 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);
+ $info = format_plural($on_server, 'There is 1 item indexed on the server for this index. (<a href="@url">More information</a>)', 'There are @count items indexed on the server for this index. (<a href="@url">More information</a>)', $vars);
+ // Reset the index's internal property cache to correctly incorporate the
+ // new data alterations.
+ $index->resetCaches();
+
+ $index->save();
+ $index->reindex();
+ drupal_set_message(t("The indexing workflow was successfully edited. All content was scheduled for re-indexing so the new settings can take effect."));
+ }
+ else {
+ drupal_set_message(t('No values were changed.'));
+ '#description' => t('<p>The datatype of a field determines how it can be used for searching and filtering. Fields indexed with type "Fulltext" and multi-valued fields (marked with <sup>1</sup>) cannot be used for sorting. ' .
+ 'The boost is used to give additional weight to certain fields, e.g. titles or tags. It only takes effect for fulltext fields.</p>' .
+ '<p>Whether detailed field types are supported depends on the type of server this index resides on. ' .
+ 'In any case, fields of type "Fulltext" will always be fulltext-searchable.</p>'),
+ );
+ if ($index->server) {
+ $form['description']['#description'] .= '<p>' . t('Check the <a href="@server-url">' . "server's</a> service class description for details.",
+ t('Clear server @name', array('@name' => $entity->name)),
+ t('Do you really want to clear all indexed data from this server?'),
+ t('This will permanently remove all data currently indexed on this server. Before the data is reindexed, searches on the indexes associated with this server will not return any results. This action cannot be undone. <strong>Use with caution!</strong>'),
+ t("The server's indexed data was successfully cleared."),
+ );
+ break;
+
+ case 'disable':
+ $text = array(
+ t('Disable server @name', array('@name' => $entity->name)),
+ t('Do you really want to disable this server?'),
+ t('This will disconnect all indexes from this server and disable them. Searches on these indexes will not be available until they are added to another server and re-enabled. All indexed data (except for read-only indexes) on this server will be cleared.'),
+ t('The server and its indexes were successfully disabled.'),
+ );
+ break;
+
+ case 'delete':
+ if ($entity->hasStatus(ENTITY_OVERRIDDEN)) {
+ $text = array(
+ t('Revert server @name', array('@name' => $entity->name)),
+ t('Do you really want to revert this server?'),
+ t('This will revert all settings for this server back to the defaults. This action cannot be undone.'),
+ t('The server settings have been successfully reverted.'),
+ );
+ }
+ else {
+ $text = array(
+ t('Delete server @name', array('@name' => $entity->name)),
+ t('Do you really want to delete this server?'),
+ t('This will delete the server and disable all associated indexes. ' .
+ "Searches on these indexes won't be available until they are moved to another server and re-enabled."),
+ t('The server was successfully deleted.'),
+ );
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+ break;
+ case 'index':
+ switch ($action) {
+ case 'reindex':
+ $text = array(
+ t('Re-index index @name', array('@name' => $entity->name)),
+ t('Do you really want to queue all items on this index for re-indexing?'),
+ t('This will mark all items for this index to be marked as needing to be indexed. Searches on this index will continue to yield results while the items are being re-indexed. This action cannot be undone.'),
+ t('The index was successfully marked for re-indexing.'),
+ );
+ break;
+
+ case 'clear':
+ $text = array(
+ t('Clear index @name', array('@name' => $entity->name)),
+ t('Do you really want to clear the indexed data of this index?'),
+ t('This will remove all data currently indexed for this index. Before the data is reindexed, searches on the index will not return any results. This action cannot be undone.'),
+ t('The index was successfully cleared.'),
+ );
+ break;
+
+ case 'disable':
+ $text = array(
+ t('Disable index @name', array('@name' => $entity->name)),
+ t('Do you really want to disable this index?'),
+ t("Searches on this index won't be available until it is re-enabled."),
+ t('The index was successfully disabled.'),
+ );
+ break;
+
+ case 'delete':
+ if ($entity->hasStatus(ENTITY_OVERRIDDEN)) {
+ $text = array(
+ t('Revert index @name', array('@name' => $entity->name)),
+ t('Do you really want to revert this index?'),
+ t('This will revert all settings on this index back to the defaults. This action cannot be undone.'),
+ t('The index settings have been successfully reverted.'),
+ );
+ }
+ else {
+ $text = array(
+ t('Delete index @name', array('@name' => $entity->name)),
+ t('Do you really want to delete this index?'),
+ t('This will remove the index from the server and delete all settings. ' .
+ * Original file by agentrickard for Palantir.net
+ */
+
+/**
+ * Implements hook_drush_command().
+ */
+function search_api_drush_command() {
+ $items = array();
+
+ $items['search-api-list'] = array(
+ 'description' => 'List all search indexes.',
+ 'examples' => array(
+ 'drush searchapi-list' => dt('List all search indexes.'),
+ 'drush sapi-l' => dt('Alias to list all search indexes.'),
+ ),
+ '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(
+ 'drush searchapi-status' => dt('Show the status of all search indexes.'),
+ 'drush sapi-s' => dt('Alias to show the status of all search indexes.'),
+ 'drush sapi-s 1' => dt('Show the status of the search index with the ID !id.', array('!id' => 1)),
+ 'drush sapi-s default_node_index' => dt('Show the status of 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.'),
+ ),
+ 'aliases' => array('sapi-s'),
+ );
+
+ $items['search-api-index'] = array(
+ 'description' => 'Index items for one or all enabled search_api indexes.',
+ 'examples' => array(
+ 'drush searchapi-index' => dt('Index items for all enabled indexes.'),
+ 'drush sapi-i' => dt('Alias to index items for all enabled indexes.'),
+ 'drush sapi-i 1' => dt('Index items for the index with the ID !id.', array('!id' => 1)),
+ 'drush sapi-i default_node_index' => dt('Index items for the index with the machine name !name.', array('!name' => 'default_node_index')),
+ 'drush sapi-i 1 100' => dt("Index a maximum number of !limit items (index's cron batch size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!id' => 1)),
+ 'drush sapi-i 1 100 10' => dt("Index a maximum number of !limit items (!batch_size items per batch run) for the index with the ID !id.", array('!limit' => 100, '!batch_size' => 10, '!id' => 1)),
+ ),
+ 'arguments' => array(
+ 'index_id' => dt('The numeric ID or machine name of an index.'),
+ 'limit' => dt("The number of items to index (index's cron batch size items per run). Set to 0 to index all items. Defaults to 0 (index all)."),
+ 'batch_size' => dt("The number of items to index per batch run. Set to 0 to index all items at once. Defaults to the index's cron batch size."),
+ ),
+ 'aliases' => array('sapi-i'),
+ );
+
+ $items['search-api-reindex'] = array(
+ 'description' => 'Force reindexing of one or all search indexes, without clearing existing index data.',
+ 'examples' => array(
+ 'drush searchapi-reindex' => dt('Schedule all search indexes for reindexing.'),
+ 'drush sapi-r' => dt('Alias to schedule all search indexes for reindexing .'),
+ 'drush sapi-r 1' => dt('Schedule the search index with the ID !id for re-indexing.', array('!id' => 1)),
+ 'drush sapi-r default_node_index' => dt('Schedule the search index with the machine name !name for re-indexing.', array('!name' => 'default_node_index')),
+ ),
+ 'arguments' => array(
+ 'index_id' => dt('The numeric ID or machine name of an index.'),
+ ),
+ 'aliases' => array('sapi-r'),
+ );
+
+ $items['search-api-clear'] = array(
+ 'description' => 'Clear one or all search indexes and mark them for re-indexing.',
+ 'examples' => array(
+ 'drush searchapi-clear' => dt('Clear all search indexes.'),
+ 'drush sapi-c' => dt('Alias to clear all search indexes.'),
+ '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.'),
+ drush_log(dt("Indexing a maximum number of !limit items (!batch_size items per batch run) for the index !index.", array('!index' => $index->name, '!limit' => $limit, '!batch_size' => $batch_size)), 'ok');
+
+ // Create the batch.
+ if (!_search_api_batch_indexing_create($index, $batch_size, $limit, $remaining, TRUE)) {
+ drush_log(dt("Couldn't create a batch, please check the batch size and limit parameters."), 'error');
+ }
+ else {
+ // Launch the batch process.
+ drush_backend_batch_process();
+ }
+ }
+}
+
+/**
+ * Copy of formal_plural that works with drush as 't' may not be available.
+ $output .= '<p>' . t('The following service classes are available for creating a search server.') . "</p>\n";
+ $output .= implode("\n\n", $classes);
+ }
+ return $output;
+ case 'admin/config/search/search_api':
+ return '<p>' . t('A search server and search index are used to execute searches. Several indexes can exist per server.<br />You need at least one server and one index to create searches on your site.') . '</p>';
+ }
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function search_api_hook_info() {
+ // We use the same group for all hooks, so save code lines.
+ $info['explanation'] = format_plural(count($indexes), 'Item type in use by the following index: !indexes.', 'Item type(s) in use by the following indexes: !indexes.', $args);
+ }
+ }
+ }
+ // Check for defined service classes.
+ if (module_hook($file->name, 'search_api_service_info')) {
+ $classes = array();
+ foreach (search_api_get_service_info() as $class => $class_info) {
+ if ($class_info['module'] == $file->name) {
+ $classes[] = $class;
+ }
+ }
+ if ($classes) {
+ $sql = 'SELECT machine_name, name FROM {search_api_server} WHERE class IN (:classes)';
+ $explanation = format_plural(count($servers), 'Service class in use by the following server: !servers.', 'Service class(es) in use by the following servers: !servers.', $args);
+ 'description' => t('Add node access information to the index. <strong>Caution:</strong> This only affects the indexed nodes themselves, not any node reference fields that are indexed with them, or displayed in search results.'),
+ 'description' => t('Add node access information to the index. <strong>Caution:</strong> This only affects the indexed nodes themselves, not any node reference fields that are indexed with them, or displayed in search results.'),
+ 'description' => t('Exclude unpublished nodes from the index. <strong>Caution:</strong> This only affects the indexed nodes themselves. If an enabled node has references to disabled nodes, those will still be indexed (or displayed) normally.'),
+ watchdog('search_api', 'An illegal user UID was given for node access: @uid.', array('@uid' => $query->getOption('search_api_access_account', $user)), WATCHDOG_WARNING);
+ }
+ }
+}
+
+/**
+ * Adds a node access filter to a search query, if applicable.
+ *
+ * @param object $account
+ * The user object, who searches.
+ * @param SearchApiQueryInterface $query
+ * The query to which a node access filter should be added, if applicable.
+ * @param string $type
+ * (optional) The type of search – either "node" or "comment". Defaults to
+ * "node".
+ *
+ * @throws SearchApiException
+ * If not all necessary fields are indexed on the index.
+ $drupal_set_message($format_plural($results['not indexed'], '1 item could not be indexed. Check the logs for details.', '@count items could not be indexed. Check the logs for details.'), 'warning');
+ }
+ }
+ else {
+ $drupal_set_message($t("Couldn't index items. Check the logs for details."), 'error');
+ }
+ }
+ else {
+ $drupal_set_message($t("An error occurred while trying to index items. Check the logs for details."), 'error');
+ $this->assertText(t('The indexed fields were successfully changed. The index was cleared and will have to be re-indexed with the new settings.'), 'Field settings saved.');
+ $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->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() {
+ $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.');
+
+ $results = $this->doSearch(2, 4);
+ $this->assertEqual($results['result count'], 10, 'Correct search result count with ranged query.');
+ // 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.