From b079dd812591c3d7389777b8adc64a9ac2052db8 Mon Sep 17 00:00:00 2001 From: digital-competence Date: Fri, 9 Dec 2016 13:19:35 +0100 Subject: [PATCH] TASK: Migrate existing code (#2) --- Classes/Command/IndexCommandController.php | 15 ++ Classes/Connection/ConnectionInterface.php | 75 ++++++++ Classes/Connection/Elasticsearch.php | 140 +++++++++++++++ .../Connection/Elasticsearch/Connection.php | 95 ++++++++++ .../Elasticsearch/DocumentFactory.php | 82 +++++++++ .../Connection/Elasticsearch/IndexFactory.php | 58 ++++++ .../Connection/Elasticsearch/SearchResult.php | 18 +- .../Connection/Elasticsearch/TypeFactory.php | 45 +++++ Classes/Connection/SearchRequestInterface.php | 34 ++++ Classes/Connection/SearchResultInterface.php | 29 +++ Classes/Controller/SearchController.php | 64 +++++++ Classes/Domain/Index/IndexerFactory.php | 60 +++++++ Classes/Domain/Index/IndexerInterface.php | 32 ++++ Classes/Domain/Index/IndexingException.php | 29 +++ Classes/Domain/Index/TcaIndexer.php | 117 ++++++++++++ .../Index/TcaIndexer/TcaTableService.php | 167 ++++++++++++++++++ Classes/Domain/Model/SearchRequest.php | 60 +++++++ Classes/Domain/Search/SearchService.php | 69 ++++++++ Classes/Domain/Service/DataHandler.php | 84 +++++++++ Classes/Hook/DataHandler.php | 2 +- Configuration/TypoScript/constants.txt | 0 Configuration/TypoScript/setup.txt | 12 ++ .../Private/Templates/Search/Search.html | 13 ++ Tests/Unit/Hook/DataHandlerTest.php | 17 +- composer.json | 3 +- ext_emconf.php | 4 +- ext_localconf.php | 48 +++++ ext_tables.php | 13 ++ 28 files changed, 1355 insertions(+), 30 deletions(-) create mode 100644 Classes/Connection/ConnectionInterface.php create mode 100644 Classes/Connection/Elasticsearch.php create mode 100644 Classes/Connection/Elasticsearch/Connection.php create mode 100644 Classes/Connection/Elasticsearch/DocumentFactory.php create mode 100644 Classes/Connection/Elasticsearch/IndexFactory.php rename Tests/Fakes/FakeLogManager.php => Classes/Connection/Elasticsearch/SearchResult.php (62%) create mode 100644 Classes/Connection/Elasticsearch/TypeFactory.php create mode 100644 Classes/Connection/SearchRequestInterface.php create mode 100644 Classes/Connection/SearchResultInterface.php create mode 100644 Classes/Controller/SearchController.php create mode 100644 Classes/Domain/Index/IndexerFactory.php create mode 100644 Classes/Domain/Index/IndexerInterface.php create mode 100644 Classes/Domain/Index/IndexingException.php create mode 100644 Classes/Domain/Index/TcaIndexer.php create mode 100644 Classes/Domain/Index/TcaIndexer/TcaTableService.php create mode 100644 Classes/Domain/Model/SearchRequest.php create mode 100644 Classes/Domain/Search/SearchService.php create mode 100644 Classes/Domain/Service/DataHandler.php create mode 100644 Configuration/TypoScript/constants.txt create mode 100644 Configuration/TypoScript/setup.txt create mode 100644 Resources/Private/Templates/Search/Search.html create mode 100644 ext_localconf.php create mode 100644 ext_tables.php diff --git a/Classes/Command/IndexCommandController.php b/Classes/Command/IndexCommandController.php index 77cb651..8f61e6f 100644 --- a/Classes/Command/IndexCommandController.php +++ b/Classes/Command/IndexCommandController.php @@ -20,6 +20,7 @@ namespace Leonmrni\SearchCore\Command; * 02110-1301, USA. */ +use Leonmrni\SearchCore\Domain\Index\IndexerFactory; use TYPO3\CMS\Extbase\Mvc\Controller\CommandController; /** @@ -27,6 +28,19 @@ use TYPO3\CMS\Extbase\Mvc\Controller\CommandController; */ class IndexCommandController extends CommandController { + /** + * @var IndexerFactory + */ + protected $indexerFactory; + + /** + * @param IndexerFactory $factory + */ + public function injectIndexerFactory(IndexerFactory $factory) + { + $this->indexerFactory = $factory; + } + /** * Will index the given table or everything. * @@ -36,5 +50,6 @@ class IndexCommandController extends CommandController { // TODO: Allow to index multiple tables at once? // TODO: Also allow to index everything? + $this->indexerFactory->getIndexer($table)->index(); } } diff --git a/Classes/Connection/ConnectionInterface.php b/Classes/Connection/ConnectionInterface.php new file mode 100644 index 0000000..ca3474d --- /dev/null +++ b/Classes/Connection/ConnectionInterface.php @@ -0,0 +1,75 @@ + + * + * 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. + */ + +/** + * Defines interface for connections to storage backend for interacting with documents. + */ +interface ConnectionInterface +{ + /** + * Will add a new document, based on his identifier and record type. + * + * @param string $recordType + * @param int $identifier + * @param array $record + * + * @return + */ + public function add($recordType, $identifier, array $record); + + /** + * Add the given records. + * + * @param string $recordType + * @param array $records + */ + public function addDocuments($recordType, array $records); + + /** + * Will update an existing document, based on his identifier and record type. + * + * @param string $recordType + * @param int $identifier + * @param array $record + * + * @return + */ + public function update($recordType, $identifier, array $record); + + /** + * Will remove an existing document, based on his identifier and record type. + * + * @param string $recordType + * @param int $identifier + * + * @return + */ + public function delete($recordType, $identifier); + + /** + * Search by given request and return result. + * + * @param SearchRequestInterface $searchRequest + * @return SearchResultInterface + */ + public function search(SearchRequestInterface $searchRequest); +} diff --git a/Classes/Connection/Elasticsearch.php b/Classes/Connection/Elasticsearch.php new file mode 100644 index 0000000..118139a --- /dev/null +++ b/Classes/Connection/Elasticsearch.php @@ -0,0 +1,140 @@ + + * + * 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; + +/** + * Outer wrapper to elasticsearch. + */ +class Elasticsearch implements Singleton, ConnectionInterface +{ + /** + * @var Elasticsearch\Connection + */ + protected $connection; + + /** + * @var IndexFactory + */ + protected $indexFactory; + + /** + * @var TypeFactory + */ + protected $typeFactory; + + /** + * @var DocumentFactory + */ + protected $documentFactory; + + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + /** + * @param Elasticsearch\Connection $connection + * @param Elasticsearch\IndexFactory $indexFactory + * @param Elasticsearch\TypeFactory $typeFactory + * @param Elasticsearch\DocumentFactory $documentFactory + */ + public function __construct( + Elasticsearch\Connection $connection, + Elasticsearch\IndexFactory $indexFactory, + Elasticsearch\TypeFactory $typeFactory, + Elasticsearch\DocumentFactory $documentFactory + ) { + $this->connection = $connection; + $this->indexFactory = $indexFactory; + $this->typeFactory = $typeFactory; + $this->documentFactory = $documentFactory; + } + + public function add($recordType, $identifier, array $record) + { + throw new \Exception('Implement', 1481190734); + } + + public function delete($recordType, $identifier) + { + throw new \Exception('Implement', 1481190734); + } + + public function update($tableName, $identifier, array $record) + { + $this->addDocument($tableName, $identifier, $record); + } + + protected function addDocument($tableName, $identifier, array $record) + { + throw new \Exception('Implement', 1481192791); + } + + /** + * Add the given records to elasticsearch. + * + * @param string $tableName + * @param array $records + */ + public function addDocuments($tableName, array $records) + { + $type = $this->typeFactory->getType( + $this->indexFactory->getIndex( + $this->connection, + $tableName + ), + $tableName + ); + + $type->addDocuments( + $this->documentFactory->getDocuments($tableName, $records) + ); + + $type->getIndex()->refresh(); + } + + /** + * @param SearchRequestInterface $searchRequest + * @return SearchResultInterface + */ + public function search(SearchRequestInterface $searchRequest) + { + $this->logger->debug('Search for', [$searchRequest->getSearchTerm()]); + + $search = new \Elastica\Search($this->connection->getClient()); + $search->addIndex('typo3content'); + + // TODO: Return wrapped result to implement our interface. + return $search->search($searchRequest->getSearchTerm()); + } +} diff --git a/Classes/Connection/Elasticsearch/Connection.php b/Classes/Connection/Elasticsearch/Connection.php new file mode 100644 index 0000000..81f31d2 --- /dev/null +++ b/Classes/Connection/Elasticsearch/Connection.php @@ -0,0 +1,95 @@ + + * + * 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; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +/** + * The current connection to elasticsearch. + * + * Wrapper for Elastica\Client. + */ +class Connection implements Singleton +{ + /** + * @var \Elastica\Client + */ + protected $elasticaClient; + + /** + * @var array + */ + protected $settings; + + /** + * @param \Elastica\Client $elasticaClient + */ + public function __construct(\Elastica\Client $elasticaClient = null) + { + $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'], + // TODO: Make configurable + // 'log' => 'file', + ]); + // TODO: Make configurable. + // new \Elastica\Log($this->elasticaClient); + } + } + + /** + * Get the concrete client for internal usage! + * + * @return \Elastica\Client + */ + public function getClient() + { + return $this->elasticaClient; + } +} diff --git a/Classes/Connection/Elasticsearch/DocumentFactory.php b/Classes/Connection/Elasticsearch/DocumentFactory.php new file mode 100644 index 0000000..5b58aa8 --- /dev/null +++ b/Classes/Connection/Elasticsearch/DocumentFactory.php @@ -0,0 +1,82 @@ + + * + * 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; + +/** + * Factory to create documents to index in Elasticsearch. + */ +class DocumentFactory implements Singleton +{ + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + /** + * Creates document from record. + * + * @param string $tableName + * @param array $record + * + * @return \Elastica\Document + */ + public function getDocument($tableName, array $record) + { + if (!isset($record['search_identifier'])) { + throw new \Exception('No search_identifier provided for record.', 1481194385); + } + + $identifier = $record['search_identifier']; + unset($record['search_identifier']); + + $this->logger->debug('Convert record to document', [$identifier, $record]); + return new \Elastica\Document($identifier, $record); + } + + /** + * Creates documents based on records. + * + * @param string $tableName + * @param array $records + * + * @return array + */ + public function getDocuments($tableName, array $records) + { + foreach ($records as &$record) { + $record = $this->getDocument($tableName, $record); + } + + return $records; + } +} diff --git a/Classes/Connection/Elasticsearch/IndexFactory.php b/Classes/Connection/Elasticsearch/IndexFactory.php new file mode 100644 index 0000000..54d648b --- /dev/null +++ b/Classes/Connection/Elasticsearch/IndexFactory.php @@ -0,0 +1,58 @@ + + * + * 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; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +/** + * Factory to get indexes. + * + * The factory will take care of configuration and creation of index if necessary. + */ +class IndexFactory implements Singleton +{ + /** + * Get an index bases on TYPO3 table name. + * + * @param Connection $connection + * @param string $tableName + * + * @return \Elastica\Index + */ + public function getIndex(Connection $connection, $tableName) + { + // TODO: Fetch index name from configuration, based on $tableName. + $index = $connection->getClient()->getIndex('typo3content'); + + try { + // TODO: Provide configuration?! + // http://elastica.io/getting-started/storing-and-indexing-documents.html#section-analysis + $index->create(); + } catch (\Elastica\Exception\ResponseException $exception) { + if (stripos($exception->getMessage(), 'already exists') === false) { + throw $exception; + } + } + + return $index; + } +} diff --git a/Tests/Fakes/FakeLogManager.php b/Classes/Connection/Elasticsearch/SearchResult.php similarity index 62% rename from Tests/Fakes/FakeLogManager.php rename to Classes/Connection/Elasticsearch/SearchResult.php index 6988721..3f90d60 100644 --- a/Tests/Fakes/FakeLogManager.php +++ b/Classes/Connection/Elasticsearch/SearchResult.php @@ -1,8 +1,8 @@ + * Copyright (C) 2016 Daniel Siepmann * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,20 +20,12 @@ namespace Leonmrni\SearchCore\Tests\Fakes; * 02110-1301, USA. */ -use TYPO3\CMS\Core\Log\LogManager; +use Leonmrni\SearchCore\Connection\SearchResultInterface; /** - * Fakes the LogManager to prevent dependencies and logging during tests. + * */ -class FakeLogManager extends LogManager +class SearchResult extends \Elastica\SearchResult implements SearchResultInterface { - public function __construct() - { - $this->loggers['fake'] = new Logger('fake'); - } - public function getLogger($name = '') - { - return $this->loggers['fake']; - } } diff --git a/Classes/Connection/Elasticsearch/TypeFactory.php b/Classes/Connection/Elasticsearch/TypeFactory.php new file mode 100644 index 0000000..7fa7adc --- /dev/null +++ b/Classes/Connection/Elasticsearch/TypeFactory.php @@ -0,0 +1,45 @@ + + * + * 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; +use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; + +/** + * Factory to get indexes. + * + * The factory will take care of configuration and creation of index if necessary. + */ +class TypeFactory implements Singleton +{ + /** + * Get an index bases on TYPO3 table name. + * + * @param \Elastica\Index $index + * @param string $tableName + * + * @return \Elastica\Type + */ + public function getType(\Elastica\Index $index, $tableName) + { + return $index->getType($tableName); + } +} diff --git a/Classes/Connection/SearchRequestInterface.php b/Classes/Connection/SearchRequestInterface.php new file mode 100644 index 0000000..ecf7a74 --- /dev/null +++ b/Classes/Connection/SearchRequestInterface.php @@ -0,0 +1,34 @@ + + * + * 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. + */ + +/** + * + */ +interface SearchRequestInterface +{ + /** + * Returns the actual string the user searched for. + * + * @return string + */ + public function getSearchTerm(); +} diff --git a/Classes/Connection/SearchResultInterface.php b/Classes/Connection/SearchResultInterface.php new file mode 100644 index 0000000..f00e97c --- /dev/null +++ b/Classes/Connection/SearchResultInterface.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. + */ + +/** + * + */ +interface SearchResultInterface extends \Iterator, \Countable, \ArrayAccess +{ + +} diff --git a/Classes/Controller/SearchController.php b/Classes/Controller/SearchController.php new file mode 100644 index 0000000..9f4873c --- /dev/null +++ b/Classes/Controller/SearchController.php @@ -0,0 +1,64 @@ + + * + * 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\Model\SearchRequest; +use Leonmrni\SearchCore\Domain\Search\SearchService; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +/** + * Handling search logic in TYPO3 Frontend. + */ +class SearchController extends ActionController +{ + /** + * @var SearchService + */ + protected $searchService; + + /** + * @param SearchService $searchService + */ + public function __construct(SearchService $searchService) + { + $this->searchService = $searchService; + + parent::__construct(); + } + + /** + * Process a search and deliver original request and result to view. + * + * @param SearchRequest $searchRequest + */ + public function searchAction(SearchRequest $searchRequest = null) + { + $searchResult = null; + if ($searchRequest !== null) { + $searchResult = $this->searchService->search($searchRequest); + } + + $this->view->assignMultiple([ + 'searchRequest' => $searchRequest, + 'searchResult' => $searchResult, + ]); + } +} diff --git a/Classes/Domain/Index/IndexerFactory.php b/Classes/Domain/Index/IndexerFactory.php new file mode 100644 index 0000000..0f99396 --- /dev/null +++ b/Classes/Domain/Index/IndexerFactory.php @@ -0,0 +1,60 @@ + + * + * 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; +use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; + +/** + * Factory to get configured indexer based on configuration. + */ +class IndexerFactory implements Singleton +{ + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @param string $tableName + * + * @return IndexerInterface + */ + public function getIndexer($tableName) + { + // This is the place to use configuration to return different indexer. + return $this->objectManager->get( + TcaIndexer::Class, + $this->objectManager->get( + TcaIndexer\TcaTableService::class, + $tableName + ) + ); + } +} diff --git a/Classes/Domain/Index/IndexerInterface.php b/Classes/Domain/Index/IndexerInterface.php new file mode 100644 index 0000000..da93f5d --- /dev/null +++ b/Classes/Domain/Index/IndexerInterface.php @@ -0,0 +1,32 @@ + + * + * 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. + */ + +/** + * Interface that all indexer should implement. + */ +interface IndexerInterface +{ + /** + * Index the index. + */ + public function index(); +} diff --git a/Classes/Domain/Index/IndexingException.php b/Classes/Domain/Index/IndexingException.php new file mode 100644 index 0000000..a22660d --- /dev/null +++ b/Classes/Domain/Index/IndexingException.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. + */ + +/** + * Exception thrown on errors while indexing. + */ +class IndexingException extends \Exception +{ + const CODE_UNKOWN_TCA_TABLE = 1481190283; +} diff --git a/Classes/Domain/Index/TcaIndexer.php b/Classes/Domain/Index/TcaIndexer.php new file mode 100644 index 0000000..1dd303e --- /dev/null +++ b/Classes/Domain/Index/TcaIndexer.php @@ -0,0 +1,117 @@ + + * + * 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; +use Leonmrni\SearchCore\Connection\ConnectionInterface; + +/** + * Will index the given table using configuration from TCA. + */ +class TcaIndexer implements IndexerInterface +{ + /** + * @var ConnectionInterface + */ + protected $connection; + + /** + * @var TcaIndexer\TcaTableService + */ + protected $tcaTableService; + + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + /** + * @param string $tableName + * @param ConnectionInterface $connection + */ + public function __construct( + TcaIndexer\TcaTableService $tcaTableService, + ConnectionInterface $connection + ) { + $this->tcaTableService = $tcaTableService; + $this->connection = $connection; + } + + public function index() + { + $this->logger->info('Start indexing'); + foreach ($this->getRecordGenerator() as $records) { + $this->logger->debug('Index records.', [$records]); + $this->connection->addDocuments($this->tcaTableService->getTableName(), $records); + } + $this->logger->info('Finish indexing'); + } + + /** + * @return \Iterator + */ + protected function getRecordGenerator() + { + $offset = 0; + // TODO: Make configurable. + $limit = 50; + + while (($records = $this->getRecords($offset, $limit)) !== []) { + yield $records; + $offset += $limit; + } + } + + /** + * @param int $offset + * @param int $limit + * @return array + */ + protected function getRecords($offset, $limit) + { + $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows( + $this->tcaTableService->getFields(), + $this->tcaTableService->getTableName(), + $this->tcaTableService->getWhereClause(), + '', + '', + (int) $offset . ',' . (int) $limit + ); + + foreach ($records as &$record) { + $this->tcaTableService->prepareRecord($record); + } + + // TODO: Ignore records from sys folder? + + return $records; + } +} diff --git a/Classes/Domain/Index/TcaIndexer/TcaTableService.php b/Classes/Domain/Index/TcaIndexer/TcaTableService.php new file mode 100644 index 0000000..e30cbd6 --- /dev/null +++ b/Classes/Domain/Index/TcaIndexer/TcaTableService.php @@ -0,0 +1,167 @@ + + * + * 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\Backend\Utility\BackendUtility; +use Leonmrni\SearchCore\Domain\Index\IndexingException; + +/** + * Encapsulate logik related to tca configuration. + */ +class TcaTableService +{ + /** + * TCA for current table. + * !REFERENCE! To save memory. + * @var array + */ + protected $tca; + + /** + * @var string + */ + protected $tableName; + + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + public function __construct($tableName) + { + if (!isset($GLOBALS['TCA'][$tableName])) { + throw new IndexingException( + 'Table "' . $tableName . '" is not configured in TCA.', + IndexingException::CODE_UNKOWN_TCA_TABLE + ); + } + + $this->tableName = $tableName; + $this->tca = &$GLOBALS['TCA'][$this->tableName]; + } + + /** + * @return string + */ + public function getTableName() + { + return $this->tableName; + } + + /** + * Adjust record accordingly to configuration. + * @param array &$record + */ + public function prepareRecord(array &$record) + { + // TODO: Resolve values from 'items' like static select, radio or checkbox. + + if (isset($record['uid']) && !isset($record['search_identifier'])) { + $record['search_identifier'] = $record['uid']; + } + if (isset($record[$this->tca['ctrl']['label']]) && !isset($record['search_title'])) { + $record['search_title'] = $record[$this->tca['ctrl']['label']]; + } + } + + /** + * @return string + */ + public function getWhereClause() + { + $whereClause = '1=1 ' + . BackendUtility::BEenableFields($this->tableName) + . BackendUtility::deleteClause($this->tableName) + ; + + $this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]); + return $whereClause; + } + + /** + * @return string + */ + public function getFields() + { + $fields = 'uid,pid,' . implode( + ',', + array_filter( + array_keys($this->tca['columns']), + function ($columnName) { + $columnConfig = $this->tca['columns'][$columnName]['config']; + return !$this->isRelation($columnConfig) && !$this->isSystemField($columnName); + } + ) + ); + + $this->logger->debug('Generated fields.', [$this->tableName, $fields]); + return $fields; + } + + /** + * @param array + * @return bool + */ + protected function isRelation(array &$columnConfig) + { + if (isset($columnConfig['foreign_table'])) { + return true; + } + + return false; + } + + /** + * @param string + * @return bool + */ + protected function isSystemField($columnName) + { + $systemFields = [ + // Versioning fields, + // https://docs.typo3.org/typo3cms/TCAReference/Reference/Ctrl/Index.html#versioningws + 't3ver_oid', 't3ver_id', 't3ver_label', 't3ver_wsid', + 't3ver_state', 't3ver_stage', 't3ver_count', 't3ver_tstamp', + 't3ver_move_id', 't3ver_swapmode', + $this->tca['ctrl']['transOrigDiffSourceField'], + $this->tca['ctrl']['cruser_id'], + $this->tca['ctrl']['fe_cruser_id'], + $this->tca['ctrl']['fe_crgroup_id'], + $this->tca['ctrl']['languageField'], + $this->tca['ctrl']['origUid'], + ]; + if (in_array($columnName, $systemFields)) { + return true; + } + + return false; + } +} diff --git a/Classes/Domain/Model/SearchRequest.php b/Classes/Domain/Model/SearchRequest.php new file mode 100644 index 0000000..ba16349 --- /dev/null +++ b/Classes/Domain/Model/SearchRequest.php @@ -0,0 +1,60 @@ + + * + * 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\SearchRequestInterface; + +/** + * Represents a search request used to process an actual search. + */ +class SearchRequest implements SearchRequestInterface +{ + /** + * The search string provided by the user, the actual term to search for. + * + * @var string + */ + protected $query; + + /** + * @param string $query + */ + public function __construct($query) + { + $this->query = $query; + } + + /** + * @return string + */ + public function getQuery() + { + return $this->query; + } + + /** + * @return string + */ + public function getSearchTerm() + { + return '"' . $this->query . '"'; + } +} diff --git a/Classes/Domain/Search/SearchService.php b/Classes/Domain/Search/SearchService.php new file mode 100644 index 0000000..7867aa1 --- /dev/null +++ b/Classes/Domain/Search/SearchService.php @@ -0,0 +1,69 @@ + + * + * 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\SearchRequestInterface; +use Leonmrni\SearchCore\Connection\SearchResultInterface; +use Leonmrni\SearchCore\Domain\Model\SearchRequest; + +/** + * Service to process a search request. + */ +class SearchService +{ + /** + * @var ConnectionInterface + */ + protected $connection; + + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + /** + * @param ConnectionInterface $connection + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * @param SearchRequestInterface $searchRequest + * @return SearchResultInterface + */ + public function search(SearchRequestInterface $searchRequest) + { + return $this->connection->search($searchRequest); + } +} diff --git a/Classes/Domain/Service/DataHandler.php b/Classes/Domain/Service/DataHandler.php new file mode 100644 index 0000000..110dd98 --- /dev/null +++ b/Classes/Domain/Service/DataHandler.php @@ -0,0 +1,84 @@ + + * + * 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; + +/** + * Handles all data related things like updates, deletes and inserts. + * + * This is the place to add mappings of further parts to adjust the data before + * sending ot to connection. + */ +class DataHandler implements Singleton +{ + /** + * @var \Leonmrni\SearchCore\Connection\ConnectionInterface + * @inject + */ + protected $connection; + + /** + * @var \TYPO3\CMS\Core\Log\Logger + */ + protected $logger; + + /** + * Inject log manager to get concrete logger from it. + * + * @param \TYPO3\CMS\Core\Log\LogManager $logManager + */ + public function injectLogger(\TYPO3\CMS\Core\Log\LogManager $logManager) + { + $this->logger = $logManager->getLogger(__CLASS__); + } + + /** + * @param string $table + * @param int $identifier + */ + public function delete($table, $identifier) + { + $this->logger->debug('Record received for delete.', [$table, $identifier]); + $this->connection->delete($table, $identifier); + } + + /** + * @param string $table + * @param int $identifier + * @param array $record + */ + public function add($table, $identifier, array $record) + { + $this->logger->debug('Record received for add.', [$table, $identifier, $record]); + $this->connection->add($table, $identifier, $record); + } + + /** + * @param string $table + * @param int $identifier + */ + public function update($table, $identifier, array $record) + { + $this->logger->debug('Record received for update.', [$table, $identifier, $record]); + $this->connection->update($table, $identifier, $record); + } +} diff --git a/Classes/Hook/DataHandler.php b/Classes/Hook/DataHandler.php index 761e593..21d8565 100644 --- a/Classes/Hook/DataHandler.php +++ b/Classes/Hook/DataHandler.php @@ -27,7 +27,7 @@ use TYPO3\CMS\Core\Log\Logger; use TYPO3\CMS\Core\SingletonInterface as Singleton; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Object\ObjectManager; -use Leonmrni\SearchCore\Service\DataHandler as OwnDataHandler; +use Leonmrni\SearchCore\Domain\Service\DataHandler as OwnDataHandler; /** * Wrapper for TYPO3 Hooks to internal API. diff --git a/Configuration/TypoScript/constants.txt b/Configuration/TypoScript/constants.txt new file mode 100644 index 0000000..e69de29 diff --git a/Configuration/TypoScript/setup.txt b/Configuration/TypoScript/setup.txt new file mode 100644 index 0000000..22c4ed8 --- /dev/null +++ b/Configuration/TypoScript/setup.txt @@ -0,0 +1,12 @@ +plugin { + tx_searchcore { + settings { + connection { + host = localhost + port = 9200 + } + } + } +} + +module.tx_searchcore < plugin.tx_searchcore diff --git a/Resources/Private/Templates/Search/Search.html b/Resources/Private/Templates/Search/Search.html new file mode 100644 index 0000000..dc9e6b2 --- /dev/null +++ b/Resources/Private/Templates/Search/Search.html @@ -0,0 +1,13 @@ + + + + + + + + + + {result.id} [{result.type}] - {result.hit._source.search_title} + +
+
diff --git a/Tests/Unit/Hook/DataHandlerTest.php b/Tests/Unit/Hook/DataHandlerTest.php index 1569d21..cf01df6 100644 --- a/Tests/Unit/Hook/DataHandlerTest.php +++ b/Tests/Unit/Hook/DataHandlerTest.php @@ -20,12 +20,10 @@ namespace Leonmrni\SearchCore\Tests\Unit\Hook; * 02110-1301, USA. */ -use Leonmrni\SearchCore\Hook\DataHandler as Hook; -use Leonmrni\SearchCore\Service\DataHandler; -use Leonmrni\SearchCore\Tests\Fakes\FakeLogManager; use TYPO3\CMS\Core\DataHandling\DataHandler as CoreDataHandler; -use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\Tests\UnitTestCase; +use Leonmrni\SearchCore\Domain\Service\DataHandler; +use Leonmrni\SearchCore\Hook\DataHandler as Hook; /** * @@ -47,9 +45,6 @@ class DataHandlerTest extends UnitTestCase */ protected function setUp() { - \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\Container\Container') - ->registerImplementation(LogManager::class, FakeLogManager::class); - $this->subject = $this->getAccessibleMock(DataHandler::class); $this->hook = $this->getAccessibleMock( Hook::class, @@ -57,9 +52,7 @@ class DataHandlerTest extends UnitTestCase 'getTablesToProcess', 'getRecord' ], - [ - $this->subject, - ] + [$this->subject] ); $this->hook->method('getTablesToProcess') @@ -82,7 +75,7 @@ class DataHandlerTest extends UnitTestCase $this->subject->expects($this->exactly(0))->method('add'); $this->subject->expects($this->exactly(0))->method('update'); - $dataHandler = $this->getAccessibleMock(CoreDataHandler::class, [], [], '', false); + $dataHandler = new CoreDataHandler(); $dataHandler->substNEWwithIDs = ['NEW34' => $recordUid]; $this->hook->processCmdmap_deleteAction($table, $recordUid, [], false, $dataHandler); @@ -101,7 +94,7 @@ class DataHandlerTest extends UnitTestCase $this->subject->expects($this->once())->method('add'); $this->subject->expects($this->once())->method('update'); - $dataHandler = $this->getAccessibleMock(CoreDataHandler::class, [], [], '', false); + $dataHandler = new CoreDataHandler(); $dataHandler->substNEWwithIDs = ['NEW34' => $recordUid]; $this->hook->processCmdmap_deleteAction($table, $recordUid, [], false, $dataHandler); diff --git a/composer.json b/composer.json index babad24..b8fea1d 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,12 @@ "TYPO3\\CMS\\Core\\Tests\\": ".Build/vendor/typo3/cms/typo3/sysext/core/Tests/" } }, - "require" : { "php": ">=5.6.0", "typo3/cms": ">=6.2.0" }, "require-dev": { - "phpunit/phpunit": "~4.8.0" + "phpunit/phpunit": "~4.8.0" }, "config": { "optimize-autoloader": true, diff --git a/ext_emconf.php b/ext_emconf.php index 8614d21..185982f 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -6,8 +6,8 @@ $EM_CONF[$_EXTKEY] = [ 'category' => 'be', 'constraints' => [ 'depends' => [ - 'typo3' => '6.2.0-8.99.99', - 'php' => '5.6.0-7.99.99' + 'typo3' => '7.6.2-8.99.99', + 'php' => '7.0.0-7.99.99' ], 'conflicts' => [], ], diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..91f7230 --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,48 @@ + to handle records modified through + // Frontend and backend modules not using datahandler + + $GLOBALS['TYPO3_CONF_VARS'] = TYPO3\CMS\Extbase\Utility\ArrayUtility::arrayMergeRecursiveOverrule( + $GLOBALS['TYPO3_CONF_VARS'], + [ + 'SC_OPTIONS' => [ + 'extbase' => [ + 'commandControllers' => [ + Leonmrni\SearchCore\Command\IndexCommandController::class, + ], + ], + // Not yet, first finish whole indexing through command controller as it's more important. + // 't3lib/class.t3lib_tcemain.php' => [ + // 'processCmdmapClass' => [ + // $extensionKey => \Leonmrni\SearchCore\Hook\DataHandler::class, + // ], + // 'processDatamapClass' => [ + // $extensionKey => \Leonmrni\SearchCore\Hook\DataHandler::class, + // ], + // ], + ], + ] + ); + + TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'Leonmrni.' . $extensionKey, + 'search', + [ + 'Search' => 'search' + ], + [ + 'Search' => 'search' // TODO: Enable caching. But submitting form results in previous result?! + ] + ); + + \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\Container\Container') + ->registerImplementation( + 'Leonmrni\SearchCore\Connection\ConnectionInterface', + 'Leonmrni\SearchCore\Connection\Elasticsearch' + ); + }, + $_EXTKEY +); diff --git a/ext_tables.php b/ext_tables.php new file mode 100644 index 0000000..d6d07ed --- /dev/null +++ b/ext_tables.php @@ -0,0 +1,13 @@ +