mirror of
https://github.com/Codappix/search_core.git
synced 2024-12-25 08:36:10 +01:00
Merge branch 'develop' into support/76
Conflicts: Classes/DataProcessing/ProcessorInterface.php Classes/Domain/Index/AbstractIndexer.php Classes/Integration/Form/Finisher/DataHandlerFinisher.php Makefile Tests/Functional/Connection/Elasticsearch/FilterTest.php Tests/Functional/Fixtures/BasicSetup.ts Tests/Unit/Integration/Form/Finisher/DataHandlerFinisherTest.php composer.json
This commit is contained in:
commit
ee3987a746
61 changed files with 1534 additions and 387 deletions
|
@ -35,6 +35,7 @@ services:
|
|||
install: make install
|
||||
|
||||
script:
|
||||
- make cgl
|
||||
- make unitTests
|
||||
- make functionalTests
|
||||
|
||||
|
|
|
@ -132,7 +132,10 @@ class Elasticsearch implements Singleton, ConnectionInterface
|
|||
}
|
||||
);
|
||||
} catch (\Elastica\Exception\NotFoundException $exception) {
|
||||
$this->logger->debug('Tried to delete document in index, which does not exist.', [$documentType, $identifier]);
|
||||
$this->logger->debug(
|
||||
'Tried to delete document in index, which does not exist.',
|
||||
[$documentType, $identifier]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,25 +45,26 @@ class Facet implements FacetInterface
|
|||
*/
|
||||
protected $options = [];
|
||||
|
||||
public function __construct($name, array $aggregation, ConfigurationContainerInterface $configuration)
|
||||
public function __construct(string $name, array $aggregation, ConfigurationContainerInterface $configuration)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->buckets = $aggregation['buckets'];
|
||||
$this->field = $configuration->getIfExists('searching.facets.' . $this->name . '.field') ?: '';
|
||||
|
||||
$config = $configuration->getIfExists('searching.facets.' . $this->name) ?: [];
|
||||
foreach ($config as $configEntry) {
|
||||
if (isset($configEntry['field'])) {
|
||||
$this->field = $configEntry['field'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getField()
|
||||
public function getField() : string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
@ -73,7 +74,7 @@ class Facet implements FacetInterface
|
|||
*
|
||||
* @return array<FacetOptionInterface>
|
||||
*/
|
||||
public function getOptions()
|
||||
public function getOptions() : array
|
||||
{
|
||||
$this->initOptions();
|
||||
|
||||
|
|
|
@ -29,6 +29,11 @@ class FacetOption implements FacetOptionInterface
|
|||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $displayName = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
@ -40,21 +45,21 @@ class FacetOption implements FacetOptionInterface
|
|||
public function __construct(array $bucket)
|
||||
{
|
||||
$this->name = $bucket['key'];
|
||||
$this->displayName = isset($bucket['key_as_string']) ? $bucket['key_as_string'] : $this->getName();
|
||||
$this->count = $bucket['doc_count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCount()
|
||||
public function getDisplayName() : string
|
||||
{
|
||||
return $this->displayName;
|
||||
}
|
||||
|
||||
public function getCount() : int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
|
|
@ -24,10 +24,14 @@ use Codappix\SearchCore\Connection\FacetInterface;
|
|||
use Codappix\SearchCore\Connection\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\QueryResultInterfaceStub;
|
||||
use Codappix\SearchCore\Domain\Model\ResultItem;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
class SearchResult implements SearchResultInterface
|
||||
{
|
||||
use QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* @var SearchRequestInterface
|
||||
*/
|
||||
|
@ -104,7 +108,7 @@ class SearchResult implements SearchResultInterface
|
|||
}
|
||||
|
||||
foreach ($this->result->getResults() as $result) {
|
||||
$this->results[] = new ResultItem($result);
|
||||
$this->results[] = new ResultItem($result->getData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,41 +157,8 @@ class SearchResult implements SearchResultInterface
|
|||
$this->position = 0;
|
||||
}
|
||||
|
||||
// Extbase QueryResultInterface - Implemented to support Pagination of Fluid.
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->searchRequest;
|
||||
}
|
||||
|
||||
public function getFirst()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195121);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195135);
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
// Return false to allow Fluid to use appropriate getter methods.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('Use getter to fetch properties.', 1502196933);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196934);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196936);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,17 @@ interface FacetOptionInterface
|
|||
/**
|
||||
* Returns the name of this option. Equivalent
|
||||
* to value used for filtering.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
public function getName() : string;
|
||||
|
||||
/**
|
||||
* If a pre-rendered name is provided, this will be returned.
|
||||
* Otherwise it's the same as getName().
|
||||
*/
|
||||
public function getDisplayName() : string;
|
||||
|
||||
/**
|
||||
* Returns the number of found results for this option.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCount();
|
||||
public function getCount() : int;
|
||||
}
|
||||
|
|
|
@ -28,15 +28,11 @@ interface FacetRequestInterface
|
|||
/**
|
||||
* The identifier of the facet, used as key in arrays and to get the facet
|
||||
* from search request, etc.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier();
|
||||
public function getIdentifier() : string;
|
||||
|
||||
/**
|
||||
* The field to use for facet building.
|
||||
*
|
||||
* @return string
|
||||
* The config to use for facet building.
|
||||
*/
|
||||
public function getField();
|
||||
public function getConfig() : array;
|
||||
}
|
||||
|
|
|
@ -25,5 +25,12 @@ namespace Codappix\SearchCore\Connection;
|
|||
*/
|
||||
interface ResultItemInterface extends \ArrayAccess
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns every information as array.
|
||||
*
|
||||
* Provide key/column/field => data.
|
||||
*
|
||||
* Used e.g. for dataprocessing.
|
||||
*/
|
||||
public function getPlainData() : array;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Codappix\SearchCore\Connection;
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
|
||||
|
||||
interface SearchRequestInterface extends QueryInterface
|
||||
|
@ -40,4 +41,10 @@ interface SearchRequestInterface extends QueryInterface
|
|||
* @return array
|
||||
*/
|
||||
public function getFilter();
|
||||
|
||||
/**
|
||||
* Workaround for paginate widget support which will
|
||||
* use the request to build another search.
|
||||
*/
|
||||
public function setSearchService(SearchService $searchService);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\DataProcessing;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Service\TypoScriptService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Executes an existing TYPO3 DataProcessor on the given data.
|
||||
*/
|
||||
class ContentObjectDataProcessorAdapterProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @var TypoScriptService
|
||||
*/
|
||||
protected $typoScriptService;
|
||||
|
||||
public function __construct(TypoScriptService $typoScriptService)
|
||||
{
|
||||
$this->typoScriptService = $typoScriptService;
|
||||
}
|
||||
|
||||
public function processData(array $data, array $configuration) : array
|
||||
{
|
||||
$dataProcessor = GeneralUtility::makeInstance($configuration['_dataProcessor']);
|
||||
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
|
||||
$contentObjectRenderer->data = $data;
|
||||
if (isset($configuration['_table'])) {
|
||||
$contentObjectRenderer->start($data, $configuration['_table']);
|
||||
}
|
||||
|
||||
return $dataProcessor->process(
|
||||
$contentObjectRenderer,
|
||||
[],
|
||||
$this->typoScriptService->convertPlainArrayToTypoScriptArray($configuration),
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
class CopyToProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
$all = [];
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
class GeoPointProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
if (! $this->canApply($record, $configuration)) {
|
||||
return $record;
|
||||
|
|
|
@ -21,14 +21,13 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
|
||||
/**
|
||||
* All DataProcessing Processors should implement this interface, otherwise
|
||||
* they will not be executed.
|
||||
* All DataProcessing Processors should implement this interface.
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Processes the given record.
|
||||
* Processes the given data.
|
||||
* Also retrieves the configuration for this processor instance.
|
||||
*/
|
||||
public function processRecord(array $record, array $configuration) : array;
|
||||
public function processData(array $record, array $configuration) : array;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|||
*/
|
||||
class RemoveProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
if (!isset($configuration['fields'])) {
|
||||
return $record;
|
||||
|
|
56
Classes/DataProcessing/Service.php
Normal file
56
Classes/DataProcessing/Service.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\DataProcessing;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
/**
|
||||
* Eases work with data processing.
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
public function __construct(ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the dataprocessor depending on configuration and returns the result.
|
||||
*
|
||||
* @param array|string $configuration Either the full configuration or only the class name.
|
||||
*/
|
||||
public function executeDataProcessor($configuration, array $data) : array
|
||||
{
|
||||
if (is_string($configuration)) {
|
||||
$configuration = [
|
||||
'_typoScriptNodeValue' => $configuration,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->objectManager->get($configuration['_typoScriptNodeValue'])
|
||||
->processData($data, $configuration);
|
||||
}
|
||||
}
|
|
@ -43,6 +43,12 @@ abstract class AbstractIndexer implements IndexerInterface
|
|||
*/
|
||||
protected $identifier = '';
|
||||
|
||||
/**
|
||||
* @var \Codappix\SearchCore\DataProcessing\Service
|
||||
* @inject
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Log\Logger
|
||||
*/
|
||||
|
@ -135,18 +141,7 @@ abstract class AbstractIndexer implements IndexerInterface
|
|||
{
|
||||
try {
|
||||
foreach ($this->configuration->get('indexing.' . $this->identifier . '.dataProcessing') as $configuration) {
|
||||
$className = '';
|
||||
if (is_string($configuration)) {
|
||||
$className = $configuration;
|
||||
$configuration = [];
|
||||
} else {
|
||||
$className = $configuration['_typoScriptNodeValue'];
|
||||
}
|
||||
|
||||
$dataProcessor = GeneralUtility::makeInstance($className);
|
||||
if ($dataProcessor instanceof ProcessorInterface) {
|
||||
$record = $dataProcessor->processRecord($record, $configuration);
|
||||
}
|
||||
$record = $this->dataProcessorService->executeDataProcessor($configuration, $record);
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// Nothing to do.
|
||||
|
|
|
@ -167,7 +167,9 @@ class TcaTableService
|
|||
;
|
||||
}
|
||||
|
||||
$userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause');
|
||||
$userDefinedWhere = $this->configuration->getIfExists(
|
||||
'indexing.' . $this->getTableName() . '.additionalWhereClause'
|
||||
);
|
||||
if (is_string($userDefinedWhere)) {
|
||||
$whereClause .= ' AND ' . $userDefinedWhere;
|
||||
}
|
||||
|
@ -348,6 +350,9 @@ class TcaTableService
|
|||
*/
|
||||
protected function getBlackListedRootLine() : array
|
||||
{
|
||||
return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'));
|
||||
return GeneralUtility::intExplode(
|
||||
',',
|
||||
$this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,37 +30,27 @@ class FacetRequest implements FacetRequestInterface
|
|||
protected $identifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var array
|
||||
*/
|
||||
protected $field = '';
|
||||
protected $config = [];
|
||||
|
||||
/**
|
||||
* TODO: Add validation / exception?
|
||||
* As the facets come from configuration this might be a good idea to help
|
||||
* integrators find issues.
|
||||
*
|
||||
* @param string $identifier
|
||||
* @param string $field
|
||||
*/
|
||||
public function __construct($identifier, $field)
|
||||
public function __construct(string $identifier, array $config)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
$this->field = $field;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier()
|
||||
public function getIdentifier() : string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getField()
|
||||
public function getConfig() : array
|
||||
{
|
||||
return $this->field;
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
|
|
61
Classes/Domain/Model/QueryResultInterfaceStub.php
Normal file
61
Classes/Domain/Model/QueryResultInterfaceStub.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* As we have to stay compatible with QueryResultInterface
|
||||
* of extbase but can and need not to provide all methods,
|
||||
* this stub will provde the non implemented methods to
|
||||
* keep real implementations clean.
|
||||
*/
|
||||
trait QueryResultInterfaceStub
|
||||
{
|
||||
public function getFirst()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195121);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195135);
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
// Return false to allow Fluid to use appropriate getter methods.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('Use getter to fetch properties.', 1502196933);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196934);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196936);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Connection\Elasticsearch;
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
|
@ -29,9 +29,14 @@ class ResultItem implements ResultItemInterface
|
|||
*/
|
||||
protected $data = [];
|
||||
|
||||
public function __construct(\Elastica\Result $result)
|
||||
public function __construct(array $result)
|
||||
{
|
||||
$this->data = $result->getData();
|
||||
$this->data = $result;
|
||||
}
|
||||
|
||||
public function getPlainData() : array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
|
@ -23,6 +23,7 @@ namespace Codappix\SearchCore\Domain\Model;
|
|||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\FacetRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
|
||||
/**
|
||||
* Represents a search request used to process an actual search.
|
||||
|
@ -63,6 +64,11 @@ class SearchRequest implements SearchRequestInterface
|
|||
*/
|
||||
protected $connection = null;
|
||||
|
||||
/**
|
||||
* @var SearchService
|
||||
*/
|
||||
protected $searchService = null;
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*/
|
||||
|
@ -143,18 +149,29 @@ class SearchRequest implements SearchRequestInterface
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function setSearchService(SearchService $searchService)
|
||||
{
|
||||
$this->searchService = $searchService;
|
||||
}
|
||||
|
||||
// Extbase QueryInterface
|
||||
// Current implementation covers only paginate widget support.
|
||||
public function execute($returnRawQueryResult = false)
|
||||
{
|
||||
if ($this->connection instanceof ConnectionInterface) {
|
||||
return $this->connection->search($this);
|
||||
if (! ($this->connection instanceof ConnectionInterface)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Connection was not set before, therefore execute can not work. Use `setConnection` before.',
|
||||
1502197732
|
||||
);
|
||||
}
|
||||
if (! ($this->searchService instanceof SearchService)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'SearchService was not set before, therefore execute can not work. Use `setSearchService` before.',
|
||||
1520325175
|
||||
);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
'Connection was not set before, therefore execute can not work. Use `setConnection` before.',
|
||||
1502197732
|
||||
);
|
||||
return $this->searchService->processResult($this->connection->search($this));
|
||||
}
|
||||
|
||||
public function setLimit($limit)
|
||||
|
|
129
Classes/Domain/Model/SearchResult.php
Normal file
129
Classes/Domain/Model/SearchResult.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* Generic model for mapping a concrete search result from a connection.
|
||||
*/
|
||||
class SearchResult implements SearchResultInterface
|
||||
{
|
||||
use QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* @var SearchResultInterface
|
||||
*/
|
||||
protected $originalSearchResult;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $resultItems = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $results = [];
|
||||
|
||||
/**
|
||||
* For Iterator interface.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $position = 0;
|
||||
|
||||
public function __construct(SearchResultInterface $originalSearchResult, array $resultItems)
|
||||
{
|
||||
$this->originalSearchResult = $originalSearchResult;
|
||||
$this->resultItems = $resultItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ResultItemInterface>
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
$this->initResults();
|
||||
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
protected function initResults()
|
||||
{
|
||||
if ($this->results !== []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->resultItems as $item) {
|
||||
$this->results[] = new ResultItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFacets()
|
||||
{
|
||||
return $this->originalSearchResult->getFacets();
|
||||
}
|
||||
|
||||
public function getCurrentCount()
|
||||
{
|
||||
return $this->originalSearchResult->getCurrentCount();
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return $this->originalSearchResult->count();
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
return $this->getResults()[$this->position];
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
++$this->position;
|
||||
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->getResults()[$this->position]);
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->originalSearchResult->getQuery();
|
||||
}
|
||||
}
|
|
@ -120,6 +120,10 @@ class QueryFactory
|
|||
return;
|
||||
}
|
||||
|
||||
if (trim($searchRequest->getSearchTerm()) === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$boostQueryParts = [];
|
||||
|
||||
foreach ($fields as $fieldName => $boostValue) {
|
||||
|
@ -162,7 +166,11 @@ class QueryFactory
|
|||
{
|
||||
try {
|
||||
$query = ArrayUtility::arrayMergeRecursiveOverrule($query, [
|
||||
'stored_fields' => GeneralUtility::trimExplode(',', $this->configuration->get('searching.fields.stored_fields'), true),
|
||||
'stored_fields' => GeneralUtility::trimExplode(
|
||||
',',
|
||||
$this->configuration->get('searching.fields.stored_fields'),
|
||||
true
|
||||
),
|
||||
]);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// Nothing configured
|
||||
|
@ -170,7 +178,10 @@ class QueryFactory
|
|||
|
||||
try {
|
||||
$scriptFields = $this->configuration->get('searching.fields.script_fields');
|
||||
$scriptFields = $this->configurationUtility->replaceArrayValuesWithRequestContent($searchRequest, $scriptFields);
|
||||
$scriptFields = $this->configurationUtility->replaceArrayValuesWithRequestContent(
|
||||
$searchRequest,
|
||||
$scriptFields
|
||||
);
|
||||
$scriptFields = $this->configurationUtility->filterByCondition($scriptFields);
|
||||
if ($scriptFields !== []) {
|
||||
$query = ArrayUtility::arrayMergeRecursiveOverrule($query, ['script_fields' => $scriptFields]);
|
||||
|
@ -232,6 +243,18 @@ class QueryFactory
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($config['raw'])) {
|
||||
$filter = array_merge($config['raw'], $filter);
|
||||
}
|
||||
|
||||
if ($config['type'] === 'range') {
|
||||
return [
|
||||
'range' => [
|
||||
$config['field'] => $filter,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [$config['field'] => $filter];
|
||||
}
|
||||
|
||||
|
@ -240,11 +263,7 @@ class QueryFactory
|
|||
foreach ($searchRequest->getFacets() as $facet) {
|
||||
$query = ArrayUtility::arrayMergeRecursiveOverrule($query, [
|
||||
'aggs' => [
|
||||
$facet->getIdentifier() => [
|
||||
'terms' => [
|
||||
'field' => $facet->getField(),
|
||||
],
|
||||
],
|
||||
$facet->getIdentifier() => $facet->getConfig(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@ use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
|||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Model\FacetRequest;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
|
@ -49,19 +51,27 @@ class SearchService
|
|||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection
|
||||
* @param ConfigurationContainerInterface $configuration
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
* @param DataProcessorService $dataProcessorService
|
||||
*/
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
ConfigurationContainerInterface $configuration,
|
||||
ObjectManagerInterface $objectManager
|
||||
ObjectManagerInterface $objectManager,
|
||||
DataProcessorService $dataProcessorService
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->configuration = $configuration;
|
||||
$this->objectManager = $objectManager;
|
||||
$this->dataProcessorService = $dataProcessorService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,12 +80,15 @@ class SearchService
|
|||
*/
|
||||
public function search(SearchRequestInterface $searchRequest)
|
||||
{
|
||||
$searchRequest->setConnection($this->connection);
|
||||
$this->addSize($searchRequest);
|
||||
$this->addConfiguredFacets($searchRequest);
|
||||
$this->addConfiguredFilters($searchRequest);
|
||||
|
||||
return $this->connection->search($searchRequest);
|
||||
// Add connection to request to enable paginate widget support
|
||||
$searchRequest->setConnection($this->connection);
|
||||
$searchRequest->setSearchService($this);
|
||||
|
||||
return $this->processResult($this->connection->search($searchRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,15 +116,10 @@ class SearchService
|
|||
}
|
||||
|
||||
foreach ($facetsConfig as $identifier => $facetConfig) {
|
||||
if (!isset($facetConfig['field']) || trim($facetConfig['field']) === '') {
|
||||
// TODO: Finish throw
|
||||
throw new \Exception('message', 1499171142);
|
||||
}
|
||||
|
||||
$searchRequest->addFacet($this->objectManager->get(
|
||||
FacetRequest::class,
|
||||
$identifier,
|
||||
$facetConfig['field']
|
||||
$facetConfig
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -138,4 +146,30 @@ class SearchService
|
|||
// Nothing todo, no filter configured.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the result, e.g. applies configured data processing to result.
|
||||
*/
|
||||
public function processResult(SearchResultInterface $searchResult) : SearchResultInterface
|
||||
{
|
||||
try {
|
||||
$newSearchResultItems = [];
|
||||
foreach ($this->configuration->get('searching.dataProcessing') as $configuration) {
|
||||
foreach ($searchResult as $resultItem) {
|
||||
$newSearchResultItems[] = $this->dataProcessorService->executeDataProcessor(
|
||||
$configuration,
|
||||
$resultItem->getPlainData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->objectManager->get(
|
||||
SearchResult::class,
|
||||
$searchResult,
|
||||
$newSearchResultItems
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return $searchResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,16 +82,6 @@ class DataHandler implements Singleton
|
|||
$this->indexerFactory = $indexerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param array $record
|
||||
*/
|
||||
public function add($table, array $record)
|
||||
{
|
||||
$this->logger->debug('Record received for add.', [$table, $record]);
|
||||
$this->getIndexer($table)->indexDocument($record['uid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*/
|
||||
|
|
|
@ -91,42 +91,36 @@ class DataHandler implements Singleton
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by CoreDataHandler on database operations, e.g. if new records were created or records were updated.
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $table
|
||||
* @param string|int $uid
|
||||
* @param array $fieldArray
|
||||
* @param CoreDataHandler $dataHandler
|
||||
*
|
||||
* @return bool False if hook was not processed.
|
||||
*/
|
||||
public function processDatamap_afterDatabaseOperations($status, $table, $uid, array $fieldArray, CoreDataHandler $dataHandler)
|
||||
public function processDatamap_afterAllOperations(CoreDataHandler $dataHandler)
|
||||
{
|
||||
foreach ($dataHandler->datamap as $table => $record) {
|
||||
$uid = key($record);
|
||||
$fieldData = current($record);
|
||||
|
||||
if (isset($fieldArray['uid'])) {
|
||||
$uid = $fieldArray['uid'];
|
||||
} elseif (isset($dataHandler->substNEWwithIDs[$uid])) {
|
||||
$uid = $dataHandler->substNEWwithIDs[$uid];
|
||||
}
|
||||
|
||||
$this->processRecord($table, $uid);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processRecord(string $table, int $uid) : bool
|
||||
{
|
||||
if (! $this->shouldProcessHookForTable($table)) {
|
||||
$this->logger->debug('Database update not processed.', [$table, $uid]);
|
||||
$this->logger->debug('Indexing of record not processed.', [$table, $uid]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($status === 'new') {
|
||||
$fieldArray['uid'] = $dataHandler->substNEWwithIDs[$uid];
|
||||
$this->dataHandler->add($table, $fieldArray);
|
||||
$record = $this->getRecord($table, $uid);
|
||||
if ($record !== null) {
|
||||
$this->dataHandler->update($table, $record);
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($status === 'update') {
|
||||
$record = $this->getRecord($table, $uid);
|
||||
if ($record !== null) {
|
||||
$this->dataHandler->update($table, $record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->logger->debug(
|
||||
'Database update not processed, cause status is unhandled.',
|
||||
[$status, $table, $uid, $fieldArray]
|
||||
);
|
||||
$this->logger->debug('Indexing of record not processed, as he was not found in Database.', [$table, $uid]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
8
Documentation/source/changelog.rst
Normal file
8
Documentation/source/changelog.rst
Normal file
|
@ -0,0 +1,8 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
changelog/*
|
|
@ -0,0 +1,40 @@
|
|||
Breacking Change 120 "Pass facets configuration to elasticsearch"
|
||||
=================================================================
|
||||
|
||||
In order to allow arbitrary facet configuration, we do not process the facet configuration anymore.
|
||||
Instead integrators are able to configure facets for search service "as is". We just pipe the
|
||||
configuration through.
|
||||
|
||||
Therefore the following, which worked before, does not work anymore:
|
||||
|
||||
.. code-block:: typoscript
|
||||
:linenos:
|
||||
:emphasize-lines: 4
|
||||
|
||||
plugin.tx_searchcore.settings.search {
|
||||
facets {
|
||||
category {
|
||||
field = categories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Instead you have to provide the full configuration yourself:
|
||||
|
||||
.. code-block:: typoscript
|
||||
:linenos:
|
||||
:emphasize-lines: 4,6
|
||||
|
||||
plugin.tx_searchcore.settings.search {
|
||||
facets {
|
||||
category {
|
||||
terms {
|
||||
field = categories
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You need to add line 4 and 6, the additional level ``terms`` for elasticsearch.
|
||||
|
||||
See :issue:`120`.
|
|
@ -36,5 +36,7 @@ DataProcessing
|
|||
|
||||
Before data is transfered to search service, it can be processed by "DataProcessors" like already
|
||||
known by :ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing` of :ref:`t3tsref:cobj-fluidtemplate`.
|
||||
The same is true for retrieved search results. They can be processed again by "DataProcessors" to
|
||||
prepare data for display in Templates or further usage.
|
||||
|
||||
Configuration is done through TypoScript, see :ref:`dataProcessing`.
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
Configuration
|
||||
=============
|
||||
|
||||
Installation wide configuration is handled inside of the extension manager. Just check out the
|
||||
options there, they all have labels.
|
||||
|
||||
The extension offers the following configuration options through TypoScript. If you overwrite them
|
||||
through `setup` make sure to keep them in the `module` area as they will be accessed from backend
|
||||
mode of TYPO3. Do so by placing the following line at the end::
|
||||
mode of TYPO3 for indexing. Do so by placing the following line at the end::
|
||||
|
||||
module.tx_searchcore < plugin.tx_searchcore
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
``Codappix\SearchCore\DataProcessing\ContentObjectDataProcessorAdapterProcessor``
|
||||
=================================================================================
|
||||
|
||||
Will execute an existing TYPO3 data processor.
|
||||
|
||||
Possible Options:
|
||||
|
||||
``_dataProcessor``
|
||||
Necessary, defined which data processor to apply. Provide the same as you would to call the
|
||||
processor.
|
||||
``_table``
|
||||
Defines the "current" table as used by some processors, e.g.
|
||||
``TYPO3\CMS\Frontend\DataProcessing\FilesProcessor``.
|
||||
|
||||
All further options are passed to the configured data processor. Therefore they are documented at
|
||||
each data processor.
|
||||
|
||||
Example::
|
||||
|
||||
plugin.tx_searchcore.settings.searching.dataProcessing {
|
||||
1 = Codappix\SearchCore\DataProcessing\ContentObjectDataProcessorAdapterProcessor
|
||||
1 {
|
||||
_table = pages
|
||||
_dataProcessor = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
|
||||
|
||||
references.fieldName = media
|
||||
as = images
|
||||
}
|
||||
}
|
||||
|
||||
The above example will create a new field ``images`` with resolved FAL relations from ``media``
|
||||
field.
|
|
@ -0,0 +1,36 @@
|
|||
The following Processor are available:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
/configuration/dataProcessing/ContentObjectDataProcessorAdapterProcessor
|
||||
/configuration/dataProcessing/CopyToProcessor
|
||||
/configuration/dataProcessing/GeoPointProcessor
|
||||
/configuration/dataProcessing/RemoveProcessor
|
||||
|
||||
The following Processor are planned:
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ReplaceProcessor``
|
||||
Will execute a search and replace on configured fields.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RootLevelProcessor``
|
||||
Will attach the root level to the record.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ChannelProcessor``
|
||||
Will add a configurable channel to the record, e.g. if you have different areas in your
|
||||
website like "products" and "infos".
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RelationResolverProcessor``
|
||||
Resolves all relations using the TCA.
|
||||
|
||||
Of course you are able to provide further processors. Just implement
|
||||
``Codappix\SearchCore\DataProcessing\ProcessorInterface`` and use the FQCN (=Fully qualified
|
||||
class name) as done in the examples above.
|
||||
|
||||
By implementing also the same interface as necessary for TYPO3
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`, you are able to reuse the same code
|
||||
also for Fluid to prepare the same record fetched from DB for your fluid.
|
||||
|
||||
Dependency injection is possible inside of processors, as we instantiate through extbase
|
||||
``ObjectManager``.
|
|
@ -146,7 +146,7 @@ Example::
|
|||
dataProcessing
|
||||
--------------
|
||||
|
||||
Used by: All connections while indexing.
|
||||
Used by: All connections while indexing, due to implementation inside ``AbstractIndexer``.
|
||||
|
||||
Configure modifications on each document before sending it to the configured connection. Same as
|
||||
provided by TYPO3 for :ref:`t3tsref:cobj-fluidtemplate` through
|
||||
|
@ -170,38 +170,7 @@ Example::
|
|||
|
||||
The above example will copy all existing fields to the field ``search_spellcheck``. Afterwards
|
||||
all fields, including ``search_spellcheck`` will be copied to ``search_all``.
|
||||
E.g. used to index all information into a field for :ref:`spellchecking` or searching with
|
||||
different :ref:`mapping`.
|
||||
|
||||
The following Processor are available:
|
||||
.. include:: /configuration/dataProcessing/availableAndPlanned.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
dataProcessing/CopyToProcessor
|
||||
dataProcessing/RemoveProcessor
|
||||
dataProcessing/GeoPointProcessor
|
||||
|
||||
The following Processor are planned:
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ReplaceProcessor``
|
||||
Will execute a search and replace on configured fields.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RootLevelProcessor``
|
||||
Will attach the root level to the record.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ChannelProcessor``
|
||||
Will add a configurable channel to the record, e.g. if you have different areas in your
|
||||
website like "products" and "infos".
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RelationResolverProcessor``
|
||||
Resolves all relations using the TCA.
|
||||
|
||||
Of course you are able to provide further processors. Just implement
|
||||
``Codappix\SearchCore\DataProcessing\ProcessorInterface`` and use the FQCN (=Fully qualified
|
||||
class name) as done in the examples above.
|
||||
|
||||
By implementing also the same interface as necessary for TYPO3
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`, you are able to reuse the same code
|
||||
also for Fluid to prepare the same record fetched from DB for your fluid.
|
||||
Also data processors are available for search results too, see :ref:`searching_dataProcessing`.
|
||||
|
|
|
@ -119,20 +119,48 @@ E.g. you submit a filter in form of:
|
|||
|
||||
.. code-block:: html
|
||||
|
||||
<f:form.textfield property="filter.distance.location.lat" value="51.168098" />
|
||||
<f:form.textfield property="filter.distance.location.lon" value="6.381384" />
|
||||
<f:form.textfield property="filter.distance.distance" value="100km" />
|
||||
<f:comment>
|
||||
Due to TYPO3 7.x fluid limitations, we build this input ourself.
|
||||
No longer necessary in 8 and above
|
||||
</f:comment>
|
||||
<select name="tx_searchcore_search[searchRequest][filter][month][from]" class="_control" >
|
||||
<option value="">Month</option>
|
||||
<f:for each="{searchResult.facets.month.options}" as="month">
|
||||
<f:if condition="{month.count}">
|
||||
<option
|
||||
value="{month.displayName -> f:format.date(format: 'Y-m')}"
|
||||
{f:if(condition: '{searchRequest.filter.month.from} == {month.displayName -> f:format.date(format: \'Y-m\')}', then: 'selected="true"')}
|
||||
>{month.displayName -> f:format.date(format: '%B %Y')}</option>
|
||||
</f:if>
|
||||
</f:for>
|
||||
</select>
|
||||
<select name="tx_searchcore_search[searchRequest][filter][month][to]" class="_control" >
|
||||
<option value="">Month</option>
|
||||
<f:for each="{searchResult.facets.month.options}" as="month">
|
||||
<f:if condition="{month.count}">
|
||||
<option
|
||||
value="{month.displayName -> f:format.date(format: 'Y-m')}"
|
||||
{f:if(condition: '{searchRequest.filter.month.from} == {month.displayName -> f:format.date(format: \'Y-m\')}', then: 'selected="true"')}
|
||||
>{month.displayName -> f:format.date(format: '%B %Y')}</option>
|
||||
</f:if>
|
||||
</f:for>
|
||||
</select>
|
||||
|
||||
This will create a ``distance`` filter with subproperties. To make this filter actually work, you
|
||||
can add the following TypoScript, which will be added to the filter::
|
||||
|
||||
mapping {
|
||||
filter {
|
||||
distance {
|
||||
field = geo_distance
|
||||
month {
|
||||
type = range
|
||||
field = released
|
||||
raw {
|
||||
format = yyyy-MM
|
||||
}
|
||||
|
||||
fields {
|
||||
distance = distance
|
||||
location = location
|
||||
gte = from
|
||||
lte = to
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,9 +171,12 @@ in elasticsearch. In above example they do match, but you can also use different
|
|||
On the left hand side is the elasticsearch field name, on the right side the one submitted as a
|
||||
filter.
|
||||
|
||||
The ``field``, in above example ``geo_distance``, will be used as the elasticsearch field for
|
||||
The ``field``, in above example ``released``, will be used as the elasticsearch field for
|
||||
filtering. This way you can use arbitrary filter names and map them to existing elasticsearch fields.
|
||||
|
||||
Everything that is configured inside ``raw`` is passed, as is, to search service, e.g.
|
||||
elasticsearch.
|
||||
|
||||
.. _fields:
|
||||
|
||||
fields
|
||||
|
@ -216,3 +247,37 @@ Example::
|
|||
}
|
||||
|
||||
Only ``filter`` is allowed as value. Will submit an empty query to switch to filter mode.
|
||||
|
||||
.. _searching_dataProcessing:
|
||||
|
||||
dataProcessing
|
||||
--------------
|
||||
|
||||
Used by: All connections while indexing, due to implementation inside ``SearchService``.
|
||||
|
||||
Configure modifications on each document before returning search result. Same as provided by TYPO3
|
||||
for :ref:`t3tsref:cobj-fluidtemplate` through
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`.
|
||||
|
||||
All processors are applied in configured order. Allowing to work with already processed data.
|
||||
|
||||
Example::
|
||||
|
||||
plugin.tx_searchcore.settings.searching.dataProcessing {
|
||||
1 = Codappix\SearchCore\DataProcessing\CopyToProcessor
|
||||
1 {
|
||||
to = search_spellcheck
|
||||
}
|
||||
|
||||
2 = Codappix\SearchCore\DataProcessing\CopyToProcessor
|
||||
2 {
|
||||
to = search_all
|
||||
}
|
||||
}
|
||||
|
||||
The above example will copy all existing fields to the field ``search_spellcheck``. Afterwards
|
||||
all fields, including ``search_spellcheck`` will be copied to ``search_all``.
|
||||
|
||||
.. include:: /configuration/dataProcessing/availableAndPlanned.rst
|
||||
|
||||
Also data processors are available while indexing too, see :ref:`dataProcessing`.
|
||||
|
|
|
@ -29,6 +29,16 @@ Also multiple filter are supported. Filtering results by fields for string conte
|
|||
Facets / aggregates are also possible. Therefore a mapping has to be defined in TypoScript for
|
||||
indexing, and the facets itself while searching.
|
||||
|
||||
.. _features_dataProcessing:
|
||||
|
||||
DataProcessing
|
||||
==============
|
||||
|
||||
DataProcessing, as known from ``FLUIDTEMPLATE`` is available while indexing and for search results.
|
||||
Each item can be processed by multiple processor to prepare data for indexing and output.
|
||||
|
||||
See :ref:`concepts_indexing_dataprocessing` in :ref:`concepts` section.
|
||||
|
||||
.. _features_planned:
|
||||
|
||||
Planned
|
||||
|
|
|
@ -15,3 +15,4 @@ Table of Contents
|
|||
connections
|
||||
indexer
|
||||
development
|
||||
changelog
|
||||
|
|
|
@ -19,4 +19,8 @@ In that case you need to install all dependencies yourself. Dependencies are:
|
|||
Afterwards you need to enable the extension through the extension manager and include the static
|
||||
TypoScript setup.
|
||||
|
||||
If you **don't** want to use the included elasticsearch integration, you have to disable it in the
|
||||
extension manager configuration of the extension by checking the checkbox.
|
||||
It's currently enabled by default but will be moved into its own extension in the future.
|
||||
|
||||
.. _downloading: https://github.com/DanielSiepmann/search_core/archive/master.zip
|
||||
|
|
3
Makefile
3
Makefile
|
@ -15,6 +15,9 @@ install: clean
|
|||
COMPOSER_PROCESS_TIMEOUT=1000 composer require -vv --dev --prefer-source typo3/cms="$(TYPO3_VERSION)"
|
||||
git checkout composer.json
|
||||
|
||||
cgl:
|
||||
./.Build/bin/phpcs
|
||||
|
||||
functionalTests:
|
||||
typo3DatabaseName=$(typo3DatabaseName) \
|
||||
typo3DatabaseUsername=$(typo3DatabaseUsername) \
|
||||
|
|
78
Tests/Functional/Connection/Elasticsearch/FacetTest.php
Normal file
78
Tests/Functional/Connection/Elasticsearch/FacetTest.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Functional\Connection\Elasticsearch;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Domain\Index\IndexerFactory;
|
||||
use Codappix\SearchCore\Domain\Model\SearchRequest;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
|
||||
class FacetTest extends AbstractFunctionalTestCase
|
||||
{
|
||||
protected function getTypoScriptFilesForFrontendRootPage()
|
||||
{
|
||||
return array_merge(
|
||||
parent::getTypoScriptFilesForFrontendRootPage(),
|
||||
['EXT:search_core/Tests/Functional/Fixtures/Searching/Facet.ts']
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDataSets()
|
||||
{
|
||||
return array_merge(
|
||||
parent::getDataSets(),
|
||||
['Tests/Functional/Fixtures/Searching/Filter.xml']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function itsPossibleToFetchFacetsForField()
|
||||
{
|
||||
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(IndexerFactory::class)
|
||||
->getIndexer('tt_content')
|
||||
->indexAllDocuments()
|
||||
;
|
||||
|
||||
$searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(SearchService::class);
|
||||
|
||||
$searchRequest = new SearchRequest();
|
||||
$result = $searchService->search($searchRequest);
|
||||
|
||||
$this->assertSame(1, count($result->getFacets()), 'Did not receive the single defined facet.');
|
||||
|
||||
$facet = current($result->getFacets());
|
||||
$this->assertSame('contentTypes', $facet->getName(), 'Name of facet was not as expected.');
|
||||
$this->assertSame('CType', $facet->getField(), 'Field of facet was not expected.');
|
||||
|
||||
$options = $facet->getOptions();
|
||||
$this->assertSame(2, count($options), 'Did not receive the expected number of possible options for facet.');
|
||||
$option = $options['HTML'];
|
||||
$this->assertSame('HTML', $option->getName(), 'Option did not have expected Name.');
|
||||
$this->assertSame(1, $option->getCount(), 'Option did not have expected count.');
|
||||
$option = $options['Header'];
|
||||
$this->assertSame('Header', $option->getName(), 'Option did not have expected Name.');
|
||||
$this->assertSame(1, $option->getCount(), 'Option did not have expected count.');
|
||||
}
|
||||
}
|
|
@ -58,37 +58,4 @@ class FilterTest extends AbstractFunctionalTestCase
|
|||
$this->assertSame(5, (int) $result->getResults()[0]['uid'], 'Did not get the expected result entry.');
|
||||
$this->assertSame(1, count($result), 'Did not receive the single filtered element.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function itsPossibleToFetchFacetsForField()
|
||||
{
|
||||
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(IndexerFactory::class)
|
||||
->getIndexer('tt_content')
|
||||
->indexAllDocuments()
|
||||
;
|
||||
|
||||
$searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(SearchService::class);
|
||||
|
||||
$searchRequest = new SearchRequest();
|
||||
$result = $searchService->search($searchRequest);
|
||||
|
||||
$this->assertSame(1, count($result->getFacets()), 'Did not receive the single defined facet.');
|
||||
|
||||
$facet = current($result->getFacets());
|
||||
$this->assertSame('contentTypes', $facet->getName(), 'Name of facet was not as expected.');
|
||||
$this->assertSame('CType', $facet->getField(), 'Field of facet was not expected.');
|
||||
|
||||
$options = $facet->getOptions();
|
||||
$this->assertSame(2, count($options), 'Did not receive the expected number of possible options for facet.');
|
||||
$option = $options['HTML'];
|
||||
$this->assertSame('HTML', $option->getName(), 'Option did not have expected Name.');
|
||||
$this->assertSame(1, $option->getCount(), 'Option did not have expected count.');
|
||||
$option = $options['Header'];
|
||||
$this->assertSame('Header', $option->getName(), 'Option did not have expected Name.');
|
||||
$this->assertSame(1, $option->getCount(), 'Option did not have expected count.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Functional\DataProcessing;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\DataProcessing\ContentObjectDataProcessorAdapterProcessor;
|
||||
use Codappix\SearchCore\Tests\Functional\AbstractFunctionalTestCase;
|
||||
use TYPO3\CMS\Extbase\Service\TypoScriptService;
|
||||
use TYPO3\CMS\Frontend\DataProcessing\SplitProcessor;
|
||||
|
||||
class ContentObjectDataProcessorAdapterProcessorTest extends AbstractFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function contentObjectDataProcessorIsExecuted()
|
||||
{
|
||||
$record = ['content' => 'value1, value2'];
|
||||
$configuration = [
|
||||
'_dataProcessor' => SplitProcessor::class,
|
||||
'delimiter' => ',',
|
||||
'fieldName' => 'content',
|
||||
'as' => 'new_content',
|
||||
];
|
||||
$expectedData = [
|
||||
'content' => 'value1, value2',
|
||||
'new_content' => ['value1', 'value2'],
|
||||
];
|
||||
|
||||
$subject = new ContentObjectDataProcessorAdapterProcessor(new TypoScriptService);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -37,13 +37,6 @@ plugin {
|
|||
}
|
||||
|
||||
searching {
|
||||
fields = search_title
|
||||
facets {
|
||||
contentTypes {
|
||||
field = CType
|
||||
}
|
||||
}
|
||||
|
||||
fields {
|
||||
query = _all
|
||||
}
|
||||
|
|
17
Tests/Functional/Fixtures/Searching/Facet.ts
Normal file
17
Tests/Functional/Fixtures/Searching/Facet.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
plugin {
|
||||
tx_searchcore {
|
||||
settings {
|
||||
searching {
|
||||
facets {
|
||||
contentTypes {
|
||||
terms {
|
||||
field = CType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.tx_searchcore < plugin.tx_searchcore
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Functional\Hooks\DataHandler;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
|
||||
use Codappix\SearchCore\Domain\Service\DataHandler as DataHandlerService;
|
||||
use Codappix\SearchCore\Hook\DataHandler as DataHandlerHook;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler as Typo3DataHandler;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
|
||||
class IgnoresUnkownOperationTest extends AbstractDataHandlerTest
|
||||
{
|
||||
/**
|
||||
* @var DataHandlerService|\PHPUnit_Framework_MockObject_MockObject|AccessibleObjectInterface
|
||||
*/
|
||||
protected $subject;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataHandlerCommandSomethingIsIgnored()
|
||||
{
|
||||
$subject = new DataHandlerHook($this->subject);
|
||||
$this->assertFalse(
|
||||
$subject->processDatamap_afterDatabaseOperations(
|
||||
'something',
|
||||
'tt_content',
|
||||
1,
|
||||
[],
|
||||
new Typo3DataHandler
|
||||
),
|
||||
'Hook processed status "something".'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -64,7 +64,7 @@ class NonAllowedTablesTest extends AbstractDataHandlerTest
|
|||
/**
|
||||
* @test
|
||||
*/
|
||||
public function updateWillNotBeTriggeredForSysCategory()
|
||||
public function updateWillNotBeTriggeredForExistingSysCategory()
|
||||
{
|
||||
$this->subject->expects($this->exactly(0))->method('update');
|
||||
|
||||
|
@ -83,9 +83,9 @@ class NonAllowedTablesTest extends AbstractDataHandlerTest
|
|||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addWillNotBeTriggeredForSysCategoy()
|
||||
public function updateWillNotBeTriggeredForNewSysCategoy()
|
||||
{
|
||||
$this->subject->expects($this->exactly(0))->method('add');
|
||||
$this->subject->expects($this->exactly(0))->method('update');
|
||||
|
||||
$tce = GeneralUtility::makeInstance(Typo3DataHandler::class);
|
||||
$tce->stripslashes_values = 0;
|
||||
|
|
|
@ -66,7 +66,7 @@ class ProcessesAllowedTablesTest extends AbstractDataHandlerTest
|
|||
/**
|
||||
* @test
|
||||
*/
|
||||
public function updateWillBeTriggeredForTtContent()
|
||||
public function updateWillBeTriggeredForExistingTtContent()
|
||||
{
|
||||
$this->subject->expects($this->exactly(1))->method('update')
|
||||
->with(
|
||||
|
@ -94,9 +94,9 @@ class ProcessesAllowedTablesTest extends AbstractDataHandlerTest
|
|||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addWillBeTriggeredForTtContent()
|
||||
public function updateWillBeTriggeredForNewTtContent()
|
||||
{
|
||||
$this->subject->expects($this->exactly(1))->method('add')
|
||||
$this->subject->expects($this->exactly(1))->method('update')
|
||||
->with(
|
||||
$this->equalTo('tt_content'),
|
||||
$this->callback(function ($record) {
|
||||
|
|
|
@ -31,8 +31,11 @@ class ConfigurationUtilityTest extends AbstractUnitTestCase
|
|||
* @test
|
||||
* @dataProvider possibleRequestAndConfigurationForFluidtemplate
|
||||
*/
|
||||
public function recursiveEntriesAreProcessedAsFluidtemplate(SearchRequestInterface $searchRequest, array $array, array $expected)
|
||||
{
|
||||
public function recursiveEntriesAreProcessedAsFluidtemplate(
|
||||
SearchRequestInterface $searchRequest,
|
||||
array $array,
|
||||
array $expected
|
||||
) {
|
||||
$subject = new ConfigurationUtility();
|
||||
|
||||
$this->assertSame(
|
||||
|
|
64
Tests/Unit/Connection/Elasticsearch/FacetOptionTest.php
Normal file
64
Tests/Unit/Connection/Elasticsearch/FacetOptionTest.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Unit\Connection\Elasticsearch;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\Elasticsearch\FacetOption;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class FacetOptionTest extends AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function displayNameIsReturnedAsExpected()
|
||||
{
|
||||
$bucket = [
|
||||
'key' => 'Name',
|
||||
'key_as_string' => 'DisplayName',
|
||||
'doc_count' => 10,
|
||||
];
|
||||
$subject = new FacetOption($bucket);
|
||||
|
||||
$this->assertSame(
|
||||
$bucket['key_as_string'],
|
||||
$subject->getDisplayName(),
|
||||
'Display name was not returned as expected.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function displayNameIsReturnedAsExpectedIfNotProvided()
|
||||
{
|
||||
$bucket = [
|
||||
'key' => 'Name',
|
||||
'doc_count' => 10,
|
||||
];
|
||||
$subject = new FacetOption($bucket);
|
||||
|
||||
$this->assertSame(
|
||||
$bucket['key'],
|
||||
$subject->getDisplayName(),
|
||||
'Display name was not returned as expected.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -27,15 +27,15 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new CopyToProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Copy all fields to new field' => [
|
||||
|
@ -54,7 +54,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'new_field',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field 2' => 'Some more content like ipsum',
|
||||
'new_field' => 'Some content like lorem' . PHP_EOL . 'Some more content like ipsum',
|
||||
|
@ -71,7 +71,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'new_field',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
|
|
@ -27,15 +27,15 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function geoPointsAreAddedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function geoPointsAreAddedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new GeoPointProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Create new field with existing lat and lng' => [
|
||||
|
@ -56,7 +56,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 23.232,
|
||||
'lng' => 45.43,
|
||||
'location' => [
|
||||
|
@ -73,7 +73,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'location',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 23.232,
|
||||
'lng' => 45.43,
|
||||
],
|
||||
|
@ -88,7 +88,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => '',
|
||||
'lng' => '',
|
||||
],
|
||||
|
@ -103,7 +103,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 'av',
|
||||
'lng' => 'dsf',
|
||||
],
|
||||
|
|
|
@ -27,15 +27,15 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new RemoveProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Nothing configured' => [
|
||||
|
@ -56,7 +56,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
],
|
||||
'configuration' => [
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
@ -76,7 +76,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field with sub2',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
],
|
||||
],
|
||||
|
@ -92,7 +92,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'non existing',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
@ -113,7 +113,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field 3, field with sub2',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
],
|
||||
],
|
||||
|
@ -125,7 +125,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field 1',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -24,6 +24,7 @@ use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
|
|||
use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\DataProcessing\CopyToProcessor;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Index\AbstractIndexer;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
|
@ -44,17 +45,26 @@ class AbstractIndexerTest extends AbstractUnitTestCase
|
|||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->configuration = $this->getMockBuilder(ConfigurationContainerInterface::class)->getMock();
|
||||
$this->connection = $this->getMockBuilder(ConnectionInterface::class)->getMock();
|
||||
$this->dataProcessorService = $this->getMockBuilder(DataProcessorService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->subject = $this->getMockForAbstractClass(AbstractIndexer::class, [
|
||||
$this->connection,
|
||||
$this->configuration
|
||||
]);
|
||||
$this->inject($this->subject, 'dataProcessorService', $this->dataProcessorService);
|
||||
$this->subject->injectLogger($this->getMockedLogger());
|
||||
$this->subject->setIdentifier('testTable');
|
||||
$this->subject->expects($this->any())
|
||||
|
@ -73,7 +83,30 @@ class AbstractIndexerTest extends AbstractUnitTestCase
|
|||
$expectedRecord['new_test_field2'] = 'test' . PHP_EOL . 'test';
|
||||
$expectedRecord['search_abstract'] = '';
|
||||
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->dataProcessorService->expects($this->any())
|
||||
->method('executeDataProcessor')
|
||||
->withConsecutive(
|
||||
[
|
||||
[
|
||||
'_typoScriptNodeValue' => CopyToProcessor::class,
|
||||
'to' => 'new_test_field',
|
||||
],
|
||||
$record,
|
||||
],
|
||||
[
|
||||
[
|
||||
'_typoScriptNodeValue' => CopyToProcessor::class,
|
||||
'to' => 'new_test_field2',
|
||||
],
|
||||
array_merge($record, ['new_test_field' => 'test']),
|
||||
]
|
||||
)
|
||||
->will($this->onConsecutiveCalls(
|
||||
array_merge($record, ['new_test_field' => 'test']),
|
||||
$expectedRecord
|
||||
));
|
||||
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->withConsecutive(['indexing.testTable.dataProcessing'], ['indexing.testTable.abstractFields'])
|
||||
->will($this->onConsecutiveCalls([
|
||||
|
|
110
Tests/Unit/Domain/Model/ResultItemTest.php
Normal file
110
Tests/Unit/Domain/Model/ResultItemTest.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Domain\Model\ResultItem;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class ResultItemTest extends AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function plainDataCanBeRetrieved()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
$expectedData = $originalData;
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertSame(
|
||||
$expectedData,
|
||||
$subject->getPlainData(),
|
||||
'Could not retrieve plain data from result item.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanBeRetrievedInArrayNotation()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
$expectedData = $originalData;
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertSame(
|
||||
$originalData['title'],
|
||||
$subject['title'],
|
||||
'Could not retrieve title in array notation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function existenceOfDataCanBeChecked()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertTrue(isset($subject['title']), 'Could not determine that title exists.');
|
||||
$this->assertFalse(isset($subject['title2']), 'Could not determine that title2 does not exists.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanNotBeChanged()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$subject['title'] = 'New Title';
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanNotBeRemoved()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
unset($subject['title']);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,10 @@ namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\SearchRequest;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class SearchRequestTest extends AbstractUnitTestCase
|
||||
|
@ -31,12 +34,12 @@ class SearchRequestTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function emptyFilterWillNotBeSet(array $filter)
|
||||
{
|
||||
$searchRequest = new SearchRequest();
|
||||
$searchRequest->setFilter($filter);
|
||||
$subject = new SearchRequest();
|
||||
$subject->setFilter($filter);
|
||||
|
||||
$this->assertSame(
|
||||
[],
|
||||
$searchRequest->getFilter(),
|
||||
$subject->getFilter(),
|
||||
'Empty filter were set, even if they should not.'
|
||||
);
|
||||
}
|
||||
|
@ -68,13 +71,67 @@ class SearchRequestTest extends AbstractUnitTestCase
|
|||
public function filterIsSet()
|
||||
{
|
||||
$filter = ['someField' => 'someValue'];
|
||||
$searchRequest = new SearchRequest();
|
||||
$searchRequest->setFilter($filter);
|
||||
$subject = new SearchRequest();
|
||||
$subject->setFilter($filter);
|
||||
|
||||
$this->assertSame(
|
||||
$filter,
|
||||
$searchRequest->getFilter(),
|
||||
$subject->getFilter(),
|
||||
'Filter was not set.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionIsThrownIfSearchServiceWasNotSet()
|
||||
{
|
||||
$subject = new SearchRequest();
|
||||
$subject->setConnection($this->getMockBuilder(ConnectionInterface::class)->getMock());
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$subject->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionIsThrownIfConnectionWasNotSet()
|
||||
{
|
||||
$subject = new SearchRequest();
|
||||
$subject->setSearchService(
|
||||
$this->getMockBuilder(SearchService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$subject->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function executionMakesUseOfProvidedConnectionAndSearchService()
|
||||
{
|
||||
$searchServiceMock = $this->getMockBuilder(SearchService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$connectionMock = $this->getMockBuilder(ConnectionInterface::class)
|
||||
->getMock();
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)
|
||||
->getMock();
|
||||
|
||||
$subject = new SearchRequest();
|
||||
$subject->setSearchService($searchServiceMock);
|
||||
$subject->setConnection($connectionMock);
|
||||
|
||||
$connectionMock->expects($this->once())
|
||||
->method('search')
|
||||
->with($subject)
|
||||
->willReturn($searchResultMock);
|
||||
$searchServiceMock->expects($this->once())
|
||||
->method('processResult')
|
||||
->with($searchResultMock);
|
||||
|
||||
$subject->execute();
|
||||
}
|
||||
}
|
||||
|
|
100
Tests/Unit/Domain/Model/SearchResultTest.php
Normal file
100
Tests/Unit/Domain/Model/SearchResultTest.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class SearchResultTest extends AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function countIsRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('count');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function currentCountIsRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('getCurrentCount');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->getCurrentCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function facetsAreRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('getFacets');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->getFacets();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function resultItemsCanBeRetrieved()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$data = [
|
||||
[
|
||||
'uid' => 10,
|
||||
'title' => 'Some Title',
|
||||
],
|
||||
[
|
||||
'uid' => 11,
|
||||
'title' => 'Some Title 2',
|
||||
],
|
||||
[
|
||||
'uid' => 12,
|
||||
'title' => 'Some Title 3',
|
||||
],
|
||||
];
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, $data);
|
||||
$resultItems = $subject->getResults();
|
||||
|
||||
$this->assertCount(3, $resultItems);
|
||||
|
||||
$this->assertSame($data[0]['uid'], $resultItems[0]['uid']);
|
||||
$this->assertSame($data[1]['uid'], $resultItems[1]['uid']);
|
||||
$this->assertSame($data[2]['uid'], $resultItems[2]['uid']);
|
||||
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[0]);
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[1]);
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[2]);
|
||||
}
|
||||
}
|
|
@ -86,6 +86,58 @@ class QueryFactoryTest extends AbstractUnitTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rangeFilterIsAddedToQuery()
|
||||
{
|
||||
$this->configureConfigurationMockWithDefault();
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->will($this->returnCallback(function ($configName) {
|
||||
if ($configName === 'searching.mapping.filter.month') {
|
||||
return [
|
||||
'type' => 'range',
|
||||
'field' => 'released',
|
||||
'raw' => [
|
||||
'format' => 'yyyy-MM',
|
||||
],
|
||||
'fields' => [
|
||||
'gte' => 'from',
|
||||
'lte' => 'to',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter([
|
||||
'month' => [
|
||||
'from' => '2016-03',
|
||||
'to' => '2017-11',
|
||||
],
|
||||
]);
|
||||
|
||||
$query = $this->subject->create($searchRequest);
|
||||
$this->assertSame(
|
||||
[
|
||||
[
|
||||
'range' => [
|
||||
'released' => [
|
||||
'format' => 'yyyy-MM',
|
||||
'gte' => '2016-03',
|
||||
'lte' => '2017-11',
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
$query->toArray()['query']['bool']['filter'],
|
||||
'Filter was not added to query.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
|
@ -118,8 +170,8 @@ class QueryFactoryTest extends AbstractUnitTestCase
|
|||
{
|
||||
$this->configureConfigurationMockWithDefault();
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->addFacet(new FacetRequest('Identifier', 'FieldName'));
|
||||
$searchRequest->addFacet(new FacetRequest('Identifier 2', 'FieldName 2'));
|
||||
$searchRequest->addFacet(new FacetRequest('Identifier', ['terms' => ['field' => 'FieldName']]));
|
||||
$searchRequest->addFacet(new FacetRequest('Identifier 2', ['terms' => ['field' => 'FieldName 2']]));
|
||||
|
||||
$query = $this->subject->create($searchRequest);
|
||||
$this->assertSame(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Copyright\SearchCore\Tests\Unit\Domain\Search;
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Search;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
|
@ -23,7 +23,10 @@ namespace Copyright\SearchCore\Tests\Unit\Domain\Search;
|
|||
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
|
||||
use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Model\SearchRequest;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
@ -35,10 +38,38 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
protected $subject;
|
||||
|
||||
/**
|
||||
* @var SearchResultInterface
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var ConfigurationContainerInterface
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->result = $this->getMockBuilder(SearchResultInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->connection = $this->getMockBuilder(ConnectionInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
@ -48,11 +79,15 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
$this->objectManager = $this->getMockBuilder(ObjectManagerInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->dataProcessorService = $this->getMockBuilder(DataProcessorService::class)
|
||||
->setConstructorArgs([$this->objectManager])
|
||||
->getMock();
|
||||
|
||||
$this->subject = new SearchService(
|
||||
$this->connection,
|
||||
$this->configuration,
|
||||
$this->objectManager
|
||||
$this->objectManager,
|
||||
$this->dataProcessorService
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -61,19 +96,19 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function sizeIsAddedFromConfiguration()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(45, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getLimit() === 45;
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -84,19 +119,19 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function defaultSizeIsAddedIfNothingIsConfigured()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getLimit() === 10;
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -107,20 +142,23 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function configuredFilterAreAddedToRequestWithoutAnyFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['property' => 'something']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['property' => 'something'],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['property' => 'something'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -131,14 +169,16 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function configuredFilterAreAddedToRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['property' => 'something']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['property' => 'something'],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
|
@ -147,7 +187,8 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
'anotherProperty' => 'anything',
|
||||
'property' => 'something',
|
||||
];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
|
@ -159,20 +200,20 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function nonConfiguredFilterIsNotChangingRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['anotherProperty' => 'anything'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
|
@ -184,23 +225,101 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function emptyConfiguredFilterIsNotChangingRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['anotherProperty' => '']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['anotherProperty' => ''],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['anotherProperty' => 'anything'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
$this->subject->search($searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function originalSearchResultIsReturnedIfNoDataProcessorIsConfigured()
|
||||
{
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn($searchResultMock);
|
||||
|
||||
$this->dataProcessorService->expects($this->never())->method('executeDataProcessor');
|
||||
|
||||
$searchRequest = new SearchRequest('');
|
||||
$this->assertSame(
|
||||
$searchResultMock,
|
||||
$this->subject->search($searchRequest),
|
||||
'Did not get created result without applied data processing'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configuredDataProcessorsAreExecutedOnSearchResult()
|
||||
{
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->onConsecutiveCalls(
|
||||
$this->throwException(new InvalidArgumentException),
|
||||
['SomeProcessorClass']
|
||||
));
|
||||
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$searchResult = new SearchResult($searchResultMock, [['field 1' => 'value 1']]);
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn($searchResult);
|
||||
|
||||
$this->dataProcessorService->expects($this->once())
|
||||
->method('executeDataProcessor')
|
||||
->with('SomeProcessorClass', ['field 1' => 'value 1'])
|
||||
->willReturn([
|
||||
'field 1' => 'value 1',
|
||||
'field 2' => 'value 2',
|
||||
]);
|
||||
|
||||
$this->objectManager->expects($this->once())
|
||||
->method('get')
|
||||
->with(SearchResult::class, $searchResult, [
|
||||
['field 1' => 'value 1', 'field 2' => 'value 2']
|
||||
])
|
||||
->willReturn($searchResultMock);
|
||||
|
||||
$searchRequest = new SearchRequest('');
|
||||
$this->assertSame(
|
||||
$searchResultMock,
|
||||
$this->subject->search($searchRequest),
|
||||
'Did not get created result with applied data processing'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"TYPO3\\CMS\\Core\\Tests\\": ".Build/vendor/typo3/cms/typo3/sysext/core/Tests/"
|
||||
}
|
||||
},
|
||||
"require" : {
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"typo3/cms": "~7.6",
|
||||
"ruflin/elastica": "~3.2"
|
||||
|
|
4
ext_conf_template.txt
Normal file
4
ext_conf_template.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
disable {
|
||||
# cat=basic/enable; type=boolean; label=Disable Elasticsearch, which is enabled by default
|
||||
elasticsearch = true
|
||||
}
|
|
@ -37,11 +37,18 @@ call_user_func(
|
|||
]
|
||||
);
|
||||
|
||||
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\Container\Container')
|
||||
->registerImplementation(
|
||||
'Codappix\SearchCore\Connection\ConnectionInterface',
|
||||
'Codappix\SearchCore\Connection\Elasticsearch'
|
||||
);
|
||||
// API does make use of object manager, therefore use GLOBALS
|
||||
$extensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey]);
|
||||
if ($extensionConfiguration === false
|
||||
|| !isset($extensionConfiguration['disable.']['elasticsearch'])
|
||||
|| $extensionConfiguration['disable.']['elasticsearch'] !== '1'
|
||||
) {
|
||||
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
|
||||
->registerImplementation(
|
||||
\Codappix\SearchCore\Connection\ConnectionInterface::class,
|
||||
\Codappix\SearchCore\Connection\Elasticsearch::class
|
||||
);
|
||||
}
|
||||
},
|
||||
$_EXTKEY
|
||||
);
|
||||
|
|
23
phpcs.xml.dist
Normal file
23
phpcs.xml.dist
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="search_core">
|
||||
<description>The coding standard for search_core.</description>
|
||||
|
||||
<file>Classes/</file>
|
||||
<file>Tests/</file>
|
||||
|
||||
<!-- Set default settings -->
|
||||
<arg value="sp"/>
|
||||
<arg name="colors"/>
|
||||
<arg name="encoding" value="utf-8" />
|
||||
<arg name="extensions" value="php,php.dist,inc" />
|
||||
|
||||
<!-- Base rules -->
|
||||
<rule ref="PSR2">
|
||||
<!-- As it does not work with new array syntax. -->
|
||||
<exclude name="Squiz.Arrays.ArrayBracketSpacing.SpaceBeforeBracket" />
|
||||
</rule>
|
||||
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
|
||||
<!-- We have to follow the TYPO3 hook method names. -->
|
||||
<exclude-pattern>Classes/Hook/DataHandler.php</exclude-pattern>
|
||||
</rule>
|
||||
</ruleset>
|
Loading…
Reference in a new issue