mirror of
https://github.com/Codappix/search_core.git
synced 2024-11-25 21:56:11 +01:00
FEATURE: Index resolved relations
* TCAIndexer is now able to resolve relations of any kind by using TYPO3 Core API. * Indexed will be a single string or an array, depending of how many relations were resolved. * The same value will be indexed as shown by TCA in backend while editing or displaying.
This commit is contained in:
parent
2b4a3a5bd6
commit
0953f4bb1f
7 changed files with 347 additions and 18 deletions
|
@ -20,9 +20,6 @@ namespace Leonmrni\SearchCore\Configuration;
|
||||||
* 02110-1301, USA.
|
* 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class InvalidArgumentException extends \InvalidArgumentException
|
class InvalidArgumentException extends \InvalidArgumentException
|
||||||
{
|
{
|
||||||
const OPTION_DOES_NOT_EXIST = 1481623127;
|
const OPTION_DOES_NOT_EXIST = 1481623127;
|
||||||
|
|
27
Classes/Domain/Index/TcaIndexer/InvalidArgumentException.php
Normal file
27
Classes/Domain/Index/TcaIndexer/InvalidArgumentException.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException
|
||||||
|
{
|
||||||
|
const COLUMN_DOES_NOT_EXIST = 1481632388;
|
||||||
|
const RECORD_NOT_FOUND = 1481643208;
|
||||||
|
}
|
119
Classes/Domain/Index/TcaIndexer/RelationResolver.php
Normal file
119
Classes/Domain/Index/TcaIndexer/RelationResolver.php
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?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 TYPO3\CMS\Core\SingletonInterface as Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves relations from TCA using TCA.
|
||||||
|
*
|
||||||
|
* E.g. resolves mm relations, items for selects, group db, etc.
|
||||||
|
* Will replace the column with an array of resolved labels.
|
||||||
|
*/
|
||||||
|
class RelationResolver implements Singleton
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \TYPO3\CMS\Backend\Form\DataPreprocessor
|
||||||
|
* @inject
|
||||||
|
*/
|
||||||
|
protected $formEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve relations for the given record.
|
||||||
|
*
|
||||||
|
* @param TcaTableService $service
|
||||||
|
* @param array $record
|
||||||
|
*/
|
||||||
|
public function resolveRelationsForRecord(TcaTableService $service, array &$record)
|
||||||
|
{
|
||||||
|
$preprocessedData = $this->formEngine->renderRecordRaw(
|
||||||
|
$service->getTableName(),
|
||||||
|
$record['uid'],
|
||||||
|
$record['pid'],
|
||||||
|
$record
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (array_keys($record) as $column) {
|
||||||
|
try {
|
||||||
|
$config = $service->getColumnConfig($column);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
// Column is not configured.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->isRelation($config)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$record[$column] = $this->resolveValue(
|
||||||
|
$preprocessedData[$column],
|
||||||
|
$config,
|
||||||
|
$column
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the given value from TYPO3 API response.
|
||||||
|
*
|
||||||
|
* As FormEngine uses an internal format, we resolve it to a usable format
|
||||||
|
* for indexing.
|
||||||
|
*
|
||||||
|
* TODO: Unittest to break as soon as TYPO3 api has changed, so we know
|
||||||
|
* exactly that we need to tackle this place.
|
||||||
|
*
|
||||||
|
* @param string $value The value from FormEngine to resolve.
|
||||||
|
*
|
||||||
|
* @return array<String>|string
|
||||||
|
*/
|
||||||
|
protected function resolveValue($value)
|
||||||
|
{
|
||||||
|
$newValue = [];
|
||||||
|
if ($value === '' || $value === '0') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($value, '|') === false) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (\TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $value) as $value) {
|
||||||
|
$value = substr($value, strpos($value, '|') + 1);
|
||||||
|
$value = rawurldecode($value);
|
||||||
|
$newValue[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array Column config.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isRelation(array &$config)
|
||||||
|
{
|
||||||
|
return isset($config['foreign_table'])
|
||||||
|
|| (isset($config['items']) && is_array($config['items']))
|
||||||
|
|| (isset($config['internal_type']) && strtolower($config['internal_type']) === 'db')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ use Leonmrni\SearchCore\Domain\Index\IndexingException;
|
||||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulate logik related to tca configuration.
|
* Encapsulate logik related to TCA configuration.
|
||||||
*/
|
*/
|
||||||
class TcaTableService
|
class TcaTableService
|
||||||
{
|
{
|
||||||
|
@ -51,6 +51,11 @@ class TcaTableService
|
||||||
*/
|
*/
|
||||||
protected $logger;
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RelationResolver
|
||||||
|
*/
|
||||||
|
protected $relationResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject log manager to get concrete logger from it.
|
* Inject log manager to get concrete logger from it.
|
||||||
*
|
*
|
||||||
|
@ -65,8 +70,11 @@ class TcaTableService
|
||||||
* @param string $tableName
|
* @param string $tableName
|
||||||
* @param ConfigurationContainer $configuration
|
* @param ConfigurationContainer $configuration
|
||||||
*/
|
*/
|
||||||
public function __construct($tableName, ConfigurationContainerInterface $configuration)
|
public function __construct(
|
||||||
{
|
$tableName,
|
||||||
|
RelationResolver $relationResolver,
|
||||||
|
ConfigurationContainerInterface $configuration
|
||||||
|
) {
|
||||||
if (!isset($GLOBALS['TCA'][$tableName])) {
|
if (!isset($GLOBALS['TCA'][$tableName])) {
|
||||||
throw new IndexingException(
|
throw new IndexingException(
|
||||||
'Table "' . $tableName . '" is not configured in TCA.',
|
'Table "' . $tableName . '" is not configured in TCA.',
|
||||||
|
@ -77,6 +85,7 @@ class TcaTableService
|
||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
$this->tca = &$GLOBALS['TCA'][$this->tableName];
|
$this->tca = &$GLOBALS['TCA'][$this->tableName];
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
|
$this->relationResolver = $relationResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +110,7 @@ class TcaTableService
|
||||||
*/
|
*/
|
||||||
public function prepareRecord(array &$record)
|
public function prepareRecord(array &$record)
|
||||||
{
|
{
|
||||||
// TODO: Resolve values from 'items' like static select, radio or checkbox.
|
$this->relationResolver->resolveRelationsForRecord($this, $record);
|
||||||
|
|
||||||
if (isset($record['uid']) && !isset($record['search_identifier'])) {
|
if (isset($record['uid']) && !isset($record['search_identifier'])) {
|
||||||
$record['search_identifier'] = $record['uid'];
|
$record['search_identifier'] = $record['uid'];
|
||||||
|
@ -144,8 +153,7 @@ class TcaTableService
|
||||||
array_filter(
|
array_filter(
|
||||||
array_keys($this->tca['columns']),
|
array_keys($this->tca['columns']),
|
||||||
function ($columnName) {
|
function ($columnName) {
|
||||||
$columnConfig = $this->tca['columns'][$columnName]['config'];
|
return !$this->isSystemField($columnName);
|
||||||
return !$this->isRelation($columnConfig) && !$this->isSystemField($columnName);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -158,15 +166,6 @@ class TcaTableService
|
||||||
return implode(',', $fields);
|
return implode(',', $fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function isRelation(array &$columnConfig)
|
|
||||||
{
|
|
||||||
return isset($columnConfig['foreign_table']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string
|
* @param string
|
||||||
* @return bool
|
* @return bool
|
||||||
|
@ -189,4 +188,19 @@ class TcaTableService
|
||||||
|
|
||||||
return in_array($columnName, $systemFields);
|
return in_array($columnName, $systemFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $columnName
|
||||||
|
* @return array
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function getColumnConfig($columnName)
|
||||||
|
{
|
||||||
|
if (!isset($this->tca['columns'][$columnName])) {
|
||||||
|
throw new InvalidArgumentException('Column does not exist.', InvalidArgumentException::COLUMN_DOES_NOT_EXIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->tca['columns'][$columnName]['config'];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ abstract class AbstractFunctionalTestCase extends CoreTestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeLanguageObject();
|
||||||
|
|
||||||
// Provide necessary configuration for extension
|
// Provide necessary configuration for extension
|
||||||
$this->importDataSet('Tests/Functional/Fixtures/BasicSetup.xml');
|
$this->importDataSet('Tests/Functional/Fixtures/BasicSetup.xml');
|
||||||
$this->setUpFrontendRootPage(1, ['EXT:search_core/Tests/Functional/Fixtures/BasicSetup.ts']);
|
$this->setUpFrontendRootPage(1, ['EXT:search_core/Tests/Functional/Fixtures/BasicSetup.ts']);
|
||||||
|
|
123
Tests/Functional/Fixtures/Indexing/ResolveRelations.xml
Normal file
123
Tests/Functional/Fixtures/Indexing/ResolveRelations.xml
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<dataset>
|
||||||
|
<tt_content>
|
||||||
|
<uid>9</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1480686370</tstamp>
|
||||||
|
<crdate>1480686370</crdate>
|
||||||
|
<hidden>0</hidden>
|
||||||
|
<sorting>72</sorting>
|
||||||
|
<CType>textmedia</CType>
|
||||||
|
<header>Record with relation to multiple sys category record</header>
|
||||||
|
<bodytext>some content</bodytext>
|
||||||
|
<media>0</media>
|
||||||
|
<layout>0</layout>
|
||||||
|
<deleted>0</deleted>
|
||||||
|
<cols>0</cols>
|
||||||
|
<starttime>0</starttime>
|
||||||
|
<endtime>0</endtime>
|
||||||
|
<colPos>0</colPos>
|
||||||
|
<categories>3</categories>
|
||||||
|
<filelink_sorting>0</filelink_sorting>
|
||||||
|
</tt_content>
|
||||||
|
|
||||||
|
<tt_content>
|
||||||
|
<uid>10</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1480686370</tstamp>
|
||||||
|
<crdate>1480686370</crdate>
|
||||||
|
<hidden>0</hidden>
|
||||||
|
<sorting>72</sorting>
|
||||||
|
<CType>textmedia</CType>
|
||||||
|
<header>Record with relation to a single sys category record</header>
|
||||||
|
<bodytext>some content</bodytext>
|
||||||
|
<media>0</media>
|
||||||
|
<layout>0</layout>
|
||||||
|
<deleted>0</deleted>
|
||||||
|
<cols>0</cols>
|
||||||
|
<starttime>0</starttime>
|
||||||
|
<endtime>0</endtime>
|
||||||
|
<colPos>0</colPos>
|
||||||
|
<categories>1</categories>
|
||||||
|
<filelink_sorting>0</filelink_sorting>
|
||||||
|
</tt_content>
|
||||||
|
|
||||||
|
<sys_category>
|
||||||
|
<uid>1</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1480686370</tstamp>
|
||||||
|
<crdate>1480686370</crdate>
|
||||||
|
<deleted>0</deleted>
|
||||||
|
<starttime>0</starttime>
|
||||||
|
<endtime>0</endtime>
|
||||||
|
<sorting>1</sorting>
|
||||||
|
<title>Category 1</title>
|
||||||
|
<description>Category for testing</description>
|
||||||
|
<parent>0</parent>
|
||||||
|
<items>1</items>
|
||||||
|
</sys_category>
|
||||||
|
|
||||||
|
<sys_category>
|
||||||
|
<uid>2</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1480686370</tstamp>
|
||||||
|
<crdate>1480686370</crdate>
|
||||||
|
<deleted>0</deleted>
|
||||||
|
<starttime>0</starttime>
|
||||||
|
<endtime>0</endtime>
|
||||||
|
<sorting>1</sorting>
|
||||||
|
<title>Category 2</title>
|
||||||
|
<description>Another category for testing</description>
|
||||||
|
<parent>1</parent>
|
||||||
|
<items>1</items>
|
||||||
|
</sys_category>
|
||||||
|
|
||||||
|
<sys_category>
|
||||||
|
<uid>3</uid>
|
||||||
|
<pid>1</pid>
|
||||||
|
<tstamp>1480686370</tstamp>
|
||||||
|
<crdate>1480686370</crdate>
|
||||||
|
<deleted>1</deleted>
|
||||||
|
<starttime>0</starttime>
|
||||||
|
<endtime>0</endtime>
|
||||||
|
<sorting>1</sorting>
|
||||||
|
<title>Deleted category</title>
|
||||||
|
<description></description>
|
||||||
|
<parent>0</parent>
|
||||||
|
<items>1</items>
|
||||||
|
</sys_category>
|
||||||
|
|
||||||
|
<sys_category_record_mm>
|
||||||
|
<uid_local>1</uid_local>
|
||||||
|
<uid_foreign>9</uid_foreign>
|
||||||
|
<tablenames>tt_content</tablenames>
|
||||||
|
<fieldname>categories</fieldname>
|
||||||
|
<sorting>2</sorting>
|
||||||
|
<sorting_foreign>2</sorting_foreign>
|
||||||
|
</sys_category_record_mm>
|
||||||
|
<sys_category_record_mm>
|
||||||
|
<uid_local>2</uid_local>
|
||||||
|
<uid_foreign>9</uid_foreign>
|
||||||
|
<tablenames>tt_content</tablenames>
|
||||||
|
<fieldname>categories</fieldname>
|
||||||
|
<sorting>1</sorting>
|
||||||
|
<sorting_foreign>1</sorting_foreign>
|
||||||
|
</sys_category_record_mm>
|
||||||
|
<sys_category_record_mm>
|
||||||
|
<uid_local>3</uid_local>
|
||||||
|
<uid_foreign>9</uid_foreign>
|
||||||
|
<tablenames>tt_content</tablenames>
|
||||||
|
<fieldname>categories</fieldname>
|
||||||
|
<sorting>3</sorting>
|
||||||
|
<sorting_foreign>3</sorting_foreign>
|
||||||
|
</sys_category_record_mm>
|
||||||
|
|
||||||
|
<sys_category_record_mm>
|
||||||
|
<uid_local>2</uid_local>
|
||||||
|
<uid_foreign>10</uid_foreign>
|
||||||
|
<tablenames>tt_content</tablenames>
|
||||||
|
<fieldname>categories</fieldname>
|
||||||
|
<sorting>1</sorting>
|
||||||
|
<sorting_foreign>1</sorting_foreign>
|
||||||
|
</sys_category_record_mm>
|
||||||
|
</dataset>
|
|
@ -123,4 +123,51 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase
|
||||||
'Record was not indexed.'
|
'Record was not indexed.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function resolvesRelations()
|
||||||
|
{
|
||||||
|
$this->setUpBackendUserFromFixture(1);
|
||||||
|
$this->importDataSet('Tests/Functional/Fixtures/Indexing/ResolveRelations.xml');
|
||||||
|
|
||||||
|
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
|
||||||
|
->get(IndexerFactory::class)
|
||||||
|
->getIndexer('tt_content')
|
||||||
|
->index()
|
||||||
|
;
|
||||||
|
|
||||||
|
$response = $this->client->request('typo3content/_search?q=*:*');
|
||||||
|
|
||||||
|
$this->assertArraySubset(
|
||||||
|
['_source' => [
|
||||||
|
'uid' => '9',
|
||||||
|
'CType' => 'textmedia', // Testing items
|
||||||
|
'categories' => ['Category 2', 'Category 1'], // Testing mm (with sorting)
|
||||||
|
]],
|
||||||
|
$response->getData()['hits']['hits'][0],
|
||||||
|
false,
|
||||||
|
'Record was not indexed with resolved category relation to a single value.'
|
||||||
|
);
|
||||||
|
$this->assertArraySubset(
|
||||||
|
['_source' => [
|
||||||
|
'uid' => '10',
|
||||||
|
'CType' => 'textmedia',
|
||||||
|
'categories' => ['Category 2'],
|
||||||
|
]],
|
||||||
|
$response->getData()['hits']['hits'][1],
|
||||||
|
false,
|
||||||
|
'Record was not indexed with resolved category relation to multiple values.'
|
||||||
|
);
|
||||||
|
$this->assertArraySubset(
|
||||||
|
['_source' => [
|
||||||
|
'uid' => '6',
|
||||||
|
'categories' => null,
|
||||||
|
]],
|
||||||
|
$response->getData()['hits']['hits'][2],
|
||||||
|
false,
|
||||||
|
'Record was indexed with resolved category relation, but should not have any.'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue