Commit 3a44b3d8 authored by borisson_'s avatar borisson_ Committed by borisson_

Issue #2625186 by borisson_, michiellucas, StryKaizer: Support and/or for facets

parent 3ac6b333
......@@ -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 = [];
/**
......@@ -252,6 +259,14 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this->id;
}
/**
* {@inheritdoc}
*/
protected function urlRouteParameters($rel) {
$parameters = parent::urlRouteParameters($rel);
return $parameters;
}
/**
* {@inheritdoc}
*/
......@@ -267,6 +282,13 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this;
}
/**
* {@inheritdoc}
*/
public function getQueryTypes() {
return $this->query_type_name;
}
/**
* {@inheritdoc}
*/
......@@ -275,6 +297,39 @@ 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');
$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() {
......@@ -289,6 +344,13 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $widget->getQueryType($query_types);
}
/**
* {@inheritdoc}
*/
public function getQueryOperator() {
return $this->getOption('query_operator', 'OR');
}
/**
* {@inheritdoc}
*/
......@@ -359,12 +421,7 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this;
}
/**
* {@inheritdoc}
*/
public function getQueryTypes() {
return $this->query_type_name;
}
/**
* {@inheritdoc}
......@@ -458,44 +515,6 @@ 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');
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}
*/
......@@ -519,6 +538,20 @@ class Facet extends ConfigEntityBase implements FacetInterface {
}
}
/**
* {@inheritdoc}
*/
public function setUnfilteredResults(array $all_results = []) {
$this->unfiltered_results = $all_results;
}
/**
* {@inheritdoc}
*/
public function getUnfilteredResults() {
return $this->unfiltered_results;
}
/**
* {@inheritdoc}
*/
......@@ -550,7 +583,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']
]);
}
}
}
......
......@@ -126,6 +126,24 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function setResults(array $results);
/**
* Sets an array of unfiltered results.
*
* These unfiltered results are used to set the correct count of the actual
* facet results when using the OR query operator. They are not results value
* objects like those in ::$results.
*
* @param array
* Unfiltered results.
*/
public function setUnfilteredResults(array $all_results = []);
/**
* Gets an array of unfiltered results.
*
* @return array
*/
public function getUnfilteredResults();
/**
* Get the query type instance.
......@@ -135,6 +153,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.
*
......
......@@ -159,8 +159,14 @@ 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.
if (!is_null($unfiltered_results)) {
$facet->setUnfilteredResults($unfiltered_results);
}
}
}
}
......
......@@ -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()]));
}
......
......@@ -41,11 +41,33 @@ class SearchApiString extends QueryTypePluginBase {
public function execute() {
$query = $this->query;
// Alter the query here.
if (!empty($query)) {
$options = &$query->getOptions();
$unfiltered_results = [];
// Only alter the query when there's an actual query object to alter.
if (!empty($query)) {
$operator = $this->facet->getQueryOperator();
$field_identifier = $this->facet->getFieldIdentifier();
// Copy the query object so we can do an unfiltered query. We need to have
// this unfiltered results to make sure that the count of a facet is
// correct. The unfiltered results get returned to the facet manager, the
// facet manager will save it on facet::unfiltered_results.
$unfiltered_query = $query;
$unfiltered_options = &$unfiltered_query->getOptions();
$unfiltered_options['search_api_facets'][$field_identifier] = array(
'field' => $field_identifier,
'limit' => 50,
'operator' => 'and',
'min_count' => 0,
'missing' => FALSE,
);
$unfiltered_results = $unfiltered_query
->execute()
->getExtraData('search_api_facets');
// Set the options for the actual query.
$options = &$query->getOptions();
$options['search_api_facets'][$field_identifier] = array(
'field' => $field_identifier,
'limit' => 50,
......@@ -56,25 +78,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);
......
......@@ -45,22 +45,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[] = new Link($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[] = new Link($text, $result->getUrl());
}
}
......
......@@ -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();
......
......@@ -289,6 +289,43 @@ class IntegrationTest extends FacetWebTestBase {
$this->assertNoLink('orange');
}
/**
* 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.
*
......
......@@ -152,7 +152,7 @@ class UrlIntegrationTest extends FacetWebTestBase {
$this->assertResponse(200);
$this->assertLink('(-) item');
$this->assertNoLink('article');
$this->assertLink('article');
$this->assertUrl($url);
}
......
......@@ -79,4 +79,98 @@ class WidgetIntegrationTest extends FacetWebTestBase {
$this->assertFieldChecked('edit-type-item');
}
/**
* Test links widget's basic functionality.
*/
public function testLinksWidget() {
$id = 'links_widget';
$name = '>.Facet &* Links';
$facet_add_page = 'admin/config/search/facets/add-facet';
$this->drupalGet($facet_add_page);
$form_values = [
'id' => $id,
'status' => 1,
'url_alias' => $id,
'name' => $name,
'facet_source_id' => 'search_api_views:search_api_test_view:page_1',
'facet_source_configs[search_api_views:search_api_test_view:page_1][field_identifier]' => 'type',
];
$this->drupalPostForm(NULL, ['facet_source_id' => 'search_api_views:search_api_test_view:page_1'], $this->t('Configure facet source'));
$this->drupalPostForm(NULL, $form_values, $this->t('Save'));
$this->drupalPostForm(NULL, ['widget' => 'links'], $this->t('Save'));
$block_values = [
'plugin_id' => 'facet_block:' . $id,
'settings' => [
'region' => 'footer',
'id' => str_replace('_', '-', $id),
]
];
$this->drupalPlaceBlock($block_values['plugin_id'], $block_values['settings']);
$this->drupalGet('search-api-test-fulltext');
$this->assertLink('item');
$this->assertLink('article');
$this->clickLink('item');
$this->assertLink('(-) item');
}
/**
* Tests the functionality of a widget to hide/show the item-count.
*/
public function testLinksShowHideCount() {
$id = 'links_widget';
$name = '>.Facet &* Links';
$facet_add_page = 'admin/config/search/facets/add-facet';
$facet_edit_page = 'admin/config/search/facets/' . $id . '/display';
$this->drupalGet($facet_add_page);
$form_values = [
'id' => $id,
'status' => 1,
'url_alias' => $id,
'name' => $name,
'facet_source_id' => 'search_api_views:search_api_test_view:page_1',
'facet_source_configs[search_api_views:search_api_test_view:page_1][field_identifier]' => 'type',
];
$this->drupalPostForm(NULL, ['facet_source_id' => 'search_api_views:search_api_test_view:page_1'], $this->t('Configure facet source'));
$this->drupalPostForm(NULL, $form_values, $this->t('Save'));
$this->drupalPostForm(NULL, ['widget' => 'links'], $this->t('Save'));
$block_values = [
'plugin_id' => 'facet_block:' . $id,
'settings' => [
'region' => 'footer',
'id' => str_replace('_', '-', $id),
]
];
$this->drupalPlaceBlock($block_values['plugin_id'], $block_values['settings']);
// Go to the view and check that the facet links are shown with their
// default settings.
$this->drupalGet('search-api-test-fulltext');
$this->assertLink('item');
$this->assertLink('article');
$this->drupalGet($facet_edit_page);
$this->drupalPostForm(NULL, ['widget' => 'links', 'widget_configs[show_numbers]' => TRUE], $this->t('Save'));
// Go back to the same view and check that links now display the count
$this->drupalGet('search-api-test-fulltext');
$this->assertLink('item (3)');
$this->assertLink('article (2)');
$this->drupalGet($facet_edit_page);
$this->drupalPostForm(NULL, ['widget' => 'links', 'widget_configs[show_numbers]' => FALSE], $this->t('Save'));
// The count should be hidden again.
$this->drupalGet('search-api-test-fulltext');
$this->assertLink('item');
$this->assertLink('article');
}
}
......@@ -24,7 +24,10 @@ class SearchApiStringTest extends UnitTestCase {
*/
public function testQueryType() {
$query = new SearchApiQuery([], 'search_api_query', []);
$facet = new Facet([], 'facets_facet');
$facet = new Facet(
['options' => ['query_operator' => 'AND']],
'facets_facet'
);
$original_results = [
['count' => 3, 'filter' => 'badger'],
......
......@@ -76,29 +76,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->assertInstanceOf('\Drupal\Core\Link', $output['#items'][$index]);
$this->assertEquals($value, $output['#items'][$index]->getText());
}
}
/**
* Test widget.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment