first commit
This commit is contained in:
290
Classes/Domain/Search/ResultSet/Facets/AbstractFacet.php
Normal file
290
Classes/Domain/Search/ResultSet/Facets/AbstractFacet.php
Normal file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Value object that represent a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractFacet
|
||||
{
|
||||
const TYPE_ABSTRACT = 'abstract';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_ABSTRACT;
|
||||
|
||||
/**
|
||||
* The resultSet where this facet belongs to.
|
||||
*
|
||||
* @var SearchResultSet
|
||||
*/
|
||||
protected $resultSet = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isAvailable = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isUsed = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $allRequirementsMet = true;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* AbstractFacet constructor.
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
$this->resultSet = $resultSet;
|
||||
$this->name = $name;
|
||||
$this->field = $field;
|
||||
$this->label = $label;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the object manager
|
||||
*
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get solr field name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getField()
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
*/
|
||||
public function setLabel($label)
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $isAvailable
|
||||
*/
|
||||
public function setIsAvailable($isAvailable)
|
||||
{
|
||||
$this->isAvailable = $isAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsAvailable()
|
||||
{
|
||||
return $this->isAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $isUsed
|
||||
*/
|
||||
public function setIsUsed($isUsed)
|
||||
{
|
||||
$this->isUsed = $isUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsUsed()
|
||||
{
|
||||
return $this->isUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return static::$type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getAllRequirementsMet()
|
||||
{
|
||||
return $this->allRequirementsMet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $allRequirementsMet
|
||||
*/
|
||||
public function setAllRequirementsMet($allRequirementsMet)
|
||||
{
|
||||
$this->allRequirementsMet = $allRequirementsMet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function getResultSet()
|
||||
{
|
||||
return $this->resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfiguration()
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get facet partial name used for rendering the facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartialName()
|
||||
{
|
||||
return 'Default';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupName()
|
||||
{
|
||||
return isset($this->configuration['groupName']) ? $this->configuration['groupName'] : 'main';
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this facet should ne included in the available facets. When nothing is configured,
|
||||
* the method return TRUE.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIncludeInAvailableFacets()
|
||||
{
|
||||
return ((int)$this->getFacetSettingOrDefaultValue('includeInAvailableFacets', 1)) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this facets should be included in the used facets. When nothing is configured,
|
||||
* the methods returns true.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIncludeInUsedFacets()
|
||||
{
|
||||
|
||||
return ((int)$this->getFacetSettingOrDefaultValue('includeInUsedFacets', 1)) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured requirements
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequirements()
|
||||
{
|
||||
return $this->getFacetSettingOrDefaultValue('requirements.', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of this method should return a "flatten" collection of all items.
|
||||
*
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
abstract public function getAllFacetItems();
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $defaultValue
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFacetSettingOrDefaultValue($key, $defaultValue)
|
||||
{
|
||||
if (!isset($this->configuration[$key])) {
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
return ($this->configuration[$key]);
|
||||
}
|
||||
}
|
116
Classes/Domain/Search/ResultSet/Facets/AbstractFacetItem.php
Normal file
116
Classes/Domain/Search/ResultSet/Facets/AbstractFacetItem.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Abstract item that represent a value of a facet. E.g. an option or a node
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractFacetItem
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $documentCount = 0;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $selected = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $metrics = [];
|
||||
|
||||
/**
|
||||
* @var AbstractFacet
|
||||
*/
|
||||
protected $facet;
|
||||
|
||||
/**
|
||||
* @param AbstractFacet $facet
|
||||
* @param string $label
|
||||
* @param int $documentCount
|
||||
* @param bool $selected
|
||||
* @param array $metrics
|
||||
*/
|
||||
public function __construct(AbstractFacet $facet, $label = '', $documentCount = 0, $selected = false, $metrics = [])
|
||||
{
|
||||
$this->facet = $facet;
|
||||
$this->label = $label;
|
||||
$this->documentCount = $documentCount;
|
||||
$this->selected = $selected;
|
||||
$this->metrics = $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDocumentCount()
|
||||
{
|
||||
return $this->documentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet
|
||||
*/
|
||||
public function getFacet()
|
||||
{
|
||||
return $this->facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getSelected()
|
||||
{
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMetrics()
|
||||
{
|
||||
return $this->metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getUriValue();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract function getCollectionKey();
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItem;
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* Collection for facet options.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractFacetItemCollection extends AbstractCollection
|
||||
{
|
||||
/**
|
||||
* @param AbstractFacetItem $item
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function add($item)
|
||||
{
|
||||
if ($item === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->data[$item->getCollectionKey()] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return AbstractFacetItem
|
||||
*/
|
||||
public function getByValue($value)
|
||||
{
|
||||
return isset($this->data[$value]) ? $this->data[$value] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the count (with get prefixed to be usable in fluid).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
return $this->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getSelected()
|
||||
{
|
||||
return $this->getFilteredCopy(function(AbstractFacetItem $item) {
|
||||
return $item->getSelected();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $manualSorting
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getManualSortedCopy(array $manualSorting)
|
||||
{
|
||||
$result = clone $this;
|
||||
$copiedItems = $result->data;
|
||||
$sortedOptions = [];
|
||||
foreach ($manualSorting as $item) {
|
||||
if (isset($copiedItems[$item])) {
|
||||
$sortedOptions[$item] = $copiedItems[$item];
|
||||
unset($copiedItems[$item]);
|
||||
}
|
||||
}
|
||||
// in the end all items get appended that are not configured in the manual sort order
|
||||
$sortedOptions = $sortedOptions + $copiedItems;
|
||||
$result->data = $sortedOptions;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getReversedOrderCopy()
|
||||
{
|
||||
$result = clone $this;
|
||||
$result->data = array_reverse($result->data, true);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractFacetPackage
|
||||
*/
|
||||
abstract class AbstractFacetPackage {
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function setObjectManager(ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getParserClassName();
|
||||
|
||||
/**
|
||||
* @return FacetParserInterface
|
||||
* @throws InvalidFacetPackageException
|
||||
*/
|
||||
public function getParser()
|
||||
{
|
||||
$parser = $this->objectManager->get($this->getParserClassName());
|
||||
if (!$parser instanceof FacetParserInterface) {
|
||||
throw new InvalidFacetPackageException('Invalid parser for package ' . __CLASS__);
|
||||
}
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlDecoderClassName() {
|
||||
return (string)DefaultUrlDecoder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidUrlDecoderException
|
||||
* @return FacetUrlDecoderInterface
|
||||
*/
|
||||
public function getUrlDecoder()
|
||||
{
|
||||
$urlDecoder = $this->objectManager->get($this->getUrlDecoderClassName());
|
||||
if (!$urlDecoder instanceof FacetUrlDecoderInterface) {
|
||||
throw new InvalidUrlDecoderException('Invalid urldecoder for package ' . __CLASS__);
|
||||
}
|
||||
return $urlDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryBuilderClassName() {
|
||||
return (string)DefaultFacetQueryBuilder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidQueryBuilderException
|
||||
* @return FacetQueryBuilderInterface
|
||||
*/
|
||||
public function getQueryBuilder()
|
||||
{
|
||||
$urlDecoder = $this->objectManager->get($this->getQueryBuilderClassName());
|
||||
if(!$urlDecoder instanceof FacetQueryBuilderInterface) {
|
||||
throw new InvalidQueryBuilderException('Invalid querybuilder for package ' . __CLASS__);
|
||||
}
|
||||
return $urlDecoder;
|
||||
}
|
||||
}
|
189
Classes/Domain/Search/ResultSet/Facets/AbstractFacetParser.php
Normal file
189
Classes/Domain/Search/ResultSet/Facets/AbstractFacetParser.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionsFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Class AbstractFacetParser
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractFacetParser implements FacetParserInterface
|
||||
{
|
||||
/**
|
||||
* @var ContentObjectRenderer
|
||||
*/
|
||||
protected static $reUseAbleContentObject;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function injectObjectManager(ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContentObjectRenderer
|
||||
*/
|
||||
protected function getReUseAbleContentObject()
|
||||
{
|
||||
/** @var $contentObject ContentObjectRenderer */
|
||||
if (self::$reUseAbleContentObject !== null) {
|
||||
return self::$reUseAbleContentObject;
|
||||
}
|
||||
|
||||
self::$reUseAbleContentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
return self::$reUseAbleContentObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $configuration
|
||||
* @return string
|
||||
*/
|
||||
protected function getPlainLabelOrApplyCObject($configuration)
|
||||
{
|
||||
// when no label is configured we return an empty string
|
||||
if (!isset($configuration['label'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// when no sub configuration is set, we use the string, configured as label
|
||||
if (!isset($configuration['label.'])) {
|
||||
return $configuration['label'];
|
||||
}
|
||||
|
||||
// when label and label. was set, we apply the cObject
|
||||
return $this->getReUseAbleContentObject()->cObjGetSingle($configuration['label'], $configuration['label.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param integer $count
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return string
|
||||
*/
|
||||
protected function getLabelFromRenderingInstructions($value, $count, $facetName, $facetConfiguration)
|
||||
{
|
||||
$hasRenderingInstructions = isset($facetConfiguration['renderingInstruction']) && isset($facetConfiguration['renderingInstruction.']);
|
||||
if (!$hasRenderingInstructions) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->getReUseAbleContentObject()->start(['optionValue' => $value, 'optionCount' => $count, 'facetName' => $facetName]);
|
||||
return $this->getReUseAbleContentObject()->cObjGetSingle(
|
||||
$facetConfiguration['renderingInstruction'],
|
||||
$facetConfiguration['renderingInstruction.']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the active facetValue for a facet from the search request.
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @return array
|
||||
*/
|
||||
protected function getActiveFacetValuesFromRequest(SearchResultSet $resultSet, $facetName)
|
||||
{
|
||||
$activeFacetValues = $resultSet->getUsedSearchRequest()->getActiveFacetValuesByName($facetName);
|
||||
$activeFacetValues = is_array($activeFacetValues) ? $activeFacetValues : [];
|
||||
|
||||
return $activeFacetValues;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $facetValuesFromSolrResponse
|
||||
* @param array $facetValuesFromSearchRequest
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getMergedFacetValueFromSearchRequestAndSolrResponse($facetValuesFromSolrResponse, $facetValuesFromSearchRequest)
|
||||
{
|
||||
$facetValueItemsToCreate = $facetValuesFromSolrResponse;
|
||||
|
||||
foreach ($facetValuesFromSearchRequest as $valueFromRequest) {
|
||||
// if we have options in the request that have not been in the response we add them with a count of 0
|
||||
if (!isset($facetValueItemsToCreate[$valueFromRequest])) {
|
||||
$facetValueItemsToCreate[$valueFromRequest] = 0;
|
||||
}
|
||||
}
|
||||
return $facetValueItemsToCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractOptionsFacet $facet
|
||||
* @param array $facetConfiguration
|
||||
* @return AbstractOptionsFacet
|
||||
*/
|
||||
protected function applyManualSortOrder(AbstractOptionsFacet $facet, array $facetConfiguration)
|
||||
{
|
||||
if (!isset($facetConfiguration['manualSortOrder'])) {
|
||||
return $facet;
|
||||
}
|
||||
$fields = GeneralUtility::trimExplode(',', $facetConfiguration['manualSortOrder']);
|
||||
// @extensionScannerIgnoreLine
|
||||
$sortedOptions = $facet->getOptions()->getManualSortedCopy($fields);
|
||||
|
||||
// @extensionScannerIgnoreLine
|
||||
$facet->setOptions($sortedOptions);
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractOptionsFacet $facet
|
||||
* @param array $facetConfiguration
|
||||
* @return AbstractOptionsFacet
|
||||
*/
|
||||
protected function applyReverseOrder(AbstractOptionsFacet $facet, array $facetConfiguration)
|
||||
{
|
||||
if (empty($facetConfiguration['reverseOrder'])) {
|
||||
return $facet;
|
||||
}
|
||||
|
||||
// @extensionScannerIgnoreLine
|
||||
$facet->setOptions($facet->getOptions()->getReversedOrderCopy());
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $facetConfiguration
|
||||
* @return boolean
|
||||
*/
|
||||
protected function getIsExcludedFacetValue($value, array $facetConfiguration)
|
||||
{
|
||||
if (!isset($facetConfiguration['excludeValues'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$excludedValue = GeneralUtility::trimExplode(',', $facetConfiguration['excludeValues']);
|
||||
return in_array($value, $excludedValue);
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
class DefaultFacetQueryBuilder implements FacetQueryBuilderInterface {
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$facetParameters = [];
|
||||
$facetConfiguration = $configuration->getSearchFacetingFacetByName($facetName);
|
||||
|
||||
$tags = $this->buildExcludeTags($facetConfiguration, $configuration);
|
||||
$facetParameters['facet.field'][] = $tags . $facetConfiguration['field'];
|
||||
|
||||
$sortingExpression = new SortingExpression();
|
||||
$facetSortExpression = $sortingExpression->getForFacet($facetConfiguration['sortBy']);
|
||||
if (!empty($facetSortExpression)) {
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.sort'] = $facetSortExpression;
|
||||
}
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $facetConfiguration
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return string
|
||||
*/
|
||||
protected function buildExcludeTags(array $facetConfiguration, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
// simple for now, may add overrides f.<field_name>.facet.* later
|
||||
if ($configuration->getSearchFacetingKeepAllFacetsOnSelection()) {
|
||||
$facets = [];
|
||||
foreach ($configuration->getSearchFacetingFacets() as $facet) {
|
||||
$facets[] = $facet['field'];
|
||||
}
|
||||
|
||||
return '{!ex=' . implode(',', $facets) . '}';
|
||||
} elseif ($facetConfiguration['keepAllOptionsOnSelection'] == 1) {
|
||||
return '{!ex=' . $facetConfiguration['field'] . '}';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
31
Classes/Domain/Search/ResultSet/Facets/DefaultUrlDecoder.php
Normal file
31
Classes/Domain/Search/ResultSet/Facets/DefaultUrlDecoder.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
class DefaultUrlDecoder implements FacetUrlDecoderInterface {
|
||||
|
||||
/**
|
||||
* Parses the query filter from GET parameters in the URL and translates it
|
||||
* to a Lucene filter value.
|
||||
*
|
||||
* @param string $value the filter query from plugin
|
||||
* @param array $configuration Facet configuration
|
||||
* @return string Value to be used in a Lucene filter
|
||||
*/
|
||||
public function decode($value, array $configuration = [])
|
||||
{
|
||||
return '"' . addslashes($value) . '"';
|
||||
}
|
||||
}
|
78
Classes/Domain/Search/ResultSet/Facets/FacetCollection.php
Normal file
78
Classes/Domain/Search/ResultSet/Facets/FacetCollection.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* Class FacetCollection
|
||||
*/
|
||||
class FacetCollection extends AbstractCollection
|
||||
{
|
||||
|
||||
/**
|
||||
* @param AbstractFacet $facet
|
||||
*/
|
||||
public function addFacet(AbstractFacet $facet)
|
||||
{
|
||||
$this->data[$facet->getName()] = $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FacetCollection
|
||||
*/
|
||||
public function getUsed()
|
||||
{
|
||||
return $this->getFilteredCopy(
|
||||
function(AbstractFacet $facet) {
|
||||
return $facet->getIsUsed() && $facet->getIncludeInUsedFacets();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FacetCollection
|
||||
*/
|
||||
public function getAvailable()
|
||||
{
|
||||
return $this->getFilteredCopy(
|
||||
function(AbstractFacet $facet) {
|
||||
return $facet->getIsAvailable() && $facet->getIncludeInAvailableFacets() && $facet->getAllRequirementsMet();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $requiredGroup
|
||||
* @return AbstractCollection
|
||||
*/
|
||||
public function getByGroupName($requiredGroup = 'all')
|
||||
{
|
||||
return $this->getFilteredCopy(
|
||||
function(AbstractFacet $facet) use ($requiredGroup) {
|
||||
return $facet->getGroupName() == $requiredGroup;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $requiredName
|
||||
* @return AbstractCollection
|
||||
*/
|
||||
public function getByName($requiredName) {
|
||||
return $this->getFilteredCopy(
|
||||
function(AbstractFacet $facet) use ($requiredName) {
|
||||
return $facet->getName() == $requiredName;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return AbstractFacet
|
||||
*/
|
||||
public function getByPosition($position)
|
||||
{
|
||||
return parent::getByPosition($position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Interface FacetParserInterface
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
interface FacetParserInterface
|
||||
{
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return AbstractFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration);
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* Query Filter Encoder Interface
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
*/
|
||||
interface FacetQueryBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration);
|
||||
}
|
108
Classes/Domain/Search/ResultSet/Facets/FacetRegistry.php
Normal file
108
Classes/Domain/Search/ResultSet/Facets/FacetRegistry.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy\HierarchyPackage;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options\OptionsPackage;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup\QueryGroupPackage;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange\DateRangePackage;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange\NumericRangePackage;
|
||||
use WapplerSystems\Meilisearch\System\Object\AbstractClassRegistry;
|
||||
|
||||
/**
|
||||
* Class FacetRegistry
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class FacetRegistry extends AbstractClassRegistry
|
||||
{
|
||||
/**
|
||||
* Array of available parser classNames
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $classMap = [
|
||||
'options' => OptionsPackage::class,
|
||||
'hierarchy' => HierarchyPackage::class,
|
||||
'queryGroup' => QueryGroupPackage::class,
|
||||
'dateRange' => DateRangePackage::class,
|
||||
'numericRange' => NumericRangePackage::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Default parser className
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultClass = OptionsPackage::class;
|
||||
|
||||
/**
|
||||
* Get defaultParser
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultPackage()
|
||||
{
|
||||
return $this->defaultClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set defaultParser
|
||||
*
|
||||
* @param string $defaultPackageClassName
|
||||
*/
|
||||
public function setDefaultPackage($defaultPackageClassName)
|
||||
{
|
||||
$this->defaultClass = $defaultPackageClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered parser classNames
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPackages()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $type
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function registerPackage($className, $type)
|
||||
{
|
||||
return $this->register($className, $type, AbstractFacetPackage::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package
|
||||
*
|
||||
* @param string $type
|
||||
* @return AbstractFacetPackage
|
||||
* @throws InvalidFacetPackageException
|
||||
*/
|
||||
public function getPackage($type)
|
||||
{
|
||||
$instance = $this->getInstance($type);
|
||||
if (!$instance instanceof AbstractFacetPackage) {
|
||||
throw new InvalidFacetPackageException('Invalid class registered for ' . htmlspecialchars($type));
|
||||
}
|
||||
$instance->setObjectManager($this->objectManager);
|
||||
return $instance;
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* The facet url encode is responsible to encode and decode values for EXT:meilisearch urls.
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
interface FacetUrlDecoderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Parses the query filter from GET parameters in the URL and translates it
|
||||
* to a Lucene filter value.
|
||||
*
|
||||
* @param string $value the filter query from plugin
|
||||
* @param array $configuration Facet configuration
|
||||
* @return string Value to be used in a Lucene filter
|
||||
*/
|
||||
public function decode($value, array $configuration = []);
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017- Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
class InvalidFacetPackageException extends \Exception {}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017- Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
class InvalidFacetParserException extends \Exception {}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017- Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
class InvalidQueryBuilderException extends \Exception {}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017- Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
class InvalidUrlDecoderException extends \Exception {}
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItem;
|
||||
|
||||
/**
|
||||
* Base class for all facet items that are represented as option
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class AbstractOptionFacetItem extends AbstractFacetItem
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value = '';
|
||||
|
||||
/**
|
||||
* @param AbstractFacet $facet
|
||||
* @param string $label
|
||||
* @param string $value
|
||||
* @param int $documentCount
|
||||
* @param bool $selected
|
||||
* @param array $metrics
|
||||
*/
|
||||
public function __construct(AbstractFacet $facet, $label = '', $value = '', $documentCount = 0, $selected = false, $metrics = [])
|
||||
{
|
||||
$this->value = $value;
|
||||
parent::__construct($facet, $label, $documentCount, $selected, $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUriValue()
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCollectionKey()
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Class AbstractOptionsFacet
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class AbstractOptionsFacet extends AbstractFacet
|
||||
{
|
||||
|
||||
/**
|
||||
* @var OptionCollection
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
$this->options = new OptionCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OptionCollection
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OptionCollection $options
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractOptionFacetItem $option
|
||||
*/
|
||||
public function addOption(AbstractOptionFacetItem $option)
|
||||
{
|
||||
$this->options->add($option);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of this method should return a "flatten" collection of all items.
|
||||
*
|
||||
* @return OptionCollection
|
||||
*/
|
||||
public function getAllFacetItems()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get facet partial name used for rendering the facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartialName()
|
||||
{
|
||||
return !empty($this->configuration['partialName']) ? $this->configuration['partialName'] : 'Options';
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Value object that represent a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class HierarchyFacet extends AbstractFacet
|
||||
{
|
||||
const TYPE_HIERARCHY = 'hierarchy';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_HIERARCHY;
|
||||
|
||||
/**
|
||||
* @var NodeCollection
|
||||
*/
|
||||
protected $childNodes;
|
||||
|
||||
/**
|
||||
* @var NodeCollection
|
||||
*/
|
||||
protected $allNodes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $nodesByKey = [];
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
$this->childNodes = new NodeCollection();
|
||||
$this->allNodes = new NodeCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
*/
|
||||
public function addChildNode(Node $node)
|
||||
{
|
||||
$this->childNodes->add($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NodeCollection
|
||||
*/
|
||||
public function getChildNodes()
|
||||
{
|
||||
return $this->childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new node on the right position with the right parent node.
|
||||
*
|
||||
* @param string $parentKey
|
||||
* @param string $key
|
||||
* @param string $label
|
||||
* @param string $value
|
||||
* @param integer $count
|
||||
* @param boolean $selected
|
||||
*/
|
||||
public function createNode($parentKey, $key, $label, $value, $count, $selected)
|
||||
{
|
||||
/** @var $parentNode Node|null */
|
||||
$parentNode = isset($this->nodesByKey[$parentKey]) ? $this->nodesByKey[$parentKey] : null;
|
||||
/** @var Node $node */
|
||||
$node = $this->objectManager->get(Node::class, $this, $parentNode, $key, $label, $value, $count, $selected);
|
||||
$this->nodesByKey[$key] = $node;
|
||||
|
||||
if ($parentNode === null) {
|
||||
$this->addChildNode($node);
|
||||
} else {
|
||||
$parentNode->addChildNode($node);
|
||||
}
|
||||
|
||||
$this->allNodes->add($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get facet partial name used for rendering the facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartialName()
|
||||
{
|
||||
return !empty($this->configuration['partialName']) ? $this->configuration['partialName'] : 'Hierarchy';
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of this method should return a "flatten" collection of all items.
|
||||
*
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getAllFacetItems()
|
||||
{
|
||||
return $this->allNodes;
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ParsingUtil;
|
||||
|
||||
/**
|
||||
* Class HierarchyFacetParser
|
||||
*/
|
||||
class HierarchyFacetParser extends AbstractFacetParser
|
||||
{
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return HierarchyFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration)
|
||||
{
|
||||
$response = $resultSet->getResponse();
|
||||
$fieldName = $facetConfiguration['field'];
|
||||
$label = $this->getPlainLabelOrApplyCObject($facetConfiguration);
|
||||
$optionsFromSolrResponse = isset($response->facet_counts->facet_fields->{$fieldName}) ? ParsingUtil::getMapArrayFromFlatArray($response->facet_counts->facet_fields->{$fieldName}) : [];
|
||||
$optionsFromRequest = $this->getActiveFacetValuesFromRequest($resultSet, $facetName);
|
||||
$hasOptionsInResponse = !empty($optionsFromSolrResponse);
|
||||
$hasSelectedOptionsInRequest = count($optionsFromRequest) > 0;
|
||||
$hasNoOptionsToShow = !$hasOptionsInResponse && !$hasSelectedOptionsInRequest;
|
||||
$hideEmpty = !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingShowEmptyFacetsByName($facetName);
|
||||
|
||||
if ($hasNoOptionsToShow && $hideEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var $facet HierarchyFacet */
|
||||
$facet = $this->objectManager->get(HierarchyFacet::class, $resultSet, $facetName, $fieldName, $label, $facetConfiguration);
|
||||
|
||||
$hasActiveOptions = count($optionsFromRequest) > 0;
|
||||
$facet->setIsUsed($hasActiveOptions);
|
||||
|
||||
$facet->setIsAvailable($hasOptionsInResponse);
|
||||
|
||||
$nodesToCreate = $this->getMergedFacetValueFromSearchRequestAndSolrResponse($optionsFromSolrResponse, $optionsFromRequest);
|
||||
|
||||
if ($this->facetOptionsMustBeResorted($facetConfiguration)) {
|
||||
$nodesToCreate = $this->sortFacetOptionsInNaturalOrder($nodesToCreate);
|
||||
}
|
||||
|
||||
foreach ($nodesToCreate as $value => $count) {
|
||||
if ($this->getIsExcludedFacetValue($value, $facetConfiguration)) {
|
||||
continue;
|
||||
}
|
||||
$isActive = in_array($value, $optionsFromRequest);
|
||||
$delimiterPosition = strpos($value, '-');
|
||||
$path = substr($value, $delimiterPosition + 1);
|
||||
$pathArray = $this->getPathAsArray($path);
|
||||
$key = array_pop($pathArray);
|
||||
$parentKey = array_pop($pathArray);
|
||||
$value = '/' . $path;
|
||||
$label = $this->getLabelFromRenderingInstructions($key, $count, $facetName, $facetConfiguration);
|
||||
|
||||
$facet->createNode($parentKey, $key, $label, $value, $count, $isActive);
|
||||
}
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts facet options in natural order.
|
||||
* Options must be sorted in natural order,
|
||||
* because lower nesting levels must be instantiated first, to serve as parents for higher nested levels.
|
||||
* See implementation of HierarchyFacet::createNode().
|
||||
*
|
||||
* @param array $flatOptionsListForFacet
|
||||
* @return void sorted list of facet options
|
||||
*/
|
||||
protected function sortFacetOptionsInNaturalOrder(array $flatOptionsListForHierarchyFacet)
|
||||
{
|
||||
uksort($flatOptionsListForHierarchyFacet, "strnatcmp");
|
||||
return $flatOptionsListForHierarchyFacet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if options must be resorted.
|
||||
*
|
||||
* Apache Solr facet.sort can be set globally or per facet.
|
||||
* Relevant TypoScript paths:
|
||||
* plugin.tx_meilisearch.search.faceting.sortBy causes facet.sort Apache Solr parameter
|
||||
* plugin.tx_meilisearch.search.faceting.facets.[facetName].sortBy causes f.<fieldname>.facet.sort parameter
|
||||
*
|
||||
* see: https://lucene.apache.org/solr/guide/6_6/faceting.html#Faceting-Thefacet.sortParameter
|
||||
* see: https://wiki.apache.org/solr/SimpleFacetParameters#facet.sort : "This parameter can be specified on a per field basis."
|
||||
*
|
||||
* @param array $facetConfiguration
|
||||
* @return bool
|
||||
*/
|
||||
protected function facetOptionsMustBeResorted(array $facetConfiguration)
|
||||
{
|
||||
if (isset($facetConfiguration['sortBy']) && $facetConfiguration['sortBy'] === 'index') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the path array from a hierarchical facet. It substitutes escaped slashes to keep them
|
||||
* when they are used inside a facetValue.
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
protected function getPathAsArray($path)
|
||||
{
|
||||
$path = str_replace('\/', '@@@', $path);
|
||||
$path = rtrim($path, "/");
|
||||
$segments = explode('/', $path);
|
||||
return array_map(function($item) {
|
||||
return str_replace('@@@', '/', $item);
|
||||
}, $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the active facetValue for a facet from the search request.
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @return array
|
||||
*/
|
||||
protected function getActiveFacetValuesFromRequest(SearchResultSet $resultSet, $facetName)
|
||||
{
|
||||
$activeFacetValues = [];
|
||||
$values = $resultSet->getUsedSearchRequest()->getActiveFacetValuesByName($facetName);
|
||||
|
||||
foreach (is_array($values) ? $values : [] as $valueFromRequest) {
|
||||
// Attach the 'depth' param again to the value
|
||||
if (strpos($valueFromRequest, '-') === false) {
|
||||
$valueFromRequest = HierarchyTool::substituteSlashes($valueFromRequest);
|
||||
$valueFromRequest = trim($valueFromRequest, '/');
|
||||
$valueFromRequest = (count(explode('/', $valueFromRequest)) - 1) . '-' . $valueFromRequest . '/';
|
||||
$valueFromRequest = HierarchyTool::unSubstituteSlashes($valueFromRequest);
|
||||
}
|
||||
$activeFacetValues[] = $valueFromRequest;
|
||||
}
|
||||
return $activeFacetValues;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetPackage;
|
||||
|
||||
/**
|
||||
* Class HierarchyPackage
|
||||
*/
|
||||
class HierarchyPackage extends AbstractFacetPackage {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParserClassName() {
|
||||
return (string)HierarchyFacetParser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlDecoderClassName() {
|
||||
return (string)HierarchyUrlDecoder::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
class HierarchyTool
|
||||
{
|
||||
/**
|
||||
* Replaces all escaped slashes in a hierarchy path with @@@slash@@@ to afterwards
|
||||
* only have slashes in the content that are real path separators.
|
||||
*
|
||||
* @param string $pathWithContentSlashes
|
||||
* @return string
|
||||
*/
|
||||
public static function substituteSlashes(string $pathWithContentSlashes): string
|
||||
{
|
||||
return (string)str_replace('\/', '@@@slash@@@', $pathWithContentSlashes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces @@@slash@@@ with \/ to have the path usable for solr again.
|
||||
*
|
||||
* @param string $pathWithReplacedContentSlashes
|
||||
* @return string
|
||||
*/
|
||||
public static function unSubstituteSlashes(string $pathWithReplacedContentSlashes): string
|
||||
{
|
||||
return (string)str_replace('@@@slash@@@', '\/', $pathWithReplacedContentSlashes);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetUrlDecoderInterface;
|
||||
|
||||
/**
|
||||
* Filter encoder to build Solr hierarchy queries from tx_meilisearch[filter]
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
*/
|
||||
class HierarchyUrlDecoder implements FacetUrlDecoderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Delimiter for hierarchies in the URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DELIMITER = '/';
|
||||
|
||||
/**
|
||||
* Parses the given hierarchy filter and returns a Solr filter query.
|
||||
*
|
||||
* @param string $hierarchy The hierarchy filter query.
|
||||
* @param array $configuration Facet configuration
|
||||
* @return string Lucene query language filter to be used for querying Solr
|
||||
*/
|
||||
public function decode($hierarchy, array $configuration = [])
|
||||
{
|
||||
$escapedHierarchy = HierarchyTool::substituteSlashes($hierarchy);
|
||||
|
||||
$escapedHierarchy = substr($escapedHierarchy, 1);
|
||||
$escapedHierarchy = rtrim($escapedHierarchy, '/');
|
||||
$hierarchyItems = explode(self::DELIMITER, $escapedHierarchy);
|
||||
$filterContent = (count($hierarchyItems) - 1) . '-' . $escapedHierarchy . '/';
|
||||
|
||||
$filterContent = HierarchyTool::unSubstituteSlashes($filterContent);
|
||||
|
||||
return '"' . str_replace("\\", "\\\\", $filterContent) . '"';
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionFacetItem;
|
||||
|
||||
/**
|
||||
* Value object that represent an option of a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Node extends AbstractOptionFacetItem
|
||||
{
|
||||
|
||||
/**
|
||||
* @var NodeCollection
|
||||
*/
|
||||
protected $childNodes;
|
||||
|
||||
/**
|
||||
* @var Node
|
||||
*/
|
||||
protected $parentNode;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $depth;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* @param HierarchyFacet $facet
|
||||
* @param Node $parentNode
|
||||
* @param string $key
|
||||
* @param string $label
|
||||
* @param string $value
|
||||
* @param int $documentCount
|
||||
* @param bool $selected
|
||||
*/
|
||||
public function __construct(HierarchyFacet $facet, $parentNode = null, $key = '', $label = '', $value = '', $documentCount = 0, $selected = false)
|
||||
{
|
||||
parent::__construct($facet, $label, $value, $documentCount, $selected);
|
||||
$this->value = $value;
|
||||
$this->childNodes = new NodeCollection();
|
||||
$this->parentNode = $parentNode;
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
*/
|
||||
public function addChildNode(Node $node)
|
||||
{
|
||||
$this->childNodes->add($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NodeCollection
|
||||
*/
|
||||
public function getChildNodes()
|
||||
{
|
||||
return $this->childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node|null
|
||||
*/
|
||||
public function getParentNode()
|
||||
{
|
||||
return $this->parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasParentNode()
|
||||
{
|
||||
return $this->parentNode !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasChildNodeSelected()
|
||||
{
|
||||
/** @var Node $childNode */
|
||||
foreach ($this->childNodes as $childNode) {
|
||||
if ($childNode->getSelected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Hierarchy;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
|
||||
/**
|
||||
* Collection for facet options.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NodeCollection extends AbstractFacetItemCollection
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Node $node
|
||||
* @return NodeCollection
|
||||
*/
|
||||
public function add($node)
|
||||
{
|
||||
return parent::add($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return Node
|
||||
*/
|
||||
public function getByPosition($position)
|
||||
{
|
||||
return parent::getByPosition($position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options\Option;
|
||||
|
||||
/**
|
||||
* Collection for facet options.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class OptionCollection extends AbstractFacetItemCollection
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns an array of prefixes from the option labels.
|
||||
*
|
||||
* Red, Blue, Green => r, g, b
|
||||
*
|
||||
* Can be used in combination with getByPrefix() to group facet options by prefix (e.g. alphabetical).
|
||||
*
|
||||
* @param int $length
|
||||
* @return array
|
||||
*/
|
||||
public function getLowercaseLabelPrefixes($length = 1)
|
||||
{
|
||||
$prefixes = $this->getLabelPrefixes($length);
|
||||
return array_map('mb_strtolower', $prefixes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filteredPrefix
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getByLowercaseLabelPrefix($filteredPrefix)
|
||||
{
|
||||
return $this->getFilteredCopy(function(Option $option) use ($filteredPrefix)
|
||||
{
|
||||
$filteredPrefixLength = mb_strlen($filteredPrefix);
|
||||
$currentPrefix = mb_substr(mb_strtolower($option->getLabel()), 0, $filteredPrefixLength);
|
||||
|
||||
return $currentPrefix === $filteredPrefix;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $length
|
||||
* @return array
|
||||
*/
|
||||
protected function getLabelPrefixes($length = 1) : array
|
||||
{
|
||||
$prefixes = [];
|
||||
foreach ($this->data as $option) {
|
||||
/** @var $option Option */
|
||||
$prefix = mb_substr($option->getLabel(), 0, $length);
|
||||
$prefixes[$prefix] = $prefix;
|
||||
}
|
||||
|
||||
return array_values($prefixes);
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionFacetItem;
|
||||
|
||||
/**
|
||||
* Value object that represent an option of a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Option extends AbstractOptionFacetItem
|
||||
{
|
||||
/**
|
||||
* @param OptionsFacet $facet
|
||||
* @param string $label
|
||||
* @param string $value
|
||||
* @param int $documentCount
|
||||
* @param bool $selected
|
||||
* @param array $metrics
|
||||
*/
|
||||
public function __construct(OptionsFacet $facet, $label = '', $value = '', $documentCount = 0, $selected = false, $metrics = [])
|
||||
{
|
||||
parent::__construct($facet, $label, $value, $documentCount, $selected, $metrics);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionsFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Value object that represent a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class OptionsFacet extends AbstractOptionsFacet
|
||||
{
|
||||
const TYPE_OPTIONS = 'options';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_OPTIONS;
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ResponseAdapter;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
|
||||
|
||||
/**
|
||||
* Class OptionsFacetParser
|
||||
*/
|
||||
class OptionsFacetParser extends AbstractFacetParser
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $dispatcher
|
||||
*/
|
||||
public function injectDispatcher(Dispatcher $dispatcher)
|
||||
{
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return OptionsFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration)
|
||||
{
|
||||
$response = $resultSet->getResponse();
|
||||
$fieldName = $facetConfiguration['field'];
|
||||
$label = $this->getPlainLabelOrApplyCObject($facetConfiguration);
|
||||
$optionsFromSolrResponse = $this->getOptionsFromSolrResponse($facetName, $response);
|
||||
$metricsFromSolrResponse = $this->getMetricsFromSolrResponse($facetName, $response);
|
||||
$optionsFromRequest = $this->getActiveFacetValuesFromRequest($resultSet, $facetName);
|
||||
$hasOptionsInResponse = !empty($optionsFromSolrResponse);
|
||||
$hasSelectedOptionsInRequest = count($optionsFromRequest) > 0;
|
||||
$hasNoOptionsToShow = !$hasOptionsInResponse && !$hasSelectedOptionsInRequest;
|
||||
$hideEmpty = !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingShowEmptyFacetsByName($facetName);
|
||||
|
||||
if ($hasNoOptionsToShow && $hideEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var $facet OptionsFacet */
|
||||
$facet = $this->objectManager->get(
|
||||
OptionsFacet::class,
|
||||
$resultSet,
|
||||
$facetName,
|
||||
$fieldName,
|
||||
$label,
|
||||
$facetConfiguration
|
||||
);
|
||||
|
||||
$hasActiveOptions = count($optionsFromRequest) > 0;
|
||||
$facet->setIsUsed($hasActiveOptions);
|
||||
$facet->setIsAvailable($hasOptionsInResponse);
|
||||
|
||||
$optionsToCreate = $this->getMergedFacetValueFromSearchRequestAndSolrResponse($optionsFromSolrResponse, $optionsFromRequest);
|
||||
foreach ($optionsToCreate as $optionsValue => $count) {
|
||||
if ($this->getIsExcludedFacetValue($optionsValue, $facetConfiguration)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isOptionsActive = in_array($optionsValue, $optionsFromRequest);
|
||||
$label = $this->getLabelFromRenderingInstructions($optionsValue, $count, $facetName, $facetConfiguration);
|
||||
$facet->addOption($this->objectManager->get(Option::class, $facet, $label, $optionsValue, $count, $isOptionsActive, $metricsFromSolrResponse[$optionsValue]));
|
||||
}
|
||||
|
||||
// after all options have been created we apply a manualSortOrder if configured
|
||||
// the sortBy (lex,..) is done by the solr server and triggered by the query, therefore it does not
|
||||
// need to be handled in the frontend.
|
||||
$this->applyManualSortOrder($facet, $facetConfiguration);
|
||||
$this->applyReverseOrder($facet, $facetConfiguration);
|
||||
|
||||
if(!is_null($this->dispatcher)) {
|
||||
$this->dispatcher->dispatch(__CLASS__, 'optionsParsed', [&$facet, $facetConfiguration]);
|
||||
}
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param ResponseAdapter $response
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptionsFromSolrResponse($facetName, ResponseAdapter $response)
|
||||
{
|
||||
$optionsFromSolrResponse = [];
|
||||
if (!isset($response->facets->{$facetName})) {
|
||||
return $optionsFromSolrResponse;
|
||||
}
|
||||
|
||||
foreach ($response->facets->{$facetName}->buckets as $bucket) {
|
||||
$optionValue = $bucket->val;
|
||||
$optionCount = $bucket->count;
|
||||
$optionsFromSolrResponse[$optionValue] = $optionCount;
|
||||
}
|
||||
|
||||
return $optionsFromSolrResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param ResponseAdapter $response
|
||||
* @return array
|
||||
*/
|
||||
protected function getMetricsFromSolrResponse($facetName, ResponseAdapter $response)
|
||||
{
|
||||
$metricsFromSolrResponse = [];
|
||||
|
||||
if (!isset($response->facets->{$facetName}->buckets)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($response->facets->{$facetName}->buckets as $bucket) {
|
||||
$bucketVariables = get_object_vars($bucket);
|
||||
foreach ($bucketVariables as $key => $value) {
|
||||
if (strpos($key, 'metrics_') === 0) {
|
||||
$metricsKey = str_replace('metrics_', '', $key);
|
||||
$metricsFromSolrResponse[$bucket->val][$metricsKey] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $metricsFromSolrResponse;
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\DefaultFacetQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetQueryBuilderInterface;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\SortingExpression;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* Class OptionsFacetQueryBuilder
|
||||
*
|
||||
* The Options facet query builder builds the facets as json structure
|
||||
*
|
||||
* @Todo: When we use json faceting for other facets some logic of this class can be moved to the base class.
|
||||
*/
|
||||
class OptionsFacetQueryBuilder extends DefaultFacetQueryBuilder implements FacetQueryBuilderInterface {
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$facetParameters = [];
|
||||
$facetConfiguration = $configuration->getSearchFacetingFacetByName($facetName);
|
||||
|
||||
$jsonFacetOptions = [
|
||||
'type' => 'terms',
|
||||
'field' => $facetConfiguration['field'],
|
||||
];
|
||||
|
||||
$jsonFacetOptions['limit'] = $this->buildLimitForJson($facetConfiguration, $configuration);
|
||||
$jsonFacetOptions['mincount'] = $this->buildMincountForJson($facetConfiguration, $configuration);
|
||||
|
||||
$sorting = $this->buildSortingForJson($facetConfiguration);
|
||||
if (!empty($sorting)) {
|
||||
$jsonFacetOptions['sort'] = $sorting;
|
||||
}
|
||||
|
||||
if (is_array($facetConfiguration['metrics.'])) {
|
||||
foreach ($facetConfiguration['metrics.'] as $key => $value) {
|
||||
$jsonFacetOptions['facet']['metrics_' . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$excludeTags = $this->buildExcludeTagsForJson($facetConfiguration, $configuration);
|
||||
if (!empty($excludeTags)) {
|
||||
$jsonFacetOptions['domain']['excludeTags'] = $excludeTags;
|
||||
}
|
||||
|
||||
$facetParameters['json.facet'][$facetName] = $jsonFacetOptions;
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $facetConfiguration
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return string
|
||||
*/
|
||||
protected function buildExcludeTagsForJson(array $facetConfiguration, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$excludeFields = [];
|
||||
|
||||
if ($configuration->getSearchFacetingKeepAllFacetsOnSelection()) {
|
||||
if (!$configuration->getSearchFacetingCountAllFacetsForSelection()) {
|
||||
// keepAllOptionsOnSelection globally active
|
||||
foreach ($configuration->getSearchFacetingFacets() as $facet) {
|
||||
$excludeFields[] = $facet['field'];
|
||||
}
|
||||
} else {
|
||||
$excludeFields[] = $facetConfiguration['field'];
|
||||
}
|
||||
}
|
||||
|
||||
$isKeepAllOptionsActiveForSingleFacet = $facetConfiguration['keepAllOptionsOnSelection'] == 1;
|
||||
if ($isKeepAllOptionsActiveForSingleFacet) {
|
||||
$excludeFields[] = $facetConfiguration['field'];
|
||||
}
|
||||
|
||||
if (!empty($facetConfiguration['additionalExcludeTags'])) {
|
||||
$excludeFields[] = $facetConfiguration['additionalExcludeTags'];
|
||||
}
|
||||
|
||||
return implode(',', array_unique($excludeFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $facetConfiguration
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return int
|
||||
*/
|
||||
protected function buildLimitForJson(array $facetConfiguration, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
if (isset($facetConfiguration['facetLimit'])) {
|
||||
return (int)$facetConfiguration['facetLimit'];
|
||||
} elseif (!is_null($configuration->getSearchFacetingFacetLimit()) && $configuration->getSearchFacetingFacetLimit() >= 0) {
|
||||
return $configuration->getSearchFacetingFacetLimit();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $facetConfiguration
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return int
|
||||
*/
|
||||
protected function buildMincountForJson(array $facetConfiguration, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
if (isset($facetConfiguration['minimumCount'])) {
|
||||
return (int)$facetConfiguration['minimumCount'];
|
||||
} elseif (!is_null($configuration->getSearchFacetingMinimumCount()) && (int)$configuration->getSearchFacetingMinimumCount() >= 0) {
|
||||
return $configuration->getSearchFacetingMinimumCount();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $facetConfiguration
|
||||
* @return string
|
||||
*/
|
||||
protected function buildSortingForJson(array $facetConfiguration) {
|
||||
if (isset($facetConfiguration['sortBy'])) {
|
||||
$sortingExpression = new SortingExpression();
|
||||
$sorting = $facetConfiguration['sortBy'];
|
||||
$direction = $facetConfiguration['sortDirection'];
|
||||
return $sortingExpression->getForJsonFacet($sorting, $direction);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\Options;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetPackage;
|
||||
|
||||
/**
|
||||
* Class OptionsPackage
|
||||
*/
|
||||
class OptionsPackage extends AbstractFacetPackage {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParserClassName() {
|
||||
return (string)OptionsFacetParser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryBuilderClassName() {
|
||||
return (string)OptionsFacetQueryBuilder::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionFacetItem;
|
||||
|
||||
/**
|
||||
* Value object that represent an option of a queryGroup facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Option extends AbstractOptionFacetItem
|
||||
{
|
||||
/**
|
||||
* @param QueryGroupFacet $facet
|
||||
* @param string $label
|
||||
* @param string $value
|
||||
* @param int $documentCount
|
||||
* @param bool $selected
|
||||
*/
|
||||
public function __construct(QueryGroupFacet $facet, $label = '', $value = '', $documentCount = 0, $selected = false)
|
||||
{
|
||||
parent::__construct($facet, $label, $value, $documentCount, $selected);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\AbstractOptionsFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Class QueryGroupFacet
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class QueryGroupFacet extends AbstractOptionsFacet
|
||||
{
|
||||
const TYPE_QUERY_GROUP = 'queryGroup';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_QUERY_GROUP;
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ResponseAdapter;
|
||||
|
||||
/**
|
||||
* Class QueryGroupFacetParser
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class QueryGroupFacetParser extends AbstractFacetParser
|
||||
{
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return QueryGroupFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration)
|
||||
{
|
||||
$response = $resultSet->getResponse();
|
||||
$fieldName = $facetConfiguration['field'];
|
||||
$label = $this->getPlainLabelOrApplyCObject($facetConfiguration);
|
||||
|
||||
$rawOptions = $this->getRawOptions($response, $fieldName);
|
||||
$noOptionsInResponse = $rawOptions === [];
|
||||
$hideEmpty = !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingShowEmptyFacetsByName($facetName);
|
||||
|
||||
if ($noOptionsInResponse && $hideEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var QueryGroupFacet $facet */
|
||||
$facet = $this->objectManager->get(
|
||||
QueryGroupFacet::class,
|
||||
$resultSet,
|
||||
$facetName,
|
||||
$fieldName,
|
||||
$label,
|
||||
$facetConfiguration
|
||||
);
|
||||
|
||||
$activeFacets = $resultSet->getUsedSearchRequest()->getActiveFacetNames();
|
||||
$facet->setIsUsed(in_array($facetName, $activeFacets, true));
|
||||
|
||||
if (!$noOptionsInResponse) {
|
||||
$facet->setIsAvailable(true);
|
||||
foreach ($rawOptions as $query => $count) {
|
||||
$value = $this->getValueByQuery($query, $facetConfiguration);
|
||||
// Skip unknown queries
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->getIsExcludedFacetValue($query, $facetConfiguration)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isOptionsActive = $resultSet->getUsedSearchRequest()->getHasFacetValue($facetName, $value);
|
||||
$label = $this->getLabelFromRenderingInstructions(
|
||||
$value,
|
||||
$count,
|
||||
$facetName,
|
||||
$facetConfiguration
|
||||
);
|
||||
$facet->addOption($this->objectManager->get(Option::class, $facet, $label, $value, $count, $isOptionsActive));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// after all options have been created we apply a manualSortOrder if configured
|
||||
// the sortBy (lex,..) is done by the solr server and triggered by the query, therefore it does not
|
||||
// need to be handled in the frontend.
|
||||
$this->applyManualSortOrder($facet, $facetConfiguration);
|
||||
$this->applyReverseOrder($facet, $facetConfiguration);
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw query options
|
||||
*
|
||||
* @param ResponseAdapter $response
|
||||
* @param string $fieldName
|
||||
* @return array
|
||||
*/
|
||||
protected function getRawOptions(ResponseAdapter $response, $fieldName)
|
||||
{
|
||||
$options = [];
|
||||
|
||||
foreach ($response->facet_counts->facet_queries as $rawValue => $count) {
|
||||
if ((int)$count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// todo: add test cases to check if this is needed https://forge.typo3.org/issues/45440
|
||||
// remove tags from the facet.query response, for facet.field
|
||||
// and facet.range Solr does that on its own automatically
|
||||
$rawValue = preg_replace('/^\{!ex=[^\}]*\}(.*)/', '\\1', $rawValue);
|
||||
|
||||
list($field, $query) = explode(':', $rawValue, 2);
|
||||
if ($field === $fieldName) {
|
||||
$options[$query] = $count;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array $facetConfiguration
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getValueByQuery($query, array $facetConfiguration)
|
||||
{
|
||||
$value = null;
|
||||
foreach ($facetConfiguration['queryGroup.'] as $valueKey => $config) {
|
||||
if (isset($config['query']) && $config['query'] === $query) {
|
||||
$value = rtrim($valueKey, '.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\DefaultFacetQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetQueryBuilderInterface;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
class QueryGroupFacetQueryBuilder extends DefaultFacetQueryBuilder implements FacetQueryBuilderInterface {
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$facetParameters = [];
|
||||
$facetConfiguration = $configuration->getSearchFacetingFacetByName($facetName);
|
||||
foreach ($facetConfiguration['queryGroup.'] as $queryName => $queryConfiguration) {
|
||||
$tags = $this->buildExcludeTags($facetConfiguration, $configuration);
|
||||
$facetParameters['facet.query'][] = $tags . $facetConfiguration['field'] . ':' . $queryConfiguration['query'];
|
||||
}
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetPackage;
|
||||
|
||||
/**
|
||||
* Class QueryGroupPackage
|
||||
*/
|
||||
class QueryGroupPackage extends AbstractFacetPackage {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParserClassName() {
|
||||
return (string)QueryGroupFacetParser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryBuilderClassName()
|
||||
{
|
||||
return (string)QueryGroupFacetQueryBuilder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlDecoderClassName()
|
||||
{
|
||||
return (string)QueryGroupUrlDecoder::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\OptionBased\QueryGroup;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetUrlDecoderInterface;
|
||||
|
||||
/**
|
||||
* Filter encoder to build facet query parameters
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
*/
|
||||
class QueryGroupUrlDecoder implements FacetUrlDecoderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Parses the query filter from GET parameters in the URL and translates it
|
||||
* to a Lucene filter value.
|
||||
*
|
||||
* @param string $filterValue the filter query from plugin
|
||||
* @param array $configuration options set in a facet's configuration
|
||||
* @return string Value to be used in a Lucene filter
|
||||
*/
|
||||
public function decode($filterValue, array $configuration = [])
|
||||
{
|
||||
return $configuration[$filterValue . '.']['query'];
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItem;
|
||||
|
||||
/**
|
||||
* Abstract class that is used as base class for range facet items
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractRangeFacetItem extends AbstractFacetItem
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $rangeCounts;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $gap;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUriValue()
|
||||
{
|
||||
return $this->getRangeString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCollectionKey()
|
||||
{
|
||||
return $this->getRangeString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRangeCounts()
|
||||
{
|
||||
return $this->rangeCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getGap()
|
||||
{
|
||||
return $this->gap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getRangeString();
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ParsingUtil;
|
||||
|
||||
/**
|
||||
* Class AbstractRangeFacetParser
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
abstract class AbstractRangeFacetParser extends AbstractFacetParser
|
||||
{
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @param string $facetClass
|
||||
* @param string $facetItemClass
|
||||
* @param string $facetRangeCountClass
|
||||
* @return AbstractRangeFacet|null
|
||||
*/
|
||||
protected function getParsedFacet(SearchResultSet $resultSet, $facetName, array $facetConfiguration, $facetClass, $facetItemClass, $facetRangeCountClass)
|
||||
{
|
||||
$fieldName = $facetConfiguration['field'];
|
||||
$label = $this->getPlainLabelOrApplyCObject($facetConfiguration);
|
||||
$activeValue = $this->getActiveFacetValuesFromRequest($resultSet, $facetName);
|
||||
$response = $resultSet->getResponse();
|
||||
|
||||
$valuesFromResponse = isset($response->facet_counts->facet_ranges->{$fieldName}) ? get_object_vars($response->facet_counts->facet_ranges->{$fieldName}) : [];
|
||||
|
||||
$facet = $this->objectManager->get(
|
||||
$facetClass,
|
||||
$resultSet,
|
||||
$facetName,
|
||||
$fieldName,
|
||||
$label,
|
||||
$facetConfiguration
|
||||
);
|
||||
|
||||
$facet->setIsAvailable(count($valuesFromResponse) > 0);
|
||||
$facet->setIsUsed(count($activeValue) > 0);
|
||||
|
||||
if (is_array($valuesFromResponse)) {
|
||||
$rangeCounts = [];
|
||||
$allCount = 0;
|
||||
|
||||
$countsFromResponse = isset($valuesFromResponse['counts']) ? ParsingUtil::getMapArrayFromFlatArray($valuesFromResponse['counts']) : [];
|
||||
|
||||
foreach ($countsFromResponse as $rangeCountValue => $count) {
|
||||
$rangeCountValue = $this->parseResponseValue($rangeCountValue);
|
||||
$rangeCount = $this->objectManager->get($facetRangeCountClass, $rangeCountValue, $count);
|
||||
$rangeCounts[] = $rangeCount;
|
||||
$allCount += $count;
|
||||
}
|
||||
|
||||
$fromInResponse = $this->parseResponseValue($valuesFromResponse['start']);
|
||||
$toInResponse = $this->parseResponseValue($valuesFromResponse['end']);
|
||||
|
||||
if (preg_match('/(-?\d*?)-(-?\d*)/', $activeValue[0], $rawValues) == 1) {
|
||||
$rawFrom = $rawValues[1];
|
||||
$rawTo = $rawValues[2];
|
||||
} else {
|
||||
$rawFrom = 0;
|
||||
$rawTo = 0;
|
||||
}
|
||||
|
||||
$from = $this->parseRequestValue($rawFrom);
|
||||
$to = $this->parseRequestValue($rawTo);
|
||||
|
||||
$type = isset($facetConfiguration['type']) ? $facetConfiguration['type'] : 'numericRange';
|
||||
$gap = isset($facetConfiguration[$type . '.']['gap']) ? $facetConfiguration[$type . '.']['gap'] : 1;
|
||||
|
||||
$range = $this->objectManager->get($facetItemClass, $facet, $from, $to, $fromInResponse, $toInResponse, $gap, $allCount, $rangeCounts, true);
|
||||
$facet->setRange($range);
|
||||
}
|
||||
|
||||
return $facet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $requestValue
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function parseRequestValue($requestValue);
|
||||
|
||||
/**
|
||||
* @param string $responseValue
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function parseResponseValue($responseValue);
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\AbstractRangeFacetItem;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Value object that represent an option of a options facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class DateRange extends AbstractRangeFacetItem
|
||||
{
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $startRequested;
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $endRequested;
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $startInResponse;
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $endInResponse;
|
||||
|
||||
/**
|
||||
* @param DateRangeFacet $facet
|
||||
* @param DateTime|null $startRequested
|
||||
* @param DateTime|null $endRequested
|
||||
* @param DateTime|null $startInResponse
|
||||
* @param DateTime|null $endInResponse
|
||||
* @param string $gap
|
||||
* @param int $documentCount
|
||||
* @param array $rangeCounts
|
||||
* @param bool $selected
|
||||
*/
|
||||
public function __construct(DateRangeFacet $facet, DateTime $startRequested = null, DateTime $endRequested = null, DateTime $startInResponse = null, DateTime $endInResponse = null, $gap = '', $documentCount = 0, $rangeCounts, $selected = false)
|
||||
{
|
||||
$this->startInResponse = $startInResponse;
|
||||
$this->endInResponse = $endInResponse;
|
||||
$this->startRequested = $startRequested;
|
||||
$this->endRequested = $endRequested;
|
||||
$this->rangeCounts = $rangeCounts;
|
||||
$this->gap = $gap;
|
||||
|
||||
$label = '';
|
||||
if ($startRequested instanceof DateTime && $endRequested instanceof DateTime) {
|
||||
$label = $this->getRangeString();
|
||||
}
|
||||
|
||||
|
||||
parent::__construct($facet, $label, $documentCount, $selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getRangeString()
|
||||
{
|
||||
return $this->startRequested->format('Ymd') . '0000-' . $this->endRequested->format('Ymd') . '0000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the end date that was requested by the user for this facet.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getEndRequested()
|
||||
{
|
||||
return $this->endRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the start date that was requested by the used for the facet.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getStartRequested()
|
||||
{
|
||||
return $this->startRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the end date that was received from solr for this facet.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getEndInResponse()
|
||||
{
|
||||
return $this->endInResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the start date that was received from solr for this facet.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getStartInResponse()
|
||||
{
|
||||
return $this->startInResponse;
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
|
||||
/**
|
||||
* Collection for facet options.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class DateRangeCollection extends AbstractFacetItemCollection
|
||||
{
|
||||
|
||||
/**
|
||||
* @param DateRange $dateRange
|
||||
* @return DateRangeCollection
|
||||
*/
|
||||
public function add($dateRange)
|
||||
{
|
||||
return parent::add($dateRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return DateRange
|
||||
*/
|
||||
public function getByPosition($position)
|
||||
{
|
||||
return parent::getByPosition($position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItem;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Value object that represent an date range count. The count has a date and the count of documents
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class DateRangeCount
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $documentCount = 0;
|
||||
|
||||
/**
|
||||
* @param $date
|
||||
* @param $documentCount
|
||||
*/
|
||||
public function __construct($date, $documentCount)
|
||||
{
|
||||
$this->date = $date;
|
||||
$this->documentCount = $documentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDocumentCount()
|
||||
{
|
||||
return $this->documentCount;
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Value object that represent a date range facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class DateRangeFacet extends AbstractFacet
|
||||
{
|
||||
const TYPE_DATE_RANGE = 'dateRange';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_DATE_RANGE;
|
||||
|
||||
/**
|
||||
* @var DateRange
|
||||
*/
|
||||
protected $range;
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateRange $range
|
||||
*/
|
||||
public function setRange(DateRange $range)
|
||||
{
|
||||
$this->range = $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateRange
|
||||
*/
|
||||
public function getRange()
|
||||
{
|
||||
return $this->range;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get facet partial name used for rendering the facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartialName()
|
||||
{
|
||||
return !empty($this->configuration['partialName']) ? $this->configuration['partialName'] : 'RangeDate.html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the DateRange contains only one or two items when return a collection with the range only to
|
||||
* allow to render the date range as other facet items.
|
||||
*
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getAllFacetItems()
|
||||
{
|
||||
return new DateRangeCollection([$this->range]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\AbstractRangeFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Data\DateTime;
|
||||
|
||||
/**
|
||||
* Class DateRangeFacetParser
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class DateRangeFacetParser extends AbstractRangeFacetParser
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetClass = DateRangeFacet::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetItemClass = DateRange::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetRangeCountClass = DateRangeCount::class;
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return DateRangeFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration)
|
||||
{
|
||||
return $this->getParsedFacet(
|
||||
$resultSet,
|
||||
$facetName,
|
||||
$facetConfiguration,
|
||||
$this->facetClass,
|
||||
$this->facetItemClass,
|
||||
$this->facetRangeCountClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rawDate
|
||||
* @return DateTime|null
|
||||
*/
|
||||
protected function parseRequestValue($rawDate)
|
||||
{
|
||||
$rawDate = \DateTime::createFromFormat('Ymd', substr($rawDate, 0, 8));
|
||||
if ($rawDate === false) {
|
||||
return null;
|
||||
}
|
||||
$date = new DateTime($rawDate->format(DateTime::ISO8601));
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $isoDateString
|
||||
* @return DateTime
|
||||
*/
|
||||
protected function parseResponseValue($isoDateString)
|
||||
{
|
||||
$rawDate = \DateTime::createFromFormat(\DateTime::ISO8601, $isoDateString);
|
||||
return new DateTime($rawDate->format(DateTime::ISO8601));
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetQueryBuilderInterface;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
class DateRangeFacetQueryBuilder implements FacetQueryBuilderInterface {
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$facetParameters = [];
|
||||
$facetConfiguration = $configuration->getSearchFacetingFacetByName($facetName);
|
||||
|
||||
$tag = '';
|
||||
if ($facetConfiguration['keepAllOptionsOnSelection'] == 1) {
|
||||
$tag = '{!ex=' . $facetConfiguration['field'] . '}';
|
||||
}
|
||||
$facetParameters['facet.range'][] = $tag . $facetConfiguration['field'];
|
||||
|
||||
$start = 'NOW/DAY-1YEAR';
|
||||
if ($facetConfiguration['dateRange.']['start']) {
|
||||
$start = $facetConfiguration['dateRange.']['start'];
|
||||
}
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.start'] = $start;
|
||||
|
||||
$end = 'NOW/DAY+1YEAR';
|
||||
if ($facetConfiguration['dateRange.']['end']) {
|
||||
$end = $facetConfiguration['dateRange.']['end'];
|
||||
}
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.end'] = $end;
|
||||
|
||||
$gap = '+1DAY';
|
||||
if ($facetConfiguration['dateRange.']['gap']) {
|
||||
$gap = $facetConfiguration['dateRange.']['gap'];
|
||||
}
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.gap'] = $gap;
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetPackage;
|
||||
|
||||
/**
|
||||
* Class DateRangePackage
|
||||
*/
|
||||
class DateRangePackage extends AbstractFacetPackage {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParserClassName() {
|
||||
return (string)DateRangeFacetParser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryBuilderClassName()
|
||||
{
|
||||
return (string)DateRangeFacetQueryBuilder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlDecoderClassName()
|
||||
{
|
||||
return (string)DateRangeUrlDecoder::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\DateRange;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2011 Markus Goldbach <markus.goldbach@dkd.de>
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetUrlDecoderInterface;
|
||||
use WapplerSystems\Meilisearch\System\DateTime\FormatService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Parser to build solr range queries from tx_meilisearch[filter]
|
||||
*
|
||||
* @author Markus Goldbach <markus.goldbach@dkd.de>
|
||||
*/
|
||||
class DateRangeUrlDecoder implements FacetUrlDecoderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Delimiter for date parts in the URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DELIMITER = '-';
|
||||
|
||||
/**
|
||||
* Parses the given date range from a GET parameter and returns a Solr
|
||||
* date range filter.
|
||||
*
|
||||
* @param string $dateRange The range filter query string from the query URL
|
||||
* @param array $configuration Facet configuration
|
||||
* @return string Lucene query language filter to be used for querying Solr
|
||||
*/
|
||||
public function decode($dateRange, array $configuration = [])
|
||||
{
|
||||
list($dateRangeStart, $dateRangeEnd) = explode(self::DELIMITER, $dateRange);
|
||||
|
||||
$formatService = GeneralUtility::makeInstance(FormatService::class);
|
||||
$fromPart = '*';
|
||||
if($dateRangeStart !== ''){
|
||||
$fromPart = $formatService->timestampToIso(strtotime($dateRangeStart));
|
||||
}
|
||||
|
||||
$toPart = '*';
|
||||
if($dateRangeEnd !== ''){
|
||||
$dateRangeEnd .= '59'; // adding 59 seconds
|
||||
$toPart = $formatService->timestampToIso(strtotime($dateRangeEnd));
|
||||
}
|
||||
|
||||
$dateRangeFilter = '[' . $fromPart . ' TO ' . $toPart . ']';
|
||||
return $dateRangeFilter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\AbstractRangeFacetItem;
|
||||
|
||||
/**
|
||||
* Value object that represent an option of a numric range facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NumericRange extends AbstractRangeFacetItem
|
||||
{
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $startRequested;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $endRequested;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $startInResponse;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $endInResponse;
|
||||
|
||||
/**
|
||||
* @param NumericRangeFacet $facet
|
||||
* @param float|null $startRequested
|
||||
* @param float|null $endRequested
|
||||
* @param float|null $startInResponse
|
||||
* @param float|null $endInResponse
|
||||
* @param string $gap
|
||||
* @param int $documentCount
|
||||
* @param array $rangeCounts
|
||||
* @param bool $selected
|
||||
*/
|
||||
public function __construct(NumericRangeFacet $facet, $startRequested = null, $endRequested = null, $startInResponse = null, $endInResponse = null, $gap = '', $documentCount = 0, $rangeCounts, $selected = false)
|
||||
{
|
||||
$this->startInResponse = $startInResponse;
|
||||
$this->endInResponse = $endInResponse;
|
||||
$this->startRequested = $startRequested;
|
||||
$this->endRequested = $endRequested;
|
||||
$this->rangeCounts = $rangeCounts;
|
||||
$this->gap = $gap;
|
||||
|
||||
$label = '';
|
||||
if ($startRequested !== null && $endRequested !== null) {
|
||||
$label = $this->getRangeString();
|
||||
}
|
||||
|
||||
|
||||
parent::__construct($facet, $label, $documentCount, $selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getRangeString()
|
||||
{
|
||||
return $this->startRequested . '-' . $this->endRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the end date that was requested by the user for this facet.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getEndRequested()
|
||||
{
|
||||
return $this->endRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the start date that was requested by the used for the facet.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getStartRequested()
|
||||
{
|
||||
return $this->startRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the end date that was received from solr for this facet.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getEndInResponse()
|
||||
{
|
||||
return $this->endInResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the start date that was received from solr for this facet.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getStartInResponse()
|
||||
{
|
||||
return $this->startInResponse;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
|
||||
/**
|
||||
* Collection for facet options.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NumericRangeCollection extends AbstractFacetItemCollection
|
||||
{
|
||||
|
||||
/**
|
||||
* @param NumericRange $numericRange
|
||||
* @return NumericRangeCollection
|
||||
*/
|
||||
public function add($numericRange)
|
||||
{
|
||||
return parent::add($numericRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @return NumericRange
|
||||
*/
|
||||
public function getByPosition($position)
|
||||
{
|
||||
return parent::getByPosition($position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItem;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Value object that represent an date range count. The count has a date and the count of documents
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NumericRangeCount
|
||||
{
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $rangeItem;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $documentCount = 0;
|
||||
|
||||
/**
|
||||
* @param float $rangeItem
|
||||
* @param integer $documentCount
|
||||
*/
|
||||
public function __construct($rangeItem, $documentCount)
|
||||
{
|
||||
$this->rangeItem = $rangeItem;
|
||||
$this->documentCount = $documentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getRangeItem()
|
||||
{
|
||||
return $this->rangeItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDocumentCount()
|
||||
{
|
||||
return $this->documentCount;
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetItemCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Value object that represent a date range facet.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NumericRangeFacet extends AbstractFacet
|
||||
{
|
||||
const TYPE_NUMERIC_RANGE = 'numericRange';
|
||||
|
||||
/**
|
||||
* String
|
||||
* @var string
|
||||
*/
|
||||
protected static $type = self::TYPE_NUMERIC_RANGE;
|
||||
|
||||
/**
|
||||
* @var NumericRange
|
||||
*/
|
||||
protected $numericRange;
|
||||
|
||||
/**
|
||||
* OptionsFacet constructor
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $label
|
||||
* @param array $configuration Facet configuration passed from typoscript
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $label = '', array $configuration = [])
|
||||
{
|
||||
parent::__construct($resultSet, $name, $field, $label, $configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NumericRange $range
|
||||
*/
|
||||
public function setRange(NumericRange $range)
|
||||
{
|
||||
$this->numericRange = $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NumericRange
|
||||
*/
|
||||
public function getRange()
|
||||
{
|
||||
return $this->numericRange;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get facet partial name used for rendering the facet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPartialName()
|
||||
{
|
||||
return !empty($this->configuration['partialName']) ? $this->configuration['partialName'] : 'RangeNumeric.html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the DateRange contains only one or two items when return a collection with the range only to
|
||||
* allow to render the date range as other facet items.
|
||||
*
|
||||
* @return AbstractFacetItemCollection
|
||||
*/
|
||||
public function getAllFacetItems()
|
||||
{
|
||||
return new NumericRangeCollection([$this->numericRange]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\AbstractRangeFacetParser;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Class NumericRangeFacetParser
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class NumericRangeFacetParser extends AbstractRangeFacetParser
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetClass = NumericRangeFacet::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetItemClass = NumericRange::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $facetRangeCountClass = NumericRangeCount::class;
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $facetName
|
||||
* @param array $facetConfiguration
|
||||
* @return NumericRangeFacet|null
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, $facetName, array $facetConfiguration)
|
||||
{
|
||||
return $this->getParsedFacet(
|
||||
$resultSet,
|
||||
$facetName,
|
||||
$facetConfiguration,
|
||||
$this->facetClass,
|
||||
$this->facetItemClass,
|
||||
$this->facetRangeCountClass
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param mixed $rawValue
|
||||
* @return mixed (numeric value)
|
||||
*/
|
||||
protected function parseRequestValue($rawValue)
|
||||
{
|
||||
return is_numeric($rawValue) ? $rawValue : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $rawValue
|
||||
* @return mixed (numeric value)
|
||||
*/
|
||||
protected function parseResponseValue($rawValue)
|
||||
{
|
||||
return is_numeric($rawValue) ? $rawValue : 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetQueryBuilderInterface;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
class NumericRangeFacetQueryBuilder implements FacetQueryBuilderInterface {
|
||||
|
||||
/**
|
||||
* @param string $facetName
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return array
|
||||
*/
|
||||
public function build($facetName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$facetParameters = [];
|
||||
$facetConfiguration = $configuration->getSearchFacetingFacetByName($facetName);
|
||||
|
||||
$tag = '';
|
||||
if ($facetConfiguration['keepAllOptionsOnSelection'] == 1) {
|
||||
$tag = '{!ex=' . $facetConfiguration['field'] . '}';
|
||||
}
|
||||
$facetParameters['facet.range'][] = $tag . $facetConfiguration['field'];
|
||||
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.start'] = $facetConfiguration['numericRange.']['start'];
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.end'] = $facetConfiguration['numericRange.']['end'];
|
||||
$facetParameters['f.' . $facetConfiguration['field'] . '.facet.range.gap'] = $facetConfiguration['numericRange.']['gap'];
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacetPackage;
|
||||
|
||||
/**
|
||||
* Class NumericRangePackage
|
||||
*/
|
||||
class NumericRangePackage extends AbstractFacetPackage {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getParserClassName() {
|
||||
return (string)NumericRangeFacetParser::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQueryBuilderClassName()
|
||||
{
|
||||
return (string)NumericRangeFacetQueryBuilder::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrlDecoderClassName()
|
||||
{
|
||||
return (string)NumericRangeUrlDecoder::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RangeBased\NumericRange;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2011 Markus Goldbach <markus.goldbach@dkd.de>
|
||||
* (c) 2012-2015 Ingo Renner <ingo@typo3.org>
|
||||
* (c) 2016 Markus Friedrich <markus.friedrich@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetUrlDecoderInterface;
|
||||
|
||||
/**
|
||||
* Parser to build Solr range queries from tx_meilisearch[filter]
|
||||
*
|
||||
* @author Markus Goldbach <markus.goldbach@dkd.de>
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
* @author Markus Friedrich <markus.friedrich@dkd.de>
|
||||
*/
|
||||
class NumericRangeUrlDecoder implements FacetUrlDecoderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Delimiter for ranges in the URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DELIMITER = '-';
|
||||
|
||||
/**
|
||||
* Parses the given range from a GET parameter and returns a Solr range
|
||||
* filter.
|
||||
*
|
||||
* @param string $range The range filter from the URL.
|
||||
* @param array $configuration Facet configuration
|
||||
* @return string Lucene query language filter to be used for querying Solr
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function decode($range, array $configuration = [])
|
||||
{
|
||||
preg_match('/(-?\d*?)' . self::DELIMITER . '(-?\d*)/', $range, $filterParts);
|
||||
if ($filterParts[1] == '' || $filterParts[2] == '') {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid numeric range given',
|
||||
1466062730
|
||||
);
|
||||
}
|
||||
|
||||
return '[' . (int)$filterParts[1] . ' TO ' . (int)$filterParts[2] . ']';
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RenderingInstructions;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 Hendrik Putzek <hendrik.putzek@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\DateTime\FormatService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Formats a given date string to another format
|
||||
*
|
||||
* Accepted typoscript parameters:
|
||||
* inputFormat -> The format of the input string
|
||||
* outputFormat -> The format of the processed string (output)
|
||||
*
|
||||
* If the input date can not be processed by the given inputFormat string it is
|
||||
* returned unprocessed.
|
||||
*
|
||||
* @author Hendrik Putzek <hendrik.putzek@dkd.de>
|
||||
*/
|
||||
class FormatDate
|
||||
{
|
||||
|
||||
/**
|
||||
* @var FormatService
|
||||
*/
|
||||
protected $formatService = null;
|
||||
|
||||
/**
|
||||
* FormatDate constructor.
|
||||
* @param FormatService|null $formatService
|
||||
*/
|
||||
public function __construct(FormatService $formatService = null)
|
||||
{
|
||||
$this->formatService = $formatService ?? GeneralUtility::makeInstance(FormatService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a given date string to another format
|
||||
*
|
||||
* @param string $content the content to process
|
||||
* @param array $conf typoscript configuration
|
||||
* @return string formatted date
|
||||
*/
|
||||
public function format($content, $conf)
|
||||
{
|
||||
// set default values
|
||||
$inputFormat = $conf['inputFormat'] ?: 'Y-m-d\TH:i:s\Z';
|
||||
$outputFormat = $conf['outputFormat'] ?: '';
|
||||
return $this->formatService->format($content, $inputFormat, $outputFormat);
|
||||
}
|
||||
}
|
124
Classes/Domain/Search/ResultSet/Facets/RequirementsService.php
Normal file
124
Classes/Domain/Search/ResultSet/Facets/RequirementsService.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Service class to check for a facet if allRequirements are met for that facet.
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class RequirementsService
|
||||
{
|
||||
/**
|
||||
* Checks if facet meets all requirements.
|
||||
*
|
||||
* Evaluates configuration in "plugin.tx_meilisearch.search.faceting.facets.[facetName].requirements",
|
||||
*
|
||||
* @param AbstractFacet $facet
|
||||
* @return bool true if facet might be rendered
|
||||
*/
|
||||
public function getAllRequirementsMet(AbstractFacet $facet)
|
||||
{
|
||||
$requirements = $facet->getRequirements();
|
||||
if (!is_array($requirements) || count($requirements) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($requirements as $requirement) {
|
||||
$requirementMet = $this->getRequirementMet($facet, $requirement);
|
||||
$requirementMet = $this->getNegationWhenConfigured($requirementMet, $requirement);
|
||||
|
||||
if (!$requirementMet) {
|
||||
// early return as soon as one requirement is not met
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a single requirement is met.
|
||||
*
|
||||
* @param AbstractFacet $facet
|
||||
* @param array $requirement
|
||||
* @return bool
|
||||
*/
|
||||
protected function getRequirementMet(AbstractFacet $facet, $requirement = []) {
|
||||
$selectedItemValues = $this->getSelectedItemValues($facet, $requirement['facet']);
|
||||
$csvActiveFacetItemValues = implode(', ', $selectedItemValues);
|
||||
$requirementValues = GeneralUtility::trimExplode(',', $requirement['values']);
|
||||
|
||||
foreach ($requirementValues as $value) {
|
||||
$noFacetOptionSelectedRequirementMet = ($value === '__none' && empty($selectedItemValues));
|
||||
$anyFacetOptionSelectedRequirementMet = ($value === '__any' && !empty($selectedItemValues));
|
||||
|
||||
if ($noFacetOptionSelectedRequirementMet || $anyFacetOptionSelectedRequirementMet || in_array($value, $selectedItemValues) || fnmatch($value, $csvActiveFacetItemValues)) {
|
||||
// when we find a single matching requirement we can exit and return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active item values of a facet
|
||||
*
|
||||
* @param string $facetNameToCheckRequirementsOn
|
||||
* @return AbstractFacetItem[]
|
||||
*/
|
||||
protected function getSelectedItemValues(AbstractFacet $facet, $facetNameToCheckRequirementsOn)
|
||||
{
|
||||
$facetToCheckRequirements = $facet->getResultSet()->getFacets()->getByName($facetNameToCheckRequirementsOn)->getByPosition(0);
|
||||
if (!$facetToCheckRequirements instanceof AbstractFacet) {
|
||||
throw new \InvalidArgumentException('Requirement for unexisting facet configured');
|
||||
}
|
||||
|
||||
if (!$facetToCheckRequirements->getIsUsed()) {
|
||||
// unused facets do not have active values.
|
||||
return [];
|
||||
}
|
||||
|
||||
$itemValues = [];
|
||||
$activeFacetItems = $facetToCheckRequirements->getAllFacetItems();
|
||||
foreach ($activeFacetItems as $item) {
|
||||
/** @var AbstractFacetItem $item */
|
||||
if ($item->getSelected()) {
|
||||
$itemValues[] = $item->getUriValue();
|
||||
}
|
||||
}
|
||||
|
||||
return $itemValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates the result when configured.
|
||||
*
|
||||
* @param boolean $value
|
||||
* @param array $configuration
|
||||
* @return boolean
|
||||
*/
|
||||
protected function getNegationWhenConfigured($value, $configuration)
|
||||
{
|
||||
if (!is_array($configuration) || empty($configuration['negate']) || (bool)$configuration['negate'] === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return !((bool)$value);
|
||||
}
|
||||
}
|
71
Classes/Domain/Search/ResultSet/Facets/SortingExpression.php
Normal file
71
Classes/Domain/Search/ResultSet/Facets/SortingExpression.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017- Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* Expression for facet sorting
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
* @author Jens Jacobsen <jens.jacobsen@ueberbit.de>
|
||||
*/
|
||||
class SortingExpression
|
||||
{
|
||||
/**
|
||||
* Return expression for facet sorting
|
||||
*
|
||||
* @param string $sorting
|
||||
* @return string
|
||||
*/
|
||||
public function getForFacet($sorting)
|
||||
{
|
||||
$noSortingSet = $sorting !== 0 && $sorting !== FALSE && empty($sorting);
|
||||
$sortingIsCount = $sorting === 'count' || $sorting === 1 || $sorting === '1' || $sorting === TRUE;
|
||||
if ($noSortingSet) {
|
||||
return '';
|
||||
} elseif ($sortingIsCount) {
|
||||
return 'count';
|
||||
} else {
|
||||
return 'index';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return expression for facet sorting combined with direction
|
||||
*
|
||||
* @param string $sorting
|
||||
* @param string $direction
|
||||
* @return string
|
||||
*/
|
||||
public function getForJsonFacet($sorting, $direction)
|
||||
{
|
||||
$isMetricSorting = strpos($sorting, 'metrics_') === 0;
|
||||
$expression = $isMetricSorting ? $sorting : $this->getForFacet($sorting);
|
||||
$direction = strtolower($direction);
|
||||
if (!empty($direction) && in_array($direction, ['asc', 'desc'])) {
|
||||
$expression .= ' ' . $direction;
|
||||
}
|
||||
return $expression;
|
||||
}
|
||||
}
|
122
Classes/Domain/Search/ResultSet/Grouping/Group.php
Normal file
122
Classes/Domain/Search/ResultSet/Grouping/Group.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* A group is identified by a groupName and can contain multiple groupItems (that reference the search results).
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Group
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $groupName = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $resultsPerPage = 10;
|
||||
|
||||
/**
|
||||
* @var GroupItemCollection
|
||||
*/
|
||||
protected $groupItems = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $groupConfiguration = [];
|
||||
|
||||
/**
|
||||
* Group constructor.
|
||||
* @param string $groupName
|
||||
* @param int $resultsPerPage
|
||||
*/
|
||||
public function __construct(string $groupName, int $resultsPerPage = 10)
|
||||
{
|
||||
$this->groupName = $groupName;
|
||||
$this->groupItems = new GroupItemCollection();
|
||||
$this->resultsPerPage = $resultsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupName(): string
|
||||
{
|
||||
return $this->groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $groupName
|
||||
*/
|
||||
public function setGroupName(string $groupName)
|
||||
{
|
||||
$this->groupName = $groupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GroupItemCollection
|
||||
*/
|
||||
public function getGroupItems(): GroupItemCollection
|
||||
{
|
||||
return $this->groupItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GroupItemCollection $groupItems
|
||||
*/
|
||||
public function setGroupItems(GroupItemCollection $groupItems)
|
||||
{
|
||||
$this->groupItems = $groupItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GroupItem $groupItem
|
||||
*/
|
||||
public function addGroupItem(GroupItem $groupItem)
|
||||
{
|
||||
$this->groupItems[] = $groupItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getResultsPerPage(): int
|
||||
{
|
||||
return $this->resultsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $resultsPerPage
|
||||
*/
|
||||
public function setResultsPerPage(int $resultsPerPage)
|
||||
{
|
||||
$this->resultsPerPage = $resultsPerPage;
|
||||
}
|
||||
}
|
88
Classes/Domain/Search/ResultSet/Grouping/GroupCollection.php
Normal file
88
Classes/Domain/Search/ResultSet/Grouping/GroupCollection.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* The Group contains the Group objects.
|
||||
*/
|
||||
class GroupCollection extends AbstractCollection {
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Group|null
|
||||
*/
|
||||
public function getByName($name)
|
||||
{
|
||||
foreach ($this->data as $group) {
|
||||
/** @var $group Group */
|
||||
if ($group->getGroupName() === $name) {
|
||||
return $group;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasWithName($name): bool
|
||||
{
|
||||
foreach ($this->data as $group) {
|
||||
/** @var $group Group */
|
||||
if ($group->getGroupName() === $name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getGroupNames(): array
|
||||
{
|
||||
$names = [];
|
||||
foreach ($this->data as $group) {
|
||||
/** @var $group Group */
|
||||
$names[] = $group->getGroupName();
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Group $group
|
||||
*/
|
||||
public function add(Group $group)
|
||||
{
|
||||
$this->data[] = $group;
|
||||
}
|
||||
}
|
156
Classes/Domain/Search/ResultSet/Grouping/GroupItem.php
Normal file
156
Classes/Domain/Search/ResultSet/Grouping/GroupItem.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.schmidt@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResult;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultCollection;
|
||||
|
||||
/**
|
||||
* Class GroupItem
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class GroupItem
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $groupValue = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $allResultCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $start = 0;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $maximumScore = 0;
|
||||
|
||||
/**
|
||||
* @var SearchResultCollection
|
||||
*/
|
||||
protected $searchResults;
|
||||
|
||||
/**
|
||||
* @var Group
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* @param Group $group
|
||||
* @param string $groupValue
|
||||
* @param int $numFound
|
||||
* @param int $start
|
||||
* @param float $maxScore
|
||||
*/
|
||||
public function __construct(Group $group, $groupValue, $numFound, $start, $maxScore)
|
||||
{
|
||||
$this->group = $group;
|
||||
$this->groupValue = $groupValue;
|
||||
$this->allResultCount = $numFound;
|
||||
$this->start = $start;
|
||||
$this->maximumScore = $maxScore;
|
||||
$this->searchResults = new SearchResultCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groupValue
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGroupValue()
|
||||
{
|
||||
return $this->groupValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get numFound
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAllResultCount()
|
||||
{
|
||||
return $this->allResultCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get start
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maxScore
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getMaximumScore()
|
||||
{
|
||||
return $this->maximumScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResultCollection
|
||||
*/
|
||||
public function getSearchResults(): SearchResultCollection
|
||||
{
|
||||
return $this->searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultCollection $searchResults
|
||||
*/
|
||||
public function setSearchResults(SearchResultCollection $searchResults)
|
||||
{
|
||||
$this->searchResults = $searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResult $searchResult
|
||||
*/
|
||||
public function addSearchResult(SearchResult $searchResult)
|
||||
{
|
||||
$this->searchResults[] = $searchResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Group
|
||||
*/
|
||||
public function getGroup(): Group
|
||||
{
|
||||
return $this->group;
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* The GroupItemCollection contains the GroupItem objects.
|
||||
*/
|
||||
class GroupItemCollection extends AbstractCollection {}
|
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* A ResultParser is responsible to create the result object structure from the \Apache_Solr_Response
|
||||
* and assign it to the SearchResultSet.
|
||||
*/
|
||||
abstract class AbstractResultParser {
|
||||
|
||||
/**
|
||||
* @var SearchResultBuilder
|
||||
*/
|
||||
protected $searchResultBuilder;
|
||||
|
||||
/**
|
||||
* @var DocumentEscapeService
|
||||
*/
|
||||
protected $documentEscapeService;
|
||||
|
||||
/**
|
||||
* AbstractResultParser constructor.
|
||||
* @param SearchResultBuilder|null $resultBuilder
|
||||
* @param DocumentEscapeService|null $documentEscapeService
|
||||
*/
|
||||
public function __construct(SearchResultBuilder $resultBuilder = null, DocumentEscapeService $documentEscapeService = null) {
|
||||
$this->searchResultBuilder = $resultBuilder ?? GeneralUtility::makeInstance(SearchResultBuilder::class);
|
||||
$this->documentEscapeService = $documentEscapeService ?? GeneralUtility::makeInstance(DocumentEscapeService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param bool $useRawDocuments
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
abstract public function parse(SearchResultSet $resultSet, bool $useRawDocuments = true);
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function canParse(SearchResultSet $resultSet);
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The DefaultResultParser is able to parse normal(ungroupd results)
|
||||
*/
|
||||
class DefaultResultParser extends AbstractResultParser {
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param bool $useRawDocuments
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function parse(SearchResultSet $resultSet, bool $useRawDocuments = true)
|
||||
{
|
||||
$searchResults = GeneralUtility::makeInstance(SearchResultCollection::class);
|
||||
$parsedData = $resultSet->getResponse()->getParsedData();
|
||||
|
||||
// @extensionScannerIgnoreLine
|
||||
$resultSet->setMaximumScore($parsedData->response->maxScore ?? 0.0);
|
||||
// @extensionScannerIgnoreLine
|
||||
$resultSet->setAllResultCount($parsedData->response->numFound ?? 0);
|
||||
|
||||
// @extensionScannerIgnoreLine
|
||||
if (!is_array($parsedData->response->docs)) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
// @extensionScannerIgnoreLine
|
||||
$documents = $parsedData->response->docs;
|
||||
if (!$useRawDocuments) {
|
||||
$documents = $this->documentEscapeService->applyHtmlSpecialCharsOnAllFields($documents);
|
||||
}
|
||||
|
||||
foreach ($documents as $searchResult) {
|
||||
$searchResultObject = $this->searchResultBuilder->fromApacheSolrDocument($searchResult);
|
||||
$searchResults[] = $searchResultObject;
|
||||
}
|
||||
|
||||
$resultSet->setSearchResults($searchResults);
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return bool
|
||||
*/
|
||||
public function canParse(SearchResultSet $resultSet)
|
||||
{
|
||||
// This parsers should not be used when grouping is enabled
|
||||
$configuration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration();
|
||||
if ($configuration instanceof TypoScriptConfiguration && $configuration->getSearchGrouping())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
|
||||
/**
|
||||
* Applies htmlspecialschars on documents of a solr response.
|
||||
*/
|
||||
class DocumentEscapeService {
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $typoScriptConfiguration = null;
|
||||
|
||||
/**
|
||||
* DocumentEscapeService constructor.
|
||||
* @param TypoScriptConfiguration|null $typoScriptConfiguration
|
||||
*/
|
||||
public function __construct(TypoScriptConfiguration $typoScriptConfiguration = null) {
|
||||
$this->typoScriptConfiguration = $typoScriptConfiguration ?? Util::getSolrConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to apply htmlspecialchars on all document fields that
|
||||
* are not configured to be secure. Secure mean that we know where the content is coming from.
|
||||
*
|
||||
* @param Document[] $documents
|
||||
* @return Document[]
|
||||
*/
|
||||
public function applyHtmlSpecialCharsOnAllFields(array $documents)
|
||||
{
|
||||
$trustedSolrFields = $this->typoScriptConfiguration->getSearchTrustedFieldsArray();
|
||||
|
||||
foreach ($documents as $key => $document) {
|
||||
$fieldNames = array_keys($document->getFields() ?? []);
|
||||
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
if (is_array($trustedSolrFields) && in_array($fieldName, $trustedSolrFields)) {
|
||||
// we skip this field, since it was marked as secure
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->applyHtmlSpecialCharsOnSingleFieldValue($document[$fieldName]);
|
||||
$document->setField($fieldName, $value);
|
||||
}
|
||||
|
||||
$documents[$key] = $document;
|
||||
}
|
||||
|
||||
return $documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies htmlspecialchars on all items of an array of a single value.
|
||||
*
|
||||
* @param $fieldValue
|
||||
* @return array|string
|
||||
*/
|
||||
protected function applyHtmlSpecialCharsOnSingleFieldValue($fieldValue)
|
||||
{
|
||||
if (is_array($fieldValue)) {
|
||||
foreach ($fieldValue as $key => $fieldValueItem) {
|
||||
$fieldValue[$key] = htmlspecialchars($fieldValueItem, null, null, false);
|
||||
}
|
||||
} else {
|
||||
$fieldValue = htmlspecialchars($fieldValue, null, null, false);
|
||||
}
|
||||
|
||||
return $fieldValue;
|
||||
}
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
*
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class ResultParserRegistry
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class ResultParserRegistry implements SingletonInterface
|
||||
{
|
||||
/**
|
||||
* Array of available parser classNames
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parsers = [
|
||||
100 => DefaultResultParser::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var AbstractResultParser[]
|
||||
*/
|
||||
protected $parserInstances;
|
||||
|
||||
/**
|
||||
* Get registered parser classNames
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParsers()
|
||||
{
|
||||
return $this->parsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to register a custom parser.
|
||||
*
|
||||
* @param string $className classname of the parser that should be used
|
||||
* @param int $priority higher priority means more important
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function registerParser($className, $priority)
|
||||
{
|
||||
// check if the class is available for TYPO3 before registering the driver
|
||||
if (!class_exists($className)) {
|
||||
throw new \InvalidArgumentException('Class ' . $className . ' does not exist.', 1468863997);
|
||||
}
|
||||
|
||||
if (!is_subclass_of($className, AbstractResultParser::class)) {
|
||||
throw new \InvalidArgumentException('Parser ' . $className . ' needs to implement the AbstractResultParser.', 1468863998);
|
||||
}
|
||||
|
||||
if (array_key_exists((int)$priority, $this->parsers)) {
|
||||
throw new \InvalidArgumentException('There is already a parser registerd with priority ' . (int)$priority . '.', 1468863999);
|
||||
}
|
||||
|
||||
$this->parsers[(int)$priority] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if a certain parser is allready registered
|
||||
*
|
||||
* @param string $className
|
||||
* @param int $priority
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasParser($className, $priority)
|
||||
{
|
||||
if (empty($this->parsers[$priority])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->parsers[$priority] === $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractResultParser[]
|
||||
*/
|
||||
public function getParserInstances()
|
||||
{
|
||||
if ($this->parserInstances === null) {
|
||||
ksort($this->parsers);
|
||||
$orderedParsers = array_reverse($this->parsers);
|
||||
foreach ($orderedParsers as $className) {
|
||||
$this->parserInstances[] = $this->createParserInstance($className);
|
||||
}
|
||||
}
|
||||
return $this->parserInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return AbstractResultParser|null
|
||||
*/
|
||||
public function getParser(SearchResultSet $resultSet)
|
||||
{
|
||||
/** @var AbstractResultParser $parser */
|
||||
foreach ($this->getParserInstances() as $parser) {
|
||||
if ($parser->canParse($resultSet)) {
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a certain parser class
|
||||
*
|
||||
* @return AbstractResultParser
|
||||
*/
|
||||
protected function createParserInstance($className)
|
||||
{
|
||||
return GeneralUtility::makeInstance($className);
|
||||
}
|
||||
}
|
242
Classes/Domain/Search/ResultSet/Result/SearchResult.php
Normal file
242
Classes/Domain/Search/ResultSet/Result/SearchResult.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 Timo Schmidt <timo.schmidt@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping\GroupItem;
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
|
||||
/**
|
||||
* Solr document class that should be used in the frontend in the search context.
|
||||
*
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
*/
|
||||
class SearchResult extends Document
|
||||
{
|
||||
/**
|
||||
* The variant field value
|
||||
*
|
||||
* Value of Solr collapse field, which is defined via
|
||||
* TypoScript variable "variants.variantField"
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $variantFieldValue = '';
|
||||
|
||||
/**
|
||||
* Number of variants found
|
||||
*
|
||||
* May differ from documents in variants as
|
||||
* returned variants are limited by expand.rows
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $variantsNumFound = 0;
|
||||
|
||||
/**
|
||||
* @var SearchResult[]
|
||||
*/
|
||||
protected $variants = [];
|
||||
|
||||
/**
|
||||
* Indicates if an instance of this document is a variant (a sub document of another).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isVariant = false;
|
||||
|
||||
/**
|
||||
* References the parent document of the document is a variant.
|
||||
*
|
||||
* @var SearchResult|null
|
||||
*/
|
||||
protected $variantParent = null;
|
||||
|
||||
/**
|
||||
* @var GroupItem
|
||||
*/
|
||||
protected $groupItem = null;
|
||||
|
||||
|
||||
/**
|
||||
* @return GroupItem
|
||||
*/
|
||||
public function getGroupItem(): GroupItem
|
||||
{
|
||||
return $this->groupItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasGroupItem()
|
||||
{
|
||||
return $this->groupItem !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GroupItem $group
|
||||
*/
|
||||
public function setGroupItem(GroupItem $group)
|
||||
{
|
||||
$this->groupItem = $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVariantFieldValue(): string
|
||||
{
|
||||
return $this->variantFieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $variantFieldValue
|
||||
*/
|
||||
public function setVariantFieldValue(string $variantFieldValue)
|
||||
{
|
||||
$this->variantFieldValue = $variantFieldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVariantsNumFound(): int
|
||||
{
|
||||
return $this->variantsNumFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $numFound
|
||||
*/
|
||||
public function setVariantsNumFound(int $numFound)
|
||||
{
|
||||
$this->variantsNumFound = $numFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResult[]
|
||||
*/
|
||||
public function getVariants()
|
||||
{
|
||||
return $this->variants;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResult $expandedResult
|
||||
*/
|
||||
public function addVariant(SearchResult $expandedResult)
|
||||
{
|
||||
$this->variants[] = $expandedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getIsVariant()
|
||||
{
|
||||
return $this->isVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isVariant
|
||||
*/
|
||||
public function setIsVariant($isVariant)
|
||||
{
|
||||
$this->isVariant = $isVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResult
|
||||
*/
|
||||
public function getVariantParent()
|
||||
{
|
||||
return $this->variantParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResult $variantParent
|
||||
*/
|
||||
public function setVariantParent(SearchResult $variantParent)
|
||||
{
|
||||
$this->variantParent = $variantParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->fields['content'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsElevated()
|
||||
{
|
||||
return $this->fields['isElevated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->fields['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->fields['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getScore()
|
||||
{
|
||||
return $this->fields['score'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->fields['url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->fields['title'];
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The SearchResultBuilder is responsible to build a SearchResult object from an \WapplerSystems\Meilisearch\System\Solr\Document\Document
|
||||
* and should use a different class as SearchResult if configured.
|
||||
*/
|
||||
class SearchResultBuilder {
|
||||
|
||||
/**
|
||||
* This method is used to wrap the original solr document instance in an instance of the configured SearchResult
|
||||
* class.
|
||||
*
|
||||
* @param Document $originalDocument
|
||||
* @throws \InvalidArgumentException
|
||||
* @return SearchResult
|
||||
*/
|
||||
public function fromApacheSolrDocument(Document $originalDocument)
|
||||
{
|
||||
|
||||
$searchResultClassName = $this->getResultClassName();
|
||||
$result = GeneralUtility::makeInstance($searchResultClassName, /** @scrutinizer ignore-type */ $originalDocument->getFields() ?? []);
|
||||
|
||||
if (!$result instanceof SearchResult) {
|
||||
throw new \InvalidArgumentException('Could not create result object with class: ' . (string)$searchResultClassName, 1470037679);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getResultClassName()
|
||||
{
|
||||
return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName ']) ?
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultClassName '] : SearchResult::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Grouping\GroupCollection;
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* The SearchResultCollection contains the SearchResult object and related objects. e.g groups.
|
||||
*/
|
||||
class SearchResultCollection extends AbstractCollection {
|
||||
|
||||
/**
|
||||
* @var GroupCollection
|
||||
*/
|
||||
protected $groups = null;
|
||||
|
||||
/**
|
||||
* SearchResultCollection constructor.
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
parent::__construct($data);
|
||||
$this->groups = new GroupCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GroupCollection
|
||||
*/
|
||||
public function getGroups(): GroupCollection
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GroupCollection $groups
|
||||
*/
|
||||
public function setGroups(GroupCollection $groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasGroups(): bool
|
||||
{
|
||||
return $this->groups->getCount() > 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetRegistry;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\RequirementsService;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting\Sorting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Spellchecking\Suggestion;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* This processor is used to transform the solr response into a
|
||||
* domain object hierarchy that can be used in the application (controller and view).
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class ResultSetReconstitutionProcessor implements SearchResultSetProcessor
|
||||
{
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @return ObjectManagerInterface
|
||||
*/
|
||||
public function getObjectManager()
|
||||
{
|
||||
if ($this->objectManager === null) {
|
||||
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
}
|
||||
return $this->objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function setObjectManager($objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return FacetRegistry
|
||||
*/
|
||||
protected function getFacetRegistry()
|
||||
{
|
||||
// @extensionScannerIgnoreLine
|
||||
return $this->getObjectManager()->get(FacetRegistry::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation can be used to influence a SearchResultSet that is
|
||||
* created and processed in the SearchResultSetService.
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function process(SearchResultSet $resultSet)
|
||||
{
|
||||
if (!$resultSet instanceof SearchResultSet) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
$resultSet = $this->parseSpellCheckingResponseIntoObjects($resultSet);
|
||||
$resultSet = $this->parseSortingIntoObjects($resultSet);
|
||||
|
||||
// here we can reconstitute other domain objects from the solr response
|
||||
$resultSet = $this->parseFacetsIntoObjects($resultSet);
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
protected function parseSortingIntoObjects(SearchResultSet $resultSet)
|
||||
{
|
||||
$configuration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration();
|
||||
$hasSorting = $resultSet->getUsedSearchRequest()->getHasSorting();
|
||||
$activeSortingName = $resultSet->getUsedSearchRequest()->getSortingName();
|
||||
$activeSortingDirection = $resultSet->getUsedSearchRequest()->getSortingDirection();
|
||||
|
||||
// no configuration available
|
||||
if (!isset($configuration)) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
// no sorting enabled
|
||||
if (!$configuration->getSearchSorting()) {
|
||||
return $resultSet;
|
||||
}
|
||||
foreach ($configuration->getSearchSortingOptionsConfiguration() as $sortingKeyName => $sortingOptions) {
|
||||
$sortingName = rtrim($sortingKeyName, '.');
|
||||
$selected = false;
|
||||
$direction = $configuration->getSearchSortingDefaultOrderBySortOptionName($sortingName);
|
||||
|
||||
// when we have an active sorting in the request we compare the sortingName and mark is as active and
|
||||
// use the direction from the request
|
||||
if ($hasSorting && $activeSortingName == $sortingName) {
|
||||
$selected = true;
|
||||
$direction = $activeSortingDirection;
|
||||
}
|
||||
|
||||
$field = $sortingOptions['field'];
|
||||
$label = $sortingOptions['label'];
|
||||
|
||||
$isResetOption = $field === 'relevance';
|
||||
|
||||
// Allow stdWrap on label:
|
||||
$labelHasSubConfiguration = is_array($sortingOptions['label.']);
|
||||
if ($labelHasSubConfiguration) {
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$label = $cObj->stdWrap($label, $sortingOptions['label.']);
|
||||
}
|
||||
|
||||
$sorting = $this->getObjectManager()->get(Sorting::class, $resultSet, $sortingName, $field, $direction, $label, $selected, $isResetOption);
|
||||
$resultSet->addSorting($sorting);
|
||||
}
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
private function parseSpellCheckingResponseIntoObjects(SearchResultSet $resultSet)
|
||||
{
|
||||
//read the response
|
||||
$response = $resultSet->getResponse();
|
||||
|
||||
if (!is_array($response->spellcheck->suggestions)) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
$misspelledTerm = '';
|
||||
foreach ($response->spellcheck->suggestions as $key => $suggestionData) {
|
||||
if (is_string($suggestionData)) {
|
||||
$misspelledTerm = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($misspelledTerm === '') {
|
||||
throw new \UnexpectedValueException('No missspelled term before suggestion');
|
||||
}
|
||||
|
||||
if (!is_object($suggestionData) && !is_array($suggestionData->suggestion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($suggestionData->suggestion as $suggestedTerm) {
|
||||
$suggestion = $this->createSuggestionFromResponseFragment($suggestionData, $suggestedTerm, $misspelledTerm);
|
||||
//add it to the resultSet
|
||||
$resultSet->addSpellCheckingSuggestion($suggestion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $suggestionData
|
||||
* @param string $suggestedTerm
|
||||
* @param string $misspelledTerm
|
||||
* @return Suggestion
|
||||
*/
|
||||
private function createSuggestionFromResponseFragment($suggestionData, $suggestedTerm, $misspelledTerm)
|
||||
{
|
||||
$numFound = isset($suggestionData->numFound) ? $suggestionData->numFound : 0;
|
||||
$startOffset = isset($suggestionData->startOffset) ? $suggestionData->startOffset : 0;
|
||||
$endOffset = isset($suggestionData->endOffset) ? $suggestionData->endOffset : 0;
|
||||
|
||||
// by now we avoid to use GeneralUtility::makeInstance, since we only create a value object
|
||||
// and the usage might be a overhead.
|
||||
$suggestion = new Suggestion($suggestedTerm, $misspelledTerm, $numFound, $startOffset, $endOffset);
|
||||
return $suggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse available facets into objects
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
private function parseFacetsIntoObjects(SearchResultSet $resultSet)
|
||||
{
|
||||
// Make sure we can access the facet configuration
|
||||
if (!$resultSet->getUsedSearchRequest() || !$resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
// Read the response
|
||||
$response = $resultSet->getResponse();
|
||||
if (!is_object($response->facet_counts) && !is_object($response->facets)) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/** @var FacetRegistry $facetRegistry */
|
||||
$facetRegistry = $this->getFacetRegistry();
|
||||
$facetsConfiguration = $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration()->getSearchFacetingFacets();
|
||||
|
||||
foreach ($facetsConfiguration as $name => $options) {
|
||||
if (!is_array($options)) {
|
||||
continue;
|
||||
}
|
||||
$facetName = rtrim($name, '.');
|
||||
$type = !empty($options['type']) ? $options['type'] : '';
|
||||
|
||||
$parser = $facetRegistry->getPackage($type)->getParser();
|
||||
$facet = $parser->parse($resultSet, $facetName, $options);
|
||||
if ($facet !== null) {
|
||||
$resultSet->addFacet($facet);
|
||||
}
|
||||
}
|
||||
|
||||
$this->applyRequirements($resultSet);
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
*/
|
||||
protected function applyRequirements(SearchResultSet $resultSet)
|
||||
{
|
||||
$requirementsService = $this->getRequirementsService();
|
||||
$facets = $resultSet->getFacets();
|
||||
foreach ($facets as $facet) {
|
||||
/** @var $facet AbstractFacet */
|
||||
$requirementsMet = $requirementsService->getAllRequirementsMet($facet);
|
||||
$facet->setAllRequirementsMet($requirementsMet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RequirementsService
|
||||
*/
|
||||
protected function getRequirementsService()
|
||||
{
|
||||
// @extensionScannerIgnoreLine
|
||||
return $this->getObjectManager()->get(RequirementsService::class);
|
||||
}
|
||||
}
|
445
Classes/Domain/Search/ResultSet/SearchResultSet.php
Normal file
445
Classes/Domain/Search/ResultSet/SearchResultSet.php
Normal file
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 Timo Schmidt <timo.schmidt@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\Query;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\AbstractFacet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\FacetCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResult;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting\Sorting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting\SortingCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Spellchecking\Suggestion;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\SearchRequest;
|
||||
use WapplerSystems\Meilisearch\Search;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ResponseAdapter;
|
||||
|
||||
/**
|
||||
* The SearchResultSet is used to provided access to the \Apache_Solr_Response and
|
||||
* other relevant information, like the used Query and Request objects.
|
||||
*
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
*/
|
||||
class SearchResultSet
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Query
|
||||
*/
|
||||
protected $usedQuery = null;
|
||||
|
||||
/**
|
||||
* @var SearchRequest
|
||||
*/
|
||||
protected $usedSearchRequest = null;
|
||||
|
||||
/**
|
||||
* @var Search
|
||||
*/
|
||||
protected $usedSearch;
|
||||
|
||||
/**
|
||||
* @var ResponseAdapter
|
||||
*/
|
||||
protected $response = null;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $usedPage = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $usedResultsPerPage = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $usedAdditionalFilters = [];
|
||||
|
||||
/**
|
||||
* @var SearchResultCollection
|
||||
*/
|
||||
protected $searchResults = null;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $allResultCount = 0;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $maximumScore = 0.0;
|
||||
|
||||
/**
|
||||
* @var Suggestion[]
|
||||
*/
|
||||
protected $spellCheckingSuggestions = [];
|
||||
|
||||
/**
|
||||
* @var FacetCollection
|
||||
*/
|
||||
protected $facets = null;
|
||||
|
||||
/**
|
||||
* @var SortingCollection
|
||||
*/
|
||||
protected $sortings = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isAutoCorrected = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $initialQueryString = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $correctedQueryString = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasSearched = false;
|
||||
|
||||
/**
|
||||
* @return \WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->facets = new FacetCollection();
|
||||
$this->sortings = new SortingCollection();
|
||||
$this->searchResults = new SearchResultCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $allResultCount
|
||||
*/
|
||||
public function setAllResultCount($allResultCount)
|
||||
{
|
||||
$this->allResultCount = $allResultCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAllResultCount()
|
||||
{
|
||||
return $this->allResultCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Suggestion $suggestion
|
||||
*/
|
||||
public function addSpellCheckingSuggestion(Suggestion $suggestion)
|
||||
{
|
||||
$this->spellCheckingSuggestions[$suggestion->getSuggestion()] = $suggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasSpellCheckingSuggestions()
|
||||
{
|
||||
return count($this->spellCheckingSuggestions) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WapplerSystems\Meilisearch\Domain\Search\ResultSet\Spellchecking\Suggestion[] $spellCheckingSuggestions
|
||||
*/
|
||||
public function setSpellCheckingSuggestions($spellCheckingSuggestions)
|
||||
{
|
||||
$this->spellCheckingSuggestions = $spellCheckingSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WapplerSystems\Meilisearch\Domain\Search\ResultSet\Spellchecking\Suggestion[]
|
||||
*/
|
||||
public function getSpellCheckingSuggestions()
|
||||
{
|
||||
return $this->spellCheckingSuggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FacetCollection
|
||||
*/
|
||||
public function getFacets()
|
||||
{
|
||||
return $this->facets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractFacet $facet
|
||||
*/
|
||||
public function addFacet(AbstractFacet $facet)
|
||||
{
|
||||
$this->facets->addFacet($facet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getMaximumScore()
|
||||
{
|
||||
return $this->maximumScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $maximumScore
|
||||
*/
|
||||
public function setMaximumScore($maximumScore)
|
||||
{
|
||||
$this->maximumScore = $maximumScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Sorting $sorting
|
||||
*/
|
||||
public function addSorting(Sorting $sorting)
|
||||
{
|
||||
$this->sortings->addSorting($sorting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SortingCollection
|
||||
*/
|
||||
public function getSortings()
|
||||
{
|
||||
return $this->sortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseAdapter $response
|
||||
*/
|
||||
public function setResponse($response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseAdapter
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $usedAdditionalFilters
|
||||
*/
|
||||
public function setUsedAdditionalFilters($usedAdditionalFilters)
|
||||
{
|
||||
$this->usedAdditionalFilters = $usedAdditionalFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getUsedAdditionalFilters()
|
||||
{
|
||||
return $this->usedAdditionalFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query $usedQuery
|
||||
*/
|
||||
public function setUsedQuery($usedQuery)
|
||||
{
|
||||
$this->usedQuery = $usedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query object that has been used to build this result set.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getUsedQuery()
|
||||
{
|
||||
return $this->usedQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
*/
|
||||
public function setUsedPage($page)
|
||||
{
|
||||
$this->usedPage = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the page argument that has been used to build this SearchResultSet.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUsedPage()
|
||||
{
|
||||
return $this->usedPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WapplerSystems\Meilisearch\Domain\Search\SearchRequest $usedSearchRequest
|
||||
*/
|
||||
public function setUsedSearchRequest($usedSearchRequest)
|
||||
{
|
||||
$this->usedSearchRequest = $usedSearchRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the SearchRequest that has been used to build this SearchResultSet.
|
||||
*
|
||||
* @return \WapplerSystems\Meilisearch\Domain\Search\SearchRequest
|
||||
*/
|
||||
public function getUsedSearchRequest()
|
||||
{
|
||||
return $this->usedSearchRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WapplerSystems\Meilisearch\Search $usedSearch
|
||||
*/
|
||||
public function setUsedSearch($usedSearch)
|
||||
{
|
||||
$this->usedSearch = $usedSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WapplerSystems\Meilisearch\Search
|
||||
*/
|
||||
public function getUsedSearch()
|
||||
{
|
||||
return $this->usedSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $usedResultsPerPage
|
||||
*/
|
||||
public function setUsedResultsPerPage($usedResultsPerPage)
|
||||
{
|
||||
$this->usedResultsPerPage = $usedResultsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUsedResultsPerPage()
|
||||
{
|
||||
return $this->usedResultsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResultCollection
|
||||
*/
|
||||
public function getSearchResults()
|
||||
{
|
||||
return $this->searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultCollection $searchResults
|
||||
*/
|
||||
public function setSearchResults($searchResults)
|
||||
{
|
||||
$this->searchResults = $searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResult $searchResult
|
||||
*/
|
||||
public function addSearchResult(SearchResult $searchResult)
|
||||
{
|
||||
$this->searchResults[] = $searchResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsAutoCorrected()
|
||||
{
|
||||
return $this->isAutoCorrected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $wasAutoCorrected
|
||||
*/
|
||||
public function setIsAutoCorrected($wasAutoCorrected)
|
||||
{
|
||||
$this->isAutoCorrected = $wasAutoCorrected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getInitialQueryString()
|
||||
{
|
||||
return $this->initialQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $initialQueryString
|
||||
*/
|
||||
public function setInitialQueryString($initialQueryString)
|
||||
{
|
||||
$this->initialQueryString = $initialQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCorrectedQueryString()
|
||||
{
|
||||
return $this->correctedQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $correctedQueryString
|
||||
*/
|
||||
public function setCorrectedQueryString($correctedQueryString)
|
||||
{
|
||||
$this->correctedQueryString = $correctedQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasSearched(): bool
|
||||
{
|
||||
return $this->hasSearched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $hasSearched
|
||||
*/
|
||||
public function setHasSearched(bool $hasSearched)
|
||||
{
|
||||
$this->hasSearched = $hasSearched;
|
||||
}
|
||||
}
|
44
Classes/Domain/Search/ResultSet/SearchResultSetProcessor.php
Normal file
44
Classes/Domain/Search/ResultSet/SearchResultSetProcessor.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 Timo Schmidt <timo.schmidt@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* The implementation can be used to influence a SearchResultSet that is
|
||||
* created and processed in the SearchResultSetService.
|
||||
*
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
*/
|
||||
interface SearchResultSetProcessor
|
||||
{
|
||||
/**
|
||||
* The implementation can be used to influence a SearchResultSet that is
|
||||
* created and processed in the SearchResultSetService.
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return mixed
|
||||
*/
|
||||
public function process(SearchResultSet $resultSet);
|
||||
}
|
499
Classes/Domain/Search/ResultSet/SearchResultSetService.php
Normal file
499
Classes/Domain/Search/ResultSet/SearchResultSetService.php
Normal file
@@ -0,0 +1,499 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 Timo Schmidt <timo.schmidt@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\QueryFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\QueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\Query;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\SearchQuery;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser\ResultParserRegistry;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResult;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultCollection;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\SearchRequest;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\SearchRequestAware;
|
||||
use WapplerSystems\Meilisearch\Domain\Variants\VariantsProcessor;
|
||||
use WapplerSystems\Meilisearch\Query\Modifier\Modifier;
|
||||
use WapplerSystems\Meilisearch\Search;
|
||||
use WapplerSystems\Meilisearch\Search\QueryAware;
|
||||
use WapplerSystems\Meilisearch\Search\SearchAware;
|
||||
use WapplerSystems\Meilisearch\Search\SearchComponentManager;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
use WapplerSystems\Meilisearch\System\Solr\ResponseAdapter;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrIncompleteResponseException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\SearchResultBuilder;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
/**
|
||||
* The SearchResultSetService is responsible to build a SearchResultSet from a SearchRequest.
|
||||
* It encapsulates the logic to trigger a search in order to be able to reuse it in multiple places.
|
||||
*
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
*/
|
||||
class SearchResultSetService
|
||||
{
|
||||
|
||||
/**
|
||||
* Track, if the number of results per page has been changed by the current request
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $resultsPerPageChanged = false;
|
||||
|
||||
/**
|
||||
* @var Search
|
||||
*/
|
||||
protected $search;
|
||||
|
||||
/**
|
||||
* @var SearchResultSet
|
||||
*/
|
||||
protected $lastResultSet = null;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isSolrAvailable = false;
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $typoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* @var SolrLogManager
|
||||
*/
|
||||
protected $logger = null;
|
||||
|
||||
/**
|
||||
* @var SearchResultBuilder
|
||||
*/
|
||||
protected $searchResultBuilder;
|
||||
|
||||
/**
|
||||
* @var QueryBuilder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @param Search $search
|
||||
* @param SolrLogManager $solrLogManager
|
||||
* @param SearchResultBuilder $resultBuilder
|
||||
* @param QueryBuilder $queryBuilder
|
||||
*/
|
||||
public function __construct(TypoScriptConfiguration $configuration, Search $search, SolrLogManager $solrLogManager = null, SearchResultBuilder $resultBuilder = null, QueryBuilder $queryBuilder = null)
|
||||
{
|
||||
$this->search = $search;
|
||||
$this->typoScriptConfiguration = $configuration;
|
||||
$this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
||||
$this->searchResultBuilder = $resultBuilder ?? GeneralUtility::makeInstance(SearchResultBuilder::class);
|
||||
$this->queryBuilder = $queryBuilder ?? GeneralUtility::makeInstance(QueryBuilder::class, /** @scrutinizer ignore-type */ $configuration, /** @scrutinizer ignore-type */ $solrLogManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
*/
|
||||
public function injectObjectManager(ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $useCache
|
||||
* @return bool
|
||||
*/
|
||||
public function getIsSolrAvailable($useCache = true)
|
||||
{
|
||||
$this->isSolrAvailable = $this->search->ping($useCache);
|
||||
return $this->isSolrAvailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the used search instance.
|
||||
*
|
||||
* @return Search
|
||||
*/
|
||||
public function getSearch()
|
||||
{
|
||||
return $this->search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query $query
|
||||
* @param SearchRequest $searchRequest
|
||||
*/
|
||||
protected function initializeRegisteredSearchComponents(Query $query, SearchRequest $searchRequest)
|
||||
{
|
||||
$searchComponents = $this->getRegisteredSearchComponents();
|
||||
|
||||
foreach ($searchComponents as $searchComponent) {
|
||||
/** @var Search\SearchComponent $searchComponent */
|
||||
$searchComponent->setSearchConfiguration($this->typoScriptConfiguration->getSearchConfiguration());
|
||||
|
||||
if ($searchComponent instanceof QueryAware) {
|
||||
$searchComponent->setQuery($query);
|
||||
}
|
||||
|
||||
if ($searchComponent instanceof SearchRequestAware) {
|
||||
$searchComponent->setSearchRequest($searchRequest);
|
||||
}
|
||||
|
||||
$searchComponent->initializeSearchComponent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getResultSetClassName()
|
||||
{
|
||||
return isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName ']) ?
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['searchResultSetClassName '] : SearchResultSet::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a search and returns a SearchResultSet.
|
||||
*
|
||||
* @param SearchRequest $searchRequest
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function search(SearchRequest $searchRequest)
|
||||
{
|
||||
$resultSet = $this->getInitializedSearchResultSet($searchRequest);
|
||||
$this->lastResultSet = $resultSet;
|
||||
|
||||
$resultSet = $this->handleSearchHook('beforeSearch', $resultSet);
|
||||
if ($this->shouldReturnEmptyResultSetWithoutExecutedSearch($searchRequest)) {
|
||||
$resultSet->setHasSearched(false);
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
$query = $this->queryBuilder->buildSearchQuery($searchRequest->getRawUserQuery(), (int)$searchRequest->getResultsPerPage(), $searchRequest->getAdditionalFilters());
|
||||
$this->initializeRegisteredSearchComponents($query, $searchRequest);
|
||||
$resultSet->setUsedQuery($query);
|
||||
|
||||
// performing the actual search, sending the query to the Solr server
|
||||
$query = $this->modifyQuery($query, $searchRequest, $this->search);
|
||||
$response = $this->doASearch($query, $searchRequest);
|
||||
|
||||
if ((int)$searchRequest->getResultsPerPage() === 0) {
|
||||
// when resultPerPage was forced to 0 we also set the numFound to 0 to hide results, e.g.
|
||||
// when results for the initial search should not be shown.
|
||||
// @extensionScannerIgnoreLine
|
||||
$response->response->numFound = 0;
|
||||
}
|
||||
|
||||
$resultSet->setHasSearched(true);
|
||||
$resultSet->setResponse($response);
|
||||
|
||||
$this->getParsedSearchResults($resultSet);
|
||||
|
||||
$resultSet->setUsedAdditionalFilters($this->queryBuilder->getAdditionalFilters());
|
||||
|
||||
/** @var $variantsProcessor VariantsProcessor */
|
||||
$variantsProcessor = GeneralUtility::makeInstance(
|
||||
VariantsProcessor::class,
|
||||
/** @scrutinizer ignore-type */ $this->typoScriptConfiguration,
|
||||
/** @scrutinizer ignore-type */ $this->searchResultBuilder
|
||||
);
|
||||
$variantsProcessor->process($resultSet);
|
||||
|
||||
/** @var $searchResultReconstitutionProcessor ResultSetReconstitutionProcessor */
|
||||
$searchResultReconstitutionProcessor = GeneralUtility::makeInstance(ResultSetReconstitutionProcessor::class);
|
||||
$searchResultReconstitutionProcessor->process($resultSet);
|
||||
|
||||
$resultSet = $this->getAutoCorrection($resultSet);
|
||||
|
||||
return $this->handleSearchHook('afterSearch', $resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the configured parser and retrieves the parsed search resutls.
|
||||
*
|
||||
* @param SearchResultSet $resultSet
|
||||
*/
|
||||
protected function getParsedSearchResults($resultSet)
|
||||
{
|
||||
/** @var ResultParserRegistry $parserRegistry */
|
||||
$parserRegistry = GeneralUtility::makeInstance(ResultParserRegistry::class, /** @scrutinizer ignore-type */ $this->typoScriptConfiguration);
|
||||
$useRawDocuments = (bool)$this->typoScriptConfiguration->getValueByPathOrDefaultValue('plugin.tx_meilisearch.features.useRawDocuments', false);
|
||||
$parserRegistry->getParser($resultSet)->parse($resultSet, $useRawDocuments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates conditions on the request and configuration and returns true if no search should be triggered and an empty
|
||||
* SearchResultSet should be returned.
|
||||
*
|
||||
* @param SearchRequest $searchRequest
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldReturnEmptyResultSetWithoutExecutedSearch(SearchRequest $searchRequest)
|
||||
{
|
||||
if ($searchRequest->getRawUserQueryIsNull() && !$this->getInitialSearchIsConfigured()) {
|
||||
// when no rawQuery was passed or no initialSearch is configured, we pass an empty result set
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($searchRequest->getRawUserQueryIsEmptyString() && !$this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
|
||||
// the user entered an empty query string "" or " " and empty querystring is not allowed
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the SearchResultSet from the SearchRequest
|
||||
*
|
||||
* @param SearchRequest $searchRequest
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
protected function getInitializedSearchResultSet(SearchRequest $searchRequest):SearchResultSet
|
||||
{
|
||||
/** @var $resultSet SearchResultSet */
|
||||
$resultSetClass = $this->getResultSetClassName();
|
||||
$resultSet = $this->objectManager->get($resultSetClass);
|
||||
|
||||
$resultSet->setUsedSearchRequest($searchRequest);
|
||||
$resultSet->setUsedPage((int)$searchRequest->getPage());
|
||||
$resultSet->setUsedResultsPerPage((int)$searchRequest->getResultsPerPage());
|
||||
$resultSet->setUsedSearch($this->search);
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the search and builds a fake response for a current bug in Apache Solr 6.3
|
||||
*
|
||||
* @param Query $query
|
||||
* @param SearchRequest $searchRequest
|
||||
* @return ResponseAdapter
|
||||
*/
|
||||
protected function doASearch($query, $searchRequest): ResponseAdapter
|
||||
{
|
||||
// the offset mulitplier is page - 1 but not less then zero
|
||||
$offsetMultiplier = max(0, $searchRequest->getPage() - 1);
|
||||
$offSet = $offsetMultiplier * (int)$searchRequest->getResultsPerPage();
|
||||
|
||||
$response = $this->search->search($query, $offSet, null);
|
||||
if($response === null) {
|
||||
throw new SolrIncompleteResponseException('The response retrieved from solr was incomplete', 1505989678);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $searchResultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
protected function getAutoCorrection(SearchResultSet $searchResultSet)
|
||||
{
|
||||
// no secondary search configured
|
||||
if (!$this->typoScriptConfiguration->getSearchSpellcheckingSearchUsingSpellCheckerSuggestion()) {
|
||||
return $searchResultSet;
|
||||
}
|
||||
|
||||
// more then zero results
|
||||
if ($searchResultSet->getAllResultCount() > 0) {
|
||||
return $searchResultSet;
|
||||
}
|
||||
|
||||
// no corrections present
|
||||
if (!$searchResultSet->getHasSpellCheckingSuggestions()) {
|
||||
return $searchResultSet;
|
||||
}
|
||||
|
||||
$searchResultSet = $this->peformAutoCorrection($searchResultSet);
|
||||
|
||||
return $searchResultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $searchResultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
protected function peformAutoCorrection(SearchResultSet $searchResultSet)
|
||||
{
|
||||
$searchRequest = $searchResultSet->getUsedSearchRequest();
|
||||
$suggestions = $searchResultSet->getSpellCheckingSuggestions();
|
||||
|
||||
$maximumRuns = $this->typoScriptConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry(1);
|
||||
$runs = 0;
|
||||
|
||||
foreach ($suggestions as $suggestion) {
|
||||
$runs++;
|
||||
|
||||
$correction = $suggestion->getSuggestion();
|
||||
$initialQuery = $searchRequest->getRawUserQuery();
|
||||
|
||||
$searchRequest->setRawQueryString($correction);
|
||||
$searchResultSet = $this->search($searchRequest);
|
||||
if ($searchResultSet->getAllResultCount() > 0) {
|
||||
$searchResultSet->setIsAutoCorrected(true);
|
||||
$searchResultSet->setCorrectedQueryString($correction);
|
||||
$searchResultSet->setInitialQueryString($initialQuery);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($runs > $maximumRuns) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $searchResultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to modify a query before eventually handing it over to Solr.
|
||||
*
|
||||
* @param Query $query The current query before it's being handed over to Solr.
|
||||
* @param SearchRequest $searchRequest The searchRequest, relevant in the current context
|
||||
* @param Search $search The search, relevant in the current context
|
||||
* @throws \UnexpectedValueException
|
||||
* @return Query The modified query that is actually going to be given to Solr.
|
||||
*/
|
||||
protected function modifyQuery(Query $query, SearchRequest $searchRequest, Search $search)
|
||||
{
|
||||
// hook to modify the search query
|
||||
if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'])) {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['modifySearchQuery'] as $classReference) {
|
||||
$queryModifier = $this->objectManager->get($classReference);
|
||||
|
||||
if ($queryModifier instanceof Modifier) {
|
||||
if ($queryModifier instanceof SearchAware) {
|
||||
$queryModifier->setSearch($search);
|
||||
}
|
||||
|
||||
if ($queryModifier instanceof SearchRequestAware) {
|
||||
$queryModifier->setSearchRequest($searchRequest);
|
||||
}
|
||||
|
||||
$query = $queryModifier->modifyQuery($query);
|
||||
} else {
|
||||
throw new \UnexpectedValueException(
|
||||
get_class($queryModifier) . ' must implement interface ' . Modifier::class,
|
||||
1310387414
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single document from solr by document id.
|
||||
*
|
||||
* @param string $documentId
|
||||
* @return SearchResult
|
||||
*/
|
||||
public function getDocumentById($documentId)
|
||||
{
|
||||
/* @var $query SearchQuery */
|
||||
$query = $this->queryBuilder->newSearchQuery($documentId)->useQueryFields(QueryFields::fromString('id'))->getQuery();
|
||||
$response = $this->search->search($query, 0, 1);
|
||||
$parsedData = $response->getParsedData();
|
||||
// @extensionScannerIgnoreLine
|
||||
$resultDocument = isset($parsedData->response->docs[0]) ? $parsedData->response->docs[0] : null;
|
||||
|
||||
if (!$resultDocument instanceof Document) {
|
||||
throw new \UnexpectedValueException("Response did not contain a valid Document object");
|
||||
}
|
||||
|
||||
return $this->searchResultBuilder->fromApacheSolrDocument($resultDocument);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to call the registered hooks during the search execution.
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
private function handleSearchHook($eventName, SearchResultSet $resultSet)
|
||||
{
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName])) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr'][$eventName] as $classReference) {
|
||||
$afterSearchProcessor = $this->objectManager->get($classReference);
|
||||
if ($afterSearchProcessor instanceof SearchResultSetProcessor) {
|
||||
$afterSearchProcessor->process($resultSet);
|
||||
}
|
||||
}
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function getLastResultSet()
|
||||
{
|
||||
return $this->lastResultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns true when the last search was executed with an empty query
|
||||
* string or whitespaces only. When no search was triggered it will return false.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getLastSearchWasExecutedWithEmptyQueryString()
|
||||
{
|
||||
$wasEmptyQueryString = false;
|
||||
if ($this->lastResultSet != null) {
|
||||
$wasEmptyQueryString = $this->lastResultSet->getUsedSearchRequest()->getRawUserQueryIsEmptyString();
|
||||
}
|
||||
|
||||
return $wasEmptyQueryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function getInitialSearchIsConfigured()
|
||||
{
|
||||
return $this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialEmptyQuery() || $this->typoScriptConfiguration->getSearchInitializeWithQuery() || $this->typoScriptConfiguration->getSearchShowResultsOfInitialQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRegisteredSearchComponents()
|
||||
{
|
||||
return GeneralUtility::makeInstance(SearchComponentManager::class)->getSearchComponents();
|
||||
}
|
||||
}
|
169
Classes/Domain/Search/ResultSet/Sorting/Sorting.php
Normal file
169
Classes/Domain/Search/ResultSet/Sorting/Sorting.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting;
|
||||
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
|
||||
/**
|
||||
* Class Sorting
|
||||
*/
|
||||
class Sorting
|
||||
{
|
||||
const DIRECTION_DESC = 'desc';
|
||||
|
||||
const DIRECTION_ASC = 'asc';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $validDirections = [self::DIRECTION_DESC, self::DIRECTION_ASC];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $field = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $direction = self::DIRECTION_ASC;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $selected = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isResetOption = false;
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @param string $direction
|
||||
* @param string $label
|
||||
* @param boolean $selected
|
||||
* @param boolean $isResetOption
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(SearchResultSet $resultSet, $name, $field, $direction, $label, $selected, $isResetOption)
|
||||
{
|
||||
if (!self::getIsValidDirection($direction)) {
|
||||
throw new \InvalidArgumentException("Invalid sorting direction");
|
||||
}
|
||||
$this->name = $name;
|
||||
$this->direction = $direction;
|
||||
$this->field = $field;
|
||||
$this->label = $label;
|
||||
$this->selected = $selected;
|
||||
$this->isResetOption = $isResetOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDirection()
|
||||
{
|
||||
return $this->direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getIsAscDirection()
|
||||
{
|
||||
return $this->direction === self::DIRECTION_ASC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getIsDescDirection()
|
||||
{
|
||||
return $this->direction === self::DIRECTION_DESC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the opposite direction of the current assigned direction.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOppositeDirection()
|
||||
{
|
||||
return self::getOppositeDirectionFromDirection($this->direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getField()
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getSelected()
|
||||
{
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
* @return bool
|
||||
*/
|
||||
public static function getIsValidDirection($direction)
|
||||
{
|
||||
return in_array($direction, self::$validDirections);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
* @return string
|
||||
*/
|
||||
public static function getOppositeDirectionFromDirection($direction)
|
||||
{
|
||||
if ($direction === self::DIRECTION_ASC) {
|
||||
return self::DIRECTION_DESC;
|
||||
} else {
|
||||
return self::DIRECTION_ASC;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsResetOption()
|
||||
{
|
||||
return $this->isResetOption;
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting;
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Data\AbstractCollection;
|
||||
|
||||
/**
|
||||
* Class SortingCollection
|
||||
*/
|
||||
class SortingCollection extends AbstractCollection
|
||||
{
|
||||
/**
|
||||
* @var Sorting
|
||||
*/
|
||||
protected $selected;
|
||||
|
||||
/**
|
||||
* @param Sorting $sorting
|
||||
*/
|
||||
public function addSorting(Sorting $sorting)
|
||||
{
|
||||
if ($sorting->getSelected()) {
|
||||
$this->selected = $sorting;
|
||||
}
|
||||
|
||||
$this->data[$sorting->getName()] = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Sorting $selected
|
||||
*/
|
||||
protected function setSelected(Sorting $selected)
|
||||
{
|
||||
$this->selected = $selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Sorting
|
||||
*/
|
||||
public function getSelected()
|
||||
{
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasSelected()
|
||||
{
|
||||
return $this->selected !== null;
|
||||
}
|
||||
}
|
82
Classes/Domain/Search/ResultSet/Sorting/SortingHelper.php
Normal file
82
Classes/Domain/Search/ResultSet/Sorting/SortingHelper.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Sorting;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2018 Timo Hund <timo.hund@dkd.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Class SortingHelper
|
||||
*/
|
||||
class SortingHelper {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $sortingConfiguration Raw configuration from plugin.tx_meilisearch.search.sorting.options
|
||||
*/
|
||||
public function __construct(array $sortingConfiguration)
|
||||
{
|
||||
$this->configuration = $sortingConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the tx_meilisearch[sort] URL parameter containing the option names and
|
||||
* directions to sort by and resolves it to the actual sort fields and
|
||||
* directions as configured through TypoScript. Makes sure that only
|
||||
* configured sorting options get applied to the query.
|
||||
*
|
||||
* @param string $urlParameters tx_meilisearch[sort] URL parameter.
|
||||
* @return string The actual index field configured to sort by for the given sort option name
|
||||
* @throws \InvalidArgumentException if the given sort option is not configured
|
||||
*/
|
||||
public function getSortFieldFromUrlParameter($urlParameters)
|
||||
{
|
||||
$sortFields = [];
|
||||
$sortParameters = GeneralUtility::trimExplode(',', $urlParameters);
|
||||
|
||||
$removeTsKeyDot = function($sortingKey) { return trim($sortingKey, '.'); };
|
||||
$configuredSortingName = array_map($removeTsKeyDot, array_keys($this->configuration));
|
||||
|
||||
foreach ($sortParameters as $sortParameter) {
|
||||
list($sortOption, $sortDirection) = explode(' ', $sortParameter);
|
||||
|
||||
if (!in_array($sortOption, $configuredSortingName)) {
|
||||
throw new \InvalidArgumentException('No sorting configuration found for option name ' . $sortOption, 1316187644);
|
||||
}
|
||||
|
||||
$sortField = $this->configuration[$sortOption . '.']['field'];
|
||||
$sortFields[] = $sortField . ' ' . $sortDirection;
|
||||
}
|
||||
|
||||
return implode(', ', $sortFields);
|
||||
}
|
||||
}
|
106
Classes/Domain/Search/ResultSet/Spellchecking/Suggestion.php
Normal file
106
Classes/Domain/Search/ResultSet/Spellchecking/Suggestion.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ResultSet\Spellchecking;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Value object that represent a spellchecking suggestion.
|
||||
*
|
||||
* @author Frans Saris <frans@beech.it>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Suggestion
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $suggestion = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $missSpelled = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numFound = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $startOffset = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $endOffset = 0;
|
||||
|
||||
/**
|
||||
* @param string $suggestion the suggested term
|
||||
* @param string $missSpelled the misspelled original term
|
||||
* @param int $numFound
|
||||
* @param int $startOffset
|
||||
* @param int $endOffset
|
||||
*/
|
||||
public function __construct($suggestion = '', $missSpelled = '', $numFound = 1, $startOffset = 0, $endOffset = 0)
|
||||
{
|
||||
$this->suggestion = $suggestion;
|
||||
$this->missSpelled = $missSpelled;
|
||||
$this->numFound = $numFound;
|
||||
$this->startOffset = $startOffset;
|
||||
$this->endOffset = $endOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEndOffset()
|
||||
{
|
||||
return $this->endOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNumFound()
|
||||
{
|
||||
return $this->numFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStartOffset()
|
||||
{
|
||||
return $this->startOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSuggestion()
|
||||
{
|
||||
return $this->suggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMissSpelled()
|
||||
{
|
||||
return $this->missSpelled;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user