Skip to content

Commit 793d9ce

Browse files
committed
[FEATURE] Content Block as Site Set
1 parent 3789355 commit 793d9ce

14 files changed

Lines changed: 612 additions & 2 deletions
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\ContentBlocks\EventListener;
19+
20+
use TYPO3\CMS\Backend\Controller\Event\ModifyNewContentElementWizardItemsEvent;
21+
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType;
22+
use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock;
23+
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
24+
use TYPO3\CMS\ContentBlocks\SiteSet\ContentBlockSiteRegistry;
25+
use TYPO3\CMS\Core\Attribute\AsEventListener;
26+
use TYPO3\CMS\Core\Site\Entity\Site;
27+
28+
/**
29+
* @internal
30+
*/
31+
#[AsEventListener('RestrictContentBlockInNewContentElementWizard')]
32+
readonly class RestrictContentBlockInNewContentElementWizard
33+
{
34+
public function __construct(
35+
protected ContentBlockRegistry $contentBlockRegistry,
36+
protected ContentBlockSiteRegistry $contentBlockSiteRegistry,
37+
) {}
38+
39+
public function __invoke(ModifyNewContentElementWizardItemsEvent $event): void
40+
{
41+
/** @var Site $site */
42+
$site = $event->getRequest()->getAttribute('site');
43+
$table = ContentType::CONTENT_ELEMENT->getTable();
44+
$contentBlocks = $this->contentBlockSiteRegistry->resolveContentBlocksRegisteredAsSiteSet($site, $table);
45+
// If there is no Content Block registered as Site Set, allow all.
46+
if ($contentBlocks === []) {
47+
return;
48+
}
49+
$contentBlockTypeNames = array_map(fn(LoadedContentBlock $contentBlock) => $contentBlock->getYaml()['typeName'], $contentBlocks);
50+
$wizardItems = $event->getWizardItems();
51+
foreach ($wizardItems as $identifier => $item) {
52+
$typeName = $item['defaultValues']['CType'] ?? null;
53+
if ($typeName === null) {
54+
continue;
55+
}
56+
if ($this->contentBlockRegistry->getByTypeName($table, $typeName) === null) {
57+
continue;
58+
}
59+
if (in_array($typeName, $contentBlockTypeNames, true)) {
60+
continue;
61+
}
62+
unset($wizardItems[$identifier]);
63+
}
64+
$event->setWizardItems($wizardItems);
65+
}
66+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\ContentBlocks\EventListener;
19+
20+
use TYPO3\CMS\Backend\Controller\Event\ModifyNewRecordCreationLinksEvent;
21+
use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock;
22+
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
23+
use TYPO3\CMS\ContentBlocks\SiteSet\ContentBlockSiteRegistry;
24+
use TYPO3\CMS\Core\Attribute\AsEventListener;
25+
use TYPO3\CMS\Core\Site\Entity\Site;
26+
27+
#[AsEventListener('RestrictContentBlockInNewRecordView')]
28+
readonly class RestrictContentBlockInNewRecordView
29+
{
30+
public function __construct(
31+
protected ContentBlockRegistry $contentBlockRegistry,
32+
protected ContentBlockSiteRegistry $contentBlockSiteRegistry,
33+
) {}
34+
35+
public function __invoke(ModifyNewRecordCreationLinksEvent $event): void
36+
{
37+
/** @var Site $site */
38+
$site = $event->request->getAttribute('site');
39+
foreach ($event->groupedCreationLinks as $groupName => $group) {
40+
if ($groupName === 'pages' || $groupName === 'content') {
41+
continue;
42+
}
43+
foreach ($group['items'] as $mainType => $item) {
44+
$contentBlocks = $this->contentBlockSiteRegistry->resolveContentBlocksRegisteredAsSiteSet($site, $mainType);
45+
if (array_key_exists('types', $item)) {
46+
// If there is no Content Block registered as Site Set, allow all.
47+
if ($contentBlocks === []) {
48+
continue;
49+
}
50+
foreach ($item['types'] as $type => $typeItem) {
51+
$isRecordAllowed = $this->isRecordAllowed($contentBlocks, $mainType, $type);
52+
if ($isRecordAllowed === false) {
53+
unset($event->groupedCreationLinks[$groupName]['items'][$mainType]['types'][$type]);
54+
if ($event->groupedCreationLinks[$groupName]['items'][$mainType]['types'] === []) {
55+
unset($event->groupedCreationLinks[$groupName]['items'][$mainType]);
56+
}
57+
}
58+
}
59+
continue;
60+
}
61+
$isRecordAllowed = $this->isRecordAllowed($contentBlocks, $mainType);
62+
if ($isRecordAllowed === false) {
63+
unset($event->groupedCreationLinks[$groupName]['items'][$mainType]);
64+
}
65+
}
66+
}
67+
}
68+
69+
/**
70+
* @param array<LoadedContentBlock> $registeredContentBlocks
71+
*/
72+
protected function isRecordAllowed(array $registeredContentBlocks, string $mainType, ?string $subType = null): bool
73+
{
74+
$contentBlock = $this->contentBlockRegistry->getByTypeName($mainType, $subType ?? '1');
75+
if ($contentBlock === null) {
76+
return true;
77+
}
78+
if ($subType === null) {
79+
$contentBlockTableNames = array_map(fn(LoadedContentBlock $contentBlock) => $contentBlock->getYaml()['table'], $registeredContentBlocks);
80+
$isAllowed = in_array($mainType, $contentBlockTableNames, true);
81+
return $isAllowed;
82+
}
83+
foreach ($registeredContentBlocks as $registeredContentBlock) {
84+
if ($registeredContentBlock->getYaml()['table'] !== $mainType) {
85+
continue;
86+
}
87+
if ($registeredContentBlock->getYaml()['typeName'] !== $subType) {
88+
continue;
89+
}
90+
return true;
91+
}
92+
return false;
93+
}
94+
}

Classes/Form/FormDataProvider/AllowedRecordTypeFilter.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@
1717

1818
namespace TYPO3\CMS\ContentBlocks\Form\FormDataProvider;
1919

20+
use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock;
21+
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
2022
use TYPO3\CMS\Core\Schema\Struct\SelectItem;
2123

2224
/**
2325
* @internal
2426
*/
2527
readonly class AllowedRecordTypeFilter
2628
{
29+
public function __construct(
30+
protected ContentBlockRegistry $contentBlockRegistry,
31+
) {}
32+
2733
/**
2834
* @param array<array|SelectItem> $items
2935
* @param string[] $allowedRecordTypes
@@ -47,4 +53,33 @@ public function filterAndSortItems(array $items, array $allowedRecordTypes): arr
4753
$filteredItems = array_values($filteredItems);
4854
return $filteredItems;
4955
}
56+
57+
/**
58+
* @param array<array|SelectItem> $items
59+
* @param LoadedContentBlock[] $allowedContentBlocks
60+
* @return SelectItem[]
61+
*/
62+
public function filterByAllowedContentBlocks(array $items, array $allowedContentBlocks, string $tableName): array
63+
{
64+
$contentBlockTypeNames = array_map(fn(LoadedContentBlock $contentBlock) => $contentBlock->getYaml()['typeName'], $allowedContentBlocks);
65+
$filteredItems = [];
66+
foreach ($items as $item) {
67+
$selectItem = $item;
68+
if ($selectItem instanceof SelectItem === false) {
69+
$selectItem = SelectItem::fromTcaItemArray($item);
70+
}
71+
if ($selectItem->isDivider()) {
72+
$filteredItems[] = $selectItem;
73+
continue;
74+
}
75+
if ($this->contentBlockRegistry->getByTypeName($tableName, $selectItem->getValue()) === null) {
76+
$filteredItems[] = $selectItem;
77+
continue;
78+
}
79+
if (in_array($selectItem->getValue(), $contentBlockTypeNames, true)) {
80+
$filteredItems[] = $selectItem;
81+
}
82+
}
83+
return $filteredItems;
84+
}
5085
}

Classes/Form/FormDataProvider/AllowedRecordTypesInCollection.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
use TYPO3\CMS\Core\Schema\Exception\InvalidSchemaTypeException;
2424
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
2525

26+
/**
27+
* @internal
28+
*/
2629
final readonly class AllowedRecordTypesInCollection implements FormDataProviderInterface
2730
{
2831
public function __construct(
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\ContentBlocks\Form\FormDataProvider;
19+
20+
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
21+
use TYPO3\CMS\ContentBlocks\SiteSet\ContentBlockSiteRegistry;
22+
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
23+
use TYPO3\CMS\Core\Site\Entity\Site;
24+
25+
/**
26+
* @internal
27+
*/
28+
final readonly class AllowedRecordTypesInSite implements FormDataProviderInterface
29+
{
30+
public function __construct(
31+
protected ContentBlockSiteRegistry $contentBlockSiteRegistry,
32+
protected TcaSchemaFactory $tcaSchemaFactory,
33+
protected AllowedRecordTypeFilter $allowedRecordTypeFilter,
34+
) {}
35+
36+
public function addData(array $result): array
37+
{
38+
$tableName = $result['tableName'];
39+
$contentElementSchema = $this->tcaSchemaFactory->get($tableName);
40+
if ($tableName !== $contentElementSchema->getName()) {
41+
return $result;
42+
}
43+
$site = $result['site'];
44+
if ($site instanceof Site === false) {
45+
return $result;
46+
}
47+
$contentBlocks = $this->contentBlockSiteRegistry->resolveContentBlocksRegisteredAsSiteSet($site, $tableName);
48+
// If there is no Content Block registered as Site Set, allow all.
49+
if ($contentBlocks === []) {
50+
return $result;
51+
}
52+
$typeField = $contentElementSchema->getSubSchemaTypeInformation()->getFieldName();
53+
$items = $result['processedTca']['columns'][$typeField]['config']['items'] ?? [];
54+
if ($items === []) {
55+
return $result;
56+
}
57+
$filteredItems = $this->allowedRecordTypeFilter->filterByAllowedContentBlocks($items, $contentBlocks, $tableName);
58+
$result['processedTca']['columns'][$typeField]['config']['items'] = $filteredItems;
59+
return $result;
60+
}
61+
}

Classes/Registry/ContentBlockRegistry.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public function getAll(): array
7272
return $this->contentBlocks;
7373
}
7474

75+
public function getByTypeName(string $table, string $typeName): ?LoadedContentBlock
76+
{
77+
return $this->typeNamesByTable[$table]['types'][$typeName] ?? null;
78+
}
79+
7580
public function getFromRawRecord(string $table, array $record = []): ?LoadedContentBlock
7681
{
7782
if (array_key_exists($table, $this->typeNamesByTable) === false) {
@@ -98,14 +103,17 @@ private function registerTypeName(LoadedContentBlock $contentBlock): void
98103
// If typeName is not set explicitly, then it is inferred from the name, which is unique.
99104
$contentType = $contentBlock->getContentType();
100105
$yaml = $contentBlock->getYaml();
106+
$table = $contentType->getTable() ?? $yaml['table'];
101107
$typeField = $yaml['typeField'] ?? null;
108+
if ($typeField === null && ($this->typeNamesByTable[$table] ?? []) !== []) {
109+
$typeField = $this->typeNamesByTable[$table]['typeField'];
110+
}
102111
if ($typeField === null && $contentType !== ContentType::FILE_TYPE) {
103112
$typeName = '1';
104113
}
105114
$typeName ??= (string)$yaml['typeName'];
106115

107116
// The typeName has to be unique per table. Get it from the YAML for Record Types.
108-
$table = $contentType->getTable() ?? $yaml['table'];
109117
if (!isset($this->typeNamesByTable[$table]['types'][$typeName])) {
110118
$this->typeNamesByTable[$table]['typeField'] ??= $typeField;
111119
$this->typeNamesByTable[$table]['types'][$typeName] = $contentBlock;

0 commit comments

Comments
 (0)