Merge pull request #86 from Codappix/feature/predefined-filter

Feature: Predefined filter
This commit is contained in:
Daniel Siepmann 2017-09-15 22:56:47 +02:00 committed by GitHub
commit 0dd65085b6
9 changed files with 281 additions and 7 deletions

View file

@ -44,6 +44,17 @@ class SearchController extends ActionController
parent::__construct();
}
public function initializeSearchAction()
{
if (isset($this->settings['searching']['mode']) && $this->settings['searching']['mode'] === 'filter'
&& $this->request->hasArgument('searchRequest') === false
) {
$this->request->setArguments([
'searchRequest' => $this->objectManager->get(SearchRequest::class),
]);
}
}
/**
* Process a search and deliver original request and result to view.
*

View file

@ -66,7 +66,7 @@ class SearchRequest implements SearchRequestInterface
/**
* @param string $query
*/
public function __construct($query)
public function __construct($query = '')
{
$this->query = (string) $query;
}

View file

@ -89,7 +89,7 @@ class QueryFactory
/**
* @param SearchRequestInterface $searchRequest
* @param array &$query
* @param array $query
*/
protected function addSize(SearchRequestInterface $searchRequest, array &$query)
{
@ -101,10 +101,14 @@ class QueryFactory
/**
* @param SearchRequestInterface $searchRequest
* @param array &$query
* @param array $query
*/
protected function addSearch(SearchRequestInterface $searchRequest, array &$query)
{
if (trim($searchRequest->getSearchTerm()) === '') {
return;
}
$query = ArrayUtility::setValueByPath(
$query,
'query.bool.must.0.match._all.query',
@ -123,7 +127,7 @@ class QueryFactory
/**
* @param SearchRequestInterface $searchRequest
* @param array &$query
* @param array $query
*/
protected function addBoosts(SearchRequestInterface $searchRequest, array &$query)
{
@ -156,7 +160,7 @@ class QueryFactory
}
/**
* @param array &$query
* @param array $query
*/
protected function addFactorBoost(array &$query)
{
@ -174,7 +178,7 @@ class QueryFactory
/**
* @param SearchRequestInterface $searchRequest
* @param array &$query
* @param array $query
*/
protected function addFilter(SearchRequestInterface $searchRequest, array &$query)
{
@ -202,7 +206,7 @@ class QueryFactory
/**
* @param SearchRequestInterface $searchRequest
* @param array &$query
* @param array $query
*/
protected function addFacets(SearchRequestInterface $searchRequest, array &$query)
{

View file

@ -72,6 +72,7 @@ class SearchService
$searchRequest->setConnection($this->connection);
$this->addSize($searchRequest);
$this->addConfiguredFacets($searchRequest);
$this->addConfiguredFilters($searchRequest);
return $this->connection->search($searchRequest);
}
@ -113,4 +114,21 @@ class SearchService
));
}
}
/**
* Add filters from configuration, e.g. flexform or TypoScript.
*
* @param SearchRequestInterface $searchRequest
*/
protected function addConfiguredFilters(SearchRequestInterface $searchRequest)
{
try {
$searchRequest->setFilter(array_merge(
$searchRequest->getFilter(),
$this->configuration->get('searching.filter')
));
} catch (InvalidArgumentException $e) {
// Nothing todo, no filter configured.
}
}
}

View file

@ -0,0 +1,3 @@
<?php
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['searchcore_search'] = 'recursive,pages';

View file

@ -279,6 +279,23 @@ Searching
The above example will provide a facet with options for all found ``CType`` results together
with a count.
.. _filter:
``filter``
"""""""""""
Used by: While building search request.
Define filter that should be set for all requests.
Example::
plugin.tx_searchcore.settings.searching.filter {
property = value
}
For Elasticsearch the fields have to be filterable, e.g. need a mapping as ``keyword``.
.. _minimumShouldMatch:
``minimumShouldMatch``
@ -329,3 +346,20 @@ Searching
factor = 2
missing = 1
}
.. _mode:
``mode``
""""""""
Used by: Controller while preparing action.
Define to switch from search to filter mode.
Example::
plugin.tx_searchcore.settings.searching {
mode = filter
}
Only ``filter`` is allowed as value. Will submit an empty query to switch to filter mode.

View file

@ -0,0 +1,99 @@
<?php
namespace Codappix\Tests\Unit\Controller;
/*
* 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\Controller\SearchController;
use Codappix\SearchCore\Domain\Model\SearchRequest;
use Codappix\SearchCore\Domain\Search\SearchService;
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
use TYPO3\CMS\Extbase\Mvc\Web\Request;
use TYPO3\CMS\Extbase\Object\ObjectManager;
class SearchControllerTest extends AbstractUnitTestCase
{
/**
* @var SearchController
*/
protected $subject;
/**
* @var Request
*/
protected $request;
public function setUp()
{
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\Cache\CacheManager::class
)->setCacheConfigurations([
'extbase_object' => [
'backend' => \TYPO3\CMS\Core\Cache\Backend\NullBackend::class,
],
'extbase_datamapfactory_datamap' => [
'backend' => \TYPO3\CMS\Core\Cache\Backend\NullBackend::class,
],
]);
parent::setUp();
$searchService = $this->getMockBuilder(SearchService::class)
->disableOriginalConstructor()
->getMock();
$this->request = new Request();
$this->subject = new SearchController($searchService);
$this->inject($this->subject, 'request', $this->request);
$this->inject($this->subject, 'objectManager', new ObjectManager());
}
/**
* @test
*/
public function searchRequestArgumentIsAddedIfModeIsFilterAndArgumentDoesNotExist()
{
$this->inject($this->subject, 'settings', [
'searching' => [
'mode' => 'filter',
]
]);
$this->subject->initializeSearchAction();
$this->assertInstanceOf(
SearchRequest::class,
$this->request->getArgument('searchRequest'),
'Search request was not created.'
);
}
/**
* @test
*/
public function searchRequestArgumentIsNotAddedIfModeIsNotFilter()
{
$this->inject($this->subject, 'settings', ['searching' => []]);
$this->subject->initializeSearchAction();
$this->assertFalse(
$this->request->hasArgument('searchRequest'),
'Search request should not exist.'
);
}
}

View file

@ -324,4 +324,23 @@ class QueryFactoryTest extends AbstractUnitTestCase
'Boosts were not added to query.'
);
}
/**
* @test
*/
public function emptySearchStringWillNotAddSearchToQuery()
{
$searchRequest = new SearchRequest();
$this->configuration->expects($this->any())
->method('get')
->will($this->throwException(new InvalidArgumentException));
$query = $this->subject->create($searchRequest);
$this->assertInstanceOf(
stdClass,
$query->toArray()['query']['match_all'],
'Empty search request does not create expected query.'
);
}
}

View file

@ -21,6 +21,7 @@ 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\Domain\Model\SearchRequest;
use Codappix\SearchCore\Domain\Search\SearchService;
@ -64,6 +65,10 @@ class SearchServiceTest extends AbstractUnitTestCase
->method('getIfExists')
->withConsecutive(['searching.size'], ['searching.facets'])
->will($this->onConsecutiveCalls(45, null));
$this->configuration->expects($this->exactly(1))
->method('get')
->with('searching.filter')
->will($this->throwException(new InvalidArgumentException));
$this->connection->expects($this->once())
->method('search')
->with($this->callback(function ($searchRequest) {
@ -83,6 +88,10 @@ class SearchServiceTest extends AbstractUnitTestCase
->method('getIfExists')
->withConsecutive(['searching.size'], ['searching.facets'])
->will($this->onConsecutiveCalls(null, null));
$this->configuration->expects($this->exactly(1))
->method('get')
->with('searching.filter')
->will($this->throwException(new InvalidArgumentException));
$this->connection->expects($this->once())
->method('search')
->with($this->callback(function ($searchRequest) {
@ -92,4 +101,81 @@ class SearchServiceTest extends AbstractUnitTestCase
$searchRequest = new SearchRequest('SearchWord');
$this->subject->search($searchRequest);
}
/**
* @test
*/
public function configuredFilterAreAddedToRequestWithoutAnyFilter()
{
$this->configuration->expects($this->exactly(2))
->method('getIfExists')
->withConsecutive(['searching.size'], ['searching.facets'])
->will($this->onConsecutiveCalls(null, null));
$this->configuration->expects($this->exactly(1))
->method('get')
->with('searching.filter')
->willReturn(['property' => 'something']);
$this->connection->expects($this->once())
->method('search')
->with($this->callback(function ($searchRequest) {
return $searchRequest->getFilter() === ['property' => 'something'];
}));
$searchRequest = new SearchRequest('SearchWord');
$this->subject->search($searchRequest);
}
/**
* @test
*/
public function configuredFilterAreAddedToRequestWithExistingFilter()
{
$this->configuration->expects($this->exactly(2))
->method('getIfExists')
->withConsecutive(['searching.size'], ['searching.facets'])
->will($this->onConsecutiveCalls(null, null));
$this->configuration->expects($this->exactly(1))
->method('get')
->with('searching.filter')
->willReturn(['property' => 'something']);
$this->connection->expects($this->once())
->method('search')
->with($this->callback(function ($searchRequest) {
return $searchRequest->getFilter() === [
'anotherProperty' => 'anything',
'property' => 'something',
];
}));
$searchRequest = new SearchRequest('SearchWord');
$searchRequest->setFilter(['anotherProperty' => 'anything']);
$this->subject->search($searchRequest);
}
/**
* @test
*/
public function nonConfiguredFilterIsNotChangingRequestWithExistingFilter()
{
$this->configuration->expects($this->exactly(2))
->method('getIfExists')
->withConsecutive(['searching.size'], ['searching.facets'])
->will($this->onConsecutiveCalls(null, null));
$this->configuration->expects($this->exactly(1))
->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'];
}));
$searchRequest = new SearchRequest('SearchWord');
$searchRequest->setFilter(['anotherProperty' => 'anything']);
$this->subject->search($searchRequest);
}
}