Skip to content

Commit

Permalink
[BUGFIX] Detach colPos list items provider from BackendLayoutView (#2191
Browse files Browse the repository at this point in the history
)
  • Loading branch information
NamelessCoder authored Oct 28, 2024
1 parent e863026 commit 83fe4e3
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 347 deletions.
58 changes: 58 additions & 0 deletions Classes/Integration/HookSubscribers/ColumnPositionItems.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace FluidTYPO3\Flux\Integration\HookSubscribers;

use FluidTYPO3\Flux\Integration\FormEngine\SelectOption;
use FluidTYPO3\Flux\Provider\Interfaces\GridProviderInterface;
use FluidTYPO3\Flux\Provider\ProviderResolver;
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
use FluidTYPO3\Flux\Utility\ColumnNumberUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class ColumnPositionItems
{
private WorkspacesAwareRecordService $recordService;
private ProviderResolver $providerResolver;

public function __construct(WorkspacesAwareRecordService $recordService, ProviderResolver $providerResolver)
{
$this->recordService = $recordService;
$this->providerResolver = $providerResolver;
}

/**
* Gets colPos items to be shown in the forms engine.
* This method is called as "itemsProcFunc" with the accordant context
* for tt_content.colPos.
*/
public function colPosListItemProcFunc(array &$parameters): void
{
$parentRecordUid = ColumnNumberUtility::calculateParentUid($parameters['row']['colPos']);
$parentRecord = $this->recordService->getSingle('tt_content', '*', $parentRecordUid);
$provider = $this->providerResolver->resolvePrimaryConfigurationProvider('tt_content', null, $parentRecord);
if ($parentRecord && $provider instanceof GridProviderInterface) {
$grid = $provider->getGrid($parentRecord);
/** @var SelectOption $dividerItem */
$dividerItem = GeneralUtility::makeInstance(
SelectOption::class,
'LLL:EXT:flux/Resources/Private/Language/locallang.xlf:flux.backendLayout.columnsInParent',
'--div--'
);
$parameters['items'][] = $dividerItem->toArray();
foreach ($grid->getRows() as $row) {
foreach ($row->getColumns() as $column) {
/** @var SelectOption $item */
$item = GeneralUtility::makeInstance(
SelectOption::class,
$column->getLabel(),
ColumnNumberUtility::calculateColumnNumberForParentAndColumn(
$parentRecordUid,
$column->getColumnPosition()
)
);
$parameters['items'][] = $item->toArray();
}
}
}
}
}
83 changes: 3 additions & 80 deletions Classes/Integration/Overrides/BackendLayoutView.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
* LICENSE.md file that was distributed with this source code.
*/

use FluidTYPO3\Flux\Integration\FormEngine\SelectOption;
use FluidTYPO3\Flux\Provider\Interfaces\GridProviderInterface;
use FluidTYPO3\Flux\Provider\ProviderResolver;
use FluidTYPO3\Flux\Utility\ColumnNumberUtility;
use FluidTYPO3\Flux\Utility\DoctrineQueryProxy;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand All @@ -21,7 +19,6 @@ class BackendLayoutView extends \TYPO3\CMS\Backend\View\BackendLayoutView
{
protected ?GridProviderInterface $provider = null;
protected array $record = [];
protected bool $addingItemsForContent = false;

public function setProvider(GridProviderInterface $provider): void
{
Expand All @@ -33,43 +30,11 @@ public function setRecord(array $record): void
$this->record = $record;
}

/**
* Gets colPos items to be shown in the forms engine.
* This method is called as "itemsProcFunc" with the accordant context
* for tt_content.colPos.
*/
public function colPosListItemProcFunc(array $parameters): void
{
$this->record = $parameters['row'];
$this->addingItemsForContent = true;
parent::colPosListItemProcFunc($parameters);
$this->addingItemsForContent = false;
}

/**
* @param int $pageId
*/
public function getSelectedBackendLayout($pageId): ?array
{
if ($this->addingItemsForContent) {
$identifier = $this->getSelectedCombinedIdentifier($pageId);
if ($identifier === false) {
return null;
}

// Early return parent method's output if selected identifier is not from Flux
if (substr((string) $identifier, 0, 6) !== 'flux__') {
return parent::getSelectedBackendLayout($pageId);
}
$pageRecord = $this->loadRecordFromTable('pages', (int)$pageId);
if (!$pageRecord) {
return null;
}
$pageLevelProvider = $this->resolvePrimaryProviderForRecord('pages', $pageRecord);
if ($pageLevelProvider instanceof GridProviderInterface) {
return $pageLevelProvider->getGrid($pageRecord)->buildExtendedBackendLayoutArray(0);
}
}
// Delegate resolving of backend layout structure to the Provider, which will return a Grid, which can create
// a full backend layout data array.
if ($this->provider instanceof GridProviderInterface) {
Expand All @@ -95,51 +60,6 @@ protected function resolveParentRecordUid(array $record): int
return (int) $uid;
}

/**
* Override which will merge allowed colPos values from two places:
*
* 1) The currently selected backend layout (which may be a Flux-based
* or any other type).
* 2) If a Provider can be resolved for the parent record and it has
* a grid, items from that grid are included.
*
* The result is a "colPos" items collection which includes page columns
* and columns directly inside the current parent.
*
* @param int $pageId
* @param array $items
*/
protected function addColPosListLayoutItems($pageId, $items): array
{
$layout = $this->getSelectedBackendLayout($pageId);
if (isset($layout, $layout['__items'])) {
$items = $layout['__items'];
}
if ($this->addingItemsForContent) {
$parentRecordUid = ColumnNumberUtility::calculateParentUid((integer) ($this->record['colPos'] ?? 0));
if ($parentRecordUid > 0) {
$parentRecord = $this->loadRecordFromTable('tt_content', $parentRecordUid);
if (!$parentRecord) {
return $items;
}
$provider = $this->resolvePrimaryProviderForRecord('tt_content', $parentRecord);
if ($provider) {
$label = $this->getLanguageService()->sL(
'LLL:EXT:flux/Resources/Private/Language/locallang.xlf:flux.backendLayout.columnsInParent'
);
$items = array_merge(
$items,
[
(new SelectOption($label, '--div--'))->toArray()
],
$provider->getGrid($parentRecord)->buildExtendedBackendLayoutArray($parentRecordUid)['__items']
);
}
}
}
return $items;
}

/**
* @codeCoverageIgnore
*/
Expand All @@ -157,6 +77,9 @@ protected function loadRecordFromTable(string $table, int $uid): ?array
return $results[0] ?? null;
}

/**
* @codeCoverageIgnore
*/
protected function resolvePrimaryProviderForRecord(string $table, array $record): ?GridProviderInterface
{
/** @var ProviderResolver $providerResolver */
Expand Down
2 changes: 2 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ services:
arguments:
$locator: !tagged_locator { tag: 'flux.datatransformer', index_by: 'identifier' }

FluidTYPO3\Flux\Integration\HookSubscribers\ColumnPositionItems:
public: true
FluidTYPO3\Flux\Backend\BackendLayoutDataProvider:
public: true
FluidTYPO3\Flux\Backend\PageLayoutDataProvider:
Expand Down
2 changes: 1 addition & 1 deletion Configuration/TCA/Overrides/tt_content.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
\FluidTYPO3\Flux\Integration\MultipleItemsProcFunc::register(
'tt_content',
'colPos',
\FluidTYPO3\Flux\Integration\Overrides\BackendLayoutView::class . '->colPosListItemProcFunc'
\FluidTYPO3\Flux\Integration\HookSubscribers\ColumnPositionItems::class . '->colPosListItemProcFunc'
);

$GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['label'] = 'LLL:EXT:flux/Resources/Private/Language/locallang.xlf:tt_content.pi_flexform';
Expand Down
103 changes: 103 additions & 0 deletions Tests/Unit/Integration/HookSubscribers/ColumnPositionItemsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace FluidTYPO3\Flux\Tests\Unit\Integration\HookSubscribers;

/*
* This file is part of the FluidTYPO3/Flux project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/

use FluidTYPO3\Flux\Form\Container\Column;
use FluidTYPO3\Flux\Form\Container\Grid;
use FluidTYPO3\Flux\Form\Container\Row;
use FluidTYPO3\Flux\Integration\FormEngine\SelectOption;
use FluidTYPO3\Flux\Integration\HookSubscribers\ColumnPositionItems;
use FluidTYPO3\Flux\Provider\ProviderInterface;
use FluidTYPO3\Flux\Provider\ProviderResolver;
use FluidTYPO3\Flux\Service\WorkspacesAwareRecordService;
use FluidTYPO3\Flux\Tests\Unit\AbstractTestCase;

class ColumnPositionItemsTest extends AbstractTestCase
{
private WorkspacesAwareRecordService $recordService;
private ProviderResolver $providerResolver;

protected function setUp(): void
{
parent::setUp();
$this->recordService = $this->getMockBuilder(WorkspacesAwareRecordService::class)
->onlyMethods(['getSingle'])
->disableOriginalConstructor()
->getMock();
$this->providerResolver = $this->getMockBuilder(ProviderResolver::class)
->onlyMethods(['resolvePrimaryConfigurationProvider'])
->disableOriginalConstructor()
->getMock();
}

public function testDoesNotProcessWithoutParentRecord(): void
{
$this->recordService->method('getSingle')->willReturn(null);
$this->providerResolver->method('resolvePrimaryConfigurationProvider')->willReturn(
$this->getMockBuilder(ProviderInterface::class)->getMockForAbstractClass()
);
$subject = new ColumnPositionItems($this->recordService, $this->providerResolver);

$parameters = ['row' => ['colPos' => 101]];
$subject->colPosListItemProcFunc($parameters);
self::assertSame($parameters, $parameters);
}

public function testDoesNotProcessWithoutProvider(): void
{
$this->recordService->method('getSingle')->willReturn(['uid' => 123]);
$this->providerResolver->method('resolvePrimaryConfigurationProvider')->willReturn(null);
$subject = new ColumnPositionItems($this->recordService, $this->providerResolver);

$parameters = ['row' => ['colPos' => 101]];
$subject->colPosListItemProcFunc($parameters);
self::assertSame($parameters, $parameters);
}

public function testDoesNotProcessWithoutProviderAndParentRecord(): void
{
$this->recordService->method('getSingle')->willReturn(null);
$this->providerResolver->method('resolvePrimaryConfigurationProvider')->willReturn(null);
$subject = new ColumnPositionItems($this->recordService, $this->providerResolver);

$parameters = ['row' => ['colPos' => 101]];
$subject->colPosListItemProcFunc($parameters);
self::assertSame($parameters, $parameters);
}

public function testAddsExpectedItems(): void
{
$grid = Grid::create();
$grid->createContainer(Row::class, 'row')->createContainer(Column::class, 'col')->setColumnPosition(3);

$provider = $this->getMockBuilder(ProviderInterface::class)->getMockForAbstractClass();
$provider->method('getGrid')->willReturn($grid);

$this->recordService->method('getSingle')->willReturn(['uid' => 123]);
$this->providerResolver->method('resolvePrimaryConfigurationProvider')->willReturn($provider);
$subject = new ColumnPositionItems($this->recordService, $this->providerResolver);

$parameters = ['row' => ['colPos' => 103]];
$expected = $parameters;
$expected['items'] = [
(new SelectOption(
'LLL:EXT:flux/Resources/Private/Language/locallang.xlf:flux.backendLayout.columnsInParent',
'--div--'
))->toArray(),
(new SelectOption(
'LLL:EXT:flux/Resources/Private/Language/locallang.xlf:flux..columns.col',
103
))->toArray(),
];

$subject->colPosListItemProcFunc($parameters);
self::assertSame($expected, $parameters);
}
}
Loading

0 comments on commit 83fe4e3

Please sign in to comment.