TASK: Migrate existing code (#2)

This commit is contained in:
Daniel Siepmann 2016-12-09 13:19:35 +01:00 committed by GitHub
parent d89e616a4f
commit b079dd8125
28 changed files with 1355 additions and 30 deletions

View file

@ -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();
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Leonmrni\SearchCore\Connection;
/*
* 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.
*/
/**
* 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);
}

View file

@ -0,0 +1,140 @@
<?php
namespace Leonmrni\SearchCore\Connection;
/*
* 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 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());
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Leonmrni\SearchCore\Connection\Elasticsearch;
/*
* 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 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;
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Leonmrni\SearchCore\Connection\Elasticsearch;
/*
* 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 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;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Leonmrni\SearchCore\Connection\Elasticsearch;
/*
* 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 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;
}
}

View file

@ -1,8 +1,8 @@
<?php
namespace Leonmrni\SearchCore\Tests\Fakes;
namespace Leonmrni\SearchCore\Connection\Elasticsearch;
/*
* Copyright (C) 2016 Daniel Siepmann <daniel.siepmann@typo3.org>
* 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
@ -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'];
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Leonmrni\SearchCore\Connection\Elasticsearch;
/*
* 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 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);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Leonmrni\SearchCore\Connection;
/*
* 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.
*/
/**
*
*/
interface SearchRequestInterface
{
/**
* Returns the actual string the user searched for.
*
* @return string
*/
public function getSearchTerm();
}

View file

@ -0,0 +1,29 @@
<?php
namespace Leonmrni\SearchCore\Connection;
/*
* 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.
*/
/**
*
*/
interface SearchResultInterface extends \Iterator, \Countable, \ArrayAccess
{
}

View file

@ -0,0 +1,64 @@
<?php
namespace Leonmrni\SearchCore\Controller;
/*
* 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 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,
]);
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Leonmrni\SearchCore\Domain\Index;
/*
* 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 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
)
);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Leonmrni\SearchCore\Domain\Index;
/*
* 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.
*/
/**
* Interface that all indexer should implement.
*/
interface IndexerInterface
{
/**
* Index the index.
*/
public function index();
}

View file

@ -0,0 +1,29 @@
<?php
namespace Leonmrni\SearchCore\Domain\Index;
/*
* 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.
*/
/**
* Exception thrown on errors while indexing.
*/
class IndexingException extends \Exception
{
const CODE_UNKOWN_TCA_TABLE = 1481190283;
}

View file

@ -0,0 +1,117 @@
<?php
namespace Leonmrni\SearchCore\Domain\Index;
/*
* 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 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;
}
}

View file

@ -0,0 +1,167 @@
<?php
namespace Leonmrni\SearchCore\Domain\Index\TcaIndexer;
/*
* 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 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;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Leonmrni\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 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 . '"';
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Leonmrni\SearchCore\Domain\Search;
/*
* 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 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);
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Leonmrni\SearchCore\Domain\Service;
/*
* 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 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);
}
}

View file

@ -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.

View file

View file

@ -0,0 +1,12 @@
plugin {
tx_searchcore {
settings {
connection {
host = localhost
port = 9200
}
}
}
}
module.tx_searchcore < plugin.tx_searchcore

View file

@ -0,0 +1,13 @@
<f:form name="searchRequest" object="{searchRequest}">
<f:form.textfield property="query" />
<f:form.submit value="search" />
</f:form>
<f:for each="{searchResult}" as="result">
<f:link.page pageUid="{result.hit._source.pid}">
{result.id} [{result.type}] - {result.hit._source.search_title}
</f:link.page>
<br>
</f:for>

View file

@ -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);

View file

@ -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,

View file

@ -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' => [],
],

48
ext_localconf.php Normal file
View file

@ -0,0 +1,48 @@
<?php
call_user_func(
function ($extensionKey) {
// TODO: Add hook for Extbase -> 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
);

13
ext_tables.php Normal file
View file

@ -0,0 +1,13 @@
<?php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
$_EXTKEY,
'Configuration/TypoScript/',
'Search Core'
);
TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'Leonmrni.' . $_EXTKEY,
'search',
'Search Core'
);