first commit
This commit is contained in:
111
Classes/Domain/Index/Classification/Classification.php
Normal file
111
Classes/Domain/Index/Classification/Classification.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Classification;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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 Classification
|
||||
*/
|
||||
class Classification {
|
||||
|
||||
/**
|
||||
* Array of regular expressions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $matchPatterns = [];
|
||||
|
||||
/**
|
||||
* Array of regular expressions
|
||||
* @var array
|
||||
*/
|
||||
protected $unMatchPatterns = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $mappedClass = '';
|
||||
|
||||
/**
|
||||
* Classification constructor.
|
||||
* @param array $matchPatterns
|
||||
* @param array $unMatchPatterns
|
||||
* @param string $mappedClass
|
||||
*/
|
||||
public function __construct(array $matchPatterns = [], array $unMatchPatterns = [],string $mappedClass = '')
|
||||
{
|
||||
$this->matchPatterns = $matchPatterns;
|
||||
$this->unMatchPatterns = $unMatchPatterns;
|
||||
$this->mappedClass = $mappedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getUnMatchPatterns(): array
|
||||
{
|
||||
return $this->unMatchPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $unMatchPatterns
|
||||
*/
|
||||
public function setUnMatchPatterns(array $unMatchPatterns)
|
||||
{
|
||||
$this->unMatchPatterns = $unMatchPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMatchPatterns(): array
|
||||
{
|
||||
return $this->matchPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $matchPatterns
|
||||
*/
|
||||
public function setMatchPatterns(array $matchPatterns)
|
||||
{
|
||||
$this->matchPatterns = $matchPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMappedClass(): string
|
||||
{
|
||||
return $this->mappedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mappedClass
|
||||
*/
|
||||
public function setMappedClass(string $mappedClass)
|
||||
{
|
||||
$this->mappedClass = $mappedClass;
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Classification;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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 ClassificationService
|
||||
*/
|
||||
class ClassificationService {
|
||||
|
||||
/**
|
||||
* @param string $stringToMatch
|
||||
* @param Classification[] $classifications
|
||||
* @return array
|
||||
*/
|
||||
public function getMatchingClassNames(string $stringToMatch, $classifications) : array
|
||||
{
|
||||
$matchingClassification = [];
|
||||
foreach ($classifications as $classification) {
|
||||
$matchingClassification = $this->applyMatchPatterns($stringToMatch, $classification, $matchingClassification);
|
||||
$matchingClassification = $this->applyUnMatchPatterns($stringToMatch, $classification, $matchingClassification);
|
||||
}
|
||||
|
||||
return array_values($matchingClassification);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stringToMatch
|
||||
* @param Classification $classification
|
||||
* @param $matchingClassification
|
||||
* @return array
|
||||
*/
|
||||
protected function applyMatchPatterns(string $stringToMatch, $classification, $matchingClassification): array
|
||||
{
|
||||
/** @var $classification Classification */
|
||||
foreach ($classification->getMatchPatterns() as $matchPattern) {
|
||||
if (preg_match_all('~' . $matchPattern . '~ims', $stringToMatch) > 0) {
|
||||
$matchingClassification[] = $classification->getMappedClass();
|
||||
// if we found one match, we do not need to check the other patterns
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array_unique($matchingClassification);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stringToMatch
|
||||
* @param Classification $classification
|
||||
* @param $matchingClassification
|
||||
* @param $messages
|
||||
* @return array
|
||||
*/
|
||||
protected function applyUnMatchPatterns(string $stringToMatch, $classification, $matchingClassification): array
|
||||
{
|
||||
foreach ($classification->getUnMatchPatterns() as $unMatchPattern) {
|
||||
if (preg_match_all('~' . $unMatchPattern . '~ims', $stringToMatch) > 0) {
|
||||
// if we found one match, we do not need to check the other patterns
|
||||
$position = array_search($classification->getMappedClass(), $matchingClassification);
|
||||
if ($position !== false) {
|
||||
unset($matchingClassification[$position]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matchingClassification;
|
||||
}
|
||||
}
|
318
Classes/Domain/Index/IndexService.php
Normal file
318
Classes/Domain/Index/IndexService.php
Normal file
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 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\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Indexer;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Item;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Queue;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use WapplerSystems\Meilisearch\Task\IndexQueueWorkerTask;
|
||||
use Solarium\Exception\HttpException;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
|
||||
|
||||
/**
|
||||
* Service to perform indexing operations
|
||||
*
|
||||
* @author Timo Hund <timo.schmidt@dkd.de>
|
||||
*/
|
||||
class IndexService
|
||||
{
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var Site
|
||||
*/
|
||||
protected $site;
|
||||
|
||||
/**
|
||||
* @var IndexQueueWorkerTask
|
||||
*/
|
||||
protected $contextTask;
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $indexQueue;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $signalSlotDispatcher;
|
||||
|
||||
/**
|
||||
* @var \WapplerSystems\Meilisearch\System\Logging\SolrLogManager
|
||||
*/
|
||||
protected $logger = null;
|
||||
|
||||
/**
|
||||
* IndexService constructor.
|
||||
* @param Site $site
|
||||
* @param Queue|null $queue
|
||||
* @param Dispatcher|null $dispatcher
|
||||
* @param SolrLogManager|null $solrLogManager
|
||||
*/
|
||||
public function __construct(Site $site, Queue $queue = null, Dispatcher $dispatcher = null, SolrLogManager $solrLogManager = null)
|
||||
{
|
||||
$this->site = $site;
|
||||
$this->indexQueue = $queue ?? GeneralUtility::makeInstance(Queue::class);
|
||||
$this->signalSlotDispatcher = $dispatcher ?? GeneralUtility::makeInstance(Dispatcher::class);
|
||||
$this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WapplerSystems\Meilisearch\Task\IndexQueueWorkerTask $contextTask
|
||||
*/
|
||||
public function setContextTask($contextTask)
|
||||
{
|
||||
$this->contextTask = $contextTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WapplerSystems\Meilisearch\Task\IndexQueueWorkerTask
|
||||
*/
|
||||
public function getContextTask()
|
||||
{
|
||||
return $this->contextTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes items from the Index Queue.
|
||||
*
|
||||
* @param int $limit
|
||||
* @return bool
|
||||
*/
|
||||
public function indexItems($limit)
|
||||
{
|
||||
$errors = 0;
|
||||
$indexRunId = uniqid();
|
||||
$configurationToUse = $this->site->getSolrConfiguration();
|
||||
$enableCommitsSetting = $configurationToUse->getEnableCommits();
|
||||
|
||||
// get items to index
|
||||
$itemsToIndex = $this->indexQueue->getItemsToIndex($this->site, $limit);
|
||||
|
||||
$this->emitSignal('beforeIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
|
||||
|
||||
foreach ($itemsToIndex as $itemToIndex) {
|
||||
try {
|
||||
// try indexing
|
||||
$this->emitSignal('beforeIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
|
||||
$this->indexItem($itemToIndex, $configurationToUse);
|
||||
$this->emitSignal('afterIndexItem', [$itemToIndex, $this->getContextTask(), $indexRunId]);
|
||||
} catch (\Exception $e) {
|
||||
$errors++;
|
||||
$this->indexQueue->markItemAsFailed($itemToIndex, $e->getCode() . ': ' . $e->__toString());
|
||||
$this->generateIndexingErrorLog($itemToIndex, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->emitSignal('afterIndexItems', [$itemsToIndex, $this->getContextTask(), $indexRunId]);
|
||||
|
||||
if ($enableCommitsSetting && count($itemsToIndex) > 0) {
|
||||
$solrServers = GeneralUtility::makeInstance(ConnectionManager::class)->getConnectionsBySite($this->site);
|
||||
foreach ($solrServers as $solrServer) {
|
||||
try {
|
||||
$solrServer->getWriteService()->commit(false, false, false);
|
||||
} catch (HttpException $e) {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ($errors === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a message in the error log when an error occured.
|
||||
*
|
||||
* @param Item $itemToIndex
|
||||
* @param \Exception $e
|
||||
*/
|
||||
protected function generateIndexingErrorLog(Item $itemToIndex, \Exception $e)
|
||||
{
|
||||
$message = 'Failed indexing Index Queue item ' . $itemToIndex->getIndexQueueUid();
|
||||
$data = ['code' => $e->getCode(), 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'item' => (array)$itemToIndex];
|
||||
|
||||
$this->logger->log(
|
||||
SolrLogManager::ERROR,
|
||||
$message,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an emits a singal for the IndexService.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
protected function emitSignal($name, $arguments)
|
||||
{
|
||||
return $this->signalSlotDispatcher->dispatch(__CLASS__, $name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes an item from the Index Queue.
|
||||
*
|
||||
* @param Item $item An index queue item to index
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return bool TRUE if the item was successfully indexed, FALSE otherwise
|
||||
*/
|
||||
protected function indexItem(Item $item, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$indexer = $this->getIndexerByItem($item->getIndexingConfigurationName(), $configuration);
|
||||
|
||||
// Remember original http host value
|
||||
$originalHttpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
|
||||
|
||||
$itemChangedDate = $item->getChanged();
|
||||
$itemChangedDateAfterIndex = 0;
|
||||
|
||||
try {
|
||||
$this->initializeHttpServerEnvironment($item);
|
||||
$itemIndexed = $indexer->index($item);
|
||||
|
||||
// update IQ item so that the IQ can determine what's been indexed already
|
||||
if ($itemIndexed) {
|
||||
$this->indexQueue->updateIndexTimeByItem($item);
|
||||
$itemChangedDateAfterIndex = $item->getChanged();
|
||||
}
|
||||
|
||||
if ($itemChangedDateAfterIndex > $itemChangedDate && $itemChangedDateAfterIndex > time()) {
|
||||
$this->indexQueue->setForcedChangeTimeByItem($item, $itemChangedDateAfterIndex);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->restoreOriginalHttpHost($originalHttpHost);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->restoreOriginalHttpHost($originalHttpHost);
|
||||
|
||||
return $itemIndexed;
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory method to get an indexer depending on an item's configuration.
|
||||
*
|
||||
* By default all items are indexed using the default indexer
|
||||
* (WapplerSystems\Meilisearch\IndexQueue\Indexer) coming with EXT:meilisearch. Pages by default are
|
||||
* configured to be indexed through a dedicated indexer
|
||||
* (WapplerSystems\Meilisearch\IndexQueue\PageIndexer). In all other cases a dedicated indexer
|
||||
* can be specified through TypoScript if needed.
|
||||
*
|
||||
* @param string $indexingConfigurationName Indexing configuration name.
|
||||
* @param TypoScriptConfiguration $configuration
|
||||
* @return Indexer
|
||||
*/
|
||||
protected function getIndexerByItem($indexingConfigurationName, TypoScriptConfiguration $configuration)
|
||||
{
|
||||
$indexerClass = $configuration->getIndexQueueIndexerByConfigurationName($indexingConfigurationName);
|
||||
$indexerConfiguration = $configuration->getIndexQueueIndexerConfigurationByConfigurationName($indexingConfigurationName);
|
||||
|
||||
$indexer = GeneralUtility::makeInstance($indexerClass, /** @scrutinizer ignore-type */ $indexerConfiguration);
|
||||
if (!($indexer instanceof Indexer)) {
|
||||
throw new \RuntimeException(
|
||||
'The indexer class "' . $indexerClass . '" for indexing configuration "' . $indexingConfigurationName . '" is not a valid indexer. Must be a subclass of WapplerSystems\Meilisearch\IndexQueue\Indexer.',
|
||||
1260463206
|
||||
);
|
||||
}
|
||||
|
||||
return $indexer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the indexing progress.
|
||||
*
|
||||
* @return float Indexing progress as a two decimal precision float. f.e. 44.87
|
||||
*/
|
||||
public function getProgress()
|
||||
{
|
||||
return $this->indexQueue->getStatisticsBySite($this->site)->getSuccessPercentage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of failed queue items for the current site.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFailCount()
|
||||
{
|
||||
return $this->indexQueue->getStatisticsBySite($this->site)->getFailedCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the $_SERVER['HTTP_HOST'] environment variable in CLI
|
||||
* environments dependent on the Index Queue item's root page.
|
||||
*
|
||||
* When the Index Queue Worker task is executed by a cron job there is no
|
||||
* HTTP_HOST since we are in a CLI environment. RealURL needs the host
|
||||
* information to generate a proper URL though. Using the Index Queue item's
|
||||
* root page information we can determine the correct host although being
|
||||
* in a CLI environment.
|
||||
*
|
||||
* @param Item $item Index Queue item to use to determine the host.
|
||||
* @param
|
||||
*/
|
||||
protected function initializeHttpServerEnvironment(Item $item)
|
||||
{
|
||||
static $hosts = [];
|
||||
$rootpageId = $item->getRootPageUid();
|
||||
$hostFound = !empty($hosts[$rootpageId]);
|
||||
|
||||
if (!$hostFound) {
|
||||
$hosts[$rootpageId] = $item->getSite()->getDomain();
|
||||
}
|
||||
|
||||
$_SERVER['HTTP_HOST'] = $hosts[$rootpageId];
|
||||
|
||||
// needed since TYPO3 7.5
|
||||
GeneralUtility::flushInternalRuntimeCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $originalHttpHost
|
||||
*/
|
||||
protected function restoreOriginalHttpHost($originalHttpHost)
|
||||
{
|
||||
if (!is_null($originalHttpHost)) {
|
||||
$_SERVER['HTTP_HOST'] = $originalHttpHost;
|
||||
} else {
|
||||
unset($_SERVER['HTTP_HOST']);
|
||||
}
|
||||
|
||||
// needed since TYPO3 7.5
|
||||
GeneralUtility::flushInternalRuntimeCaches();
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\PageIndexer\Helper\UriBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2019 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.
|
||||
* A copy is found in the textfile GPL.txt and important notices to the license
|
||||
* from the author is found in LICENSE.txt distributed with these scripts.
|
||||
*
|
||||
*
|
||||
* 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\IndexQueue\Item;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\PageIndexerDataUrlModifier;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use WapplerSystems\Meilisearch\System\Url\UrlHelper;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Implementations of this class are able to build an indexing url for solr page indexing.
|
||||
*/
|
||||
abstract class AbstractUriStrategy
|
||||
{
|
||||
/**
|
||||
* @var SolrLogManager|null|object
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* AbstractUriStrategy constructor.
|
||||
* @param SolrLogManager|null $logger
|
||||
*/
|
||||
public function __construct(SolrLogManager $logger = null)
|
||||
{
|
||||
$this->logger = $logger ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UrlHelper $urlHelper
|
||||
* @param array $overrideConfiguration
|
||||
* @return UrlHelper
|
||||
*/
|
||||
protected function applyTypoScriptOverridesOnIndexingUrl(UrlHelper $urlHelper, array $overrideConfiguration = []): UrlHelper
|
||||
{
|
||||
// check whether we should use ssl / https
|
||||
if (!empty($overrideConfiguration['scheme'])) {
|
||||
$urlHelper->setScheme($overrideConfiguration['scheme']);
|
||||
}
|
||||
|
||||
// overwriting the host
|
||||
if (!empty($overrideConfiguration['host'])) {
|
||||
$urlHelper->setHost($overrideConfiguration['host']);
|
||||
}
|
||||
|
||||
// overwriting the port
|
||||
if (!empty($overrideConfiguration['port'])) {
|
||||
$urlHelper->setPort($overrideConfiguration['port']);
|
||||
}
|
||||
|
||||
// setting a path if TYPO3 is installed in a sub directory
|
||||
if (!empty($overrideConfiguration['path'])) {
|
||||
$urlHelper->setPath($overrideConfiguration['path']);
|
||||
}
|
||||
|
||||
return $urlHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
* @param int $language
|
||||
* @param string $mountPointParameter
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public function getPageIndexingUriFromPageItemAndLanguageId(Item $item, int $language = 0, string $mountPointParameter = '', $options = []): string
|
||||
{
|
||||
$pageIndexUri = $this->buildPageIndexingUriFromPageItemAndLanguageId($item, $language, $mountPointParameter);
|
||||
$urlHelper = GeneralUtility::makeInstance(UrlHelper::class, $pageIndexUri);
|
||||
$overrideConfiguration = is_array($options['frontendDataHelper.']) ? $options['frontendDataHelper.'] : [];
|
||||
$urlHelper = $this->applyTypoScriptOverridesOnIndexingUrl($urlHelper, $overrideConfiguration);
|
||||
$dataUrl = $urlHelper->getUrl();
|
||||
|
||||
if (!GeneralUtility::isValidUrl($dataUrl)) {
|
||||
$this->logger->log(
|
||||
SolrLogManager::ERROR,
|
||||
'Could not create a valid URL to get frontend data while trying to index a page.',
|
||||
[
|
||||
'item' => (array)$item,
|
||||
'constructed URL' => $dataUrl,
|
||||
'scheme' => $urlHelper->getScheme(),
|
||||
'host' => $urlHelper->getHost(),
|
||||
'path' => $urlHelper->getPath(),
|
||||
'page ID' => $item->getRecordUid(),
|
||||
'indexer options' => $options
|
||||
]
|
||||
);
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Could not create a valid URL to get frontend data while trying to index a page. Created URL: ' . $dataUrl,
|
||||
1311080805
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return $this->applyDataUrlModifier($item, $language, $dataUrl, $urlHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
* @param int $language
|
||||
* @param string $mountPointParameter
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function buildPageIndexingUriFromPageItemAndLanguageId(Item $item, int $language = 0, string $mountPointParameter = '');
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
* @param int $language
|
||||
* @param string $dataUrl
|
||||
* @param UrlHelper $urlHelper
|
||||
* @return string
|
||||
*/
|
||||
protected function applyDataUrlModifier(Item $item, int $language, $dataUrl, UrlHelper $urlHelper):string
|
||||
{
|
||||
if (empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier'])) {
|
||||
return $dataUrl;
|
||||
}
|
||||
|
||||
$dataUrlModifier = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier']);
|
||||
if (!$dataUrlModifier instanceof PageIndexerDataUrlModifier) {
|
||||
throw new \RuntimeException($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['IndexQueuePageIndexer']['dataUrlModifier'] . ' is not an implementation of WapplerSystems\Meilisearch\IndexQueue\PageIndexerDataUrlModifier', 1290523345);
|
||||
}
|
||||
|
||||
return $dataUrlModifier->modifyDataUrl($dataUrl,
|
||||
[
|
||||
'item' => $item, 'scheme' => $urlHelper->getScheme(), 'host' => $urlHelper->getHost(),
|
||||
'path' => $urlHelper->getPath(), 'pageId' => $item->getRecordUid(), 'language' => $language
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\PageIndexer\Helper\UriBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2019 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.
|
||||
* A copy is found in the textfile GPL.txt and important notices to the license
|
||||
* from the author is found in LICENSE.txt distributed with these scripts.
|
||||
*
|
||||
*
|
||||
* 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\IndexQueue\Item;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class is used to build the indexing url for a TYPO3 site that is managed with the TYPO3 site management.
|
||||
*
|
||||
* These sites have the pageId and language information encoded in the speaking url.
|
||||
*/
|
||||
class TYPO3SiteStrategy extends AbstractUriStrategy
|
||||
{
|
||||
/**
|
||||
* @var SiteFinder
|
||||
*/
|
||||
protected $siteFinder = null;
|
||||
|
||||
/**
|
||||
* TYPO3SiteStrategy constructor.
|
||||
* @param SolrLogManager|null $logger
|
||||
* @param SiteFinder|null $siteFinder
|
||||
*/
|
||||
public function __construct(SolrLogManager $logger = null, SiteFinder $siteFinder = null)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Item $item
|
||||
* @param int $language
|
||||
* @param string $mountPointParameter
|
||||
* @return string
|
||||
* @throws SiteNotFoundException
|
||||
* @throws InvalidRouteArgumentsException
|
||||
*/
|
||||
protected function buildPageIndexingUriFromPageItemAndLanguageId(Item $item, int $language = 0, string $mountPointParameter = '')
|
||||
{
|
||||
$site = $this->siteFinder->getSiteByPageId((int)$item->getRecordUid());
|
||||
$parameters = [];
|
||||
|
||||
if ($language > 0) {
|
||||
$parameters['_language'] = $language;
|
||||
};
|
||||
|
||||
if ($mountPointParameter !== '') {
|
||||
$parameters['MP'] = $mountPointParameter;
|
||||
}
|
||||
|
||||
$pageIndexUri = (string)$site->getRouter()->generateUri($item->getRecord(), $parameters);
|
||||
return $pageIndexUri;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\PageIndexer\Helper;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2019 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.
|
||||
* A copy is found in the textfile GPL.txt and important notices to the license
|
||||
* from the author is found in LICENSE.txt distributed with these scripts.
|
||||
*
|
||||
*
|
||||
* 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\Index\PageIndexer\Helper\UriBuilder\AbstractUriStrategy;
|
||||
use WapplerSystems\Meilisearch\Domain\Index\PageIndexer\Helper\UriBuilder\TYPO3SiteStrategy;
|
||||
use WapplerSystems\Meilisearch\System\Util\SiteUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class is responsible to retrieve an "UriStrategy" the can build uri's for the site where the
|
||||
* passed page belongs to.
|
||||
*
|
||||
* This can be:
|
||||
* * A TYPO3 site managed with site management
|
||||
* * A TYPO3 site without site management where the url is build by EXT:meilisearch with L and id param and information from the domain
|
||||
* record or solr specific configuration.
|
||||
*/
|
||||
class UriStrategyFactory
|
||||
{
|
||||
/**
|
||||
* @param integer $pageId
|
||||
* @oaram array $overrideConfiguration
|
||||
* @return AbstractUriStrategy
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getForPageId(int $pageId): AbstractUriStrategy
|
||||
{
|
||||
if (!SiteUtility::getIsSiteManagedSite($pageId)) {
|
||||
throw new \Exception('Site of page with uid ' . $pageId . ' is not a TYPO3 managed site');
|
||||
}
|
||||
|
||||
return GeneralUtility::makeInstance(TYPO3SiteStrategy::class);
|
||||
}
|
||||
}
|
175
Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php
Normal file
175
Classes/Domain/Index/Queue/GarbageRemover/AbstractStrategy.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\GarbageRemover;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 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 WapplerSystems\Meilisearch\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\GarbageCollectorPostProcessor;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Queue;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrConnection;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* An implementation ob a garbage remover strategy is responsible to remove all garbage from the index queue and
|
||||
* the solr server for a certain table and uid combination.
|
||||
*/
|
||||
abstract class AbstractStrategy
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* @var ConnectionManager
|
||||
*/
|
||||
protected $connectionManager;
|
||||
|
||||
/**
|
||||
* AbstractStrategy constructor.
|
||||
* @param Queue|null $queue
|
||||
* @param ConnectionManager|null $connectionManager
|
||||
*/
|
||||
public function __construct(Queue $queue = null, ConnectionManager $connectionManager = null)
|
||||
{
|
||||
$this->queue = $queue ?? GeneralUtility::makeInstance(Queue::class);
|
||||
$this->connectionManager = $connectionManager ?? GeneralUtility::makeInstance(ConnectionManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call's the removal of the strategy and afterwards the garbagecollector post processing hook.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return mixed
|
||||
*/
|
||||
public function removeGarbageOf($table, $uid)
|
||||
{
|
||||
$this->removeGarbageOfByStrategy($table, $uid);
|
||||
$this->callPostProcessGarbageCollectorHook($table, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* A implementation of the GarbageCollection strategy is responsible to remove the garbage from
|
||||
* the indexqueue and from the solr server.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function removeGarbageOfByStrategy($table, $uid);
|
||||
|
||||
/**
|
||||
* Deletes a document from solr and from the index queue.
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
*/
|
||||
protected function deleteInSolrAndRemoveFromIndexQueue($table, $uid)
|
||||
{
|
||||
$this->deleteIndexDocuments($table, $uid);
|
||||
$this->queue->deleteItem($table, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a document from solr and updates the item in the index queue (e.g. on page content updates).
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
*/
|
||||
protected function deleteInSolrAndUpdateIndexQueue($table, $uid)
|
||||
{
|
||||
$this->deleteIndexDocuments($table, $uid);
|
||||
$this->queue->updateItem($table, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes index documents for a given record identification.
|
||||
*
|
||||
* @param string $table The record's table name.
|
||||
* @param int $uid The record's uid.
|
||||
*/
|
||||
protected function deleteIndexDocuments($table, $uid, $language = 0)
|
||||
{
|
||||
// record can be indexed for multiple sites
|
||||
$indexQueueItems = $this->queue->getItems($table, $uid);
|
||||
foreach ($indexQueueItems as $indexQueueItem) {
|
||||
$site = $indexQueueItem->getSite();
|
||||
$enableCommitsSetting = $site->getSolrConfiguration()->getEnableCommits();
|
||||
$siteHash = $site->getSiteHash();
|
||||
// a site can have multiple connections (cores / languages)
|
||||
$solrConnections = $this->connectionManager->getConnectionsBySite($site);
|
||||
if ($language > 0) {
|
||||
$solrConnections = [$language => $solrConnections[$language]];
|
||||
}
|
||||
$this->deleteRecordInAllSolrConnections($table, $uid, $solrConnections, $siteHash, $enableCommitsSetting);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the record in all solr connections from that site.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @param SolrConnection[] $solrConnections
|
||||
* @param string $siteHash
|
||||
* @param boolean $enableCommitsSetting
|
||||
*/
|
||||
protected function deleteRecordInAllSolrConnections($table, $uid, $solrConnections, $siteHash, $enableCommitsSetting)
|
||||
{
|
||||
foreach ($solrConnections as $solr) {
|
||||
$solr->getWriteService()->deleteByQuery('type:' . $table . ' AND uid:' . (int)$uid . ' AND siteHash:' . $siteHash);
|
||||
if ($enableCommitsSetting) {
|
||||
$solr->getWriteService()->commit(false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the registered post processing hooks after the garbageCollection.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
*/
|
||||
protected function callPostProcessGarbageCollectorHook($table, $uid)
|
||||
{
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessGarbageCollector'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessGarbageCollector'] as $classReference) {
|
||||
$garbageCollectorPostProcessor = GeneralUtility::makeInstance($classReference);
|
||||
|
||||
if ($garbageCollectorPostProcessor instanceof GarbageCollectorPostProcessor) {
|
||||
$garbageCollectorPostProcessor->postProcessGarbageCollector($table, $uid);
|
||||
} else {
|
||||
$message = get_class($garbageCollectorPostProcessor) . ' must implement interface ' .
|
||||
GarbageCollectorPostProcessor::class;
|
||||
throw new \UnexpectedValueException($message, 1345807460);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
Classes/Domain/Index/Queue/GarbageRemover/PageStrategy.php
Normal file
80
Classes/Domain/Index/Queue/GarbageRemover/PageStrategy.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\GarbageRemover;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 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\Backend\Utility\BackendUtility;
|
||||
|
||||
|
||||
/**
|
||||
* Class PageStrategy
|
||||
*/
|
||||
class PageStrategy extends AbstractStrategy {
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return mixed
|
||||
*/
|
||||
protected function removeGarbageOfByStrategy($table, $uid)
|
||||
{
|
||||
if ($table === 'tt_content') {
|
||||
$this->collectPageGarbageByContentChange($uid);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($table === 'pages') {
|
||||
$this->collectPageGarbageByPageChange($uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the relevant page id for an content element update. Deletes the page from solr and requeues the
|
||||
* page for a reindex.
|
||||
*
|
||||
* @param int $ttContentUid
|
||||
*/
|
||||
protected function collectPageGarbageByContentChange($ttContentUid)
|
||||
{
|
||||
$contentElement = BackendUtility::getRecord('tt_content', $ttContentUid, 'uid, pid', '', false);
|
||||
$this->deleteInSolrAndUpdateIndexQueue('pages', $contentElement['pid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a page was changed it is removed from the index and index queue.
|
||||
*
|
||||
* @param int $uid
|
||||
*/
|
||||
protected function collectPageGarbageByPageChange($uid)
|
||||
{
|
||||
$pageOverlay = BackendUtility::getRecord('pages', $uid, 'l10n_parent, sys_language_uid', '', false);
|
||||
if (!empty($pageOverlay['l10n_parent']) && intval($pageOverlay['l10n_parent']) !== 0) {
|
||||
$this->deleteIndexDocuments('pages', (int)$pageOverlay['l10n_parent'], (int)$pageOverlay['sys_language_uid']);
|
||||
} else {
|
||||
$this->deleteInSolrAndRemoveFromIndexQueue('pages', $uid);
|
||||
}
|
||||
}
|
||||
}
|
43
Classes/Domain/Index/Queue/GarbageRemover/RecordStrategy.php
Normal file
43
Classes/Domain/Index/Queue/GarbageRemover/RecordStrategy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\GarbageRemover;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 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!
|
||||
***************************************************************/
|
||||
/**
|
||||
* Class RecordStrategy
|
||||
*/
|
||||
class RecordStrategy extends AbstractStrategy {
|
||||
|
||||
/**
|
||||
* Removes the garbage of a record.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return mixed
|
||||
*/
|
||||
protected function removeGarbageOfByStrategy($table, $uid)
|
||||
{
|
||||
$this->deleteInSolrAndRemoveFromIndexQueue($table, $uid);
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\GarbageRemover;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 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!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* Class StrategyFactory
|
||||
*/
|
||||
class StrategyFactory {
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @return PageStrategy|RecordStrategy
|
||||
*/
|
||||
public static function getByTable($table)
|
||||
{
|
||||
$isPageRelated = in_array($table, ['tt_content','pages']);
|
||||
return $isPageRelated ? new PageStrategy() : new RecordStrategy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\Records\AbstractRepository;
|
||||
|
||||
/**
|
||||
* Class IndexQueueIndexingPropertyRepository
|
||||
* Handles all CRUD operations to tx_meilisearch_indexqueue_indexing_property table
|
||||
*/
|
||||
class IndexQueueIndexingPropertyRepository extends AbstractRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tx_meilisearch_indexqueue_indexing_property';
|
||||
|
||||
/**
|
||||
* Removes existing indexing properties.
|
||||
*
|
||||
* @param int $rootPid
|
||||
* @param int $indexQueueUid
|
||||
* @return int
|
||||
*/
|
||||
public function removeByRootPidAndIndexQueueUid(int $rootPid, int $indexQueueUid) : int
|
||||
{
|
||||
$queryBuider = $this->getQueryBuilder();
|
||||
return $queryBuider
|
||||
->delete($this->table)
|
||||
->where(
|
||||
$queryBuider->expr()->eq('root', $queryBuider->createNamedParameter($rootPid, \PDO::PARAM_INT)),
|
||||
$queryBuider->expr()->eq('item_id', $queryBuider->createNamedParameter($indexQueueUid, \PDO::PARAM_INT))
|
||||
)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a list of given properties
|
||||
*
|
||||
* @param array $properties assoc array with column names as key
|
||||
* @return int
|
||||
*/
|
||||
public function bulkInsert(array $properties) : int
|
||||
{
|
||||
return $this->getQueryBuilder()->getConnection()->bulkInsert($this->table, $properties, ['root', 'item_id', 'property_key', 'property_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of properties related to index queue item
|
||||
*
|
||||
* @param int $indexQueueUid
|
||||
* @return array list of records for searched index queue item
|
||||
*/
|
||||
public function findAllByIndexQueueUid(int $indexQueueUid) : array
|
||||
{
|
||||
$queryBuider = $this->getQueryBuilder();
|
||||
return $queryBuider
|
||||
->select('property_key', 'property_value')
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuider->expr()->eq('item_id', $queryBuider->createNamedParameter($indexQueueUid, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()->fetchAll();
|
||||
}
|
||||
}
|
162
Classes/Domain/Index/Queue/QueueInitializationService.php
Normal file
162
Classes/Domain/Index/Queue/QueueInitializationService.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\IndexQueue\InitializationPostProcessor;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Queue;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The queue initialization service is responsible to run the initialization of the index queue for a combination of sites
|
||||
* and index queue configurations.
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
* @author Ingo Renner <ingo.renner@dkd.de>
|
||||
*/
|
||||
class QueueInitializationService {
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* QueueInitializationService constructor.
|
||||
*/
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate and rebuild the tx_meilisearch_indexqueue_item table. This is the most
|
||||
* complete way to force reindexing, or to build the Index Queue for the
|
||||
* first time. The Index Queue initialization is site-specific.
|
||||
*
|
||||
* @param Site $site The site to initialize
|
||||
* @param string $indexingConfigurationName Name of a specific indexing configuration, when * is passed any is used
|
||||
* @return array An array of booleans, each representing whether the
|
||||
* initialization for an indexing configuration was successful
|
||||
*/
|
||||
public function initializeBySiteAndIndexConfiguration(Site $site, $indexingConfigurationName = '*'): array
|
||||
{
|
||||
return $this->initializeBySiteAndIndexConfigurations($site, [$indexingConfigurationName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates and rebuilds the tx_meilisearch_indexqueue_item table for a set of sites and a set of index configurations.
|
||||
*
|
||||
* @param array $sites The array of sites to initialize
|
||||
* @param array $indexingConfigurationNames the array of index configurations to initialize.
|
||||
* @return array
|
||||
*/
|
||||
public function initializeBySitesAndConfigurations(array $sites, array $indexingConfigurationNames = ['*']): array
|
||||
{
|
||||
$initializationStatesBySiteId = [];
|
||||
foreach($sites as $site) {
|
||||
/** @var Site $site */
|
||||
$initializationResult = $this->initializeBySiteAndIndexConfigurations($site, $indexingConfigurationNames);
|
||||
$initializationStatesBySiteId[$site->getRootPageId()] = $initializationResult;
|
||||
}
|
||||
|
||||
return $initializationStatesBySiteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a set index configurations for a given site.
|
||||
*
|
||||
* @param Site $site
|
||||
* @param array $indexingConfigurationNames if one of the names is a * (wildcard) all configurations are used,
|
||||
* @return array
|
||||
*/
|
||||
public function initializeBySiteAndIndexConfigurations(Site $site, array $indexingConfigurationNames): array
|
||||
{
|
||||
$initializationStatus = [];
|
||||
|
||||
$hasWildcardConfiguration = in_array('*', $indexingConfigurationNames);
|
||||
$indexingConfigurationNames = $hasWildcardConfiguration ? $site->getSolrConfiguration()->getEnabledIndexQueueConfigurationNames() : $indexingConfigurationNames;
|
||||
foreach ($indexingConfigurationNames as $indexingConfigurationName) {
|
||||
$initializationStatus[$indexingConfigurationName] = $this->applyInitialization($site, (string)$indexingConfigurationName);
|
||||
}
|
||||
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'])) {
|
||||
return $initializationStatus;
|
||||
}
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessIndexQueueInitialization'] as $classReference) {
|
||||
$indexQueueInitializationPostProcessor = GeneralUtility::makeInstance($classReference);
|
||||
if ($indexQueueInitializationPostProcessor instanceof InitializationPostProcessor) {
|
||||
$indexQueueInitializationPostProcessor->postProcessIndexQueueInitialization($site, $indexingConfigurationNames, $initializationStatus);
|
||||
} else {
|
||||
throw new \UnexpectedValueException(get_class($indexQueueInitializationPostProcessor) . ' must implement interface ' . InitializationPostProcessor::class, 1345815561);
|
||||
}
|
||||
}
|
||||
|
||||
return $initializationStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Index Queue for a specific indexing configuration.
|
||||
*
|
||||
* @param Site $site The site to initialize
|
||||
* @param string $indexingConfigurationName name of a specific
|
||||
* indexing configuration
|
||||
* @return bool TRUE if the initialization was successful, FALSE otherwise
|
||||
*/
|
||||
protected function applyInitialization(Site $site, $indexingConfigurationName): bool
|
||||
{
|
||||
// clear queue
|
||||
$this->queue->deleteItemsBySite($site, $indexingConfigurationName);
|
||||
|
||||
$solrConfiguration = $site->getSolrConfiguration();
|
||||
$tableToIndex = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
|
||||
$initializerClass = $solrConfiguration->getIndexQueueInitializerClassByConfigurationName($indexingConfigurationName);
|
||||
$indexConfiguration = $solrConfiguration->getIndexQueueConfigurationByName($indexingConfigurationName);
|
||||
|
||||
return $this->executeInitializer($site, $indexingConfigurationName, $initializerClass, $tableToIndex, $indexConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Site $site
|
||||
* @param string $indexingConfigurationName
|
||||
* @param string $initializerClass
|
||||
* @param string $tableToIndex
|
||||
* @param array $indexConfiguration
|
||||
* @return bool
|
||||
*/
|
||||
protected function executeInitializer(Site $site, $indexingConfigurationName, $initializerClass, $tableToIndex, $indexConfiguration): bool
|
||||
{
|
||||
$initializer = GeneralUtility::makeInstance($initializerClass);
|
||||
/** @var $initializer \WapplerSystems\Meilisearch\IndexQueue\Initializer\AbstractInitializer */
|
||||
$initializer->setSite($site);
|
||||
$initializer->setType($tableToIndex);
|
||||
$initializer->setIndexingConfigurationName($indexingConfigurationName);
|
||||
$initializer->setIndexingConfiguration($indexConfiguration);
|
||||
|
||||
return $initializer->initialize();
|
||||
}
|
||||
|
||||
}
|
902
Classes/Domain/Index/Queue/QueueItemRepository.php
Normal file
902
Classes/Domain/Index/Queue/QueueItemRepository.php
Normal file
@@ -0,0 +1,902 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\IndexQueue\Item;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use WapplerSystems\Meilisearch\System\Records\AbstractRepository;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class QueueItemRepository
|
||||
* Handles all CRUD operations to tx_meilisearch_indexqueue_item table
|
||||
*
|
||||
*/
|
||||
class QueueItemRepository extends AbstractRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tx_meilisearch_indexqueue_item';
|
||||
|
||||
/**
|
||||
* @var SolrLogManager
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* QueueItemRepository constructor.
|
||||
*
|
||||
* @param SolrLogManager|null $logManager
|
||||
*/
|
||||
public function __construct(SolrLogManager $logManager = null)
|
||||
{
|
||||
$this->logger = $logManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the last indexed row
|
||||
*
|
||||
* @param int $rootPageId The root page uid for which to get the last indexed row
|
||||
* @return array
|
||||
*/
|
||||
public function findLastIndexedRow(int $rootPageId) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$row = $queryBuilder
|
||||
->select('uid', 'indexed')
|
||||
->from($this->table)
|
||||
->where($queryBuilder->expr()->eq('root', $rootPageId))
|
||||
->andWhere($queryBuilder->expr()->neq('indexed', 0))
|
||||
->orderBy('indexed', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->execute()->fetchAll();
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds indexing errors for the current site
|
||||
*
|
||||
* @param Site $site
|
||||
* @return array Error items for the current site's Index Queue
|
||||
*/
|
||||
public function findErrorsBySite(Site $site) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$errors = $queryBuilder
|
||||
->select('uid', 'item_type', 'item_uid', 'errors')
|
||||
->from($this->table)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter('')),
|
||||
$queryBuilder->expr()->eq('root', $site->getRootPageId())
|
||||
)
|
||||
->execute()->fetchAll();
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all the errors for all index queue items.
|
||||
*
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function flushAllErrors() : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)->execute();
|
||||
return $affectedRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the errors for a single site.
|
||||
*
|
||||
* @param Site $site
|
||||
* @return int
|
||||
*/
|
||||
public function flushErrorsBySite(Site $site) : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq('root', (int)$site->getRootPageId())
|
||||
)
|
||||
->execute();
|
||||
return $affectedRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the error for a single item.
|
||||
*
|
||||
* @param Item $item
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function flushErrorByItem(Item $item) : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$affectedRows = $this->getPreparedFlushErrorQuery($queryBuilder)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq('uid', $item->getIndexQueueUid())
|
||||
)
|
||||
->execute();
|
||||
return $affectedRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the QueryBuilder with a query the resets the error field for items that have an error.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
private function getPreparedFlushErrorQuery(QueryBuilder $queryBuilder)
|
||||
{
|
||||
return $queryBuilder
|
||||
->update($this->table)
|
||||
->set('errors', '')
|
||||
->where(
|
||||
$queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter(''))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing queue entry by $itemType $itemUid and $rootPageId.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid, usually an integer uid, could be a
|
||||
* different value for non-database-record types.
|
||||
* @param string $indexingConfiguration The name of the related indexConfiguration
|
||||
* @param int $rootPageId The uid of the rootPage
|
||||
* @param int $changedTime The forced change time that should be used for updating
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function updateExistingItemByItemTypeAndItemUidAndRootPageId(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration = '') : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder
|
||||
->update($this->table)
|
||||
->set('changed', $changedTime)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
|
||||
$queryBuilder->expr()->eq('item_uid', $itemUid),
|
||||
$queryBuilder->expr()->eq('root', $rootPageId)
|
||||
);
|
||||
|
||||
if (!empty($indexingConfiguration)) {
|
||||
$queryBuilder->set('indexing_configuration', $indexingConfiguration);
|
||||
}
|
||||
|
||||
return $queryBuilder->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the index queue.
|
||||
*
|
||||
* Not meant for public use.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid, usually an integer uid, could be a different value for non-database-record types.
|
||||
* @param int $rootPageId
|
||||
* @param int $changedTime
|
||||
* @param string $indexingConfiguration The item's indexing configuration to use. Optional, overwrites existing / determined configuration.
|
||||
* @return int the number of inserted rows, which is typically 1
|
||||
*/
|
||||
public function add(string $itemType, int $itemUid, int $rootPageId, int $changedTime, string $indexingConfiguration) : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return $queryBuilder
|
||||
->insert($this->table)
|
||||
->values([
|
||||
'root' => $rootPageId,
|
||||
'item_type' => $itemType,
|
||||
'item_uid' => $itemUid,
|
||||
'changed' => $changedTime,
|
||||
'errors' => '',
|
||||
'indexing_configuration' => $indexingConfiguration
|
||||
])
|
||||
->execute();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
|
||||
*
|
||||
* @param array $sites
|
||||
* @param array $indexQueueConfigurationNames
|
||||
* @param array $itemTypes
|
||||
* @param array $itemUids
|
||||
* @param array $uids
|
||||
* @return int
|
||||
*/
|
||||
public function countItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = []): int
|
||||
{
|
||||
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
||||
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
||||
$itemTypeList = implode(",", $itemTypes);
|
||||
$itemUids = array_map("intval", $itemUids);
|
||||
$uids = array_map("intval", $uids);
|
||||
|
||||
$queryBuilderForCountingItems = $this->getQueryBuilder();
|
||||
$queryBuilderForCountingItems->count('uid')->from($this->table);
|
||||
$queryBuilderForCountingItems = $this->addItemWhereClauses($queryBuilderForCountingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
||||
|
||||
return (int)$queryBuilderForCountingItems->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent changed time of a page's content elements
|
||||
*
|
||||
* @param int $pageUid
|
||||
* @return int|null Timestamp of the most recent content element change or null if nothing is found.
|
||||
*/
|
||||
public function getPageItemChangedTimeByPageUid(int $pageUid)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$pageContentLastChangedTime = $queryBuilder
|
||||
->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('pid', $pageUid)
|
||||
)
|
||||
->execute()->fetch();
|
||||
|
||||
return $pageContentLastChangedTime['changed_time'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent changed time for an item taking into account
|
||||
* localized records.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid
|
||||
* @return int Timestamp of the most recent content element change
|
||||
*/
|
||||
public function getLocalizableItemChangedTime(string $itemType, int $itemUid) : int
|
||||
{
|
||||
$localizedChangedTime = 0;
|
||||
|
||||
if (isset($GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'])) {
|
||||
// table is localizable
|
||||
$translationOriginalPointerField = $GLOBALS['TCA'][$itemType]['ctrl']['transOrigPointerField'];
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($itemType);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$localizedChangedTime = $queryBuilder
|
||||
->add('select', $queryBuilder->expr()->max('tstamp', 'changed_time'))
|
||||
->from($itemType)
|
||||
->orWhere(
|
||||
$queryBuilder->expr()->eq('uid', $itemUid),
|
||||
$queryBuilder->expr()->eq($translationOriginalPointerField, $itemUid)
|
||||
)->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
return (int)$localizedChangedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepared QueryBuilder for contains* methods in this repository
|
||||
*
|
||||
* @param string $itemType
|
||||
* @param int $itemUid
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
protected function getQueryBuilderForContainsMethods(string $itemType, int $itemUid) : QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return $queryBuilder->count('uid')->from($this->table)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter($itemType)),
|
||||
$queryBuilder->expr()->eq('item_uid', $itemUid)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Index Queue contains a specific item.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid
|
||||
* @return bool TRUE if the item is found in the queue, FALSE otherwise
|
||||
*/
|
||||
public function containsItem(string $itemType, int $itemUid) : bool
|
||||
{
|
||||
return (bool)$this->getQueryBuilderForContainsMethods($itemType, $itemUid)->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Index Queue contains a specific item.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid
|
||||
* @param integer $rootPageId
|
||||
* @return bool TRUE if the item is found in the queue, FALSE otherwise
|
||||
*/
|
||||
public function containsItemWithRootPageId(string $itemType, int $itemUid, int $rootPageId) : bool
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
|
||||
return (bool)$queryBuilder
|
||||
->andWhere($queryBuilder->expr()->eq('root', $rootPageId))
|
||||
->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Index Queue contains a specific item that has been
|
||||
* marked as indexed.
|
||||
*
|
||||
* @param string $itemType The item's type, usually a table name.
|
||||
* @param int $itemUid The item's uid
|
||||
* @return bool TRUE if the item is found in the queue and marked as
|
||||
* indexed, FALSE otherwise
|
||||
*/
|
||||
public function containsIndexedItem(string $itemType, int $itemUid) : bool
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilderForContainsMethods($itemType, $itemUid);
|
||||
return (bool)$queryBuilder
|
||||
->andWhere($queryBuilder->expr()->gt('indexed', 0))
|
||||
->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the Index Queue.
|
||||
*
|
||||
* @param string $itemType The type of the item to remove, usually a table name.
|
||||
* @param int $itemUid The uid of the item to remove
|
||||
*/
|
||||
public function deleteItem(string $itemType, int $itemUid = null)
|
||||
{
|
||||
$itemUids = empty($itemUid) ? [] : [$itemUid];
|
||||
$this->deleteItems([], [], [$itemType], $itemUids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items of a certain type from the Index Queue.
|
||||
*
|
||||
* @param string $itemType The type of items to remove, usually a table name.
|
||||
*/
|
||||
public function deleteItemsByType(string $itemType)
|
||||
{
|
||||
$this->deleteItem($itemType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items of a certain site from the Index Queue. Accepts an
|
||||
* optional parameter to limit the deleted items by indexing configuration.
|
||||
*
|
||||
* @param Site $site The site to remove items for.
|
||||
* @param string $indexingConfigurationName Name of a specific indexing configuration
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteItemsBySite(Site $site, string $indexingConfigurationName = '')
|
||||
{
|
||||
$indexingConfigurationNames = empty($indexingConfigurationName) ? [] : [$indexingConfigurationName];
|
||||
$this->deleteItems([$site], $indexingConfigurationNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes items in the index queue filtered by the passed arguments.
|
||||
*
|
||||
* @param array $sites
|
||||
* @param array $indexQueueConfigurationNames
|
||||
* @param array $itemTypes
|
||||
* @param array $itemUids
|
||||
* @param array $uids
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deleteItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [])
|
||||
{
|
||||
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
||||
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
||||
$itemTypeList = implode(",", $itemTypes);
|
||||
$itemUids = array_map("intval", $itemUids);
|
||||
$uids = array_map("intval", $uids);
|
||||
|
||||
$queryBuilderForDeletingItems = $this->getQueryBuilder();
|
||||
$queryBuilderForDeletingItems->delete($this->table);
|
||||
$queryBuilderForDeletingItems = $this->addItemWhereClauses($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
||||
|
||||
$queryBuilderForDeletingProperties = $this->buildQueryForPropertyDeletion($queryBuilderForDeletingItems, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
||||
|
||||
$queryBuilderForDeletingItems->getConnection()->beginTransaction();
|
||||
try {
|
||||
$queryBuilderForDeletingItems->execute();
|
||||
$queryBuilderForDeletingProperties->execute();
|
||||
|
||||
$queryBuilderForDeletingItems->getConnection()->commit();
|
||||
} catch (\Exception $e) {
|
||||
$queryBuilderForDeletingItems->getConnection()->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the query builder to delete items in the index queue filtered by the passed arguments.
|
||||
*
|
||||
* @param array $rootPageIds filter on a set of rootPageUids.
|
||||
* @param string $indexQueueConfigurationList
|
||||
* @param string $itemTypeList
|
||||
* @param array $itemUids filter on a set of item uids
|
||||
* @param array $uids filter on a set of queue item uids
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
private function addItemWhereClauses(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
|
||||
{
|
||||
|
||||
if (!empty($rootPageIds)) {
|
||||
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('root', $rootPageIds));
|
||||
};
|
||||
|
||||
if (!empty($indexQueueConfigurationList)) {
|
||||
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('indexing_configuration', $queryBuilderForDeletingItems->createNamedParameter($indexQueueConfigurationList)));
|
||||
}
|
||||
|
||||
if (!empty($itemTypeList)) {
|
||||
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_type', $queryBuilderForDeletingItems->createNamedParameter($itemTypeList)));
|
||||
}
|
||||
|
||||
if (!empty($itemUids)) {
|
||||
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('item_uid', $itemUids));
|
||||
}
|
||||
|
||||
if (!empty($uids)) {
|
||||
$queryBuilderForDeletingItems->andWhere($queryBuilderForDeletingItems->expr()->in('uid', $uids));
|
||||
}
|
||||
|
||||
return $queryBuilderForDeletingItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a query builder to delete the indexing properties of an item by the passed conditions.
|
||||
*
|
||||
* @param QueryBuilder $queryBuilderForDeletingItems
|
||||
* @param array $rootPageIds
|
||||
* @param string $indexQueueConfigurationList
|
||||
* @param string $itemTypeList
|
||||
* @param array $itemUids
|
||||
* @param array $uids
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
private function buildQueryForPropertyDeletion(QueryBuilder $queryBuilderForDeletingItems, array $rootPageIds, string $indexQueueConfigurationList, string $itemTypeList, array $itemUids, array $uids): QueryBuilder
|
||||
{
|
||||
$queryBuilderForSelectingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
|
||||
$queryBuilderForSelectingProperties->select('items.uid')->from('tx_meilisearch_indexqueue_indexing_property', 'properties')->innerJoin(
|
||||
'properties',
|
||||
$this->table,
|
||||
'items',
|
||||
(string)$queryBuilderForSelectingProperties->expr()->andX(
|
||||
$queryBuilderForSelectingProperties->expr()->eq('items.uid', $queryBuilderForSelectingProperties->quoteIdentifier('properties.item_id')),
|
||||
empty($rootPageIds) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.root', $rootPageIds),
|
||||
empty($indexQueueConfigurationList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.indexing_configuration', $queryBuilderForSelectingProperties->createNamedParameter($indexQueueConfigurationList)),
|
||||
empty($itemTypeList) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_type', $queryBuilderForSelectingProperties->createNamedParameter($itemTypeList)),
|
||||
empty($itemUids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.item_uid', $itemUids),
|
||||
empty($uids) ? '' : $queryBuilderForSelectingProperties->expr()->in('items.uid', $uids)
|
||||
)
|
||||
);
|
||||
$propertyEntriesToDelete = implode(',', array_column($queryBuilderForSelectingProperties->execute()->fetchAll(), 'uid'));
|
||||
|
||||
$queryBuilderForDeletingProperties = $queryBuilderForDeletingItems->getConnection()->createQueryBuilder();
|
||||
|
||||
// make sure executing the propety deletion query doesn't fail if there are no properties to delete
|
||||
if (empty($propertyEntriesToDelete)) {
|
||||
$propertyEntriesToDelete = '0';
|
||||
}
|
||||
|
||||
$queryBuilderForDeletingProperties->delete('tx_meilisearch_indexqueue_indexing_property')->where(
|
||||
$queryBuilderForDeletingProperties->expr()->in('item_id', $propertyEntriesToDelete)
|
||||
);
|
||||
|
||||
return $queryBuilderForDeletingProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the Index Queue.
|
||||
*
|
||||
* @return int The number of affected rows. For a truncate this is unreliable as theres no meaningful information.
|
||||
*/
|
||||
public function deleteAllItems()
|
||||
{
|
||||
return $this->getQueryBuilder()->getConnection()->truncate($this->table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single Index Queue item by its uid.
|
||||
*
|
||||
* @param int $uid Index Queue item uid
|
||||
* @return Item|null The request Index Queue item or NULL if no item with $itemId was found
|
||||
*/
|
||||
public function findItemByUid(int $uid)
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$indexQueueItemRecord = $queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->where($queryBuilder->expr()->eq('uid', $uid))
|
||||
->execute()->fetch();
|
||||
|
||||
if (!isset($indexQueueItemRecord['uid'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Item $item*/
|
||||
$item = GeneralUtility::makeInstance(Item::class, /** @scrutinizer ignore-type */ $indexQueueItemRecord);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Index Queue items by type and uid.
|
||||
*
|
||||
* @param string $itemType item type, usually the table name
|
||||
* @param int $itemUid item uid
|
||||
* @return Item[] An array of items matching $itemType and $itemUid
|
||||
*/
|
||||
public function findItemsByItemTypeAndItemUid(string $itemType, int $itemUid) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$compositeExpression = $queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->eq('item_type', $queryBuilder->getConnection()->quote($itemType, \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->eq('item_uid', $itemUid)
|
||||
);
|
||||
return $this->getItemsByCompositeExpression($compositeExpression, $queryBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of items by CompositeExpression.
|
||||
* D
|
||||
*
|
||||
* @param CompositeExpression|null $expression Optional expression to filter records.
|
||||
* @param QueryBuilder|null $queryBuilder QueryBuilder to use
|
||||
* @return array
|
||||
*/
|
||||
protected function getItemsByCompositeExpression(CompositeExpression $expression = null, QueryBuilder $queryBuilder = null) : array
|
||||
{
|
||||
if (!$queryBuilder instanceof QueryBuilder) {
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
}
|
||||
|
||||
$queryBuilder->select('*')->from($this->table);
|
||||
if (isset($expression)) {
|
||||
$queryBuilder->where($expression);
|
||||
}
|
||||
|
||||
$indexQueueItemRecords = $queryBuilder->execute()->fetchAll();
|
||||
return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all items in the queue.
|
||||
*
|
||||
* @return Item[] all Items from Queue without restrictions
|
||||
*/
|
||||
public function findAll() : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$allRecords = $queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->execute()->fetchAll();
|
||||
return $this->getIndexQueueItemObjectsFromRecords($allRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets $limit number of items to index for a particular $site.
|
||||
*
|
||||
* @param Site $site TYPO3 site
|
||||
* @param int $limit Number of items to get from the queue
|
||||
* @return Item[] Items to index to the given solr server
|
||||
*/
|
||||
public function findItemsToIndex(Site $site, int $limit = 50) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
// determine which items to index with this run
|
||||
$indexQueueItemRecords = $queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->andWhere(
|
||||
$queryBuilder->expr()->eq('root', $site->getRootPageId()),
|
||||
$queryBuilder->expr()->gt('changed', 'indexed'),
|
||||
$queryBuilder->expr()->lte('changed', time()),
|
||||
$queryBuilder->expr()->eq('errors', $queryBuilder->createNamedParameter(''))
|
||||
)
|
||||
->orderBy('indexing_priority', 'DESC')
|
||||
->addOrderBy('changed', 'DESC')
|
||||
->addOrderBy('uid', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->execute()->fetchAll();
|
||||
|
||||
return $this->getIndexQueueItemObjectsFromRecords($indexQueueItemRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the count of items that match certain filters. Each filter is passed as parts of the where claus combined with AND.
|
||||
*
|
||||
* @param array $sites
|
||||
* @param array $indexQueueConfigurationNames
|
||||
* @param array $itemTypes
|
||||
* @param array $itemUids
|
||||
* @param array $uids
|
||||
* @param int $start
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
public function findItems(array $sites = [], array $indexQueueConfigurationNames = [], array $itemTypes = [], array $itemUids = [], array $uids = [], $start = 0, $limit = 50): array
|
||||
{
|
||||
$rootPageIds = Site::getRootPageIdsFromSites($sites);
|
||||
$indexQueueConfigurationList = implode(",", $indexQueueConfigurationNames);
|
||||
$itemTypeList = implode(",", $itemTypes);
|
||||
$itemUids = array_map("intval", $itemUids);
|
||||
$uids = array_map("intval", $uids);
|
||||
$itemQueryBuilder = $this->getQueryBuilder()->select('*')->from($this->table);
|
||||
$itemQueryBuilder = $this->addItemWhereClauses($itemQueryBuilder, $rootPageIds, $indexQueueConfigurationList, $itemTypeList, $itemUids, $uids);
|
||||
$itemRecords = $itemQueryBuilder->setFirstResult($start)->setMaxResults($limit)->execute()->fetchAll();
|
||||
return $this->getIndexQueueItemObjectsFromRecords($itemRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of WapplerSystems\Meilisearch\IndexQueue\Item objects from an array of
|
||||
* index queue records.
|
||||
*
|
||||
* @param array $indexQueueItemRecords Array of plain index queue records
|
||||
* @return array Array of WapplerSystems\Meilisearch\IndexQueue\Item objects
|
||||
*/
|
||||
protected function getIndexQueueItemObjectsFromRecords(array $indexQueueItemRecords) : array
|
||||
{
|
||||
$tableRecords = $this->getAllQueueItemRecordsByUidsGroupedByTable($indexQueueItemRecords);
|
||||
return $this->getQueueItemObjectsByRecords($indexQueueItemRecords, $tableRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the records for suitable item type.
|
||||
*
|
||||
* @param array $indexQueueItemRecords
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllQueueItemRecordsByUidsGroupedByTable(array $indexQueueItemRecords) : array
|
||||
{
|
||||
$tableUids = [];
|
||||
$tableRecords = [];
|
||||
// grouping records by table
|
||||
foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
|
||||
$tableUids[$indexQueueItemRecord['item_type']][] = $indexQueueItemRecord['item_uid'];
|
||||
}
|
||||
|
||||
// fetching records by table, saves us a lot of single queries
|
||||
foreach ($tableUids as $table => $uids) {
|
||||
$uidList = implode(',', $uids);
|
||||
|
||||
$queryBuilderForRecordTable = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
|
||||
$queryBuilderForRecordTable->getRestrictions()->removeAll();
|
||||
$resultsFromRecordTable = $queryBuilderForRecordTable
|
||||
->select('*')
|
||||
->from($table)
|
||||
->where($queryBuilderForRecordTable->expr()->in('uid', $uidList))
|
||||
->execute();
|
||||
$records = [];
|
||||
while ($record = $resultsFromRecordTable->fetch()) {
|
||||
$records[$record['uid']] = $record;
|
||||
}
|
||||
|
||||
$tableRecords[$table] = $records;
|
||||
$this->hookPostProcessFetchRecordsForIndexQueueItem($table, $uids, $tableRecords);
|
||||
}
|
||||
|
||||
return $tableRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls defined in postProcessFetchRecordsForIndexQueueItem hook method.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $uids
|
||||
* @param array $tableRecords
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function hookPostProcessFetchRecordsForIndexQueueItem(string $table, array $uids, array &$tableRecords)
|
||||
{
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'])) {
|
||||
return;
|
||||
}
|
||||
$params = ['table' => $table, 'uids' => $uids, 'tableRecords' => &$tableRecords];
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['solr']['postProcessFetchRecordsForIndexQueueItem'] as $reference) {
|
||||
GeneralUtility::callUserFunction($reference, $params, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a list of Item objects from database records.
|
||||
*
|
||||
* @param array $indexQueueItemRecords records from database
|
||||
* @param array $tableRecords
|
||||
* @return array
|
||||
*/
|
||||
protected function getQueueItemObjectsByRecords(array $indexQueueItemRecords, array $tableRecords) : array
|
||||
{
|
||||
$indexQueueItems = [];
|
||||
foreach ($indexQueueItemRecords as $indexQueueItemRecord) {
|
||||
if (isset($tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']])) {
|
||||
$indexQueueItems[] = GeneralUtility::makeInstance(
|
||||
Item::class,
|
||||
/** @scrutinizer ignore-type */ $indexQueueItemRecord,
|
||||
/** @scrutinizer ignore-type */ $tableRecords[$indexQueueItemRecord['item_type']][$indexQueueItemRecord['item_uid']]
|
||||
);
|
||||
} else {
|
||||
$this->logger->log(
|
||||
SolrLogManager::ERROR,
|
||||
'Record missing for Index Queue item. Item removed.',
|
||||
[
|
||||
$indexQueueItemRecord
|
||||
]
|
||||
);
|
||||
$this->deleteItem($indexQueueItemRecord['item_type'],
|
||||
$indexQueueItemRecord['item_uid']);
|
||||
}
|
||||
}
|
||||
|
||||
return $indexQueueItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an item as failed and causes the indexer to skip the item in the
|
||||
* next run.
|
||||
*
|
||||
* @param int|Item $item Either the item's Index Queue uid or the complete item
|
||||
* @param string $errorMessage Error message
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function markItemAsFailed($item, string $errorMessage = ''): int
|
||||
{
|
||||
$itemUid = ($item instanceof Item) ? $item->getIndexQueueUid() : (int)$item;
|
||||
$errorMessage = empty($errorMessage) ? '1' : $errorMessage;
|
||||
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return (int)$queryBuilder
|
||||
->update($this->table)
|
||||
->set('errors', $errorMessage)
|
||||
->where($queryBuilder->expr()->eq('uid', $itemUid))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp of when an item last has been indexed.
|
||||
*
|
||||
* @param Item $item
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function updateIndexTimeByItem(Item $item) : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return (int)$queryBuilder
|
||||
->update($this->table)
|
||||
->set('indexed', time())
|
||||
->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the change timestamp of an item.
|
||||
*
|
||||
* @param Item $item
|
||||
* @param int $changedTime
|
||||
* @return int affected rows
|
||||
*/
|
||||
public function updateChangedTimeByItem(Item $item, int $changedTime) : int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return (int)$queryBuilder
|
||||
->update($this->table)
|
||||
->set('changed', $changedTime)
|
||||
->where($queryBuilder->expr()->eq('uid', $item->getIndexQueueUid()))
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Queue by given sql
|
||||
*
|
||||
* Note: Do not use platform specific functions!
|
||||
*
|
||||
* @param string $sqlStatement Native SQL statement
|
||||
* @return int The number of affected rows.
|
||||
* @internal
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function initializeByNativeSQLStatement(string $sqlStatement) : int
|
||||
{
|
||||
return $this->getQueryBuilder()->getConnection()->exec($sqlStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of pageIds from mountPoints that allready have a queue entry.
|
||||
*
|
||||
* @param string $identifier identifier of the mount point
|
||||
* @return array pageIds from mountPoints that allready have a queue entry
|
||||
*/
|
||||
public function findPageIdsOfExistingMountPagesByMountIdentifier(string $identifier) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$resultSet = $queryBuilder
|
||||
->select('item_uid')
|
||||
->add('select', $queryBuilder->expr()->count('*', 'queueItemCount'), true)
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
|
||||
$queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
|
||||
)
|
||||
->groupBy('item_uid')
|
||||
->execute();
|
||||
|
||||
$mountedPagesIdsWithQueueItems = [];
|
||||
while ($record = $resultSet->fetch()) {
|
||||
if ($record['queueItemCount'] > 0) {
|
||||
$mountedPagesIdsWithQueueItems[] = $record['item_uid'];
|
||||
}
|
||||
}
|
||||
|
||||
return $mountedPagesIdsWithQueueItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of items for mount destinations mathed by root page ID, Mount Identifier and a list of mounted page IDs.
|
||||
*
|
||||
* @param int $rootPid
|
||||
* @param string $identifier identifier of the mount point
|
||||
* @param array $mountedPids An array of mounted page IDs
|
||||
* @return array
|
||||
*/
|
||||
public function findAllIndexQueueItemsByRootPidAndMountIdentifierAndMountedPids(int $rootPid, string $identifier, array $mountedPids) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return $queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('root', $queryBuilder->createNamedParameter($rootPid, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('item_type', $queryBuilder->createNamedParameter('pages')),
|
||||
$queryBuilder->expr()->in('item_uid', $mountedPids),
|
||||
$queryBuilder->expr()->eq('has_indexing_properties', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('pages_mountidentifier', $queryBuilder->createNamedParameter($identifier))
|
||||
)
|
||||
->execute()->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates has_indexing_properties field for given Item
|
||||
*
|
||||
* @param int $itemUid
|
||||
* @param bool $hasIndexingPropertiesFlag
|
||||
* @return int number of affected rows, 1 on success
|
||||
*/
|
||||
public function updateHasIndexingPropertiesFlagByItemUid(int $itemUid, bool $hasIndexingPropertiesFlag): int
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
|
||||
return $queryBuilder
|
||||
->update($this->table)
|
||||
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($itemUid, \PDO::PARAM_INT)))
|
||||
->set('has_indexing_properties', $queryBuilder->createNamedParameter($hasIndexingPropertiesFlag, \PDO::PARAM_INT), false)
|
||||
->execute();
|
||||
}
|
||||
}
|
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\RecordMonitor\Helper;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 - Thomas Hohn <tho@systime.dk>
|
||||
* 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\Cache\TwoLevelCache;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Extracted logic from the AbstractDataHandlerListener in order to
|
||||
* handle ConfigurationAwareRecords
|
||||
*
|
||||
* @author Thomas Hohn Hund <tho@systime.dk>
|
||||
*/
|
||||
class ConfigurationAwareRecordService
|
||||
{
|
||||
/**
|
||||
* Retrieves the name of the Index Queue Configuration for a record.
|
||||
*
|
||||
* @param string $recordTable Table to read from
|
||||
* @param int $recordUid Id of the record
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return string|null Name of indexing configuration
|
||||
*/
|
||||
public function getIndexingConfigurationName($recordTable, $recordUid, TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$name = null;
|
||||
$indexingConfigurations = $solrConfiguration->getEnabledIndexQueueConfigurationNames();
|
||||
foreach ($indexingConfigurations as $indexingConfigurationName) {
|
||||
if (!$solrConfiguration->getIndexQueueConfigurationIsEnabled($indexingConfigurationName)) {
|
||||
// ignore disabled indexing configurations
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = $this->getRecordIfIndexConfigurationIsValid($recordTable, $recordUid,
|
||||
$indexingConfigurationName, $solrConfiguration);
|
||||
if (!empty($record)) {
|
||||
$name = $indexingConfigurationName;
|
||||
// FIXME currently returns after the first configuration match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a record, taking into account the additionalWhereClauses of the
|
||||
* Indexing Queue configurations.
|
||||
*
|
||||
* @param string $recordTable Table to read from
|
||||
* @param int $recordUid Id of the record
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return array Record if found, otherwise empty array
|
||||
*/
|
||||
public function getRecord($recordTable, $recordUid, TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$record = [];
|
||||
$indexingConfigurations = $solrConfiguration->getEnabledIndexQueueConfigurationNames();
|
||||
foreach ($indexingConfigurations as $indexingConfigurationName) {
|
||||
$record = $this->getRecordIfIndexConfigurationIsValid($recordTable, $recordUid,
|
||||
$indexingConfigurationName, $solrConfiguration);
|
||||
if (!empty($record)) {
|
||||
// if we found a record which matches the conditions, we can continue
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method return the record array if the table is valid for this indexingConfiguration.
|
||||
* Otherwise an empty array will be returned.
|
||||
*
|
||||
* @param string $recordTable
|
||||
* @param integer $recordUid
|
||||
* @param string $indexingConfigurationName
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return array
|
||||
*/
|
||||
protected function getRecordIfIndexConfigurationIsValid($recordTable, $recordUid, $indexingConfigurationName, TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
if (!$this->isValidTableForIndexConfigurationName($recordTable, $indexingConfigurationName, $solrConfiguration)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$recordWhereClause = $solrConfiguration->getIndexQueueAdditionalWhereClauseByConfigurationName($indexingConfigurationName);
|
||||
|
||||
$row = $this->getRecordForIndexConfigurationIsValid($recordTable, $recordUid, $recordWhereClause);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the row need by getRecordIfIndexConfigurationIsValid either directly from database
|
||||
* or from cache
|
||||
*
|
||||
* @param string $recordTable
|
||||
* @param integer $recordUid
|
||||
* @param string $recordWhereClause
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRecordForIndexConfigurationIsValid($recordTable, $recordUid, $recordWhereClause)
|
||||
{
|
||||
$cache = GeneralUtility::makeInstance(TwoLevelCache::class, /** @scrutinizer ignore-type */ 'cache_runtime');
|
||||
$cacheId = md5('ConfigurationAwareRecordService' . ':' . 'getRecordIfIndexConfigurationIsValid' . ':' . $recordTable . ':' . $recordUid . ':' . $recordWhereClause);
|
||||
|
||||
$row = $cache->get($cacheId);
|
||||
if (!empty($row)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
$row = (array)BackendUtility::getRecord($recordTable, $recordUid, '*', $recordWhereClause);
|
||||
$cache->set($cacheId, $row);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to check if a table is an allowed table for an index configuration.
|
||||
*
|
||||
* @param string $recordTable
|
||||
* @param string $indexingConfigurationName
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isValidTableForIndexConfigurationName($recordTable, $indexingConfigurationName, TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$tableToIndex = $solrConfiguration->getIndexQueueTableNameOrFallbackToConfigurationName($indexingConfigurationName);
|
||||
|
||||
$isMatchingTable = ($tableToIndex === $recordTable);
|
||||
|
||||
if ($isMatchingTable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves the parent pages record when the parent record is accessible
|
||||
* through the recordWhereClause
|
||||
*
|
||||
* @param int $recordUid
|
||||
* @param string $parentWhereClause
|
||||
* @return array
|
||||
*/
|
||||
protected function getPageOverlayRecordIfParentIsAccessible($recordUid, $parentWhereClause)
|
||||
{
|
||||
$overlayRecord = (array)BackendUtility::getRecord('pages', $recordUid, '*');
|
||||
$overlayParentId = $overlayRecord['l10n_parent'];
|
||||
|
||||
$pageRecord = (array)BackendUtility::getRecord('pages', $overlayParentId, '*', $parentWhereClause);
|
||||
|
||||
if (empty($pageRecord)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $overlayRecord;
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\RecordMonitor\Helper;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2015-2016 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\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\IndexQueue\Initializer\Page;
|
||||
use WapplerSystems\Meilisearch\System\Page\Rootline;
|
||||
use WapplerSystems\Meilisearch\System\Records\Pages\PagesRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* Extracted logic from the RecordMonitor to trigger mount page updates.
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class MountPagesUpdater
|
||||
{
|
||||
|
||||
/**
|
||||
* @var PagesRepository
|
||||
*/
|
||||
protected $pagesRepository;
|
||||
|
||||
/**
|
||||
* MountPagesUpdater constructor.
|
||||
*
|
||||
* @param PagesRepository|null $pagesRepository
|
||||
*/
|
||||
public function __construct(PagesRepository $pagesRepository = null) {
|
||||
$this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updates of the Index Queue in case a newly created or changed
|
||||
* page is part of a tree that is mounted into a another site.
|
||||
*
|
||||
* @param int $pageId Page Id (uid).
|
||||
*/
|
||||
public function update($pageId)
|
||||
{
|
||||
// get the root line of the page, every parent page could be a Mount Page source
|
||||
$rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageId);
|
||||
try {
|
||||
$rootLineArray = $rootlineUtility->get();
|
||||
} catch (\RuntimeException $e) {
|
||||
$rootLineArray = [];
|
||||
}
|
||||
|
||||
$currentPage = array_shift($rootLineArray);
|
||||
$currentPageUid = (int)$currentPage['uid'];
|
||||
|
||||
if (empty($rootLineArray) && $currentPageUid === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var $rootLine Rootline */
|
||||
$rootLine = GeneralUtility::makeInstance(Rootline::class, /** @scrutinizer ignore-type */ $rootLineArray);
|
||||
$rootLineParentPageIds = array_map('intval', $rootLine->getParentPageIds());
|
||||
$destinationMountProperties = $this->pagesRepository->findMountPointPropertiesByPageIdOrByRootLineParentPageIds($currentPageUid, $rootLineParentPageIds);
|
||||
|
||||
if (empty($destinationMountProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($destinationMountProperties as $destinationMount) {
|
||||
$this->addPageToMountingSiteIndexQueue($pageId, $destinationMount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a page to the Index Queue of a site mounting the page.
|
||||
*
|
||||
* @param int $mountedPageId ID (uid) of the mounted page.
|
||||
* @param array $mountProperties Array of mount point properties mountPageSource, mountPageDestination, and mountPageOverlayed
|
||||
*/
|
||||
protected function addPageToMountingSiteIndexQueue($mountedPageId, array $mountProperties)
|
||||
{
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
$mountingSite = $siteRepository->getSiteByPageId($mountProperties['mountPageDestination']);
|
||||
|
||||
/** @var $pageInitializer Page */
|
||||
$pageInitializer = GeneralUtility::makeInstance(Page::class);
|
||||
$pageInitializer->setSite($mountingSite);
|
||||
|
||||
$pageInitializer->initializeMountedPage($mountProperties, $mountedPageId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\RecordMonitor\Helper;
|
||||
|
||||
/***************************************************************
|
||||
* 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\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use WapplerSystems\Meilisearch\System\Cache\TwoLevelCache;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\ExtensionConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Page\Rootline;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\RootlineUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageRepository;
|
||||
|
||||
/**
|
||||
* RootPageResolver.
|
||||
*
|
||||
* Responsibility: The RootPageResolver is responsible to determine all relevant site root page id's
|
||||
* for a certain records, by table and uid.
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class RootPageResolver implements SingletonInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ConfigurationAwareRecordService
|
||||
*/
|
||||
protected $recordService;
|
||||
|
||||
/**
|
||||
* @var TwoLevelCache
|
||||
*/
|
||||
protected $runtimeCache;
|
||||
|
||||
/**
|
||||
* @var ExtensionConfiguration
|
||||
*/
|
||||
protected $extensionConfiguration;
|
||||
|
||||
/**
|
||||
* RootPageResolver constructor.
|
||||
* @param ConfigurationAwareRecordService|null $recordService
|
||||
* @param TwoLevelCache|null $twoLevelCache
|
||||
*/
|
||||
public function __construct(ConfigurationAwareRecordService $recordService = null, TwoLevelCache $twoLevelCache = null)
|
||||
{
|
||||
$this->recordService = $recordService ?? GeneralUtility::makeInstance(ConfigurationAwareRecordService::class);
|
||||
$this->runtimeCache = $twoLevelCache ?? GeneralUtility::makeInstance(TwoLevelCache::class, /** @scrutinizer ignore-type */ 'cache_runtime');
|
||||
$this->extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines the responsible site roots for a record by getting the rootPage of the record and checking
|
||||
* if the pid is references in another site with additionalPageIds and returning those rootPageIds as well.
|
||||
* The result is cached by the caching framework.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return array
|
||||
*/
|
||||
public function getResponsibleRootPageIds($table, $uid)
|
||||
{
|
||||
$cacheId = 'RootPageResolver' . '_' . 'getResponsibleRootPageIds' . '_' . $table . '_' . $uid;
|
||||
$methodResult = $this->runtimeCache->get($cacheId);
|
||||
if (!empty($methodResult)) {
|
||||
return $methodResult;
|
||||
}
|
||||
|
||||
$methodResult = $this->buildResponsibleRootPageIds($table, $uid);
|
||||
$this->runtimeCache->set($cacheId, $methodResult);
|
||||
|
||||
return $methodResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed pageId is a root page.
|
||||
*
|
||||
* @param int $pageId Page ID
|
||||
* @return bool TRUE if the page is marked as root page, FALSE otherwise
|
||||
*/
|
||||
public function getIsRootPageId($pageId)
|
||||
{
|
||||
// Page 0 can never be a root page
|
||||
if ($pageId === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Page -1 is a workspace thing
|
||||
if ($pageId === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cacheId = 'RootPageResolver' . '_' . 'getIsRootPageId' . '_' . $pageId;
|
||||
$isSiteRoot = $this->runtimeCache->get($cacheId);
|
||||
|
||||
if (!empty($isSiteRoot)) {
|
||||
return $isSiteRoot;
|
||||
}
|
||||
|
||||
$page = $this->getPageRecordByPageId($pageId);
|
||||
if (empty($page)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'The page for the given page ID \'' . $pageId
|
||||
. '\' could not be found in the database and can therefore not be used as site root page.',
|
||||
1487171426
|
||||
);
|
||||
}
|
||||
|
||||
$isSiteRoot = Site::isRootPage($page);
|
||||
$this->runtimeCache->set($cacheId, $isSiteRoot);
|
||||
|
||||
return $isSiteRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $pageId
|
||||
* @param string $fieldList
|
||||
* @return array
|
||||
*/
|
||||
protected function getPageRecordByPageId($pageId, $fieldList = 'is_siteroot')
|
||||
{
|
||||
return (array)BackendUtility::getRecord('pages', $pageId, $fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the rootpage ID for a given page.
|
||||
*
|
||||
* @param int $pageId A page ID somewhere in a tree.
|
||||
* @param bool $forceFallback Force the explicit detection and do not use the current frontend root line
|
||||
* @param string $mountPointIdentifier
|
||||
* @return int The page's tree branch's root page ID
|
||||
*/
|
||||
public function getRootPageId($pageId = 0, $forceFallback = false, $mountPointIdentifier = '')
|
||||
{
|
||||
/** @var Rootline $rootLine */
|
||||
$rootLine = GeneralUtility::makeInstance(Rootline::class);
|
||||
$rootPageId = intval($pageId) ?: intval($GLOBALS['TSFE']->id);
|
||||
|
||||
// frontend
|
||||
if (!empty($GLOBALS['TSFE']->rootLine)) {
|
||||
$rootLine->setRootLineArray($GLOBALS['TSFE']->rootLine);
|
||||
}
|
||||
|
||||
// fallback, backend
|
||||
if ($pageId != 0 && ($forceFallback || !$rootLine->getHasRootPage())) {
|
||||
$rootlineUtility = GeneralUtility::makeInstance(RootlineUtility::class, $pageId, $mountPointIdentifier);
|
||||
try {
|
||||
$rootLineArray = $rootlineUtility->get();
|
||||
} catch (\RuntimeException $e) {
|
||||
$rootLineArray = [];
|
||||
}
|
||||
$rootLine->setRootLineArray($rootLineArray);
|
||||
}
|
||||
|
||||
$rootPageFromRootLine = $rootLine->getRootPageId();
|
||||
|
||||
return $rootPageFromRootLine === 0 ? $rootPageId : $rootPageFromRootLine;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method determines the responsible site roots for a record by getting the rootPage of the record and checking
|
||||
* if the pid is references in another site with additionalPageIds and returning those rootPageIds as well.
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
* @return array
|
||||
*/
|
||||
protected function buildResponsibleRootPageIds($table, $uid)
|
||||
{
|
||||
$rootPages = [];
|
||||
$rootPageId = $this->getRootPageIdByTableAndUid($table, $uid);
|
||||
if ($this->getIsRootPageId($rootPageId)) {
|
||||
$rootPages[] = $rootPageId;
|
||||
}
|
||||
if ($this->extensionConfiguration->getIsUseConfigurationTrackRecordsOutsideSiteroot()) {
|
||||
$recordPageId = $this->getRecordPageId($table, $uid);
|
||||
if ($recordPageId === 0) {
|
||||
return $rootPages;
|
||||
}
|
||||
$alternativeSiteRoots = $this->getAlternativeSiteRootPagesIds($table, $uid, $recordPageId);
|
||||
$rootPages = array_merge($rootPages, $alternativeSiteRoots);
|
||||
}
|
||||
|
||||
return $rootPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if the record is a pages record or another one and determines the rootPageId from the records
|
||||
* rootline.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return int
|
||||
*/
|
||||
protected function getRootPageIdByTableAndUid($table, $uid)
|
||||
{
|
||||
if ($table === 'pages') {
|
||||
$rootPageId = $this->getRootPageId($uid);
|
||||
return $rootPageId;
|
||||
} else {
|
||||
$recordPageId = $this->getRecordPageId($table, $uid);
|
||||
$rootPageId = $this->getRootPageId($recordPageId, true);
|
||||
return $rootPageId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pageId of the record or 0 when no valid record was given.
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRecordPageId($table, $uid)
|
||||
{
|
||||
$record = BackendUtility::getRecord($table, $uid, 'pid');
|
||||
return !empty($record['pid']) ? (int)$record['pid'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no root page can be determined we check if the pageIdOf the record is configured as additionalPageId in the index
|
||||
* configuration of another site, if so we return the rootPageId of this site.
|
||||
* The result is cached by the caching framework.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @param int $recordPageId
|
||||
* @return array
|
||||
*/
|
||||
public function getAlternativeSiteRootPagesIds($table, $uid, $recordPageId)
|
||||
{
|
||||
$siteRootsByObservedPageIds = $this->getSiteRootsByObservedPageIds($table, $uid);
|
||||
if (!isset($siteRootsByObservedPageIds[$recordPageId])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $siteRootsByObservedPageIds[$recordPageId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an optimized array structure we the monitored pageId as key and the relevant site rootIds as value.
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
* @return array
|
||||
*/
|
||||
protected function getSiteRootsByObservedPageIds($table, $uid)
|
||||
{
|
||||
$cacheId = 'RootPageResolver' . '_' . 'getSiteRootsByObservedPageIds' . '_' . $table . '_' . $uid;
|
||||
$methodResult = $this->runtimeCache->get($cacheId);
|
||||
if (!empty($methodResult)) {
|
||||
return $methodResult;
|
||||
}
|
||||
|
||||
$methodResult = $this->buildSiteRootsByObservedPageIds($table, $uid);
|
||||
$this->runtimeCache->set($cacheId, $methodResult);
|
||||
|
||||
return $methodResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods build an array with observer page id as key and rootPageIds as values to determine which root pages
|
||||
* are responsible for this record by referencing the pageId in additionalPageIds configuration.
|
||||
*
|
||||
* @param string $table
|
||||
* @param integer $uid
|
||||
* @return array
|
||||
*/
|
||||
protected function buildSiteRootsByObservedPageIds($table, $uid)
|
||||
{
|
||||
$siteRootByObservedPageIds = [];
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
$allSites = $siteRepository->getAvailableSites();
|
||||
|
||||
foreach ($allSites as $site) {
|
||||
$solrConfiguration = $site->getSolrConfiguration();
|
||||
$indexingConfigurationName = $this->recordService->getIndexingConfigurationName($table, $uid, $solrConfiguration);
|
||||
if ($indexingConfigurationName === null) {
|
||||
continue;
|
||||
}
|
||||
$observedPageIdsOfSiteRoot = $solrConfiguration->getIndexQueueAdditionalPageIdsByConfigurationName($indexingConfigurationName);
|
||||
foreach ($observedPageIdsOfSiteRoot as $observedPageIdOfSiteRoot) {
|
||||
$siteRootByObservedPageIds[$observedPageIdOfSiteRoot][] = $site->getRootPageId();
|
||||
}
|
||||
}
|
||||
|
||||
return $siteRootByObservedPageIds;
|
||||
}
|
||||
}
|
138
Classes/Domain/Index/Queue/Statistic/QueueStatistic.php
Normal file
138
Classes/Domain/Index/Queue/Statistic/QueueStatistic.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\Statistic;
|
||||
|
||||
/***************************************************************
|
||||
* 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 QueueStatistic
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $failedCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $pendingCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $successCount = 0;
|
||||
|
||||
/**
|
||||
* @param int $failedCount
|
||||
*/
|
||||
public function setFailedCount($failedCount)
|
||||
{
|
||||
$this->failedCount = $failedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFailedCount()
|
||||
{
|
||||
return $this->failedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getFailedPercentage()
|
||||
{
|
||||
return $this->getPercentage($this->getFailedCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $pendingCount
|
||||
*/
|
||||
public function setPendingCount($pendingCount)
|
||||
{
|
||||
$this->pendingCount = $pendingCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPendingCount()
|
||||
{
|
||||
return $this->pendingCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getPendingPercentage()
|
||||
{
|
||||
return $this->getPercentage($this->getPendingCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $successCount
|
||||
*/
|
||||
public function setSuccessCount($successCount)
|
||||
{
|
||||
$this->successCount = $successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSuccessCount()
|
||||
{
|
||||
return $this->successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getSuccessPercentage()
|
||||
{
|
||||
return $this->getPercentage($this->getSuccessCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTotalCount()
|
||||
{
|
||||
return $this->pendingCount + $this->failedCount + $this->successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $count
|
||||
* @return float
|
||||
*/
|
||||
protected function getPercentage($count)
|
||||
{
|
||||
$total = $this->getTotalCount();
|
||||
if ($total === 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (float)round((100 / $total) * $count, 2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Index\Queue\Statistic;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\Records\AbstractRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedMethodException;
|
||||
|
||||
/**
|
||||
* Class QueueStatisticsRepository
|
||||
*/
|
||||
class QueueStatisticsRepository extends AbstractRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tx_meilisearch_indexqueue_item';
|
||||
|
||||
/**
|
||||
* Extracts the number of pending, indexed and erroneous items from the
|
||||
* Index Queue.
|
||||
*
|
||||
* @param int $rootPid
|
||||
* @param string $indexingConfigurationName
|
||||
*
|
||||
* @return QueueStatistic
|
||||
*/
|
||||
public function findOneByRootPidAndOptionalIndexingConfigurationName(int $rootPid, $indexingConfigurationName = null): QueueStatistic
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder
|
||||
->add('select', vsprintf('(%s < %s) AS %s', [
|
||||
$queryBuilder->quoteIdentifier('indexed'),
|
||||
$queryBuilder->quoteIdentifier('changed'),
|
||||
$queryBuilder->quoteIdentifier('pending')
|
||||
]), true)
|
||||
->add('select', vsprintf('(%s) AS %s', [
|
||||
$queryBuilder->expr()->notLike('errors', $queryBuilder->createNamedParameter('')),
|
||||
$queryBuilder->quoteIdentifier('failed')
|
||||
]), true)
|
||||
->add('select', $queryBuilder->expr()->count('*', 'count'), true)
|
||||
->from($this->table)
|
||||
->where($queryBuilder->expr()->eq('root', $queryBuilder->createNamedParameter($rootPid, \PDO::PARAM_INT)))
|
||||
->groupBy('pending', 'failed');
|
||||
|
||||
if (!empty($indexingConfigurationName)) {
|
||||
$queryBuilder->andWhere($queryBuilder->expr()->eq('indexing_configuration', $queryBuilder->createNamedParameter($indexingConfigurationName)));
|
||||
}
|
||||
|
||||
return $this->buildQueueStatisticFromResultSet($queryBuilder->execute()->fetchAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates and fills QueueStatistic with values
|
||||
*
|
||||
* @param array $indexQueueStatisticResultSet
|
||||
* @return QueueStatistic
|
||||
*/
|
||||
protected function buildQueueStatisticFromResultSet(array $indexQueueStatisticResultSet): QueueStatistic
|
||||
{
|
||||
/* @var $statistic QueueStatistic */
|
||||
$statistic = GeneralUtility::makeInstance(QueueStatistic::class);
|
||||
foreach ($indexQueueStatisticResultSet as $row) {
|
||||
if ($row['failed'] == 1) {
|
||||
$statistic->setFailedCount((int)$row['count']);
|
||||
} elseif ($row['pending'] == 1) {
|
||||
$statistic->setPendingCount((int)$row['count']);
|
||||
} else {
|
||||
$statistic->setSuccessCount((int)$row['count']);
|
||||
}
|
||||
}
|
||||
|
||||
return $statistic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use this method.
|
||||
*
|
||||
* @return int
|
||||
* @throws UnsupportedMethodException
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
throw new UnsupportedMethodException('Can not count the Index Queue Statistics.', 1504694750);
|
||||
}
|
||||
}
|
312
Classes/Domain/Search/ApacheSolrDocument/Builder.php
Normal file
312
Classes/Domain/Search/ApacheSolrDocument/Builder.php
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ApacheSolrDocument;
|
||||
|
||||
/***************************************************************
|
||||
* 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\Access\Rootline;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\Domain\Variants\IdBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\Site;
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
use WapplerSystems\Meilisearch\Typo3PageContentExtractor;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Builder class to build an ApacheSolrDocument
|
||||
*
|
||||
* Responsible to build \WapplerSystems\Meilisearch\System\Solr\Document\Document
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class Builder
|
||||
{
|
||||
/**
|
||||
* @var IdBuilder
|
||||
*/
|
||||
protected $variantIdBuilder;
|
||||
|
||||
/**
|
||||
* Builder constructor.
|
||||
* @param IdBuilder|null $variantIdBuilder
|
||||
*/
|
||||
public function __construct(IdBuilder $variantIdBuilder = null)
|
||||
{
|
||||
$this->variantIdBuilder = $variantIdBuilder ?? GeneralUtility::makeInstance(IdBuilder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to build an Document from a TYPO3 page.
|
||||
*
|
||||
* @param TypoScriptFrontendController $page
|
||||
* @param string $url
|
||||
* @param Rootline $pageAccessRootline
|
||||
* @param string $mountPointParameter
|
||||
* @return Document|object
|
||||
*/
|
||||
public function fromPage(TypoScriptFrontendController $page, $url, Rootline $pageAccessRootline, $mountPointParameter): Document
|
||||
{
|
||||
/* @var $document Document */
|
||||
$document = GeneralUtility::makeInstance(Document::class);
|
||||
$site = $this->getSiteByPageId($page->id);
|
||||
$pageRecord = $page->page;
|
||||
|
||||
$accessGroups = $this->getDocumentIdGroups($pageAccessRootline);
|
||||
$documentId = $this->getPageDocumentId($page, $accessGroups, $mountPointParameter);
|
||||
|
||||
$document->setField('id', $documentId);
|
||||
$document->setField('site', $site->getDomain());
|
||||
$document->setField('siteHash', $site->getSiteHash());
|
||||
$document->setField('appKey', 'EXT:meilisearch');
|
||||
$document->setField('type', 'pages');
|
||||
|
||||
// system fields
|
||||
$document->setField('uid', $page->id);
|
||||
$document->setField('pid', $pageRecord['pid']);
|
||||
|
||||
// variantId
|
||||
$variantId = $this->variantIdBuilder->buildFromTypeAndUid('pages', $page->id);
|
||||
$document->setField('variantId', $variantId);
|
||||
|
||||
$document->setField('typeNum', $page->type);
|
||||
$document->setField('created', $pageRecord['crdate']);
|
||||
$document->setField('changed', $pageRecord['SYS_LASTCHANGED']);
|
||||
|
||||
$rootline = $this->getRootLineFieldValue($page->id, $mountPointParameter);
|
||||
$document->setField('rootline', $rootline);
|
||||
|
||||
// access
|
||||
$this->addAccessField($document, $pageAccessRootline);
|
||||
$this->addEndtimeField($document, $pageRecord);
|
||||
|
||||
// content
|
||||
// @extensionScannerIgnoreLine
|
||||
$contentExtractor = $this->getExtractorForPageContent($page->content);
|
||||
$document->setField('title', $contentExtractor->getPageTitle());
|
||||
$document->setField('subTitle', $pageRecord['subtitle']);
|
||||
$document->setField('navTitle', $pageRecord['nav_title']);
|
||||
$document->setField('author', $pageRecord['author']);
|
||||
$document->setField('description', $pageRecord['description']);
|
||||
$document->setField('abstract', $pageRecord['abstract']);
|
||||
$document->setField('content', $contentExtractor->getIndexableContent());
|
||||
$document->setField('url', $url);
|
||||
|
||||
$this->addKeywordsField($document, $pageRecord);
|
||||
$this->addTagContentFields($document, $contentExtractor->getTagContent());
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Solr document with the basic / core fields set already.
|
||||
*
|
||||
* @param array $itemRecord
|
||||
* @param string $type
|
||||
* @param int $rootPageUid
|
||||
* @param string $accessRootLine
|
||||
* @return Document
|
||||
*/
|
||||
public function fromRecord(array $itemRecord, string $type, int $rootPageUid, string $accessRootLine): Document
|
||||
{
|
||||
/* @var $document Document */
|
||||
$document = GeneralUtility::makeInstance(Document::class);
|
||||
|
||||
$site = $this->getSiteByPageId($rootPageUid);
|
||||
|
||||
$documentId = $this->getDocumentId($type, $site->getRootPageId(), $itemRecord['uid']);
|
||||
|
||||
// required fields
|
||||
$document->setField('id', $documentId);
|
||||
$document->setField('type', $type);
|
||||
$document->setField('appKey', 'EXT:meilisearch');
|
||||
|
||||
// site, siteHash
|
||||
$document->setField('site', $site->getDomain());
|
||||
$document->setField('siteHash', $site->getSiteHash());
|
||||
|
||||
// uid, pid
|
||||
$document->setField('uid', $itemRecord['uid']);
|
||||
$document->setField('pid', $itemRecord['pid']);
|
||||
|
||||
// variantId
|
||||
$variantId = $this->variantIdBuilder->buildFromTypeAndUid($type, $itemRecord['uid']);
|
||||
$document->setField('variantId', $variantId);
|
||||
|
||||
// created, changed
|
||||
if (!empty($GLOBALS['TCA'][$type]['ctrl']['crdate'])) {
|
||||
$document->setField('created', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['crdate']]);
|
||||
}
|
||||
if (!empty($GLOBALS['TCA'][$type]['ctrl']['tstamp'])) {
|
||||
$document->setField('changed', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['tstamp']]);
|
||||
}
|
||||
|
||||
// access, endtime
|
||||
$document->setField('access', $accessRootLine);
|
||||
if (!empty($GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime'])
|
||||
&& $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']] != 0
|
||||
) {
|
||||
$document->setField('endtime', $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']]);
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptFrontendController $page
|
||||
* @param string $accessGroups
|
||||
* @param string $mountPointParameter
|
||||
* @return string
|
||||
*/
|
||||
protected function getPageDocumentId(TypoScriptFrontendController $frontendController, string $accessGroups, string $mountPointParameter): string
|
||||
{
|
||||
return Util::getPageDocumentId($frontendController->id, $frontendController->type, Util::getLanguageUid(), $accessGroups, $mountPointParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param int $rootPageId
|
||||
* @param int $recordUid
|
||||
* @return string
|
||||
*/
|
||||
protected function getDocumentId(string $type, int $rootPageId, int $recordUid): string
|
||||
{
|
||||
return Util::getDocumentId($type, $rootPageId, $recordUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $pageId
|
||||
* @return Site
|
||||
*/
|
||||
protected function getSiteByPageId($pageId)
|
||||
{
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
return $siteRepository->getSiteByPageId($pageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pageContent
|
||||
* @return Typo3PageContentExtractor
|
||||
*/
|
||||
protected function getExtractorForPageContent($pageContent)
|
||||
{
|
||||
return GeneralUtility::makeInstance(Typo3PageContentExtractor::class, /** @scrutinizer ignore-type */ $pageContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the content for the rootline field.
|
||||
*
|
||||
* @param int $pageId
|
||||
* @param string $mountPointParameter
|
||||
* @return string
|
||||
*/
|
||||
protected function getRootLineFieldValue($pageId, $mountPointParameter)
|
||||
{
|
||||
$rootline = $pageId;
|
||||
if ($mountPointParameter !== '') {
|
||||
$rootline .= ',' . $mountPointParameter;
|
||||
}
|
||||
return $rootline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a comma separated list of frontend user groups to use for the
|
||||
* document ID.
|
||||
*
|
||||
* @param Rootline $pageAccessRootline
|
||||
* @return string A comma separated list of frontend user groups.
|
||||
*/
|
||||
protected function getDocumentIdGroups(Rootline $pageAccessRootline)
|
||||
{
|
||||
$groups = $pageAccessRootline->getGroups();
|
||||
$groups = Rootline::cleanGroupArray($groups);
|
||||
|
||||
if (empty($groups)) {
|
||||
$groups[] = 0;
|
||||
}
|
||||
|
||||
$groups = implode(',', $groups);
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the access field to the document if needed.
|
||||
*
|
||||
* @param Document $document
|
||||
* @param Rootline $pageAccessRootline
|
||||
*/
|
||||
protected function addAccessField(Document $document, Rootline $pageAccessRootline)
|
||||
{
|
||||
$access = (string)$pageAccessRootline;
|
||||
if (trim($access) !== '') {
|
||||
$document->setField('access', $access);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the endtime field value to the Document.
|
||||
*
|
||||
* @param Document $document
|
||||
* @param array $pageRecord
|
||||
*/
|
||||
protected function addEndtimeField(Document $document, $pageRecord)
|
||||
{
|
||||
if ($pageRecord['endtime']) {
|
||||
$document->setField('endtime', $pageRecord['endtime']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds keywords, multi valued.
|
||||
*
|
||||
* @param Document $document
|
||||
* @param array $pageRecord
|
||||
*/
|
||||
protected function addKeywordsField(Document $document, $pageRecord)
|
||||
{
|
||||
if (!isset($pageRecord['keywords'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keywords = array_unique(GeneralUtility::trimExplode(',', $pageRecord['keywords'], true));
|
||||
foreach ($keywords as $keyword) {
|
||||
$document->addField('keywords', $keyword);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add content from several tags like headers, anchors, ...
|
||||
*
|
||||
* @param Document $document
|
||||
* @param array $tagContent
|
||||
*/
|
||||
protected function addTagContentFields(Document $document, $tagContent = [])
|
||||
{
|
||||
foreach ($tagContent as $fieldName => $fieldValue) {
|
||||
$document->setField($fieldName, $fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
170
Classes/Domain/Search/ApacheSolrDocument/Repository.php
Normal file
170
Classes/Domain/Search/ApacheSolrDocument/Repository.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\ApacheSolrDocument;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 Rafael Kähm <rafael.kaehm@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\ConnectionManager;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\QueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Result\Parser\DocumentEscapeService;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\NoSolrConnectionFoundException;
|
||||
use WapplerSystems\Meilisearch\Search;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Solr\Document\Document;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrCommunicationException;
|
||||
use WapplerSystems\Meilisearch\System\Solr\SolrConnection;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class Repository
|
||||
*/
|
||||
class Repository implements SingletonInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Search
|
||||
*
|
||||
* @var \WapplerSystems\Meilisearch\Search
|
||||
*/
|
||||
protected $search;
|
||||
|
||||
/**
|
||||
* @var DocumentEscapeService
|
||||
*/
|
||||
protected $documentEscapeService = null;
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration|null
|
||||
*/
|
||||
protected $typoScriptConfiguration = null;
|
||||
|
||||
/**
|
||||
* @var QueryBuilder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* Repository constructor.
|
||||
* @param DocumentEscapeService|null $documentEscapeService
|
||||
* @param QueryBuilder|null $queryBuilder
|
||||
*/
|
||||
public function __construct(DocumentEscapeService $documentEscapeService = null, TypoScriptConfiguration $typoScriptConfiguration = null, QueryBuilder $queryBuilder = null)
|
||||
{
|
||||
$this->typoScriptConfiguration = $typoScriptConfiguration ?? Util::getSolrConfiguration();
|
||||
$this->documentEscapeService = $documentEscapeService ?? GeneralUtility::makeInstance(DocumentEscapeService::class, /** @scrutinizer ignore-type */ $typoScriptConfiguration);
|
||||
$this->queryBuilder = $queryBuilder ?? GeneralUtility::makeInstance(QueryBuilder::class, /** @scrutinizer ignore-type */ $this->typoScriptConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns firs found \WapplerSystems\Meilisearch\System\Solr\Document\Document for current page by given language id.
|
||||
*
|
||||
* @param $languageId
|
||||
* @return Document|false
|
||||
*/
|
||||
public function findOneByPageIdAndByLanguageId($pageId, $languageId)
|
||||
{
|
||||
$documentCollection = $this->findByPageIdAndByLanguageId($pageId, $languageId);
|
||||
return reset($documentCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all found \WapplerSystems\Meilisearch\System\Solr\Document\Document[] by given page id and language id.
|
||||
* Returns empty array if nothing found, e.g. if no language or no page(or no index for page) is present.
|
||||
*
|
||||
* @param int $pageId
|
||||
* @param int $languageId
|
||||
* @return Document[]
|
||||
*/
|
||||
public function findByPageIdAndByLanguageId($pageId, $languageId)
|
||||
{
|
||||
try {
|
||||
$this->initializeSearch($pageId, $languageId);
|
||||
$pageQuery = $this->queryBuilder->buildPageQuery($pageId);
|
||||
$response = $this->search->search($pageQuery, 0, 10000);
|
||||
} catch (NoSolrConnectionFoundException $exception) {
|
||||
return [];
|
||||
} catch (SolrCommunicationException $exception) {
|
||||
return [];
|
||||
}
|
||||
$data = $response->getParsedData();
|
||||
// @extensionScannerIgnoreLine
|
||||
return $this->documentEscapeService->applyHtmlSpecialCharsOnAllFields($data->response->docs ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param int $uid
|
||||
* @param int $pageId
|
||||
* @param int $languageId
|
||||
* @return Document[]|array
|
||||
*/
|
||||
public function findByTypeAndPidAndUidAndLanguageId($type, $uid, $pageId, $languageId): array
|
||||
{
|
||||
try {
|
||||
$this->initializeSearch($pageId, $languageId);
|
||||
$recordQuery = $this->queryBuilder->buildRecordQuery($type, $uid, $pageId);
|
||||
$response = $this->search->search($recordQuery, 0, 10000);
|
||||
} catch (NoSolrConnectionFoundException $exception) {
|
||||
return [];
|
||||
} catch (SolrCommunicationException $exception) {
|
||||
return [];
|
||||
}
|
||||
$data = $response->getParsedData();
|
||||
// @extensionScannerIgnoreLine
|
||||
return $this->documentEscapeService->applyHtmlSpecialCharsOnAllFields($data->response->docs ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Search for given language
|
||||
*
|
||||
* @param int $languageId
|
||||
*/
|
||||
protected function initializeSearch($pageId, $languageId = 0)
|
||||
{
|
||||
if (!is_int($pageId)) {
|
||||
throw new \InvalidArgumentException('Invalid page ID = ' . $pageId, 1487332926);
|
||||
}
|
||||
if (!is_int($languageId)) { // @todo: Check if lang id is defined and present?
|
||||
throw new \InvalidArgumentException('Invalid language ID = ' . $languageId, 1487335178);
|
||||
}
|
||||
/* @var $connectionManager ConnectionManager */
|
||||
$connectionManager = GeneralUtility::makeInstance(ConnectionManager::class);
|
||||
$solrConnection = $connectionManager->getConnectionByPageId($pageId, $languageId);
|
||||
|
||||
$this->search = $this->getSearch($solrConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance of the Search object.
|
||||
*
|
||||
* @param SolrConnection $solrConnection
|
||||
* @return Search
|
||||
*/
|
||||
protected function getSearch($solrConnection)
|
||||
{
|
||||
return GeneralUtility::makeInstance(Search::class, /** @scrutinizer ignore-type */ $solrConnection);
|
||||
}
|
||||
}
|
@@ -0,0 +1,179 @@
|
||||
<?php declare(strict_types = 1);
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\FrequentSearches;
|
||||
|
||||
/*
|
||||
* 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\Statistics\StatisticsRepository;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* The FrequentSearchesService is used to retrieve the frequent searches from the database or cache.
|
||||
*
|
||||
* @author Dimitri Ebert <dimitri.ebert@dkd.de>
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
* @copyright (c) 2015-2021 dkd Internet Service GmbH <info@dkd.de>
|
||||
*/
|
||||
class FrequentSearchesService
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of the caching frontend used to cache this command's output.
|
||||
*
|
||||
* @var AbstractFrontend
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var TypoScriptFrontendController
|
||||
*/
|
||||
protected $tsfe;
|
||||
|
||||
/**
|
||||
* @var StatisticsRepository
|
||||
*/
|
||||
protected $statisticsRepository;
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $typoscriptConfiguration
|
||||
* @param AbstractFrontend|null $cache
|
||||
* @param TypoScriptFrontendController|null $tsfe
|
||||
* @param StatisticsRepository|null $statisticsRepository
|
||||
*/
|
||||
public function __construct(
|
||||
TypoScriptConfiguration $typoscriptConfiguration,
|
||||
AbstractFrontend $cache = null,
|
||||
TypoScriptFrontendController $tsfe = null,
|
||||
StatisticsRepository $statisticsRepository = null
|
||||
) {
|
||||
$this->configuration = $typoscriptConfiguration;
|
||||
$this->cache = $cache;
|
||||
$this->tsfe = $tsfe;
|
||||
$this->statisticsRepository = $statisticsRepository ?? GeneralUtility::makeInstance(StatisticsRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array with terms and hits
|
||||
*
|
||||
* @return array Tags as array with terms and hits
|
||||
*/
|
||||
public function getFrequentSearchTerms() : array
|
||||
{
|
||||
$frequentSearchConfiguration = $this->configuration->getSearchFrequentSearchesConfiguration();
|
||||
|
||||
$identifier = $this->getCacheIdentifier($frequentSearchConfiguration);
|
||||
|
||||
if ($this->hasValidCache() && $this->cache->has($identifier)) {
|
||||
$terms = $this->cache->get($identifier);
|
||||
} else {
|
||||
$terms = $this->getFrequentSearchTermsFromStatistics($frequentSearchConfiguration);
|
||||
|
||||
if ($frequentSearchConfiguration['sortBy'] === 'hits') {
|
||||
arsort($terms);
|
||||
} else {
|
||||
ksort($terms);
|
||||
}
|
||||
|
||||
$lifetime = null;
|
||||
if (isset($frequentSearchConfiguration['cacheLifetime'])) {
|
||||
$lifetime = intval($frequentSearchConfiguration['cacheLifetime']);
|
||||
}
|
||||
|
||||
if ($this->hasValidCache()) {
|
||||
$this->cache->set($identifier, $terms, [], $lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets frequent search terms from the statistics tracking table.
|
||||
*
|
||||
* @param array $frequentSearchConfiguration
|
||||
* @return array Array of frequent search terms, keys are the terms, values are hits
|
||||
*/
|
||||
protected function getFrequentSearchTermsFromStatistics(array $frequentSearchConfiguration) : array
|
||||
{
|
||||
$terms = [];
|
||||
|
||||
if ($frequentSearchConfiguration['select.']['checkRootPageId']) {
|
||||
$checkRootPidWhere = 'root_pid = ' . $this->tsfe->tmpl->rootLine[0]['uid'];
|
||||
} else {
|
||||
$checkRootPidWhere = '1';
|
||||
}
|
||||
if ($frequentSearchConfiguration['select.']['checkLanguage']) {
|
||||
$checkLanguageWhere = ' AND language =' . Util::getLanguageUid();
|
||||
} else {
|
||||
$checkLanguageWhere = '';
|
||||
}
|
||||
|
||||
$frequentSearchConfiguration['select.']['ADD_WHERE'] = $checkRootPidWhere .
|
||||
$checkLanguageWhere . ' ' .
|
||||
$frequentSearchConfiguration['select.']['ADD_WHERE'];
|
||||
|
||||
$frequentSearchTerms = $this->statisticsRepository
|
||||
->getFrequentSearchTermsFromStatisticsByFrequentSearchConfiguration($frequentSearchConfiguration);
|
||||
|
||||
if (!is_array($frequentSearchTerms)) {
|
||||
return $terms;
|
||||
}
|
||||
|
||||
foreach ($frequentSearchTerms as $term) {
|
||||
$cleanedTerm = html_entity_decode($term['search_term'], ENT_QUOTES, 'UTF-8');
|
||||
$terms[$cleanedTerm] = $term['hits'];
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $frequentSearchConfiguration
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheIdentifier(array $frequentSearchConfiguration) : string
|
||||
{
|
||||
// Use configuration as cache identifier
|
||||
$identifier = 'frequentSearchesTags';
|
||||
|
||||
if ($frequentSearchConfiguration['select.']['checkRootPageId']) {
|
||||
$identifier .= '_RP' . (int)$this->tsfe->tmpl->rootLine[0]['uid'];
|
||||
}
|
||||
if ($frequentSearchConfiguration['select.']['checkLanguage']) {
|
||||
$identifier .= '_L' . Util::getLanguageUid();
|
||||
}
|
||||
|
||||
$identifier .= '_' . md5(serialize($frequentSearchConfiguration));
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this service has a valid cache class
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasValidCache(): bool
|
||||
{
|
||||
return ($this->cache instanceof FrontendInterface);
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Highlight;
|
||||
|
||||
/*
|
||||
* 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!
|
||||
|
||||
|
||||
/**
|
||||
* Provides highlighting of the search words on the document's actual page by
|
||||
* adding parameters to a document's URL property.
|
||||
*
|
||||
* Initial code from WapplerSystems\Meilisearch\ResultDocumentModifier\SiteHighlighter
|
||||
*
|
||||
* @author Stefan Sprenger <stefan.sprenger@dkd.de>
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
|
||||
use WapplerSystems\Meilisearch\System\Url\UrlHelper;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Class SiteHighlighterUrlModifier
|
||||
*/
|
||||
class SiteHighlighterUrlModifier {
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $searchWords
|
||||
* @param boolean $addNoCache
|
||||
* @param boolean $keepCHash
|
||||
* @return string
|
||||
*/
|
||||
public function modify($url, $searchWords, $addNoCache = true, $keepCHash = false) {
|
||||
$searchWords = str_replace('"', '', $searchWords);
|
||||
$searchWords = GeneralUtility::trimExplode(' ', $searchWords, true);
|
||||
|
||||
/** @var UrlHelper $urlHelper */
|
||||
$urlHelper = GeneralUtility::makeInstance(UrlHelper::class, /** @scrutinizer ignore-type */ $url);
|
||||
$urlHelper->addQueryParameter('sword_list', $searchWords);
|
||||
|
||||
if ($addNoCache) {
|
||||
$urlHelper->addQueryParameter('no_cache', '1');
|
||||
}
|
||||
|
||||
if (!$keepCHash) {
|
||||
$urlHelper->removeQueryParameter('cHash');
|
||||
}
|
||||
|
||||
return $urlHelper->getUrl();
|
||||
}
|
||||
}
|
156
Classes/Domain/Search/LastSearches/LastSearchesRepository.php
Normal file
156
Classes/Domain/Search/LastSearches/LastSearchesRepository.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\LastSearches;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\Records\AbstractRepository;
|
||||
|
||||
class LastSearchesRepository extends AbstractRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'tx_meilisearch_last_searches';
|
||||
|
||||
/**
|
||||
* Finds the last searched keywords from the database
|
||||
*
|
||||
* @param int $limit
|
||||
* @return array An array containing the last searches of the current user
|
||||
*/
|
||||
public function findAllKeywords($limit = 10) : array
|
||||
{
|
||||
$lastSearchesResultSet = $this->getLastSearchesResultSet($limit);
|
||||
if (empty($lastSearchesResultSet)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$lastSearches = [];
|
||||
foreach ($lastSearchesResultSet as $row) {
|
||||
$lastSearches[] = html_entity_decode($row['keywords'], ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
return $lastSearches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all last searches
|
||||
*
|
||||
* @param $limit
|
||||
* @return array
|
||||
*/
|
||||
protected function getLastSearchesResultSet($limit) : array
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
return $queryBuilder
|
||||
->select('keywords')
|
||||
->addSelectLiteral(
|
||||
$queryBuilder->expr()->max('tstamp','maxtstamp')
|
||||
)
|
||||
->from($this->table)
|
||||
// There is no support for DISTINCT, a ->groupBy() has to be used instead.
|
||||
->groupBy('keywords')
|
||||
->orderBy('maxtstamp', 'DESC')
|
||||
->setMaxResults($limit)->execute()->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds keywords to last searches or updates the oldest row by given limit.
|
||||
*
|
||||
* @param string $lastSearchesKeywords
|
||||
* @param int $lastSearchesLimit
|
||||
* @return void
|
||||
*/
|
||||
public function add(string $lastSearchesKeywords, int $lastSearchesLimit)
|
||||
{
|
||||
$nextSequenceId = $this->resolveNextSequenceIdForGivenLimit($lastSearchesLimit);
|
||||
$rowsCount = $this->count();
|
||||
if ($nextSequenceId < $rowsCount) {
|
||||
$this->update([
|
||||
'sequence_id' => $nextSequenceId,
|
||||
'keywords' => $lastSearchesKeywords
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$queryBuilder
|
||||
->insert($this->table)
|
||||
->values([
|
||||
'sequence_id' => $nextSequenceId,
|
||||
'keywords' => $lastSearchesKeywords,
|
||||
'tstamp' => time()
|
||||
])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves next sequence id by given last searches limit.
|
||||
*
|
||||
* @param int $lastSearchesLimit
|
||||
* @return int
|
||||
*/
|
||||
protected function resolveNextSequenceIdForGivenLimit(int $lastSearchesLimit) : int
|
||||
{
|
||||
$nextSequenceId = 0;
|
||||
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
$result = $queryBuilder->select('sequence_id')
|
||||
->from($this->table)
|
||||
->orderBy('tstamp', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->execute()->fetch();
|
||||
|
||||
if (!empty($result)) {
|
||||
$nextSequenceId = ($result['sequence_id'] + 1) % $lastSearchesLimit;
|
||||
}
|
||||
|
||||
return $nextSequenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates last searches row by using sequence_id from given $lastSearchesRow array
|
||||
*
|
||||
* @param array $lastSearchesRow
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function update(array $lastSearchesRow)
|
||||
{
|
||||
$queryBuilder = $this->getQueryBuilder();
|
||||
|
||||
$affectedRows = $queryBuilder
|
||||
->update($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sequence_id', $queryBuilder->createNamedParameter($lastSearchesRow['sequence_id']))
|
||||
)
|
||||
->set('tstamp', time())
|
||||
->set('keywords', $lastSearchesRow['keywords'])
|
||||
->execute();
|
||||
|
||||
if ($affectedRows < 1) {
|
||||
throw new \InvalidArgumentException(vsprintf('By trying to update last searches row with values "%s" nothing was updated, make sure the given "sequence_id" exists in database.', [\json_encode($lastSearchesRow)]), 1502717923);
|
||||
}
|
||||
}
|
||||
}
|
148
Classes/Domain/Search/LastSearches/LastSearchesService.php
Normal file
148
Classes/Domain/Search/LastSearches/LastSearchesService.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\LastSearches;
|
||||
|
||||
/***************************************************************
|
||||
* 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\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Session\FrontendUserSession;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The LastSearchesService is responsible to return the LastSearches from the session or database,
|
||||
* depending on the configuration.
|
||||
*
|
||||
* @author Timo Schmidt <timo.schmidt@dkd.de>
|
||||
*/
|
||||
class LastSearchesService
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var FrontendUserSession
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @var LastSearchesRepository
|
||||
*/
|
||||
protected $lastSearchesRepository;
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $typoscriptConfiguration
|
||||
* @param FrontendUserSession|null $session
|
||||
* @param LastSearchesRepository|null $lastSearchesRepository
|
||||
*/
|
||||
public function __construct(TypoScriptConfiguration $typoscriptConfiguration, FrontendUserSession $session = null, LastSearchesRepository $lastSearchesRepository = null)
|
||||
{
|
||||
$this->configuration = $typoscriptConfiguration;
|
||||
$this->session = $session ?? GeneralUtility::makeInstance(FrontendUserSession::class);
|
||||
$this->lastSearchesRepository = $lastSearchesRepository ?? GeneralUtility::makeInstance(LastSearchesRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last searches from the session or database depending on the configuration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLastSearches()
|
||||
{
|
||||
$lastSearchesKeywords = [];
|
||||
$mode = $this->configuration->getSearchLastSearchesMode();
|
||||
$limit = $this->configuration->getSearchLastSearchesLimit();
|
||||
|
||||
switch ($mode) {
|
||||
case 'user':
|
||||
$lastSearchesKeywords = $this->getLastSearchesFromSession($limit);
|
||||
break;
|
||||
case 'global':
|
||||
$lastSearchesKeywords = $this->lastSearchesRepository->findAllKeywords($limit);
|
||||
break;
|
||||
}
|
||||
|
||||
return $lastSearchesKeywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the keywords to the last searches in the database or session depending on the configuration.
|
||||
*
|
||||
* @param string $keywords
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function addToLastSearches($keywords)
|
||||
{
|
||||
$mode = $this->configuration->getSearchLastSearchesMode();
|
||||
switch ($mode) {
|
||||
case 'user':
|
||||
$this->storeKeywordsToSession($keywords);
|
||||
break;
|
||||
case 'global':
|
||||
$this->lastSearchesRepository->add($keywords, (int)$this->configuration->getSearchLastSearchesLimit());
|
||||
break;
|
||||
default:
|
||||
throw new \UnexpectedValueException(
|
||||
'Unknown mode for plugin.tx_meilisearch.search.lastSearches.mode, valid modes are "user" or "global".',
|
||||
1342456570
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last searched keywords from the user's session
|
||||
*
|
||||
* @param int $limit
|
||||
* @return array An array containing the last searches of the current user
|
||||
*/
|
||||
protected function getLastSearchesFromSession($limit)
|
||||
{
|
||||
$lastSearches = $this->session->getLastSearches();
|
||||
$lastSearches = array_slice(array_reverse(array_unique($lastSearches)), 0, $limit);
|
||||
|
||||
return $lastSearches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the keywords from the current query to the user's session.
|
||||
*
|
||||
* @param string $keywords The current query's keywords
|
||||
* @return void
|
||||
*/
|
||||
protected function storeKeywordsToSession($keywords)
|
||||
{
|
||||
$currentLastSearches = $this->session->getLastSearches();
|
||||
$lastSearches = $currentLastSearches;
|
||||
$newLastSearchesCount = array_push($lastSearches, $keywords);
|
||||
|
||||
while ($newLastSearchesCount > $this->configuration->getSearchLastSearchesLimit()) {
|
||||
array_shift($lastSearches);
|
||||
$newLastSearchesCount = count($lastSearches);
|
||||
}
|
||||
|
||||
$this->session->setLastSearches($lastSearches);
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\LastSearches;
|
||||
|
||||
/***************************************************************
|
||||
* 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\LastSearches\LastSearchesService;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSet;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\SearchResultSetProcessor;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Writes the last searches
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class LastSearchesWriterProcessor implements SearchResultSetProcessor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return SearchResultSet
|
||||
*/
|
||||
public function process(SearchResultSet $resultSet) {
|
||||
|
||||
if ($resultSet->getAllResultCount() === 0) {
|
||||
// when the search does not produce a result we do not store the last searches
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
if (!isset($GLOBALS['TSFE'])) {
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
$query = $resultSet->getUsedSearchRequest()->getRawUserQuery();
|
||||
|
||||
if (is_string($query)) {
|
||||
$lastSearchesService = $this->getLastSearchesService($resultSet);
|
||||
$lastSearchesService->addToLastSearches($query);
|
||||
}
|
||||
|
||||
return $resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SearchResultSet $resultSet
|
||||
* @return LastSearchesService
|
||||
*/
|
||||
protected function getLastSearchesService(SearchResultSet $resultSet) {
|
||||
return GeneralUtility::makeInstance(LastSearchesService::class,
|
||||
/** @scrutinizer ignore-type */ $resultSet->getUsedSearchRequest()->getContextTypoScriptConfiguration());
|
||||
}
|
||||
}
|
558
Classes/Domain/Search/Query/AbstractQueryBuilder.php
Normal file
558
Classes/Domain/Search/Query/AbstractQueryBuilder.php
Normal file
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\ParameterBuilder\BigramPhraseFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Elevation;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Faceting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\FieldCollapsing;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Grouping;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Highlighting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Operator;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\PhraseFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\QueryFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\ReturnFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Slops;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Sorting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Sortings;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Spellchecking;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\TrigramPhraseFields;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* The AbstractQueryBuilder contains all logic to initialize solr queries independent from TYPO3.
|
||||
*/
|
||||
abstract class AbstractQueryBuilder {
|
||||
|
||||
/**
|
||||
* @var Query
|
||||
*/
|
||||
protected $queryToBuild = null;
|
||||
|
||||
/**
|
||||
* @param Query $query
|
||||
* @return $this
|
||||
*/
|
||||
public function startFrom(Query $query)
|
||||
{
|
||||
$this->queryToBuild = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery(): Query
|
||||
{
|
||||
return $this->queryToBuild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $omitHeader
|
||||
* @return $this
|
||||
*/
|
||||
public function useOmitHeader($omitHeader = true)
|
||||
{
|
||||
$this->queryToBuild->setOmitHeader($omitHeader);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses an array of filters and applies them to the query.
|
||||
*
|
||||
* @param array $filterArray
|
||||
* @return $this
|
||||
*/
|
||||
public function useFilterArray(array $filterArray)
|
||||
{
|
||||
foreach ($filterArray as $key => $additionalFilter) {
|
||||
$this->useFilter($additionalFilter, $key);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the queryString that is used to search
|
||||
*
|
||||
* @param string $queryString
|
||||
* @return $this
|
||||
*/
|
||||
public function useQueryString($queryString)
|
||||
{
|
||||
$this->queryToBuild->setQuery($queryString);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the passed queryType to the query.
|
||||
*
|
||||
* @param string $queryType
|
||||
* @return $this
|
||||
*/
|
||||
public function useQueryType(string $queryType)
|
||||
{
|
||||
$this->queryToBuild->addParam('qt', $queryType);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the queryType (qt) from the query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeQueryType()
|
||||
{
|
||||
$this->queryToBuild->addParam('qt', null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to remove all sortings from the query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAllSortings()
|
||||
{
|
||||
$this->queryToBuild->clearSorts();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the passed sorting to the query.
|
||||
*
|
||||
* @param Sorting $sorting
|
||||
* @return $this
|
||||
*/
|
||||
public function useSorting(Sorting $sorting)
|
||||
{
|
||||
if (strpos($sorting->getFieldName(), 'relevance') !== false) {
|
||||
$this->removeAllSortings();
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->queryToBuild->addSort($sorting->getFieldName(), $sorting->getDirection());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the passed sorting to the query.
|
||||
*
|
||||
* @param Sortings $sortings
|
||||
* @return $this
|
||||
*/
|
||||
public function useSortings(Sortings $sortings)
|
||||
{
|
||||
foreach($sortings->getSortings() as $sorting) {
|
||||
$this->useSorting($sorting);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $resultsPerPage
|
||||
* @return $this
|
||||
*/
|
||||
public function useResultsPerPage($resultsPerPage)
|
||||
{
|
||||
$this->queryToBuild->setRows($resultsPerPage);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
* @return $this
|
||||
*/
|
||||
public function usePage($page)
|
||||
{
|
||||
$this->queryToBuild->setStart($page);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Operator $operator
|
||||
* @return $this
|
||||
*/
|
||||
public function useOperator(Operator $operator)
|
||||
{
|
||||
$this->queryToBuild->setQueryDefaultOperator( $operator->getOperator());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default query operator.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeOperator()
|
||||
{
|
||||
$this->queryToBuild->setQueryDefaultOperator('');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Slops $slops
|
||||
* @return $this
|
||||
*/
|
||||
public function useSlops(Slops $slops)
|
||||
{
|
||||
return $slops->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the passed boostQuer(y|ies) for the query.
|
||||
*
|
||||
* @param string|array $boostQueries
|
||||
* @return $this
|
||||
*/
|
||||
public function useBoostQueries($boostQueries)
|
||||
{
|
||||
$boostQueryArray = [];
|
||||
if(is_array($boostQueries)) {
|
||||
foreach($boostQueries as $boostQuery) {
|
||||
$boostQueryArray[] = ['key' => md5($boostQuery), 'query' => $boostQuery];
|
||||
}
|
||||
} else {
|
||||
$boostQueryArray[] = ['key' => md5($boostQueries), 'query' => $boostQueries];
|
||||
}
|
||||
|
||||
$this->queryToBuild->getEDisMax()->setBoostQueries($boostQueryArray);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all boost queries from the query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAllBoostQueries()
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->clearBoostQueries();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the passed boostFunction for the query.
|
||||
*
|
||||
* @param string $boostFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function useBoostFunction(string $boostFunction)
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setBoostFunctions($boostFunction);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all previously configured boost functions.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAllBoostFunctions()
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setBoostFunctions('');
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses the passed minimumMatch(mm) for the query.
|
||||
*
|
||||
* @param string $minimumMatch
|
||||
* @return $this
|
||||
*/
|
||||
public function useMinimumMatch(string $minimumMatch)
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setMinimumMatch($minimumMatch);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any previous passed minimumMatch parameter.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeMinimumMatch()
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setMinimumMatch('');
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies the tie parameter to the query.
|
||||
*
|
||||
* @param mixed $tie
|
||||
* @return $this
|
||||
*/
|
||||
public function useTieParameter($tie)
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setTie($tie);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies custom QueryFields to the query.
|
||||
*
|
||||
* @param QueryFields $queryFields
|
||||
* @return $this
|
||||
*/
|
||||
public function useQueryFields(QueryFields $queryFields)
|
||||
{
|
||||
return $queryFields->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies custom ReturnFields to the query.
|
||||
*
|
||||
* @param ReturnFields $returnFields
|
||||
* @return $this
|
||||
*/
|
||||
public function useReturnFields(ReturnFields $returnFields)
|
||||
{
|
||||
return $returnFields->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to use a specific filter string in the solr query.
|
||||
*
|
||||
* @param string $filterString
|
||||
* @param string $filterName
|
||||
* @return $this
|
||||
*/
|
||||
public function useFilter($filterString, $filterName = '')
|
||||
{
|
||||
$filterName = $filterName === '' ? $filterString : $filterName;
|
||||
|
||||
$nameWasPassedAndFilterIsAllreadySet = $filterName !== '' && $this->queryToBuild->getFilterQuery($filterName) !== null;
|
||||
if($nameWasPassedAndFilterIsAllreadySet) {
|
||||
return $this;
|
||||
}
|
||||
$this->queryToBuild->addFilterQuery(['key' => $filterName, 'query' => $filterString]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a filter by the fieldName.
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFilterByFieldName($fieldName)
|
||||
{
|
||||
return $this->removeFilterByFunction(
|
||||
function($key, $query) use ($fieldName) {
|
||||
$queryString = $query->getQuery();
|
||||
$storedFieldName = substr($queryString,0, strpos($queryString, ":"));
|
||||
return $storedFieldName == $fieldName;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a filter by the name of the filter (also known as key).
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFilterByName($name)
|
||||
{
|
||||
return $this->removeFilterByFunction(
|
||||
function($key, $query) use ($name) {
|
||||
$key = $query->getKey();
|
||||
return $key == $name;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a filter by the filter value.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFilterByValue($value)
|
||||
{
|
||||
return $this->removeFilterByFunction(
|
||||
function($key, $query) use ($value) {
|
||||
$query = $query->getQuery();
|
||||
return $query == $value;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure $filterFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFilterByFunction($filterFunction)
|
||||
{
|
||||
$queries = $this->queryToBuild->getFilterQueries();
|
||||
foreach($queries as $key => $query) {
|
||||
$canBeRemoved = $filterFunction($key, $query);
|
||||
if($canBeRemoved) {
|
||||
unset($queries[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queryToBuild->setFilterQueries($queries);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the alternative query to the Query
|
||||
* @param string $query
|
||||
* @return $this
|
||||
*/
|
||||
public function useAlternativeQuery(string $query)
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setQueryAlternative($query);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the alternative query from the Query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAlternativeQuery()
|
||||
{
|
||||
$this->queryToBuild->getEDisMax()->setQueryAlternative(null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom Faceting configuration to the query.
|
||||
*
|
||||
* @param Faceting $faceting
|
||||
* @return $this
|
||||
*/
|
||||
public function useFaceting(Faceting $faceting)
|
||||
{
|
||||
return $faceting->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FieldCollapsing $fieldCollapsing
|
||||
* @return $this
|
||||
*/
|
||||
public function useFieldCollapsing(FieldCollapsing $fieldCollapsing)
|
||||
{
|
||||
return $fieldCollapsing->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom initialized grouping to the query.
|
||||
*
|
||||
* @param Grouping $grouping
|
||||
* @return $this
|
||||
*/
|
||||
public function useGrouping(Grouping $grouping)
|
||||
{
|
||||
return $grouping->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Highlighting $highlighting
|
||||
* @return $this
|
||||
*/
|
||||
public function useHighlighting(Highlighting $highlighting)
|
||||
{
|
||||
return $highlighting->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $debugMode
|
||||
* @return $this
|
||||
*/
|
||||
public function useDebug($debugMode)
|
||||
{
|
||||
if (!$debugMode) {
|
||||
$this->queryToBuild->addParam('debugQuery', null);
|
||||
$this->queryToBuild->addParam('echoParams', null);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->queryToBuild->addParam('debugQuery', 'true');
|
||||
$this->queryToBuild->addParam('echoParams', 'all');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Elevation $elevation
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useElevation(Elevation $elevation)
|
||||
{
|
||||
return $elevation->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Spellchecking $spellchecking
|
||||
* @return $this
|
||||
*/
|
||||
public function useSpellchecking(Spellchecking $spellchecking)
|
||||
{
|
||||
return $spellchecking->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom configured PhraseFields to the query.
|
||||
*
|
||||
* @param PhraseFields $phraseFields
|
||||
* @return $this
|
||||
*/
|
||||
public function usePhraseFields(PhraseFields $phraseFields)
|
||||
{
|
||||
return $phraseFields->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom configured BigramPhraseFields to the query.
|
||||
*
|
||||
* @param BigramPhraseFields $bigramPhraseFields
|
||||
* @return $this
|
||||
*/
|
||||
public function useBigramPhraseFields(BigramPhraseFields $bigramPhraseFields)
|
||||
{
|
||||
return $bigramPhraseFields->build($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a custom configured TrigramPhraseFields to the query.
|
||||
*
|
||||
* @param TrigramPhraseFields $trigramPhraseFields
|
||||
* @return $this
|
||||
*/
|
||||
public function useTrigramPhraseFields(TrigramPhraseFields $trigramPhraseFields)
|
||||
{
|
||||
return $trigramPhraseFields->build($this);
|
||||
}
|
||||
}
|
47
Classes/Domain/Search/Query/ExtractingQuery.php
Normal file
47
Classes/Domain/Search/Query/ExtractingQuery.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-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 Solarium\QueryType\Extract\Query as SolariumExtractQuery;
|
||||
|
||||
/**
|
||||
* Specialized query for content extraction using Solr Cell
|
||||
*
|
||||
*/
|
||||
class ExtractingQuery extends SolariumExtractQuery
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $file Absolute path to the file to extract content and meta data from.
|
||||
*/
|
||||
public function __construct($file)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setFile($file);
|
||||
$this->addParam('extractFormat', 'text');
|
||||
}
|
||||
|
||||
}
|
147
Classes/Domain/Search/Query/Helper/EscapeService.php
Normal file
147
Classes/Domain/Search/Query/Helper/EscapeService.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\Helper;
|
||||
|
||||
/***************************************************************
|
||||
* 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!
|
||||
***************************************************************/
|
||||
|
||||
/**
|
||||
* The EscpaeService is responsible to escape the querystring as expected for Apache Solr.
|
||||
*
|
||||
* This class should have no dependencies since it only contains static functions
|
||||
*
|
||||
* @author Timo Hund <timo.hund@dkd.de>
|
||||
*/
|
||||
class EscapeService {
|
||||
|
||||
/**
|
||||
* Quote and escape search strings
|
||||
*
|
||||
* @param string|int|double $string String to escape
|
||||
* @return string|int|double The escaped/quoted string
|
||||
*/
|
||||
public static function escape($string)
|
||||
{
|
||||
// when we have a numeric string only, nothing needs to be done
|
||||
if (is_numeric($string)) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
// when no whitespaces are in the query we can also just escape the special characters
|
||||
if (preg_match('/\W/', $string) != 1) {
|
||||
return static::escapeSpecialCharacters($string);
|
||||
}
|
||||
|
||||
// when there are no quotes inside the query string we can also just escape the whole string
|
||||
$hasQuotes = strrpos($string, '"') !== false;
|
||||
if (!$hasQuotes) {
|
||||
return static::escapeSpecialCharacters($string);
|
||||
}
|
||||
|
||||
$result = static::tokenizeByQuotesAndEscapeDependingOnContext($string);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies trim and htmlspecialchars on the querystring to use it as output.
|
||||
*
|
||||
* @param mixed $string
|
||||
* @return string
|
||||
*/
|
||||
public static function clean($string): string
|
||||
{
|
||||
$string = trim($string);
|
||||
$string = htmlspecialchars($string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to escape the content in the query string surrounded by quotes
|
||||
* different then when it is not in a quoted context.
|
||||
*
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected static function tokenizeByQuotesAndEscapeDependingOnContext($string)
|
||||
{
|
||||
$result = '';
|
||||
$quotesCount = substr_count($string, '"');
|
||||
$isEvenAmountOfQuotes = $quotesCount % 2 === 0;
|
||||
|
||||
// go over all quote segments and apply escapePhrase inside a quoted
|
||||
// context and escapeSpecialCharacters outside the quoted context.
|
||||
$segments = explode('"', $string);
|
||||
$segmentsIndex = 0;
|
||||
foreach ($segments as $segment) {
|
||||
$isInQuote = $segmentsIndex % 2 !== 0;
|
||||
$isLastQuote = $segmentsIndex === $quotesCount;
|
||||
|
||||
if ($isLastQuote && !$isEvenAmountOfQuotes) {
|
||||
$result .= '\"';
|
||||
}
|
||||
|
||||
if ($isInQuote && !$isLastQuote) {
|
||||
$result .= static::escapePhrase($segment);
|
||||
} else {
|
||||
$result .= static::escapeSpecialCharacters($segment);
|
||||
}
|
||||
|
||||
$segmentsIndex++;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a value meant to be contained in a phrase with characters with
|
||||
* special meanings in Lucene query syntax.
|
||||
*
|
||||
* @param string $value Unescaped - "dirty" - string
|
||||
* @return string Escaped - "clean" - string
|
||||
*/
|
||||
protected static function escapePhrase($value)
|
||||
{
|
||||
$pattern = '/("|\\\)/';
|
||||
$replace = '\\\$1';
|
||||
|
||||
return '"' . preg_replace($pattern, $replace, $value) . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes characters with special meanings in Lucene query syntax.
|
||||
*
|
||||
* @param string $value Unescaped - "dirty" - string
|
||||
* @return string Escaped - "clean" - string
|
||||
*/
|
||||
protected static function escapeSpecialCharacters($value)
|
||||
{
|
||||
// list taken from http://lucene.apache.org/core/4_4_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description
|
||||
// which mentions: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
|
||||
// of which we escape: ( ) { } [ ] ^ " ~ : \ /
|
||||
// and explicitly don't escape: + - && || ! * ?
|
||||
$pattern = '/(\\(|\\)|\\{|\\}|\\[|\\]|\\^|"|~|\:|\\\\|\\/)/';
|
||||
$replace = '\\\$1';
|
||||
|
||||
return preg_replace($pattern, $replace, $value);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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!
|
||||
***************************************************************/
|
||||
abstract class AbstractDeactivatable {
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isEnabled = false;
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsEnabled()
|
||||
{
|
||||
return $this->isEnabled;
|
||||
}
|
||||
/**
|
||||
* @param boolean $isEnabled
|
||||
*/
|
||||
public function setIsEnabled($isEnabled)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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;
|
||||
|
||||
/**
|
||||
* The AbstractFieldList class
|
||||
*/
|
||||
abstract class AbstractFieldList extends AbstractDeactivatable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldList = [];
|
||||
|
||||
/**
|
||||
* Parameter key which should be used for Apache Solr URL query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parameterKey = '';
|
||||
|
||||
/**
|
||||
* FieldList parameter builder constructor.
|
||||
*
|
||||
* @param array $fieldList
|
||||
*/
|
||||
public function __construct($isEnabled, array $fieldList = [])
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->fieldList = $fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return array
|
||||
*/
|
||||
protected static function buildFieldList(string $fieldListString, string $delimiter):array
|
||||
{
|
||||
$fields = GeneralUtility::trimExplode($delimiter, $fieldListString, true);
|
||||
$fieldList = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$fieldNameAndBoost = explode('^', $field);
|
||||
|
||||
$boost = 1.0;
|
||||
if (isset($fieldNameAndBoost[1])) {
|
||||
$boost = floatval($fieldNameAndBoost[1]);
|
||||
}
|
||||
|
||||
$fieldName = $fieldNameAndBoost[0];
|
||||
$fieldList[$fieldName] = $boost;
|
||||
}
|
||||
return $fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
* @param float $boost
|
||||
*
|
||||
* @return AbstractFieldList
|
||||
*/
|
||||
public function add(string $fieldName, float $boost = 1.0): AbstractFieldList
|
||||
{
|
||||
$this->fieldList[$fieldName] = (float)$boost;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the string representation
|
||||
*
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
public function toString(string $delimiter = ' ')
|
||||
{
|
||||
$fieldListString = '';
|
||||
|
||||
foreach ($this->fieldList as $fieldName => $fieldBoost) {
|
||||
$fieldListString .= $fieldName;
|
||||
|
||||
if ($fieldBoost != 1.0) {
|
||||
$fieldListString .= '^' . number_format($fieldBoost, 1, '.', '');
|
||||
}
|
||||
|
||||
$fieldListString .= $delimiter;
|
||||
}
|
||||
|
||||
return rtrim($fieldListString, $delimiter);
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The BigramPhraseFields class
|
||||
*/
|
||||
class BigramPhraseFields extends AbstractFieldList implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* Parameter key which should be used for Apache Solr URL query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parameterKey = 'pf2';
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return BigramPhraseFields
|
||||
*/
|
||||
public static function fromString(string $fieldListString, string $delimiter = ',') : BigramPhraseFields
|
||||
{
|
||||
return self::initializeFromString($fieldListString, $delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return BigramPhraseFields
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getBigramPhraseSearchIsEnabled();
|
||||
if (!$isEnabled) {
|
||||
return new BigramPhraseFields(false);
|
||||
}
|
||||
|
||||
return self::fromString((string)$solrConfiguration->getSearchQueryBigramPhraseFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return BigramPhraseFields
|
||||
*/
|
||||
protected static function initializeFromString(string $fieldListString, string $delimiter = ',') : BigramPhraseFields
|
||||
{
|
||||
$fieldList = self::buildFieldList($fieldListString, $delimiter);
|
||||
return new BigramPhraseFields(true, $fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$bigramPhraseFieldsString = $this->toString();
|
||||
if ($bigramPhraseFieldsString === '' || !$this->getIsEnabled()) {
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$parentBuilder->getQuery()->getEDisMax()->setPhraseBigramFields($bigramPhraseFieldsString);
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
139
Classes/Domain/Search/Query/ParameterBuilder/Elevation.php
Normal file
139
Classes/Domain/Search/Query/ParameterBuilder/Elevation.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2018 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The Elevation ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the elevation.
|
||||
*/
|
||||
class Elevation extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isForced = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $markElevatedResults = true;
|
||||
|
||||
/**
|
||||
* Elevation constructor.
|
||||
* @param boolean $isEnabled
|
||||
* @param boolean $isForced
|
||||
* @param boolean $markElevatedResults
|
||||
*/
|
||||
public function __construct($isEnabled = false, $isForced = true, $markElevatedResults = true)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->isForced = $isForced;
|
||||
$this->markElevatedResults = $markElevatedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsForced(): bool
|
||||
{
|
||||
return $this->isForced;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $isForced
|
||||
*/
|
||||
public function setIsForced(bool $isForced)
|
||||
{
|
||||
$this->isForced = $isForced;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getMarkElevatedResults(): bool
|
||||
{
|
||||
return $this->markElevatedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $markElevatedResults
|
||||
*/
|
||||
public function setMarkElevatedResults(bool $markElevatedResults)
|
||||
{
|
||||
$this->markElevatedResults = $markElevatedResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Elevation
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchElevation();
|
||||
if (!$isEnabled) {
|
||||
return new Elevation(false);
|
||||
}
|
||||
|
||||
$force = $solrConfiguration->getSearchElevationForceElevation();
|
||||
$markResults = $solrConfiguration->getSearchElevationMarkElevatedResults();
|
||||
return new Elevation(true, $force, $markResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Elevation
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Elevation(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if (!$this->getIsEnabled()) {
|
||||
$query->addParam('enableElevation', null);
|
||||
$query->addParam('forceElevation', null);
|
||||
$query->removeField('isElevated:[elevated]');
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$query->addParam('enableElevation', 'true');
|
||||
$forceElevationString = $this->getIsForced() ? 'true' : 'false';
|
||||
$query->addParam('forceElevation', $forceElevationString);
|
||||
|
||||
if ($this->getMarkElevatedResults()) {
|
||||
$query->addField('isElevated:[elevated]');
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
282
Classes/Domain/Search/Query/ParameterBuilder/Faceting.php
Normal file
282
Classes/Domain/Search/Query/ParameterBuilder/Faceting.php
Normal file
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\QueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\ResultSet\Facets\SortingExpression;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The Faceting ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the highlighting.
|
||||
*/
|
||||
class Faceting extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sorting = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $minCount = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 10;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $additionalParameters = [];
|
||||
|
||||
/**
|
||||
* Faceting constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param string $sorting
|
||||
* @param int $minCount
|
||||
* @param int $limit
|
||||
* @param array $fields
|
||||
* @param array $additionalParameters
|
||||
*/
|
||||
public function __construct($isEnabled, $sorting = '', $minCount = 1, $limit = 10, $fields = [], $additionalParameters = [])
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->sorting = $sorting;
|
||||
$this->minCount = $minCount;
|
||||
$this->limit = $limit;
|
||||
$this->fields = $fields;
|
||||
$this->additionalParameters = $additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSorting()
|
||||
{
|
||||
return $this->sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sorting
|
||||
*/
|
||||
public function setSorting($sorting)
|
||||
{
|
||||
$this->sorting = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMinCount()
|
||||
{
|
||||
return $this->minCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $minCount
|
||||
*/
|
||||
public function setMinCount($minCount)
|
||||
{
|
||||
$this->minCount = $minCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLimit()
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $limit
|
||||
*/
|
||||
public function setLimit($limit)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*/
|
||||
public function setFields(array $fields)
|
||||
{
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
*/
|
||||
public function addField($fieldName)
|
||||
{
|
||||
$this->fields[] = $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalParameters(): array
|
||||
{
|
||||
return $this->additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $additionalParameters
|
||||
*/
|
||||
public function setAdditionalParameters(array $additionalParameters)
|
||||
{
|
||||
$this->additionalParameters = $additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $value
|
||||
*/
|
||||
public function addAdditionalParameter($key, $value)
|
||||
{
|
||||
$this->additionalParameters[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the facet sorting configuration and applies it to the queryParameters.
|
||||
*
|
||||
* @param array $facetParameters
|
||||
* @return array
|
||||
*/
|
||||
protected function applySorting(array $facetParameters)
|
||||
{
|
||||
$sortingExpression = new SortingExpression();
|
||||
$globalSortingExpression = $sortingExpression->getForFacet($this->sorting);
|
||||
|
||||
if (!empty($globalSortingExpression)) {
|
||||
$facetParameters['facet.sort'] = $globalSortingExpression;
|
||||
}
|
||||
|
||||
return $facetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Faceting
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchFaceting();
|
||||
if (!$isEnabled) {
|
||||
return new Faceting(false);
|
||||
}
|
||||
|
||||
$minCount = $solrConfiguration->getSearchFacetingMinimumCount();
|
||||
$limit = $solrConfiguration->getSearchFacetingFacetLimit();
|
||||
$sorting = $solrConfiguration->getSearchFacetingSortBy();
|
||||
|
||||
return new Faceting($isEnabled, $sorting, $minCount, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Faceting
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Faceting(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all parameters that are required for faceting.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFacetParameters() {
|
||||
$facetParameters = [];
|
||||
$facetParameters['facet'] = 'true';
|
||||
$facetParameters['facet.mincount'] = $this->getMinCount();
|
||||
$facetParameters['facet.limit'] = $this->getLimit();
|
||||
$facetParameters['facet.field'] = $this->getFields();
|
||||
|
||||
foreach ($this->getAdditionalParameters() as $additionalParameterKey => $additionalParameterValue) {
|
||||
$facetParameters[$additionalParameterKey] = $additionalParameterValue;
|
||||
}
|
||||
|
||||
if ($facetParameters['json.facet']) {
|
||||
$facetParameters['json.facet'] = json_encode($facetParameters['json.facet']);
|
||||
}
|
||||
|
||||
$facetParameters = $this->applySorting($facetParameters);
|
||||
return $facetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if (!$this->getIsEnabled()) {
|
||||
//@todo use unset functionality when present
|
||||
$query->addParam('facet', null);
|
||||
$query->addParam('lex', null);
|
||||
$query->addParam('json.mincount', null);
|
||||
$query->addParam('json.limit', null);
|
||||
$query->addParam('json.field', null);
|
||||
$query->addParam('facet.sort', null);
|
||||
|
||||
$params = $query->getParams();
|
||||
foreach($params as $key => $value) {
|
||||
if (strpos($key, 'f.') !== false) {
|
||||
$query->addParam($key, null);
|
||||
}
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
//@todo check of $this->queryToBuilder->getFacetSet() can be used
|
||||
$facetingParameters = $this->getFacetParameters();
|
||||
foreach($facetingParameters as $key => $value) {
|
||||
$query->addParam($key, $value);
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
159
Classes/Domain/Search/Query/ParameterBuilder/FieldCollapsing.php
Normal file
159
Classes/Domain/Search/Query/ParameterBuilder/FieldCollapsing.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The FieldCollapsing ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the field collapsing.
|
||||
*/
|
||||
class FieldCollapsing extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $collapseFieldName = 'variantId';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $expand = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $expandRowCount = 10;
|
||||
|
||||
/**
|
||||
* FieldCollapsing constructor.
|
||||
* @param bool $isEnabled
|
||||
* @param string $collapseFieldName
|
||||
* @param bool $expand
|
||||
* @param int $expandRowCount
|
||||
*/
|
||||
public function __construct($isEnabled, $collapseFieldName = 'variantId', $expand = false, $expandRowCount = 10)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->collapseFieldName = $collapseFieldName;
|
||||
$this->expand = $expand;
|
||||
$this->expandRowCount = $expandRowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCollapseFieldName(): string
|
||||
{
|
||||
return $this->collapseFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $collapseFieldName
|
||||
*/
|
||||
public function setCollapseFieldName(string $collapseFieldName)
|
||||
{
|
||||
$this->collapseFieldName = $collapseFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getIsExpand(): bool
|
||||
{
|
||||
return $this->expand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $expand
|
||||
*/
|
||||
public function setExpand(bool $expand)
|
||||
{
|
||||
$this->expand = $expand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getExpandRowCount(): int
|
||||
{
|
||||
return $this->expandRowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $expandRowCount
|
||||
*/
|
||||
public function setExpandRowCount(int $expandRowCount)
|
||||
{
|
||||
$this->expandRowCount = $expandRowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return FieldCollapsing
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchVariants();
|
||||
if (!$isEnabled) {
|
||||
return new FieldCollapsing(false);
|
||||
}
|
||||
|
||||
$collapseField = $solrConfiguration->getSearchVariantsField();
|
||||
$expand = (bool)$solrConfiguration->getSearchVariantsExpand();
|
||||
$expandRows = $solrConfiguration->getSearchVariantsLimit();
|
||||
|
||||
return new FieldCollapsing(true, $collapseField, $expand, $expandRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FieldCollapsing
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new FieldCollapsing(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if(!$this->getIsEnabled()) {
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$parentBuilder->useFilter('{!collapse field=' . $this->getCollapseFieldName(). '}', 'fieldCollapsing');
|
||||
if($this->getIsExpand()) {
|
||||
$query->addParam('expand', 'true');
|
||||
$query->addParam('expand.rows', $this->getExpandRowCount());
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
158
Classes/Domain/Search/Query/ParameterBuilder/Filters.php
Normal file
158
Classes/Domain/Search/Query/ParameterBuilder/Filters.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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 TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The Filters ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the filtering.
|
||||
*/
|
||||
class Filters
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Removes a filter on a field
|
||||
*
|
||||
* @param string $filterFieldName The field name the filter should be removed for
|
||||
* @return void
|
||||
*/
|
||||
public function removeByFieldName($filterFieldName)
|
||||
{
|
||||
$this->removeByPrefix($filterFieldName . ':');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filterFieldName
|
||||
*/
|
||||
public function removeByPrefix($filterFieldName)
|
||||
{
|
||||
foreach ($this->filters as $key => $filterString) {
|
||||
if (GeneralUtility::isFirstPartOfStr($filterString, $filterFieldName)) {
|
||||
unset($this->filters[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a filter based on name of filter array
|
||||
*
|
||||
* @param string $name name of the filter
|
||||
*/
|
||||
public function removeByName($name)
|
||||
{
|
||||
unset($this->filters[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $filterString
|
||||
* @param string $name
|
||||
*/
|
||||
public function add($filterString, $name = '')
|
||||
{
|
||||
if ($name !== '') {
|
||||
$this->filters[$name] = $filterString;
|
||||
} else {
|
||||
$this->filters[] = $filterString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add's multiple filters to the filter collection.
|
||||
*
|
||||
* @param array $filterArray
|
||||
* @return Filters
|
||||
*/
|
||||
public function addMultiple($filterArray)
|
||||
{
|
||||
foreach($filterArray as $key => $value) {
|
||||
if (!$this->hasWithName($key)) {
|
||||
$this->add($value, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasWithName($name)
|
||||
{
|
||||
return array_key_exists($name, $this->filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a filter by the filter value. The value has the following format:
|
||||
*
|
||||
* "fieldname:value"
|
||||
*
|
||||
* @param string $filterString The filter to remove, in the form of field:value
|
||||
*/
|
||||
public function removeByValue($filterString)
|
||||
{
|
||||
$key = array_search($filterString, $this->filters);
|
||||
if ($key === false) {
|
||||
// value not found, nothing to do
|
||||
return;
|
||||
}
|
||||
unset($this->filters[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all currently applied filters.
|
||||
*
|
||||
* @return array Array of filters
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Filters
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
return new Filters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Filters
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Filters();
|
||||
}
|
||||
}
|
254
Classes/Domain/Search/Query/ParameterBuilder/Grouping.php
Normal file
254
Classes/Domain/Search/Query/ParameterBuilder/Grouping.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The Grouping ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the grouping.
|
||||
*/
|
||||
class Grouping extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fields = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sortings = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queries = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $numberOfGroups = 5;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $resultsPerGroup = 1;
|
||||
|
||||
/**
|
||||
* Grouping constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param array $fields
|
||||
* @param array $sortings
|
||||
* @param array $queries
|
||||
* @param int $numberOfGroups
|
||||
* @param int $resultsPerGroup
|
||||
*/
|
||||
public function __construct($isEnabled, array $fields = [], array $sortings = [], array $queries = [], $numberOfGroups = 5, $resultsPerGroup = 1)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->fields = $fields;
|
||||
$this->sortings = $sortings;
|
||||
$this->queries = $queries;
|
||||
$this->numberOfGroups = $numberOfGroups;
|
||||
$this->resultsPerGroup = $resultsPerGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFields()
|
||||
{
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*/
|
||||
public function setFields(array $fields)
|
||||
{
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*/
|
||||
public function addField(string $field)
|
||||
{
|
||||
$this->fields[] = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSortings()
|
||||
{
|
||||
return $this->sortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sorting
|
||||
*/
|
||||
public function addSorting($sorting)
|
||||
{
|
||||
$this->sortings[] = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sortings
|
||||
*/
|
||||
public function setSortings(array $sortings)
|
||||
{
|
||||
$this->sortings = $sortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getQueries(): array
|
||||
{
|
||||
return $this->queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*/
|
||||
public function addQuery($query)
|
||||
{
|
||||
$this->queries[] = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $queries
|
||||
*/
|
||||
public function setQueries(array $queries)
|
||||
{
|
||||
$this->queries = $queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNumberOfGroups()
|
||||
{
|
||||
return $this->numberOfGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $numberOfGroups
|
||||
*/
|
||||
public function setNumberOfGroups($numberOfGroups)
|
||||
{
|
||||
$this->numberOfGroups = $numberOfGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getResultsPerGroup()
|
||||
{
|
||||
return $this->resultsPerGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $resultsPerGroup
|
||||
*/
|
||||
public function setResultsPerGroup($resultsPerGroup)
|
||||
{
|
||||
$resultsPerGroup = max(intval($resultsPerGroup), 0);
|
||||
$this->resultsPerGroup = $resultsPerGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Grouping
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchGrouping();
|
||||
if (!$isEnabled) {
|
||||
return new Grouping(false);
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$queries = [];
|
||||
$sortings = [];
|
||||
|
||||
$resultsPerGroup = $solrConfiguration->getSearchGroupingHighestGroupResultsLimit();
|
||||
$configuredGroups = $solrConfiguration->getSearchGroupingGroupsConfiguration();
|
||||
$numberOfGroups = $solrConfiguration->getSearchGroupingNumberOfGroups();
|
||||
$sortBy = $solrConfiguration->getSearchGroupingSortBy();
|
||||
|
||||
foreach ($configuredGroups as $groupName => $groupConfiguration) {
|
||||
if (isset($groupConfiguration['field'])) {
|
||||
$fields[] = $groupConfiguration['field'];
|
||||
} elseif (isset($groupConfiguration['query'])) {
|
||||
$queries[] = $groupConfiguration['query'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty(trim($sortBy))) {
|
||||
$sortings[] = $sortBy;
|
||||
}
|
||||
|
||||
return new Grouping($isEnabled, $fields, $sortings, $queries, $numberOfGroups, $resultsPerGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Grouping
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Grouping(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if(!$this->getIsEnabled()) {
|
||||
$query->removeComponent($query->getGrouping());
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$query->getGrouping()->setFields($this->getFields());
|
||||
$query->getGrouping()->setLimit($this->getResultsPerGroup());
|
||||
$query->getGrouping()->setQueries($this->getQueries());
|
||||
$query->getGrouping()->setFormat('grouped');
|
||||
$query->getGrouping()->setNumberOfGroups(true);
|
||||
|
||||
$query->setRows($this->getNumberOfGroups());
|
||||
|
||||
$sorting = implode(' ', $this->getSortings());
|
||||
$query->getGrouping()->setSort($sorting);
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
211
Classes/Domain/Search/Query/ParameterBuilder/Highlighting.php
Normal file
211
Classes/Domain/Search/Query/ParameterBuilder/Highlighting.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\QueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The Highlighting ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the highlighting.
|
||||
*/
|
||||
class Highlighting extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $fragmentSize = 200;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $highlightingFieldList = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $postfix = '';
|
||||
|
||||
/**
|
||||
* Highlighting constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param int $fragmentSize
|
||||
* @param string $highlightingFieldList
|
||||
* @param string $prefix
|
||||
* @param string $postfix
|
||||
*/
|
||||
public function __construct($isEnabled = false, $fragmentSize = 200, $highlightingFieldList = '', $prefix = '', $postfix = '')
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->fragmentSize = $fragmentSize;
|
||||
$this->highlightingFieldList = $highlightingFieldList;
|
||||
$this->prefix = $prefix;
|
||||
$this->postfix = $postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFragmentSize(): int
|
||||
{
|
||||
return $this->fragmentSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fragmentSize
|
||||
*/
|
||||
public function setFragmentSize(int $fragmentSize)
|
||||
{
|
||||
$this->fragmentSize = $fragmentSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHighlightingFieldList(): string
|
||||
{
|
||||
return $this->highlightingFieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $highlightingFieldList
|
||||
*/
|
||||
public function setHighlightingFieldList(string $highlightingFieldList)
|
||||
{
|
||||
$this->highlightingFieldList = $highlightingFieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefix(): string
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function setPrefix(string $prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPostfix(): string
|
||||
{
|
||||
return $this->postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $postfix
|
||||
*/
|
||||
public function setPostfix(string $postfix)
|
||||
{
|
||||
$this->postfix = $postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseFastVectorHighlighter()
|
||||
{
|
||||
return ($this->fragmentSize >= 18);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Highlighting
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchResultsHighlighting();
|
||||
if (!$isEnabled) {
|
||||
return new Highlighting(false);
|
||||
}
|
||||
|
||||
$fragmentSize = $solrConfiguration->getSearchResultsHighlightingFragmentSize();
|
||||
$highlightingFields = $solrConfiguration->getSearchResultsHighlightingFields();
|
||||
$wrap = explode('|', $solrConfiguration->getSearchResultsHighlightingWrap());
|
||||
$prefix = isset($wrap[0]) ? $wrap[0] : '';
|
||||
$postfix = isset($wrap[1]) ? $wrap[1] : '';
|
||||
|
||||
|
||||
return new Highlighting($isEnabled, $fragmentSize, $highlightingFields, $prefix, $postfix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Highlighting
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Highlighting(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if(!$this->getIsEnabled()) {
|
||||
$query->removeComponent($query->getHighlighting());
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$query->getHighlighting()->setFragSize($this->getFragmentSize());
|
||||
$query->getHighlighting()->setFields(GeneralUtility::trimExplode(",", $this->getHighlightingFieldList()));
|
||||
|
||||
if ($this->getUseFastVectorHighlighter()) {
|
||||
$query->getHighlighting()->setUseFastVectorHighlighter(true);
|
||||
$query->getHighlighting()->setTagPrefix($this->getPrefix());
|
||||
$query->getHighlighting()->setTagPostfix($this->getPostfix());
|
||||
} else {
|
||||
$query->getHighlighting()->setUseFastVectorHighlighter(false);
|
||||
$query->getHighlighting()->setTagPrefix('');
|
||||
$query->getHighlighting()->setTagPostfix('');
|
||||
}
|
||||
|
||||
if ($this->getPrefix() !== '' && $this->getPostfix() !== '') {
|
||||
$query->getHighlighting()->setSimplePrefix($this->getPrefix());
|
||||
$query->getHighlighting()->setSimplePostfix($this->getPostfix());
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
106
Classes/Domain/Search/Query/ParameterBuilder/Operator.php
Normal file
106
Classes/Domain/Search/Query/ParameterBuilder/Operator.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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!
|
||||
***************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* The Operator ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the operator q.op.
|
||||
*/
|
||||
class Operator extends AbstractDeactivatable
|
||||
{
|
||||
const OPERATOR_AND = 'AND';
|
||||
const OPERATOR_OR = 'OR';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $operator = 'AND';
|
||||
|
||||
/**
|
||||
* Faceting constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param string $operator
|
||||
*/
|
||||
public function __construct($isEnabled, $operator = Operator::OPERATOR_AND)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->setOperator($operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
*/
|
||||
public function setOperator($operator)
|
||||
{
|
||||
if (!in_array($operator, [self::OPERATOR_AND, self::OPERATOR_OR])) {
|
||||
throw new \InvalidArgumentException("Invalid operator");
|
||||
}
|
||||
|
||||
$this->operator = $operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator(): string
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Operator
|
||||
*/
|
||||
public static function getEmpty(): Operator
|
||||
{
|
||||
return new Operator(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Operator
|
||||
*/
|
||||
public static function getAnd(): Operator
|
||||
{
|
||||
return new Operator(true, static::OPERATOR_AND);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Operator
|
||||
*/
|
||||
public static function getOr(): Operator
|
||||
{
|
||||
return new Operator(true, static::OPERATOR_OR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @return Operator
|
||||
*/
|
||||
public static function fromString($operator)
|
||||
{
|
||||
return new Operator(true, $operator);
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
|
||||
/**
|
||||
* The implementation of ParameterBuilder is responsible to build an array with
|
||||
* the query parameter that are needed for solr
|
||||
*
|
||||
* Interface ParameterProvider
|
||||
*/
|
||||
interface ParameterBuilder {
|
||||
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder;
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The PhraseFields class
|
||||
*/
|
||||
class PhraseFields extends AbstractFieldList implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* Parameter key which should be used for Apache Solr URL query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parameterKey = 'pf';
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return PhraseFields
|
||||
*/
|
||||
public static function fromString(string $fieldListString, string $delimiter = ',') : PhraseFields
|
||||
{
|
||||
return self::initializeFromString($fieldListString, $delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return PhraseFields
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getPhraseSearchIsEnabled();
|
||||
if (!$isEnabled) {
|
||||
return new PhraseFields(false);
|
||||
}
|
||||
|
||||
return self::fromString((string)$solrConfiguration->getSearchQueryPhraseFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return PhraseFields
|
||||
*/
|
||||
protected static function initializeFromString(string $fieldListString, string $delimiter = ',') : PhraseFields
|
||||
{
|
||||
$fieldList = self::buildFieldList($fieldListString, $delimiter);
|
||||
return new PhraseFields(true, $fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$phraseFieldString = $this->toString();
|
||||
if ($phraseFieldString === '' || !$this->getIsEnabled()) {
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$parentBuilder->getQuery()->getEDisMax()->setPhraseFields($phraseFieldString);
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
117
Classes/Domain/Search/Query/ParameterBuilder/QueryFields.php
Normal file
117
Classes/Domain/Search/Query/ParameterBuilder/QueryFields.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The QueryFields class holds all information for the query which fields should be used to query (Solr qf parameter).
|
||||
*/
|
||||
class QueryFields implements ParameterBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queryFields = [];
|
||||
|
||||
/**
|
||||
* QueryFields constructor.
|
||||
*
|
||||
* @param array $queryFields
|
||||
*/
|
||||
public function __construct(array $queryFields = [])
|
||||
{
|
||||
$this->queryFields = $queryFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
* @param float $boost
|
||||
*/
|
||||
public function set($fieldName, $boost = 1.0)
|
||||
{
|
||||
$this->queryFields[$fieldName] = (float)$boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the string representation
|
||||
*
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
public function toString($delimiter = ' ') {
|
||||
$queryFieldString = '';
|
||||
|
||||
foreach ($this->queryFields as $fieldName => $fieldBoost) {
|
||||
$queryFieldString .= $fieldName;
|
||||
|
||||
if ($fieldBoost != 1.0) {
|
||||
$queryFieldString .= '^' . number_format($fieldBoost, 1, '.', '');
|
||||
}
|
||||
|
||||
$queryFieldString .= $delimiter;
|
||||
}
|
||||
|
||||
return rtrim($queryFieldString, $delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string representation of the queryFields (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $queryFieldsString
|
||||
* @param string $delimiter
|
||||
* @return QueryFields
|
||||
*/
|
||||
public static function fromString($queryFieldsString, $delimiter = ',') {
|
||||
$fields = GeneralUtility::trimExplode($delimiter, $queryFieldsString, true);
|
||||
$queryFields = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$fieldNameAndBoost = explode('^', $field);
|
||||
|
||||
$boost = 1.0;
|
||||
if (isset($fieldNameAndBoost[1])) {
|
||||
$boost = floatval($fieldNameAndBoost[1]);
|
||||
}
|
||||
|
||||
$fieldName = $fieldNameAndBoost[0];
|
||||
$queryFields[$fieldName] = $boost;
|
||||
}
|
||||
|
||||
return new QueryFields($queryFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$parentBuilder->getQuery()->getEDisMax()->setQueryFields($this->toString());
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
128
Classes/Domain/Search/Query/ParameterBuilder/ReturnFields.php
Normal file
128
Classes/Domain/Search/Query/ParameterBuilder/ReturnFields.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* The ReturnFields class is responsible to hold a list of field names that should be returned from
|
||||
* solr.
|
||||
*/
|
||||
class ReturnFields implements ParameterBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldList = [];
|
||||
|
||||
/**
|
||||
* FieldList constructor.
|
||||
*
|
||||
* @param array $fieldList
|
||||
*/
|
||||
public function __construct(array $fieldList = [])
|
||||
{
|
||||
$this->fieldList = $fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the list of fields to return. Also checks whether * is
|
||||
* set for the fields, if so it's removed from the field list.
|
||||
*
|
||||
* @param string $fieldName Name of a field to return in the result documents
|
||||
*/
|
||||
public function add($fieldName)
|
||||
{
|
||||
if (strpos($fieldName, '[') === false && strpos($fieldName, ']') === false && in_array('*', $this->fieldList)) {
|
||||
$this->fieldList = array_diff($this->fieldList, ['*']);
|
||||
}
|
||||
|
||||
$this->fieldList[] = $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a field from the list of fields to return (fl parameter).
|
||||
*
|
||||
* @param string $fieldName Field to remove from the list of fields to return
|
||||
*/
|
||||
public function remove($fieldName)
|
||||
{
|
||||
$key = array_search($fieldName, $this->fieldList);
|
||||
|
||||
if ($key !== false) {
|
||||
unset($this->fieldList[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $delimiter
|
||||
* @return string
|
||||
*/
|
||||
public function toString($delimiter = ',')
|
||||
{
|
||||
return implode($delimiter, $this->fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldList
|
||||
* @param string $delimiter
|
||||
* @return ReturnFields
|
||||
*/
|
||||
public static function fromString($fieldList, $delimiter = ',')
|
||||
{
|
||||
$fieldListArray = GeneralUtility::trimExplode($delimiter, $fieldList);
|
||||
return static::fromArray($fieldListArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fieldListArray
|
||||
* @return ReturnFields
|
||||
*/
|
||||
public static function fromArray(array $fieldListArray)
|
||||
{
|
||||
return new ReturnFields($fieldListArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return array_unique(array_values($this->fieldList));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$parentBuilder->getQuery()->setFields($this->getValues());
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
257
Classes/Domain/Search/Query/ParameterBuilder/Slops.php
Normal file
257
Classes/Domain/Search/Query/ParameterBuilder/Slops.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The Slops ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the several slop arguments.
|
||||
*/
|
||||
class Slops implements ParameterBuilder
|
||||
{
|
||||
const NO_SLOP = null;
|
||||
|
||||
/**
|
||||
* The qs parameter
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $querySlop = self::NO_SLOP;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $phraseSlop = self::NO_SLOP;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $bigramPhraseSlop = self::NO_SLOP;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $trigramPhraseSlop = self::NO_SLOP;
|
||||
|
||||
/**
|
||||
* Slops constructor.
|
||||
* @param int|null $querySlop
|
||||
* @param int|null $phraseSlop
|
||||
* @param int|null $bigramPhraseSlop
|
||||
* @param int|null $trigramPhraseSlop
|
||||
*/
|
||||
public function __construct($querySlop = self::NO_SLOP, $phraseSlop = self::NO_SLOP, $bigramPhraseSlop = self::NO_SLOP, $trigramPhraseSlop = self::NO_SLOP)
|
||||
{
|
||||
$this->querySlop = $querySlop;
|
||||
$this->phraseSlop = $phraseSlop;
|
||||
$this->bigramPhraseSlop = $bigramPhraseSlop;
|
||||
$this->trigramPhraseSlop = $trigramPhraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasQuerySlop()
|
||||
{
|
||||
return $this->querySlop !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getQuerySlop()
|
||||
{
|
||||
return $this->querySlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $querySlop
|
||||
*/
|
||||
public function setQuerySlop(int $querySlop)
|
||||
{
|
||||
$this->querySlop = $querySlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasPhraseSlop()
|
||||
{
|
||||
return $this->phraseSlop !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPhraseSlop()
|
||||
{
|
||||
return $this->phraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $phraseSlop
|
||||
*/
|
||||
public function setPhraseSlop(int $phraseSlop)
|
||||
{
|
||||
$this->phraseSlop = $phraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasBigramPhraseSlop()
|
||||
{
|
||||
return $this->bigramPhraseSlop !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getBigramPhraseSlop()
|
||||
{
|
||||
return $this->bigramPhraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $bigramPhraseSlop
|
||||
*/
|
||||
public function setBigramPhraseSlop(int $bigramPhraseSlop)
|
||||
{
|
||||
$this->bigramPhraseSlop = $bigramPhraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasTrigramPhraseSlop()
|
||||
{
|
||||
return $this->trigramPhraseSlop !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getTrigramPhraseSlop()
|
||||
{
|
||||
return $this->trigramPhraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $trigramPhraseSlop
|
||||
*/
|
||||
public function setTrigramPhraseSlop(int $trigramPhraseSlop)
|
||||
{
|
||||
$this->trigramPhraseSlop = $trigramPhraseSlop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Slops
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$searchConfiguration = $solrConfiguration->getSearchConfiguration();
|
||||
$querySlop = static::getQuerySlopFromConfiguration($searchConfiguration);
|
||||
$phraseSlop = static::getPhraseSlopFromConfiguration($searchConfiguration);
|
||||
$bigramPhraseSlop = static::getBigramPhraseSlopFromConfiguration($searchConfiguration);
|
||||
$trigramPhraseSlop = static::getTrigramPhraseSlopFromConfiguration($searchConfiguration);
|
||||
return new Slops($querySlop, $phraseSlop, $bigramPhraseSlop, $trigramPhraseSlop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $searchConfiguration
|
||||
* @return int|null
|
||||
*/
|
||||
protected static function getPhraseSlopFromConfiguration($searchConfiguration)
|
||||
{
|
||||
$phraseEnabled = !(empty($searchConfiguration['query.']['phrase']) || $searchConfiguration['query.']['phrase'] !== 1);
|
||||
$phraseSlopConfigured = !empty($searchConfiguration['query.']['phrase.']['slop']);
|
||||
return ($phraseEnabled && $phraseSlopConfigured) ? $searchConfiguration['query.']['phrase.']['slop'] : self::NO_SLOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $searchConfiguration
|
||||
* @return int|null
|
||||
*/
|
||||
protected static function getQuerySlopFromConfiguration($searchConfiguration)
|
||||
{
|
||||
$phraseEnabled = !(empty($searchConfiguration['query.']['phrase']) || $searchConfiguration['query.']['phrase'] !== 1);
|
||||
$querySlopConfigured = !empty($searchConfiguration['query.']['phrase.']['querySlop']);
|
||||
return ($phraseEnabled && $querySlopConfigured) ? $searchConfiguration['query.']['phrase.']['querySlop'] : self::NO_SLOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $searchConfiguration
|
||||
* @return int|null
|
||||
*/
|
||||
protected static function getBigramPhraseSlopFromConfiguration($searchConfiguration)
|
||||
{
|
||||
$bigramPhraseEnabled = !empty($searchConfiguration['query.']['bigramPhrase']) && $searchConfiguration['query.']['bigramPhrase'] === 1;
|
||||
$bigramSlopConfigured = !empty($searchConfiguration['query.']['bigramPhrase.']['slop']);
|
||||
return ($bigramPhraseEnabled && $bigramSlopConfigured) ? $searchConfiguration['query.']['bigramPhrase.']['slop'] : self::NO_SLOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $searchConfiguration
|
||||
* @return int|null
|
||||
*/
|
||||
protected static function getTrigramPhraseSlopFromConfiguration($searchConfiguration)
|
||||
{
|
||||
$trigramPhraseEnabled = !empty($searchConfiguration['query.']['trigramPhrase']) && $searchConfiguration['query.']['trigramPhrase'] === 1;
|
||||
$trigramSlopConfigured = !empty($searchConfiguration['query.']['trigramPhrase.']['slop']);
|
||||
return ($trigramPhraseEnabled && $trigramSlopConfigured) ? $searchConfiguration['query.']['trigramPhrase.']['slop'] : self::NO_SLOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
|
||||
if ($this->getHasPhraseSlop()) {
|
||||
$query->getEDisMax()->setPhraseSlop($this->getPhraseSlop());
|
||||
}
|
||||
|
||||
if ($this->getHasBigramPhraseSlop()) {
|
||||
$query->getEDisMax()->setPhraseBigramSlop($this->getBigramPhraseSlop());
|
||||
}
|
||||
|
||||
if ($this->getHasTrigramPhraseSlop()) {
|
||||
$query->getEDisMax()->setPhraseTrigramSlop($this->getTrigramPhraseSlop());
|
||||
}
|
||||
|
||||
if ($this->getHasQuerySlop()) {
|
||||
$query->getEDisMax()->setQueryPhraseSlop($this->getQuerySlop());
|
||||
}
|
||||
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
112
Classes/Domain/Search/Query/ParameterBuilder/Sorting.php
Normal file
112
Classes/Domain/Search/Query/ParameterBuilder/Sorting.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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;
|
||||
|
||||
/**
|
||||
* The Sorting ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the sorting.
|
||||
*/
|
||||
class Sorting extends AbstractDeactivatable
|
||||
{
|
||||
const SORT_ASC = 'ASC';
|
||||
const SORT_DESC = 'DESC';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $direction = self::SORT_ASC;
|
||||
|
||||
/**
|
||||
* Debug constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param string $fieldName
|
||||
* @param string $direction
|
||||
*/
|
||||
public function __construct($isEnabled = false, $fieldName = '', $direction = self::SORT_ASC)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->setFieldName($fieldName);
|
||||
$this->setDirection($direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Sorting
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Sorting(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFieldName(): string
|
||||
{
|
||||
return $this->fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
*/
|
||||
public function setFieldName(string $fieldName)
|
||||
{
|
||||
$this->fieldName = $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDirection(): string
|
||||
{
|
||||
return $this->direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $direction
|
||||
*/
|
||||
public function setDirection(string $direction)
|
||||
{
|
||||
$this->direction = $direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a sorting representation "<fieldName> <direction>"
|
||||
* @param string $sortingString
|
||||
* @return Sorting
|
||||
*/
|
||||
public static function fromString($sortingString)
|
||||
{
|
||||
$parts = GeneralUtility::trimExplode(' ', $sortingString);
|
||||
return new Sorting(true, $parts[0], $parts[1]);
|
||||
}
|
||||
}
|
99
Classes/Domain/Search/Query/ParameterBuilder/Sortings.php
Normal file
99
Classes/Domain/Search/Query/ParameterBuilder/Sortings.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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;
|
||||
|
||||
/**
|
||||
* The Sorting ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the sorting.
|
||||
*/
|
||||
class Sortings extends AbstractDeactivatable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sortings = [];
|
||||
|
||||
/**
|
||||
* Sortings constructor.
|
||||
* @param bool $isEnabled
|
||||
* @param array $sortings
|
||||
*/
|
||||
public function __construct($isEnabled = false, $sortings = [])
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->setSortings($sortings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Sortings
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Sortings(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Sorting[]
|
||||
*/
|
||||
public function getSortings(): array
|
||||
{
|
||||
return $this->sortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sortings
|
||||
*/
|
||||
public function setSortings(array $sortings)
|
||||
{
|
||||
$this->sortings = $sortings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Sorting $sorting
|
||||
*/
|
||||
public function addSorting(Sorting $sorting)
|
||||
{
|
||||
$this->sortings[] = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a sortings representation "<fieldName> <direction>,<fieldName> <direction>"
|
||||
* @param string $sortingsString
|
||||
* @return Sortings
|
||||
*/
|
||||
public static function fromString($sortingsString)
|
||||
{
|
||||
$sortFields = GeneralUtility::trimExplode(',',$sortingsString);
|
||||
$sortings = [];
|
||||
foreach($sortFields as $sortField) {
|
||||
$sorting = Sorting::fromString($sortField);
|
||||
$sortings[] = $sorting;
|
||||
}
|
||||
|
||||
return new Sortings(true, $sortings);
|
||||
}
|
||||
}
|
102
Classes/Domain/Search/Query/ParameterBuilder/Spellchecking.php
Normal file
102
Classes/Domain/Search/Query/ParameterBuilder/Spellchecking.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The Spellchecking ParameterProvider is responsible to build the solr query parameters
|
||||
* that are needed for the spellchecking.
|
||||
*/
|
||||
class Spellchecking extends AbstractDeactivatable implements ParameterBuilder
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxCollationTries = 0;
|
||||
|
||||
/**
|
||||
* Spellchecking constructor.
|
||||
*
|
||||
* @param bool $isEnabled
|
||||
* @param int $maxCollationTries
|
||||
*/
|
||||
public function __construct($isEnabled = false, int $maxCollationTries = 0)
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
$this->maxCollationTries = $maxCollationTries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxCollationTries(): int
|
||||
{
|
||||
return $this->maxCollationTries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return Spellchecking
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getSearchSpellchecking();
|
||||
if (!$isEnabled) {
|
||||
return new Spellchecking(false);
|
||||
}
|
||||
|
||||
$maxCollationTries = $solrConfiguration->getSearchSpellcheckingNumberOfSuggestionsToTry();
|
||||
|
||||
return new Spellchecking($isEnabled, $maxCollationTries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Spellchecking
|
||||
*/
|
||||
public static function getEmpty()
|
||||
{
|
||||
return new Spellchecking(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$query = $parentBuilder->getQuery();
|
||||
if (!$this->getIsEnabled()) {
|
||||
$query->removeComponent($query->getSpellcheck());
|
||||
return $parentBuilder;
|
||||
}
|
||||
|
||||
$query->getSpellcheck()->setMaxCollationTries($this->getMaxCollationTries());
|
||||
$query->getSpellcheck()->setCollate(true);
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2010-2017 dkd Internet Service GmbH <solr-support@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\AbstractQueryBuilder;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
|
||||
/**
|
||||
* The TrigramPhraseFields class
|
||||
*/
|
||||
class TrigramPhraseFields extends AbstractFieldList implements ParameterBuilder
|
||||
{
|
||||
/**
|
||||
* Parameter key which should be used for Apache Solr URL query
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $parameterKey = 'pf3';
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return TrigramPhraseFields
|
||||
*/
|
||||
public static function fromString(string $fieldListString, string $delimiter = ',') : TrigramPhraseFields
|
||||
{
|
||||
return self::initializeFromString($fieldListString, $delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
* @return TrigramPhraseFields
|
||||
*/
|
||||
public static function fromTypoScriptConfiguration(TypoScriptConfiguration $solrConfiguration)
|
||||
{
|
||||
$isEnabled = $solrConfiguration->getTrigramPhraseSearchIsEnabled();
|
||||
if (!$isEnabled) {
|
||||
return new TrigramPhraseFields(false);
|
||||
}
|
||||
|
||||
return self::fromString((string)$solrConfiguration->getSearchQueryTrigramPhraseFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string representation of the fieldList (e.g. content^100, title^10) to the object representation.
|
||||
*
|
||||
* @param string $fieldListString
|
||||
* @param string $delimiter
|
||||
* @return TrigramPhraseFields
|
||||
*/
|
||||
protected static function initializeFromString(string $fieldListString, string $delimiter = ',') : TrigramPhraseFields
|
||||
{
|
||||
$fieldList = self::buildFieldList($fieldListString, $delimiter);
|
||||
return new TrigramPhraseFields(true, $fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AbstractQueryBuilder $parentBuilder
|
||||
* @return AbstractQueryBuilder
|
||||
*/
|
||||
public function build(AbstractQueryBuilder $parentBuilder): AbstractQueryBuilder
|
||||
{
|
||||
$trigramPhraseFieldsString = $this->toString();
|
||||
if ($trigramPhraseFieldsString === '' || !$this->getIsEnabled()) {
|
||||
return $parentBuilder;
|
||||
}
|
||||
$parentBuilder->getQuery()->getEDisMax()->setPhraseTrigramFields($trigramPhraseFieldsString);
|
||||
return $parentBuilder;
|
||||
}
|
||||
}
|
47
Classes/Domain/Search/Query/Query.php
Normal file
47
Classes/Domain/Search/Query/Query.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2009-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 Solarium\QueryType\Select\Query\Query as SolariumQuery;
|
||||
|
||||
class Query extends SolariumQuery {
|
||||
|
||||
/**
|
||||
* Returns the query parameters that should be used.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryParameters() {
|
||||
return $this->getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getQuery();
|
||||
}
|
||||
}
|
587
Classes/Domain/Search/Query/QueryBuilder.php
Normal file
587
Classes/Domain/Search/Query/QueryBuilder.php
Normal file
@@ -0,0 +1,587 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2017 <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\Query\ParameterBuilder\BigramPhraseFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Elevation;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Faceting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\FieldCollapsing;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Filters;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Grouping;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Highlighting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\PhraseFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\QueryFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\ReturnFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Slops;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Sorting;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Sortings;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\Spellchecking;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\ParameterBuilder\TrigramPhraseFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\SiteHashService;
|
||||
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
|
||||
use WapplerSystems\Meilisearch\FieldProcessor\PageUidToHierarchy;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\System\Logging\SolrLogManager;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* The concrete QueryBuilder contains all TYPO3 specific initialization logic of solr queries, for TYPO3.
|
||||
*/
|
||||
class QueryBuilder extends AbstractQueryBuilder {
|
||||
|
||||
/**
|
||||
* Additional filters, which will be added to the query, as well as to
|
||||
* suggest queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $additionalFilters = [];
|
||||
|
||||
/**
|
||||
* @var TypoScriptConfiguration
|
||||
*/
|
||||
protected $typoScriptConfiguration = null;
|
||||
|
||||
/**
|
||||
* @var SolrLogManager;
|
||||
*/
|
||||
protected $logger = null;
|
||||
|
||||
/**
|
||||
* @var SiteHashService
|
||||
*/
|
||||
protected $siteHashService = null;
|
||||
|
||||
/**
|
||||
* QueryBuilder constructor.
|
||||
* @param TypoScriptConfiguration|null $configuration
|
||||
* @param SolrLogManager|null $solrLogManager
|
||||
* @param SiteHashService|null $siteHashService
|
||||
*/
|
||||
public function __construct(TypoScriptConfiguration $configuration = null, SolrLogManager $solrLogManager = null, SiteHashService $siteHashService = null)
|
||||
{
|
||||
$this->typoScriptConfiguration = $configuration ?? Util::getSolrConfiguration();
|
||||
$this->logger = $solrLogManager ?? GeneralUtility::makeInstance(SolrLogManager::class, /** @scrutinizer ignore-type */ __CLASS__);
|
||||
$this->siteHashService = $siteHashService ?? GeneralUtility::makeInstance(SiteHashService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $queryString
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function newSearchQuery($queryString): QueryBuilder
|
||||
{
|
||||
$this->queryToBuild = $this->getSearchQueryInstance((string)$queryString);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $queryString
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function newSuggestQuery($queryString): QueryBuilder
|
||||
{
|
||||
$this->queryToBuild = $this->getSuggestQueryInstance($queryString);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Query object and SearchComponents and returns
|
||||
* the initialized query object, when a search should be executed.
|
||||
*
|
||||
* @param string|null $rawQuery
|
||||
* @param int $resultsPerPage
|
||||
* @param array $additionalFiltersFromRequest
|
||||
* @return SearchQuery
|
||||
*/
|
||||
public function buildSearchQuery($rawQuery, $resultsPerPage = 10, array $additionalFiltersFromRequest = []) : SearchQuery
|
||||
{
|
||||
if ($this->typoScriptConfiguration->getLoggingQuerySearchWords()) {
|
||||
$this->logger->log(SolrLogManager::INFO, 'Received search query', [$rawQuery]);
|
||||
}
|
||||
|
||||
/* @var $query SearchQuery */
|
||||
return $this->newSearchQuery($rawQuery)
|
||||
->useResultsPerPage($resultsPerPage)
|
||||
->useReturnFieldsFromTypoScript()
|
||||
->useQueryFieldsFromTypoScript()
|
||||
->useInitialQueryFromTypoScript()
|
||||
->useFiltersFromTypoScript()
|
||||
->useFilterArray($additionalFiltersFromRequest)
|
||||
->useFacetingFromTypoScript()
|
||||
->useVariantsFromTypoScript()
|
||||
->useGroupingFromTypoScript()
|
||||
->useHighlightingFromTypoScript()
|
||||
->usePhraseFieldsFromTypoScript()
|
||||
->useBigramPhraseFieldsFromTypoScript()
|
||||
->useTrigramPhraseFieldsFromTypoScript()
|
||||
->useOmitHeader(false)
|
||||
->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a SuggestQuery with all applied filters.
|
||||
*
|
||||
* @param string $queryString
|
||||
* @param array $additionalFilters
|
||||
* @param integer $requestedPageId
|
||||
* @param string $groupList
|
||||
* @return SuggestQuery
|
||||
*/
|
||||
public function buildSuggestQuery(string $queryString, array $additionalFilters, int $requestedPageId, string $groupList) : SuggestQuery
|
||||
{
|
||||
$this->newSuggestQuery($queryString)
|
||||
->useFiltersFromTypoScript()
|
||||
->useSiteHashFromTypoScript($requestedPageId)
|
||||
->useUserAccessGroups(explode(',', $groupList))
|
||||
->useOmitHeader();
|
||||
|
||||
|
||||
if (!empty($additionalFilters)) {
|
||||
$this->useFilterArray($additionalFilters);
|
||||
}
|
||||
|
||||
return $this->queryToBuild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Query for Search which finds document for given page.
|
||||
* Note: The Connection is per language as recommended in ext-solr docs.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function buildPageQuery($pageId)
|
||||
{
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
$site = $siteRepository->getSiteByPageId($pageId);
|
||||
|
||||
return $this->newSearchQuery('')
|
||||
->useQueryString('*:*')
|
||||
->useFilter('(type:pages AND uid:' . $pageId . ') OR (*:* AND pid:' . $pageId . ' NOT type:pages)', 'type')
|
||||
->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
|
||||
->useReturnFields(ReturnFields::fromString('*'))
|
||||
->useSortings(Sortings::fromString('type asc, title asc'))
|
||||
->useQueryType('standard')
|
||||
->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query for single record
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function buildRecordQuery($type, $uid, $pageId): Query
|
||||
{
|
||||
$siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
|
||||
$site = $siteRepository->getSiteByPageId($pageId);
|
||||
|
||||
return $this->newSearchQuery('')
|
||||
->useQueryString('*:*')
|
||||
->useFilter('type:' . $type . ' AND uid:' . $uid, 'type')
|
||||
->useFilter('siteHash:' . $site->getSiteHash(), 'siteHash')
|
||||
->useReturnFields(ReturnFields::fromString('*'))
|
||||
->useSortings(Sortings::fromString('type asc, title asc'))
|
||||
->useQueryType('standard')
|
||||
->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useSlopsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useSlops(Slops::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the configured boost queries from typoscript
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useBoostQueriesFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
|
||||
|
||||
if (!empty($searchConfiguration['query.']['boostQuery'])) {
|
||||
return $this->useBoostQueries($searchConfiguration['query.']['boostQuery']);
|
||||
}
|
||||
|
||||
if (!empty($searchConfiguration['query.']['boostQuery.'])) {
|
||||
$boostQueries = $searchConfiguration['query.']['boostQuery.'];
|
||||
return $this->useBoostQueries(array_values($boostQueries));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the configured boostFunction from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useBoostFunctionFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
|
||||
if (!empty($searchConfiguration['query.']['boostFunction'])) {
|
||||
return $this->useBoostFunction($searchConfiguration['query.']['boostFunction']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the configured minimumMatch from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useMinimumMatchFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
|
||||
if (!empty($searchConfiguration['query.']['minimumMatch'])) {
|
||||
return $this->useMinimumMatch($searchConfiguration['query.']['minimumMatch']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useTieParameterFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$searchConfiguration = $this->typoScriptConfiguration->getSearchConfiguration();
|
||||
if (empty($searchConfiguration['query.']['tieParameter'])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->useTieParameter($searchConfiguration['query.']['tieParameter']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured query fields from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useQueryFieldsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useQueryFields(QueryFields::fromString($this->typoScriptConfiguration->getSearchQueryQueryFields()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured return fields from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useReturnFieldsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$returnFieldsArray = (array)$this->typoScriptConfiguration->getSearchQueryReturnFieldsAsArray(['*', 'score']);
|
||||
return $this->useReturnFields(ReturnFields::fromArray($returnFieldsArray));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Can be used to apply the allowed sites from plugin.tx_meilisearch.search.query.allowedSites to the query.
|
||||
*
|
||||
* @param int $requestedPageId
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useSiteHashFromTypoScript(int $requestedPageId): QueryBuilder
|
||||
{
|
||||
$queryConfiguration = $this->typoScriptConfiguration->getObjectByPathOrDefault('plugin.tx_meilisearch.search.query.', []);
|
||||
$allowedSites = $this->siteHashService->getAllowedSitesForPageIdAndAllowedSitesConfiguration($requestedPageId, $queryConfiguration['allowedSites']);
|
||||
return $this->useSiteHashFromAllowedSites($allowedSites);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to apply a list of allowed sites to the query.
|
||||
*
|
||||
* @param string $allowedSites
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useSiteHashFromAllowedSites($allowedSites): QueryBuilder
|
||||
{
|
||||
$isAnySiteAllowed = trim($allowedSites) === '*';
|
||||
if ($isAnySiteAllowed) {
|
||||
// no filter required
|
||||
return $this;
|
||||
}
|
||||
|
||||
$allowedSites = GeneralUtility::trimExplode(',', $allowedSites);
|
||||
$filters = [];
|
||||
foreach ($allowedSites as $site) {
|
||||
$siteHash = $this->siteHashService->getSiteHashForDomain($site);
|
||||
$filters[] = 'siteHash:"' . $siteHash . '"';
|
||||
}
|
||||
|
||||
$siteHashFilterString = implode(' OR ', $filters);
|
||||
return $this->useFilter($siteHashFilterString, 'siteHash');
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to filter the result on an applied list of user groups.
|
||||
*
|
||||
* @param array $groups
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useUserAccessGroups(array $groups): QueryBuilder
|
||||
{
|
||||
$groups = array_map('intval', $groups);
|
||||
$groups[] = 0; // always grant access to public documents
|
||||
$groups = array_unique($groups);
|
||||
sort($groups, SORT_NUMERIC);
|
||||
|
||||
$accessFilter = '{!typo3access}' . implode(',', $groups);
|
||||
$this->queryToBuild->removeFilterQuery('access');
|
||||
return $this->useFilter($accessFilter, 'access');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured initial query settings to set the alternative query for solr as required.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useInitialQueryFromTypoScript(): QueryBuilder
|
||||
{
|
||||
if ($this->typoScriptConfiguration->getSearchInitializeWithEmptyQuery() || $this->typoScriptConfiguration->getSearchQueryAllowEmptyQuery()) {
|
||||
// empty main query, but using a "return everything"
|
||||
// alternative query in q.alt
|
||||
$this->useAlternativeQuery('*:*');
|
||||
}
|
||||
|
||||
if ($this->typoScriptConfiguration->getSearchInitializeWithQuery()) {
|
||||
$this->useAlternativeQuery($this->typoScriptConfiguration->getSearchInitializeWithQuery());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured facets from the typoscript configuration on the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useFacetingFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useFaceting(Faceting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured variants from the typoscript configuration on the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useVariantsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useFieldCollapsing(FieldCollapsing::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured groupings from the typoscript configuration to the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useGroupingFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useGrouping(Grouping::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured highlighting from the typoscript configuration to the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useHighlightingFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useHighlighting(Highlighting::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured filters (page section and other from typoscript).
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useFiltersFromTypoScript(): QueryBuilder
|
||||
{
|
||||
$filters = Filters::fromTypoScriptConfiguration($this->typoScriptConfiguration);
|
||||
$this->queryToBuild->setFilterQueries($filters->getValues());
|
||||
|
||||
$this->useFilterArray($this->getAdditionalFilters());
|
||||
|
||||
$searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
|
||||
|
||||
if (!is_array($searchQueryFilters) || count($searchQueryFilters) <= 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// special filter to limit search to specific page tree branches
|
||||
if (array_key_exists('__pageSections', $searchQueryFilters)) {
|
||||
$pageIds = GeneralUtility::trimExplode(',', $searchQueryFilters['__pageSections']);
|
||||
$this->usePageSectionsFromPageIds($pageIds);
|
||||
$this->typoScriptConfiguration->removeSearchQueryFilterForPageSections();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured elevation from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useElevationFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useElevation(Elevation::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured spellchecking from the typoscript configuration.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useSpellcheckingFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useSpellchecking(Spellchecking::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the passed pageIds as __pageSection filter.
|
||||
*
|
||||
* @param array $pageIds
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function usePageSectionsFromPageIds(array $pageIds = []): QueryBuilder
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
/** @var $processor PageUidToHierarchy */
|
||||
$processor = GeneralUtility::makeInstance(PageUidToHierarchy::class);
|
||||
$hierarchies = $processor->process($pageIds);
|
||||
|
||||
foreach ($hierarchies as $hierarchy) {
|
||||
$lastLevel = array_pop($hierarchy);
|
||||
$filters[] = 'rootline:"' . $lastLevel . '"';
|
||||
}
|
||||
|
||||
$pageSectionsFilterString = implode(' OR ', $filters);
|
||||
return $this->useFilter($pageSectionsFilterString, 'pageSections');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured phrase fields from the typoscript configuration to the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function usePhraseFieldsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->usePhraseFields(PhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured bigram phrase fields from the typoscript configuration to the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useBigramPhraseFieldsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useBigramPhraseFields(BigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured trigram phrase fields from the typoscript configuration to the query.
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function useTrigramPhraseFieldsFromTypoScript(): QueryBuilder
|
||||
{
|
||||
return $this->useTrigramPhraseFields(TrigramPhraseFields::fromTypoScriptConfiguration($this->typoScriptConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration filters from the TypoScript configuration, except the __pageSections filter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalFilters() : array
|
||||
{
|
||||
// when we've build the additionalFilter once, we could return them
|
||||
if (count($this->additionalFilters) > 0) {
|
||||
return $this->additionalFilters;
|
||||
}
|
||||
|
||||
$searchQueryFilters = $this->typoScriptConfiguration->getSearchQueryFilterConfiguration();
|
||||
if (!is_array($searchQueryFilters) || count($searchQueryFilters) <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
|
||||
// all other regular filters
|
||||
foreach ($searchQueryFilters as $filterKey => $filter) {
|
||||
// the __pageSections filter should not be handled as additional filter
|
||||
if ($filterKey === '__pageSections') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filterIsArray = is_array($searchQueryFilters[$filterKey]);
|
||||
if ($filterIsArray) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasSubConfiguration = is_array($searchQueryFilters[$filterKey . '.']);
|
||||
if ($hasSubConfiguration) {
|
||||
$filter = $cObj->stdWrap($searchQueryFilters[$filterKey], $searchQueryFilters[$filterKey . '.']);
|
||||
}
|
||||
|
||||
$this->additionalFilters[$filterKey] = $filter;
|
||||
}
|
||||
|
||||
return $this->additionalFilters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rawQuery
|
||||
* @return SearchQuery
|
||||
*/
|
||||
protected function getSearchQueryInstance(string $rawQuery): SearchQuery
|
||||
{
|
||||
$query = GeneralUtility::makeInstance(SearchQuery::class);
|
||||
$query->setQuery($rawQuery);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $rawQuery
|
||||
* @return SuggestQuery
|
||||
*/
|
||||
protected function getSuggestQueryInstance($rawQuery): SuggestQuery
|
||||
{
|
||||
$query = GeneralUtility::makeInstance(SuggestQuery::class, /** @scrutinizer ignore-type */ $rawQuery, /** @scrutinizer ignore-type */ $this->typoScriptConfiguration);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
27
Classes/Domain/Search/Query/SearchQuery.php
Normal file
27
Classes/Domain/Search/Query/SearchQuery.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2009-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!
|
||||
***************************************************************/
|
||||
|
||||
class SearchQuery extends Query {}
|
86
Classes/Domain/Search/Query/SuggestQuery.php
Normal file
86
Classes/Domain/Search/Query/SuggestQuery.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace WapplerSystems\Meilisearch\Domain\Search\Query;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2009-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\Query\ParameterBuilder\ReturnFields;
|
||||
use WapplerSystems\Meilisearch\Domain\Search\Query\Helper\EscapeService;
|
||||
use WapplerSystems\Meilisearch\System\Configuration\TypoScriptConfiguration;
|
||||
use WapplerSystems\Meilisearch\Util;
|
||||
|
||||
/**
|
||||
* A query specialized to get search suggestions
|
||||
*
|
||||
* @author Ingo Renner <ingo@typo3.org>
|
||||
*/
|
||||
class SuggestQuery extends Query
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix;
|
||||
|
||||
/**
|
||||
* SuggestQuery constructor.
|
||||
*
|
||||
* @param string $keywords
|
||||
* @param TypoScriptConfiguration $solrConfiguration
|
||||
*/
|
||||
public function __construct($keywords, $solrConfiguration = null)
|
||||
{
|
||||
parent::__construct();
|
||||
$keywords = (string)$keywords;
|
||||
|
||||
$solrConfiguration = $solrConfiguration ?? Util::getSolrConfiguration();
|
||||
|
||||
$this->setQuery($keywords);
|
||||
$this->configuration = $solrConfiguration->getObjectByPathOrDefault('plugin.tx_meilisearch.suggest.', []);
|
||||
|
||||
if (!empty($this->configuration['treatMultipleTermsAsSingleTerm'])) {
|
||||
$this->prefix = EscapeService::escape($keywords);
|
||||
} else {
|
||||
$matches = [];
|
||||
preg_match('/^(:?(.* |))([^ ]+)$/', $keywords, $matches);
|
||||
$fullKeywords = trim($matches[2]);
|
||||
$partialKeyword = trim($matches[3]);
|
||||
|
||||
$this->setQuery($fullKeywords);
|
||||
$this->prefix = EscapeService::escape($partialKeyword);
|
||||
}
|
||||
|
||||
$this->getEDisMax()->setQueryAlternative('*:*');
|
||||
$this->setFields(ReturnFields::fromString($this->configuration['suggestField'])->getValues());
|
||||
$this->addParam('facet', 'on');
|
||||
$this->addParam('facet.prefix', $this->prefix);
|
||||
$this->addParam('facet.field', $this->configuration['suggestField']);
|
||||
$this->addParam('facet.limit', $this->configuration['numberOfSuggestions']);
|
||||
$this->addParam('facet.mincount', 1);
|
||||
$this->addParam('facet.method', 'enum');
|
||||
}
|
||||
}
|
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);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user