diff --git a/Classes/Database/Doctrine/Join.php b/Classes/Database/Doctrine/Join.php new file mode 100644 index 0000000..df1a8c6 --- /dev/null +++ b/Classes/Database/Doctrine/Join.php @@ -0,0 +1,50 @@ + + * + * 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 Join +{ + /** + * @var string + */ + protected $table = ''; + + /** + * @var string + */ + protected $condition = ''; + + public function __construct(string $table, string $condition) + { + $this->table = $table; + $this->condition = $condition; + } + + public function getTable() : string + { + return $this->table; + } + + public function getCondition() : string + { + return $this->condition; + } +} diff --git a/Classes/Database/Doctrine/Where.php b/Classes/Database/Doctrine/Where.php new file mode 100644 index 0000000..3398991 --- /dev/null +++ b/Classes/Database/Doctrine/Where.php @@ -0,0 +1,53 @@ + + * + * 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 Where +{ + /** + * @var string + */ + protected $statement = ''; + + /** + * @var array + */ + protected $parameters = []; + + public function __construct(string $statement, array $parameters) + { + $this->statement = $statement; + $this->parameters = $parameters; + } + + public function getStatement() : string + { + return $this->statement; + } + + public function getParameters() : array + { + return $this->parameters; + } +} diff --git a/Classes/Domain/Index/TcaIndexer.php b/Classes/Domain/Index/TcaIndexer.php index c0a77a1..18444ce 100644 --- a/Classes/Domain/Index/TcaIndexer.php +++ b/Classes/Domain/Index/TcaIndexer.php @@ -21,6 +21,8 @@ namespace Codappix\SearchCore\Domain\Index; */ use Codappix\SearchCore\Connection\ConnectionInterface; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Will index the given table using configuration from TCA. @@ -51,14 +53,22 @@ class TcaIndexer extends AbstractIndexer */ protected function getRecords($offset, $limit) { - $records = $this->getDatabaseConnection()->exec_SELECTgetRows( - $this->tcaTableService->getFields(), - $this->tcaTableService->getTableClause(), - $this->tcaTableService->getWhereClause(), - '', - '', - (int) $offset . ',' . (int) $limit - ); + $queryBuilder = $this->getDatabaseConnection()->getQueryBuilderForTable($this->tcaTableService->getTableName()); + $where = $this->tcaTableService->getWhereClause(); + $query = $queryBuilder->select(... $this->tcaTableService->getFields()) + ->from($this->tcaTableService->getTableClause()) + ->where($where->getStatement()) + ->setParameters($where->getParameters()) + ->setFirstResult($offset) + ->setMaxResults($limit); + + foreach ($this->tcaTableService->getJoins() as $join) { + $query->from($join->getTable()); + $query->andWhere($join->getCondition()); + } + + $records = $query->execute()->fetchAll(); + if ($records === null) { return null; } @@ -106,7 +116,6 @@ class TcaIndexer extends AbstractIndexer protected function getDatabaseConnection() { - return GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionByName('Default'); + return GeneralUtility::makeInstance(ConnectionPool::class); } } diff --git a/Classes/Domain/Index/TcaIndexer/RelationResolver.php b/Classes/Domain/Index/TcaIndexer/RelationResolver.php index b09e483..93cfffb 100644 --- a/Classes/Domain/Index/TcaIndexer/RelationResolver.php +++ b/Classes/Domain/Index/TcaIndexer/RelationResolver.php @@ -42,38 +42,31 @@ class RelationResolver implements Singleton * @param TcaTableService $service * @param array $record */ - public function resolveRelationsForRecord(TcaTableService $service, array &$record) + public function resolveRelationsForRecord(TcaTableService $service, array &$record) : void { - $formData = GeneralUtility::makeInstance( - FormDataCompiler::class, - GeneralUtility::makeInstance(TcaDatabaseRecord::class) - )->compile([ - 'tableName' => $service->getTableName(), - 'vanillaUid' => (int)$record['uid'], - 'command' => 'edit', - ]); - $record = $formData['databaseRow']; - foreach (array_keys($record) as $column) { + // TODO: Define / configure fields to exclude?! + if ($column === 'pid') { + continue; + } + $record[$column] = BackendUtility::getProcessedValueExtra($service->getTableName(), $column, $record[$column], 0, $record['uid']); + try { $config = $service->getColumnConfig($column); + + if ($this->isRelation($config)) { + $record[$column] = $this->resolveValue($record[$column], $config); + } } catch (InvalidArgumentException $e) { // Column is not configured. - continue; } - - if (! $this->isRelation($config) || !is_array($formData['processedTca']['columns'][$column])) { - continue; - } - - $record[$column] = $this->resolveValue($record[$column], $formData['processedTca']['columns'][$column]); } } /** * Resolve the given value from TYPO3 API response. * - * @param string $value The value from FormEngine to resolve. + * @param mixed $value The value from FormEngine to resolve. * @param array $tcaColumn The tca config of the relation. * * @return array|string @@ -83,15 +76,10 @@ class RelationResolver implements Singleton if ($value === '' || $value === '0') { return ''; } - if ($tcaColumn['config']['type'] === 'select') { - return $this->resolveSelectValue($value, $tcaColumn); - } - if ($tcaColumn['config']['type'] === 'group' && strpos($value, '|') !== false) { + + if ($tcaColumn['type'] === 'select' || $tcaColumn['type'] === 'group') { return $this->resolveForeignDbValue($value); } - if ($tcaColumn['config']['type'] === 'inline') { - return $this->resolveInlineValue($tcaColumn); - } return ''; } @@ -100,66 +88,24 @@ class RelationResolver implements Singleton * @param array Column config. * @return bool */ - protected function isRelation(array &$config) + protected function isRelation(array &$config) : bool { return isset($config['foreign_table']) - || (isset($config['items']) && is_array($config['items'])) + || (isset($config['renderType']) && $config['renderType'] !== 'selectSingle') || (isset($config['internal_type']) && strtolower($config['internal_type']) === 'db') ; } - /** - * Resolves internal representation of select to array of labels. - * - * @param array $value - * @param array $tcaColumn - * @return array - */ - protected function resolveSelectValue(array $values, array $tcaColumn) - { - $resolvedValues = []; - - foreach ($tcaColumn['config']['items'] as $item) { - if (in_array($item[1], $values)) { - $resolvedValues[] = $item[0]; - } - } - - if ($tcaColumn['config']['renderType'] === 'selectSingle' || $tcaColumn['config']['maxitems'] === 1) { - return current($resolvedValues); - } - - return $resolvedValues; - } - /** * @param string $value * * @return array */ - protected function resolveForeignDbValue($value) + protected function resolveForeignDbValue(string $value) : array { - $titles = []; - - foreach (explode(',', urldecode($value)) as $title) { - $titles[] = explode('|', $title)[1]; + if ($value === 'N/A') { + return []; } - - return $titles; - } - - /** - * @param array $tcaColumn - * @return array - */ - protected function resolveInlineValue(array $tcaColumn) - { - $titles = []; - - foreach ($tcaColumn['children'] as $selected) { - $titles[] = $selected['recordTitle']; - } - - return $titles; + return array_map('trim', explode(';', $value)); } } diff --git a/Classes/Domain/Index/TcaIndexer/TcaTableService.php b/Classes/Domain/Index/TcaIndexer/TcaTableService.php index 65adf70..77b7578 100644 --- a/Classes/Domain/Index/TcaIndexer/TcaTableService.php +++ b/Classes/Domain/Index/TcaIndexer/TcaTableService.php @@ -21,6 +21,8 @@ namespace Codappix\SearchCore\Domain\Index\TcaIndexer; */ use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; +use Codappix\SearchCore\Database\Doctrine\Join; +use Codappix\SearchCore\Database\Doctrine\Where; use Codappix\SearchCore\Domain\Index\IndexingException; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -92,7 +94,7 @@ class TcaTableService /** * @return string */ - public function getTableName() + public function getTableName() : string { return $this->tableName; } @@ -100,9 +102,9 @@ class TcaTableService /** * @return string */ - public function getTableClause() + public function getTableClause() : string { - return $this->tableName . ' LEFT JOIN pages on ' . $this->tableName . '.pid = pages.uid'; + return $this->tableName; } /** @@ -111,7 +113,7 @@ class TcaTableService * @param array &$records * @return void */ - public function filterRecordsByRootLineBlacklist(array &$records) + public function filterRecordsByRootLineBlacklist(array &$records) : void { $records = array_filter( $records, @@ -125,7 +127,7 @@ class TcaTableService * Adjust record accordingly to configuration. * @param array &$record */ - public function prepareRecord(array &$record) + public function prepareRecord(array &$record) : void { $this->relationResolver->resolveRelationsForRecord($this, $record); @@ -137,11 +139,9 @@ class TcaTableService } } - /** - * @return string - */ - public function getWhereClause() + public function getWhereClause() : Where { + $parameters = []; $whereClause = $this->getSystemWhereClause(); $userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause'); if (is_string($userDefinedWhere)) { @@ -149,16 +149,41 @@ class TcaTableService } if ($this->isBlacklistedRootLineConfigured()) { - $whereClause .= ' AND pages.uid NOT IN (' - . implode(',', $this->getBlacklistedRootLine()) - . ')' - . ' AND pages.pid NOT IN (' - . implode(',', $this->getBlacklistedRootLine()) - . ')'; + $parameters[':blacklistedRootLine'] = $this->getBlacklistedRootLine(); + $whereClause .= ' AND pages.uid NOT IN (:blacklistedRootLine)' + . ' AND pages.pid NOT IN (:blacklistedRootLine)'; } $this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]); - return $whereClause; + return new Where($whereClause, $parameters); + } + + public function getFields() : array + { + $fields = array_merge( + ['uid','pid'], + array_filter( + array_keys($this->tca['columns']), + function ($columnName) { + return !$this->isSystemField($columnName); + } + ) + ); + + foreach ($fields as $key => $field) { + $fields[$key] = $this->tableName . '.' . $field; + } + + $this->logger->debug('Generated fields.', [$this->tableName, $fields]); + return $fields; + return implode(', ', $fields); + } + + public function getJoins() : array + { + return [ + new Join('pages', 'pages.uid = ' . $this->tableName . '.pid'), + ]; } /** @@ -177,34 +202,11 @@ class TcaTableService ; } - /** - * @return string - */ - public function getFields() - { - $fields = array_merge( - ['uid','pid'], - array_filter( - array_keys($this->tca['columns']), - function ($columnName) { - return !$this->isSystemField($columnName); - } - ) - ); - - foreach ($fields as $key => $field) { - $fields[$key] = $this->tableName . '.' . $field; - } - - $this->logger->debug('Generated fields.', [$this->tableName, $fields]); - return implode(',', $fields); - } - /** * @param string * @return bool */ - protected function isSystemField($columnName) + protected function isSystemField($columnName) : bool { $systemFields = [ // Versioning fields, @@ -228,7 +230,7 @@ class TcaTableService * @return array * @throws InvalidArgumentException */ - public function getColumnConfig($columnName) + public function getColumnConfig($columnName) : array { if (!isset($this->tca['columns'][$columnName])) { throw new InvalidArgumentException( @@ -250,7 +252,7 @@ class TcaTableService * @param array &$record * @return bool */ - protected function isRecordBlacklistedByRootline(array &$record) + protected function isRecordBlacklistedByRootline(array &$record) : bool { // If no rootline exists, the record is on a unreachable page and therefore blacklisted. $rootline = BackendUtility::BEgetRootLine($record['pid']); @@ -275,7 +277,7 @@ class TcaTableService * * @return bool */ - protected function isBlackListedRootLineConfigured() + protected function isBlackListedRootLineConfigured() : bool { return (bool) $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'); } @@ -285,7 +287,7 @@ class TcaTableService * * @return array */ - protected function getBlackListedRootLine() + protected function getBlackListedRootLine() : array { return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist')); } diff --git a/Makefile b/Makefile index 87bcdfc..b6bc72b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ functionalTests: typo3DatabaseHost=$(typo3DatabaseHost) \ TYPO3_PATH_WEB=$(TYPO3_WEB_DIR) \ .Build/bin/phpunit --colors --debug -v \ - --stop-on-error \ -c Tests/Functional/FunctionalTests.xml unitTests: diff --git a/Tests/Functional/Connection/Elasticsearch/FilterTest.php b/Tests/Functional/Connection/Elasticsearch/FilterTest.php index aa5a5dc..f0b8404 100644 --- a/Tests/Functional/Connection/Elasticsearch/FilterTest.php +++ b/Tests/Functional/Connection/Elasticsearch/FilterTest.php @@ -55,7 +55,7 @@ class FilterTest extends AbstractFunctionalTestCase $searchRequest->setFilter(['CType' => 'HTML']); $result = $searchService->search($searchRequest); - $this->assertSame('5', $result->getResults()[0]['uid'], 'Did not get the expected result entry.'); + $this->assertSame(5, $result->getResults()[0]['uid'], 'Did not get the expected result entry.'); $this->assertSame(1, count($result), 'Did not receive the single filtered element.'); } diff --git a/Tests/Functional/Connection/Elasticsearch/IndexTcaTableTest.php b/Tests/Functional/Connection/Elasticsearch/IndexTcaTableTest.php index 9678d83..8a76c77 100644 --- a/Tests/Functional/Connection/Elasticsearch/IndexTcaTableTest.php +++ b/Tests/Functional/Connection/Elasticsearch/IndexTcaTableTest.php @@ -150,7 +150,7 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase ['_source' => [ 'uid' => '9', 'CType' => 'Header', // Testing items - 'categories' => ['Category 1', 'Category 2'], // Testing mm (with sorting) + 'categories' => ['Category 2', 'Category 1'], // Testing mm ]], $response->getData()['hits']['hits'][0], false, diff --git a/Tests/Unit/Domain/Index/TcaIndexer/TcaTableServiceTest.php b/Tests/Unit/Domain/Index/TcaIndexer/TcaTableServiceTest.php index 330c16e..58c03b4 100644 --- a/Tests/Unit/Domain/Index/TcaIndexer/TcaTableServiceTest.php +++ b/Tests/Unit/Domain/Index/TcaIndexer/TcaTableServiceTest.php @@ -64,9 +64,14 @@ class TcaTableServiceTest extends AbstractUnitTestCase ->method('getSystemWhereClause') ->will($this->returnValue('1=1 AND pages.no_search = 0')); + $whereClause =$this->subject->getWhereClause(); $this->assertSame( '1=1 AND pages.no_search = 0', - $this->subject->getWhereClause() + $whereClause->getStatement() + ); + $this->assertSame( + [], + $whereClause->getParameters() ); } @@ -83,9 +88,14 @@ class TcaTableServiceTest extends AbstractUnitTestCase ->method('getSystemWhereClause') ->will($this->returnValue('1=1 AND pages.no_search = 0')); + $whereClause = $this->subject->getWhereClause(); $this->assertSame( '1=1 AND pages.no_search = 0 AND table.field = "someValue"', - $this->subject->getWhereClause() + $whereClause->getStatement() + ); + $this->assertSame( + [], + $whereClause->getParameters() ); } }