diff --git a/src/Entity/Facet.php b/src/Entity/Facet.php index a6495fb..55ad07a 100644 --- a/src/Entity/Facet.php +++ b/src/Entity/Facet.php @@ -231,6 +231,14 @@ class Facet extends ConfigEntityBase implements FacetInterface { /** * {@inheritdoc} */ + protected function urlRouteParameters($rel) { + $parameters = parent::urlRouteParameters($rel); + return $parameters; + } + + /** + * {@inheritdoc} + */ public function getDescription() { return $this->description; } @@ -246,11 +254,51 @@ class Facet extends ConfigEntityBase implements FacetInterface { /** * {@inheritdoc} */ + public function getQueryTypes() { + return $this->query_type_name; + } + + /** + * {@inheritdoc} + */ public function getWidget() { return $this->widget; } /** + * Retrieves all processors supported by this facet. + * + * @return \Drupal\facets\Processor\ProcessorInterface[] + * The loaded processors, keyed by processor ID. + */ + protected function loadProcessors() { + if (!isset($this->processors)) { + /* @var $processor_plugin_manager \Drupal\facets\Processor\ProcessorPluginManager */ + $processor_plugin_manager = \Drupal::service('plugin.manager.facets.processor'); + $processor_settings = $this->getOption('processors', []); + + foreach ($processor_plugin_manager->getDefinitions() as $name => $processor_definition) { + if (class_exists($processor_definition['class']) && empty($this->processors[$name])) { + // Create our settings for this processor. + $settings = empty($processor_settings[$name]['settings']) ? [] : $processor_settings[$name]['settings']; + $settings['facet'] = $this; + + /* @var $processor \Drupal\facets\Processor\ProcessorInterface */ + $processor = $processor_plugin_manager->createInstance($name, $settings); + $this->processors[$name] = $processor; + } + elseif (!class_exists($processor_definition['class'])) { + \Drupal::logger('facets') + ->warning('Processor @id specifies a non-existing @class.', array( + '@id' => $name, + '@class' => $processor_definition['class'] + )); + } + } + } + + return $this->processors; + } /** * {@inheritdoc} */ public function getQueryType() { @@ -268,6 +316,13 @@ class Facet extends ConfigEntityBase implements FacetInterface { /** * {@inheritdoc} */ + public function getQueryOperator() { + return $this->getOption('query_operator', 'OR'); + } + + /** + * {@inheritdoc} + */ public function getFieldAlias() { // For now, create the field alias based on the field identifier. $field_alias = preg_replace('/[:\/]+/', '_', $this->field_identifier); @@ -335,12 +390,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { return $this; } - /** - * {@inheritdoc} - */ - public function getQueryTypes() { - return $this->query_type_name; - } + /** * {@inheritdoc} @@ -434,44 +484,9 @@ class Facet extends ConfigEntityBase implements FacetInterface { return $this->facetSourceConfig; } - /** - * Retrieves all processors supported by this facet. - * - * @return \Drupal\facets\Processor\ProcessorInterface[] - * The loaded processors, keyed by processor ID. - */ - protected function loadProcessors() { - if (!isset($this->processors)) { - /* @var $processor_plugin_manager \Drupal\facets\Processor\ProcessorPluginManager */ - $processor_plugin_manager = \Drupal::service('plugin.manager.facets.processor'); - $processor_settings = $this->getOption('processors', []); - - foreach ($processor_plugin_manager->getDefinitions() as $name => $processor_definition) { - if (class_exists($processor_definition['class']) && empty($this->processors[$name])) { - // Create our settings for this processor. - $settings = empty($processor_settings[$name]['settings']) ? [] : $processor_settings[$name]['settings']; - $settings['facet'] = $this; - /* @var $processor \Drupal\facets\Processor\ProcessorInterface */ - $processor = $processor_plugin_manager->createInstance($name, $settings); - $this->processors[$name] = $processor; - } - elseif (!class_exists($processor_definition['class'])) { - \Drupal::logger('facets')->warning('Processor @id specifies a non-existing @class.', array('@id' => $name, '@class' => $processor_definition['class'])); - } - } - } - return $this->processors; - } - /** - * {@inheritdoc} - */ - protected function urlRouteParameters($rel) { - $parameters = parent::urlRouteParameters($rel); - return $parameters; - } /** * {@inheritdoc} @@ -527,7 +542,11 @@ class Facet extends ConfigEntityBase implements FacetInterface { $this->facetSourcePlugins[$name] = $facet_source; } elseif (!class_exists($facet_source_definition['class'])) { - \Drupal::logger('facets')->warning('Facet Source @id specifies a non-existing @class.', ['@id' => $name, '@class' => $facet_source_definition['class']]); + \Drupal::logger('facets') + ->warning('Facet Source @id specifies a non-existing @class.', [ + '@id' => $name, + '@class' => $facet_source_definition['class'] + ]); } } } diff --git a/src/FacetInterface.php b/src/FacetInterface.php index c01c6ba..48c2612 100644 --- a/src/FacetInterface.php +++ b/src/FacetInterface.php @@ -136,6 +136,14 @@ interface FacetInterface extends ConfigEntityInterface { public function getQueryType(); /** + * Get the query operator. + * + * @return string + * The query operator being used. + */ + public function getQueryOperator(); + + /** * Get the plugin name for the url processor. * * @return string diff --git a/src/Form/FacetDisplayForm.php b/src/Form/FacetDisplayForm.php index e096de0..f582b20 100644 --- a/src/Form/FacetDisplayForm.php +++ b/src/Form/FacetDisplayForm.php @@ -353,6 +353,15 @@ class FacetDisplayForm extends EntityForm { '#default_value' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text'] : '', ]; + // Query operator. + $form['facet_settings']['query_operator'] = [ + '#type' => 'radios', + '#title' => $this->t('Operator'), + '#options' => ['OR' => $this->t('OR'), 'AND' => $this->t('AND')], + '#description' => $this->t('AND filters are exclusive and narrow the result set. OR filters are inclusive and widen the result set.'), + '#default_value' => $facet->getQueryOperator(), + ]; + $form['weights'] = array( '#type' => 'details', '#title' => t('Advanced settings'), @@ -530,6 +539,8 @@ class FacetDisplayForm extends EntityForm { } $facet->setOption('empty_behavior', $empty_behavior_config); + $facet->setOption('query_operator', $form_state->getValue(['facet_settings', 'query_operator'])); + $facet->save(); drupal_set_message(t('Facet %name has been updated.', ['%name' => $facet->getName()])); } diff --git a/src/Plugin/facets/query_type/SearchApiString.php b/src/Plugin/facets/query_type/SearchApiString.php index c44d0bb..7796041 100644 --- a/src/Plugin/facets/query_type/SearchApiString.php +++ b/src/Plugin/facets/query_type/SearchApiString.php @@ -44,6 +44,7 @@ class SearchApiString extends QueryTypePluginBase { // Alter the query here. if (!empty($query)) { $options = &$query->getOptions(); + $operator = $this->facet->getQueryOperator(); $field_identifier = $this->facet->getFieldIdentifier(); $options['search_api_facets'][$field_identifier] = array( @@ -56,12 +57,13 @@ class SearchApiString extends QueryTypePluginBase { // Add the filter to the query if there are active values. $active_items = $this->facet->getActiveItems(); + if (count($active_items)) { + $filter = $query->createConditionGroup($operator); foreach ($active_items as $value) { - $filter = $query->createConditionGroup(); $filter->addCondition($this->facet->getFieldIdentifier(), $value); - $query->addConditionGroup($filter); } + $query->addConditionGroup($filter); } } } @@ -73,7 +75,7 @@ class SearchApiString extends QueryTypePluginBase { if (!empty($this->results)) { $facet_results = array(); foreach ($this->results as $result) { - if ($result['count']) { + if ($result['count'] || $this->facet->getQueryOperator() == 'OR') { $facet_results[] = new Result(trim($result['filter'], '"'), trim($result['filter'], '"'), $result['count']); } } diff --git a/src/Plugin/facets/widget/CheckboxWidget.php b/src/Plugin/facets/widget/CheckboxWidget.php index 6d8bf49..2ca872b 100644 --- a/src/Plugin/facets/widget/CheckboxWidget.php +++ b/src/Plugin/facets/widget/CheckboxWidget.php @@ -52,18 +52,16 @@ class CheckboxWidget implements WidgetInterface { $show_numbers = (bool) $configuration['show_numbers']; foreach ($results as $result) { - if ($result->getCount()) { - // Get the link. - $text = $result->getDisplayValue(); - if ($show_numbers) { - $text .= ' (' . $result->getCount() . ')'; - } - if ($result->isActive()) { - $text = '(-) ' . $text; - } - $link = $this->linkGenerator()->generate($text, $result->getUrl()); - $items[] = $link; + // Get the link. + $text = $result->getDisplayValue(); + if ($show_numbers) { + $text .= ' (' . $result->getCount() . ')'; } + if ($result->isActive()) { + $text = '(-) ' . $text; + } + $link = $this->linkGenerator()->generate($text, $result->getUrl()); + $items[] = $link; } $build = [ '#theme' => 'item_list', diff --git a/src/Plugin/facets/widget/LinksWidget.php b/src/Plugin/facets/widget/LinksWidget.php index 196cd5e..88e1a15 100644 --- a/src/Plugin/facets/widget/LinksWidget.php +++ b/src/Plugin/facets/widget/LinksWidget.php @@ -52,22 +52,20 @@ class LinksWidget implements WidgetInterface { $show_numbers = (bool) $configuration['show_numbers']; foreach ($results as $result) { - if ($result->getCount()) { - // Get the link. - $text = $result->getDisplayValue(); - if ($show_numbers) { - $text .= ' (' . $result->getCount() . ')'; - } - if ($result->isActive()) { - $text = '(-) ' . $text; - } - - if (is_null($result->getUrl())) { - $items[] = $text; - } - else { - $items[] = $this->linkGenerator()->generate($text, $result->getUrl()); - } + // Get the link. + $text = $result->getDisplayValue(); + if ($show_numbers) { + $text .= ' (' . $result->getCount() . ')'; + } + if ($result->isActive()) { + $text = '(-) ' . $text; + } + + if (is_null($result->getUrl())) { + $items[] = $text; + } + else { + $items[] = $this->linkGenerator()->generate($text, $result->getUrl()); } } diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php index dc8c55a..335da54 100644 --- a/src/Tests/IntegrationTest.php +++ b/src/Tests/IntegrationTest.php @@ -196,6 +196,43 @@ class IntegrationTest extends FacetWebTestBase { } /** + * Tests the facet's and/or functionality. + */ + public function testAndOrFacet() { + $facet_name = 'test & facet'; + $facet_id = 'test_facet'; + $facet_edit_page = 'admin/config/search/facets/' . $facet_id . '/display'; + + $this->drupalLogin($this->adminUser); + $this->addFacet($facet_name); + $this->createFacetBlock('test_facet'); + + $this->drupalGet($facet_edit_page); + $this->drupalPostForm(NULL, ['facet_settings[query_operator]' => 'AND'], $this->t('Save')); + + $this->insertExampleContent(); + $this->assertEqual($this->indexItems($this->indexId), 5, '5 items were indexed.'); + + $this->drupalGet('search-api-test-fulltext'); + $this->assertLink('item'); + $this->assertLink('article'); + + $this->clickLink('item'); + $this->assertLink('(-) item'); + $this->assertNoLink('article'); + + $this->drupalGet($facet_edit_page); + $this->drupalPostForm(NULL, ['facet_settings[query_operator]' => 'OR'], $this->t('Save')); + $this->drupalGet('search-api-test-fulltext'); + $this->assertLink('item'); + $this->assertLink('article'); + + $this->clickLink('item'); + $this->assertLink('(-) item'); + $this->assertLink('article'); + } + + /** * Deletes a facet block by id. * * @param string $id