Skip to content

Commit 99f8a72

Browse files
committed
✨ inital commit
0 parents  commit 99f8a72

File tree

11 files changed

+10340
-0
lines changed

11 files changed

+10340
-0
lines changed

.github/workflows/tasks.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Tasks
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
lint-php:
7+
name: "php: ${{ matrix.php }} TYPO3: ${{ matrix.typo3 }}"
8+
runs-on: ubuntu-latest
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
php: [ '8.2', '8.3' ]
13+
typo3: [ '11', '12' ]
14+
steps:
15+
- name: Setup PHP with PECL extension
16+
uses: shivammathur/setup-php@v2
17+
with:
18+
php-version: ${{ matrix.php }}
19+
- uses: actions/checkout@v2
20+
- uses: actions/cache@v2
21+
with:
22+
path: ~/.composer/cache/files
23+
key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
24+
restore-keys: |
25+
${{ runner.os }}-${{ matrix.php }}-composer-
26+
- run: rm composer.lock
27+
- run: composer require typo3/minimal="^${{ matrix.typo3 }}" --dev --ignore-platform-req=php+
28+
- run: composer install --no-interaction --no-progress --ignore-platform-req=php+
29+
- run: ./vendor/bin/grumphp run --ansi

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public/
2+
vendor/
3+
var/
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AUS\RelationProcessor\DataProcessing;
6+
7+
use RuntimeException;
8+
use TYPO3\CMS\Core\Database\Connection;
9+
use TYPO3\CMS\Core\Database\ConnectionPool;
10+
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
11+
use TYPO3\CMS\Core\Utility\GeneralUtility;
12+
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
13+
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
14+
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
15+
16+
final readonly class RelationProcessor implements DataProcessorInterface
17+
{
18+
public function __construct(
19+
private ConnectionPool $connectionPool,
20+
private ContentDataProcessor $contentDataProcessor
21+
) {
22+
}
23+
24+
/**
25+
* Process content object data
26+
*
27+
* @param ContentObjectRenderer $cObj The data of the content element or page
28+
* @param array<string, mixed> $contentObjectConfiguration The configuration of Content Object
29+
* @param array<string, mixed> $processorConfiguration The configuration of this processor
30+
* @param array<string, mixed> $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
31+
* @return array<string, mixed> the processed data as key/value store
32+
*/
33+
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
34+
{
35+
$table = $cObj->getCurrentTable();
36+
$uid = $cObj->data['uid'];
37+
$field = $processorConfiguration['field'];
38+
39+
$relations = $this->getRelation($cObj, $table, $field, $uid);
40+
$request = $cObj->getRequest();
41+
$processedRecordVariables = [];
42+
43+
foreach ($relations as $key => $record) {
44+
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
45+
$recordContentObjectRenderer->start($record, $GLOBALS['TCA'][$table]['columns'][$field]['config']['foreign_table'], $request);
46+
$processedRecordVariables[$key] = ['data' => $record];
47+
$processedRecordVariables[$key] = $this->contentDataProcessor->process(
48+
$recordContentObjectRenderer,
49+
$processorConfiguration,
50+
$processedRecordVariables[$key]
51+
);
52+
}
53+
54+
$processedData['data'][$field] = $processedRecordVariables;
55+
return $processedData;
56+
}
57+
58+
/**
59+
* @return list<array<string, string|int|float|bool>>
60+
*/
61+
public function getRelation(ContentObjectRenderer $cObj, string $table, string $field, int $uid): array
62+
{
63+
$tcaConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config'] ?? throw new RuntimeException(
64+
'TCA config for ' . $table . '.' . $field . ' not found'
65+
);
66+
if (isset($tcaConfig['MM_hasUidField'])) {
67+
throw new RuntimeException('TCA config MM_hasUidField not supported');
68+
}
69+
70+
if (isset($tcaConfig['MM_is_foreign'])) {
71+
throw new RuntimeException('TCA config MM_is_foreign not supported');
72+
}
73+
74+
if (isset($tcaConfig['MM_oppositeUsage'])) {
75+
throw new RuntimeException('TCA config MM_oppositeUsage not supported');
76+
}
77+
78+
if (isset($tcaConfig['foreign_field'])) {
79+
//inline not supported right now
80+
throw new RuntimeException('TCA config foreign_field not supported');
81+
}
82+
83+
$mmTable = $tcaConfig['MM'] ?? throw new RuntimeException('TCA config MM not found');
84+
$foreignTable = $tcaConfig['foreign_table'] ?? throw new RuntimeException('TCA config foreign_table not found');
85+
86+
$matchFields = $tcaConfig['MM_match_fields'] ?? [];
87+
88+
$otherWay = isset($tcaConfig['MM_opposite_field']);
89+
90+
if ($otherWay) {
91+
$selfField = 'uid_foreign';
92+
$otherField = 'uid_local';
93+
} else {
94+
$selfField = 'uid_local';
95+
$otherField = 'uid_foreign';
96+
}
97+
98+
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($foreignTable);
99+
$queryBuilder
100+
->select('relation.*')
101+
->from($foreignTable, 'relation')
102+
->join('relation', $mmTable, 'mm', $queryBuilder->expr()->eq('relation.uid', 'mm.' . $otherField))
103+
->where($queryBuilder->expr()->eq('mm.' . $selfField, $uid));
104+
105+
106+
$transOrigPointerField = $GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'] ?? null;
107+
if ($transOrigPointerField) {
108+
$queryBuilder->andWhere($queryBuilder->expr()->eq('relation.' . $transOrigPointerField, 0));
109+
}
110+
111+
foreach ($matchFields as $matchField => $value) {
112+
$queryBuilder->andWhere(
113+
$queryBuilder->expr()->eq($matchField, $queryBuilder->createNamedParameter($value, Connection::PARAM_STR))
114+
);
115+
}
116+
117+
$rows = $queryBuilder->executeQuery()->fetchAllAssociative();
118+
$records = [];
119+
120+
$pageRepository = $cObj->getTypoScriptFrontendController()?->sys_page;
121+
$pageRepository instanceof PageRepository ?: throw new RuntimeException('PageRepository not found');
122+
123+
foreach ($rows as $row) {
124+
// Versioning preview:
125+
$pageRepository->versionOL($foreignTable, $row, true);
126+
127+
if (!$row) {
128+
continue;
129+
}
130+
131+
// Language overlay:
132+
$row = $pageRepository->getLanguageOverlay($foreignTable, $row);
133+
134+
if (!$row) {
135+
continue; // Might be unset in the language overlay
136+
}
137+
138+
$records[] = $row;
139+
}
140+
141+
return $records;
142+
}
143+
}

Configuration/Services.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
public: true
6+
7+
AUS\RelationProcessor\:
8+
resource: '../Classes/*'
9+
exclude: '../Classes/Domain/Model/*'

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# EXT:relation_processor
2+
3+
## install
4+
5+
``composer req andersundsehr/relation-processor``
6+
7+
## what does it do
8+
9+
It adds a RelationProcessor so you don't have to manually define a DatabaseQueryProcessor for each relation you want to use.
10+
It uses the TCA configuration to determine the correct query to use.
11+
It uses the `PageRepository->versionOL()` and `PageRepository->getLanguageOverlay()` functions so it hase correct versioning and language overlay support.
12+
13+
14+
15+
### Example
16+
17+
````typo3_typoscript
18+
10 = AUS\RelationProcessor\DataProcessing\RelationProcessor
19+
10 {
20+
# this field is of the current table and will be used to determine the relation
21+
# eg. if you have EXT:news and this processor is used on a tt_content you can get all related news like this:
22+
field = tx_news_related_news
23+
}
24+
````
25+
26+
### Advanced Example
27+
28+
````typo3_typoscript
29+
page = PAGE
30+
page.10 = FLUIDTEMPLATE
31+
page.10 {
32+
dataProcessing {
33+
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
34+
10 {
35+
references.fieldName = header_image
36+
as = headerImage
37+
}
38+
39+
20 = AUS\RelationProcessor\DataProcessing\RelationProcessor
40+
20 {
41+
field = tx_customerproduct_companies
42+
43+
dataProcessing {
44+
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
45+
10 {
46+
references.fieldName = header_image
47+
as = headerImage
48+
}
49+
50+
20 = AUS\RelationProcessor\DataProcessing\RelationProcessor
51+
20 {
52+
field = tx_customercompany_product_family
53+
54+
dataProcessing {
55+
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
56+
10 {
57+
references.fieldName = header_image
58+
as = headerImage
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
````
67+
68+
# with ♥️ from anders und sehr GmbH
69+
70+
> If something did not work 😮
71+
> or you appreciate this Extension 🥰 let us know.
72+
73+
> We are hiring https://www.andersundsehr.com/karriere/
74+

composer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "andersundsehr/relation-processor",
3+
"description": "Adds DataProcessor to resolve all types of TCA Relations",
4+
"license": [
5+
"GPL-2.0-or-later"
6+
],
7+
"type": "typo3-cms-extension",
8+
"require": {
9+
"php": "~8.2.0 || ~8.3.0",
10+
"typo3/cms-core": "^11.5 || ^12.0",
11+
"typo3/cms-frontend": "^11.5 || ^12.0"
12+
},
13+
"require-dev": {
14+
"pluswerk/grumphp-config": "^6.9",
15+
"saschaegerer/phpstan-typo3": "^1.8.2",
16+
"ssch/typo3-rector": "^1.3.5"
17+
},
18+
"autoload": {
19+
"psr-4": {
20+
"AUS\\RelationProcessor\\": "Classes/"
21+
}
22+
},
23+
"config": {
24+
"allow-plugins": {
25+
"ergebnis/composer-normalize": true,
26+
"phpro/grumphp": true,
27+
"phpstan/extension-installer": true,
28+
"pluswerk/grumphp-config": true,
29+
"typo3/class-alias-loader": true,
30+
"typo3/cms-composer-installers": true
31+
}
32+
},
33+
"extra": {
34+
"typo3/cms": {
35+
"extension-key": "relation_processor"
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)