/** * Creates a new processor object for use in the tests. */ protected function setUp() { parent::setUp(); // Create a mock for the URL to be returned. $url = $this->getMockBuilder('Drupal\Core\Url') ->disableOriginalConstructor() ->getMock(); $url->expects($this->any()) ->method('toString') ->will($this->returnValue('http://www.example.com/node/example')); // Mock the data source of the indexer to return the mocked url object. $datasource = $this->getMock('Drupal\search_api\Datasource\DatasourceInterface'); $datasource->expects($this->any()) ->method('getItemUrl') ->withAnyParameters() ->will($this->returnValue($url)); // Create a mock for the index to return the datasource mock. /** @var \Drupal\search_api\IndexInterface $index */ $index = $this->index = $this->getMock('Drupal\search_api\IndexInterface'); $this->index->expects($this->any()) ->method('getDatasource') ->with('entity:node') ->will($this->returnValue($datasource)); // Create the tested processor and set the mocked indexer. $this->processor = new AddURL(array(), 'add_url', array()); $this->processor->setIndex($index); /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ $translation = $this->getStringTranslationStub(); $this->processor->setStringTranslation($translation); }
/** * {@inheritdoc} */ public static function supportsIndex(IndexInterface $index) { foreach ($index->getDatasources() as $datasource) { if (in_array($datasource->getEntityTypeId(), array('node', 'comment'))) { return TRUE; } } return FALSE; }
/** * Overrides \Drupal\search_api\Processor\ProcessorPluginBase::supportsIndex(). * * This plugin only supports indexes containing users. */ public static function supportsIndex(IndexInterface $index) { foreach ($index->getDatasources() as $datasource) { if ($datasource->getEntityTypeId() == 'user') { return TRUE; } } return FALSE; }
/** * Creates a new processor object for use in the tests. */ public function setUp() { parent::setUp(); $this->setUpDataTypePlugin(); $this->index = $this->getMock('Drupal\\search_api\\IndexInterface'); $this->index->expects($this->any())->method('status')->will($this->returnValue(TRUE)); $fields = $this->getTestItem()[$this->itemIds[0]]->getFields(); $this->index->expects($this->any())->method('getFields')->will($this->returnValue($fields)); $this->processor = new TestFieldsProcessorPlugin(array('index' => $this->index), '', array()); }
/** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, IndexInterface $index = NULL) { if (!isset($index)) { return array(); } $form['#index'] = $index; $form['#attached']['library'][] = 'search_api/drupal.search_api.admin_css'; if ($index->hasValidTracker()) { if (!\Drupal::getContainer()->get('search_api.index_task_manager')->isTrackingComplete($index)) { $form['tracking'] = array('#type' => 'details', '#title' => $this->t('Track items for index'), '#description' => $this->t('Not all items have been tracked for this index. This means the displayed index status is incomplete and not all items will currently be indexed.'), '#open' => TRUE, '#attributes' => array('class' => array('container-inline'))); $form['tracking']['index_now'] = array('#type' => 'submit', '#value' => $this->t('Track now'), '#name' => 'track_now'); } // Add the "Index now" form. $form['index'] = array('#type' => 'details', '#title' => $this->t('Index now'), '#open' => TRUE, '#attributes' => array('class' => array('container-inline'))); $has_remaining_items = $index->getTrackerInstance()->getRemainingItemsCount() > 0; $all_value = $this->t('all', array(), array('context' => 'items to index')); $limit = array('#type' => 'textfield', '#default_value' => $all_value, '#size' => 4, '#attributes' => array('class' => array('search-api-limit')), '#disabled' => !$has_remaining_items); $batch_size = array('#type' => 'textfield', '#default_value' => $index->getOption('cron_limit', $this->config('search_api.settings')->get('default_cron_limit')), '#size' => 4, '#attributes' => array('class' => array('search-api-batch-size')), '#disabled' => !$has_remaining_items); // Here it gets complicated. We want to build a sentence from the form // input elements, but to translate that we have to make the two form // elements (for limit and batch size) pseudo-variables in the $this->t() // call. // Since we can't pass them directly, we split the translated sentence // (which still has the two tokens), figure out their order and then put // the pieces together again using the form elements' #prefix and #suffix // properties. $sentence = preg_split('/@(limit|batch_size)/', $this->t('Index @limit items in batches of @batch_size items'), -1, PREG_SPLIT_DELIM_CAPTURE); // Check if the sentence contains the expected amount of parts. if (count($sentence) === 5) { $first = $sentence[1]; $form['index'][$first] = ${$first}; $form['index'][$first]['#prefix'] = $sentence[0]; $form['index'][$first]['#suffix'] = $sentence[2]; $second = $sentence[3]; $form['index'][$second] = ${$second}; $form['index'][$second]['#suffix'] = "{$sentence[4]} "; } else { // Sentence is broken. Use fallback method instead. $limit['#title'] = $this->t('Number of items to index'); $form['index']['limit'] = $limit; $batch_size['#title'] = $this->t('Number of items per batch run'); $form['index']['batch_size'] = $batch_size; } // Add the value "all" so it can be used by the validation. $form['index']['all'] = array('#type' => 'value', '#value' => $all_value); $form['index']['index_now'] = array('#type' => 'submit', '#value' => $this->t('Index now'), '#disabled' => !$has_remaining_items, '#name' => 'index_now'); // Add actions for reindexing and for clearing the index. $form['actions']['#type'] = 'actions'; $form['actions']['reindex'] = array('#type' => 'submit', '#value' => $this->t('Queue all items for reindexing'), '#name' => 'reindex', '#button_type' => 'danger'); $form['actions']['clear'] = array('#type' => 'submit', '#value' => $this->t('Clear all indexed data'), '#name' => 'clear', '#button_type' => 'danger'); } return $form; }
/** * Tests custom data types integration. */ public function testCustomDataTypes() { $original_value = $this->entities[1]->get('name')->value; $original_type = $this->index->getFields()['entity:entity_test/name']->getType(); $item = $this->index->loadItem('entity:entity_test/1:en'); $item = Utility::createItemFromObject($this->index, $item, 'entity:entity_test/1:en'); $name_field = $item->getField('entity:entity_test/name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value'); $this->assertEqual($processed_type, $original_type, 'The processed type matches the original type.'); // Reset the fields on the item and change to the supported data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); $this->index->getFields()['entity:entity_test/name']->setType('search_api_test_data_type'); $name_field = $item->getField('entity:entity_test/name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value'); $this->assertEqual($processed_type, 'search_api_test_data_type', 'The processed type matches the new type.'); // Reset the fields on the item and change to the non-supported data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); $this->index->getFields()['entity:entity_test/name']->setType('search_api_unsupported_test_data_type'); $name_field = $item->getField('entity:entity_test/name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); $this->assertEqual($processed_value, $original_value, 'The processed value matches the original value'); $this->assertEqual($processed_type, 'integer', 'The processed type matches the fallback type.'); // Reset the fields on the item and change to the data altering data type. $item->setFieldsExtracted(FALSE); $item->setFields(array()); $this->index->getFields()['entity:entity_test/name']->setType('search_api_altering_test_data_type'); $name_field = $item->getField('entity:entity_test/name'); $processed_value = $name_field->getValues()[0]; $processed_type = $name_field->getType(); $this->assertEqual($processed_value, strlen($original_value), 'The processed value matches the altered original value'); $this->assertEqual($processed_type, 'search_api_altering_test_data_type', 'The processed type matches the defined type.'); }
/** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $values = $form_state->getValues(); $new_settings = array(); // Store processor settings. // @todo Go through all available processors, enable/disable with method on // processor plugin to allow reaction. /** @var \Drupal\search_api\Processor\ProcessorInterface $processor */ $processors = $this->entity->getProcessors(FALSE); foreach ($processors as $processor_id => $processor) { if (empty($values['status'][$processor_id])) { continue; } $new_settings[$processor_id] = array('processor_id' => $processor_id, 'weights' => array(), 'settings' => array()); $processor_values = $values['processors'][$processor_id]; if (!empty($processor_values['weights'])) { $new_settings[$processor_id]['weights'] = $processor_values['weights']; } if (isset($form['settings'][$processor_id])) { $processor_form_state = new SubFormState($form_state, array('processors', $processor_id, 'settings')); $processor->submitConfigurationForm($form['settings'][$processor_id], $processor_form_state); $new_settings[$processor_id]['settings'] = $processor->getConfiguration(); } } // Sort the processors so we won't have unnecessary changes. ksort($new_settings); if (!$this->entity->getOption('processors', array()) !== $new_settings) { $this->entity->setOption('processors', $new_settings); $this->entity->save(); $this->entity->reindex(); drupal_set_message($this->t('The indexing workflow was successfully edited. All content was scheduled for reindexing so the new settings can take effect.')); } else { drupal_set_message($this->t('No values were changed.')); } }
/** * {@inheritdoc} */ public function getFields($extract = TRUE) { if ($extract && !$this->fieldsExtracted) { $data_type_fallback_mapping = Utility::getDataTypeFallbackMapping($this->index); foreach (array(NULL, $this->getDatasourceId()) as $datasource_id) { $fields_by_property_path = array(); foreach ($this->index->getFieldsByDatasource($datasource_id) as $field_id => $field) { // Don't overwrite fields that were previously set. if (empty($this->fields[$field_id])) { $this->fields[$field_id] = clone $field; $field_data_type = $this->fields[$field_id]->getType(); // If the field data type is in the fallback mapping list, then use // the fallback type as field type. if (isset($data_type_fallback_mapping[$field_data_type])) { $this->fields[$field_id]->setType($data_type_fallback_mapping[$field_data_type]); } $fields_by_property_path[$field->getPropertyPath()] = $this->fields[$field_id]; } } if ($datasource_id && $fields_by_property_path) { try { Utility::extractFields($this->getOriginalObject(), $fields_by_property_path); } catch (SearchApiException $e) { // If we couldn't load the object, just log an error and fail // silently to set the values. watchdog_exception('search_api', $e); } } } $this->fieldsExtracted = TRUE; } return $this->fields; }
/** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); /** @var $index \Drupal\search_api\IndexInterface */ $index = $this->getEntity(); $index->setOptions($form_state->getValue('options', array()) + $this->originalEntity->getOptions()); $datasources = $form_state->getValue('datasources', array()); /** @var \Drupal\search_api\Datasource\DatasourceInterface[] $datasource_plugins */ $datasource_plugins = $this->originalEntity->getDatasources(FALSE); $datasource_settings = array(); foreach ($datasources as $datasource_id) { $datasource = $datasource_plugins[$datasource_id]; $datasource_form = !empty($form['datasource_configs'][$datasource_id]) ? $form['datasource_configs'][$datasource_id] : array(); $datasource_form_state = new SubFormState($form_state, array('datasource_configs', $datasource_id)); $datasource->submitConfigurationForm($datasource_form, $datasource_form_state); $datasource_settings[$datasource_id] = $datasource; } $index->setDatasources($datasource_settings); // Call submitConfigurationForm() for the (possibly new) tracker. // @todo It seems if we change the tracker, we would validate/submit the old // tracker's form using the new tracker. Shouldn't be done, of course. // Similar above for datasources, though there of course the values will // just always be empty (because datasources have their plugin ID in the // form structure). $tracker_id = $form_state->getValue('tracker', NULL); if ($this->originalEntity->getTrackerId() == $tracker_id) { $tracker = $this->originalEntity->getTrackerInstance(); } else { $tracker = $this->trackerPluginManager->createInstance($tracker_id, array('index' => $this->originalEntity)); } $tracker_form_state = new SubFormState($form_state, array('tracker_config')); $tracker->submitConfigurationForm($form['tracker_config'], $tracker_form_state); $index->setTracker($tracker); }
/** * {@inheritdoc} */ public function render($row) { $datasource_id = $row->search_api_datasource; if (!$row->_item instanceof ComplexDataInterface) { $context = array('%item_id' => $row->search_api_id, '%view' => $this->view->storage->label()); $this->getLogger()->warning('Failed to load item %item_id in view %view.', $context); return ''; } // @todo Use isValidDatasource() instead. try { $datasource = $this->index->getDatasource($datasource_id); } catch (SearchApiException $e) { $context = array('%datasource' => $datasource_id, '%view' => $this->view->storage->label()); $this->getLogger()->warning('Item of unknown datasource %datasource returned in view %view.', $context); return ''; } // Always use the default view mode if it was not set explicitly in the // options. $view_mode = 'default'; $bundle = $this->index->getDatasource($datasource_id)->getItemBundle($row->_item); if (isset($this->options['view_modes'][$datasource_id][$bundle])) { $view_mode = $this->options['view_modes'][$datasource_id][$bundle]; } try { return $this->index->getDatasource($datasource_id)->viewItem($row->_item, $view_mode); } catch (SearchApiException $e) { watchdog_exception('search_api', $e); return ''; } }
/** * Cancels the editing of the index's fields. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function cancel(array &$form, FormStateInterface $form_state) { if ($this->entity instanceof UnsavedConfigurationInterface && $this->entity->hasChanges()) { $this->entity->discardChanges(); } $form_state->setRedirectUrl($this->entity->toUrl('canonical')); }
/** * 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.'); }
/** * 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; }
/** * {@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); }
/** * {@inheritdoc} */ public function getQueryTypesForFacet(FacetInterface $facet) { // Get our Facets Field Identifier, which is equal to the Search API Field // identifier. $field_id = $facet->getFieldIdentifier(); // Get the Search API Server. $server = $this->index->getServerInstance(); // Get the Search API Backend. $backend = $server->getBackend(); $fields = $this->index->getFields(); foreach ($fields as $field) { if ($field->getFieldIdentifier() == $field_id) { return $this->getQueryTypesForDataType($backend, $field->getType()); } } throw new InvalidQueryTypeException($this->t("No available query types were found for facet @facet", ['@facet' => $facet->getName()])); }
/** * Tests whether the properties are correctly altered. * * @see \Drupal\search_api\Plugin\search_api\processor\AggregatedField::alterPropertyDefinitions() */ public function testAlterPropertyDefinitions() { $fields = array('entity:test1/foo', 'entity:test1/foo:bar', 'entity:test2/foobaz:bla'); $index_fields = array(); foreach ($fields as $field_id) { $field_object = Utility::createField($this->index, $field_id); list($prefix, $label) = str_replace(':', ' » ', Utility::splitCombinedId($field_id)); $field_object->setLabelPrefix($prefix . ' » '); $field_object->setLabel($label); $index_fields[$field_id] = $field_object; } $this->index->expects($this->any())->method('getFields')->willReturn($index_fields); $this->index->method('getPropertyDefinitions')->willReturn(array()); $dummy_datasource = $this->getMock('Drupal\\search_api\\Datasource\\DatasourceInterface'); $dummy_datasource->method('label')->willReturn('datasource'); $this->index->method('getDatasources')->willReturn(array('entity:test1' => $dummy_datasource, 'entity:test2' => $dummy_datasource)); $configuration['fields'] = array('search_api_aggregation_1' => array('label' => 'Field 1', 'type' => 'union', 'fields' => array('entity:test1/foo', 'entity:test1/foo:bar', 'entity:test2/foobaz:bla')), 'search_api_aggregation_2' => array('label' => 'Field 2', 'type' => 'max', 'fields' => array('entity:test1/foo:bar'))); $this->processor->setConfiguration($configuration); /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ $translation = $this->getStringTranslationStub(); $this->processor->setStringTranslation($translation); // Check for modified properties when no datasource is given. $properties = array(); $this->processor->alterPropertyDefinitions($properties, NULL); $property_added = array_key_exists('search_api_aggregation_1', $properties); $this->assertTrue($property_added, 'The "search_api_aggregation_1" property was added to the properties.'); if ($property_added) { $this->assertInstanceOf('Drupal\\Core\\TypedData\\DataDefinitionInterface', $properties['search_api_aggregation_1'], 'The "search_api_aggregation_1" property contains a valid data definition.'); if ($properties['search_api_aggregation_1'] instanceof DataDefinitionInterface) { $this->assertEquals('string', $properties['search_api_aggregation_1']->getDataType(), 'Correct data type set in the data definition.'); $this->assertEquals('Field 1', $properties['search_api_aggregation_1']->getLabel(), 'Correct label set in the data definition.'); $fields_string = 'datasource » foo, datasource » foo:bar, datasource » foobaz:bla'; $description = $translation->translate('A @type aggregation of the following fields: @fields.', array('@type' => $translation->translate('Union'), '@fields' => $fields_string)); $this->assertEquals($description, $properties['search_api_aggregation_1']->getDescription(), 'Correct description set in the data definition.'); } } $property_added = array_key_exists('search_api_aggregation_2', $properties); $this->assertTrue($property_added, 'The "search_api_aggregation_2" property was added to the properties.'); if ($property_added) { $this->assertInstanceOf('Drupal\\Core\\TypedData\\DataDefinitionInterface', $properties['search_api_aggregation_2'], 'The "search_api_aggregation_2" property contains a valid data definition.'); if ($properties['search_api_aggregation_2'] instanceof DataDefinitionInterface) { $this->assertEquals('integer', $properties['search_api_aggregation_2']->getDataType(), 'Correct data type set in the data definition.'); $this->assertEquals('Field 2', $properties['search_api_aggregation_2']->getLabel(), 'Correct label set in the data definition.'); $description = $translation->translate('A @type aggregation of the following fields: @fields.', array('@type' => $translation->translate('Maximum'), '@fields' => 'datasource » foo:bar')); $this->assertEquals($description, $properties['search_api_aggregation_2']->getDescription(), 'Correct description set in the data definition.'); } } // Test whether the properties of specific datasources stay untouched. $properties = array(); /** @var \Drupal\search_api\Datasource\DatasourceInterface $datasource */ $datasource = $this->getMock('Drupal\\search_api\\Datasource\\DatasourceInterface'); $this->processor->alterPropertyDefinitions($properties, $datasource); $this->assertEmpty($properties, 'Datasource-specific properties did not get changed.'); }
/** * Form submission handler for adding a new field to the index. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function addField(array $form, FormStateInterface $form_state) { $button = $form_state->getTriggeringElement(); if (!$button) { return; } /** @var \Drupal\Core\TypedData\DataDefinitionInterface $property */ $property = $button['#property']; list($datasource_id, $property_path) = Utility::splitCombinedId($button['#name']); $field = Utility::createFieldFromProperty($this->entity, $property, $datasource_id, $property_path, NULL, $button['#data_type']); $field->setLabel($button['#prefixed_label']); $this->entity->addField($field); $args['%label'] = $field->getLabel(); drupal_set_message($this->t('Field %label was added to the index.', $args)); }
/** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); /** @var $index \Drupal\search_api\IndexInterface */ $index = $this->getEntity(); $index->setOptions($form_state->getValue('options', array()) + $this->originalEntity->getOptions()); $datasources = $form_state->getValue('datasources', array()); /** @var \Drupal\search_api\Datasource\DatasourceInterface[] $datasource_plugins */ $datasource_plugins = $this->originalEntity->getDatasources(FALSE); $datasource_configuration = array(); foreach ($datasources as $datasource_id) { $datasource = $datasource_plugins[$datasource_id]; $datasource_form = (!empty($form['datasource_configs'][$datasource_id])) ? $form['datasource_configs'][$datasource_id] : array(); $datasource_form_state = new SubFormState($form_state, array('datasource_configs', $datasource_id)); $datasource->submitConfigurationForm($datasource_form, $datasource_form_state); $datasource_configuration[$datasource_id] = $datasource->getConfiguration(); } $index->set('datasource_configs', $datasource_configuration); // Call submitConfigurationForm() for the (possibly new) tracker. // @todo It seems if we change the tracker, we would validate/submit the old // tracker's form using the new tracker. Shouldn't be done, of course. // Similar above for datasources, though there of course the values will // just always be empty (because datasources have their plugin ID in the // form structure). $tracker_id = $form_state->getValue('tracker', NULL); if ($this->originalEntity->getTrackerId() == $tracker_id) { $tracker = $this->originalEntity->getTracker(); } else { $tracker = $this->trackerPluginManager->createInstance($tracker_id, array('index' => $this->originalEntity)); } $tracker_form_state = new SubFormState($form_state, array('tracker_config')); $tracker->submitConfigurationForm($form['tracker_config'], $tracker_form_state); $index->set('tracker_config', $tracker->getConfiguration()); // Invalidate caches, so this gets picked up by the views wizard. Cache::invalidateTags(array('views_data')); // Remove this line when https://www.drupal.org/node/2370365 gets fixed. Cache::invalidateTags(array('extension:views')); }
/** * Generates some test items. * * @param array[] $items * Array of items to be transformed into proper search item objects. Each * item in this array is an associative array with the following keys: * - datasource: The datasource plugin ID. * - item: The item object to be indexed. * - item_id: The datasource-specific raw item ID. * - *: Any other keys will be treated as property paths, and their values * as a single value for a field with that property path. * * @return \Drupal\search_api\Item\ItemInterface[] * The generated test items. */ public function generateItems(array $items) { /** @var \Drupal\search_api\Item\ItemInterface[] $extracted_items */ $extracted_items = array(); foreach ($items as $item) { $id = Utility::createCombinedId($item['datasource'], $item['item_id']); $extracted_items[$id] = Utility::createItemFromObject($this->index, $item['item'], $id); foreach (array(NULL, $item['datasource']) as $datasource_id) { foreach ($this->index->getFieldsByDatasource($datasource_id) as $key => $field) { /** @var \Drupal\search_api\Item\FieldInterface $field */ $field = clone $field; if (isset($item[$field->getPropertyPath()])) { $field->addValue($item[$field->getPropertyPath()]); } $extracted_items[$id]->setField($key, $field); } } } return $extracted_items; }
/** * 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->urlInfo('canonical'); return $this->redirect($url->getRouteName(), $url->getRouteParameters()); }
/** * 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; }
/** * 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); }
/** * Finds a new unique field identifier on the given index. * * @param \Drupal\search_api\IndexInterface $index * The search index. * @param string $property_path * The property path on which the field identifier should be based. Only the * last component of the property path will be considered. * * @return string * A new unique field identifier on the given index. */ public static function getNewFieldId(IndexInterface $index, $property_path) { list(, $suggested_id) = static::splitPropertyPath($property_path); $field_id = $suggested_id; $i = 0; while ($index->getField($field_id)) { $field_id = $suggested_id . '_' . ++$i; } return $field_id; }
/** * {@inheritdoc} */ public function updateIndex(IndexInterface $index) { $this->checkError(__FUNCTION__); $index->reindex(); }
/** * Tests translation handling of the content entity datasource. */ public function testItemTranslations() { // Test retrieving language and translations when no translations are // available. $entity_1 = EntityTestMul::create(array('id' => 1, 'name' => 'test 1', 'user_id' => $this->container->get('current_user')->id())); $entity_1->save(); $this->assertEqual($entity_1->language()->getId(), 'en', SafeMarkup::format('%entity_type: Entity language set to site default.', array('%entity_type' => $this->testEntityTypeId))); $this->assertFalse($entity_1->getTranslationLanguages(FALSE), SafeMarkup::format('%entity_type: No translations are available', array('%entity_type' => $this->testEntityTypeId))); $entity_2 = EntityTestMul::create(array('id' => 2, 'name' => 'test 2', 'user_id' => $this->container->get('current_user')->id())); $entity_2->save(); $this->assertEqual($entity_2->language()->getId(), 'en', SafeMarkup::format('%entity_type: Entity language set to site default.', array('%entity_type' => $this->testEntityTypeId))); $this->assertFalse($entity_2->getTranslationLanguages(FALSE), SafeMarkup::format('%entity_type: No translations are available', array('%entity_type' => $this->testEntityTypeId))); // Test that the datasource returns the correct item IDs. $datasource = $this->index->getDatasource('entity:' . $this->testEntityTypeId); $datasource_item_ids = $datasource->getItemIds(); sort($datasource_item_ids); $expected = array('1:en', '2:en'); $this->assertEqual($datasource_item_ids, $expected, 'Datasource returns correct item ids.'); // Test indexing the new entity. $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 0, 'The index is empty.'); $this->assertEqual($this->index->getTracker()->getTotalItemsCount(), 2, 'There are two items to be indexed.'); $this->index->index(); $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 2, 'Two items have been indexed.'); // Now, make the first entity language-specific by assigning a language. $default_langcode = $this->langcodes[0]; $entity_1->get('langcode')->setValue($default_langcode); $entity_1->save(); $this->assertEqual($entity_1->language(), \Drupal::languageManager()->getLanguage($this->langcodes[0]), SafeMarkup::format('%entity_type: Entity language retrieved.', array('%entity_type' => $this->testEntityTypeId))); $this->assertFalse($entity_1->getTranslationLanguages(FALSE), SafeMarkup::format('%entity_type: No translations are available', array('%entity_type' => $this->testEntityTypeId))); // Test that the datasource returns the correct item IDs. $datasource_item_ids = $datasource->getItemIds(); sort($datasource_item_ids); $expected = array('1:' . $this->langcodes[0], '2:en'); $this->assertEqual($datasource_item_ids, $expected, 'Datasource returns correct item ids.'); // Test that the index needs to be updated. $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 1, 'The updated item needs to be reindexed.'); $this->assertEqual($this->index->getTracker()->getTotalItemsCount(), 2, 'There are two items in total.'); // Set two translations for the first entity and test that the datasource // returns three separate item IDs, one for each translation. $translation = $entity_1->getTranslation($this->langcodes[1]); $translation->save(); $translation = $entity_1->getTranslation($this->langcodes[2]); $translation->save(); $this->assertTrue($entity_1->getTranslationLanguages(FALSE), SafeMarkup::format('%entity_type: Translations are available', array('%entity_type' => $this->testEntityTypeId))); $datasource_item_ids = $datasource->getItemIds(); sort($datasource_item_ids); $expected = array('1:' . $this->langcodes[0], '1:' . $this->langcodes[1], '1:' . $this->langcodes[2], '2:en'); $this->assertEqual($datasource_item_ids, $expected, 'Datasource returns correct item ids for a translated entity.'); // Test that the index needs to be updated. $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 1, 'The updated items needs to be reindexed.'); $this->assertEqual($this->index->getTracker()->getTotalItemsCount(), 4, 'There are four items in total.'); // Delete one translation and test that the datasource returns only three // items. $entity_1->removeTranslation($this->langcodes[2]); $entity_1->save(); $datasource_item_ids = $datasource->getItemIds(); sort($datasource_item_ids); $expected = array('1:' . $this->langcodes[0], '1:' . $this->langcodes[1], '2:en'); $this->assertEqual($datasource_item_ids, $expected, 'Datasource returns correct item ids for a translated entity.'); // Test reindexing. $this->assertEqual($this->index->getTracker()->getTotalItemsCount(), 3, 'There are three items in total.'); $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 1, 'The updated items needs to be reindexed.'); $this->index->index(); $this->assertEqual($this->index->getTracker()->getIndexedItemsCount(), 3, 'Three items are indexed.'); }
/** * Removes a field from a search index. * * @param \Drupal\search_api\IndexInterface $search_api_index * The search index. * @param string $field_id * The ID of the field to remove. * * @return \Symfony\Component\HttpFoundation\Response * The response to send to the browser. * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException * Thrown when the field was not found. */ public function removeField(IndexInterface $search_api_index, $field_id) { $fields = $search_api_index->getFields(); if (isset($fields[$field_id])) { try { $search_api_index->removeField($field_id); $search_api_index->save(); } catch (SearchApiException $e) { $args['%field'] = $fields[$field_id]->getLabel(); drupal_set_message($this->t('The field %field is locked and cannot be removed.', $args), 'error'); } } else { throw new NotFoundHttpException(); } // Redirect to the index's "View" page. $url = $search_api_index->toUrl('fields'); return $this->redirect($url->getRouteName(), $url->getRouteParameters()); }
/** * {@inheritdoc} */ public function calculateDependencies() { $dependencies = parent::calculateDependencies(); $dependencies[$this->index->getConfigDependencyKey()][] = $this->index->getConfigDependencyName(); return $dependencies; }
/** * 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.'); }
/** * Reacts to changes in processor configuration. * * @param \Drupal\search_api\IndexInterface $original * The previous version of the index. */ protected function reactToProcessorChanges(IndexInterface $original) { $old_processors = $original->getProcessors(); $new_processors = $this->getProcessors(); // Only actually do something when the processor settings are changed. if ($old_processors != $new_processors) { $requires_reindex = FALSE; // Loop over all new settings and check if the processors were already set // in the original entity. foreach ($new_processors as $key => $processor) { // The processor is new, because it wasn't configured in the original // entity. if (!isset($old_processors[$key])) { if ($processor->requiresReindexing(NULL, $processor->getConfiguration())) { $requires_reindex = TRUE; break; } } } if (!$requires_reindex) { // Loop over all original settings and check if one of them has been // removed or changed. foreach ($old_processors as $key => $old_processor) { $new_processor = isset($new_processors[$key]) ? $new_processors[$key] : NULL; $old_config = $old_processor->getConfiguration(); $new_config = $new_processor ? $new_processor->getConfiguration() : NULL; if (!$new_processor || $old_config != $new_config) { if ($old_processor->requiresReindexing($old_config, $new_config)) { $requires_reindex = TRUE; break; } } } } if ($requires_reindex) { $this->reindex(); } } }
/** * 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]; }