diff --git a/src/Entity/Facet.php b/src/Entity/Facet.php index 6e3de73..e174402 100644 --- a/src/Entity/Facet.php +++ b/src/Entity/Facet.php @@ -162,6 +162,13 @@ class Facet extends ConfigEntityBase implements FacetInterface { */ protected $results = []; + /** + * The results. + * + * @var \Drupal\facets\Result\ResultInterface[] + */ + protected $unfiltered_results = []; + protected $active_values = []; /** @@ -255,6 +262,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; } @@ -270,11 +285,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() { @@ -292,6 +347,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); @@ -359,12 +421,7 @@ class Facet extends ConfigEntityBase implements FacetInterface { return $this; } - /** - * {@inheritdoc} - */ - public function getQueryTypes() { - return $this->query_type_name; - } + /** * {@inheritdoc} @@ -459,44 +516,6 @@ class Facet extends ConfigEntityBase implements FacetInterface { } /** - * 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'); - - foreach ($processor_plugin_manager->getDefinitions() as $processor_id => $processor_definition) { - if (class_exists($processor_definition['class']) && empty($this->processors[$processor_id])) { - $settings = empty($this->processor_configs[$processor_id]['settings']) ? [] : $this->processor_configs[$processor_id]['settings']; - $settings['enabled'] = empty($this->processor_configs[$processor_id]) ? FALSE : TRUE; - $settings['facet'] = $this; - - /* @var $processor \Drupal\facets\Processor\ProcessorInterface */ - $processor = $processor_plugin_manager->createInstance($processor_id, $settings); - $this->processors[$processor_id] = $processor; - } - elseif (!class_exists($processor_definition['class'])) { - \Drupal::logger('facets')->warning('Processor @id specifies a non-existing @class.', array('@id' => $processor_id, '@class' => $processor_definition['class'])); - } - } - } - - return $this->processors; - } - - /** - * {@inheritdoc} - */ - protected function urlRouteParameters($rel) { - $parameters = parent::urlRouteParameters($rel); - return $parameters; - } - - /** * {@inheritdoc} */ public function getResults() { @@ -520,6 +539,17 @@ class Facet extends ConfigEntityBase implements FacetInterface { } /** + * @param array $all_results + */ + public function setUnfilteredResults(array $all_results = []) { + $this->unfiltered_results = $all_results; + } + + public function getUnfilteredResults() { + return $this->unfiltered_results; + } + + /** * {@inheritdoc} */ public function isActiveValue($value) { @@ -550,7 +580,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 0f13cbe..580439c 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/FacetManager/DefaultFacetManager.php b/src/FacetManager/DefaultFacetManager.php index 688f9ad..c033c4c 100644 --- a/src/FacetManager/DefaultFacetManager.php +++ b/src/FacetManager/DefaultFacetManager.php @@ -159,8 +159,13 @@ class DefaultFacetManager { // Make sure we don't alter queries for facets with a different source. if ($facet->getFacetSourceId() == $this->facetSourceId) { /** @var \Drupal\facets\QueryType\QueryTypeInterface $query_type_plugin */ - $query_type_plugin = $this->queryTypePluginManager->createInstance($facet->getQueryType(), ['query' => $query, 'facet' => $facet]); - $query_type_plugin->execute(); + $query_type_plugin = $this->queryTypePluginManager + ->createInstance($facet->getQueryType(), ['query' => $query, 'facet' => $facet]); + $unfiltered_results = $query_type_plugin->execute(); + + // Save unfiltered results in facet. + $facet->setUnfilteredResults($unfiltered_results); + } } } diff --git a/src/Form/FacetDisplayForm.php b/src/Form/FacetDisplayForm.php index 56b5cda..6315e77 100644 --- a/src/Form/FacetDisplayForm.php +++ b/src/Form/FacetDisplayForm.php @@ -352,6 +352,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'), @@ -527,6 +536,8 @@ class FacetDisplayForm extends EntityForm { } $facet->setEmptyBehavior($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..e44b037 100644 --- a/src/Plugin/facets/query_type/SearchApiString.php +++ b/src/Plugin/facets/query_type/SearchApiString.php @@ -41,11 +41,26 @@ class SearchApiString extends QueryTypePluginBase { public function execute() { $query = $this->query; + $unfiltered_results = array(); + // Alter the query here. if (!empty($query)) { + $operator = $this->facet->getQueryOperator(); + $field_identifier = $this->facet->getFieldIdentifier(); + + $query2 = $query; + $options2 = &$query2->getOptions(); + $options2['search_api_facets'][$field_identifier] = array( + 'field' => $field_identifier, + 'limit' => 50, + 'operator' => 'and', + 'min_count' => 0, + 'missing' => FALSE, + ); + $unfiltered_results = $query2->execute()->getExtraData('search_api_facets'); + $options = &$query->getOptions(); - $field_identifier = $this->facet->getFieldIdentifier(); $options['search_api_facets'][$field_identifier] = array( 'field' => $field_identifier, 'limit' => 50, @@ -56,25 +71,36 @@ 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); } } + + return $unfiltered_results; } /** * {@inheritdoc} */ public function build() { + $query_operator = $this->facet->getQueryOperator(); + if (!empty($this->results)) { $facet_results = array(); - foreach ($this->results as $result) { - if ($result['count']) { - $facet_results[] = new Result(trim($result['filter'], '"'), trim($result['filter'], '"'), $result['count']); + foreach ($this->results as $key => $result) { + if ($result['count'] || $query_operator == 'OR') { + $count = $result['count']; + if ($query_operator === 'OR') { + $count = $this->facet->getUnfilteredResults()[$this->facet->getFieldIdentifier()][$key]['count']; + } + + $result = new Result(trim($result['filter'], '"'), trim($result['filter'], '"'), $count); + $facet_results[] = $result; } } $this->facet->setResults($facet_results); diff --git a/src/Plugin/facets/widget/LinksWidget.php b/src/Plugin/facets/widget/LinksWidget.php index c52663a..82b9f7d 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/QueryType/QueryTypeInterface.php b/src/QueryType/QueryTypeInterface.php index e4a9ed3..d43e3ac 100644 --- a/src/QueryType/QueryTypeInterface.php +++ b/src/QueryType/QueryTypeInterface.php @@ -13,6 +13,9 @@ interface QueryTypeInterface { /** * Add facet info to the query using the backend native query object. + * + * @return array + * Returns an array of unfiltered results */ public function execute(); diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php index 5fc2608..f842673 100644 --- a/src/Tests/IntegrationTest.php +++ b/src/Tests/IntegrationTest.php @@ -201,6 +201,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 diff --git a/src/Tests/UrlIntegrationTest.php b/src/Tests/UrlIntegrationTest.php index bfb7614..517e603 100644 --- a/src/Tests/UrlIntegrationTest.php +++ b/src/Tests/UrlIntegrationTest.php @@ -93,7 +93,7 @@ class UrlIntegrationTest extends FacetWebTestBase { $this->assertResponse(200); $this->assertLink('(-) item'); - $this->assertNoLink('article'); + $this->assertLink('article'); $this->assertUrl('search-api-test-fulltext', ['query' => ['y[0]' => 'facet||item']]); } diff --git a/tests/src/Unit/Plugin/widget/LinksWidgetTest.php b/tests/src/Unit/Plugin/widget/LinksWidgetTest.php index 6d737ff..e21cb40 100644 --- a/tests/src/Unit/Plugin/widget/LinksWidgetTest.php +++ b/tests/src/Unit/Plugin/widget/LinksWidgetTest.php @@ -89,28 +89,6 @@ class LinksWidgetTest extends UnitTestCase { /** * Test widget. */ - public function testHideEmptyCount() { - $original_results = $this->originalResults; - $original_results[1] = new Result('badger', 'Badger', 0); - - $facet = new Facet([], 'facet'); - $facet->setResults($original_results); - $facet->setWidgetConfigs(['show_numbers' => 1]); - - $output = $this->widget->build($facet); - - $this->assertInternalType('array', $output); - $this->assertCount(3, $output['#items']); - - $expected_links = ['Llama (10)', 'Duck (15)', 'Alpaca (9)']; - foreach ($expected_links as $index => $value) { - $this->assertEquals($value, $output['#items'][$index]); - } - } - - /** - * Test widget. - */ public function testActiveItems() { $original_results = $this->originalResults; $original_results[0]->setActiveState(TRUE);