From 93b77f6d32ba653dcb8dd6f70373e9db6abe1a2c Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <ben@bnf.dev>
Date: Thu, 22 Feb 2024 08:24:34 +0100
Subject: [PATCH] [BUGFIX] Allow TCA resultArray modification via
 CustomFileControlsEvent

The event provides access to the `$resultArray` and documents
that it can be modified to set `javaScriptModules`, but missed
to take the resulting data into account.

Releases: main, 12.4
Resolves: #103174
Change-Id: I0a53d1fbcdff0a3d57de237b44d3ff68f60c721d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83081
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: core-ci <typo3@b13.com>
---
 .../Form/Container/FilesControlContainer.php  |  10 +-
 .../Container/FilesControlContainerTest.php   | 113 ++++++++++++++++++
 2 files changed, 119 insertions(+), 4 deletions(-)
 create mode 100644 typo3/sysext/backend/Tests/Functional/Form/Container/FilesControlContainerTest.php

diff --git a/typo3/sysext/backend/Classes/Form/Container/FilesControlContainer.php b/typo3/sysext/backend/Classes/Form/Container/FilesControlContainer.php
index 199f94fd320b..c96e7c12aaa9 100644
--- a/typo3/sysext/backend/Classes/Form/Container/FilesControlContainer.php
+++ b/typo3/sysext/backend/Classes/Form/Container/FilesControlContainer.php
@@ -285,9 +285,11 @@ class FilesControlContainer extends AbstractContainer
             }
         }
 
-        $controls = GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
+        $event = GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(
             new CustomFileControlsEvent($resultArray, $table, $field, $row, $config, $formFieldIdentifier, $formFieldName)
-        )->getControls();
+        );
+        $resultArray = $event->getResultArray();
+        $controls = $event->getControls();
 
         if ($controls !== []) {
             $view->assign('customControls', [
@@ -296,9 +298,9 @@ class FilesControlContainer extends AbstractContainer
             ]);
         }
 
-        $resultArray['html'] = $view->render('Form/FilesControlContainer');
-        $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $this->javaScriptModules);
+        $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'] ?? [], $this->javaScriptModules);
         $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/form-engine/container/files-control-container.js');
+        $resultArray['html'] = $view->render('Form/FilesControlContainer');
 
         return $resultArray;
     }
diff --git a/typo3/sysext/backend/Tests/Functional/Form/Container/FilesControlContainerTest.php b/typo3/sysext/backend/Tests/Functional/Form/Container/FilesControlContainerTest.php
new file mode 100644
index 000000000000..99e6ea36e5c2
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Functional/Form/Container/FilesControlContainerTest.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Backend\Tests\Functional\Form\Container;
+
+use Symfony\Component\DependencyInjection\Container;
+use TYPO3\CMS\Backend\Form\Container\FilesControlContainer;
+use TYPO3\CMS\Backend\Form\Event\CustomFileControlsEvent;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Backend\Routing\Route;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
+use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
+use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+final class FilesControlContainerTest extends FunctionalTestCase
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $GLOBALS['BE_USER'] = new BackendUserAuthentication();
+        $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->create('default');
+    }
+
+    /**
+     * @test
+     */
+    public function customFileControlsEventIsCalled(): void
+    {
+        $customFileControlsEvent = null;
+        $controls = ['foo', 'bar'];
+        $databaseRow = [
+            'uid' => 123,
+        ];
+        $fieldConfig = [
+            'minitems' => 1,
+            'maxitems' => 2,
+        ];
+        $fieldName = 'assets';
+        $tableName = 'tx_table';
+        $formFieldIdentifier = 'data-123-tx_table-123-assets';
+        $formFieldName = 'data[tx_table][123][assets]';
+
+        /** @var Container $container */
+        $container = $this->get('service_container');
+        $container->set(
+            'custom-file-controls-listener',
+            static function (CustomFileControlsEvent $event) use (&$customFileControlsEvent, $controls) {
+                $customFileControlsEvent = $event;
+                $event->setControls($controls);
+                $event->setResultArray(['javaScriptModules' => ['fooJavaScriptModule']]);
+            }
+        );
+
+        $eventListener = $this->get(ListenerProvider::class);
+        $eventListener->addListener(CustomFileControlsEvent::class, 'custom-file-controls-listener');
+
+        $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+        $subject = new FilesControlContainer($nodeFactory, [
+            'inlineData' => [],
+            'inlineStructure' => [],
+            'inlineFirstPid' => 123,
+            'fieldName' => $fieldName,
+            'tableName' => $tableName,
+            'renderType' => 'file',
+            'databaseRow' => $databaseRow,
+            'tabAndInlineStack' => '',
+            'request' => (new ServerRequest())
+                ->withAttribute('route', new Route('', []))
+                ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE),
+            'parameterArray' => [
+                'itemFormElID' => '',
+                'itemFormElName' => '',
+                'fieldConf' => [
+                    'label' => 'foobar',
+                    'config' => $fieldConfig,
+                    'children' => [],
+                ],
+            ],
+            'returnUrl' => '',
+        ]);
+        $result = $subject->render();
+
+        self::assertInstanceOf(CustomFileControlsEvent::class, $customFileControlsEvent);
+        self::assertContains('fooJavaScriptModule', $result['javaScriptModules']);
+        self::assertContains('fooJavaScriptModule', $customFileControlsEvent->getResultArray()['javaScriptModules']);
+        self::assertEquals($controls, $customFileControlsEvent->getControls());
+        self::assertEquals($databaseRow, $customFileControlsEvent->getDatabaseRow());
+        self::assertArrayHasKey('minitems', $customFileControlsEvent->getFieldConfig());
+        self::assertArrayHasKey('maxitems', $customFileControlsEvent->getFieldConfig());
+        self::assertEquals($fieldName, $customFileControlsEvent->getFieldName());
+        self::assertEquals($formFieldIdentifier, $customFileControlsEvent->getFormFieldIdentifier());
+        self::assertEquals($formFieldName, $customFileControlsEvent->getFormFieldName());
+        self::assertEquals($tableName, $customFileControlsEvent->getTableName());
+    }
+}
-- 
GitLab