Skip to content

Commit 1ac1244

Browse files
committed
[FEATURE] Restrict FormEngine by registered Content Blocks
1 parent 5c9894b commit 1ac1244

7 files changed

Lines changed: 177 additions & 27 deletions

File tree

Classes/EventListener/RestrictContentBlockInNewContentElementWizard.php

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,33 @@
1818
namespace TYPO3\CMS\ContentBlocks\EventListener;
1919

2020
use TYPO3\CMS\Backend\Controller\Event\ModifyNewContentElementWizardItemsEvent;
21-
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType;
21+
use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock;
2222
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
23+
use TYPO3\CMS\ContentBlocks\SiteSet\ContentBlockSiteRegistry;
2324
use TYPO3\CMS\Core\Attribute\AsEventListener;
2425
use TYPO3\CMS\Core\Site\Entity\Site;
25-
use TYPO3\CMS\Core\Site\Set\SetRegistry;
2626

27+
/**
28+
* @internal
29+
*/
2730
#[AsEventListener('RestrictContentBlockInNewContentElementWizard')]
2831
readonly class RestrictContentBlockInNewContentElementWizard
2932
{
3033
public function __construct(
31-
protected SetRegistry $setRegistry,
3234
protected ContentBlockRegistry $contentBlockRegistry,
35+
protected ContentBlockSiteRegistry $contentBlockSiteRegistry,
3336
) {}
3437

3538
public function __invoke(ModifyNewContentElementWizardItemsEvent $event): void
3639
{
37-
$contentBlocks = $this->resolveContentBlocksRegisteredAsSiteSet($event);
40+
/** @var Site $site */
41+
$site = $event->getRequest()->getAttribute('site');
42+
$contentBlocks = $this->contentBlockSiteRegistry->resolveContentBlocksRegisteredAsSiteSet($site);
3843
// If there is no Content Block registered as Site Set, allow all.
3944
if ($contentBlocks === []) {
4045
return;
4146
}
47+
$contentBlockTypeNames = array_map(fn(LoadedContentBlock $contentBlock) => $contentBlock->getYaml()['typeName'], $contentBlocks);
4248
$wizardItems = $event->getWizardItems();
4349
foreach ($wizardItems as $identifier => $item) {
4450
$typeName = $item['defaultValues']['CType'] ?? null;
@@ -48,32 +54,11 @@ public function __invoke(ModifyNewContentElementWizardItemsEvent $event): void
4854
if ($this->contentBlockRegistry->getByTypeName('tt_content', $typeName) === null) {
4955
continue;
5056
}
51-
if (in_array($typeName, $contentBlocks, true)) {
57+
if (in_array($typeName, $contentBlockTypeNames, true)) {
5258
continue;
5359
}
5460
unset($wizardItems[$identifier]);
5561
}
5662
$event->setWizardItems($wizardItems);
5763
}
58-
59-
/**
60-
* @return array<string>
61-
*/
62-
protected function resolveContentBlocksRegisteredAsSiteSet(ModifyNewContentElementWizardItemsEvent $event): array
63-
{
64-
$registeredContentBlocksTypeNames = [];
65-
/** @var Site $site */
66-
$site = $event->getRequest()->getAttribute('site');
67-
$siteSets = $this->setRegistry->getSets(...$site->getSets());
68-
foreach ($siteSets as $siteSet) {
69-
if ($this->contentBlockRegistry->hasContentBlock($siteSet->name)) {
70-
$contentBlock = $this->contentBlockRegistry->getContentBlock($siteSet->name);
71-
if ($contentBlock->getContentType() !== ContentType::CONTENT_ELEMENT) {
72-
continue;
73-
}
74-
$registeredContentBlocksTypeNames[] = $contentBlock->getYaml()['typeName'];
75-
}
76-
}
77-
return $registeredContentBlocksTypeNames;
78-
}
7964
}
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 AllowedContentElementsInSite 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+
$contentElementSchema = $this->tcaSchemaFactory->get('tt_content');
39+
$tableName = $result['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);
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);
58+
$result['processedTca']['columns'][$typeField]['config']['items'] = $filteredItems;
59+
return $result;
60+
}
61+
}

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): 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('tt_content', $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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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\SiteSet;
19+
20+
use TYPO3\CMS\ContentBlocks\Definition\ContentType\ContentType;
21+
use TYPO3\CMS\ContentBlocks\Loader\LoadedContentBlock;
22+
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
23+
use TYPO3\CMS\Core\Site\Entity\Site;
24+
use TYPO3\CMS\Core\Site\Set\SetRegistry;
25+
26+
/**
27+
* @internal
28+
*/
29+
readonly class ContentBlockSiteRegistry
30+
{
31+
public function __construct(
32+
protected SetRegistry $setRegistry,
33+
protected ContentBlockRegistry $contentBlockRegistry,
34+
) {}
35+
36+
/**
37+
* @return array<LoadedContentBlock>
38+
*/
39+
public function resolveContentBlocksRegisteredAsSiteSet(
40+
Site $site,
41+
ContentType $contentType = ContentType::CONTENT_ELEMENT
42+
): array {
43+
$registeredContentBlocksTypeNames = [];
44+
$siteSets = $this->setRegistry->getSets(...$site->getSets());
45+
foreach ($siteSets as $siteSet) {
46+
if ($this->contentBlockRegistry->hasContentBlock($siteSet->name)) {
47+
$contentBlock = $this->contentBlockRegistry->getContentBlock($siteSet->name);
48+
if ($contentBlock->getContentType() !== $contentType) {
49+
continue;
50+
}
51+
$registeredContentBlocksTypeNames[] = $contentBlock;
52+
}
53+
}
54+
return $registeredContentBlocksTypeNames;
55+
}
56+
}

Tests/Unit/Form/FormDataProvider/AllowedRecordTypeFilterTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
use PHPUnit\Framework\Attributes\Test;
2121
use TYPO3\CMS\ContentBlocks\Form\FormDataProvider\AllowedRecordTypeFilter;
22+
use TYPO3\CMS\ContentBlocks\Registry\ContentBlockRegistry;
2223
use TYPO3\CMS\Core\Schema\Struct\SelectItem;
2324
use TYPO3\TestingFramework\Core\BaseTestCase;
2425

@@ -43,7 +44,8 @@ public function itemsAreFilteredAndSortedCorrectly(): void
4344
new SelectItem('select', 'A', 'a'),
4445
new SelectItem('select', 'C', 'c'),
4546
];
46-
$allowedRecordTypeFilter = new AllowedRecordTypeFilter();
47+
$contentBlockRegistry = new ContentBlockRegistry();
48+
$allowedRecordTypeFilter = new AllowedRecordTypeFilter($contentBlockRegistry);
4749
$result = $allowedRecordTypeFilter->filterAndSortItems($items, $allowedRecordTypes);
4850
self::assertEquals($expected, $result);
4951
}

ext_localconf.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
defined('TYPO3') or die();
44

5+
use TYPO3\CMS\Backend\Form\FormDataProvider\SiteResolving;
56
use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
7+
use TYPO3\CMS\ContentBlocks\Form\FormDataProvider\AllowedContentElementsInSite;
68
use TYPO3\CMS\ContentBlocks\Form\FormDataProvider\AllowedRecordTypesInCollection;
79
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
810

@@ -23,3 +25,9 @@
2325
TcaSelectItems::class,
2426
],
2527
];
28+
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['tcaDatabaseRecord'][AllowedContentElementsInSite::class] = [
29+
'depends' => [
30+
TcaSelectItems::class,
31+
SiteResolving::class,
32+
],
33+
];

0 commit comments

Comments
 (0)