Skip to content

Commit 4c1bcf9

Browse files
author
Christoph Lehmann
committed
Add Middleware, add highlighting, use Events
Page module * Records are now highlighted List module * Show only filtered records * On page 0 show filtered records of all pages
1 parent cd25d7d commit 4c1bcf9

16 files changed

+535
-220
lines changed

Classes/Controller/TreeController.php

Lines changed: 0 additions & 42 deletions
This file was deleted.

Classes/Domain/Dto/Filter.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Lemming\PageTreeFilter\Domain\Dto;
5+
6+
use TYPO3\CMS\Core\Utility\GeneralUtility;
7+
8+
class Filter
9+
{
10+
protected ?string $table = null;
11+
protected array $constraints = [];
12+
protected ?string $remainingSearchQuery = null;
13+
14+
public function __construct(
15+
protected string $rawQuery,
16+
protected bool $isHighlighting = false,
17+
protected ?int $currentPage = null
18+
) {
19+
$this->buildContraints($rawQuery);
20+
}
21+
22+
protected function buildContraints(string $searchFilter)
23+
{
24+
$remainingSearchFilterParts = [];
25+
foreach (GeneralUtility::trimExplode(' ', $searchFilter, true) as $queryPart) {
26+
$filter = GeneralUtility::trimExplode('=', $queryPart);
27+
if (count($filter) == 2) {
28+
switch ($filter[0]) {
29+
case 'table':
30+
$this->table = $filter[1];
31+
break;
32+
default:
33+
$this->constraints[] = [
34+
'field' => $filter[0],
35+
'value' => $filter[1]
36+
];
37+
}
38+
} else {
39+
$remainingSearchFilterParts[] = $queryPart;
40+
}
41+
}
42+
43+
$this->remainingSearchQuery = implode(' ', $remainingSearchFilterParts);
44+
}
45+
46+
public function getTable(): ?string
47+
{
48+
return $this->table;
49+
}
50+
51+
public function getConstraints(): array
52+
{
53+
return $this->constraints;
54+
}
55+
56+
public function getRemainingSearchQuery(): ?string
57+
{
58+
return $this->remainingSearchQuery;
59+
}
60+
61+
public function getRawQuery(): string
62+
{
63+
return $this->rawQuery;
64+
}
65+
66+
public function isHighlighting(): bool
67+
{
68+
return $this->isHighlighting;
69+
}
70+
71+
public function getCurrentPage(): ?int
72+
{
73+
return $this->currentPage;
74+
}
75+
}

Classes/Domain/Dto/Result.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Lemming\PageTreeFilter\Domain\Dto;
5+
6+
class Result
7+
{
8+
public function __construct(
9+
protected readonly array $recordUids,
10+
protected readonly Filter $filter,
11+
protected readonly bool $isValidFilter
12+
)
13+
{}
14+
15+
public function getRecordUids(): array
16+
{
17+
return $this->recordUids;
18+
}
19+
20+
public function getFilter(): Filter
21+
{
22+
return $this->filter;
23+
}
24+
25+
public function isValidFilter(): bool
26+
{
27+
return $this->isValidFilter;
28+
}
29+
}

Classes/Domain/Repository/PageTreeRepository.php

Lines changed: 17 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -3,189 +3,34 @@
33

44
namespace Lemming\PageTreeFilter\Domain\Repository;
55

6-
use Lemming\PageTreeFilter\Utility\ConfigurationUtility;
7-
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
8-
use TYPO3\CMS\Core\Database\ConnectionPool;
9-
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
10-
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
11-
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
12-
use TYPO3\CMS\Core\Utility\GeneralUtility;
6+
use Lemming\PageTreeFilter\Domain\Dto\Result;
7+
use Lemming\PageTreeFilter\Middleware\PageTreeFilterMiddleware;
8+
use Psr\Http\Message\ServerRequestInterface;
139

1410
class PageTreeRepository extends \TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository
1511
{
16-
public static array $filteredPageUids = [];
17-
18-
public static bool $filterErrorneous = false;
19-
20-
protected ?string $filterTable;
21-
22-
protected array $filterConstraints = [];
23-
24-
protected const ALLOWED_TABLE_FIELDS = [
25-
'tt_content:CType',
26-
'tt_content:list_type',
27-
];
28-
// allowed fields, regardless of table
29-
protected const ALLOWED_FIELDS = [
30-
'uid',
31-
];
32-
33-
public function fetchFilteredTree(string $searchFilter, array $allowedMountPointPageIds, string $additionalWhereClause): array
34-
{
35-
if (ConfigurationUtility::isWizardEnabled()) {
36-
$newSearchFilter = $this->extractConstraints($searchFilter);
37-
if (!empty($this->filterTable)) {
38-
$this->validate();
39-
40-
if (!self::$filterErrorneous) {
41-
self::$filteredPageUids = $this->getFilteredPageUids();
42-
43-
if (self::$filteredPageUids !== []) {
44-
$additionalWhereClause = sprintf('%s AND uid IN (%s)', $additionalWhereClause,
45-
implode(',', self::$filteredPageUids));
46-
$searchFilter = $newSearchFilter;
47-
}
48-
}
49-
}
12+
public function fetchFilteredTree(
13+
string $searchFilter,
14+
array $allowedMountPointPageIds,
15+
string $additionalWhereClause
16+
): array {
17+
$result = $this->getResult();
18+
if ($result) {
19+
$searchFilter = $result->getFilter()->getRemainingSearchQuery();
20+
$whereClause = $result->getRecordUids() === [] ? ' AND 1=0' : sprintf(' AND uid IN (%s)', implode(',', $result->getRecordUids()));
21+
$additionalWhereClause .= $whereClause;
5022
}
5123

5224
return parent::fetchFilteredTree($searchFilter, $allowedMountPointPageIds, $additionalWhereClause);
5325
}
5426

55-
protected function getFilteredPageUids(): array
56-
{
57-
$pageUids = [];
58-
59-
/** @var QueryBuilder $queryBuilder */
60-
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->filterTable);
61-
$queryBuilder
62-
->getRestrictions()
63-
->removeAll()
64-
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
65-
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class));
66-
67-
$field = $this->filterTable === 'pages' ? 'uid' : 'pid';
68-
$query = $queryBuilder
69-
->select($field)
70-
->from($this->filterTable)
71-
->groupBy($field);
72-
if ($this->filterTable === 'pages') {
73-
$query->addSelect('l10n_parent');
74-
}
75-
76-
foreach($this->filterConstraints as $constraint) {
77-
if (strpos($constraint['value'], '*') !== false) {
78-
$like = str_replace('*', '', $constraint['value']);
79-
$parts = explode('*', $constraint['value']);
80-
$leftLike = empty($parts[0]) ? '%' : '';
81-
$rightLike = empty(array_pop($parts)) ? '%' : '';
82-
$query->andWhere(
83-
$queryBuilder->expr()->like(
84-
$constraint['field'],
85-
$queryBuilder->createNamedParameter($leftLike . $queryBuilder->escapeLikeWildcards($like) . $rightLike)
86-
)
87-
);
88-
} else {
89-
if (empty($constraint['value']) && $this->isNullableColumn($queryBuilder, $constraint['field'])) {
90-
$query->andWhere(
91-
$queryBuilder->expr()->or(
92-
$queryBuilder->expr()->isNull($constraint['field']),
93-
$queryBuilder->expr()->eq($constraint['field'], $queryBuilder->createNamedParameter(''))
94-
)
95-
);
96-
} else {
97-
$query->andWhere(
98-
$queryBuilder->expr()->eq($constraint['field'], $queryBuilder->createNamedParameter($constraint['value']))
99-
);
100-
}
101-
}
102-
}
103-
104-
$rows = $query->executeQuery()->fetchAllAssociative();
105-
foreach ($rows as $row) {
106-
if ($this->filterTable === 'pages' && $row['l10n_parent'] > 0) {
107-
$pageUids[] = $row['l10n_parent'];
108-
} else {
109-
$pageUids[] = $row[$field];
110-
}
111-
}
112-
foreach ($rows as $row) {
113-
$pageUids[] = $row[$field];
114-
}
115-
116-
return $pageUids;
117-
}
118-
119-
protected function extractConstraints(string $searchFilter): string
120-
{
121-
$remainingSearchFilterParts = [];
122-
foreach(GeneralUtility::trimExplode(' ', $searchFilter, true) as $queryPart) {
123-
$filter = GeneralUtility::trimExplode('=', $queryPart);
124-
if (count($filter) == 2) {
125-
switch ($filter[0]) {
126-
case 'table':
127-
$this->filterTable = $filter[1];
128-
break;
129-
default:
130-
$this->filterConstraints[] = [
131-
'field' => $filter[0],
132-
'value' => $filter[1]
133-
];
134-
}
135-
} else {
136-
$remainingSearchFilterParts[] = $queryPart;
137-
}
138-
}
139-
140-
return implode(' ', $remainingSearchFilterParts);
141-
}
142-
143-
protected function validate()
144-
{
145-
$backendUser = $this->getBackendUser();
146-
if (!isset($GLOBALS['TCA'][$this->filterTable])) {
147-
self::$filterErrorneous = true;
148-
}
149-
if (!$backendUser->isAdmin() && !$backendUser->check('tables_select', $this->filterTable)) {
150-
self::$filterErrorneous = true;
151-
}
152-
/** @var \TYPO3\CMS\Core\Database\Connection $connection */
153-
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
154-
->getConnectionForTable($this->filterTable);
155-
foreach($this->filterConstraints as $constraint) {
156-
if (!isset($GLOBALS['TCA'][$this->filterTable]['columns'][$constraint['field']])) {
157-
// only if admin or field in ALLOWED_FIELDS: field can also be used if not in TCA, but exists in table
158-
if (($backendUser->isAdmin() || in_array($constraint['field'], self::ALLOWED_FIELDS))
159-
&& in_array($constraint['field'], array_keys($connection->createSchemaManager()->listTableColumns($this->filterTable)))
160-
) {
161-
// all good for this constraint, keep going
162-
continue;
163-
} else {
164-
self::$filterErrorneous = true;
165-
// filter error - no need to check further
166-
return;
167-
}
168-
}
169-
$tableField = $this->filterTable . ':' . $constraint['field'];
170-
if (
171-
!$backendUser->isAdmin() &&
172-
!in_array($tableField, self::ALLOWED_TABLE_FIELDS) &&
173-
!$backendUser->check('non_exclude_fields', $tableField)
174-
) {
175-
self::$filterErrorneous = true;
176-
return;
177-
}
178-
}
179-
}
180-
181-
protected function isNullableColumn(QueryBuilder $queryBuilder, string $column): bool
27+
protected function getResult(): ?Result
18228
{
183-
$schemaManager = $queryBuilder->getConnection()->createSchemaManager();
184-
return !$schemaManager->introspectTable($this->filterTable)->getColumn($column)->getNotnull();
29+
return $this->getServerRequest()->getAttribute(PageTreeFilterMiddleware::ATTRIBUTE);
18530
}
18631

187-
protected function getBackendUser(): BackendUserAuthentication
32+
protected function getServerRequest(): ServerRequestInterface
18833
{
189-
return $GLOBALS['BE_USER'];
34+
return $GLOBALS['TYPO3_REQUEST'];
19035
}
19136
}

0 commit comments

Comments
 (0)