Skip to content
Commits on Source (51)
......@@ -4,9 +4,7 @@ CONTENTS OF THIS FILE
* Requirements
* Installation
* Configuration
* Troubleshooting
* FAQ
* Maintainers
INTRODUCTION
------------
......@@ -14,8 +12,9 @@ Todo
REQUIREMENTS
------------
No other modules required, though the module is useless without an implementing
module like search api.
No other modules required, we're supporting drupal core as a source for creating
facets. Though we recommend using Search API, as that integration is better
tested.
INSTALLATION
------------
......@@ -25,17 +24,14 @@ INSTALLATION
CONFIGURATION
-------------
Todo
Before adding a facet, there should be a facet source. Facet sources can be:
- Drupal core's search.
- A view based on a Search API index with a page display.
- A page from the search_api_page module.
TROUBLESHOOTING
---------------
Todo
After adding one of those, you can add a facet on the facets configuration page:
/admin/config/search/facets
FAQ
---
Todo
MAINTAINERS
-----------
Current maintainers:
* Todo
......@@ -26,9 +26,28 @@ facets.facet.*:
query_type_name:
type: string
label: 'Query Type Name'
query_operator:
type: string
label: 'Query Operator'
exclude:
type: boolean
label: 'Exclude'
widget:
type: string
label: 'Field identifier'
label: 'Widget identifier'
empty_behavior:
type: mapping
label: 'Empty behavior'
mapping:
behavior:
type: string
label: 'The empty behavior identifier'
text_format:
type: string
label: 'Text format'
text:
type: string
label: 'Text'
widget_configs:
type: sequence
label: 'Widget plugin configurations'
......@@ -56,3 +75,17 @@ facets.facet.*:
label: 'The processor''s weight for this stage'
settings:
type: plugin.plugin_configuration.facets_processor.[%parent.processor_id]
facet_configs:
type: sequence
label: 'Facet plugin-specific options'
sequence:
type: plugin.plugin_configuration.facets_facet_options.[%key]
label: 'Facet plugin options'
condition.plugin.other_facet:
type: condition.plugin
mapping:
facet_value:
type: string
facets:
type: string
......@@ -11,6 +11,9 @@ facets.facet_source.*:
name:
type: label
label: Name'
filterKey:
filter_key:
type: string
label: 'Filter key'
url_processor:
type: string
label: 'Url processor'
......@@ -40,3 +40,9 @@ plugin.plugin_configuration.facets_processor.count_limit:
maximum_items:
type: integer
label: 'Maximum amount of items to show.'
plugin.plugin_configuration.facets_processor.url_processor_handler:
type: config_object
plugin.plugin_configuration.facets_processor.hide_non_narrowing_result_processor:
type: config_object
<?php
/**
* @file
* Hooks provided by the core_search_facets module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Adds field types as possible options for facets.
*
* @param array $allowed_field_types
* The field types.
*
* @return array
* Array that contains the field types.
*/
function hook_facets_core_allowed_field_types(array $allowed_field_types) {
$allowed_field_types[] = 'float';
return $allowed_field_types;
}
/**
* @} End of "addtogroup hooks".
*/
......@@ -45,3 +45,13 @@ function core_search_facets_search_plugin_alter(array &$definitions) {
//$definitions['node_search']['class'] = 'Drupal\core_search_facets\Plugin\Search\NodeSearchFacets';
}
}
/**
* Implements hook_facets_core_allowed_field_types().
*/
function core_search_facets_facets_core_allowed_field_types(array $allowed_field_types) {
$allowed_field_types[] = 'taxonomy_term';
$allowed_field_types[] = 'integer';
return $allowed_field_types;
}
......@@ -80,8 +80,8 @@ class FacetsQuery extends SearchQuery {
// For complex search queries, add the LIKE conditions.
/*if (!$this->simple) {
$this->join('search_dataset', 'd', 'i.sid = d.sid AND i.type = d.type');
$this->condition($this->conditions);
$this->join('search_dataset', 'd', 'i.sid = d.sid AND i.type = d.type');
$this->condition($this->conditions);
}*/
// Add conditions to query.
......
......@@ -9,15 +9,17 @@ namespace Drupal\core_search_facets\Plugin\Search;
use Drupal\Core\Config\Config;
use Drupal\Core\Database\Driver\mysql\Connection;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\facets\FacetSource\FacetSourcePluginManager;
use Drupal\node\Plugin\Search\NodeSearch;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Handles searching for node entities using the Search module index.
......@@ -34,13 +36,13 @@ class NodeSearchFacets extends NodeSearch {
$plugin_id,
$plugin_definition,
Connection $database,
EntityManagerInterface $entity_manager,
EntityTypeManagerInterface $entity_manager,
ModuleHandlerInterface $module_handler,
Config $search_settings,
LanguageManagerInterface $language_manager,
RendererInterface $renderer,
$facet_source_plugin_manager,
$request_stack,
FacetSourcePluginManager $facet_source_plugin_manager,
RequestStack $request_stack,
AccountInterface $account = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $database, $entity_manager, $module_handler, $search_settings, $language_manager, $renderer, $account);
......@@ -60,7 +62,7 @@ class NodeSearchFacets extends NodeSearch {
$plugin_id,
$plugin_definition,
$container->get('database'),
$container->get('entity.manager'),
$container->get('entity_type.manager'),
$container->get('module_handler'),
$container->get('config.factory')->get('search.settings'),
$container->get('language_manager'),
......@@ -96,6 +98,7 @@ class NodeSearchFacets extends NodeSearch {
'#access' => $this->account && $this->account->hasPermission('use advanced search'),
'#open' => $used_advanced,
);
$form['advanced']['keywords-fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('Keywords'),
......
......@@ -13,10 +13,11 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface;
use Drupal\facets\FacetInterface;
use Drupal\facets\FacetSource\FacetSourcePluginBase;
use Drupal\facets\FacetSource\FacetSourcePluginInterface;
use Drupal\facets\QueryType\QueryTypePluginManager;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\search\SearchPageInterface;
use Drupal\search\SearchPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
......@@ -54,6 +55,11 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
*/
protected $configFactory;
/**
* The plugin manager for core search plugins.
*
* @var \Drupal\search\SearchPluginManager
*/
protected $searchManager;
/**
......@@ -64,7 +70,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, $query_type_plugin_manager, $search_manager, RequestStack $request_stack) {
public function __construct(array $configuration, $plugin_id, array $plugin_definition, QueryTypePluginManager $query_type_plugin_manager, SearchPluginManager $search_manager, RequestStack $request_stack) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $query_type_plugin_manager);
$this->searchManager = $search_manager;
$this->setSearchKeys($request_stack->getMasterRequest()->query->get('keys'));
......@@ -112,7 +118,6 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
// Get the Facet Specific Query Type so we can process the results
// using the build() function of the query type.
/** @var \Drupal\facets\Entity\Facet $facet **/
$query_type = $this->queryTypePluginManager->createInstance($facet->getQueryType(), $configuration);
$query_type->build();
}
......@@ -143,12 +148,13 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
* @return array
* An array of query types.
*/
public function getQueryTypesForFieldType($field_type) {
protected function getQueryTypesForFieldType($field_type) {
$query_types = [];
switch ($field_type) {
case 'type':
case 'uid':
case 'langcode':
case 'integer':
case 'entity_reference':
$query_types['string'] = 'core_node_search_string';
break;
......@@ -161,7 +167,6 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
* {@inheritdoc}
*/
public function isRenderedInCurrentRequest() {
// @TODO Avoid the use of \Duupal so maybe inject?
$request = \Drupal::requestStack()->getMasterRequest();
$search_page = $request->attributes->get('entity');
if ($search_page instanceof SearchPageInterface) {
......@@ -178,7 +183,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourcePluginInterface $facet_source) {
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['field_identifier'] = [
'#type' => 'select',
......@@ -186,7 +191,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
'#title' => $this->t('Facet field'),
'#description' => $this->t('Choose the indexed field.'),
'#required' => TRUE,
'#default_value' => $facet->getFieldIdentifier(),
'#default_value' => $this->facet->getFieldIdentifier(),
];
return $form;
......@@ -198,11 +203,19 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
public function getFields() {
// Default fields.
$facet_fields = $this->getDefaultFields();
// @TODO Only taxonomy term reference for the moment.
// Get the allowed field types.
$allowed_field_types = \Drupal::moduleHandler()->invokeAll('facets_core_allowed_field_types', array($field_types = []));
// Get the current field instances and detect if the field type is allowed.
$fields = FieldConfig::loadMultiple();
/** @var \Drupal\Field\FieldConfigInterface $field */
foreach ($fields as $field) {
if ($field->getFieldStorageDefinition()->getSetting('target_type') == 'taxonomy_term') {
// Verify if the target type is allowed for entity reference fields,
// otherwise verify the field type(i.e. integer, float...).
$target_is_allowed = in_array($field->getFieldStorageDefinition()->getSetting('target_type'), $allowed_field_types);
$field_is_allowed = in_array($field->getFieldStorageDefinition()->getType(), $allowed_field_types);
if ($target_is_allowed || $field_is_allowed) {
/** @var \Drupal\field\Entity\FieldConfig $field */
if (!array_key_exists($field->getName(), $facet_fields)) {
$facet_fields[$field->getName()] = $this->t('@label', ['@label' => $field->getLabel()]);
......@@ -217,6 +230,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
* Getter for default node fields.
*
* @return array
* An array containing the default fields enabled on a node.
*/
protected function getDefaultFields() {
return [
......@@ -230,14 +244,14 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
* {@inheritdoc}
*/
public function getFacetQueryExtender() {
if (!$this->facetQueryExtender) {
$this->facetQueryExtender = db_select('search_index', 'i', array('target' => 'replica'))->extend('Drupal\core_search_facets\FacetsQuery');
$this->facetQueryExtender->join('node_field_data', 'n', 'n.nid = i.sid');
$this->facetQueryExtender
if (!$this->facetQueryExtender) {
$this->facetQueryExtender = db_select('search_index', 'i', array('target' => 'replica'))->extend('Drupal\core_search_facets\FacetsQuery');
$this->facetQueryExtender->join('node_field_data', 'n', 'n.nid = i.sid');
$this->facetQueryExtender
// ->condition('n.status', 1).
->addTag('node_access')
->searchExpression($this->keys, 'node_search');
}
}
return $this->facetQueryExtender;
}
......@@ -249,10 +263,10 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
$field_name = $facet->getFieldIdentifier();
$default_fields = $this->getDefaultFields();
if (array_key_exists($facet->getFieldIdentifier(), $default_fields)) {
// We add the language code of the indexed item to the result of the query.
// So in this case we need to use the search_index table alias (i) for the
// langcode field. Otherwise we will have same nid for multiple languages
// as result. For more details see NodeSearch::findResults().
// We add the language code of the indexed item to the result of the
// query. So in this case we need to use the search_index table alias (i)
// for the langcode field. Otherwise we will have same nid for multiple
// languages as result. For more details see NodeSearch::findResults().
// @TODO review if I can refactor this.
$table_alias = $facet->getFieldIdentifier() == 'langcode' ? 'i' : 'n';
$query_info = [
......@@ -267,7 +281,18 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
else {
// Gets field info, finds table name and field name.
$table = "node__{$field_name}";
$column = $facet->getFieldIdentifier() . '_target_id';
// The column name will be different depending on the field type, it's
// always the fields machine name, suffixed with '_value'. Entity
// reference fields change that suffix into '_target_id'.
$field_config = FieldStorageConfig::loadByName('node', $facet->getFieldIdentifier());
$field_type = $field_config->getType();
if ($field_type == 'entity_reference') {
$column = $facet->getFieldIdentifier() . '_target_id';
}
else {
$column = $facet->getFieldIdentifier() . '_value';
}
$query_info['fields'][$field_name . '.' . $column] = array(
'table_alias' => $table,
'field' => $column,
......
......@@ -8,7 +8,9 @@
namespace Drupal\core_search_facets\Plugin\facets\facet_source;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\facets\FacetSource\FacetSourceDeriverBase;
use Drupal\search\SearchPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -18,12 +20,24 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class CoreNodeSearchFacetSourceDeriver extends FacetSourceDeriverBase {
/**
* The plugin manager for core search plugins.
*
* @var \Drupal\search\SearchPluginManager
*/
protected $searchManager;
/**
* Create an instance of the deriver.
* Creates an instance of the deriver.
*
* @param string $base_plugin_id
* The plugin ID.
* @param \Drupal\search\SearchPluginManager $search_manager
* The plugin manager for core search plugins.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity manager.
*/
public function __construct(ContainerInterface $container, $base_plugin_id, $search_manager, $entity_type_manager) {
public function __construct($base_plugin_id, SearchPluginManager $search_manager, EntityTypeManagerInterface $entity_type_manager) {
$this->searchManager = $search_manager;
$this->entityTypeManager = $entity_type_manager;
}
......@@ -33,11 +47,10 @@ class CoreNodeSearchFacetSourceDeriver extends FacetSourceDeriverBase {
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container,
$base_plugin_id,
$container->get('plugin.manager.search'),
$container->get('entity_type.manager')
);
);
}
/**
......
......@@ -21,7 +21,7 @@ use Drupal\facets\Result\Result;
class CoreNodeSearchString extends QueryTypePluginBase {
/**
* Holds the backend's native query object.
* The backend's native query object.
*
* @var \Drupal\search_api\Query\QueryInterface
*/
......@@ -31,7 +31,6 @@ class CoreNodeSearchString extends QueryTypePluginBase {
* {@inheritdoc}
*/
public function execute() {
/** @var \Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface $facet_source */
$facet_source = $this->facet->getFacetSource();
$query_info = $facet_source->getQueryInfo($this->facet);
......
<?php
/**
* @file
* Contains \Drupal\core_search_facets\Tests\HooksTest.
*/
namespace Drupal\core_search_facets\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests integration of hooks.
*
* @group core_search_facets
*/
class HooksTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'search',
'core_search_facets_test_hooks',
'field',
];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Create a field of type float.
FieldStorageConfig::create(
[
'field_name' => 'float',
'entity_type' => 'node',
'type' => 'float',
]
)->save();
// Create an instance of the float field on the "page" content type.
FieldConfig::create(
[
'field_name' => 'float',
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Float Field Label',
]
)->save();
// Log in, so we can test all the things.
$this->drupalLogin($this->adminUser);
}
/**
* Tests various that all hooks fire correctly.
*/
public function testHooks() {
// Verify that hook_facets_core_allowed_field_types was triggered.
$facet_add_page = 'admin/config/search/facets/add-facet';
$this->drupalGet($facet_add_page);
$this->assertResponse(200);
// Select the node_search facet source.
$this->drupalGet($facet_add_page);
$this->drupalPostForm(
NULL,
['facet_source_id' => 'core_node_search:node_search'],
$this->t('Configure facet source')
);
// The field appears as expected.
$this->assertText('Float Field Label', 'Float Field appears as expected');
}
}
<?php
/**
* @file
* Contains \Drupal\core_search_facets\Tests\IntegrationTest.
*/
namespace Drupal\core_search_facets\Tests;
use Drupal\core_search_facets\Tests\WebTestBase as CoreSearchFacetsWebTestBase;
/**
* Tests the admin UI with the core search facet source.
*
* @group core_search_facets
*/
class IntegrationTest extends CoreSearchFacetsWebTestBase {
/**
* The block entities used by this test.
*
* @var \Drupal\block\BlockInterface[]
*/
protected $blocks;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->drupalLogin($this->adminUser);
// Index the content.
\Drupal::service('plugin.manager.search')->createInstance('node_search')->updateIndex();
search_update_totals();
// Make absolutely sure the ::$blocks variable doesn't pass information
// along between tests.
$this->blocks = NULL;
}
/**
* Tests various operations via the Facets' admin UI.
*/
public function testFramework() {
$facet_name = "Test Facet name";
$facet_id = 'test_facet_name';
// Check if the overview is empty.
$this->checkEmptyOverview();
// Add a new facet and edit it.
$this->addFacet($facet_name);
$this->editFacet($facet_name);
// Create and place a block for "Test Facet name" facet.
$this->createFacetBlock($facet_id);
// Verify that the facet results are correct.
$this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
$this->assertLink('page');
$this->assertLink('article');
// Verify that facet blocks appear as expected.
$this->assertFacetBlocksAppear();
$this->setShowAmountOfResults($facet_name, TRUE);
// Verify that the number of results per item.
$this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
$this->assertLink('page (10)');
$this->assertLink('article (10)');
// Do not show the block on empty behaviors.
// Truncate the search_index table because, for the moment, we don't have
// the possibility to clear the index from the API.
// See https://www.drupal.org/node/326062
\Drupal::database()->truncate('search_index')->execute();
// Verify that no facet blocks appear. Empty behavior "None" is selected by
// default.
$this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
$this->assertNoFacetBlocksAppear();
// Verify that the "empty_text" appears as expected.
$this->setEmptyBehaviorFacetText($facet_name);
$this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
$this->assertRaw('block-test-facet-name');
$this->assertRaw('No results found for this block!');
// Delete the block.
$this->deleteBlock($facet_id);
// Delete the facet and make sure the overview is empty again.
$this->deleteUnusedFacet($facet_name);
$this->checkEmptyOverview();
}
/**
* Configures the possibility to show the amount of results for facet blocks.
*
* @param string $facet_name
* The name of the facet.
* @param bool|TRUE $show
* Boolean to determine if we want to show the amount of results.
*/
protected function setShowAmountOfResults($facet_name, $show = TRUE) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_display_page = '/admin/config/search/facets/' . $facet_id . '/display';
// Go to the facet edit page and make sure "edit facet %facet" is present.
$this->drupalGet($facet_display_page);
$this->assertResponse(200);
// Configure the text for empty results behavior.
$edit = [
'widget_configs[show_numbers]' => $show,
];
$this->drupalPostForm(NULL, $edit, $this->t('Save'));
}
/**
* Deletes a facet block by id.
*
* @param string $id
* The id of the block.
*/
protected function deleteBlock($id) {
$this->drupalGet('admin/structure/block/manage/' . $this->blocks[$id]->id(), array('query' => array('destination' => 'admin')));
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, array(), t('Delete'));
$this->assertRaw(t('The block %name has been deleted.', array('%name' => $this->blocks[$id]->label())));
}
/**
* Asserts that a facet block does not appear.
*/
protected function assertNoFacetBlocksAppear() {
foreach ($this->blocks as $block) {
$this->assertNoBlockAppears($block);
}
}
/**
* Asserts that a facet block appears.
*/
protected function assertFacetBlocksAppear() {
foreach ($this->blocks as $block) {
$this->assertBlockAppears($block);
}
}
/**
* Creates a facet block by id.
*
* @param string $id
* The id of the block.
*/
protected function createFacetBlock($id) {
$block = [
'plugin_id' => 'facet_block:' . $id,
'settings' => [
'region' => 'footer',
'id' => str_replace('_', '-', $id),
],
];
$this->blocks[$id] = $this->drupalPlaceBlock($block['plugin_id'], $block['settings']);
}
/**
* Configures empty behavior option to show a text on empty results.
*
* @param string $facet_name
* The name of the facet.
*/
protected function setEmptyBehaviorFacetText($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_display_page = '/admin/config/search/facets/' . $facet_id . '/display';
// Go to the facet edit page and make sure "edit facet %facet" is present.
$this->drupalGet($facet_display_page);
$this->assertResponse(200);
// Configure the text for empty results behavior.
$edit = [
'facet_settings[empty_behavior]' => 'text',
'facet_settings[empty_behavior_container][empty_behavior_text][value]' => 'No results found for this block!',
];
$this->drupalPostForm(NULL, $edit, $this->t('Save'));
}
/**
* Configures a facet to only be visible when accessing to the facet source.
*
* @param string $facet_name
* The name of the facet.
*/
protected function setOptionShowOnlyWhenFacetSourceVisible($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_display_page = '/admin/config/search/facets/' . $facet_id . '/display';
$this->drupalGet($facet_display_page);
$this->assertResponse(200);
$edit = [
'facet_settings[only_visible_when_facet_source_is_visible]' => TRUE,
'widget' => 'links',
'widget_configs[show_numbers]' => '0',
];
$this->drupalPostForm(NULL, $edit, $this->t('Save'));
}
/**
* Get the facet overview page and make sure the overview is empty.
*/
protected function checkEmptyOverview() {
$facet_overview = '/admin/config/search/facets';
$this->drupalGet($facet_overview);
$this->assertResponse(200);
// The list overview has Field: field_name as description. This tests on the
// absence of that.
$this->assertNoText('Field:');
}
/**
* Tests adding a facet trough the interface.
*
* @param string $facet_name
* The name of the facet.
*/
protected function addFacet($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
// Go to the Add facet page and make sure that returns a 200.
$facet_add_page = '/admin/config/search/facets/add-facet';
$this->drupalGet($facet_add_page);
$this->assertResponse(200);
$form_values = [
'name' => '',
'id' => $facet_id,
'status' => 1,
'url_alias' => $facet_id,
];
// Try filling out the form, but without having filled in a name for the
// facet to test for form errors.
$this->drupalPostForm($facet_add_page, $form_values, $this->t('Save'));
$this->assertText($this->t('Facet name field is required.'));
$this->assertText($this->t('Facet source field is required.'));
// Make sure that when filling out the name, the form error disappears.
$form_values['name'] = $facet_name;
$this->drupalPostForm(NULL, $form_values, $this->t('Save'));
$this->assertNoText($this->t('Facet name field is required.'));
// Configure the facet source by selecting one of the search api views.
$this->drupalGet($facet_add_page);
$this->drupalPostForm(NULL, ['facet_source_id' => 'core_node_search:node_search'], $this->t('Configure facet source'));
// The facet field is still required.
$this->drupalPostForm(NULL, $form_values, $this->t('Save'));
$this->assertText($this->t('Facet field field is required.'));
// Fill in all fields and make sure the 'field is required' message is no
// longer shown.
$facet_source_form = [
'facet_source_configs[core_node_search:node_search][field_identifier]' => 'type',
];
$this->drupalPostForm(NULL, $form_values + $facet_source_form, $this->t('Save'));
$this->assertNoText('field is required.');
// Make sure that the redirection to the display page is correct.
$this->assertRaw(t('Facet %name has been created.', ['%name' => $facet_name]));
$this->assertUrl('admin/config/search/facets/' . $facet_id . '/display');
$this->drupalGet('admin/config/search/facets');
}
/**
* Tests editing of a facet through the UI.
*
* @param string $facet_name
* The name of the facet.
*/
public function editFacet($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_edit_page = '/admin/config/search/facets/' . $facet_id . '/edit';
// Go to the facet edit page and make sure "edit facet %facet" is present.
$this->drupalGet($facet_edit_page);
$this->assertResponse(200);
$this->assertRaw($this->t('Edit facet @facet', ['@facet' => $facet_name]));
// Change the facet name to add in "-2" to test editing of a facet works.
$form_values = ['name' => $facet_name . ' - 2'];
$this->drupalPostForm($facet_edit_page, $form_values, $this->t('Save'));
// Make sure that the redirection back to the overview was successful and
// the edited facet is shown on the overview page.
$this->assertRaw(t('Facet %name has been updated.', ['%name' => $facet_name . ' - 2']));
// Make sure the "-2" suffix is still on the facet when editing a facet.
$this->drupalGet($facet_edit_page);
$this->assertRaw($this->t('Edit facet @facet', ['@facet' => $facet_name . ' - 2']));
// Edit the form and change the facet's name back to the initial name.
$form_values = ['name' => $facet_name];
$this->drupalPostForm($facet_edit_page, $form_values, $this->t('Save'));
// Make sure that the redirection back to the overview was successful and
// the edited facet is shown on the overview page.
$this->assertRaw(t('Facet %name has been updated.', ['%name' => $facet_name]));
}
/**
* This deletes an unused facet through the UI.
*
* @param string $facet_name
* The name of the facet.
*/
protected function deleteUsedFacet($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_delete_page = '/admin/config/search/facets/' . $facet_id . '/delete';
// Go to the facet delete page and make the warning is shown.
$this->drupalGet($facet_delete_page);
$this->assertResponse(200);
// Check that the facet by testing for the message and the absence of the
// facet name on the overview.
$this->assertRaw($this->t('The facet is currently used in a block and thus can\'t be removed. Remove the block first.'));
}
/**
* This deletes a facet through the UI.
*
* @param string $facet_name
* The name of the facet.
*/
protected function deleteUnusedFacet($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_delete_page = '/admin/config/search/facets/' . $facet_id . '/delete';
// Go to the facet delete page and make the warning is shown.
$this->drupalGet($facet_delete_page);
$this->assertResponse(200);
$this->assertText($this->t('This action cannot be undone'));
// Actually submit the confirmation form.
$this->drupalPostForm(NULL, [], $this->t('Delete'));
// Check that the facet by testing for the message and the absence of the
// facet name on the overview.
$this->assertRaw($this->t('The facet %facet has been deleted.', ['%facet' => $facet_name]));
// Refresh the page because on the previous page the $facet_name is still
// visible (in the message).
$facet_overview = '/admin/config/search/facets';
$this->drupalGet($facet_overview);
$this->assertResponse(200);
$this->assertNoText($facet_name);
}
/**
* Convert facet name to machine name.
*
* @param string $facet_name
* The name of the facet.
*
* @return string
* The facet name changed to a machine name.
*/
protected function convertNameToMachineName($facet_name) {
return preg_replace('@[^a-zA-Z0-9_]+@', '_', strtolower($facet_name));
}
/**
* Go to the Delete Facet Page using the facet name.
*
* @param string $facet_name
* The name of the facet.
*/
protected function goToDeleteFacetPage($facet_name) {
$facet_id = $this->convertNameToMachineName($facet_name);
$facet_delete_page = '/admin/config/search/facets/' . $facet_id . '/delete';
// Go to the facet delete page and make the warning is shown.
$this->drupalGet($facet_delete_page);
$this->assertResponse(200);
}
}
<?php
/**
* @file
* Contains \Drupal\core_search_facets\Tests\WebTestBase.
*/
namespace Drupal\core_search_facets\Tests;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\simpletest\WebTestBase as SimpletestWebTestBase;
/**
* Provides the base class for web tests for Core Search Facets.
*/
abstract class WebTestBase extends SimpletestWebTestBase {
use StringTranslationTrait;
/**
* Modules to enable for this test.
*
* @var string[]
*/
public static $modules = [
'field',
'search',
'entity_test',
'views',
'node',
'facets',
'block',
'core_search_facets',
];
/**
* An admin user used for this test.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* A user without Search / Facet admin permission.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $unauthorizedUser;
/**
* The anonymous user used for this test.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $anonymousUser;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Create content types.
$this->drupalCreateContentType(['type' => 'page']);
$this->drupalCreateContentType(['type' => 'article']);
// Adding 10 pages.
for ($i = 0; $i < 10; $i++) {
$this->drupalCreateNode(array(
'title' => 'foo bar' . $i,
'body' => 'test page' . $i,
'type' => 'page',
));
}
// Adding 10 articles.
for ($i = 0; $i < 10; $i++) {
$this->drupalCreateNode(array(
'title' => 'foo baz' . $i,
'body' => 'test article' . $i,
'type' => 'article',
));
}
// Create the users used for the tests.
$this->adminUser = $this->drupalCreateUser([
'administer search',
'administer facets',
'access administration pages',
'administer nodes',
'access content overview',
'administer content types',
'administer blocks',
'search content',
]);
}
}
name: 'Core Search Facets Hooks Test'
type: module
description: 'Support module for core_search_facets tests, tests all the hooks.'
package: Testing
dependencies:
- facets
- core_search_facets
core: 8.x
hidden: true
<?php
/**
* @file
* Tests all the hooks defined by the core_search_facets module.
*/
/**
* Implements hook_facets_core_allowed_field_types().
*/
function core_search_facets_test_hooks_facets_core_allowed_field_types(array $allowed_field_types) {
$allowed_field_types[] = 'float';
return $allowed_field_types;
}
......@@ -3,3 +3,7 @@ type: module
description: 'Faceted search interfaces that can be used on Search API searchers.'
core: 8.x
package: Search
configure: facets.overview
test_dependencies:
- search_api:search_api
- drupal:views
drupal.facets.index-active-formatters:
version: VERSION
js:
js/index-active-formatters.js: {}
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
drupal.facets.admin_css:
version: VERSION
css:
......
......@@ -7,12 +7,10 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\search_api\Query\QueryInterface;
use Drupal\facets\FacetInterface;
/**
* Implements hook_help().
*/
function facets_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the facets module.
......
......@@ -3,6 +3,11 @@ facets_processor:
plugin_manager_service_id: plugin.manager.facets.processor
plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
facets_url_processor:
label: Facets URL processor
plugin_manager_service_id: plugin.manager.facets.url_processor
plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
facets_facet_source:
label: Facets source
plugin_manager_service_id: plugin.manager.facets.facet_source
......