diff --git a/search_api.api.php b/search_api.api.php index 0fa2331..a697b78 100644 --- a/search_api.api.php +++ b/search_api.api.php @@ -87,13 +87,13 @@ function hook_search_api_data_type_info_alter(array &$data_type_definitions) { * * @param array $mapping * An array mapping all known (and supported) Drupal data types to their - * corresponding Search API data types. Empty values mean that fields of + * corresponding Search API data types. A value of FALSE means that fields of * that type should be ignored by the Search API. * * @see \Drupal\search_api\Utility::getFieldTypeMapping() */ function hook_search_api_field_type_mapping_alter(array &$mapping) { - $mapping['duration_iso8601'] = NULL; + $mapping['duration_iso8601'] = FALSE; $mapping['my_new_type'] = 'string'; } diff --git a/search_api_db/README.txt b/search_api_db/README.txt index 41df398..ac6ed63 100644 --- a/search_api_db/README.txt +++ b/search_api_db/README.txt @@ -24,7 +24,7 @@ Supported optional features better if only a single field is used for autocompletion. - search_api_facets Introduced by module: search_api_facetapi - Allows you to create faceted searches for dynamically filtering search + Allows you to create facetted searches for dynamically filtering search results. If you feel some backend option is missing, or have other ideas for improving diff --git a/search_api_db/src/Tests/BackendTest.php b/search_api_db/src/Tests/BackendTest.php index 039041c..56c4a16 100644 --- a/search_api_db/src/Tests/BackendTest.php +++ b/search_api_db/src/Tests/BackendTest.php @@ -509,11 +509,10 @@ class BackendTest extends EntityUnitTestBase { $this->assertWarnings($results); // Regression tests for #1863672. - $keywords_field = 'keywords'; $query = $this->buildSearch(); $conditions = $query->createConditionGroup('OR'); - $conditions->addCondition($keywords_field, 'orange'); - $conditions->addCondition($keywords_field, 'apple'); + $conditions->addCondition('keywords', 'orange'); + $conditions->addCondition('keywords', 'apple'); $query->addConditionGroup($conditions); $query->sort('id', QueryInterface::SORT_ASC); $results = $query->execute(); @@ -524,12 +523,12 @@ class BackendTest extends EntityUnitTestBase { $query = $this->buildSearch(); $conditions = $query->createConditionGroup('OR'); - $conditions->addCondition($keywords_field, 'orange'); - $conditions->addCondition($keywords_field, 'strawberry'); + $conditions->addCondition('keywords', 'orange'); + $conditions->addCondition('keywords', 'strawberry'); $query->addConditionGroup($conditions); $conditions = $query->createConditionGroup('OR'); - $conditions->addCondition($keywords_field, 'apple'); - $conditions->addCondition($keywords_field, 'grape'); + $conditions->addCondition('keywords', 'apple'); + $conditions->addCondition('keywords', 'grape'); $query->addConditionGroup($conditions); $query->sort('id', QueryInterface::SORT_ASC); $results = $query->execute(); @@ -541,12 +540,12 @@ class BackendTest extends EntityUnitTestBase { $query = $this->buildSearch(); $conditions1 = $query->createConditionGroup('OR'); $conditions = $query->createConditionGroup('AND'); - $conditions->addCondition($keywords_field, 'orange'); - $conditions->addCondition($keywords_field, 'apple'); + $conditions->addCondition('keywords', 'orange'); + $conditions->addCondition('keywords', 'apple'); $conditions1->addConditionGroup($conditions); $conditions = $query->createConditionGroup('AND'); - $conditions->addCondition($keywords_field, 'strawberry'); - $conditions->addCondition($keywords_field, 'grape'); + $conditions->addCondition('keywords', 'strawberry'); + $conditions->addCondition('keywords', 'grape'); $conditions1->addConditionGroup($conditions); $query->addConditionGroup($conditions1); $query->sort('id', QueryInterface::SORT_ASC); @@ -926,8 +925,8 @@ class BackendTest extends EntityUnitTestBase { // Regression test for #2616268. $index = $this->getIndex(); - $field = $index->getField('body'); - $field->setType('string')->addToIndex(); + $field = $index->getField('body')->setType('string'); + $index->addField($field); $index->save(); $count = $this->indexItems($this->indexId); $this->assertEqual($count, 8, 'Switching type from text to string worked.'); diff --git a/src/Entity/Index.php b/src/Entity/Index.php index 8216ed4..08ad8e1 100644 --- a/src/Entity/Index.php +++ b/src/Entity/Index.php @@ -278,8 +278,9 @@ class Index extends ConfigEntityBase implements IndexInterface { */ public function setOption($name, $option) { $this->options[$name] = $option; + // If the fields are changed, reset the static fields cache. if ($name == 'fields') { - unset($this->cache); + $this->cache = array(); } return $this; } @@ -949,28 +950,6 @@ class Index extends ConfigEntityBase implements IndexInterface { } /** - * Sets this object as the index for all fields contained in the given array. - * - * This is important when loading fields from the cache, because their index - * objects might point to another instance of this index. - * - * @param array $fields - * An array containing various values, some of which might be - * \Drupal\search_api\Item\FieldInterface objects and some of which might be - * nested arrays containing such objects. - */ - protected function updateFieldsIndex(array $fields) { - foreach ($fields as $value) { - if (is_array($value)) { - $this->updateFieldsIndex($value); - } - elseif ($value instanceof FieldInterface) { - $value->setIndex($this); - } - } - } - - /** * Sets data in the static and/or stored cache. * * @param string $method @@ -1010,6 +989,28 @@ class Index extends ConfigEntityBase implements IndexInterface { } /** + * Sets this object as the index for all fields contained in the given array. + * + * This is important when loading fields from the cache, because their index + * objects might point to another instance of this index. + * + * @param array $fields + * An array containing various values, some of which might be + * \Drupal\search_api\Item\FieldInterface objects and some of which might be + * nested arrays containing such objects. + */ + protected function updateFieldsIndex(array $fields) { + foreach ($fields as $value) { + if (is_array($value)) { + $this->updateFieldsIndex($value); + } + elseif ($value instanceof FieldInterface) { + $value->setIndex($this); + } + } + } + + /** * {@inheritdoc} */ public function query(array $options = array()) { @@ -1038,8 +1039,8 @@ class Index extends ConfigEntityBase implements IndexInterface { unset($this->options['fields'][$field_id]['hidden']); } - // Obviously, we should first check for locked processors, because their - // preIndexSave() method should be called, too (if applicable). + // We first have to check for locked processors, otherwise their + // preIndexSave() methods might not be called in the next step. foreach ($this->getProcessors(FALSE) as $processor_id => $processor) { if ($processor->isLocked() && !isset($this->options['processors'][$processor_id])) { $this->options['processors'][$processor_id] = array( diff --git a/src/Form/IndexAddFieldsForm.php b/src/Form/IndexAddFieldsForm.php index 3a39737..dbc2ad4 100644 --- a/src/Form/IndexAddFieldsForm.php +++ b/src/Form/IndexAddFieldsForm.php @@ -25,7 +25,7 @@ use Drupal\user\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a form for adding indexed fields to a search index. + * Provides a form for adding fields to a search index. */ class IndexAddFieldsForm extends EntityForm { @@ -67,7 +67,7 @@ class IndexAddFieldsForm extends EntityForm { /** * The date formatter. * - * @var \Drupal\Core\Datetime\DateFormatter|null + * @var \Drupal\Core\Datetime\DateFormatter */ protected $dateFormatter; @@ -103,7 +103,7 @@ class IndexAddFieldsForm extends EntityForm { } /** - * Constructs an IndexFieldsForm object. + * Constructs an IndexAddFieldsForm object. * * @param \Drupal\user\SharedTempStoreFactory $temp_store_factory * The factory for shared temporary storages. @@ -131,22 +131,11 @@ class IndexAddFieldsForm extends EntityForm { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - /** @var \Drupal\user\SharedTempStoreFactory $temp_store_factory */ $temp_store_factory = $container->get('user.shared_tempstore'); - - /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ $entity_type_manager = $container->get('entity_type.manager'); - - /** @var \Drupal\search_api\DataType\DataTypePluginManager $data_type_plugin_manager */ $data_type_plugin_manager = $container->get('plugin.manager.search_api.data_type'); - - /** @var $renderer \Drupal\Core\Render\RendererInterface */ $renderer = $container->get('renderer'); - - /** @var $date_formatter \Drupal\Core\Datetime\DateFormatter */ $date_formatter = $container->get('date.formatter'); - - /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ $request_stack = $container->get('request_stack'); $parameters = $request_stack->getCurrentRequest()->query->all(); @@ -184,39 +173,13 @@ class IndexAddFieldsForm extends EntityForm { } /** - * Sets the renderer. - * - * @param \Drupal\Core\Render\RendererInterface $renderer - * The new renderer. - * - * @return $this - */ - public function setRenderer(RendererInterface $renderer) { - $this->renderer = $renderer; - return $this; - } - - /** * Retrieves the date formatter. * * @return \Drupal\Core\Datetime\DateFormatter * The date formatter. */ public function getDateFormatter() { - return $this->dateFormatter ?: \Drupal::service('date.formatter'); - } - - /** - * Sets the date formatter. - * - * @param \Drupal\Core\Datetime\DateFormatter $date_formatter - * The new date formatter. - * - * @return $this - */ - public function setDateFormatter(DateFormatter $date_formatter) { - $this->dateFormatter = $date_formatter; - return $this; + return $this->dateFormatter; } /** @@ -405,7 +368,8 @@ class IndexAddFieldsForm extends EntityForm { if ($property instanceof ComplexDataDefinitionInterface) { $can_be_indexed = FALSE; $nested_properties = $property->getPropertyDefinitions(); - if ($main_property = $property->getMainPropertyName()) { + $main_property = $property->getMainPropertyName(); + if ($main_property && isset($nested_properties[$main_property])) { $parent_child_type = $property->getDataType() . '.'; $property = $nested_properties[$main_property]; $parent_child_type .= $property->getDataType(); @@ -439,6 +403,11 @@ class IndexAddFieldsForm extends EntityForm { $can_be_indexed = FALSE; } + // If the property can neither be expanded nor indexed, just skip it. + if (!($nested_properties || $can_be_indexed)) { + continue; + } + $nested_list = array(); $expand_link = array(); if ($nested_properties) { diff --git a/src/Form/IndexBreakLockForm.php b/src/Form/IndexBreakLockForm.php index 4ff817a..7ec13b3 100644 --- a/src/Form/IndexBreakLockForm.php +++ b/src/Form/IndexBreakLockForm.php @@ -8,7 +8,7 @@ namespace Drupal\search_api\Form; use Drupal\Core\Entity\EntityConfirmFormBase; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\RendererInterface; use Drupal\user\SharedTempStoreFactory; @@ -29,9 +29,9 @@ class IndexBreakLockForm extends EntityConfirmFormBase { /** * The entity manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** * The renderer. @@ -45,14 +45,14 @@ class IndexBreakLockForm extends EntityConfirmFormBase { * * @param \Drupal\user\SharedTempStoreFactory $temp_store_factory * The factory for shared temporary storages. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The Entity manager. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer to use. */ - public function __construct(SharedTempStoreFactory $temp_store_factory, EntityManagerInterface $entity_manager, RendererInterface $renderer) { + public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer) { $this->tempStore = $temp_store_factory->get('search_api_index'); - $this->entityManager = $entity_manager; + $this->entityTypeManager = $entity_type_manager; $this->renderer = $renderer; } @@ -60,16 +60,11 @@ class IndexBreakLockForm extends EntityConfirmFormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - /** @var \Drupal\user\SharedTempStoreFactory $temp_store_factory */ $temp_store_factory = $container->get('user.shared_tempstore'); - - /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ - $entity_manager = $container->get('entity.manager'); - - /** @var $renderer \Drupal\Core\Render\RendererInterface */ + $entity_type_manager = $container->get('entity_type.manager'); $renderer = $container->get('renderer'); - return new static($temp_store_factory, $entity_manager, $renderer); + return new static($temp_store_factory, $entity_type_manager, $renderer); } /** @@ -91,7 +86,7 @@ class IndexBreakLockForm extends EntityConfirmFormBase { */ public function getDescription() { $locked = $this->tempStore->getMetadata($this->entity->id()); - $account = $this->entityManager->getStorage('user')->load($locked->owner); + $account = $this->entityTypeManager->getStorage('user')->load($locked->owner); $username = array( '#theme' => 'username', '#account' => $account, @@ -103,7 +98,7 @@ class IndexBreakLockForm extends EntityConfirmFormBase { * {@inheritdoc} */ public function getCancelUrl() { - return $this->entity->urlInfo('fields'); + return $this->entity->toUrl('fields'); } /** @@ -129,7 +124,7 @@ class IndexBreakLockForm extends EntityConfirmFormBase { */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->tempStore->delete($this->entity->id()); - $form_state->setRedirectUrl($this->entity->urlInfo('fields')); + $form_state->setRedirectUrl($this->entity->toUrl('fields')); drupal_set_message($this->t('The lock has been broken and you may now edit this search index.')); } diff --git a/src/Form/IndexFieldsForm.php b/src/Form/IndexFieldsForm.php index c77abe9..dd8a980 100644 --- a/src/Form/IndexFieldsForm.php +++ b/src/Form/IndexFieldsForm.php @@ -62,7 +62,7 @@ class IndexFieldsForm extends EntityForm { /** * The date formatter. * - * @var \Drupal\Core\Datetime\DateFormatter|null + * @var \Drupal\Core\Datetime\DateFormatter */ protected $dateFormatter; @@ -106,19 +106,10 @@ class IndexFieldsForm extends EntityForm { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - /** @var \Drupal\user\SharedTempStoreFactory $temp_store_factory */ $temp_store_factory = $container->get('user.shared_tempstore'); - - /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */ $entity_type_manager = $container->get('entity_type.manager'); - - /** @var \Drupal\search_api\DataType\DataTypePluginManager $data_type_plugin_manager */ $data_type_plugin_manager = $container->get('plugin.manager.search_api.data_type'); - - /** @var $renderer \Drupal\Core\Render\RendererInterface */ $renderer = $container->get('renderer'); - - /** @var $date_formatter \Drupal\Core\Datetime\DateFormatter */ $date_formatter = $container->get('date.formatter'); return new static($temp_store_factory, $entity_type_manager, $data_type_plugin_manager, $renderer, $date_formatter); @@ -155,39 +146,13 @@ class IndexFieldsForm extends EntityForm { } /** - * Sets the renderer. - * - * @param \Drupal\Core\Render\RendererInterface $renderer - * The new renderer. - * - * @return $this - */ - public function setRenderer(RendererInterface $renderer) { - $this->renderer = $renderer; - return $this; - } - - /** * Retrieves the date formatter. * * @return \Drupal\Core\Datetime\DateFormatter * The date formatter. */ public function getDateFormatter() { - return $this->dateFormatter ?: \Drupal::service('date.formatter'); - } - - /** - * Sets the date formatter. - * - * @param \Drupal\Core\Datetime\DateFormatter $date_formatter - * The new date formatter. - * - * @return $this - */ - public function setDateFormatter(DateFormatter $date_formatter) { - $this->dateFormatter = $date_formatter; - return $this; + return $this->dateFormatter; } /** diff --git a/src/Item/Field.php b/src/Item/Field.php index 935fcb8..adaba08 100644 --- a/src/Item/Field.php +++ b/src/Item/Field.php @@ -494,9 +494,11 @@ class Field implements \IteratorAggregate, FieldInterface { * Implements the magic __toString() method to simplify debugging. */ public function __toString() { - $out = $this->getLabel() . ' [' . $this->getFieldIdentifier() . - ']: indexed as type ' . $this->getType(); - if (Utility::isTextType($this->getType())) { + $label = $this->getLabel(); + $field_id = $this->getFieldIdentifier(); + $type = $this->getType(); + $out = "$label [$field_id]: indexed as type $type"; + if (Utility::isTextType($type)) { $out .= ' (boost ' . $this->getBoost() . ')'; } if ($this->getValues()) { diff --git a/src/Plugin/search_api/processor/AggregatedFields.php b/src/Plugin/search_api/processor/AggregatedFields.php index 3678e49..2e58df9 100644 --- a/src/Plugin/search_api/processor/AggregatedFields.php +++ b/src/Plugin/search_api/processor/AggregatedFields.php @@ -391,8 +391,9 @@ class AggregatedFields extends ProcessorPluginBase { // use it. This might actually make problems in case the values have // already been processed in some way, or use a data type that // transformed their original value – but on the other hand, it's - // (currently) the only way to include computed (processor-added) - // properties here, so it seems like a fair trade-off. + // (currently – see #2575003) the only way to include computed + // (processor-added) properties here, so it seems like a fair + // trade-off. foreach ($this->filterForPropertyPath($item->getFields(FALSE), $property_path) as $field) { if ($field->getDatasourceId() === $datasource_id) { $property_values[$combined_id] = $field->getValues(); @@ -400,10 +401,10 @@ class AggregatedFields extends ProcessorPluginBase { } } - // If the field is not already on the item, we need to extract it. (We + // If the field is not already on the item, we need to extract it. We // set our own combined ID as the field identifier as kind of a hack, // to easily be able to add the field values to $property_values - // afterwards). + // afterwards. if ($datasource_id) { $missing_fields[$property_path] = Utility::createField($this->index, $combined_id); } diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php index 5deb39b..266d43d 100644 --- a/src/Tests/IntegrationTest.php +++ b/src/Tests/IntegrationTest.php @@ -557,7 +557,7 @@ class IntegrationTest extends WebTestBase { * Tests whether removing fields from the index works correctly. */ protected function removeFieldsFromIndex() { - // Find the "Remove" Link for the "body" field. + // Find the "Remove" link for the "body" field. $links = $this->xpath('//a[@data-drupal-selector=:id]', array(':id' => 'edit-fields-body-remove')); if (empty($links)) { $this->fail('Found "Remove" link for body field'); diff --git a/src/UnsavedConfigurationInterface.php b/src/UnsavedConfigurationInterface.php index ff823b1..62d04ea 100644 --- a/src/UnsavedConfigurationInterface.php +++ b/src/UnsavedConfigurationInterface.php @@ -79,11 +79,11 @@ interface UnsavedConfigurationInterface { /** * Saves the changes represented by this object permanently. * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity manager to use, if necessary. Or NULL to retrieve the entity * manager from the global container. */ - public function savePermanent(EntityTypeManagerInterface $entity_manager = NULL); + public function savePermanent(EntityTypeManagerInterface $entity_type_manager = NULL); /** * Discards the changes represented by this object. diff --git a/src/UnsavedIndexConfiguration.php b/src/UnsavedIndexConfiguration.php index 377da70..8fe07be 100644 --- a/src/UnsavedIndexConfiguration.php +++ b/src/UnsavedIndexConfiguration.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\search_api\Item\FieldInterface; use Drupal\search_api\Query\QueryInterface; use Drupal\search_api\Query\ResultSetInterface; use Drupal\user\SharedTempStore; @@ -128,13 +129,13 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI /** * {@inheritdoc} */ - public function savePermanent(EntityTypeManagerInterface $entity_manager = NULL) { + public function savePermanent(EntityTypeManagerInterface $entity_type_manager = NULL) { // Make sure to overwrite only those properties that were changed in this // copy. Unlike in the Views UI, we have several edit pages for indexes // ("Edit", "Fields", "Processors") and only one of them is locked, so this // is necessary. /** @var \Drupal\search_api\IndexInterface $original */ - $original = $entity_manager->getStorage($this->entity->getEntityTypeId())->loadUnchanged($this->entity->id()); + $original = $entity_type_manager->getStorage($this->entity->getEntityTypeId())->loadUnchanged($this->entity->id()); foreach ($this->changedProperties as $property) { $original->set($property, $this->entity->get($property)); } @@ -907,4 +908,54 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI return $this->entity->getThirdPartyProviders(); } + /** + * Adds a field to this index. + * + * If the field is already present (with the same datasource and property + * path) its settings will be updated. + * + * @param \Drupal\search_api\Item\FieldInterface $field + * The field to add, or update. + * + * @throws \Drupal\search_api\SearchApiException + * Thrown if the field could not be added, either because a different field + * with the same field ID would be overwritten, or because the field + * identifier is one of the pseudo-fields that can be used in search + * queries. + */ + public function addField(FieldInterface $field) { + // @todo Implement addField() method. + } + + /** + * Changes the field ID of a field. + * + * @param string $old_field_id + * The old ID of the field. + * @param string $new_field_id + * The new ID of the field. + * + * @throws \Drupal\search_api\SearchApiException + * Thrown if no field with the old ID exists, or because the new ID is + * already taken, or because the new field ID is one of the pseudo-fields + * that can be used in search queries. + */ + public function renameField($old_field_id, $new_field_id) { + // @todo Implement renameField() method. + } + + /** + * Removes a field from the index. + * + * If the field doesn't exist, the call will fail silently. + * + * @param string $field_id + * The ID of the field to remove. + * + * @throws \Drupal\search_api\SearchApiException + * Thrown if the field is locked. + */ + public function removeField($field_id) { + // @todo Implement removeField() method. + } } diff --git a/tests/search_api_test_hooks/search_api_test_hooks.module b/tests/search_api_test_hooks/search_api_test_hooks.module index d400acd..19ac1b3 100644 --- a/tests/search_api_test_hooks/search_api_test_hooks.module +++ b/tests/search_api_test_hooks/search_api_test_hooks.module @@ -76,8 +76,8 @@ function search_api_test_hooks_search_api_data_type_info_alter(array &$data_type * Implements hook_search_api_field_type_mapping_alter. */ function search_api_test_hooks_search_api_field_type_mapping_alter(array &$mapping) { - $mapping['datetime_iso8601'] = NULL; - $mapping['timestamp'] = NULL; + $mapping['datetime_iso8601'] = FALSE; + $mapping['timestamp'] = FALSE; } /**