/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $index = $this->entity; // Do not allow the form to be cached. See // \Drupal\views_ui\ViewEditForm::form(). $form_state->disableCache(); if ($index instanceof UnsavedConfigurationInterface && $index->hasChanges()) { if ($index->isLocked()) { $form['#disabled'] = TRUE; $username = array('#theme' => 'username', '#account' => $index->getLockOwner($this->entityTypeManager)); $lock_message_substitutions = array('@user' => $this->getRenderer()->render($username), '@age' => $this->dateFormatter->formatTimeDiffSince($index->getLastUpdated()), ':url' => $index->toUrl('break-lock-form')->toString()); $form['locked'] = array('#type' => 'container', '#attributes' => array('class' => array('index-locked', 'messages', 'messages--warning')), '#children' => $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $lock_message_substitutions), '#weight' => -10); } } $args['%index'] = $index->label(); $form['#title'] = $this->t('Add fields to index %index', $args); $form['properties'] = array('#theme' => 'search_api_form_item_list'); $datasources = array('' => NULL); $datasources += $this->entity->getDatasources(); foreach ($datasources as $datasource) { $form['properties'][] = $this->getDatasourceListItem($datasource); } // Log any unmapped types that were encountered. if ($this->unmappedFields) { $unmapped_types = array(); foreach ($this->unmappedFields as $type => $fields) { $unmapped_types[] = implode(', ', $fields) . ' (' . new FormattableMarkup('type @type', array('@type' => $type)) . ')'; } $vars['@fields'] = implode('; ', $unmapped_types); $vars['%index'] = $this->entity->label(); \Drupal::logger('search_api')->warning('Warning while retrieving available fields for index %index: could not find a type mapping for the following fields: @fields.', $vars); } $form['actions'] = $this->actionsElement($form, $form_state); return $form; }
/** * Enables a search index without a confirmation form. * * @param \Drupal\search_api\IndexInterface $search_api_index * The index to be enabled. * * @return \Symfony\Component\HttpFoundation\Response * The response to send to the browser. */ public function indexBypassEnable(IndexInterface $search_api_index) { // Enable the index. $search_api_index->setStatus(TRUE)->save(); // \Drupal\search_api\Entity\Index::preSave() doesn't allow an index to be // enabled if its server is not set or disabled. if ($search_api_index->status()) { // Notify the user about the status change. drupal_set_message($this->t('The search index %name has been enabled.', array('%name' => $search_api_index->label()))); } else { // Notify the user that the status change did not succeed. drupal_set_message($this->t('The search index %name could not be enabled. Check if its server is set and enabled.', array('%name' => $search_api_index->label()))); } // Redirect to the index's "View" page. $url = $search_api_index->toUrl('canonical'); return $this->redirect($url->getRouteName(), $url->getRouteParameters()); }
public function getDataDefinition() { if (!isset($this->dataDefinition)) { $definitions = $this->index->getPropertyDefinitions($this->datasourceId); if (!isset($definitions[$this->propertyPath])) { $args['@field'] = $this->fieldIdentifier; $args['%index'] = $this->index->label(); throw new SearchApiException(new FormattableMarkup('Could not retrieve data definition for field "@field" on index %index.', $args)); } $this->dataDefinition = $definitions[$this->propertyPath]; } return $this->dataDefinition; }
/** * Tests task system integration for the server's removeIndex() method. */ public function testRemoveIndex() { // Set exception for updateIndex() and removeIndex(), and reset the list of // successful backend method calls. $this->state->set('search_api_test_backend.exception.updateIndex', TRUE); $this->state->set('search_api_test_backend.exception.removeIndex', TRUE); $this->getCalledServerMethods(); // First try to update the index and fail. Then try to remove it and check // that the tasks were set correctly. $this->server->updateIndex($this->index); $this->server->removeIndex($this->index); $this->assertEquals(array(), $this->getCalledServerMethods(), 'updateIndex and removeIndex correctly threw exceptions.'); $tasks = $this->getServerTasks(); if (count($tasks) == 1) { $task_created = $tasks[0]->type === 'removeIndex'; } $this->assertTrue(!empty($task_created), 'The removeIndex task was successfully added and other tasks removed.'); if ($tasks) { $this->assertEquals($this->index->id(), $tasks[0]->index_id, 'The right index ID was used for the removeIndex task.'); } // Check whether other task-system-integrated methods now fail, too. try { $this->server->indexItems($this->index, array()); $this->fail('Pending server tasks did not prevent indexing of items.'); } catch (SearchApiException $e) { $args['%index'] = $this->index->label(); $expected_message = new FormattableMarkup('Could not index items on index %index because pending server tasks could not be executed.', $args); $this->assertEquals($expected_message, $e->getMessage(), 'Pending server tasks prevented indexing of items.'); } $this->assertEquals(array(), $this->getCalledServerMethods(), 'indexItems was not executed.'); $tasks = $this->getServerTasks(); $this->assertEquals(1, count($tasks), 'No task added for indexItems.'); // Let removeIndex() succeed again, then trigger the task execution with a // cron run. $this->state->set("search_api_test_backend.exception.removeIndex", FALSE); search_api_cron(); $this->assertEquals(array(), $this->getServerTasks(), 'Server tasks were correctly executed.'); $this->assertEquals(array('removeIndex'), $this->getCalledServerMethods(), 'Right methods were called during task execution.'); }
/** * Builds the form for the basic index properties. * * @param \Drupal\search_api\IndexInterface $index * The index that is being created or edited. */ public function buildEntityForm(array &$form, FormStateInterface $form_state, IndexInterface $index) { $form['#tree'] = TRUE; $form['name'] = array('#type' => 'textfield', '#title' => $this->t('Index name'), '#description' => $this->t('Enter the displayed name for the index.'), '#default_value' => $index->label(), '#required' => TRUE); $form['id'] = array('#type' => 'machine_name', '#default_value' => $index->id(), '#maxlength' => 50, '#required' => TRUE, '#machine_name' => array('exists' => array($this->getIndexStorage(), 'load'), 'source' => array('name'))); // If the user changed the datasources or the tracker, notify them that they // need to be configured. // @todo Only do that if the datasources/tracker have configuration forms. // (Same in \Drupal\search_api\Form\ServerForm.) $values = $form_state->getValues(); if (!empty($values['datasources'])) { drupal_set_message($this->t('Please configure the used datasources.'), 'warning'); } if (!empty($values['tracker'])) { drupal_set_message($this->t('Please configure the used tracker.'), 'warning'); } $form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css'; $datasource_options = array(); foreach ($this->getDatasourcePluginManager()->getDefinitions() as $datasource_id => $definition) { $datasource_options[$datasource_id] = !empty($definition['label']) ? $definition['label'] : $datasource_id; } $form['datasources'] = array('#type' => 'select', '#title' => $this->t('Data sources'), '#description' => $this->t('Select one or more data sources of items that will be stored in this index.'), '#options' => $datasource_options, '#default_value' => $index->getDatasourceIds(), '#multiple' => TRUE, '#required' => TRUE, '#ajax' => array('trigger_as' => array('name' => 'datasourcepluginids_configure'), 'callback' => '::buildAjaxDatasourceConfigForm', 'wrapper' => 'search-api-datasources-config-form', 'method' => 'replace', 'effect' => 'fade')); $form['datasource_configs'] = array('#type' => 'container', '#attributes' => array('id' => 'search-api-datasources-config-form'), '#tree' => TRUE); $form['datasource_configure_button'] = array('#type' => 'submit', '#name' => 'datasourcepluginids_configure', '#value' => $this->t('Configure'), '#limit_validation_errors' => array(array('datasources')), '#submit' => array('::submitAjaxDatasourceConfigForm'), '#ajax' => array('callback' => '::buildAjaxDatasourceConfigForm', 'wrapper' => 'search-api-datasources-config-form'), '#attributes' => array('class' => array('js-hide'))); $this->buildDatasourcesConfigForm($form, $form_state, $index); $tracker_options = $this->getTrackerPluginManager()->getOptionsList(); $form['tracker'] = array('#type' => 'radios', '#title' => $this->t('Tracker'), '#description' => $this->t('Select the type of tracker which should be used for keeping track of item changes.'), '#options' => $this->getTrackerPluginManager()->getOptionsList(), '#default_value' => $index->hasValidTracker() ? $index->getTracker()->getPluginId() : key($tracker_options), '#required' => TRUE, '#disabled' => !$index->isNew(), '#ajax' => array('trigger_as' => array('name' => 'trackerpluginid_configure'), 'callback' => '::buildAjaxTrackerConfigForm', 'wrapper' => 'search-api-tracker-config-form', 'method' => 'replace', 'effect' => 'fade'), '#access' => count($tracker_options) > 1); $form['tracker_config'] = array('#type' => 'container', '#attributes' => array('id' => 'search-api-tracker-config-form'), '#tree' => TRUE); $form['tracker_configure_button'] = array('#type' => 'submit', '#name' => 'trackerpluginid_configure', '#value' => $this->t('Configure'), '#limit_validation_errors' => array(array('tracker')), '#submit' => array('::submitAjaxTrackerConfigForm'), '#ajax' => array('callback' => '::buildAjaxTrackerConfigForm', 'wrapper' => 'search-api-tracker-config-form'), '#attributes' => array('class' => array('js-hide')), '#access' => count($tracker_options) > 1); $this->buildTrackerConfigForm($form, $form_state, $index); $form['server'] = array('#type' => 'radios', '#title' => $this->t('Server'), '#description' => $this->t('Select the server this index should use. Indexes cannot be enabled without a connection to a valid, enabled server.'), '#options' => array(NULL => '<em>' . $this->t('- No server -') . '</em>') + $this->getServerOptions(), '#default_value' => $index->hasValidServer() ? $index->getServerId() : NULL); $form['status'] = array('#type' => 'checkbox', '#title' => $this->t('Enabled'), '#description' => $this->t('Only enabled indexes can be used for indexing and searching. This setting will only take effect if the selected server is also enabled.'), '#default_value' => $index->status(), '#disabled' => !$index->status() && (!$index->hasValidServer() || !$index->getServer()->status()), '#states' => array('invisible' => array(':input[name="server"]' => array('value' => '')))); $form['description'] = array('#type' => 'textarea', '#title' => $this->t('Description'), '#description' => $this->t('Enter a description for the index.'), '#default_value' => $index->getDescription()); $form['options'] = array('#tree' => TRUE, '#type' => 'details', '#title' => $this->t('Index options'), '#collapsed' => TRUE); // We display the "read-only" flag along with the other options, even though // it is a property directly on the index object. We use "#parents" to move // it to the correct place in the form values. $form['options']['read_only'] = array('#type' => 'checkbox', '#title' => $this->t('Read only'), '#description' => $this->t('Do not write to this index or track the status of items in this index.'), '#default_value' => $index->isReadOnly(), '#parents' => array('read_only')); $form['options']['index_directly'] = array('#type' => 'checkbox', '#title' => $this->t('Index items immediately'), '#description' => $this->t('Immediately index new or updated items instead of waiting for the next cron run. This might have serious performance drawbacks and is generally not advised for larger sites.'), '#default_value' => $index->getOption('index_directly')); $form['options']['cron_limit'] = array('#type' => 'textfield', '#title' => $this->t('Cron batch size'), '#description' => $this->t('Set how many items will be indexed at once when indexing items during a cron run. "0" means that no items will be indexed by cron for this index, "-1" means that cron should index all items at once.'), '#default_value' => $index->getOption('cron_limit'), '#size' => 4); }
/** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css'; // Retrieve lists of all processors, and the stages and weights they have. if (!$form_state->has('processors')) { $all_processors = $this->entity->getProcessors(FALSE); $sort_processors = function (ProcessorInterface $a, ProcessorInterface $b) { return strnatcasecmp($a->label(), $b->label()); }; uasort($all_processors, $sort_processors); } else { $all_processors = $form_state->get('processors'); } $stages = $this->processorPluginManager->getProcessingStages(); $processors_by_stage = array(); foreach ($stages as $stage => $definition) { $processors_by_stage[$stage] = $this->entity->getProcessorsByStage($stage, FALSE); } $processor_settings = $this->entity->getOption('processors'); $form['#tree'] = TRUE; $form['#attached']['library'][] = 'search_api/drupal.search_api.index-active-formatters'; $form['#title'] = $this->t('Manage processors for search index %label', array('%label' => $this->entity->label())); $form['description']['#markup'] = '<p>' . $this->t('Configure processors which will pre- and post-process data at index and search time.') . '</p>'; // Add the list of processors with checkboxes to enable/disable them. $form['status'] = array( '#type' => 'fieldset', '#title' => $this->t('Enabled'), '#attributes' => array('class' => array( 'search-api-status-wrapper', )), ); foreach ($all_processors as $processor_id => $processor) { $clean_css_id = Html::cleanCssIdentifier($processor_id); $form['status'][$processor_id] = array( '#type' => 'checkbox', '#title' => $processor->label(), '#default_value' => $processor->isLocked() || !empty($processor_settings[$processor_id]), '#description' => $processor->getDescription(), '#attributes' => array( 'class' => array( 'search-api-processor-status-' . $clean_css_id, ), 'data-id' => $clean_css_id, ), '#disabled' => $processor->isLocked(), '#access' => !$processor->isHidden(), ); } $form['weights'] = array( '#type' => 'fieldset', '#title' => t('Processor order'), ); // Order enabled processors per stage. foreach ($stages as $stage => $description) { $form['weights'][$stage] = array ( '#type' => 'fieldset', '#title' => $description['label'], '#attributes' => array('class' => array( 'search-api-stage-wrapper', 'search-api-stage-wrapper-' . Html::cleanCssIdentifier($stage), )), ); $form['weights'][$stage]['order'] = array( '#type' => 'table', ); $form['weights'][$stage]['order']['#tabledrag'][] = array( 'action' => 'order', 'relationship' => 'sibling', 'group' => 'search-api-processor-weight-' . Html::cleanCssIdentifier($stage), ); } foreach ($processors_by_stage as $stage => $processors) { /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */ foreach ($processors as $processor_id => $processor) { $weight = isset($processor_settings[$processor_id]['weights'][$stage]) ? $processor_settings[$processor_id]['weights'][$stage] : $processor->getDefaultWeight($stage); if ($processor->isHidden()) { $form['processors'][$processor_id]['weights'][$stage] = array( '#type' => 'value', '#value' => $weight, ); continue; } $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'draggable'; $form['weights'][$stage]['order'][$processor_id]['#attributes']['class'][] = 'search-api-processor-weight--' . Html::cleanCssIdentifier($processor_id); $form['weights'][$stage]['order'][$processor_id]['#weight'] = $weight; $form['weights'][$stage]['order'][$processor_id]['label']['#plain_text'] = $processor->label(); $form['weights'][$stage]['order'][$processor_id]['weight'] = array( '#type' => 'weight', '#title' => $this->t('Weight for processor %title', array('%title' => $processor->label())), '#title_display' => 'invisible', '#default_value' => $weight, '#parents' => array('processors', $processor_id, 'weights', $stage), '#attributes' => array('class' => array( 'search-api-processor-weight-' . Html::cleanCssIdentifier($stage), )), ); } } // Add vertical tabs containing the settings for the processors. Tabs for // disabled processors are hidden with JS magic, but need to be included in // case the processor is enabled. $form['processor_settings'] = array( '#title' => $this->t('Processor settings'), '#type' => 'vertical_tabs', ); foreach ($all_processors as $processor_id => $processor) { $processor_form_state = new SubFormState($form_state, array('processors', $processor_id, 'settings')); $processor_form = $processor->buildConfigurationForm($form, $processor_form_state); if ($processor_form) { $form['settings'][$processor_id] = array( '#type' => 'details', '#title' => $processor->label(), '#group' => 'processor_settings', '#parents' => array('processors', $processor_id, 'settings'), '#attributes' => array('class' => array( 'search-api-processor-settings-' . Html::cleanCssIdentifier($processor_id), )), ); $form['settings'][$processor_id] += $processor_form; } } return $form; }
/** * Tests whether loading an index works correctly. * * @param \Drupal\search_api\IndexInterface $index * The index used for the test. */ public function indexLoad(IndexInterface $index) { $loaded_index = $this->storage->load($index->id()); $this->assertIdentical($index->label(), $loaded_index->label()); }
/** * {@inheritdoc} */ public function deleteAllIndexItems(IndexInterface $index) { if ($index->isReadOnly()) { $vars = array('%index' => $index->label()); \Drupal::logger('search_api')->warning('Trying to delete items from index %index which is marked as read-only.', $vars); return; } $server_task_manager = \Drupal::getContainer()->get('search_api.server_task_manager'); try { if ($server_task_manager->execute($this)) { $this->getBackend()->deleteAllIndexItems($index); return; } } catch (SearchApiException $e) { $vars = array('%server' => $this->label(), '%index' => $index->label()); watchdog_exception('search_api', $e, '%type while deleting items of index %index from server %server: @message in %function (line %line of %file).', $vars); } $server_task_manager->add($this, __FUNCTION__, $index); }
/** * Tests whether loading an index works correctly. * * @param \Drupal\search_api\IndexInterface $index * The index used for the test. */ protected function indexLoad(IndexInterface $index) { $loaded_index = $this->storage->load($index->id()); $this->assertSame($index->label(), $loaded_index->label()); }
/** * Creates an indexing batch for a given search index. * * @param \Drupal\search_api\IndexInterface $index * The search index for which items should be indexed. * @param int|null $batch_size * (optional) Number of items to index per batch. Defaults to the cron limit * set for the index. * @param int $limit * (optional) Maximum number of items to index. Defaults to indexing all * remaining items. * * @throws \Drupal\search_api\SearchApiException * Thrown if the batch could not be created. */ public static function create(IndexInterface $index, $batch_size = NULL, $limit = -1) { // Check if the size should be determined by the index cron limit option. if ($batch_size === NULL) { // Use the size set by the index. $batch_size = $index->getOption('cron_limit', \Drupal::config('search_api.settings')->get('default_cron_limit')); } // Check if indexing items is allowed. if ($index->status() && !$index->isReadOnly() && $batch_size !== 0 && $limit !== 0) { // Define the search index batch definition. $batch_definition = array( 'operations' => array( array(array(__CLASS__, 'process'), array($index, $batch_size, $limit)), ), 'finished' => array(__CLASS__, 'finish'), 'progress_message' => static::t('Completed about @percentage% of the indexing operation (@current of @total).'), ); // Schedule the batch. batch_set($batch_definition); } else { $args = array( '%size' => $batch_size, '%limit' => $limit, '%name' => $index->label(), ); throw new SearchApiException(new FormattableMarkup('Failed to create a batch with batch size %size and limit %limit for index %name', $args)); } }
/** * Constructs a Query object. * * @param \Drupal\search_api\IndexInterface $index * The index the query should be executed on. * @param \Drupal\search_api\Query\ResultsCacheInterface $results_cache * The results cache that should be used for this query. * @param array $options * (optional) Associative array of options configuring this query. See * \Drupal\search_api\Query\QueryInterface::setOption() for a list of * options that are recognized by default. * * @throws \Drupal\search_api\SearchApiException * Thrown if a search on that index (or with those options) won't be * possible. */ public function __construct(IndexInterface $index, ResultsCacheInterface $results_cache, array $options = array()) { if (!$index->status()) { throw new SearchApiException(new FormattableMarkup("Can't search on index %index which is disabled.", array('%index' => $index->label()))); } if (isset($options['parse mode'])) { $modes = $this->parseModes(); if (!isset($modes[$options['parse mode']])) { throw new SearchApiException(new FormattableMarkup('Unknown parse mode: @mode.', array('@mode' => $options['parse mode']))); } } $this->index = $index; $this->resultsCache = $results_cache; $this->options = $options + array( 'conjunction' => 'AND', 'parse mode' => 'terms', 'filter class' => '\Drupal\search_api\Query\Filter', 'search id' => __CLASS__, ); $this->filter = $this->createFilter('AND'); }
/** * Constructs a Query object. * * @param \Drupal\search_api\IndexInterface $index * The index the query should be executed on. * @param \Drupal\search_api\Query\ResultsCacheInterface $results_cache * The results cache that should be used for this query. * @param array $options * (optional) Associative array of options configuring this query. See * \Drupal\search_api\Query\QueryInterface::setOption() for a list of * options that are recognized by default. * * @throws \Drupal\search_api\SearchApiException * Thrown if a search on that index (or with those options) won't be * possible. */ public function __construct(IndexInterface $index, ResultsCacheInterface $results_cache, array $options = array()) { if (!$index->status()) { throw new SearchApiException(new FormattableMarkup("Can't search on index %index which is disabled.", array('%index' => $index->label()))); } $this->index = $index; $this->resultsCache = $results_cache; $this->options = $options + array('conjunction' => 'AND', 'search id' => __CLASS__); $this->conditionGroup = $this->createConditionGroup('AND'); }
/** * {@inheritdoc} */ public function label() { return $this->entity->label(); }
/** * Allows you to log or alter the items that are indexed. * * Please be aware that generally preventing the indexing of certain items is * deprecated. This is better done with processors, which can easily be * configured and only added to indexes where this behaviour is wanted. * If your module will use this hook to reject certain items from indexing, * please document this clearly to avoid confusion. * * @param \Drupal\search_api\IndexInterface $index * The search index on which items will be indexed. * @param \Drupal\search_api\Item\ItemInterface[] $items * The items that will be indexed. */ function hook_search_api_index_items_alter(\Drupal\search_api\IndexInterface $index, array &$items) { foreach ($items as $item_id => $item) { list(, $raw_id) = \Drupal\search_api\Utility::splitCombinedId($item->getId()); if ($raw_id % 5 == 0) { unset($items[$item_id]); } } drupal_set_message(t('Indexing items on index %index with the following IDs: @ids', array('%index' => $index->label(), '@ids' => implode(', ', array_keys($items))))); }