Skip to content

Commit

Permalink
Fix search with categories on translated pages
Browse files Browse the repository at this point in the history
Results from repositories have the uid of the default language, even
when the rest is translated. However, the foreign uid filter for the
index table needs the translated uid.
Additionally, the category filters are now translated and provided as
objects.
  • Loading branch information
okmiim committed Dec 17, 2023
1 parent c727dea commit 31dc70a
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 71 deletions.
32 changes: 32 additions & 0 deletions Classes/Domain/Repository/CategoryRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace HDNET\Calendarize\Domain\Repository;

use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;

class CategoryRepository extends AbstractRepository
{
public function __construct()
{
parent::__construct();
$this->objectType = Category::class;
}

public function findByIds(array $categoryIds, array $orderings = []): array|QueryResultInterface
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setRespectSysLanguage(false);

$query->matching($query->in('uid', $categoryIds));

if (!empty($orderings)) {
$query->setOrderings($orderings);
}

return $query->execute();
}
}
9 changes: 3 additions & 6 deletions Classes/Domain/Repository/EventRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public function injectIndexRepository(IndexRepository $indexRepository): void
public function findBySearch(Search $search): array
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);

$constraints = [];
if ($search->getFullText()) {
$constraints['fullText'] = $query->logicalOr(
Expand All @@ -44,12 +46,7 @@ public function findBySearch(Search $search): array
$query->matching($query->logicalAnd(...$constraints));
$rows = $query->execute(true);

$ids = [];
foreach ($rows as $row) {
$ids[] = (int)$row['uid'];
}

return $ids;
return array_map(static fn ($row) => (int)($row['_LOCALIZED_UID'] ?? $row['uid']), $rows);
}

public function findOneByImportId(string $importId, int $pid = null): ?object
Expand Down
75 changes: 21 additions & 54 deletions Classes/EventListener/CategoryFilterEventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@

namespace HDNET\Calendarize\EventListener;

use Doctrine\DBAL\ArrayParameterType;
use HDNET\Calendarize\Controller\CalendarController;
use HDNET\Calendarize\Domain\Repository\CategoryRepository;
use HDNET\Calendarize\Event\GenericActionAssignmentEvent;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;

/**
* Gets all used categories from the default Event and assigns it to extended.categories in fluid.
Expand All @@ -23,12 +20,18 @@ class CategoryFilterEventListener

protected string $itemFieldName = 'categories';

public function __construct(
private readonly ConnectionPool $connectionPool,
private readonly CategoryRepository $categoryRepository,
) {
}

public function __invoke(GenericActionAssignmentEvent $event): void
{
if (CalendarController::class !== $event->getClassName() || 'searchAction' !== $event->getFunctionName()) {
return;
}
if (!$this->checkConfiguration($event->getVariables()['configurations'] ?? [], $this->itemTableName)) {
if (!\in_array($this->itemTableName, array_column($event->getVariables()['configurations'] ?? [], 'tableName'), true)) {
return;
}
$variables = $event->getVariables();
Expand All @@ -40,51 +43,15 @@ public function __invoke(GenericActionAssignmentEvent $event): void
$event->setVariables($variables);
}

/**
* Check if the event configuration is active.
*/
protected function checkConfiguration(array $configurations, string $tableName): bool
{
foreach ($configurations as $config) {
if (($config['tableName'] ?? '') === $tableName) {
return true;
}
}

return false;
}

/**
* Gets all used categories of the default Event (self::itemTableName).
*/
protected function getCategories(string $tableName, string $fieldName): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');

$queryBuilder
->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));

/** @var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
/** @var LanguageAspect $languageAspect */
$languageAspect = $context->getAspect('language');
$languageUid = $languageAspect->getId();

$queryBuilder->select('sys_category.*')
->groupBy('sys_category.uid')
->from('sys_category')
->join(
'sys_category',
'sys_category_record_mm',
'sys_category_record_mm',
$queryBuilder->expr()->eq(
'sys_category_record_mm.uid_local',
$queryBuilder->quoteIdentifier('sys_category.uid')
)
)
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_category');
$queryBuilder->distinct()
->select('uid_local')
->from('sys_category_record_mm')
->where(
$queryBuilder->expr()->and(
$queryBuilder->expr()->eq(
Expand All @@ -94,17 +61,17 @@ protected function getCategories(string $tableName, string $fieldName): array
$queryBuilder->expr()->eq(
'sys_category_record_mm.fieldname',
$queryBuilder->createNamedParameter($fieldName, \PDO::PARAM_STR)
),
$queryBuilder->expr()->in(
'sys_category.sys_language_uid',
$queryBuilder->createNamedParameter([-1, $languageUid], ArrayParameterType::INTEGER)
)
)
)
->orderBy('sys_category.title', 'ASC');
);

return $queryBuilder
$categoryIds = $queryBuilder
->executeQuery()
->fetchAllAssociative();
->fetchFirstColumn();

return $this->categoryRepository->findByIds(
$categoryIds,
['title' => QueryInterface::ORDER_ASCENDING]
)->toArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
use HDNET\Calendarize\Domain\Repository\EventRepository;
use HDNET\Calendarize\Event\IndexRepositoryFindBySearchEvent;
use HDNET\Calendarize\Register;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings;

class DefaultEventSearchListener
/**
* Filters Event records by the search query ('categories' and 'fullText').
*/
class SearchConstraintEventListener
{
public function __construct(protected EventRepository $eventRepository)
{
Expand All @@ -23,23 +24,21 @@ public function __invoke(IndexRepositoryFindBySearchEvent $event): void
if (!\in_array(Register::UNIQUE_REGISTER_KEY, $event->getIndexTypes(), true)) {
return;
}
$foreignIds = $event->getForeignIds();
if (!empty($foreignIds['tx_calendarize_domain_model_event'])) {
// Skip if there are already ids (e.g. by other extensions)
return;
}

$search = $this->getSearchDto($event);

if (!$search->isSearch()) {
return;
}

/** @var Typo3QuerySettings $querySettings */
$querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
$querySettings->setRespectStoragePage(false);
$this->eventRepository->setDefaultQuerySettings($querySettings);
$searchTermIds = $this->eventRepository->findBySearch($search);

// Blocks result (displaying no event) on no search match (empty id array)
$searchTermIds[] = -1;

$foreignIds = $event->getForeignIds();
$foreignIds['tx_calendarize_domain_model_event'] = $searchTermIds;
$event->setForeignIds($foreignIds);
}
Expand All @@ -56,6 +55,10 @@ protected function getSearchDto(IndexRepositoryFindBySearchEvent $event): Search
$search->setCategories($categories);
} elseif (MathUtility::canBeInterpretedAsInteger($customSearch['category'] ?? '')) {
// Fallback for previous mode
@trigger_error(
'Search request with the parameter \'category\' is deprecated. Use \'categories\' instead.',
\E_USER_DEPRECATED
);
$search->setCategories([(int)$customSearch['category']]);
}

Expand Down
2 changes: 1 addition & 1 deletion Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ services:
identifier: 'categoryFilter'
event: HDNET\Calendarize\Event\GenericActionAssignmentEvent

HDNET\Calendarize\EventListener\DefaultEventSearchListener:
HDNET\Calendarize\EventListener\SearchConstraintEventListener:
tags:
- name: event.listener
identifier: 'defaultEventSearch'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"pages"
,"uid","pid",doktype,"sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title"
,1,0,1,256,0,0,0,0,0,0,"Parent page"
,2,1,254,256,0,0,0,0,0,0,"Events"
,3,2,254,256,0,0,0,0,0,0,"Events with Categories"

"sys_category",
,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","title","parent","items","l10n_diffsource","description"
,28,0,256,0,0,0,0,0,0,0,0,"Category A",0,0,,
,29,0,512,0,0,0,0,0,0,0,0,"Category B",0,0,,
,30,0,768,0,0,0,0,0,0,0,0,"Category C",0,0,,
,31,0,1024,0,0,0,0,0,0,0,0,"Category A.A",28,0,,

"sys_category_record_mm",
,"uid_local","uid_foreign","tablenames","sorting","sorting_foreign","fieldname"
,28,20,"tx_calendarize_domain_model_event",0,1,"categories"
,28,21,"tx_calendarize_domain_model_event",0,2,"categories"
,29,30,"tx_calendarize_domain_model_event",0,3,"categories"

"tx_calendarize_domain_model_event"
,uid,pid,sys_language_uid,l10n_parent,title,calendarize,categories
,10,2,0,0,No Category,10,0
,11,2,1,10,[Translated to German] No Category,11,0
,20,3,0,0,Only Category A,20,1
,21,3,1,20,[Translated to German] Only Category A,21,1
,30,3,0,0,Only Category B,30,0

"tx_calendarize_domain_model_configuration"
,"uid","pid","type","handling","start_date","all_day","t3ver_oid","t3ver_wsid","t3ver_state"
,10,2,"time","include","2025-03-01",1,0,0,0
,11,2,"time","include","2025-03-01",1,0,0,0
,20,3,"time","include","2025-03-02",1,0,0,0
,21,3,"time","include","2025-03-02",1,0,0,0
,30,3,"time","include","2025-03-03",1,0,0,0
73 changes: 73 additions & 0 deletions Tests/Functional/Domain/Repository/IndexRepositoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace HDNET\Calendarize\Tests\Functional\Domain\Repository;

use HDNET\Calendarize\Domain\Repository\IndexRepository;
use HDNET\Calendarize\Register;
use HDNET\Calendarize\Service\IndexerService;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;

class IndexRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = ['typo3conf/ext/calendarize'];

protected IndexerService $indexerService;

protected function setUp(): void
{
parent::setUp();
$this->importCSVDataSet(__DIR__ . '/Fixtures/EventsWithCategories.csv');

$this->indexerService = GeneralUtility::makeInstance(IndexerService::class);
$this->indexerService->reindexAll();
}

/**
* @dataProvider findBySearchDataProvider
*/
public function testFindBySearch(
int $language,
?\DateTimeInterface $startDate,
?\DateTimeInterface $endDate,
array $customSearch,
int $limit,
array $expectedEventIds
): void {
$context = GeneralUtility::makeInstance(Context::class);
$context->setAspect('language', new LanguageAspect($language, $language, LanguageAspect::OVERLAYS_ON));

$subject = GeneralUtility::makeInstance(IndexRepository::class);
$subject->setIndexTypes([Register::UNIQUE_REGISTER_KEY]);
$subject->setOverridePageIds([2, 3]);

$result = $subject->findBySearch($startDate, $endDate, $customSearch, $limit)->toArray();
$eventIds = array_map(static fn ($i) => $i->getForeignUid(), $result);

self::assertEquals($expectedEventIds, $eventIds);
}

public static function findBySearchDataProvider(): array
{
return [
'no filter' => [0, null, null, [], 0, [10, 20, 30]],
'category' => [0, null, null, ['categories' => ['28']], 0, [20]],
'category translated' => [1, null, null, ['categories' => ['28']], 0, [21]],
'fullText' => [0, null, null, ['fullText' => 'Only'], 0, [20, 30]],
'fullText translated' => [1, null, null, ['fullText' => 'No'], 0, [11]],
'start and end date' => [0, new \DateTime('2025-02-28'), new \DateTime('2025-03-01'), [], 0, [10]],
'dates + categories + text' => [
0,
new \DateTime('2025-02-28'),
new \DateTime('2025-03-05'),
['categories' => ['28', '29'], 'fullText' => 'B'],
0,
[30],
],
];
}
}

0 comments on commit 31dc70a

Please sign in to comment.