diff --git a/.travis.yml b/.travis.yml
index 79bf8f9..3253c3c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,12 @@
+sudo: true
+
+addons:
+ apt:
+ packages:
+ - oracle-java8-set-default
+before_install:
+ - curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.0.deb && sudo dpkg -i --force-confnew elasticsearch-5.2.0.deb && sudo service elasticsearch start
+
language: php
php:
@@ -40,11 +49,12 @@ matrix:
services:
- mysql
- - elasticsearch
install: make install
-script: make functionalTests
+script:
+ - make unitTests
+ - make functionalTests
after_script:
- make uploadCodeCoverage
diff --git a/Classes/Connection/Elasticsearch.php b/Classes/Connection/Elasticsearch.php
index 560ca07..386e19c 100644
--- a/Classes/Connection/Elasticsearch.php
+++ b/Classes/Connection/Elasticsearch.php
@@ -21,6 +21,7 @@ namespace Leonmrni\SearchCore\Connection;
*/
use TYPO3\CMS\Core\SingletonInterface as Singleton;
+use Leonmrni\SearchCore\Domain\Search\QueryFactory;
/**
* Outer wrapper to elasticsearch.
@@ -47,6 +48,11 @@ class Elasticsearch implements Singleton, ConnectionInterface
*/
protected $documentFactory;
+ /**
+ * @var QueryFactory
+ */
+ protected $queryFactory;
+
/**
* @var \TYPO3\CMS\Core\Log\Logger
*/
@@ -67,17 +73,20 @@ class Elasticsearch implements Singleton, ConnectionInterface
* @param Elasticsearch\IndexFactory $indexFactory
* @param Elasticsearch\TypeFactory $typeFactory
* @param Elasticsearch\DocumentFactory $documentFactory
+ * @param QueryFactory $queryFactory
*/
public function __construct(
Elasticsearch\Connection $connection,
Elasticsearch\IndexFactory $indexFactory,
Elasticsearch\TypeFactory $typeFactory,
- Elasticsearch\DocumentFactory $documentFactory
+ Elasticsearch\DocumentFactory $documentFactory,
+ QueryFactory $queryFactory
) {
$this->connection = $connection;
$this->indexFactory = $indexFactory;
$this->typeFactory = $typeFactory;
$this->documentFactory = $documentFactory;
+ $this->queryFactory = $queryFactory;
}
public function addDocument($documentType, array $document)
@@ -148,10 +157,11 @@ class Elasticsearch implements Singleton, ConnectionInterface
$search = new \Elastica\Search($this->connection->getClient());
$search->addIndex('typo3content');
+ $search->setQuery($this->queryFactory->create($this, $searchRequest));
// TODO: Return wrapped result to implement our interface.
// Also update php doc to reflect the change.
- return $search->search('"' . $searchRequest->getSearchTerm() . '"');
+ return $search->search();
}
/**
diff --git a/Classes/Connection/SearchRequestInterface.php b/Classes/Connection/SearchRequestInterface.php
index ecf7a74..603c02f 100644
--- a/Classes/Connection/SearchRequestInterface.php
+++ b/Classes/Connection/SearchRequestInterface.php
@@ -31,4 +31,14 @@ interface SearchRequestInterface
* @return string
*/
public function getSearchTerm();
+
+ /**
+ * @return bool
+ */
+ public function hasFilter();
+
+ /**
+ * @return array
+ */
+ public function getFilter();
}
diff --git a/Classes/Domain/Model/SearchRequest.php b/Classes/Domain/Model/SearchRequest.php
index 1de2f71..f1ad18e 100644
--- a/Classes/Domain/Model/SearchRequest.php
+++ b/Classes/Domain/Model/SearchRequest.php
@@ -32,14 +32,19 @@ class SearchRequest implements SearchRequestInterface
*
* @var string
*/
- protected $query;
+ protected $query = '';
+
+ /**
+ * @var array
+ */
+ protected $filter = [];
/**
* @param string $query
*/
public function __construct($query)
{
- $this->query = $query;
+ $this->query = (string) $query;
}
/**
@@ -57,4 +62,28 @@ class SearchRequest implements SearchRequestInterface
{
return $this->query;
}
+
+ /**
+ * @param array $filter
+ */
+ public function setFilter(array $filter)
+ {
+ $this->filter = array_map('strval', $filter);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasFilter()
+ {
+ return count($this->filter) > 0;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFilter()
+ {
+ return $this->filter;
+ }
}
diff --git a/Classes/Domain/Search/QueryFactory.php b/Classes/Domain/Search/QueryFactory.php
new file mode 100644
index 0000000..31c50df
--- /dev/null
+++ b/Classes/Domain/Search/QueryFactory.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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 Leonmrni\SearchCore\Connection\ConnectionInterface;
+use Leonmrni\SearchCore\Connection\Elasticsearch\Query;
+use Leonmrni\SearchCore\Connection\SearchRequestInterface;
+
+class QueryFactory
+{
+ /**
+ * @param ConnectionInterface $connection
+ * @param SearchRequestInterface $searchRequest
+ *
+ * @return \Elastica\Query
+ */
+ public function create(
+ ConnectionInterface $connection,
+ SearchRequestInterface $searchRequest
+ ) {
+ return $this->createElasticaQuery($searchRequest);
+ }
+
+ /**
+ * @param SearchRequestInterface $searchRequest
+ * @return \Elastica\Query
+ */
+ protected function createElasticaQuery(SearchRequestInterface $searchRequest)
+ {
+ $query = [
+ 'bool' => [
+ 'must' => [
+ [
+ 'match' => [
+ '_all' => $searchRequest->getSearchTerm()
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ if ($searchRequest->hasFilter()) {
+ $query['bool']['filter'] = ['term' => $searchRequest->getFilter()];
+ }
+
+ return new \Elastica\Query(['query' => $query]);
+ }
+}
diff --git a/Configuration/TypoScript/constants.txt b/Configuration/TypoScript/constants.txt
old mode 100755
new mode 100644
diff --git a/Configuration/TypoScript/setup.txt b/Configuration/TypoScript/setup.txt
old mode 100755
new mode 100644
diff --git a/Documentation/source/features.rst b/Documentation/source/features.rst
new file mode 100644
index 0000000..c715512
--- /dev/null
+++ b/Documentation/source/features.rst
@@ -0,0 +1,41 @@
+.. _features:
+
+Features
+========
+
+The following features are currently provided:
+
+.. _features_indexing:
+
+Indexing
+--------
+
+Indexing data to Elasticsearch is provided. The extension delivers an indexer for TCA with zero
+configuration needs. Still it's possible to configure the indexer.
+
+Own indexer are not possible yet, but will.
+
+.. _features_search:
+
+Searching
+---------
+
+Currently all fields are searched for a single search input.
+
+Also multiple filter are supported. Filtering results by fields for string contents.
+
+.. _features_planned:
+
+Planned
+---------
+
+The following features are currently planned and will be integrated:
+
+#. Mapping Configuration
+ Allowing to configure the whole mapping, to define type of input, e.g. integer, keyword.
+
+
+#. Facets / Aggregates
+ Based on the mapping configuration, facets will be configurable and fetched. Therefore mapping is
+ required and we will adjust the result set to be of a custom model providing all information in a
+ more clean way.
diff --git a/Documentation/source/index.rst b/Documentation/source/index.rst
index dfda13b..5427fc9 100644
--- a/Documentation/source/index.rst
+++ b/Documentation/source/index.rst
@@ -7,6 +7,7 @@ Table of Contents
:maxdepth: 1
:glob:
+ features
installation
configuration
usage
diff --git a/Documentation/source/usage.rst b/Documentation/source/usage.rst
index 1fc0e51..453b17d 100644
--- a/Documentation/source/usage.rst
+++ b/Documentation/source/usage.rst
@@ -37,3 +37,30 @@ Searching / Frontend Plugin
To provide a search interface you can insert the frontend Plugin as normal content element of type
plugin. The plugin is named *Search Core*.
+
+Please provide your own template, the extension will not deliver a useful template for now.
+
+The extbase mapping is used, this way you can create a form:
+
+.. code-block:: html
+
+
+
+
+
+
+.. _usage_searching_filter:
+
+Filter
+""""""
+
+Thanks to extbase mapping, filter are added to the form:
+
+.. code-block:: html
+ :emphasize-lines: 3
+
+
+
+
+
+
diff --git a/Makefile b/Makefile
index 589dd7d..a84afea 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,11 @@ functionalTests:
.Build/bin/phpunit --colors --debug -v \
-c Tests/Functional/FunctionalTests.xml
+unitTests:
+ TYPO3_PATH_WEB=$(TYPO3_WEB_DIR) \
+ .Build/bin/phpunit --colors --debug -v \
+ -c Tests/Unit/UnitTests.xml
+
uploadCodeCoverage: uploadCodeCoverageToScrutinizer uploadCodeCoverageToCodacy
uploadCodeCoverageToScrutinizer:
diff --git a/Tests/Functional/Connection/Elasticsearch/AbstractFunctionalTestCase.php b/Tests/Functional/Connection/Elasticsearch/AbstractFunctionalTestCase.php
index cdd8ddf..363ad6d 100644
--- a/Tests/Functional/Connection/Elasticsearch/AbstractFunctionalTestCase.php
+++ b/Tests/Functional/Connection/Elasticsearch/AbstractFunctionalTestCase.php
@@ -43,11 +43,18 @@ abstract class AbstractFunctionalTestCase extends BaseFunctionalTestCase
'host' => getenv('ES_HOST') ?: \Elastica\Connection::DEFAULT_HOST,
'port' => getenv('ES_PORT') ?: \Elastica\Connection::DEFAULT_PORT,
]);
+
+ $this->cleanUp();
}
public function tearDown()
{
// Delete everything so next test starts clean.
+ $this->cleanUp();
+ }
+
+ protected function cleanUp()
+ {
$this->client->getIndex('_all')->delete();
$this->client->getIndex('_all')->clearCache();
}
diff --git a/Tests/Functional/Connection/Elasticsearch/FilterTest.php b/Tests/Functional/Connection/Elasticsearch/FilterTest.php
new file mode 100644
index 0000000..88ae37f
--- /dev/null
+++ b/Tests/Functional/Connection/Elasticsearch/FilterTest.php
@@ -0,0 +1,61 @@
+
+ *
+ * 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 Leonmrni\SearchCore\Domain\Index\IndexerFactory;
+use Leonmrni\SearchCore\Domain\Model\SearchRequest;
+use Leonmrni\SearchCore\Domain\Search\SearchService;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+
+class FilterTest extends AbstractFunctionalTestCase
+{
+ protected function getDataSets()
+ {
+ return array_merge(
+ parent::getDataSets(),
+ ['Tests/Functional/Fixtures/Searching/Filter.xml']
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function itsPossibleToFilterResultsByASingleField()
+ {
+ \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('Search Word');
+
+ $result = $searchService->search($searchRequest);
+ $this->assertSame(2, count($result), 'Did not receive both indexed elements without filter.');
+
+ $searchRequest->setFilter(['CType' => 'html']);
+ $result = $searchService->search($searchRequest);
+ $this->assertSame('5', $result[0]->getData()['uid'], 'Did not get the expected result entry.');
+ $this->assertSame(1, count($result), 'Did not receive the single filtered element.');
+ }
+}
diff --git a/Tests/Functional/Fixtures/Searching/Filter.xml b/Tests/Functional/Fixtures/Searching/Filter.xml
new file mode 100644
index 0000000..103a4da
--- /dev/null
+++ b/Tests/Functional/Fixtures/Searching/Filter.xml
@@ -0,0 +1,42 @@
+
+
+
+ 5
+ 1
+ 1480686370
+ 1480686370
+ 0
+ 72
+ html
+ indexed content element with html ctype
+ Search Word
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+
+ 6
+ 1
+ 1480686370
+ 1480686370
+ 0
+ 72
+ header
+ indexed content element with header ctype
+ Search Word
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
diff --git a/Tests/Unit/AbstractUnitTestCase.php b/Tests/Unit/AbstractUnitTestCase.php
new file mode 100644
index 0000000..f3281c1
--- /dev/null
+++ b/Tests/Unit/AbstractUnitTestCase.php
@@ -0,0 +1,27 @@
+
+ *
+ * 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\Tests\UnitTestCase as CoreTestCase;
+
+abstract class AbstractUnitTestCase extends CoreTestCase
+{
+}
diff --git a/Tests/Unit/Domain/Search/QueryFactoryTest.php b/Tests/Unit/Domain/Search/QueryFactoryTest.php
new file mode 100644
index 0000000..83f031e
--- /dev/null
+++ b/Tests/Unit/Domain/Search/QueryFactoryTest.php
@@ -0,0 +1,99 @@
+
+ *
+ * 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 Leonmrni\SearchCore\Connection;
+use Leonmrni\SearchCore\Domain\Model\SearchRequest;
+use Leonmrni\SearchCore\Domain\Search\QueryFactory;
+use Leonmrni\SearchCore\Tests\Unit\AbstractUnitTestCase;
+
+class QueryFactoryTest extends AbstractUnitTestCase
+{
+ protected $subject;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->subject = new QueryFactory;
+ }
+
+ /**
+ * @test
+ */
+ public function creatonOfQueryWorksInGeneral()
+ {
+ $connection = $this->getMockBuilder(Connection\Elasticsearch::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $searchRequest = new SearchRequest('SearchWord');
+
+ $query = $this->subject->create($connection, $searchRequest);
+ $this->assertInstanceOf(
+ \Elastica\Query::class,
+ $query,
+ 'Factory did not create the expected instance.'
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function filterIsAddedToQuery()
+ {
+ $connection = $this->getMockBuilder(Connection\Elasticsearch::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $searchRequest = new SearchRequest('SearchWord');
+ $searchRequest->setFilter(['field' => 'content']);
+
+ $query = $this->subject->create($connection, $searchRequest);
+ $this->assertSame(
+ ['field' => 'content'],
+ $query->toArray()['query']['bool']['filter']['term'],
+ 'Filter was not added to query.'
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function userInputIsAlwaysString()
+ {
+ $connection = $this->getMockBuilder(Connection\Elasticsearch::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $searchRequest = new SearchRequest(10);
+ $searchRequest->setFilter(['field' => 20]);
+
+ $query = $this->subject->create($connection, $searchRequest);
+ $this->assertSame(
+ '10',
+ $query->toArray()['query']['bool']['must'][0]['match']['_all'],
+ 'Search word was not escaped as expected.'
+ );
+ $this->assertSame(
+ '20',
+ $query->toArray()['query']['bool']['filter']['term']['field'],
+ 'Search word was not escaped as expected.'
+ );
+ }
+}
diff --git a/Tests/Unit/UnitTests.xml b/Tests/Unit/UnitTests.xml
new file mode 100644
index 0000000..6456405
--- /dev/null
+++ b/Tests/Unit/UnitTests.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ .
+
+
+
+
+
+ ../../Classes
+
+
+