/** * Retrieves the properties that should be serialized. * * Used in __sleep(), but extracted to be more easily usable for subclasses. * * @return array * An array mapping property names of this object to their values. */ protected function getSerializationProperties() { $this->indexId = $this->index->id(); $properties = get_object_vars($this); // Don't serialize objects in properties. unset($properties['index'], $properties['datasource'], $properties['dataDefinition']); return $properties; }
/** * Tests task system integration for the deleteAllIndexItems() method. */ public function testDeleteAllIndexItems() { // Set exception for deleteAllIndexItems() and reset the list of successful // backend method calls. $this->state->set('search_api_test_backend.exception.deleteAllIndexItems', TRUE); $this->getCalledServerMethods(); // Try to update the index. $this->server->deleteAllIndexItems($this->index); $this->assertEqual($this->getCalledServerMethods(), array(), 'deleteAllIndexItems correctly threw an exception.'); $tasks = $this->getServerTasks(); if (count($tasks) == 1) { $task_created = $tasks[0]->type === 'deleteAllIndexItems'; } $this->assertTrue(!empty($task_created), 'The deleteAllIndexItems task was successfully added.'); if ($tasks) { $this->assertEqual($tasks[0]->index_id, $this->index->id(), 'The right index ID was used for the deleteAllIndexItems task.'); } // Check whether other task-system-integrated methods now fail, too. $this->server->updateIndex($this->index); $this->assertEqual($this->getCalledServerMethods(), array(), 'updateIndex was not executed.'); $tasks = $this->getServerTasks(); if (count($tasks) == 2) { $this->pass("Second task ('updateIndex') was added."); $this->assertEqual($tasks[0]->type, 'deleteAllIndexItems', 'First task stayed the same.'); $this->assertEqual($tasks[1]->type, 'updateIndex', 'New task was queued as last.'); } else { $this->fail("Second task (updateIndex) was not added."); } // Let deleteAllIndexItems() succeed again, then trigger the task execution // with a call to indexItems(). $this->state->set('search_api_test_backend.exception.deleteAllIndexItems', FALSE); $this->server->indexItems($this->index, array()); $this->assertEqual($this->getServerTasks(), array(), 'Server tasks were correctly executed.'); $this->assertEqual($this->getCalledServerMethods(), array('deleteAllIndexItems', 'updateIndex', 'indexItems'), 'Right methods were called during task execution.'); }
/** * {@inheritdoc} */ public function __sleep() { $this->indexId = $this->index->id(); $properties = get_object_vars($this); // Don't serialize objects in properties or the field values. unset($properties['index'], $properties['datasource'], $properties['dataDefinition'], $properties['values']); return array_keys($properties); }
/** * Builds the form fields for a set of fields. * * @param \Drupal\search_api\Item\FieldInterface[] $fields * List of fields to display. * * @return array * The build structure. */ protected function buildFieldsTable(array $fields) { $data_type_plugin_manager = $this->getDataTypePluginManager(); $types = $data_type_plugin_manager->getInstancesOptions(); $fulltext_types = array('text'); // Add all data types with fallback "text" to fulltext types as well. foreach ($data_type_plugin_manager->getInstances() as $id => $type) { if ($type->getFallbackType() == 'text') { $fulltext_types[] = $id; } } $boost_values = 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 = array_combine($boost_values, $boost_values); $build = array('#type' => 'details', '#open' => TRUE, '#theme' => 'search_api_admin_fields_table', '#parents' => array()); foreach ($fields as $key => $field) { $build['fields'][$key]['#access'] = !$field->isHidden(); $build['fields'][$key]['title']['#plain_text'] = $field->getLabel(); $build['fields'][$key]['id']['#plain_text'] = $key; if ($field->getDescription()) { $build['fields'][$key]['description'] = array('#type' => 'value', '#value' => $field->getDescription()); } $css_key = '#edit-fields-' . Html::getId($key); $build['fields'][$key]['type'] = array('#type' => 'select', '#options' => $types, '#default_value' => $field->getType()); if ($field->isTypeLocked()) { $build['fields'][$key]['type']['#disabled'] = TRUE; } $build['fields'][$key]['boost'] = array('#type' => 'select', '#options' => $boosts, '#default_value' => sprintf('%.1f', $field->getBoost())); foreach ($fulltext_types as $type) { $build['fields'][$key]['boost']['#states']['visible'][$css_key . '-type'][] = array('value' => $type); } $build['fields'][$key]['remove']['#markup'] = ''; if (!$field->isIndexedLocked()) { $route_parameters = array('search_api_index' => $this->entity->id(), 'field_id' => $key); $build['fields'][$key]['remove'] = array('#type' => 'link', '#title' => $this->t('Remove'), '#url' => Url::fromRoute('entity.search_api_index.remove_field', $route_parameters)); } } return $build; }
/** * Implements the magic __toString() method to simplify debugging. */ public function __toString() { $ret = 'Index: ' . $this->index->id() . "\n"; $ret .= 'Keys: ' . str_replace("\n", "\n ", var_export($this->origKeys, TRUE)) . "\n"; if (isset($this->keys)) { $ret .= 'Parsed keys: ' . str_replace("\n", "\n ", var_export($this->keys, TRUE)) . "\n"; $ret .= 'Searched fields: ' . (isset($this->fields) ? implode(', ', $this->fields) : '[ALL]') . "\n"; } if ($filter = (string) $this->filter) { $filter = str_replace("\n", "\n ", $filter); $ret .= "Filters:\n $filter\n"; } if ($this->sorts) { $sorts = array(); foreach ($this->sorts as $field => $order) { $sorts[] = "$field $order"; } $ret .= 'Sorting: ' . implode(', ', $sorts) . "\n"; } // @todo Fix for entities contained in options (which might kill // var_export() due to circular references). $ret .= 'Options: ' . str_replace("\n", "\n ", var_export($this->options, TRUE)) . "\n"; return $ret; }
/** * Asserts enable/disable operations for a search server or index. * * @param \Drupal\search_api\ServerInterface|\Drupal\search_api\IndexInterface $entity * A search server or index. */ protected function assertEntityStatusChange($entity) { $this->drupalGet($this->overviewPageUrl); $row_class = Html::cleanCssIdentifier($entity->getEntityTypeId() . '-' . $entity->id()); $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-enabled")]', NULL, 'The newly created entity is enabled by default.'); // The first "Disable" link on the page belongs to our server, the second // one to our index. $this->clickLink('Disable', $entity instanceof ServerInterface ? 0 : 1); // Submit the confirmation form and test that the entity has been disabled. $this->drupalPostForm(NULL, array(), 'Disable'); $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-disabled")]', NULL, 'The entity has been disabled.'); // Now enable the entity and verify that the operation succeeded. $this->clickLink('Enable'); $this->drupalGet($this->overviewPageUrl); $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-enabled")]', NULL, 'The entity has benn enabled.'); }
/** * Retrieves the necessary type fallbacks for an index. * * @param \Drupal\search_api\IndexInterface $index * The index for which to return the type fallbacks. * * @return string[] * An array containing the IDs of all custom data types that are not * supported by the index's current server, mapped to their fallback types. */ public static function getDataTypeFallbackMapping(IndexInterface $index) { // Check the static cache first. $index_id = $index->id(); if (empty(static::$dataTypeFallbackMapping[$index_id])) { $server = NULL; try { $server = $index->getServer(); } catch (SearchApiException $e) { // If the server isn't available, just ignore it here and return all // types. } static::$dataTypeFallbackMapping[$index_id] = array(); /** @var \Drupal\search_api\DataType\DataTypeInterface $data_type */ foreach (\Drupal::service('plugin.manager.search_api.data_type')->getCustomDataTypes() as $type_id => $data_type) { if (!$server || !$server->supportsDataType($type_id)) { static::$dataTypeFallbackMapping[$index_id][$type_id] = $data_type->getFallbackType(); } } } return static::$dataTypeFallbackMapping[$index_id]; }
/** * Tests whether deleting an index works correctly. * * @param \Drupal\search_api\IndexInterface $index * The index used for the test. */ public function indexDelete(IndexInterface $index) { $this->storage->delete(array($index)); $loaded_index = $this->storage->load($index->id()); $this->assertNull($loaded_index); }
/** * Retrieve the database info for the given index, or some data from it. * * @param \Drupal\search_api\IndexInterface $index * The search index. * * @return array|string * The requested data from the key-value store. */ protected function getIndexDbInfo(IndexInterface $index) { return $this->getKeyValueStore()->get($index->id(), array()); }
/** * Reloads the index with the latest copy from storage. */ protected function reloadIndex() { $storage = \Drupal::entityTypeManager()->getStorage('search_api_index'); $storage->resetCache(); $this->index = $storage->load($this->index->id()); }
/** * Retrieves the key of the index's state. * * @param \Drupal\search_api\IndexInterface $index * The index. * * @return string * The key which identifies the index's state in the site state. */ protected function getIndexStateKey(IndexInterface $index) { return 'search_api.index.' . $index->id() . '.tasks'; }
/** * {@inheritdoc} */ public function save() { return $this->tempStore->setIfOwner($this->entity->id(), $this->entity); }
/** * Retrieves the database info for the given index. * * @param \Drupal\search_api\IndexInterface $index * The search index. * * @return array * The index data from the key-value store. */ protected function getIndexDbInfo(IndexInterface $index) { $db_info = $this->getKeyValueStore()->get($index->id(), array()); if ($db_info['server'] != $this->server->id()) { return array(); } return $db_info; }
/** * React when a search index was scheduled for reindexing * * @param \Drupal\search_api\IndexInterface $index * The index scheduled for reindexing. * @param $clear * Boolean indicating whether the index was also cleared. */ function hook_search_api_index_reindex(\Drupal\search_api\IndexInterface $index, $clear = FALSE) { \Drupal\Core\Database\Database::getConnection()->insert('example_search_index_reindexed') ->fields(array( 'index' => $index->id(), 'clear' => $clear, 'update_time' => REQUEST_TIME, )) ->execute(); }
/** * Creates a list of all indexed field names mapped to their Solr field names. * * @param \Drupal\search_api\IndexInterface $index * The Search Api index. * @param bool $single_value_name * (optional) Whether to return names for fields which store only the first * value of the field. Defaults to FALSE. * @param bool $reset * (optional) Whether to reset the static cache. * * The special fields "search_api_id" and "search_api_relevance" are also * included. Any Solr fields that exist on search results are mapped back to * to their local field names in the final result set. * * @see SearchApiSolrBackend::search() */ public function getFieldNames(IndexInterface $index, $single_value_name = FALSE, $reset = FALSE) { // @todo The field name mapping should be cached per index because custom // queries needs to access it on every query. $subkey = (int) $single_value_name; if (!isset($this->fieldNames[$index->id()][$subkey]) || $reset) { // This array maps "local property name" => "solr doc property name". $ret = array('search_api_id' => 'item_id', 'search_api_relevance' => 'score'); // Add the names of any fields configured on the index. $fields = $index->getFields(); foreach ($fields as $key => $field) { // Generate a field name; this corresponds with naming conventions in // our schema.xml $type = $field->getType(); $type_info = SearchApiSolrUtility::getDataTypeInfo($type); $pref = isset($type_info['prefix']) ? $type_info['prefix'] : ''; $pref .= $single_value_name ? 's' : 'm'; $name = $pref . '_' . $key; $ret[$key] = SearchApiSolrUtility::encodeSolrDynamicFieldName($name); } // Let modules adjust the field mappings. $hook_name = $single_value_name ? 'search_api_solr_single_value_field_mapping' : 'search_api_solr_field_mapping'; $this->moduleHandler->alter($hook_name, $index, $ret); $this->fieldNames[$index->id()][$subkey] = $ret; } return $this->fieldNames[$index->id()][$subkey]; }
/** * Retrieves the necessary type fallbacks for an index. * * @param \Drupal\search_api\IndexInterface $index * The index for which to return the type fallbacks. * * @return string[] * An array containing the IDs of all custom data types that are not * supported by the index's current server, mapped to their fallback types. */ public static function getDataTypeFallbackMapping(IndexInterface $index) { // Check the static cache first. $index_id = $index->id(); if (empty(static::$dataTypeFallbackMapping[$index_id])) { $server = NULL; try { $server = $index->getServerInstance(); } catch (SearchApiException $e) { // If the server isn't available, just ignore it here and return all // custom types. } static::$dataTypeFallbackMapping[$index_id] = array(); /** @var \Drupal\search_api\DataType\DataTypeInterface $data_type */ foreach (\Drupal::service('plugin.manager.search_api.data_type')->getInstances() as $type_id => $data_type) { // We know for sure that we do not need to fall back for the default // data types as they are always present and are required to be // supported by all backends. if (!$data_type->isDefault() && (!$server || !$server->supportsDataType($type_id))) { static::$dataTypeFallbackMapping[$index_id][$type_id] = $data_type->getFallbackType(); } } } return static::$dataTypeFallbackMapping[$index_id]; }
/** * 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); }
/** * Retrieves the internal field information. * * @param \Drupal\search_api\IndexInterface $index * The index whose fields should be retrieved. * * @return array[] * An array of arrays. The outer array is keyed by field name. Each value * is an associative array with information on the field. */ protected function getFieldInfo(IndexInterface $index) { return $this->configuration['field_tables'][$index->id()]; }