Skip to content
......@@ -42,8 +42,7 @@ entity.facets_facet.display_form:
requirements:
_entity_access: 'facets_facet.edit'
entity.facets_facetsource.edit_form:
entity.facets_facet_source.edit_form:
path: '/admin/config/search/facets/facet-sources/{source_id}/edit'
defaults:
_controller: '\Drupal\facets\Controller\FacetSourceController::facetSourceConfigForm'
......
......@@ -11,6 +11,9 @@ services:
plugin.manager.facets.processor:
class: Drupal\facets\Processor\ProcessorPluginManager
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@string_translation']
plugin.manager.facets.url_processor:
class: Drupal\facets\UrlProcessor\UrlProcessorPluginManager
parent: default_plugin_manager
facets.manager:
class: Drupal\facets\FacetManager\DefaultFacetManager
arguments:
......@@ -19,9 +22,3 @@ services:
- '@plugin.manager.facets.facet_source'
- '@plugin.manager.facets.processor'
- '@entity_type.manager'
facets.facet_context:
class: Drupal\facets\ContextProvider\FacetContextProvider
arguments: ['@entity_type.manager']
tags:
- { name: context_provider }
/**
* @file
* Attaches show/hide functionality to checkboxes in the "Processor" tab.
*/
(function ($) {
"use strict";
Drupal.behaviors.facetsIndexFormatter = {
attach: function (context, settings) {
$('.search-api-status-wrapper input.form-checkbox', context).each(function () {
var $checkbox = $(this);
var processor_id = $checkbox.data('id');
var $rows = $('.search-api-processor-weight--' + processor_id, context);
var tab = $('.search-api-processor-settings-' + processor_id, context).data('verticalTab');
// Bind a click handler to this checkbox to conditionally show and hide
// the processor's table row and vertical tab pane.
$checkbox.on('click.searchApiUpdate', function () {
if ($checkbox.is(':checked')) {
$rows.show();
if (tab) {
tab.tabShow().updateSummary();
}
}
else {
$rows.hide();
if (tab) {
tab.tabHide().updateSummary();
}
}
});
// Attach summary for configurable items (only for screen-readers).
if (tab) {
tab.details.drupalSetSummary(function () {
return $checkbox.is(':checked') ? Drupal.t('Enabled') : Drupal.t('Disabled');
});
}
// Trigger our bound click handler to update elements to initial state.
$checkbox.triggerHandler('click.searchApiUpdate');
});
}
};
})(jQuery);
......@@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\facets\Annotation\FacetsFacet.
* Contains \Drupal\facets\Annotation\FacetsFacetSource.
*/
namespace Drupal\facets\Annotation;
......@@ -29,7 +29,7 @@ class FacetsFacetSource extends Plugin {
public $id;
/**
* The human-readable name of the facet soruce plugin.
* The human-readable name of the facet source plugin.
*
* @ingroup plugin_translatable
*
......
<?php
/**
* @file
* Contains \Drupal\facets\Annotation\FacetsUrlProcessor.
*/
namespace Drupal\facets\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Facets URL Processor annotation.
*
* @see \Drupal\facets\Processor\ProcessorPluginManager
* @see plugin_api
*
* @ingroup plugin_api
*
* @Annotation
*/
class FacetsUrlProcessor extends Plugin {
/**
* The URL processor plugin id.
*
* @var string
*/
public $id;
/**
* The human-readable name of the URL processor plugin.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $label;
/**
* The URL processor description.
*
* @ingroup plugin_translatable
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
}
<?php
/**
* @file
* Contains \Drupal\facets\ContextProvider\FacetContextProvider.
*/
namespace Drupal\facets\ContextProvider;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\ContextProviderInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* A provider for the core context system for facets.
*
* This provider is a provider for core's context system, it makes integration
* with blocks, panels and other layout systems easy.
*/
class FacetContextProvider implements ContextProviderInterface {
use StringTranslationTrait;
protected $facetStorage;
/**
* Creates a new instance of the context provider.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->facetStorage = $entity_type_manager->getStorage('facets_facet');
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $unqualified_context_ids = []) {
$ids = $this->facetStorage->getQuery()
->condition('uuid', $unqualified_context_ids, 'IN')
->execute();
$contexts = [];
foreach ($this->facetStorage->loadMultiple($ids) as $facet) {
$context = new Context(new ContextDefinition('entity:facets_facet'), $facet);
$contexts[$facet->uuid()] = $context;
}
return $contexts;
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
$facets = $this->facetStorage->loadMultiple();
$contexts = [];
/** @var \Drupal\facets\FacetInterface $facet */
foreach ($facets as $facet) {
$context = new Context(
new ContextDefinition('entity:facets_facet', $facet->label()),
$facet
);
$contexts[$facet->uuid()] = $context;
}
return $contexts;
}
}
......@@ -43,8 +43,12 @@ use Drupal\facets\FacetInterface;
* "facet_source_id",
* "widget",
* "widget_configs",
* "options",
* "query_operator",
* "exclude",
* "only_visible_when_facet_source_is_visible",
* "processor_configs",
* "empty_behavior",
* "facet_configs",
* },
* links = {
* "canonical" = "/admin/config/search/facets",
......@@ -95,18 +99,23 @@ class Facet extends ConfigEntityBase implements FacetInterface {
/**
* Configuration for the widget. This is a key-value stored array.
*
* @var string
* @var array
*/
protected $widget_configs;
protected $widget_configs = [];
/**
* An array of options configuring this facet.
* The operator to hand over to the query, currently AND | OR.
*
* @var array
* @var string
*/
protected $query_operator;
/**
* A boolean flag indicating if search should exclude selected facets.
*
* @see getOptions()
* @var bool
*/
protected $options = array();
protected $exclude;
/**
* The field identifier.
......@@ -159,6 +168,18 @@ class Facet extends ConfigEntityBase implements FacetInterface {
*/
protected $results = [];
/**
* The results.
*
* @var \Drupal\facets\Result\ResultInterface[]
*/
protected $unfiltered_results = [];
/**
* An array of active values.
*
* @var string[]
*/
protected $active_values = [];
/**
......@@ -177,6 +198,20 @@ class Facet extends ConfigEntityBase implements FacetInterface {
*/
protected $processors;
/**
* Configuration for the processors. This is an array of arrays.
*
* @var array
*/
protected $processor_configs = [];
/**
* Additional facet configurations.
*
* @var array
*/
protected $facet_configs = [];
/**
* Is the facet only visible when the facet source is only visible.
*
......@@ -187,6 +222,13 @@ class Facet extends ConfigEntityBase implements FacetInterface {
*/
protected $only_visible_when_facet_source_is_visible;
/**
* The no-result configuration.
*
* @var string[];
*/
protected $empty_behavior;
/**
* The widget plugin manager.
*
......@@ -210,7 +252,7 @@ class Facet extends ConfigEntityBase implements FacetInterface {
}
/**
* Gets the widget plugin manager.
* Returns the widget plugin manager.
*
* @return \Drupal\facets\Widget\WidgetPluginManager
* The widget plugin manager.
......@@ -224,8 +266,9 @@ class Facet extends ConfigEntityBase implements FacetInterface {
/**
* {@inheritdoc}
*/
public function id() {
return $this->id;
protected function urlRouteParameters($rel) {
$parameters = parent::urlRouteParameters($rel);
return $parameters;
}
/**
......@@ -243,6 +286,13 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this;
}
/**
* {@inheritdoc}
*/
public function getQueryTypes() {
return $this->query_type_name;
}
/**
* {@inheritdoc}
*/
......@@ -250,6 +300,48 @@ class Facet extends ConfigEntityBase implements FacetInterface {
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->getProcessorConfigs();
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 getProcessorConfigs() {
return !empty($this->processor_configs) ? $this->processor_configs : [];
}
/**
* {@inheritdoc}
*/
......@@ -268,56 +360,54 @@ class Facet extends ConfigEntityBase implements FacetInterface {
/**
* {@inheritdoc}
*/
public function getFieldAlias() {
// For now, create the field alias based on the field identifier.
$field_alias = preg_replace('/[:\/]+/', '_', $this->field_identifier);
return $field_alias;
public function setQueryOperator($operator = '') {
return $this->query_operator = $operator;
}
/**
* {@inheritdoc}
*/
public function setActiveItem($value) {
if (!in_array($value, $this->active_values)) {
$this->active_values[] = $value;
}
public function getQueryOperator() {
return $this->query_operator ?: 'OR';
}
/**
* {@inheritdoc}
*/
public function getActiveItems() {
return $this->active_values;
public function setExclude($exclude) {
return $this->exclude = $exclude;
}
/**
* {@inheritdoc}
*/
public function getOption($name, $default = NULL) {
return isset($this->options[$name]) ? $this->options[$name] : $default;
public function getExclude() {
return $this->exclude;
}
/**
* {@inheritdoc}
*/
public function getOptions() {
return $this->options;
public function getFieldAlias() {
// For now, create the field alias based on the field identifier.
$field_alias = preg_replace('/[:\/]+/', '_', $this->field_identifier);
return $field_alias;
}
/**
* {@inheritdoc}
*/
public function setOption($name, $option) {
$this->options[$name] = $option;
return $this;
public function setActiveItem($value) {
if (!in_array($value, $this->active_values)) {
$this->active_values[] = $value;
}
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options) {
$this->options = $options;
return $this;
public function getActiveItems() {
return $this->active_values;
}
/**
......@@ -335,13 +425,6 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this;
}
/**
* {@inheritdoc}
*/
public function getQueryTypes() {
return $this->query_type_name;
}
/**
* {@inheritdoc}
*/
......@@ -387,7 +470,7 @@ class Facet extends ConfigEntityBase implements FacetInterface {
if (!$this->facet_source_instance && $this->facet_source_id) {
/* @var $facet_source_plugin_manager \Drupal\facets\FacetSource\FacetSourcePluginManager */
$facet_source_plugin_manager = \Drupal::service('plugin.manager.facets.facet_source');
$this->facet_source_instance = $facet_source_plugin_manager->createInstance($this->facet_source_id);
$this->facet_source_instance = $facet_source_plugin_manager->createInstance($this->facet_source_id, ['facet' => $this]);
}
return $this->facet_source_instance;
......@@ -434,45 +517,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');
$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}
*/
......@@ -496,6 +540,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}
*/
......@@ -527,7 +585,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'],
]);
}
}
}
......@@ -547,9 +609,9 @@ class Facet extends ConfigEntityBase implements FacetInterface {
$processors = $this->loadProcessors();
// Filter processors by status if required. Enabled processors are those
// which have settings in the "processors" option.
// which have settings in the processor_configs.
if ($only_enabled) {
$processors_settings = $this->getOption('processors', array());
$processors_settings = $this->getProcessorConfigs();
$processors = array_intersect_key($processors, $processors_settings);
}
......@@ -561,7 +623,7 @@ class Facet extends ConfigEntityBase implements FacetInterface {
*/
public function getProcessorsByStage($stage, $only_enabled = TRUE) {
$processors = $this->loadProcessors();
$processor_settings = $this->getOption('processors', array());
$processor_settings = $this->getProcessorConfigs();
$processor_weights = array();
// Get a list of all processors meeting the criteria (stage and, optionally,
......@@ -602,4 +664,66 @@ class Facet extends ConfigEntityBase implements FacetInterface {
return $this->only_visible_when_facet_source_is_visible;
}
/**
* {@inheritdoc}
*/
public function addProcessor(array $processor) {
$this->processor_configs[$processor['processor_id']] = [
'processor_id' => $processor['processor_id'],
'weights' => $processor['weights'],
'settings' => $processor['settings'],
];
// Sort the processors so we won't have unnecessary changes.
ksort($this->processor_configs);
}
/**
* {@inheritdoc}
*/
public function removeProcessor($processor_id) {
unset($this->processor_configs[$processor_id]);
}
/**
* {@inheritdoc}
*/
public function getEmptyBehavior() {
return $this->empty_behavior;
}
/**
* {@inheritdoc}
*/
public function setEmptyBehavior(array $empty_behavior) {
$this->empty_behavior = $empty_behavior;
}
/**
* {@inheritdoc}
*/
public function setWidgetConfigs(array $widget_configs) {
$this->widget_configs = $widget_configs;
}
/**
* {@inheritdoc}
*/
public function getWidgetConfigs() {
return $this->widget_configs;
}
/**
* {@inheritdoc}
*/
public function setFacetConfigs(array $facet_configs) {
$this->facet_configs = $facet_configs;
}
/**
* {@inheritdoc}
*/
public function getFacetConfigs() {
return $this->facet_configs;
}
}
......@@ -36,7 +36,8 @@ use Drupal\facets\FacetSourceInterface;
* config_export = {
* "id",
* "name",
* "filterKey"
* "filter_key",
* "url_processor"
* },
* links = {
* "canonical" = "/admin/config/search/facets/facet-sources/",
......@@ -65,14 +66,14 @@ class FacetSource extends ConfigEntityBase implements FacetSourceInterface {
*
* @var string
*/
protected $filterKey;
protected $filter_key;
/**
* {@inheritdoc}
* The url processor name.
*
* @var string
*/
public function id() {
return $this->id;
}
protected $url_processor = 'query_string';
/**
* {@inheritdoc}
......@@ -85,14 +86,28 @@ class FacetSource extends ConfigEntityBase implements FacetSourceInterface {
* {@inheritdoc}
*/
public function setFilterKey($filter_key) {
$this->filterKey = $filter_key;
$this->filter_key = $filter_key;
}
/**
* {@inheritdoc}
*/
public function getFilterKey() {
return $this->filterKey;
return $this->filter_key;
}
/**
* {@inheritdoc}
*/
public function setUrlProcessor($processor_name) {
$this->url_processor = $processor_name;
}
/**
* {@inheritdoc}
*/
public function getUrlProcessorName() {
return $this->url_processor;
}
}
......@@ -33,7 +33,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function getWidget();
/**
* Get field identifier.
* Returns field identifier.
*
* @return string
* The field identifier of this facet.
......@@ -41,7 +41,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function getFieldIdentifier();
/**
* Set field identifier.
* Sets field identifier.
*
* @param string $field_identifier
* The field identifier of this facet.
......@@ -52,7 +52,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function setFieldIdentifier($field_identifier);
/**
* Get the field alias used to identify the facet in the url.
* Returns the field alias used to identify the facet in the url.
*
* @return string
* The field alias for the facet.
......@@ -60,7 +60,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function getFieldAlias();
/**
* Get the field name of the facet as used in the index.
* Returns the field name of the facet as used in the index.
*
* @TODO: Check if fieldIdentifier can be used as well!
*
......@@ -70,9 +70,10 @@ interface FacetInterface extends ConfigEntityInterface {
public function getName();
/**
* Gets the name of the facet for use in the URL.
* Returns the name of the facet for use in the URL.
*
* @param string $url_parameter
* @return string
* The name of the facet for use in the URL.
*/
public function getUrlAlias();
......@@ -80,6 +81,7 @@ interface FacetInterface extends ConfigEntityInterface {
* Sets the name of the facet for use in the URL.
*
* @param string $url_alias
* The name of the facet for use in the URL.
*/
public function setUrlAlias($url_alias);
......@@ -92,7 +94,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function setActiveItem($value);
/**
* Get all the active items in the facet.
* Returns all the active items in the facet.
*
* @return mixed
* An array containing all active items.
......@@ -111,7 +113,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function isActiveValue($value);
/**
* Get the result for the facet.
* Returns the result for the facet.
*
* @return \Drupal\facets\Result\ResultInterface[] $results
* The results of the facet.
......@@ -126,69 +128,61 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function setResults(array $results);
/**
* Get the query type instance.
* Sets an array of unfiltered results.
*
* @return string
* The query type plugin being used.
* 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 $all_results
* Unfiltered results.
*/
public function getQueryType();
public function setUnfilteredResults(array $all_results = []);
/**
* Get the plugin name for the url processor.
* Returns an array of unfiltered results.
*
* @return string
* The id of the url processor.
* @return array
* Unfiltered results.
*/
public function getUrlProcessorName();
public function getUnfilteredResults();
/**
* Retrieves an option.
* Returns the query type instance.
*
* @param string $name
* The name of an option.
* @param mixed $default
* The value return if the option wasn't set.
*
* @return mixed
* The value of the option.
*
* @see getOptions()
* @return string
* The query type plugin being used.
*/
public function getOption($name, $default = NULL);
public function getQueryType();
/**
* Retrieves an array of all options.
* Returns the query operator.
*
* @return array
* An associative array of option values, keyed by the option name.
* @return string
* The query operator being used.
*/
public function getOptions();
public function getQueryOperator();
/**
* Sets an option.
* Returns the value of the exclude boolean.
*
* @param string $name
* The name of an option.
* @param mixed $option
* The new option.
* This will return true when the current facet's value should be exclusive
* from the search rather than inclusive.
* When this returns TRUE, the operator will be "<>" instead of "=".
*
* @return $this
* Returns self.
* @return bool
* A boolean flag indicating if search should exlude selected facets
*/
public function setOption($name, $option);
public function getExclude();
/**
* Sets the index's options.
*
* @param array $options
* The new index options.
* Returns the plugin name for the url processor.
*
* @return $this
* Returns self.
* @return string
* The id of the url processor.
*/
public function setOptions(array $options);
public function getUrlProcessorName();
/**
* Sets a string representation of the Facet source plugin.
......@@ -203,6 +197,22 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function setFacetSourceId($facet_source_id);
/**
* Sets the query operator.
*
* @param string $operator
* The query operator being used.
*/
public function setQueryOperator($operator);
/**
* Sets the exclude.
*
* @param bool $exclude
* A boolean flag indicating if search should exclude selected facets
*/
public function setExclude($exclude);
/**
* Returns the Facet source id.
*
......@@ -228,7 +238,7 @@ interface FacetInterface extends ConfigEntityInterface {
public function getFacetSourceConfig();
/**
* Load the facet sources for this facet.
* Loads the facet sources for this facet.
*
* @param bool|TRUE $only_enabled
* Only return enabled facet sources.
......@@ -266,6 +276,14 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function getProcessorsByStage($stage, $only_enabled = TRUE);
/**
* Retrieves this facets's processor configs.
*
* @return array
* An array of processors and their configs.
*/
public function getProcessorConfigs();
/**
* Sets the "only visible when facet source is visible" boolean flag.
*
......@@ -286,4 +304,68 @@ interface FacetInterface extends ConfigEntityInterface {
*/
public function getOnlyVisibleWhenFacetSourceIsVisible();
/**
* Adds a processor for this facet.
*
* @param array $processor
* An array definition for a processor.
*/
public function addProcessor(array $processor);
/**
* Removes a processor for this facet.
*
* @param string $processor_id
* The plugin id of the processor.
*/
public function removeProcessor($processor_id);
/**
* Defines the no-results behavior.
*
* @param array $behavior
* The definition of the behavior.
*/
public function setEmptyBehavior(array $behavior);
/**
* Returns the defined no-results behavior or NULL if none defined.
*
* @return array|NULL
* The behavior definition or NULL.
*/
public function getEmptyBehavior();
/**
* Returns the configuration of the selected widget.
*
* @return array
* The configuration settings for the widget.
*/
public function getWidgetConfigs();
/**
* Sets the configuration for the widget of this facet.
*
* @param array $widget_config
* The configuration settings for the widget.
*/
public function setWidgetConfigs(array $widget_config);
/**
* Returns any additional configuration for this facet, not defined above.
*
* @return array
* An array of additional configuration for the facet.
*/
public function getFacetConfigs();
/**
* Defines any additional configuration for this facet not defined above.
*
* @param array $facet_config
* An array of additional configuration for the facet.
*/
public function setFacetConfigs(array $facet_config);
}
......@@ -120,7 +120,7 @@ class FacetListBuilder extends ConfigEntityListBuilder {
}
/**
* {@inheritdoc}
* Builds an array of facet sources for display in the overview.
*/
public function buildFacetSourceRow(array $facet_source = []) {
return array(
......@@ -138,7 +138,7 @@ class FacetListBuilder extends ConfigEntityListBuilder {
'operations' => array(
'data' => Link::createFromRoute(
$this->t('Configure'),
'entity.facets_facetsource.edit_form',
'entity.facets_facet_source.edit_form',
['source_id' => $facet_source['id']]
)->toRenderable(),
),
......
......@@ -108,15 +108,11 @@ class DefaultFacetManager {
protected $facetSourceId;
/**
* Set the search id.
* The entity storage for facets.
*
* @param string $facet_source_id
* The id of the facet source.
* @var \Drupal\Core\Entity\EntityStorageInterface|object
*/
public function setFacetSourceId($facet_source_id) {
$this->facetSourceId = $facet_source_id;
}
protected $facetStorage;
/**
* Constructs a new instance of the DefaultFacetManager.
*
......@@ -132,18 +128,27 @@ class DefaultFacetManager {
* The entity type plugin manager.
*/
public function __construct(QueryTypePluginManager $query_type_plugin_manager, WidgetPluginManager $widget_plugin_manager, FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, EntityTypeManager $entity_type_manager) {
$this->queryTypePluginManager = $query_type_plugin_manager;
$this->widgetPluginManager = $widget_plugin_manager;
$this->facetSourcePluginManager = $facet_source_manager;
$this->processorPluginManager = $processor_plugin_manager;
$this->facet_storage = $entity_type_manager->getStorage('facets_facet');
$this->facetStorage = $entity_type_manager->getStorage('facets_facet');
// Immediately initialize the facets. This can be done directly because the
// only thing needed is the url.
$this->initFacets();
}
/**
* Sets the search id.
*
* @param string $facet_source_id
* The id of the facet source.
*/
public function setFacetSourceId($facet_source_id) {
$this->facetSourceId = $facet_source_id;
}
/**
* Allows the backend to add facet queries to its native query object.
*
......@@ -160,8 +165,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);
}
}
}
}
......@@ -173,11 +184,11 @@ class DefaultFacetManager {
* An array of enabled facets.
*/
public function getEnabledFacets() {
return $this->facet_storage->loadMultiple();
return $this->facetStorage->loadMultiple();
}
/**
* Get the ID of the facet source.
* Returns the ID of the facet source.
*
* @return string
* The id of the facet source.
......@@ -204,7 +215,7 @@ class DefaultFacetManager {
}
/**
* Initialize enabled facets.
* Initializes enabled facets.
*
* In this method all pre-query processors get called and their contents are
* executed.
......@@ -214,23 +225,20 @@ class DefaultFacetManager {
$this->facets = $this->getEnabledFacets();
foreach ($this->facets as $facet) {
foreach ($facet->getProcessors() as $processor) {
$processor_definition = $processor->getPluginDefinition();
if (is_array($processor_definition['stages']) && array_key_exists(ProcessorInterface::STAGE_PRE_QUERY, $processor_definition['stages'])) {
/** @var PreQueryProcessorInterface $pre_query_processor */
$pre_query_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
if (!$pre_query_processor instanceof PreQueryProcessorInterface) {
throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface", ['@processor' => $processor_configuration['processor_id']]));
}
$pre_query_processor->preQuery($facet);
foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_PRE_QUERY) as $processor) {
/** @var PreQueryProcessorInterface $pre_query_processor */
$pre_query_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
if (!$pre_query_processor instanceof PreQueryProcessorInterface) {
throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a pre_query definition but doesn't implement the required PreQueryProcessorInterface interface", ['@processor' => $processor_configuration['processor_id']]));
}
$pre_query_processor->preQuery($facet);
}
}
}
}
/**
* Build a facet and returns it's render array.
* Builds a facet and returns it as a renderable array.
*
* This method delegates to the relevant plugins to render a facet, it calls
* out to a widget plugin to do the actual rendering when results are found.
......@@ -278,23 +286,20 @@ class DefaultFacetManager {
// @see \Drupal\facets\Processor\WidgetOrderProcessorInterface.
$results = $facet->getResults();
foreach ($facet->getProcessors() as $processor) {
$processor_definition = $this->processorPluginManager->getDefinition($processor->getPluginDefinition()['id']);
if (is_array($processor_definition['stages']) && array_key_exists(ProcessorInterface::STAGE_BUILD, $processor_definition['stages'])) {
/** @var BuildProcessorInterface $build_processor */
$build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
if (!$build_processor instanceof BuildProcessorInterface) {
throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a build definition but doesn't implement the required BuildProcessorInterface interface", ['@processor' => $processor['processor_id']]));
}
$results = $build_processor->build($facet, $results);
foreach ($facet->getProcessorsByStage(ProcessorInterface::STAGE_BUILD) as $processor) {
/** @var BuildProcessorInterface $build_processor */
$build_processor = $this->processorPluginManager->createInstance($processor->getPluginDefinition()['id'], ['facet' => $facet]);
if (!$build_processor instanceof BuildProcessorInterface) {
throw new InvalidProcessorException(new FormattableMarkup("The processor @processor has a build definition but doesn't implement the required BuildProcessorInterface interface", ['@processor' => $processor['processor_id']]));
}
$results = $build_processor->build($facet, $results);
}
$facet->setResults($results);
// No results behavior handling. Return a custom text or false depending on
// settings.
if (empty($facet->getResults())) {
$empty_behavior = $facet->getOption('empty_behavior');
$empty_behavior = $facet->getEmptyBehavior();
if ($empty_behavior['behavior'] == 'text') {
return ['#markup' => $empty_behavior['text']];
}
......@@ -321,4 +326,23 @@ class DefaultFacetManager {
$facet_source_plugin->fillFacetsWithResults($this->facets);
}
/**
* Returns one of the processed facets.
*
* Returns one of the processed facets, this is a facet with filled results.
* Keep in mind that if you want to have the facet's build processor executed,
* there needs to be an extra call to the FacetManager::build with the facet
* returned here as argument.
*
* @param string $facet_id
* The id of the facet.
*
* @return \Drupal\facets\FacetInterface|NULL
* The updated facet if it exists, NULL otherwise.
*/
public function returnProcessedFacet($facet_id) {
$this->processFacets();
return $this->facets[$facet_id];
}
}
......@@ -82,7 +82,6 @@ abstract class FacetSourceDeriverBase implements ContainerDeriverInterface {
return isset($derivatives[$derivative_id]) ? $derivatives[$derivative_id] : NULL;
}
/**
* Compares two plugin definitions according to their labels.
*
......
......@@ -11,6 +11,8 @@ use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Facets\FacetInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines a base class from which other facet sources may extend.
......@@ -42,17 +44,23 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl
*/
protected $keys;
/**
* The facet we're editing for.
*
* @var \Drupal\facets\FacetInterface
*/
protected $facet;
/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
$query_type_plugin_manager
) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, $query_type_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->queryTypePluginManager = $query_type_plugin_manager;
if (isset($configuration['facet'])) {
$this->facet = $configuration['facet'];
}
}
/**
......@@ -102,4 +110,20 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl
return $this->keys;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$facet_source_id = $this->facet->getFacetSourceId();
$field_identifier = $form_state->getValue('facet_source_configs')[$facet_source_id]['field_identifier'];
$this->facet->setFieldIdentifier($field_identifier);
}
}
......@@ -7,7 +7,7 @@
namespace Drupal\facets\FacetSource;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\facets\FacetInterface;
/**
......@@ -19,24 +19,10 @@ use Drupal\facets\FacetInterface;
*
* @see plugin_api
*/
interface FacetSourcePluginInterface {
interface FacetSourcePluginInterface extends PluginFormInterface {
/**
* Adds a configuration form for this facet source.
*
* @param array $form
* The configuration form definition.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param \Drupal\facets\FacetInterface $facet
* The facet being edited.
* @param \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source
* The facet source being edited.
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet, FacetSourcePluginInterface $facet_source);
/**
* Fill in facet data in to the configured facets.
* Fills the facet entities with results from the facet source.
*
* @param \Drupal\facets\FacetInterface[] $facets
* The configured facets.
......@@ -44,15 +30,7 @@ interface FacetSourcePluginInterface {
public function fillFacetsWithResults($facets);
/**
* Returns the path where a facet should link to.
*
* @return string
* The path of the facet.
*/
public function getPath();
/**
* Get the allowed query types for a given facet for the facet source.
* Returns the allowed query types for a given facet for the facet source.
*
* @param \Drupal\facets\FacetInterface $facet
* The facet we should get query types for.
......@@ -65,6 +43,14 @@ interface FacetSourcePluginInterface {
*/
public function getQueryTypesForFacet(FacetInterface $facet);
/**
* Returns the path where a facet should link to.
*
* @return string
* The path of the facet.
*/
public function getPath();
/**
* Returns true if the Facet source is being rendered in the current request.
*
......@@ -100,7 +86,7 @@ interface FacetSourcePluginInterface {
public function setSearchKeys($keys);
/**
* Gets the search keys, or query text, submitted by the user.
* Returns the search keys, or query text, submitted by the user.
*
* @return string
* The search keys, or query text, submitted by the user.
......
......@@ -21,15 +21,7 @@ use Drupal\Core\Plugin\DefaultPluginManager;
class FacetSourcePluginManager extends DefaultPluginManager {
/**
* Constructs a FacetSourcePluginManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* {@inheritdoc}
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/facets/facet_source', $namespaces, $module_handler, 'Drupal\facets\FacetSource\FacetSourcePluginInterface', 'Drupal\facets\Annotation\FacetsFacetSource');
......
......@@ -23,7 +23,7 @@ interface FacetSourceInterface extends ConfigEntityInterface {
public function getName();
/**
* Gets the filter key for this facet source.
* Returns the filter key for this facet source.
*
* @return string
* The filter key.
......@@ -38,4 +38,20 @@ interface FacetSourceInterface extends ConfigEntityInterface {
*/
public function setFilterKey($filter_key);
/**
* Sets the processor name to be used.
*
* @param string $processor_name
* Plugin name of the url processor.
*/
public function setUrlProcessor($processor_name);
/**
* Returns a string version of the url processor.
*
* @return string
* The url processor to be used as a string.
*/
public function getUrlProcessorName();
}
......@@ -14,6 +14,7 @@ use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\Processor\ProcessorInterface;
use Drupal\facets\Processor\ProcessorPluginManager;
use Drupal\facets\UrlProcessor\UrlProcessorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\facets\Widget\WidgetPluginManager;
use Drupal\facets\Processor\WidgetOrderProcessorInterface;
......@@ -194,6 +195,7 @@ class FacetDisplayForm extends EntityForm {
else {
$all_processors = $form_state->get('processors');
}
$enabled_processors = $facet->getProcessors(TRUE);
$stages = $this->processorPluginManager->getProcessingStages();
$processors_by_stage = array();
......@@ -201,10 +203,8 @@ class FacetDisplayForm extends EntityForm {
$processors_by_stage[$stage] = $facet->getProcessorsByStage($stage, FALSE);
}
$processor_settings = $facet->getOption('processors');
$form['#tree'] = TRUE;
$form['#attached']['library'][] = 'search_api/drupal.search_api.index-active-formatters';
$form['#attached']['library'][] = 'facets/drupal.facets.index-active-formatters';
$form['#title'] = $this->t('Manage processors for facet %label', array('%label' => $facet->label()));
// Add the list of all other processors with checkboxes to enable/disable
......@@ -219,12 +219,12 @@ class FacetDisplayForm extends EntityForm {
),
);
foreach ($all_processors as $processor_id => $processor) {
if (!($processor instanceof WidgetOrderProcessorInterface)) {
if (!($processor instanceof WidgetOrderProcessorInterface) && !($processor instanceof UrlProcessorInterface)) {
$clean_css_id = Html::cleanCssIdentifier($processor_id);
$form['facet_settings'][$processor_id]['status'] = array(
'#type' => 'checkbox',
'#title' => (string) $processor->getPluginDefinition()['label'],
'#default_value' => $processor->isLocked() || !empty($processor_settings[$processor_id]),
'#default_value' => $processor->isLocked() || !empty($enabled_processors[$processor_id]),
'#description' => $processor->getDescription(),
'#attributes' => array(
'class' => array(
......@@ -280,7 +280,7 @@ class FacetDisplayForm extends EntityForm {
$form['facet_sorting'][$processor_id]['status'] = array(
'#type' => 'checkbox',
'#title' => (string) $processor->getPluginDefinition()['label'],
'#default_value' => $processor->isLocked() || !empty($processor_settings[$processor_id]),
'#default_value' => $processor->isLocked() || !empty($enabled_processors[$processor_id]),
'#description' => $processor->getDescription(),
'#attributes' => array(
'class' => array(
......@@ -326,8 +326,7 @@ class FacetDisplayForm extends EntityForm {
'#default_value' => $facet->getOnlyVisibleWhenFacetSourceIsVisible(),
];
// Behavior for empty facets.
$empty_behavior_config = $facet->getOption('empty_behavior');
$empty_behavior_config = $facet->getEmptyBehavior();
$form['facet_settings']['empty_behavior'] = [
'#type' => 'radios',
'#title' => t('Empty facet behavior'),
......@@ -352,6 +351,21 @@ class FacetDisplayForm extends EntityForm {
'#default_value' => isset($empty_behavior_config['text_format']) ? $empty_behavior_config['text'] : '',
];
$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['facet_settings']['exclude'] = [
'#type' => 'checkbox',
'#title' => $this->t('Exclude'),
'#description' => $this->t('Make the search exclude selected facets, instead of restricting it to them.'),
'#default_value' => $facet->getExclude(),
];
$form['weights'] = array(
'#type' => 'details',
'#title' => t('Advanced settings'),
......@@ -384,6 +398,8 @@ class FacetDisplayForm extends EntityForm {
);
}
$processor_settings = $facet->getProcessorConfigs();
// Fill in the containers previously created with the processors that are
// enabled on the facet.
foreach ($processors_by_stage as $stage => $processors) {
......@@ -469,7 +485,6 @@ class FacetDisplayForm extends EntityForm {
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
$new_settings = array();
// Store processor settings.
// @todo Go through all available processors, enable/disable with method on
......@@ -482,16 +497,16 @@ class FacetDisplayForm extends EntityForm {
foreach ($processors as $processor_id => $processor) {
$form_container_key = $processor instanceof WidgetOrderProcessorInterface ? 'facet_sorting' : 'facet_settings';
if (empty($values[$form_container_key][$processor_id]['status'])) {
$facet->removeProcessor($processor_id);
continue;
}
$new_settings[$processor_id] = array(
$new_settings = array(
'processor_id' => $processor_id,
'weights' => array(),
'settings' => array(),
);
$processor_values = $values[$form_container_key][$processor_id];
if (!empty($processor_values['weights'])) {
$new_settings[$processor_id]['weights'] = $processor_values['weights'];
if (!empty($values['processors'][$processor_id]['weights'])) {
$new_settings['weights'] = $values['processors'][$processor_id]['weights'];
}
if (isset($form[$form_container_key][$processor_id]['settings'])) {
$processor_form_state = new SubFormState(
......@@ -499,16 +514,14 @@ class FacetDisplayForm extends EntityForm {
array($form_container_key, $processor_id, 'settings')
);
$processor->submitConfigurationForm($form[$form_container_key][$processor_id]['settings'], $processor_form_state, $facet);
$new_settings[$processor_id]['settings'] = $processor->getConfiguration();
$new_settings['settings'] = $processor->getConfiguration();
}
$facet->addProcessor($new_settings);
}
// Sort the processors so we won't have unnecessary changes.
ksort($new_settings);
$facet->setOption('processors', $new_settings);
$facet->setWidget($form_state->getValue('widget'));
$facet->set('widget_configs', $form_state->getValue('widget_configs'));
$facet->set('only_visible_when_facet_source_is_visible', $form_state->getValue(['facet_settings', 'only_visible_when_facet_source_is_visible']));
$facet->setWidgetConfigs($form_state->getValue('widget_configs'));
$facet->setOnlyVisibleWhenFacetSourceIsVisible($form_state->getValue(['facet_settings', 'only_visible_when_facet_source_is_visible']));
$empty_behavior_config = [];
$empty_behavior = $form_state->getValue(['facet_settings', 'empty_behavior']);
......@@ -527,14 +540,18 @@ class FacetDisplayForm extends EntityForm {
'value',
]);
}
$facet->setOption('empty_behavior', $empty_behavior_config);
$facet->setEmptyBehavior($empty_behavior_config);
$facet->setQueryOperator($form_state->getValue(['facet_settings', 'query_operator']));
$facet->setExclude($form_state->getValue(['facet_settings', 'exclude']));
$facet->save();
drupal_set_message(t('Facet %name has been updated.', ['%name' => $facet->getName()]));
}
/**
* Form submission handler for the widget subform.
* Handles form submissions for the widget subform.
*/
public function submitAjaxWidgetConfigForm($form, FormStateInterface $form_state) {
$form_state->setRebuild();
......
......@@ -22,7 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class FacetForm extends EntityForm {
/**
* The facet storage controller.
* The facet storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
......@@ -74,22 +74,6 @@ class FacetForm extends EntityForm {
return new static($entity_type_manager, $facet_source_plugin_manager, $processor_plugin_manager);
}
/**
* Gets the form entity.
*
* The form entity which has been used for populating form element defaults.
* This method is defined on the \Drupal\Core\Entity\EntityFormInterface and
* has the same contents there, we only extend to add the correct return type,
* this makes IDE's smarter about the other places where we use
* $this->getEntity().
*
* @return \Drupal\facets\FacetInterface
* The current form facet entity.
*/
public function getEntity() {
return $this->entity;
}
/**
* Retrieves the facet storage controller.
*
......@@ -174,11 +158,15 @@ class FacetForm extends EntityForm {
];
$form['url_alias'] = [
'#type' => 'textfield',
'#type' => 'machine_name',
'#title' => $this->t('The name of the facet for usage in URLs'),
'#default_value' => $facet->getUrlAlias(),
'#maxlength' => 50,
'#required' => TRUE,
'#machine_name' => [
'exists' => [$this->getFacetStorage(), 'load'],
'source' => ['name'],
],
];
$facet_sources = [];
......@@ -230,9 +218,10 @@ class FacetForm extends EntityForm {
}
/**
* Form submission handler for the facet source subform.
* Handles form submissions for the facet source subform.
*/
public function submitAjaxFacetSourceConfigForm($form, FormStateInterface $form_state) {
$form_state->setValue('id', NULL);
$form_state->setRebuild();
}
......@@ -256,9 +245,9 @@ class FacetForm extends EntityForm {
if (!is_null($facet_source_id) && $facet_source_id !== '') {
/** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */
$facet_source = $this->getFacetSourcePluginManager()->createInstance($facet_source_id);
$facet_source = $this->getFacetSourcePluginManager()->createInstance($facet_source_id, ['facet' => $this->getEntity()]);
if ($config_form = $facet_source->buildConfigurationForm([], $form_state, $this->getEntity(), $facet_source)) {
if ($config_form = $facet_source->buildConfigurationForm([], $form_state)) {
$form['facet_source_configs'][$facet_source_id]['#type'] = 'container';
$form['facet_source_configs'][$facet_source_id]['#title'] = $this->t('%plugin settings', ['%plugin' => $facet_source->getPluginDefinition()['label']]);
$form['facet_source_configs'][$facet_source_id] += $config_form;
......@@ -271,6 +260,13 @@ class FacetForm extends EntityForm {
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$facet_source_id = $form_state->getValue('facet_source_id');
if (!is_null($facet_source_id) && $facet_source_id !== '') {
/** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */
$facet_source = $this->getFacetSourcePluginManager()->createInstance($facet_source_id, ['facet' => $this->getEntity()]);
$facet_source->validateConfigurationForm($form, $form_state);
}
}
/**
......@@ -285,7 +281,6 @@ class FacetForm extends EntityForm {
if ($is_new) {
// On facet creation, enable all locked processors by default, using their
// default settings.
$initial_settings = [];
$stages = $this->getProcessorPluginManager()->getProcessingStages();
$processors_definitions = $this->getProcessorPluginManager()->getDefinitions();
......@@ -297,39 +292,38 @@ class FacetForm extends EntityForm {
$weights[$stage_id] = $processor['stages'][$stage_id];
}
}
$initial_settings[$processor_id] = array(
$facet->addProcessor([
'processor_id' => $processor_id,
'weights' => $weights,
'settings' => [],
);
]);
}
}
$facet->setOption('processors', $initial_settings);
// Set a default widget for new facets.
$facet->setWidget('links');
// Set default empty behaviour.
$facet->setOption('empty_behavior', ['behavior' => 'none']);
$facet->setEmptyBehavior(['behavior' => 'none']);
$facet->setOnlyVisibleWhenFacetSourceIsVisible(TRUE);
}
// Make sure the field identifier is copied from within the facet source
// config to the facet object and saved there.
$facet_source = $form_state->getValue('facet_source_id');
$field_identifier = $form_state->getValue('facet_source_configs')[$facet_source]['field_identifier'];
$facet->setFieldIdentifier($field_identifier);
$facet_source_id = $form_state->getValue('facet_source_id');
if (!is_null($facet_source_id) && $facet_source_id !== '') {
/** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */
$facet_source = $this->getFacetSourcePluginManager()->createInstance($facet_source_id, ['facet' => $this->getEntity()]);
$facet_source->submitConfigurationForm($form, $form_state);
}
$facet->save();
// Ensure that the caching of the view display is disabled, so the search
// correctly returns the facets. This is a temporary fix, until the cache
// metadata is correctly stored on the facet block. Only apply this when the
// facet source type is actually something this is related to views.
list($type,) = explode(':', $facet_source);
list($type,) = explode(':', $facet_source_id);
if ($type === 'search_api_views') {
list(, $view_id, $display) = explode(':', $facet_source);
list(, $view_id, $display) = explode(':', $facet_source_id);
}
if (isset($view_id)) {
......@@ -342,7 +336,7 @@ class FacetForm extends EntityForm {
if ($is_new) {
if (\Drupal::moduleHandler()->moduleExists('block')) {
$message = $this->t('Facet %name has been created. Go to the <a href=":block_overview">Block overview page</a> and add a new "Facet block". If this is your first and only facet, just adding that block make it link to this facet, if you have addded more facets already, please make sure to select the correct Facet to render.', ['%name' => $facet->getName(), ':block_overview' => \Drupal::urlGenerator()->generateFromRoute('block.admin_display')]);
$message = $this->t('Facet %name has been created. Go to the <a href=":block_overview">Block overview page</a> to place the new block in the desired region.', ['%name' => $facet->getName(), ':block_overview' => \Drupal::urlGenerator()->generateFromRoute('block.admin_display')]);
drupal_set_message($message);
$form_state->setRedirect('entity.facets_facet.display_form', ['facets_facet' => $facet->id()]);
}
......
......@@ -8,8 +8,11 @@
namespace Drupal\facets\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\Entity\FacetSource;
use Drupal\facets\UrlProcessor\UrlProcessorPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for editing facet sources.
......@@ -19,11 +22,38 @@ use Drupal\facets\Entity\FacetSource;
*/
class FacetSourceEditForm extends EntityForm {
/**
* The plugin manager for URL Processors.
*
* @var \Drupal\facets\UrlProcessor\UrlProcessorPluginManager
*/
protected $urlProcessorPluginManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $container->get('entity_type.manager');
/** @var \Drupal\facets\UrlProcessor\UrlProcessorPluginManager $url_processor_plugin_manager */
$url_processor_plugin_manager = $container->get('plugin.manager.facets.url_processor');
return new static($entity_type_manager, $url_processor_plugin_manager);
}
/**
* Constructs a FacetSourceEditForm.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\facets\UrlProcessor\UrlProcessorPluginManager $url_processor_plugin_manager
* The url processor plugin manager.
*/
public function __construct() {
$facet_source_storage = \Drupal::entityTypeManager()->getStorage('facets_facet_source');
public function __construct(EntityTypeManagerInterface $entity_type_manager, UrlProcessorPluginManager $url_processor_plugin_manager) {
$facet_source_storage = $entity_type_manager->getStorage('facets_facet_source');
$this->urlProcessorPluginManager = $url_processor_plugin_manager;
// Make sure we remove colons from the source id, those are disallowed in
// the entity id.
......@@ -36,7 +66,6 @@ class FacetSourceEditForm extends EntityForm {
$this->setEntity($facet_source);
}
else {
// We didn't have a facet source config entity yet for this facet source
// plugin, so we create it on the fly.
$facet_source = new FacetSource(
......@@ -68,8 +97,7 @@ class FacetSourceEditForm extends EntityForm {
/** @var \Drupal\facets\FacetSourceInterface $facet_source */
$facet_source = $this->getEntity();
// Filter key setting.
$form['filterKey'] = [
$form['filter_key'] = [
'#type' => 'textfield',
'#title' => $this->t('Filter key'),
'#size' => 20,
......@@ -81,6 +109,21 @@ class FacetSourceEditForm extends EntityForm {
),
];
$url_processors = array();
$url_processors_description = array();
foreach ($this->urlProcessorPluginManager->getDefinitions() as $definition) {
$url_processors[$definition['id']] = $definition['label'];
$url_processors_description[] = $definition['description'];
}
$form['url_processor'] = [
'#type' => 'radios',
'#title' => $this->t('URL Processor'),
'#options' => $url_processors,
'#default_value' => $facet_source->getUrlProcessorName(),
'#description' => $this->t(
'The URL Processor defines the url structure used for this facet source.') . '<br />- ' . implode('<br>- ', $url_processors_description),
];
// The parent's form build method will add a save button.
return parent::buildForm($form, $form_state);
}
......
......@@ -3,40 +3,43 @@
/**
* @file
* Contains Drupal\facets\Plugin\Block\FacetBlock.
*
* NOTE: There should be a facetblock or settings for the facets later.
*/
namespace Drupal\facets\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\facets\FacetManager\DefaultFacetManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'FacetBlock' block.
* Exposes a facet rendered as a block.
*
* @Block(
* id = "facet_block",
* admin_label = @Translation("Facet block"),
* context = {
* "facet" = @ContextDefinition("entity:facets_facet", label=@Translation("Facet"))
* }
* id = "facet_block",
* deriver = "Drupal\facets\Plugin\Block\FacetBlockDeriver"
* )
*/
class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The facet_manager plugin manager.
* The facet manager.
*
* @var DefaultFacetManager
* @var \Drupal\facets\FacetManager\DefaultFacetManager
*/
protected $facetManager;
/**
* Construct.
* The entity storage used for facets.
*
* @var \Drupal\Core\Entity\EntityStorageInterface $facetStorage
*/
protected $facetStorage;
/**
* Construct a FacetBlock instance.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
......@@ -45,10 +48,13 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
* @param string $plugin_definition
* The plugin implementation definition.
* @param \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager
* The facet manager service.
* The facet manager.
* @param \Drupal\Core\Entity\EntityStorageInterface $facet_storage
* The entity storage used for facets.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, DefaultFacetManager $facet_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, DefaultFacetManager $facet_manager, EntityStorageInterface $facet_storage) {
$this->facetManager = $facet_manager;
$this->facetStorage = $facet_storage;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
......@@ -56,15 +62,17 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
$facet_manager = $container->get('facets.manager');
/** @var \Drupal\Core\Entity\EntityStorageInterface $facet_storage */
$facet_storage = $container->get('entity_type.manager')->getStorage('facets_facet');
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$facet_manager
$facet_manager,
$facet_storage
);
}
......@@ -72,35 +80,57 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
* {@inheritdoc}
*/
public function build() {
/** @var Facet $facet */
$facet = $this->getContextValue('facet');
// The id saved in the configuration is in the format of
// base_plugin:facet_id. We're splitting that to get to the facet id.
$facet_mapping = $this->configuration['id'];
$facet_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $facet_mapping)[1];
/** @var \Drupal\facets\FacetInterface $facet */
$facet = $this->facetStorage->load($facet_id);
// Let the facet_manager build the facets.
$build = $this->facetManager->build($facet);
// Add contextual links only when we have results.
if (!empty($build)) {
$build['#contextual_links']['facets_facet'] = array(
'route_parameters' => array('facets_facet' => $facet->id()),
);
$build['#contextual_links']['facets_facet'] = [
'route_parameters' => ['facets_facet' => $facet->id()],
];
}
return $build;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
parent::blockSubmit($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// Makes sure a facet block is never cached.
// @TODO Make blocks cacheable, see: https://www.drupal.org/node/2581629
// A facet block cannot be cached, because it must always match the current
// search results, and Search API gets those search results from a data
// source that can be external to Drupal. Therefore it is impossible to
// guarantee that the search results are in sync with the data managed by
// Drupal. Consequently, it is not possible to cache the search results at
// all. If the search results cannot be cached, then neither can the facets,
// because they must always match.
// Fortunately, facet blocks are rendered using a lazy builder (like all
// blocks in Drupal), which means their rendering can be deferred (unlike
// the search results, which are the main content of the page, and deferring
// their rendering would mean sending an empty page to the user). This means
// that facet blocks can be rendered and sent *after* the initial page was
// loaded, by installing the BigPipe (big_pipe) module.
//
// When BigPipe is enabled, the search results will appear first, and then
// each facet block will appear one-by-one, in DOM order.
// See https://www.drupal.org/project/big_pipe.
//
// In a future version of Facet API, this could be refined, but due to the
// reliance on external data sources, it will be very difficult if not
// impossible to improve this significantly.
//
// Note: when using Drupal core's Search module instead of the contributed
// Search API module, the above limitations do not apply, but for now it is
// not considered worth the effort to optimize this just for Drupal core's
// Search.
return 0;
}
......@@ -108,22 +138,15 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
* {@inheritdoc}
*/
public function calculateDependencies() {
$facet_context_mapping = $this->configuration['context_mapping']['facet'];
$facet_id = explode(':', $facet_context_mapping)[1];
$em = \Drupal::getContainer()->get('entity_type.manager');
// The ID saved in the configuration is of the format
// 'base_plugin:facet_id'. We're splitting that to get to the facet ID.
$facet_mapping = $this->configuration['id'];
$facet_id = explode(PluginBase::DERIVATIVE_SEPARATOR, $facet_mapping)[1];
/** @var \Drupal\facets\FacetInterface $facet */
$facets = $em->getStorage('facets_facet')
->loadByProperties(['uuid' => $facet_id]);
$keys = array_keys($facets);
$facet = $facets[$keys[0]];
$config_name = $facet->getConfigDependencyName();
$facet = $this->facetStorage->load($facet_id);
return ['config' => [$config_name]];
return ['config' => [$facet->getConfigDependencyName()]];
}
}