Commit 779647c6 authored by StryKaizer's avatar StryKaizer Committed by borisson_

Issue #2896229 by borisson_, StryKaizer, joachim, ekes: move date handling to...

Issue #2896229 by borisson_, StryKaizer, joachim, ekes: move date handling to a processor, so date facets can use existing widgets
parent fcd581df
......@@ -98,3 +98,17 @@ plugin.plugin_configuration.facets_processor.hide_active_items_processor:
plugin.plugin_configuration.facets_processor.list_item:
type: config_object
plugin.plugin_configuration.facets_processor.date_item:
type: mapping
label: 'Date item processor'
mapping:
date_display:
type: string
label: 'Date display'
granularity:
type: integer
label: 'Granularity'
date_format:
type: string
label: 'Date format'
......@@ -48,19 +48,3 @@ facet.widget.config.links:
# "soft limit" and "show counts".
facet.widget.config.checkbox:
type: facet.widget.config.links
# Config schema for datebasic, you can find the implementation in
# Drupal\facets\Plugin\facets\widget\DateBasicWidget.
facet.widget.config.datebasic:
type: facet.widget.default_config
label: 'List of dates configuration'
mapping:
display_relative:
type: boolean
label: 'Display relative'
granularity:
type: integer
label: 'Granularity'
date_display:
type: string
label: 'Date display'
......@@ -96,3 +96,31 @@ function facets_update_8003() {
->condition('name', 'core_search_facets')
->execute();
}
/**
* Migrate facets with date widget to use date processor and links widget.
*/
function facets_update_8004() {
foreach (Facet::loadMultiple() as $facet) {
$widget = $facet->getWidget();
if ($widget['type'] === 'datebasic') {
// Set widget to use links instead.
$facet->setWidget('links', ['show_numbers' => $widget['config']['show_numbers']]);
// Migrate widget to processor settings and enable date_item processor.
$settings = [
'date_format' => $widget['config']['date_display'],
'granularity' => $widget['config']['granularity'],
'date_display' => 'actual_date'
];
if ($widget['config']['display_relative']) {
$settings['date_display'] = 'relative_date';
}
$facet->addProcessor([
'processor_id' => 'date_item',
'weights' => ['build' => 35],
'settings' => $settings,
]);
$facet->save();
}
}
}
......@@ -54,8 +54,8 @@ class RangeSliderWidget extends SliderWidget {
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['range'];
public function getQueryType() {
return 'range';
}
}
......@@ -155,13 +155,6 @@ class SliderWidget extends WidgetPluginBase {
return $form;
}
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['string'];
}
/**
* {@inheritdoc}
*/
......
......@@ -31,7 +31,7 @@ class SliderWidgetTest extends WidgetTestBase {
*/
public function testGetQueryType() {
$result = $this->widget->getQueryType($this->queryTypes);
$this->assertEquals('string', $result);
$this->assertEquals(NULL, $result);
}
/**
......
......@@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\facets\Exception\Exception;
use Drupal\facets\Exception\InvalidProcessorException;
use Drupal\facets\Exception\InvalidQueryTypeException;
use Drupal\facets\FacetInterface;
/**
......@@ -455,9 +456,59 @@ class Facet extends ConfigEntityBase implements FacetInterface {
$widget = $this->getWidgetInstance();
// Give the widget the chance to select a preferred query type. This is
// useful for widget that have different query type. See the date widget,
// that needs to select the date query type.
return $widget->getQueryType($query_types);
// needed for widget that have different query type. For example the need
// for a range query.
$widgetQueryType = $widget->getQueryType();
// Allow widgets to also specify a query type.
$processorQueryTypes = [];
foreach ($this->getProcessors() as $processor) {
$pqt = $processor->getQueryType();
if ($pqt !== NULL) {
$processorQueryTypes[] = $pqt;
}
}
$processorQueryTypes = array_flip($processorQueryTypes);
// The widget has made no decision and neither have the processors.
if ($widgetQueryType === NULL && count($processorQueryTypes) === 0) {
return $this->pickQueryType($query_types, 'string');
}
// The widget has made no decision but the processors have made 1 decision.
if ($widgetQueryType === NULL && count($processorQueryTypes) === 1) {
return $this->pickQueryType($query_types, key($processorQueryTypes));
}
// The widget has made a decision and the processors have not.
if ($widgetQueryType !== NULL && count($processorQueryTypes) === 0) {
return $this->pickQueryType($query_types, $widgetQueryType);
}
// The widget has made a decision and the processors have 1, being the same.
if ($widgetQueryType !== NULL && count($processorQueryTypes) === 1 && key($processorQueryTypes) === $widgetQueryType) {
return $this->pickQueryType($query_types, $widgetQueryType);
}
// Invalid choice.
throw new InvalidQueryTypeException("Invalid query type combination in widget / processors. Widget: {$widgetQueryType}, Processors: " . implode(', ', array_keys($processorQueryTypes)) . ".");
}
/**
* Choose the query type.
*
* @param array $allTypes
* An array of query type definitions.
* @param string $type
* The chose query type.
*
* @return string
* The class name of the chose query type.
*
* @throws \Drupal\facets\Exception\InvalidQueryTypeException
*/
protected function pickQueryType(array $allTypes, $type) {
if (!isset($allTypes[$type])) {
throw new InvalidQueryTypeException("Query type {$type} doesn't exist.");
}
return $allTypes[$type];
}
/**
......
<?php
namespace Drupal\facets\Plugin\facets\widget;
namespace Drupal\facets\Plugin\facets\processor;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\FacetInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\Processor\BuildProcessorInterface;
use Drupal\facets\Processor\ProcessorPluginBase;
use Drupal\facets\Plugin\facets\query_type\SearchApiDate;
/**
* A simple widget class that returns a simple array of the facet results.
* Provides a processor for dates.
*
* @FacetsWidget(
* id = "date_array",
* label = @Translation("Array with raw results for date query type"),
* description = @Translation("A configurable widget that builds an array with results."),
* @FacetsProcessor(
* id = "date_item",
* label = @Translation("Date item processor"),
* description = @Translation("Display dates with granularity options for date fields."),
* stages = {
* "build" = 35
* }
* )
*/
class DateArrayWidget extends ArrayWidget {
class DateItemProcessor extends ProcessorPluginBase implements BuildProcessorInterface {
/**
* {@inheritdoc}
*/
public function build(FacetInterface $facet, array $results) {
$config = $this->getConfiguration();
return $results;
}
/**
* Human readable array of granularity options.
......@@ -34,61 +48,57 @@ class DateArrayWidget extends ArrayWidget {
];
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'display_relative' => FALSE,
'granularity' => SearchApiDate::FACETAPI_DATE_MONTH,
'date_display' => '',
'relative_granularity' => 1,
'relative_text' => TRUE,
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) {
$configuration = $this->getConfiguration();
$form += parent::buildConfigurationForm($form, $form_state, $facet);
$this->getConfiguration();
$form['display_relative'] = [
$build['date_display'] = [
'#type' => 'radios',
'#title' => $this->t('Date display'),
'#default_value' => $configuration['display_relative'],
'#default_value' => $this->getConfiguration()['date_display'],
'#options' => [
FALSE => $this->t('Actual date with granularity'),
TRUE => $this->t('Relative date'),
'actual_date' => $this->t('Actual date with granularity'),
'relative_date' => $this->t('Relative date'),
],
];
$form['granularity'] = [
$build['granularity'] = [
'#type' => 'radios',
'#title' => $this->t('Granularity'),
'#default_value' => $configuration['granularity'],
'#default_value' => $this->getConfiguration()['granularity'],
'#options' => $this->granularityOptions(),
];
$form['date_display'] = [
$build['date_format'] = [
'#type' => 'textfield',
'#title' => $this->t('Date format'),
'#default_value' => $configuration['date_display'],
'#default_value' => $this->getConfiguration()['date_format'],
'#description' => $this->t('Override default date format used for the displayed filter format. See the <a href="http://php.net/manual/function.date.php">PHP manual</a> for available options.'),
'#states' => [
'visible' => [':input[name="widget_config[display_relative]"]' => ['value' => 0]],
'visible' => [':input[name="facet_settings[date_item][settings][date_display]"]' => ['value' => 'actual_date']],
],
];
return $form;
return $build;
}
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['date'];
public function getQueryType() {
return 'date';
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'date_display' => 'actual_date',
'granularity' => SearchApiDate::FACETAPI_DATE_MONTH,
'date_format' => '',
];
}
}
......@@ -53,11 +53,27 @@ class SearchApiDate extends QueryTypeRangeBase {
*/
const FACETAPI_DATE_SECOND = 1;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$facet = $configuration['facet'];
$processors = $facet->getProcessors();
$dateProcessorConfig = $processors['date_item']->getConfiguration();
$configuration = $this->getConfiguration();
$configuration['granularity'] = $dateProcessorConfig['granularity'];
$configuration['date_display'] = $dateProcessorConfig['date_display'];
$configuration['date_format'] = $dateProcessorConfig['date_format'];
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public function calculateRange($value) {
if ($this->getDisplayRelative()) {
if ($this->getDateDisplay() === 'relative_date') {
return $this->calculateRangeRelative($value);
}
else {
......@@ -109,11 +125,10 @@ class SearchApiDate extends QueryTypeRangeBase {
$stopDate = $dateTime::createFromFormat('Y-m-d\TH:i:s', $value . ':59');
break;
case static::FACETAPI_DATE_SECOND:
default:
$startDate = $dateTime::createFromFormat('Y-m-d\TH:i:s', $value);
$stopDate = $dateTime::createFromFormat('Y-m-d\TH:i:s', $value);
break;
}
return [
......@@ -176,11 +191,10 @@ class SearchApiDate extends QueryTypeRangeBase {
$stopDate->sub(new \DateInterval('PT1S'));
break;
case static::FACETAPI_DATE_SECOND:
default:
$startDate = $dateTime::createFromFormat('Y-m-d\TH:i:s', $value);
$stopDate = clone $startDate;
break;
}
return [
......@@ -199,7 +213,7 @@ class SearchApiDate extends QueryTypeRangeBase {
* An array with a start and end date as unix timestamps.
*/
public function calculateResultFilter($value) {
if ($this->getDisplayRelative()) {
if ($this->getDateDisplay() === 'relative_date') {
return $this->calculateResultFilterRelative($value);
}
else {
......@@ -213,7 +227,7 @@ class SearchApiDate extends QueryTypeRangeBase {
public function calculateResultFilterAbsolute($value) {
$date = new DrupalDateTime();
$date->setTimestamp($value);
$date_display = $this->getDateFormat();
$date_format = $this->getDateFormat();
switch ($this->getGranularity()) {
case static::FACETAPI_DATE_YEAR:
......@@ -241,13 +255,13 @@ class SearchApiDate extends QueryTypeRangeBase {
$raw = $date->format('Y-m-d\TH:i');
break;
case static::FACETAPI_DATE_SECOND:
default:
$format = 'd/m/Y H:i:s';
$raw = $date->format('Y-m-d\TH:i:s');
break;
}
$format = $date_display ? $date_display : $format;
$format = $date_format ? $date_format : $format;
return [
'display' => $date->format($format),
'raw' => $raw,
......@@ -362,7 +376,7 @@ class SearchApiDate extends QueryTypeRangeBase {
$raw = $date->format('Y-m-d\TH:i:s');
break;
case static::FACETAPI_DATE_SECOND:
default:
$rounded = new \DateInterval('P' . $interval->y . 'Y' . $interval->m . 'M' . $interval->d . 'DT' . $interval->h . 'H' . $interval->i . $interval->s . 'S');
$display = $interval->y ? $this->formatPlural($interval->y, '1 year', '@count years') . ' ' : '';
$display .= $interval->m ? $this->formatPlural($interval->m, '1 month', '@count months') . ' ' : '';
......@@ -384,7 +398,6 @@ class SearchApiDate extends QueryTypeRangeBase {
}
$raw = $date->format('Y-m-d\TH:i:s');
break;
}
return [
......@@ -397,23 +410,32 @@ class SearchApiDate extends QueryTypeRangeBase {
* Retrieve configuration: Granularity to use.
*
* Default behaviour an integer for the steps that the facet works in.
*
* @return int
* The granularity for this config.
*/
protected function getGranularity() {
return $this->facet->getWidgetInstance()->getConfiguration()['granularity'];
return $this->getConfiguration()['granularity'];
}
/**
* Retrieve configuration: If the date should be displayed relatively.
* Retrieve configuration: Date Display type.
*
* @return string
* Returns the display mode..
*/
protected function getDisplayRelative() {
return $this->facet->getWidgetInstance()->getConfiguration()['display_relative'];
protected function getDateDisplay() {
return $this->getConfiguration()['date_display'];
}
/**
* Retrieve configuration: Date display format.
*
* @return string
* Returns the format.
*/
protected function getDateFormat() {
return $this->facet->getWidgetInstance()->getConfiguration()['date_display'];
return $this->getConfiguration()['date_format'];
}
}
......@@ -17,13 +17,6 @@ use Drupal\facets\Result\Result;
*/
class SearchApiRange extends QueryTypePluginBase {
/**
* The backend's native query object.
*
* @var \Drupal\search_api\Query\QueryInterface
*/
protected $query;
/**
* {@inheritdoc}
*/
......
......@@ -22,13 +22,6 @@ use Drupal\facets\Result\Result;
*/
class SearchApiString extends QueryTypePluginBase {
/**
* The backend's native query object.
*
* @var \Drupal\search_api\Query\QueryInterface
*/
protected $query;
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\facets\Plugin\facets\widget;
use Drupal\Core\Form\FormStateInterface;
use Drupal\facets\FacetInterface;
use Drupal\facets\Result\Result;
use Drupal\facets\Widget\WidgetPluginBase;
use Drupal\facets\Plugin\facets\query_type\SearchApiDate;
/**
* Basic date widget.
*
* @FacetsWidget(
* id = "datebasic",
* label = @Translation("Date list"),
* description = @Translation("A simple list of dates"),
* )
*/
class DateBasicWidget extends WidgetPluginBase {
/**
* Human readable array of granularity options.
*
* @return array
* An array of granularity options.
*/
private function granularityOptions() {
return [
SearchApiDate::FACETAPI_DATE_YEAR => $this->t('Year'),
SearchApiDate::FACETAPI_DATE_MONTH => $this->t('Month'),
SearchApiDate::FACETAPI_DATE_DAY => $this->t('Day'),
SearchApiDate::FACETAPI_DATE_HOUR => $this->t('Hour'),
SearchApiDate::FACETAPI_DATE_MINUTE => $this->t('Minute'),
SearchApiDate::FACETAPI_DATE_SECOND => $this->t('Second'),
];
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'display_relative' => FALSE,
'granularity' => SearchApiDate::FACETAPI_DATE_MONTH,
'date_display' => '',
'relative_granularity' => 1,
'relative_text' => TRUE,
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) {
$configuration = $this->getConfiguration();
$form += parent::buildConfigurationForm($form, $form_state, $facet);
$form['display_relative'] = [
'#type' => 'radios',
'#title' => $this->t('Date display'),
'#default_value' => $configuration['display_relative'],
'#options' => [
FALSE => $this->t('Actual date with granularity'),
TRUE => $this->t('Relative date'),
],
];
$form['granularity'] = [
'#type' => 'radios',
'#title' => $this->t('Granularity'),
'#default_value' => $configuration['granularity'],
'#options' => $this->granularityOptions(),
];
$form['date_display'] = [
'#type' => 'textfield',
'#title' => $this->t('Date format'),
'#default_value' => $configuration['date_display'],
'#description' => $this->t('Override default date format used for the displayed filter format. See the <a href="http://php.net/manual/function.date.php">PHP manual</a> for available options.'),
'#states' => [
'visible' => [':input[name="widget_config[display_relative]"]' => ['value' => 0]],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['date'];
}
/**
* {@inheritdoc}
*/
public function build(FacetInterface $facet) {
$this->facet = $facet;
$items = array_map(function (Result $result) use ($facet) {
if (empty($result->getUrl())) {
return ['#markup' => $this->extractText($result)];
}
else {
return $this->buildListItems($facet, $result);
}
}, $facet->getResults());
return [
'#theme' => $this->getFacetItemListThemeHook($facet),
'#items' => $items,
'#attributes' => ['data-drupal-facet-id' => $facet->id()],
'#cache' => [
'contexts' => [
'url.path',
'url.query_args',
],
],
];
}
}
......@@ -92,11 +92,4 @@ class LinksWidget extends WidgetPluginBase {
return $form;
}
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['string'];
}
}
......@@ -46,8 +46,8 @@ class NumericGranularWidget extends LinksWidget {
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['numeric'];
public function getQueryType() {
return 'numeric';
}
}
......@@ -126,4 +126,13 @@ interface ProcessorInterface extends ConfigurablePluginInterface, PluginInspecti
*/
public function supportsFacet(FacetInterface $facet);
/**
* Picks the preferred query type for this widget.
*
* @return string|null
* The query type machine name to load or NULL to load the default query
* type.
*/
public function getQueryType();
}
......@@ -114,4 +114,11 @@ class ProcessorPluginBase extends PluginBase implements ProcessorInterface {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getQueryType() {
return NULL;
}
}
......@@ -37,7 +37,8 @@ abstract class QueryTypeRangeBase extends QueryTypePluginBase {
foreach ($active_items as $value) {
$range = $this->calculateRange($value);
$item_filter = $query->createConditionGroup($exclude ? 'OR' : 'AND', ['facet:' . $field_identifier]);
$conjunction = $exclude ? 'OR' : 'AND';
$item_filter = $query->createConditionGroup($conjunction, ['facet:' . $field_identifier]);
$item_filter->addCondition($this->facet->getFieldIdentifier(), $range['start'], $exclude ? '<' : '>=');
$item_filter->addCondition($this->facet->getFieldIdentifier(), $range['stop'], $exclude ? '>' : '<=');
......
......@@ -123,8 +123,8 @@ abstract class WidgetPluginBase extends PluginBase implements WidgetPluginInterf
/**
* {@inheritdoc}
*/
public function getQueryType(array $query_types) {
return $query_types['string'];
public function getQueryType() {
return NULL;
}
/**
......
......@@ -25,13 +25,11 @@ interface WidgetPluginInterface extends ConfigurablePluginInterface {
/**
* Picks the preferred query type for this widget.
*
* @param string[] $query_types
* An array keyed with query type name and it's plugin class to load.
*
* @return string
* The query type plugin class to load.
* @return string|null
* The query type machine name to load or NULL to load the default query
* type.
*/
public function getQueryType(array $query_types);
public function getQueryType();
/**
* Checks is a specific property is required for this widget.
......
<?php
namespace Drupal\facets_custom_widget\Plugin\facets\processor;
use Drupal\facets\Processor\ProcessorPluginBase;