From 162d383a9d76ac633f146ac282407a5b3dae2e2c Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 13 Dec 2016 12:33:54 +0100 Subject: [PATCH] FEATURE: Allow to configure additional where statement for indexing * Provide TypoScript option to extend where clause to skip further records, e.g. tt_content by CType. --- .../Configuration/ConfigurationContainer.php | 83 +++++++++++++++++++ .../ConfigurationContainerInterface.php | 49 +++++++++++ .../InvalidArgumentException.php | 29 +++++++ .../Connection/Elasticsearch/Connection.php | 43 +++------- .../Index/TcaIndexer/TcaTableService.php | 20 ++++- Configuration/TypoScript/setup.txt | 6 ++ .../Fixtures/Indexing/IndexTcaTable.xml | 2 +- .../Fixtures/Indexing/UserWhereClause.ts | 13 +++ .../Fixtures/Indexing/UserWhereClause.xml | 42 ++++++++++ .../Functional/Indexing/IndexTcaTableTest.php | 40 ++++++++- 10 files changed, 291 insertions(+), 36 deletions(-) create mode 100644 Classes/Configuration/ConfigurationContainer.php create mode 100644 Classes/Configuration/ConfigurationContainerInterface.php create mode 100644 Classes/Configuration/InvalidArgumentException.php create mode 100644 Tests/Functional/Fixtures/Indexing/UserWhereClause.ts create mode 100644 Tests/Functional/Fixtures/Indexing/UserWhereClause.xml diff --git a/Classes/Configuration/ConfigurationContainer.php b/Classes/Configuration/ConfigurationContainer.php new file mode 100644 index 0000000..f5a50b8 --- /dev/null +++ b/Classes/Configuration/ConfigurationContainer.php @@ -0,0 +1,83 @@ + + * + * 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\Configuration\ConfigurationManagerInterface; + +/** + * Container of all configurations for extension. + * Always inject this to have a single place for configuration and parsing only once. + */ +class ConfigurationContainer implements ConfigurationContainerInterface +{ + /** + * Plaint TypoScript array from extbase for extension / plugin. + * + * @var array + */ + protected $settings; + + /** + * Inject news settings via ConfigurationManager. + * + * @param ConfigurationManagerInterface $configurationManager + */ + public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager) + { + $this->settings = $configurationManager->getConfiguration( + ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, + 'SearchCore', + 'search' + ); + } + + /** + * @param string $section + * @param string $key + * @return mixed + * @throws InvalidArgumentException + */ + public function get($section, $key) + { + if (!isset($this->settings[$section]) || !isset($this->settings[$section][$key])) { + throw new InvalidArgumentException( + 'The given configuration option does not exit.', + InvalidArgumentException::OPTION_DOES_NOT_EXIST + ); + } + + return $this->settings[$section][$key]; + } + + /** + * @param string $section + * @param string $key + * @return mixed|null + */ + public function getIfExists($section, $key) + { + try { + return $this->get($section, $key); + } catch (InvalidArgumentException $e) { + return null; + } + } +} diff --git a/Classes/Configuration/ConfigurationContainerInterface.php b/Classes/Configuration/ConfigurationContainerInterface.php new file mode 100644 index 0000000..2b988c5 --- /dev/null +++ b/Classes/Configuration/ConfigurationContainerInterface.php @@ -0,0 +1,49 @@ + + * + * 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\SingletonInterface as Singleton; + +/** + * Container of all configurations for extension. + * Always inject this to have a single place for configuration and parsing only once. + */ +interface ConfigurationContainerInterface extends Singleton +{ + /** + * Returns the option defined by section and key. + * May throw an exception if it's not set. + * + * @param string $section + * @param string $key + * @return mixed + */ + public function get($section, $key); + + /** + * Same as get but will not throw an exception but return null. + * + * @param string $section + * @param string $key + * @return mixed|null + */ + public function getIfExists($section, $key); +} diff --git a/Classes/Configuration/InvalidArgumentException.php b/Classes/Configuration/InvalidArgumentException.php new file mode 100644 index 0000000..078485a --- /dev/null +++ b/Classes/Configuration/InvalidArgumentException.php @@ -0,0 +1,29 @@ + + * + * 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. + */ + +/** + * + */ +class InvalidArgumentException extends \InvalidArgumentException +{ + const OPTION_DOES_NOT_EXIST = 1481623127; +} diff --git a/Classes/Connection/Elasticsearch/Connection.php b/Classes/Connection/Elasticsearch/Connection.php index 81f31d2..64ac46b 100644 --- a/Classes/Connection/Elasticsearch/Connection.php +++ b/Classes/Connection/Elasticsearch/Connection.php @@ -20,8 +20,8 @@ namespace Leonmrni\SearchCore\Connection\Elasticsearch; * 02110-1301, USA. */ +use Leonmrni\SearchCore\Configuration\ConfigurationContainerInterface; use TYPO3\CMS\Core\SingletonInterface as Singleton; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; /** * The current connection to elasticsearch. @@ -36,45 +36,24 @@ class Connection implements Singleton protected $elasticaClient; /** - * @var array + * @var ConfigurationContainerInterface */ - protected $settings; + protected $configuration; /** * @param \Elastica\Client $elasticaClient */ - public function __construct(\Elastica\Client $elasticaClient = null) - { + public function __construct( + \Elastica\Client $elasticaClient = null, + ConfigurationContainerInterface $configuration + ) { + $this->configuration = $configuration; + $this->elasticaClient = $elasticaClient; - } - - /** - * Inject news settings via ConfigurationManager. - * - * TODO: Refactor to configuration object to have a singleton holding the - * settings with validation and propper getter? - * - * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager - */ - public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager) - { - $this->settings = $configurationManager->getConfiguration( - ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, - 'SearchCore', - 'search' - ); - } - - /** - * Used to configure elasticaClient if no one was injected. Will use - * injected settings for configuration. - */ - public function initializeObject() - { if ($this->elasticaClient === null) { $this->elasticaClient = new \Elastica\Client([ - 'host' => $this->settings['host'], - 'port' => $this->settings['port'], + 'host' => $this->configuration->get('connection', 'host'), + 'port' => $this->configuration->get('connection', 'port'), // TODO: Make configurable // 'log' => 'file', ]); diff --git a/Classes/Domain/Index/TcaIndexer/TcaTableService.php b/Classes/Domain/Index/TcaIndexer/TcaTableService.php index 5fa8e80..b717169 100644 --- a/Classes/Domain/Index/TcaIndexer/TcaTableService.php +++ b/Classes/Domain/Index/TcaIndexer/TcaTableService.php @@ -20,8 +20,9 @@ namespace Leonmrni\SearchCore\Domain\Index\TcaIndexer; * 02110-1301, USA. */ -use TYPO3\CMS\Backend\Utility\BackendUtility; +use Leonmrni\SearchCore\Configuration\ConfigurationContainerInterface; use Leonmrni\SearchCore\Domain\Index\IndexingException; +use TYPO3\CMS\Backend\Utility\BackendUtility; /** * Encapsulate logik related to tca configuration. @@ -40,6 +41,11 @@ class TcaTableService */ protected $tableName; + /** + * @var ConfigurationContaineInterfacer + */ + protected $configuration; + /** * @var \TYPO3\CMS\Core\Log\Logger */ @@ -55,7 +61,11 @@ class TcaTableService $this->logger = $logManager->getLogger(__CLASS__); } - public function __construct($tableName) + /** + * @param string $tableName + * @param ConfigurationContainer $configuration + */ + public function __construct($tableName, ConfigurationContainerInterface $configuration) { if (!isset($GLOBALS['TCA'][$tableName])) { throw new IndexingException( @@ -66,6 +76,7 @@ class TcaTableService $this->tableName = $tableName; $this->tca = &$GLOBALS['TCA'][$this->tableName]; + $this->configuration = $configuration; } /** @@ -114,6 +125,11 @@ class TcaTableService . ' AND pages.no_search = 0' ; + $userDefinedWhere = $this->configuration->getIfExists('index', $this->tableName); + if (is_string($userDefinedWhere)) { + $whereClause .= $userDefinedWhere; + } + $this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]); return $whereClause; } diff --git a/Configuration/TypoScript/setup.txt b/Configuration/TypoScript/setup.txt index 22c4ed8..7eeead8 100644 --- a/Configuration/TypoScript/setup.txt +++ b/Configuration/TypoScript/setup.txt @@ -5,6 +5,12 @@ plugin { host = localhost port = 9200 } + + index { + tt_content { + additionalWhereClause = tt_content.CType NOT IN ('gridelements_pi1', 'list', 'div', 'menu') + } + } } } } diff --git a/Tests/Functional/Fixtures/Indexing/IndexTcaTable.xml b/Tests/Functional/Fixtures/Indexing/IndexTcaTable.xml index 14ef723..f25a402 100644 --- a/Tests/Functional/Fixtures/Indexing/IndexTcaTable.xml +++ b/Tests/Functional/Fixtures/Indexing/IndexTcaTable.xml @@ -8,7 +8,7 @@ 0 72 textmedia -
test
+
indexed content element
this is the content of textmedia content element that should get indexed 0 0 diff --git a/Tests/Functional/Fixtures/Indexing/UserWhereClause.ts b/Tests/Functional/Fixtures/Indexing/UserWhereClause.ts new file mode 100644 index 0000000..641eaf5 --- /dev/null +++ b/Tests/Functional/Fixtures/Indexing/UserWhereClause.ts @@ -0,0 +1,13 @@ +plugin { + tx_searchcore { + settings { + index { + tt_content { + additionalWhereClause = tt_content.CType NOT IN ('div') + } + } + } + } +} + +module.tx_searchcore < plugin.tx_searchcore diff --git a/Tests/Functional/Fixtures/Indexing/UserWhereClause.xml b/Tests/Functional/Fixtures/Indexing/UserWhereClause.xml new file mode 100644 index 0000000..5d50d1c --- /dev/null +++ b/Tests/Functional/Fixtures/Indexing/UserWhereClause.xml @@ -0,0 +1,42 @@ + + + + 9 + 1 + 1480686370 + 1480686370 + 0 + 72 + textmedia +
Also indexable record
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +
+ + + 10 + 1 + 1480686370 + 1480686370 + 1 + 72 + div +
Not indexed by user where ctype condition
+ + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +
+
diff --git a/Tests/Functional/Indexing/IndexTcaTableTest.php b/Tests/Functional/Indexing/IndexTcaTableTest.php index cbd195c..88ba1e4 100644 --- a/Tests/Functional/Indexing/IndexTcaTableTest.php +++ b/Tests/Functional/Indexing/IndexTcaTableTest.php @@ -39,7 +39,7 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase /** * @test */ - public function indexBasicTtContentWithoutBasicConfiguration() + public function indexBasicTtContent() { \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class) ->get(IndexerFactory::class) @@ -51,6 +51,12 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase $this->assertTrue($response->isOK()); $this->assertSame($response->getData()['hits']['total'], 1, 'Not exactly 1 document was indexed.'); + $this->assertArraySubset( + ['_source' => ['header' => 'indexed content element']], + $response->getData()['hits']['hits'][0], + false, + 'Record was not indexed.' + ); } /** @@ -85,4 +91,36 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase $this->assertTrue($response->isOK()); $this->assertSame($response->getData()['hits']['total'], 1, 'Not exactly 1 document was indexed.'); } + + /** + * @test + */ + public function indexingRespectsUserWhereClause() + { + $this->setUpFrontendRootPage(1, ['EXT:search_core/Tests/Functional/Fixtures/Indexing/UserWhereClause.ts']); + $this->importDataSet('Tests/Functional/Fixtures/Indexing/UserWhereClause.xml'); + + \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class) + ->get(IndexerFactory::class) + ->getIndexer('tt_content') + ->index() + ; + + $response = $this->client->request('typo3content/_search?q=*:*'); + + $this->assertTrue($response->isOK()); + $this->assertSame($response->getData()['hits']['total'], 2, 'Not exactly 2 document was indexed.'); + $this->assertArraySubset( + ['_source' => ['header' => 'Also indexable record']], + $response->getData()['hits']['hits'][0], + false, + 'Record was not indexed.' + ); + $this->assertArraySubset( + ['_source' => ['header' => 'indexed content element']], + $response->getData()['hits']['hits'][1], + false, + 'Record was not indexed.' + ); + } }