Merge pull request #97 from whmyr/support/76

Update TYPO3 7.6 support with features implemented in 6.2.
This commit is contained in:
Daniel Siepmann 2017-11-09 14:34:22 +01:00 committed by GitHub
commit 63c5bf14ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 296 additions and 276 deletions

View file

@ -11,6 +11,8 @@ before_install:
language: php language: php
php: php:
- 5.6
- 7.0
- 7.1 - 7.1
env: env:
@ -23,6 +25,7 @@ env:
- typo3DatabaseHost="127.0.0.1" - typo3DatabaseHost="127.0.0.1"
- typo3DatabaseUsername="travis" - typo3DatabaseUsername="travis"
- typo3DatabasePassword="" - typo3DatabasePassword=""
- TYPO3_VERSION="~7.6"
matrix: matrix:
fast_finish: true fast_finish: true

View file

@ -39,6 +39,21 @@ class IndexFactory implements Singleton
*/ */
protected $configuration; protected $configuration;
/**
* @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 ConfigurationContainerInterface $configuration * @param ConfigurationContainerInterface $configuration
*/ */
@ -60,7 +75,10 @@ class IndexFactory implements Singleton
$index = $connection->getClient()->getIndex('typo3content'); $index = $connection->getClient()->getIndex('typo3content');
if ($index->exists() === false) { if ($index->exists() === false) {
$index->create($this->getConfigurationFor($documentType)); $config = $this->getConfigurationFor($documentType);
$this->logger->debug(sprintf('Create index %s.', $documentType), [$documentType, $config]);
$index->create($config);
$this->logger->debug(sprintf('Created index %s.', $documentType), [$documentType]);
} }
return $index; return $index;
@ -76,9 +94,11 @@ class IndexFactory implements Singleton
try { try {
$configuration = $this->configuration->get('indexing.' . $documentType . '.index'); $configuration = $this->configuration->get('indexing.' . $documentType . '.index');
if (isset($configuration['analysis']['analyzer'])) { foreach (['analyzer', 'filter'] as $optionsToExpand) {
foreach ($configuration['analysis']['analyzer'] as $key => $analyzer) { if (isset($configuration['analysis'][$optionsToExpand])) {
$configuration['analysis']['analyzer'][$key] = $this->prepareAnalyzerConfiguration($analyzer); foreach ($configuration['analysis'][$optionsToExpand] as $key => $options) {
$configuration['analysis'][$optionsToExpand][$key] = $this->prepareOptions($options);
}
} }
} }
@ -89,20 +109,20 @@ class IndexFactory implements Singleton
} }
/** /**
* @param array $analyzer * @param array $options
* *
* @return array * @return array
*/ */
protected function prepareAnalyzerConfiguration(array $analyzer) protected function prepareOptions(array $options)
{ {
$fieldsToExplode = ['char_filter', 'filter']; $fieldsToExplode = ['char_filter', 'filter', 'word_list'];
foreach ($fieldsToExplode as $fieldToExplode) { foreach ($fieldsToExplode as $fieldToExplode) {
if (isset($analyzer[$fieldToExplode])) { if (isset($options[$fieldToExplode])) {
$analyzer[$fieldToExplode] = GeneralUtility::trimExplode(',', $analyzer[$fieldToExplode], true); $options[$fieldToExplode] = GeneralUtility::trimExplode(',', $options[$fieldToExplode], true);
} }
} }
return $analyzer; return $options;
} }
} }

View file

@ -83,7 +83,7 @@ class SearchResult implements SearchResultInterface
/** /**
* Return all facets, if any. * Return all facets, if any.
* *
* @return array<FacetIterface> * @return array<FacetInterface>
*/ */
public function getFacets() public function getFacets()
{ {

View file

@ -35,7 +35,7 @@ interface SearchResultInterface extends \Iterator, \Countable, QueryResultInterf
/** /**
* Return all facets, if any. * Return all facets, if any.
* *
* @return array<FacetIterface> * @return array<FacetInterface>
*/ */
public function getFacets(); public function getFacets();

View file

@ -1,5 +1,5 @@
<?php <?php
namespace Codappix\SearchCore\Database\Doctrine; namespace Codappix\SearchCore\DataProcessing;
/* /*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de> * Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
@ -20,31 +20,35 @@ namespace Codappix\SearchCore\Database\Doctrine;
* 02110-1301, USA. * 02110-1301, USA.
*/ */
class Where /**
* Copies values from one field to another one.
*/
class CopyToProcessor implements ProcessorInterface
{ {
/** public function processRecord(array $record, array $configuration)
* @var string
*/
protected $statement = '';
/**
* @var array
*/
protected $parameters = [];
public function __construct(string $statement, array $parameters)
{ {
$this->statement = $statement; $all = [];
$this->parameters = $parameters;
$this->addArray($all, $record);
$all = array_filter($all);
$record[$configuration['to']] = implode(PHP_EOL, $all);
return $record;
} }
public function getStatement() : string /**
* @param array &$to
* @param array $from
*/
protected function addArray(array &$to, array $from)
{ {
return $this->statement; foreach ($from as $property => $value) {
if (is_array($value)) {
$this->addArray($to, $value);
continue;
} }
public function getParameters() : array $to[] = (string) $value;
{ }
return $this->parameters;
} }
} }

View file

@ -1,5 +1,5 @@
<?php <?php
namespace Codappix\SearchCore\Database\Doctrine; namespace Codappix\SearchCore\DataProcessing;
/* /*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de> * Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
@ -20,31 +20,20 @@ namespace Codappix\SearchCore\Database\Doctrine;
* 02110-1301, USA. * 02110-1301, USA.
*/ */
class Join /**
* All DataProcessing Processor should implement this interface, otherwise they
* will not be executed.
*/
interface ProcessorInterface
{ {
/** /**
* @var string * Processes the given record.
* Also retrieves the configuration for this processor instance.
*
* @param array $record
* @param array $configuration
*
* @return array
*/ */
protected $table = ''; public function processRecord(array $record, array $configuration);
/**
* @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;
}
} }

View file

@ -22,8 +22,6 @@ namespace Codappix\SearchCore\Domain\Index;
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Configuration\InvalidArgumentException; use Codappix\SearchCore\Configuration\InvalidArgumentException;
use Codappix\SearchCore\Domain\Index\IndexerInterface;
use Codappix\SearchCore\Domain\Index\TcaIndexer;
use Codappix\SearchCore\Domain\Index\TcaIndexer\TcaTableService; use Codappix\SearchCore\Domain\Index\TcaIndexer\TcaTableService;
use TYPO3\CMS\Core\SingletonInterface as Singleton; use TYPO3\CMS\Core\SingletonInterface as Singleton;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;

View file

@ -22,9 +22,6 @@ namespace Codappix\SearchCore\Domain\Index;
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Connection\ConnectionInterface; use Codappix\SearchCore\Connection\ConnectionInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/** /**
* Will index the given table using configuration from TCA. * Will index the given table using configuration from TCA.
@ -58,12 +55,14 @@ class TcaIndexer extends AbstractIndexer
*/ */
protected function getRecords($offset, $limit) protected function getRecords($offset, $limit)
{ {
$records = $this->getQuery() $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
->setFirstResult($offset) $this->tcaTableService->getFields(),
->setMaxResults($limit) $this->tcaTableService->getTableClause(),
->execute() $this->tcaTableService->getWhereClause(),
->fetchAll(); '',
'',
(int) $offset . ',' . (int) $limit
);
if ($records === null) { if ($records === null) {
return null; return null;
} }
@ -83,9 +82,12 @@ class TcaIndexer extends AbstractIndexer
*/ */
protected function getRecord($identifier) protected function getRecord($identifier)
{ {
$query = $this->getQuery(); $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
$query = $query->andWhere($this->tcaTableService->getTableName() . '.uid = ' . (int) $identifier); $this->tcaTableService->getFields(),
$record = $query->execute()->fetch(); $this->tcaTableService->getTableClause(),
$this->tcaTableService->getWhereClause()
. ' AND ' . $this->tcaTableService->getTableName() . '.uid = ' . (int) $identifier
);
if ($record === false || $record === null) { if ($record === false || $record === null) {
throw new NoRecordFoundException( throw new NoRecordFoundException(
@ -105,29 +107,4 @@ class TcaIndexer extends AbstractIndexer
{ {
return $this->tcaTableService->getTableName(); return $this->tcaTableService->getTableName();
} }
protected function getQuery($tcaTableService = null) : QueryBuilder
{
if ($tcaTableService === null) {
$tcaTableService = $this->tcaTableService;
}
$queryBuilder = $this->getDatabaseConnection()->getQueryBuilderForTable($tcaTableService->getTableName());
$where = $tcaTableService->getWhereClause();
$query = $queryBuilder->select(... $tcaTableService->getFields())
->from($tcaTableService->getTableClause())
->where($where->getStatement())
->setParameters($where->getParameters());
foreach ($tcaTableService->getJoins() as $join) {
$query->from($join->getTable());
$query->andWhere($join->getCondition());
}
return $query;
}
protected function getDatabaseConnection()
{
return GeneralUtility::makeInstance(ConnectionPool::class);
}
} }

View file

@ -34,6 +34,12 @@ class PagesIndexer extends TcaIndexer
*/ */
protected $contentTableService; protected $contentTableService;
/**
* @var \TYPO3\CMS\Core\Resource\FileRepository
* @inject
*/
protected $fileRepository;
/** /**
* @param TcaTableService $tcaTableService * @param TcaTableService $tcaTableService
* @param TcaTableService $tcaTableService * @param TcaTableService $tcaTableService
@ -65,32 +71,69 @@ class PagesIndexer extends TcaIndexer
} }
} }
$record['content'] = $this->fetchContentForPage($record['uid']); $content = $this->fetchContentForPage($record['uid']);
if ($content !== []) {
$record['content'] = $content['content'];
$record['media'] = array_unique(array_merge($record['media'], $content['images']));
}
parent::prepareRecord($record); parent::prepareRecord($record);
} }
/** /**
* @param int $uid * @param int $uid
* @return string * @return []
*/ */
protected function fetchContentForPage($uid) protected function fetchContentForPage($uid)
{ {
$contentElements = $this->getQuery($this->contentTableService)->execute()->fetchAll(); $contentElements = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
$this->contentTableService->getFields(),
$this->contentTableService->getTableClause(),
$this->contentTableService->getWhereClause() .
sprintf(' AND %s.pid = %u', $this->contentTableService->getTableName(), $uid)
);
if ($contentElements === null) { if ($contentElements === null) {
$this->logger->debug('No content for page ' . $uid); $this->logger->debug('No content for page ' . $uid);
return ''; return [];
} }
$this->logger->debug('Fetched content for page ' . $uid); $this->logger->debug('Fetched content for page ' . $uid);
$images = [];
$content = []; $content = [];
foreach ($contentElements as $contentElement) { foreach ($contentElements as $contentElement) {
$images = array_merge(
$images,
$this->getContentElementImages($contentElement['uid'])
);
$content[] = $contentElement['bodytext']; $content[] = $contentElement['bodytext'];
} }
return [
// Remove Tags. // Remove Tags.
// Interpret escaped new lines and special chars. // Interpret escaped new lines and special chars.
// Trim, e.g. trailing or leading new lines. // Trim, e.g. trailing or leading new lines.
return trim(stripcslashes(strip_tags(implode(' ', $content)))); 'content' => trim(stripcslashes(strip_tags(implode(' ', $content)))),
'images' => $images,
];
}
/**
* @param int $uidOfContentElement
* @return array
*/
protected function getContentElementImages($uidOfContentElement)
{
$imageRelationUids = [];
$imageRelations = $this->fileRepository->findByRelation(
'tt_content',
'image',
$uidOfContentElement
);
foreach ($imageRelations as $relation) {
$imageRelationUids[] = $relation->getUid();
}
return $imageRelationUids;
} }
} }

View file

@ -22,7 +22,6 @@ namespace Codappix\SearchCore\Domain\Index\TcaIndexer;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\SingletonInterface as Singleton; use TYPO3\CMS\Core\SingletonInterface as Singleton;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/** /**
* Resolves relations from TCA using TCA. * Resolves relations from TCA using TCA.
@ -32,7 +31,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
*/ */
class RelationResolver implements Singleton class RelationResolver implements Singleton
{ {
public function resolveRelationsForRecord(TcaTableService $service, array &$record) : void public function resolveRelationsForRecord(TcaTableService $service, array &$record)
{ {
foreach (array_keys($record) as $column) { foreach (array_keys($record) as $column) {
// TODO: Define / configure fields to exclude?! // TODO: Define / configure fields to exclude?!
@ -76,7 +75,7 @@ class RelationResolver implements Singleton
return []; return [];
} }
protected function isRelation(array &$config) : bool protected function isRelation(array &$config)
{ {
return isset($config['foreign_table']) return isset($config['foreign_table'])
|| (isset($config['renderType']) && $config['renderType'] !== 'selectSingle') || (isset($config['renderType']) && $config['renderType'] !== 'selectSingle')
@ -84,12 +83,12 @@ class RelationResolver implements Singleton
; ;
} }
protected function resolveForeignDbValue(string $value) : array protected function resolveForeignDbValue($value)
{ {
return array_map('trim', explode(';', $value)); return array_map('trim', explode(';', $value));
} }
protected function resolveInlineValue(string $value) : array protected function resolveInlineValue($value)
{ {
return array_map('trim', explode(',', $value)); return array_map('trim', explode(',', $value));
} }

View file

@ -21,8 +21,8 @@ namespace Codappix\SearchCore\Domain\Index\TcaIndexer;
*/ */
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Database\Doctrine\Join; use Codappix\SearchCore\Configuration\InvalidArgumentException as InvalidConfigurationArgumentException;
use Codappix\SearchCore\Database\Doctrine\Where; use Codappix\SearchCore\DataProcessing\ProcessorInterface;
use Codappix\SearchCore\Domain\Index\IndexingException; use Codappix\SearchCore\Domain\Index\IndexingException;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@ -109,7 +109,7 @@ class TcaTableService
/** /**
* @return string * @return string
*/ */
public function getTableName() : string public function getTableName()
{ {
return $this->tableName; return $this->tableName;
} }
@ -117,18 +117,22 @@ class TcaTableService
/** /**
* @return string * @return string
*/ */
public function getTableClause() : string public function getTableClause()
{ {
if ($this->tableName === 'pages') {
return $this->tableName; return $this->tableName;
} }
return $this->tableName . ' LEFT JOIN pages on ' . $this->tableName . '.pid = pages.uid';
}
/** /**
* Filter the given records by root line blacklist settings. * Filter the given records by root line blacklist settings.
* *
* @param array &$records * @param array &$records
* @return void * @return void
*/ */
public function filterRecordsByRootLineBlacklist(array &$records) : void public function filterRecordsByRootLineBlacklist(array &$records)
{ {
$records = array_filter( $records = array_filter(
$records, $records,
@ -142,10 +146,21 @@ class TcaTableService
* Adjust record accordingly to configuration. * Adjust record accordingly to configuration.
* @param array &$record * @param array &$record
*/ */
public function prepareRecord(array &$record) : void public function prepareRecord(array &$record)
{ {
$this->relationResolver->resolveRelationsForRecord($this, $record); $this->relationResolver->resolveRelationsForRecord($this, $record);
try {
foreach ($this->configuration->get('indexing.' . $this->tableName . '.dataProcessing') as $configuration) {
$dataProcessor = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($configuration['_typoScriptNodeValue']);
if ($dataProcessor instanceof ProcessorInterface) {
$record = $dataProcessor->processRecord($record, $configuration);
}
}
} catch (InvalidConfigurationArgumentException $e) {
// Nothing to do.
}
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'];
} }
@ -154,10 +169,22 @@ class TcaTableService
} }
} }
public function getWhereClause() : Where /**
* @return string
*/
public function getWhereClause()
{ {
$parameters = []; $whereClause = '1=1'
$whereClause = $this->getSystemWhereClause(); . BackendUtility::BEenableFields($this->tableName)
. BackendUtility::deleteClause($this->tableName)
. ' AND pages.no_search = 0'
;
if ($this->tableName !== 'pages') {
$whereClause .= BackendUtility::BEenableFields('pages')
. BackendUtility::deleteClause('pages')
;
}
$userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause'); $userDefinedWhere = $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.additionalWhereClause');
if (is_string($userDefinedWhere)) { if (is_string($userDefinedWhere)) {
@ -165,16 +192,22 @@ class TcaTableService
} }
if ($this->isBlacklistedRootLineConfigured()) { if ($this->isBlacklistedRootLineConfigured()) {
$parameters[':blacklistedRootLine'] = $this->getBlacklistedRootLine(); $whereClause .= ' AND pages.uid NOT IN ('
$whereClause .= ' AND pages.uid NOT IN (:blacklistedRootLine)' . implode(',', $this->getBlacklistedRootLine())
. ' AND pages.pid NOT IN (:blacklistedRootLine)'; . ')'
. ' AND pages.pid NOT IN ('
. implode(',', $this->getBlacklistedRootLine())
. ')';
} }
$this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]); $this->logger->debug('Generated where clause.', [$this->tableName, $whereClause]);
return new Where($whereClause, $parameters); return $whereClause;
} }
public function getFields() : array /**
* @return string
*/
public function getFields()
{ {
$fields = array_merge( $fields = array_merge(
['uid','pid'], ['uid','pid'],
@ -191,46 +224,14 @@ class TcaTableService
} }
$this->logger->debug('Generated fields.', [$this->tableName, $fields]); $this->logger->debug('Generated fields.', [$this->tableName, $fields]);
return $fields; return implode(',', $fields);
}
public function getJoins() : array
{
if ($this->tableName === 'pages') {
return [];
}
return [
new Join('pages', 'pages.uid = ' . $this->tableName . '.pid'),
];
}
/**
* Generate SQL for TYPO3 as a system, to make sure only available records
* are fetched.
*/
public function getSystemWhereClause() : string
{
$whereClause = '1=1'
. BackendUtility::BEenableFields($this->tableName)
. BackendUtility::deleteClause($this->tableName)
. ' AND pages.no_search = 0'
;
if ($this->tableName !== 'pages') {
$whereClause .= BackendUtility::BEenableFields('pages')
. BackendUtility::deleteClause('pages')
;
}
return $whereClause;
} }
/** /**
* @param string * @param string
* @return bool * @return bool
*/ */
protected function isSystemField($columnName) : bool protected function isSystemField($columnName)
{ {
$systemFields = [ $systemFields = [
// Versioning fields, // Versioning fields,
@ -254,7 +255,7 @@ class TcaTableService
* @return array * @return array
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function getColumnConfig($columnName) : array public function getColumnConfig($columnName)
{ {
if (!isset($this->tca['columns'][$columnName])) { if (!isset($this->tca['columns'][$columnName])) {
throw new InvalidArgumentException( throw new InvalidArgumentException(
@ -277,7 +278,7 @@ class TcaTableService
* @param array &$record * @param array &$record
* @return bool * @return bool
*/ */
protected function isRecordBlacklistedByRootline(array &$record) : bool protected function isRecordBlacklistedByRootline(array &$record)
{ {
$pageUid = $record['pid']; $pageUid = $record['pid'];
if ($this->tableName === 'pages') { if ($this->tableName === 'pages') {
@ -334,7 +335,7 @@ class TcaTableService
* *
* @return bool * @return bool
*/ */
protected function isBlackListedRootLineConfigured() : bool protected function isBlackListedRootLineConfigured()
{ {
return (bool) $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'); return (bool) $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist');
} }
@ -344,7 +345,7 @@ class TcaTableService
* *
* @return array<Int> * @return array<Int>
*/ */
protected function getBlackListedRootLine() : array protected function getBlackListedRootLine()
{ {
return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist')); return GeneralUtility::intExplode(',', $this->configuration->getIfExists('indexing.' . $this->getTableName() . '.rootLineBlacklist'));
} }

View file

@ -22,9 +22,9 @@ namespace Codappix\SearchCore\Domain\Search;
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Configuration\InvalidArgumentException; use Codappix\SearchCore\Configuration\InvalidArgumentException;
use Codappix\SearchCore\Connection\ConnectionInterface;
use Codappix\SearchCore\Connection\Elasticsearch\Query; use Codappix\SearchCore\Connection\Elasticsearch\Query;
use Codappix\SearchCore\Connection\SearchRequestInterface; use Codappix\SearchCore\Connection\SearchRequestInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\ArrayUtility; use TYPO3\CMS\Extbase\Utility\ArrayUtility;
class QueryFactory class QueryFactory
@ -109,20 +109,18 @@ class QueryFactory
return; return;
} }
$query = ArrayUtility::setValueByPath( $matchExpression = [
$query, 'type' => 'most_fields',
'query.bool.must.0.match._all.query', 'query' => $searchRequest->getSearchTerm(),
$searchRequest->getSearchTerm() 'fields' => GeneralUtility::trimExplode(',', $this->configuration->get('searching.fields')),
); ];
$minimumShouldMatch = $this->configuration->getIfExists('searching.minimumShouldMatch'); $minimumShouldMatch = $this->configuration->getIfExists('searching.minimumShouldMatch');
if ($minimumShouldMatch) { if ($minimumShouldMatch) {
$query = ArrayUtility::setValueByPath( $matchExpression['minimum_should_match'] = $minimumShouldMatch;
$query,
'query.bool.must.0.match._all.minimum_should_match',
$minimumShouldMatch
);
} }
$query = ArrayUtility::setValueByPath($query, 'query.bool.must.0.multi_match', $matchExpression);
} }
/** /**
@ -150,6 +148,7 @@ class QueryFactory
]; ];
} }
if (!empty($boostQueryParts)) {
$query = ArrayUtility::arrayMergeRecursiveOverrule($query, [ $query = ArrayUtility::arrayMergeRecursiveOverrule($query, [
'query' => [ 'query' => [
'bool' => [ 'bool' => [
@ -158,6 +157,7 @@ class QueryFactory
], ],
]); ]);
} }
}
/** /**
* @param array $query * @param array $query

View file

@ -23,7 +23,6 @@ namespace Codappix\SearchCore\Domain\Service;
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface; use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Domain\Index\IndexerFactory; use Codappix\SearchCore\Domain\Index\IndexerFactory;
use Codappix\SearchCore\Domain\Index\NoMatchingIndexerException; use Codappix\SearchCore\Domain\Index\NoMatchingIndexerException;
use Codappix\SearchCore\Domain\Index\TcaIndexer;
use TYPO3\CMS\Core\SingletonInterface as Singleton; use TYPO3\CMS\Core\SingletonInterface as Singleton;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;

View file

@ -21,7 +21,6 @@ namespace Codappix\SearchCore\Hook;
*/ */
use Codappix\SearchCore\Configuration\NoConfigurationException; use Codappix\SearchCore\Configuration\NoConfigurationException;
use Codappix\SearchCore\Domain\Index\NoMatchingIndexerException;
use Codappix\SearchCore\Domain\Service\DataHandler as OwnDataHandler; use Codappix\SearchCore\Domain\Service\DataHandler as OwnDataHandler;
use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\DataHandling\DataHandler as CoreDataHandler; use TYPO3\CMS\Core\DataHandling\DataHandler as CoreDataHandler;

View file

@ -28,4 +28,3 @@ We are also focusing on Code Quality and Testing through `travis ci`_, `scrutini
.. _travis ci: https://travis-ci.org/Codappix/search_core .. _travis ci: https://travis-ci.org/Codappix/search_core
.. _scrutinizer: https://scrutinizer-ci.com/g/Codappix/search_core/inspections .. _scrutinizer: https://scrutinizer-ci.com/g/Codappix/search_core/inspections
.. _codacy: https://www.codacy.com/app/Codappix/search_core/dashboard .. _codacy: https://www.codacy.com/app/Codappix/search_core/dashboard

View file

@ -4,15 +4,15 @@ current_dir := $(dir $(mkfile_path))
TYPO3_WEB_DIR := $(current_dir).Build/web TYPO3_WEB_DIR := $(current_dir).Build/web
TYPO3_PATH_ROOT := $(current_dir).Build/web TYPO3_PATH_ROOT := $(current_dir).Build/web
# Allow different versions on travis # Allow different versions on travis
TYPO3_VERSION ?= ~8.7 TYPO3_VERSION ?= ~7.6
typo3DatabaseName ?= "searchcore_test2" typo3DatabaseName ?= "searchcore_test"
typo3DatabaseUsername ?= "dev" typo3DatabaseUsername ?= "dev"
typo3DatabasePassword ?= "dev" typo3DatabasePassword ?= "dev"
typo3DatabaseHost ?= "127.0.0.1" typo3DatabaseHost ?= "127.0.0.1"
.PHONY: install .PHONY: install
install: clean install: clean
COMPOSER_PROCESS_TIMEOUT=1000 composer require -vv --dev --prefer-dist --ignore-platform-reqs typo3/cms="$(TYPO3_VERSION)" COMPOSER_PROCESS_TIMEOUT=1000 composer require -vv --dev --prefer-source typo3/cms="$(TYPO3_VERSION)"
git checkout composer.json git checkout composer.json
functionalTests: functionalTests:

View file

@ -48,14 +48,14 @@ class FilterTest extends AbstractFunctionalTestCase
$searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class) $searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
->get(SearchService::class); ->get(SearchService::class);
$searchRequest = new SearchRequest('Search Word'); $searchRequest = new SearchRequest();
$result = $searchService->search($searchRequest); $result = $searchService->search($searchRequest);
$this->assertSame(2, count($result), 'Did not receive both indexed elements without filter.'); $this->assertSame(2, count($result), 'Did not receive both indexed elements without filter.');
$searchRequest->setFilter(['CType' => 'HTML']); $searchRequest->setFilter(['CType' => 'HTML']);
$result = $searchService->search($searchRequest); $result = $searchService->search($searchRequest);
$this->assertSame(5, $result->getResults()[0]['uid'], 'Did not get the expected result entry.'); $this->assertSame(5, (int) $result->getResults()[0]['uid'], 'Did not get the expected result entry.');
$this->assertSame(1, count($result), 'Did not receive the single filtered element.'); $this->assertSame(1, count($result), 'Did not receive the single filtered element.');
} }
@ -73,7 +73,7 @@ class FilterTest extends AbstractFunctionalTestCase
$searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class) $searchService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(ObjectManager::class)
->get(SearchService::class); ->get(SearchService::class);
$searchRequest = new SearchRequest('Search Word'); $searchRequest = new SearchRequest();
$result = $searchService->search($searchRequest); $result = $searchService->search($searchRequest);
$this->assertSame(1, count($result->getFacets()), 'Did not receive the single defined facet.'); $this->assertSame(1, count($result->getFacets()), 'Did not receive the single defined facet.');

View file

@ -175,7 +175,7 @@ class IndexTcaTableTest extends AbstractFunctionalTestCase
['_source' => [ ['_source' => [
'uid' => '11', 'uid' => '11',
'CType' => 'Header', // Testing items 'CType' => 'Header', // Testing items
'categories' => ['Category 2', 'Category 1'], // Testing mm 'categories' => ['Category 2', 'Category 1'], // Testing mm (with sorting)
]], ]],
$response->getData()['hits']['hits'][0], $response->getData()['hits']['hits'][0],
false, false,

View file

@ -37,6 +37,7 @@ plugin {
} }
searching { searching {
fields = search_title
facets { facets {
contentTypes { contentTypes {
field = CType field = CType

View file

@ -29,7 +29,7 @@
<sorting>72</sorting> <sorting>72</sorting>
<CType>header</CType> <CType>header</CType>
<header>endtime hidden record</header> <header>endtime hidden record</header>
<bodytext></bodytext> <bodytext>Some content</bodytext>
<media>0</media> <media>0</media>
<layout>0</layout> <layout>0</layout>
<deleted>0</deleted> <deleted>0</deleted>
@ -49,7 +49,7 @@
<sorting>72</sorting> <sorting>72</sorting>
<CType>header</CType> <CType>header</CType>
<header>Hidden record</header> <header>Hidden record</header>
<bodytext></bodytext> <bodytext>Some content</bodytext>
<media>0</media> <media>0</media>
<layout>0</layout> <layout>0</layout>
<deleted>0</deleted> <deleted>0</deleted>

View file

@ -9,7 +9,7 @@
<sorting>72</sorting> <sorting>72</sorting>
<CType>header</CType> <CType>header</CType>
<header>Also indexable record</header> <header>Also indexable record</header>
<bodytext></bodytext> <bodytext>Some content</bodytext>
<media>0</media> <media>0</media>
<layout>0</layout> <layout>0</layout>
<deleted>0</deleted> <deleted>0</deleted>
@ -29,7 +29,7 @@
<sorting>72</sorting> <sorting>72</sorting>
<CType>div</CType> <CType>div</CType>
<header>Not indexed by user where ctype condition</header> <header>Not indexed by user where ctype condition</header>
<bodytext></bodytext> <bodytext>Some content</bodytext>
<media>0</media> <media>0</media>
<layout>0</layout> <layout>0</layout>
<deleted>0</deleted> <deleted>0</deleted>

View file

@ -1,7 +1,7 @@
<phpunit <phpunit
backupGlobals="true" backupGlobals="true"
backupStaticAttributes="false" backupStaticAttributes="false"
bootstrap="../../.Build/vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php" bootstrap="../../.Build/vendor/typo3/cms/typo3/sysext/core/Build/FunctionalTestsBootstrap.php"
colors="true" colors="true"
convertErrorsToExceptions="false" convertErrorsToExceptions="false"

View file

@ -72,9 +72,9 @@ class ProcessesAllowedTablesTest extends AbstractDataHandlerTest
->with( ->with(
$this->equalTo('tt_content'), $this->equalTo('tt_content'),
$this->callback(function ($record) { $this->callback(function ($record) {
return isset($record['uid']) && $record['uid'] === 1 return isset($record['uid']) && $record['uid'] == 1
&& isset($record['pid']) && $record['pid'] === 1 && isset($record['pid']) && $record['pid'] == 1
&& isset($record['colPos']) && $record['colPos'] === 1 && isset($record['colPos']) && $record['colPos'] == 1
; ;
}) })
); );
@ -100,9 +100,9 @@ class ProcessesAllowedTablesTest extends AbstractDataHandlerTest
->with( ->with(
$this->equalTo('tt_content'), $this->equalTo('tt_content'),
$this->callback(function ($record) { $this->callback(function ($record) {
return isset($record['uid']) && $record['uid'] === '2' return isset($record['uid']) && $record['uid'] == 2
&& isset($record['pid']) && $record['pid'] === 1 && isset($record['pid']) && $record['pid'] == 1
&& isset($record['header']) && $record['header'] === 'a new record' && isset($record['header']) && $record['header'] == 'a new record'
; ;
}) })
); );

View file

@ -38,6 +38,7 @@ class IndexFactoryTest extends AbstractUnitTestCase
$this->configuration = $this->getMockBuilder(ConfigurationContainerInterface::class)->getMock(); $this->configuration = $this->getMockBuilder(ConfigurationContainerInterface::class)->getMock();
$this->subject = new IndexFactory($this->configuration); $this->subject = new IndexFactory($this->configuration);
$this->subject->injectLogger($this->getMockedLogger());
} }
/** /**

View file

@ -60,18 +60,10 @@ class TcaTableServiceTest extends AbstractUnitTestCase
->method('getIfExists') ->method('getIfExists')
->withConsecutive(['indexing.table.additionalWhereClause'], ['indexing.table.rootLineBlacklist']) ->withConsecutive(['indexing.table.additionalWhereClause'], ['indexing.table.rootLineBlacklist'])
->will($this->onConsecutiveCalls(null, false)); ->will($this->onConsecutiveCalls(null, false));
$this->subject->expects($this->once())
->method('getSystemWhereClause')
->will($this->returnValue('1=1 AND pages.no_search = 0'));
$whereClause = $this->subject->getWhereClause();
$this->assertSame( $this->assertSame(
'1=1 AND pages.no_search = 0', '1=1 AND pages.no_search = 0',
$whereClause->getStatement() $this->subject->getWhereClause()
);
$this->assertSame(
[],
$whereClause->getParameters()
); );
} }
@ -84,18 +76,10 @@ class TcaTableServiceTest extends AbstractUnitTestCase
->method('getIfExists') ->method('getIfExists')
->withConsecutive(['indexing.table.additionalWhereClause'], ['indexing.table.rootLineBlacklist']) ->withConsecutive(['indexing.table.additionalWhereClause'], ['indexing.table.rootLineBlacklist'])
->will($this->onConsecutiveCalls('table.field = "someValue"', false)); ->will($this->onConsecutiveCalls('table.field = "someValue"', false));
$this->subject->expects($this->once())
->method('getSystemWhereClause')
->will($this->returnValue('1=1 AND pages.no_search = 0'));
$whereClause = $this->subject->getWhereClause();
$this->assertSame( $this->assertSame(
'1=1 AND pages.no_search = 0 AND table.field = "someValue"', '1=1 AND pages.no_search = 0 AND table.field = "someValue"',
$whereClause->getStatement() $this->subject->getWhereClause()
);
$this->assertSame(
[],
$whereClause->getParameters()
); );
} }
} }

View file

@ -50,13 +50,11 @@ class QueryFactoryTest extends AbstractUnitTestCase
/** /**
* @test * @test
*/ */
public function creatonOfQueryWorksInGeneral() public function creationOfQueryWorksInGeneral()
{ {
$searchRequest = new SearchRequest('SearchWord'); $this->mockConfiguration();
$this->configuration->expects($this->any()) $searchRequest = new SearchRequest('SearchWord');
->method('get')
->will($this->throwException(new InvalidArgumentException));
$query = $this->subject->create($searchRequest); $query = $this->subject->create($searchRequest);
$this->assertInstanceOf( $this->assertInstanceOf(
@ -71,9 +69,7 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function filterIsAddedToQuery() public function filterIsAddedToQuery()
{ {
$this->configuration->expects($this->any()) $this->mockConfiguration();
->method('get')
->will($this->throwException(new InvalidArgumentException));
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$searchRequest->setFilter(['field' => 'content']); $searchRequest->setFilter(['field' => 'content']);
@ -83,7 +79,7 @@ class QueryFactoryTest extends AbstractUnitTestCase
[ [
['term' => ['field' => 'content']] ['term' => ['field' => 'content']]
], ],
$query->toArray()['query']['bool']['filter'], $query->toArray()['query']['function_score']['query']['bool']['filter'],
'Filter was not added to query.' 'Filter was not added to query.'
); );
} }
@ -93,9 +89,7 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function emptyFilterIsNotAddedToQuery() public function emptyFilterIsNotAddedToQuery()
{ {
$this->configuration->expects($this->any()) $this->mockConfiguration();
->method('get')
->will($this->throwException(new InvalidArgumentException));
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$searchRequest->setFilter([ $searchRequest->setFilter([
@ -112,7 +106,7 @@ class QueryFactoryTest extends AbstractUnitTestCase
$query = $this->subject->create($searchRequest); $query = $this->subject->create($searchRequest);
$this->assertSame( $this->assertSame(
null, null,
$query->toArray()['query']['bool']['filter'], $query->toArray()['query']['function_score']['query']['bool']['filter'],
'Filter was added to query, even if no filter exists.' 'Filter was added to query, even if no filter exists.'
); );
} }
@ -122,9 +116,8 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function facetsAreAddedToQuery() public function facetsAreAddedToQuery()
{ {
$this->configuration->expects($this->any()) $this->mockConfiguration();
->method('get')
->will($this->throwException(new InvalidArgumentException));
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$searchRequest->addFacet(new FacetRequest('Identifier', 'FieldName')); $searchRequest->addFacet(new FacetRequest('Identifier', 'FieldName'));
$searchRequest->addFacet(new FacetRequest('Identifier 2', 'FieldName 2')); $searchRequest->addFacet(new FacetRequest('Identifier 2', 'FieldName 2'));
@ -153,9 +146,8 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function sizeIsAddedToQuery() public function sizeIsAddedToQuery()
{ {
$this->configuration->expects($this->any()) $this->mockConfiguration();
->method('get')
->will($this->throwException(new InvalidArgumentException));
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$searchRequest->setLimit(45); $searchRequest->setLimit(45);
$searchRequest->setOffset(35); $searchRequest->setOffset(35);
@ -178,10 +170,9 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function searchTermIsAddedToQuery() public function searchTermIsAddedToQuery()
{ {
$this->mockConfiguration();
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$this->configuration->expects($this->any())
->method('get')
->will($this->throwException(new InvalidArgumentException));
$query = $this->subject->create($searchRequest); $query = $this->subject->create($searchRequest);
$this->assertSame( $this->assertSame(
@ -189,16 +180,18 @@ class QueryFactoryTest extends AbstractUnitTestCase
'bool' => [ 'bool' => [
'must' => [ 'must' => [
[ [
'match' => [ 'multi_match' => [
'_all' => [ 'type' => 'most_fields',
'query' => 'SearchWord', 'query' => 'SearchWord',
'fields' => [
0 => 'test_field',
], ],
], ],
], ],
], ],
], ],
], ],
$query->toArray()['query'], $query->toArray()['query']['function_score']['query'],
'Search term was not added to query as expected.' 'Search term was not added to query as expected.'
); );
} }
@ -208,14 +201,13 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function minimumShouldMatchIsAddedToQuery() public function minimumShouldMatchIsAddedToQuery()
{ {
$searchRequest = new SearchRequest('SearchWord');
$this->configuration->expects($this->once()) $this->configuration->expects($this->once())
->method('getIfExists') ->method('getIfExists')
->with('searching.minimumShouldMatch') ->with('searching.minimumShouldMatch')
->willReturn('50%'); ->willReturn('50%');
$this->configuration->expects($this->any()) $this->mockConfiguration();
->method('get')
->will($this->throwException(new InvalidArgumentException)); $searchRequest = new SearchRequest('SearchWord');
$query = $this->subject->create($searchRequest); $query = $this->subject->create($searchRequest);
$this->assertArraySubset( $this->assertArraySubset(
@ -223,16 +215,14 @@ class QueryFactoryTest extends AbstractUnitTestCase
'bool' => [ 'bool' => [
'must' => [ 'must' => [
[ [
'match' => [ 'multi_match' => [
'_all' => [
'minimum_should_match' => '50%', 'minimum_should_match' => '50%',
], ],
], ],
], ],
], ],
], ],
], $query->toArray()['query']['function_score']['query'],
$query->toArray()['query'],
'minimum_should_match was not added to query as configured.' 'minimum_should_match was not added to query as configured.'
); );
} }
@ -244,10 +234,11 @@ class QueryFactoryTest extends AbstractUnitTestCase
{ {
$searchRequest = new SearchRequest('SearchWord'); $searchRequest = new SearchRequest('SearchWord');
$this->configuration->expects($this->exactly(2)) $this->configuration->expects($this->exactly(3))
->method('get') ->method('get')
->withConsecutive(['searching.boost'], ['searching.fieldValueFactor']) ->withConsecutive(['searching.fields'], ['searching.boost'], ['searching.fieldValueFactor'])
->will($this->onConsecutiveCalls( ->will($this->onConsecutiveCalls(
'test_field',
[ [
'search_title' => 3, 'search_title' => 3,
'search_abstract' => 1.5, 'search_abstract' => 1.5,
@ -292,10 +283,11 @@ class QueryFactoryTest extends AbstractUnitTestCase
'factor' => '2', 'factor' => '2',
'missing' => '1', 'missing' => '1',
]; ];
$this->configuration->expects($this->exactly(2)) $this->configuration->expects($this->exactly(3))
->method('get') ->method('get')
->withConsecutive(['searching.boost'], ['searching.fieldValueFactor']) ->withConsecutive(['searching.fields'], ['searching.boost'], ['searching.fieldValueFactor'])
->will($this->onConsecutiveCalls( ->will($this->onConsecutiveCalls(
'test_field',
$this->throwException(new InvalidArgumentException), $this->throwException(new InvalidArgumentException),
$fieldConfig $fieldConfig
)); ));
@ -308,9 +300,11 @@ class QueryFactoryTest extends AbstractUnitTestCase
'bool' => [ 'bool' => [
'must' => [ 'must' => [
[ [
'match' => [ 'multi_match' => [
'_all' => [ 'type' => 'most_fields',
'query' => 'SearchWord', 'query' => 'SearchWord',
'fields' => [
0 => 'test_field',
], ],
], ],
], ],
@ -330,17 +324,27 @@ class QueryFactoryTest extends AbstractUnitTestCase
*/ */
public function emptySearchStringWillNotAddSearchToQuery() public function emptySearchStringWillNotAddSearchToQuery()
{ {
$this->mockConfiguration();
$searchRequest = new SearchRequest(); $searchRequest = new SearchRequest();
$this->configuration->expects($this->any())
->method('get')
->will($this->throwException(new InvalidArgumentException));
$query = $this->subject->create($searchRequest); $query = $this->subject->create($searchRequest);
$this->assertInstanceOf( $this->assertNull(
stdClass, $query->toArray()['query']['function_score']['query'],
$query->toArray()['query']['match_all'],
'Empty search request does not create expected query.' 'Empty search request does not create expected query.'
); );
} }
protected function mockConfiguration()
{
$this->configuration->expects($this->any())
->method('get')
->will($this->returnCallback(function ($option) {
if ($option === 'searching.fields') {
return 'test_field';
}
return $this->throwException(new InvalidArgumentException);
}));
}
} }

View file

@ -1,7 +1,7 @@
<phpunit <phpunit
backupGlobals="false" backupGlobals="false"
backupStaticAttributes="false" backupStaticAttributes="false"
bootstrap="../../.Build/vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php" bootstrap="../../.Build/vendor/typo3/cms/typo3/sysext/core/Build/UnitTestsBootstrap.php"
colors="true" colors="true"
convertErrorsToExceptions="false" convertErrorsToExceptions="false"

View file

@ -16,13 +16,12 @@
} }
}, },
"require" : { "require" : {
"php": ">=7.1.0", "php": ">=5.6.0",
"typo3/cms": "~8.7", "typo3/cms": "~7.6",
"ruflin/elastica": "~3.2" "ruflin/elastica": "~3.2"
}, },
"require-dev": { "require-dev": {
"typo3/testing-framework": "~1.1.0", "phpunit/phpunit": "~5.7.0"
"phpunit/phpunit": "~6.2.0"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,

View file

@ -7,8 +7,8 @@ $EM_CONF[$_EXTKEY] = [
'clearCacheOnLoad' => 1, 'clearCacheOnLoad' => 1,
'constraints' => [ 'constraints' => [
'depends' => [ 'depends' => [
'typo3' => '8.7.0-8.7.99', 'typo3' => '7.6.0-7.6.99',
'php' => '7.1.0-7.99.99' 'php' => '5.6.0-7.99.99'
], ],
'conflicts' => [], 'conflicts' => [],
], ],

View file

@ -11,7 +11,7 @@ call_user_func(
'SC_OPTIONS' => [ 'SC_OPTIONS' => [
'extbase' => [ 'extbase' => [
'commandControllers' => [ 'commandControllers' => [
Codappix\SearchCore\Command\IndexCommandController::class, $extensionKey . '::index' => Codappix\SearchCore\Command\IndexCommandController::class,
], ],
], ],
't3lib/class.t3lib_tcemain.php' => [ 't3lib/class.t3lib_tcemain.php' => [