Commit 3a85f685 authored by Ralf Zimmermann's avatar Ralf Zimmermann Committed by Oliver Hader
Browse files

[BUGFIX] The form upgrade wizard must update all plugin settings

The form definition renaming upgrade wizard renames the persistence
identifier within the form plugin flexform.
As a result, finisher overrides can no longer be properly assigned.
This patch adds an upgrade wizard which will be able to restore these
finisher overrides.

Resolves: #85544
Releases: master, 8.7
Change-Id: Idf1ffd8432fed88431b9a0feb407f42df3304401
Reviewed-on: https://review.typo3.org/57731


Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent 8af68b11
......@@ -297,39 +297,33 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
}
}
foreach ($this->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
$relativePath = rtrim($relativePath, '/') . '/';
foreach (new \DirectoryIterator($fullPath) as $fileInfo) {
if ($fileInfo->getExtension() !== 'yaml') {
continue;
}
$form = $this->load($relativePath . $fileInfo->getFilename());
if (isset($form['identifier'], $form['type']) && $form['type'] === 'Form') {
if ($this->hasValidFileExtension($fileInfo->getFilename())) {
$forms[] = [
'identifier' => $form['identifier'],
'name' => $form['label'] ?? $form['identifier'],
'persistenceIdentifier' => $relativePath . $fileInfo->getFilename(),
'readOnly' => $this->formSettings['persistenceManager']['allowSaveToExtensionPaths'] ? false: true,
'removable' => $this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths'] ? true: false,
'location' => 'extension',
'duplicateIdentifier' => false,
'invalid' => $form['invalid'],
];
$identifiers[$form['identifier']]++;
} else {
$forms[] = [
'identifier' => $form['identifier'],
'name' => $form['label'] ?? $form['identifier'],
'persistenceIdentifier' => $relativePath . $fileInfo->getFilename(),
'readOnly' => true,
'removable' => false,
'location' => 'extension',
'duplicateIdentifier' => false,
'invalid' => false,
'deprecatedFileExtension' => true,
];
}
foreach ($this->retrieveYamlFilesFromExtensionFolders() as $fullPath => $fileName) {
$form = $this->load($fullPath);
if (isset($form['identifier'], $form['type']) && $form['type'] === 'Form') {
if ($this->hasValidFileExtension($fileName)) {
$forms[] = [
'identifier' => $form['identifier'],
'name' => $form['label'] ?? $form['identifier'],
'persistenceIdentifier' => $fullPath,
'readOnly' => $this->formSettings['persistenceManager']['allowSaveToExtensionPaths'] ? false: true,
'removable' => $this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths'] ? true: false,
'location' => 'extension',
'duplicateIdentifier' => false,
'invalid' => $form['invalid'],
];
$identifiers[$form['identifier']]++;
} else {
$forms[] = [
'identifier' => $form['identifier'],
'name' => $form['label'] ?? $form['identifier'],
'persistenceIdentifier' => $fullPath,
'readOnly' => true,
'removable' => false,
'location' => 'extension',
'duplicateIdentifier' => false,
'invalid' => false,
'deprecatedFileExtension' => true,
];
}
}
}
......@@ -381,6 +375,29 @@ class FormPersistenceManager implements FormPersistenceManagerInterface
return $filesFromStorageFolders;
}
/**
* Retrieves yaml files from extension folders for further processing.
* At this time it's not determined yet, whether these files contain form data.
*
* @return File[]
* @internal
*/
public function retrieveYamlFilesFromExtensionFolders(): array
{
$filesFromExtensionFolders = [];
foreach ($this->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
foreach (new \DirectoryIterator($fullPath) as $fileInfo) {
if ($fileInfo->getExtension() !== 'yaml') {
continue;
}
$filesFromExtensionFolders[$relativePath . $fileInfo->getFilename()] = $fileInfo->getFilename();
}
}
return $filesFromExtensionFolders;
}
/**
* Return a list of all accessible file mountpoints for the
* current backend user.
......
TYPO3:
CMS:
Form:
persistenceManager:
allowedExtensionPaths:
110: EXT:test_resources/Configuration/Form/
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'Test Resources',
'description' => 'Test Resources',
'category' => 'example',
'version' => '9.3.3',
'state' => 'beta',
'uploadfolder' => 0,
'createDirs' => '',
'clearCacheOnLoad' => 0,
'author' => 'Oliver Hader',
'author_email' => 'oliver@typo3.org',
'author_company' => '',
'constraints' => [
'depends' => [
'typo3' => '9.3.3'
],
'conflicts' => [],
'suggests' => [],
],
];
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Form\Tests\Functional\Hooks;
/*
* 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!
*/
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\ReferenceIndex;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
use TYPO3\CMS\Form\Hooks\FormFileExtensionUpdate;
use TYPO3\CMS\Form\Slot\FilePersistenceSlot;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
class FormFileExtensionUpdateTest extends FunctionalTestCase
{
/**
* @var string[]
*/
protected $coreExtensionsToLoad = [
'form',
];
/**
* @var string[]
*/
protected $testExtensionsToLoad = [
'typo3/sysext/form/Tests/Functional/Hooks/Fixtures/test_resources',
];
/**
* @var FormFileExtensionUpdate
*/
private $subject;
/**
* @var FilePersistenceSlot
*/
private $slot;
/**
* @var FlexFormTools
*/
private $flexForm;
/**
* @var ReferenceIndex
*/
private $referenceIndex;
/**
* @var Folder
*/
private $storageFolder;
protected function setUp()
{
parent::setUp();
$this->setUpBackendUserFromFixture(1);
Bootstrap::getInstance()->initializeLanguageObject();
$folderIdentifier = 'user_upload';
$storage = ResourceFactory::getInstance()->getStorageObject(1);
if ($storage->hasFolder($folderIdentifier)) {
$storage->getFolder($folderIdentifier)->delete(true);
}
$this->subject = GeneralUtility::makeInstance(FormFileExtensionUpdate::class);
$this->slot = GeneralUtility::makeInstance(FilePersistenceSlot::class);
$this->flexForm = GeneralUtility::makeInstance(FlexFormTools::class);
$this->referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
$this->storageFolder = $storage->createFolder($folderIdentifier);
}
protected function tearDown()
{
$this->storageFolder->delete(true);
parent::tearDown();
}
/*
* --- CHECK FOR UPDATE ---
*/
/**
* @return bool
*/
private function invokeCheckForUpdate(): bool
{
$description = '';
return $this->subject->checkForUpdate($description);
}
/**
* @test
*/
public function updateIsNotRequiredHavingUpdatedFormDefinitions()
{
$this->createStorageFormDefinition('updated', false);
$this->assertFalse($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsRequiredHavingOutdatedStorageFormDefinitions()
{
$this->createStorageFormDefinition('legacy', true);
$this->assertTrue($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsNotRequiredHavingUpdatedStorageReferences()
{
$this->createStorageFormDefinition('updated', false);
$this->createReference(
$this->createStorageFileIdentifier('updated.form.yaml'),
'updated'
);
$this->assertFalse($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsNotRequiredHavingUpdatedStorageReferencesWithFinisherOverrides(
) {
$this->createStorageFormDefinition('updated', false);
$finisherOverrides = [
'FirstFinisher' => StringUtility::getUniqueId(),
'SecondFinisher' => StringUtility::getUniqueId(),
];
$this->createReference(
$this->createStorageFileIdentifier('updated.form.yaml'),
'updated',
$finisherOverrides
);
$this->assertFalse($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsRequiredHavingOutdatedStorageReferences()
{
// form definition was renamed already
$this->createStorageFormDefinition('updated', false);
// but references not updated yet
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated'
);
$this->assertTrue($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsRequiredHavingOutdatedStorageReferencesWithFinisherOverrides(
) {
// form definition was renamed already
$this->createStorageFormDefinition('updated', false);
// but references not updated yet
$finisherOverrides = [
'FirstFinisher' => StringUtility::getUniqueId(),
'SecondFinisher' => StringUtility::getUniqueId(),
];
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
$this->assertTrue($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsNotRequiredHavingOutdatedExtensionFormDefinitions()
{
$this->setUpAllowedExtensionPaths();
$this->assertFalse($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsNotRequiredHavingUpdatedExtensionReferences()
{
$this->setUpAllowedExtensionPaths();
$this->createReference(
$this->createExtensionFileIdentifier('updated.form.yaml'),
'updated'
);
$this->assertFalse($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsRequiredHavingOutdatedExtensionReferences()
{
$this->setUpAllowedExtensionPaths();
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated'
);
$this->assertTrue($this->invokeCheckForUpdate());
}
/**
* @test
*/
public function updateIsRequiredHavingOutdatedExtensionReferencesWithFinisherOverrides(
) {
$this->setUpAllowedExtensionPaths();
$finisherOverrides = [
'FirstFinisher' => StringUtility::getUniqueId(),
'SecondFinisher' => StringUtility::getUniqueId(),
];
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
$this->assertTrue($this->invokeCheckForUpdate());
}
/*
* --- PERFORM UPDATE ---
*/
private function invokePerformUpdate(): bool
{
$queries = [];
$messages = '';
return $this->subject->performUpdate(
$queries,
$messages
);
}
/**
* @test
*/
public function performUpdateSucceedsHavingOutdatedStorageFormDefinitions()
{
$this->createStorageFormDefinition('legacy', true);
$this->assertTrue(
$this->invokePerformUpdate()
);
$this->assertTrue(
$this->storageFolder->hasFile('legacy.form.yaml')
);
}
/**
* @test
*/
public function performUpdateSucceedsHavingOutdatedStorageReferences()
{
// form definition was renamed already
$this->createStorageFormDefinition('updated', false);
// but references not updated yet
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated'
);
// having an additional reference
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated'
);
$this->assertTrue(
$this->invokePerformUpdate()
);
$expectedFileIdentifier = $this->createStorageFileIdentifier(
'updated.form.yaml'
);
foreach ($this->retrieveAllFlexForms() as $flexForm) {
$this->assertSame(
$expectedFileIdentifier,
$flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF']
);
}
}
/**
* @test
*/
public function performUpdateSucceedsHavingOutdatedStorageReferencesWithFinisherOverrides(
) {
// form definition was renamed already
$this->createStorageFormDefinition('updated', false);
// but references not updated yet
$finisherOverrides = [
'FirstFinisher' => StringUtility::getUniqueId(),
'SecondFinisher' => StringUtility::getUniqueId(),
];
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
// having an additional reference
$this->createReference(
$this->createStorageFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
$this->assertTrue(
$this->invokePerformUpdate()
);
$expectedFileIdentifier = $this->createStorageFileIdentifier(
'updated.form.yaml'
);
$expectedSheetIdentifiers = $this->createFinisherOverridesSheetIdentifiers(
$expectedFileIdentifier,
'updated',
$finisherOverrides
);
foreach ($this->retrieveAllFlexForms() as $flexForm) {
$this->assertSame(
$expectedFileIdentifier,
$flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'] ?? null
);
foreach ($finisherOverrides as $finisherIdentifier => $finisherValue) {
$sheetIdentifier = $expectedSheetIdentifiers[$finisherIdentifier];
$propertyName = sprintf(
'settings.finishers.%s.value',
$finisherIdentifier
);
$this->assertSame(
$finisherValue,
$flexForm['data'][$sheetIdentifier]['lDEF'][$propertyName]['vDEF'] ?? null
);
}
}
}
/**
* @test
*/
public function performUpdateSucceedsHavingOutdatedExtensionReferences()
{
$this->setUpAllowedExtensionPaths();
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated'
);
// having an additional reference
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated'
);
$this->assertTrue(
$this->invokePerformUpdate()
);
$expectedFileIdentifier = $this->createExtensionFileIdentifier(
'updated.form.yaml'
);
foreach ($this->retrieveAllFlexForms() as $flexForm) {
$this->assertSame(
$expectedFileIdentifier,
$flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'] ?? null
);
}
}
/**
* @test
*/
public function performUpdateSucceedsHavingOutdatedExtensionReferencesWithFinisherOverrides(
) {
$this->setUpAllowedExtensionPaths();
$finisherOverrides = [
'FirstFinisher' => StringUtility::getUniqueId(),
'SecondFinisher' => StringUtility::getUniqueId(),
];
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
// having an additional reference
$this->createReference(
$this->createExtensionFileIdentifier('updated.yaml'),
'updated',
$finisherOverrides
);
$this->assertTrue(
$this->invokePerformUpdate()
);
}
/*
* --- HELPER FUNCTIONS ---
*/
/**
* @param string $name
* @param bool $legacy
*/
private function createStorageFormDefinition(
string $name,
bool $legacy = false
) {
$content = implode(LF, [
'type: Form',
'identifier: ' . $name,
'prototypeName: standard'
]);
$fileName = $name . '.' . ($legacy ? 'yaml' : 'form.yaml');
$fileIdentifier = $this->createStorageFileIdentifier($fileName);
if (!$legacy) {
$this->slot->allowInvocation(
FilePersistenceSlot::COMMAND_FILE_CREATE,
$fileIdentifier
);
$this->slot->allowInvocation(
FilePersistenceSlot::COMMAND_FILE_SET_CONTENTS,
$fileIdentifier,
$this->slot->getContentSignature($content)
);
}
$this->storageFolder->createFile($fileName)->setContents($content);