Commit 44f5057e authored by StryKaizer's avatar StryKaizer Committed by borisson_
Browse files

Issue #2911515 by StryKaizer: Move facets core search into a separate module

parent 6cbf1565
......@@ -85,3 +85,14 @@ function facets_update_8002() {
->execute();
}
}
/**
* WARNING: Facets core search support has been moved into a separate project.
* If you are using this feature, you need do download the "facets_core_search" module on drupal.org."
*/
function facets_update_8003() {
\Drupal::database()->delete('key_value')
->condition('collection', 'system.schema')
->condition('name', 'core_search_facets')
->execute();
}
<?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".
*/
name: 'Core Search Facets'
type: module
description: 'Facets integration for Core.'
core: 8.x
package: Search
dependencies:
- facets:facets
- drupal:search
<?php
/**
* @file
* Contains core_search_facets.module.
*/
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\FacetSourceInterface;
/**
* Implements hook_query_TAG_alter().
*
* Tag search_$type with $type node_search.
*/
function core_search_facets_query_search_node_search_alter(AlterableInterface $query) {
// Obtain the Facet Source id for the current search.
$request = \Drupal::requestStack()->getMasterRequest();
$search_page = $request->attributes->get('entity');
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
$facet_manager = \Drupal::service('facets.manager');
$facetsource_id = 'core_node_search:' . $search_page->id();
// Add the active filters.
$facet_manager->alterQuery($query, $facetsource_id);
}
/**
* Implements hook_search_plugin_alter().
*
* Alter search plugin definitions.
*/
function core_search_facets_search_plugin_alter(array &$definitions) {
// Replace the Search Plugin class to alter the search form adding the
// possibility to show or hide the Content Type and Language advanced filters.
if (isset($definitions['node_search'])) {
$definitions['node_search']['class'] = 'Drupal\core_search_facets\Plugin\Search\NodeSearchFacets';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Edits the facet_source_edit_form, so we can add a checkbox to show or hide
* some advanced filters for the core search.
*/
function core_search_facets_form_facet_source_edit_form_alter(&$form, FormStateInterface $form_state) {
$request = \Drupal::requestStack()->getMasterRequest();
$facet_source_id = str_replace(":", "__", $request->attributes->get('facets_facet_source'));
if (strpos($facet_source_id, 'core_node_search') !== FALSE) {
$form['advanced_filters'] = [
'#type' => 'checkbox',
'#title' => t('Show Advanced Filters.'),
'#default_value' => \Drupal::config("facets.facet_source.{$facet_source_id}")->get('third_party_settings.core_search_facets.advanced_filters'),
];
$form['#entity_builders'][] = 'core_search_facets_facet_source_form_form_builder';
}
}
/**
* Entity builder for the facet source form edit form with third party options.
*
* @see core_search_facets_form_facet_source_edit_form_alter()
*/
function core_search_facets_facet_source_form_form_builder($entity_type, FacetSourceInterface $facet_source, &$form, FormStateInterface $form_state) {
$facet_source->setThirdPartySetting('core_search_facets', 'advanced_filters', $form_state->getValue('advanced_filters'));
}
/**
* 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;
}
<?php
namespace Drupal\core_search_facets;
use Drupal\Core\Database\Query\Condition;
use Drupal\search\SearchQuery;
/**
* Extension of the SearchQuery class.
*/
class FacetsQuery extends SearchQuery {
/**
* Stores joined tables.
*
* @var array
*/
protected $joinedTables = [];
/**
* Adds the facet join, but only does so once.
*
* @param array $query_info
* An associative array of query information.
* @param string $table_alias
* The alias of the table being joined.
*/
public function addFacetJoin(array $query_info, $table_alias) {
if (isset($query_info['joins'][$table_alias])) {
if (!isset($this->joinedTables[$table_alias])) {
$this->joinedTables[$table_alias] = TRUE;
$join_info = $query_info['joins'][$table_alias];
$this->join($join_info['table'], $join_info['alias'], $join_info['condition']);
}
}
}
/**
* Adds the facet field, ensures the alias is "value".
*
* @param array $query_info
* An associative array of query information.
*
* @return FacetsQuery
* An instance of this class.
*/
public function addFacetField(array $query_info) {
foreach ($query_info['fields'] as $field_info) {
$this->addField($field_info['table_alias'], $field_info['field'], 'value');
}
return $this;
}
/**
* Executes a facet query.
*/
public function execute() {
$this->parseSearchExpression();
// Adds OR conditions.
if (!empty($this->words)) {
$or = new Condition('OR');
foreach ($this->words as $word) {
$or->condition('i.word', $word);
}
$this->condition($or);
}
// Build query for keyword normalization.
$this->join('search_total', 't', 'i.word = t.word');
$this
->condition('i.type', $this->type)
->groupBy('i.langcode')
->groupBy('value')
->groupBy('i.type')
->groupBy('i.sid')
->having('COUNT(*) >= :matches', [':matches' => $this->matches]);
// Add conditions to query.
$this->join('search_dataset', 'd', 'i.sid = d.sid AND i.type = d.type AND i.langcode = d.langcode');
if (count($this->conditions)) {
$this->condition($this->conditions);
}
// Add tag and useful metadata.
$this
->addTag('search_' . $this->type)
->addMetaData('normalize', $this->normalize);
// Adds subquery to group the results in the r table.
$subquery = \Drupal::database()->select($this->query, 'r')
->fields('r', ['value'])
->groupBy('r.value');
// Adds COUNT() expression to get facet counts.
$subquery->addExpression('COUNT(r.value)', 'count');
$subquery->orderBy('count', 'DESC');
// Executes the subquery.
return $subquery->execute();
}
/**
* Returns the search expression.
*
* @return string
* The search expression.
*/
public function getSearchExpression() {
return $this->searchExpression;
}
}
<?php
namespace Drupal\core_search_facets\Plugin;
use Drupal\facets\FacetInterface;
/**
* Additional interface for core facet sources.
*
* A facet source is used to abstract the data source where facets can be added
* to. A good example of this is a Search API view. There are other possible
* facet data sources, these all implement the FacetSourcePluginInterface.
*/
interface CoreSearchFacetSourceInterface {
/**
* Sets the facet query object.
*
* @return \Drupal\core_search_facets\FacetsQuery
* The facet query object.
*/
public function getFacetQueryExtender();
/**
* Returns the query info for this facet field.
*
* @param \Drupal\facets\FacetInterface $facet
* The facet definition as returned by facets_facet_load().
*
* @return array
* An associative array containing:
* - fields: An array of field information, each of which are associative
* arrays containing:
* - table_alias: The table alias the field belongs to.
* - field: The name of the field containing the facet data.
* - joins: An array of join info, each of which are associative arrays
* containing:
* - table: The table being joined.
* - alias: The alias of the table being joined.
* - condition: The condition that joins the table.
*/
public function getQueryInfo(FacetInterface $facet);
}
<?php
namespace Drupal\core_search_facets\Plugin\Search;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\node\Plugin\Search\NodeSearch;
/**
* Handles searching for node entities using the Search module index.
*/
class NodeSearchFacets extends NodeSearch {
/**
* {@inheritdoc}
*/
public function searchFormAlter(array &$form, FormStateInterface $form_state) {
$parameters = $this->getParameters();
$keys = $this->getKeywords();
$used_advanced = !empty($parameters[self::ADVANCED_FORM]);
if ($used_advanced) {
$f = isset($parameters['f']) ? (array) $parameters['f'] : [];
$defaults = $this->parseAdvancedDefaults($f, $keys);
}
else {
$defaults = ['keys' => $keys];
}
$form['basic']['keys']['#default_value'] = $defaults['keys'];
// Add advanced search keyword-related boxes.
$form['advanced'] = [
'#type' => 'details',
'#title' => t('Advanced search'),
'#attributes' => ['class' => ['search-advanced']],
'#access' => $this->account && $this->account->hasPermission('use advanced search'),
'#open' => $used_advanced,
];
$form['advanced']['keywords-fieldset'] = [
'#type' => 'fieldset',
'#title' => t('Keywords'),
];
$form['advanced']['keywords'] = [
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
];
$form['advanced']['keywords-fieldset']['keywords']['or'] = [
'#type' => 'textfield',
'#title' => t('Containing any of the words'),
'#size' => 30,
'#maxlength' => 255,
'#default_value' => isset($defaults['or']) ? $defaults['or'] : '',
];
$form['advanced']['keywords-fieldset']['keywords']['phrase'] = [
'#type' => 'textfield',
'#title' => t('Containing the phrase'),
'#size' => 30,
'#maxlength' => 255,
'#default_value' => isset($defaults['phrase']) ? $defaults['phrase'] : '',
];
$form['advanced']['keywords-fieldset']['keywords']['negative'] = [
'#type' => 'textfield',
'#title' => t('Containing none of the words'),
'#size' => 30,
'#maxlength' => 255,
'#default_value' => isset($defaults['negative']) ? $defaults['negative'] : '',
];
$form['advanced']['submit'] = [
'#type' => 'submit',
'#value' => t('Advanced search'),
'#prefix' => '<div class="action">',
'#suffix' => '</div>',
'#weight' => 100,
];
if (\Drupal::config("facets.facet_source.core_node_search__{$this->searchPageId}")->get('third_party_settings.core_search_facets.advanced_filters')) {
// Add node types.
$types = array_map(['\Drupal\Component\Utility\Html', 'escape'], node_type_get_names());
$form['advanced']['types-fieldset'] = [
'#type' => 'fieldset',
'#title' => t('Types'),
];
$form['advanced']['types-fieldset']['type'] = [
'#type' => 'checkboxes',
'#title' => t('Only of the type(s)'),
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
'#options' => $types,
'#default_value' => isset($defaults['type']) ? $defaults['type'] : [],
];
$form['advanced']['submit'] = [
'#type' => 'submit',
'#value' => t('Advanced search'),
'#prefix' => '<div class="action">',
'#suffix' => '</div>',
'#weight' => 100,
];
// Add languages.
$language_options = [];
$language_list = $this->languageManager->getLanguages(LanguageInterface::STATE_ALL);
foreach ($language_list as $langcode => $language) {
// Make locked languages appear special in the list.
$language_options[$langcode] = $language->isLocked() ? t('- @name -', ['@name' => $language->getName()]) : $language->getName();
}
if (count($language_options) > 1) {
$form['advanced']['lang-fieldset'] = [
'#type' => 'fieldset',
'#title' => t('Languages'),
];
$form['advanced']['lang-fieldset']['language'] = [
'#type' => 'checkboxes',
'#title' => t('Languages'),
'#prefix' => '<div class="criterion">',
'#suffix' => '</div>',
'#options' => $language_options,
'#default_value' => isset($defaults['language']) ? $defaults['language'] : [],
];
}
}
}
}
<?php
namespace Drupal\core_search_facets\Plugin\facets\facet_source;
use Drupal\Core\Database\Connection;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface;
use Drupal\facets\FacetInterface;
use Drupal\facets\FacetSource\FacetSourcePluginBase;
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\Request;
/**
* A facet source to support node search pages.
*
* @FacetsFacetSource(
* id = "core_node_search",
* deriver = "Drupal\core_search_facets\Plugin\facets\facet_source\CoreNodeSearchFacetSourceDeriver"
* )
*/
class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSearchFacetSourceInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected $entityTypeManager;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManagerInterface|null
*/
protected $typedDataManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface|null
*/
protected $configFactory;
/**
* The plugin manager for core search plugins.
*
* @var \Drupal\search\SearchPluginManager
*/
protected $searchManager;
/**
* The facet query being executed.
*
* @var \Drupal\core_search_facets\FacetsQuery
*/
protected $facetQueryExtender;
/**
* The clone of the master request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current primary database.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\facets\QueryType\QueryTypePluginManager $query_type_plugin_manager
* The query type plugin manager.
* @param \Drupal\search\SearchPluginManager $search_manager
* The plugin manager for core search plugins.
* @param \Symfony\Component\HttpFoundation\Request $request
* The master request.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Database\Connection $database
* The current primary database.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, QueryTypePluginManager $query_type_plugin_manager, SearchPluginManager $search_manager, Request $request, ModuleHandlerInterface $module_handler, Connection $database) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $query_type_plugin_manager);
$this->searchManager = $search_manager;
$this->request = clone $request;
$this->setSearchKeys($this->request->query->get('keys'));
$this->moduleHandler = $module_handler;
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.facets.query_type'),
$container->get('plugin.manager.search'),
$container->get('request_stack')->getMasterRequest(),
$container->get('module_handler'),
$container->get('database')
);
}
/**
* {@inheritdoc}
*/
public function getPath() {
$search_page = $this->request->attributes->get('entity');
if ($search_page instanceof SearchPageInterface) {
return '/search/' . $search_page->getPath();
}
return '/';
}
/**
* {@inheritdoc}
*/
public function fillFacetsWithResults(array $facets) {
foreach ($facets as $facet) {
$configuration = [
'query' => NULL,
'facet' => $facet,
];
// Get the Facet Specific Query Type so we can process the results
// using the build() function of the query type.
$query_type = $this->queryTypePluginManager->createInstance($facet->getQueryType(), $configuration);
$query_type->build();
}
}
/**
* {@inheritdoc}
*/
public function getQueryTypesForFacet(FacetInterface $facet) {
// Verify if the field exists. Otherwise the type will be a column
// (type,uid...) from the node and we can use the field identifier directly.