[TASK] Migrate form upgrade wizard to new API
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Tests / Functional / Hooks / FormFileExtensionUpdateTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Tests\Functional\Hooks;
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 use Doctrine\DBAL\FetchMode;
19 use Symfony\Component\Console\Output\NullOutput;
20 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
21 use TYPO3\CMS\Core\Core\Bootstrap;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Database\ReferenceIndex;
24 use TYPO3\CMS\Core\Resource\Folder;
25 use TYPO3\CMS\Core\Resource\ResourceFactory;
26 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Utility\StringUtility;
29 use TYPO3\CMS\Form\Hooks\FormFileExtensionUpdate;
30 use TYPO3\CMS\Form\Slot\FilePersistenceSlot;
31 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
32
33 class FormFileExtensionUpdateTest extends FunctionalTestCase
34 {
35 /**
36 * @var string[]
37 */
38 protected $coreExtensionsToLoad = [
39 'form',
40 ];
41
42 /**
43 * @var string[]
44 */
45 protected $testExtensionsToLoad = [
46 'typo3/sysext/form/Tests/Functional/Hooks/Fixtures/test_resources',
47 ];
48
49 /**
50 * @var FormFileExtensionUpdate
51 */
52 private $subject;
53
54 /**
55 * @var FilePersistenceSlot
56 */
57 private $slot;
58
59 /**
60 * @var FlexFormTools
61 */
62 private $flexForm;
63
64 /**
65 * @var ReferenceIndex
66 */
67 private $referenceIndex;
68
69 /**
70 * @var Folder
71 */
72 private $storageFolder;
73
74 protected function setUp()
75 {
76 parent::setUp();
77
78 $this->setUpBackendUserFromFixture(1);
79 Bootstrap::initializeLanguageObject();
80
81 $folderIdentifier = 'form_definitions';
82 $storage = ResourceFactory::getInstance()->getStorageObject(1);
83
84 if ($storage->hasFolder($folderIdentifier)) {
85 $storage->getFolder($folderIdentifier)->delete(true);
86 }
87
88 $output = new NullOutput();
89 $this->subject = GeneralUtility::makeInstance(FormFileExtensionUpdate::class);
90 $this->subject->setOutput($output);
91 $this->slot = GeneralUtility::makeInstance(FilePersistenceSlot::class);
92 $this->flexForm = GeneralUtility::makeInstance(FlexFormTools::class);
93 $this->referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
94 $this->storageFolder = $storage->createFolder($folderIdentifier);
95 }
96
97 protected function tearDown()
98 {
99 $this->storageFolder->delete(true);
100 parent::tearDown();
101 }
102
103 /*
104 * --- CHECK FOR UPDATE ---
105 */
106
107 /**
108 * @return bool
109 */
110 private function invokeCheckForUpdate(): bool
111 {
112 return $this->subject->updateNecessary();
113 }
114
115 /**
116 * @test
117 */
118 public function updateIsNotRequiredHavingUpdatedFormDefinitions()
119 {
120 $this->createStorageFormDefinition('updated', false);
121 $this->assertFalse($this->invokeCheckForUpdate());
122 }
123
124 /**
125 * @test
126 */
127 public function updateIsRequiredHavingOutdatedStorageFormDefinitions()
128 {
129 $this->createStorageFormDefinition('legacy', true);
130 $this->assertTrue($this->invokeCheckForUpdate());
131 }
132
133 /**
134 * @test
135 */
136 public function updateIsNotRequiredHavingUpdatedStorageReferences()
137 {
138 $this->createStorageFormDefinition('updated', false);
139 $this->createReference(
140 $this->createStorageFileIdentifier('updated.form.yaml'),
141 'updated'
142 );
143 $this->assertFalse($this->invokeCheckForUpdate());
144 }
145
146 /**
147 * @test
148 */
149 public function updateIsNotRequiredHavingUpdatedStorageReferencesWithFinisherOverrides(
150 ) {
151 $this->createStorageFormDefinition('updated', false);
152 $finisherOverrides = [
153 'FirstFinisher' => StringUtility::getUniqueId(),
154 'SecondFinisher' => StringUtility::getUniqueId(),
155 ];
156 $this->createReference(
157 $this->createStorageFileIdentifier('updated.form.yaml'),
158 'updated',
159 $finisherOverrides
160 );
161 $this->assertFalse($this->invokeCheckForUpdate());
162 }
163
164 /**
165 * @test
166 */
167 public function updateIsRequiredHavingOutdatedStorageReferences()
168 {
169 // form definition was renamed already
170 $this->createStorageFormDefinition('updated', false);
171 // but references not updated yet
172 $this->createReference(
173 $this->createStorageFileIdentifier('updated.yaml'),
174 'updated'
175 );
176 $this->assertTrue($this->invokeCheckForUpdate());
177 }
178
179 /**
180 * @test
181 */
182 public function updateIsRequiredHavingOutdatedStorageReferencesWithFinisherOverrides(
183 ) {
184 // form definition was renamed already
185 $this->createStorageFormDefinition('updated', false);
186 // but references not updated yet
187 $finisherOverrides = [
188 'FirstFinisher' => StringUtility::getUniqueId(),
189 'SecondFinisher' => StringUtility::getUniqueId(),
190 ];
191 $this->createReference(
192 $this->createStorageFileIdentifier('updated.yaml'),
193 'updated',
194 $finisherOverrides
195 );
196 $this->assertTrue($this->invokeCheckForUpdate());
197 }
198
199 /**
200 * @test
201 */
202 public function updateIsNotRequiredHavingOutdatedExtensionFormDefinitions()
203 {
204 $this->setUpAllowedExtensionPaths();
205 $this->assertFalse($this->invokeCheckForUpdate());
206 }
207
208 /**
209 * @test
210 */
211 public function updateIsNotRequiredHavingUpdatedExtensionReferences()
212 {
213 $this->setUpAllowedExtensionPaths();
214 $this->createReference(
215 $this->createExtensionFileIdentifier('updated.form.yaml'),
216 'updated'
217 );
218 $this->assertFalse($this->invokeCheckForUpdate());
219 }
220
221 /**
222 * @test
223 */
224 public function updateIsRequiredHavingOutdatedExtensionReferences()
225 {
226 $this->setUpAllowedExtensionPaths();
227 $this->createReference(
228 $this->createExtensionFileIdentifier('updated.yaml'),
229 'updated'
230 );
231 $this->assertTrue($this->invokeCheckForUpdate());
232 }
233
234 /**
235 * @test
236 */
237 public function updateIsRequiredHavingOutdatedExtensionReferencesWithFinisherOverrides(
238 ) {
239 $this->setUpAllowedExtensionPaths();
240 $finisherOverrides = [
241 'FirstFinisher' => StringUtility::getUniqueId(),
242 'SecondFinisher' => StringUtility::getUniqueId(),
243 ];
244 $this->createReference(
245 $this->createExtensionFileIdentifier('updated.yaml'),
246 'updated',
247 $finisherOverrides
248 );
249 $this->assertTrue($this->invokeCheckForUpdate());
250 }
251
252 /*
253 * --- PERFORM UPDATE ---
254 */
255
256 private function invokePerformUpdate(): bool
257 {
258 return $this->subject->executeUpdate();
259 }
260
261 /**
262 * @test
263 */
264 public function performUpdateSucceedsHavingOutdatedStorageFormDefinitions()
265 {
266 $this->createStorageFormDefinition('legacy', true);
267 $this->assertTrue(
268 $this->invokePerformUpdate()
269 );
270 $this->assertTrue(
271 $this->storageFolder->hasFile('legacy.form.yaml')
272 );
273 }
274
275 /**
276 * @test
277 */
278 public function performUpdateSucceedsHavingOutdatedStorageReferences()
279 {
280 // form definition was renamed already
281 $this->createStorageFormDefinition('updated', false);
282 // but references not updated yet
283 $this->createReference(
284 $this->createStorageFileIdentifier('updated.yaml'),
285 'updated'
286 );
287 // having an additional reference
288 $this->createReference(
289 $this->createStorageFileIdentifier('updated.yaml'),
290 'updated'
291 );
292 $this->assertTrue(
293 $this->invokePerformUpdate()
294 );
295 $expectedFileIdentifier = $this->createStorageFileIdentifier(
296 'updated.form.yaml'
297 );
298 foreach ($this->retrieveAllFlexForms() as $flexForm) {
299 $this->assertSame(
300 $expectedFileIdentifier,
301 $flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF']
302 );
303 }
304 }
305
306 /**
307 * @test
308 */
309 public function performUpdateSucceedsHavingOutdatedStorageReferencesWithFinisherOverrides(
310 ) {
311 // form definition was renamed already
312 $this->createStorageFormDefinition('updated', false);
313 // but references not updated yet
314 $finisherOverrides = [
315 'FirstFinisher' => StringUtility::getUniqueId(),
316 'SecondFinisher' => StringUtility::getUniqueId(),
317 ];
318 $this->createReference(
319 $this->createStorageFileIdentifier('updated.yaml'),
320 'updated',
321 $finisherOverrides
322 );
323 // having an additional reference
324 $this->createReference(
325 $this->createStorageFileIdentifier('updated.yaml'),
326 'updated',
327 $finisherOverrides
328 );
329 $this->assertTrue(
330 $this->invokePerformUpdate()
331 );
332 $expectedFileIdentifier = $this->createStorageFileIdentifier(
333 'updated.form.yaml'
334 );
335 $expectedSheetIdentifiers = $this->createFinisherOverridesSheetIdentifiers(
336 $expectedFileIdentifier,
337 'updated',
338 $finisherOverrides
339 );
340 foreach ($this->retrieveAllFlexForms() as $flexForm) {
341 $this->assertSame(
342 $expectedFileIdentifier,
343 $flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'] ?? null
344 );
345 foreach ($finisherOverrides as $finisherIdentifier => $finisherValue) {
346 $sheetIdentifier = $expectedSheetIdentifiers[$finisherIdentifier];
347 $propertyName = sprintf(
348 'settings.finishers.%s.value',
349 $finisherIdentifier
350 );
351 $this->assertSame(
352 $finisherValue,
353 $flexForm['data'][$sheetIdentifier]['lDEF'][$propertyName]['vDEF'] ?? null
354 );
355 }
356 }
357 }
358
359 /**
360 * @test
361 */
362 public function performUpdateSucceedsHavingOutdatedExtensionReferences()
363 {
364 $this->setUpAllowedExtensionPaths();
365 $this->createReference(
366 $this->createExtensionFileIdentifier('updated.yaml'),
367 'updated'
368 );
369 // having an additional reference
370 $this->createReference(
371 $this->createExtensionFileIdentifier('updated.yaml'),
372 'updated'
373 );
374 $this->assertTrue(
375 $this->invokePerformUpdate()
376 );
377 $expectedFileIdentifier = $this->createExtensionFileIdentifier(
378 'updated.form.yaml'
379 );
380 foreach ($this->retrieveAllFlexForms() as $flexForm) {
381 $this->assertSame(
382 $expectedFileIdentifier,
383 $flexForm['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'] ?? null
384 );
385 }
386 }
387
388 /**
389 * @test
390 */
391 public function performUpdateSucceedsHavingOutdatedExtensionReferencesWithFinisherOverrides(
392 ) {
393 $this->setUpAllowedExtensionPaths();
394 $finisherOverrides = [
395 'FirstFinisher' => StringUtility::getUniqueId(),
396 'SecondFinisher' => StringUtility::getUniqueId(),
397 ];
398 $this->createReference(
399 $this->createExtensionFileIdentifier('updated.yaml'),
400 'updated',
401 $finisherOverrides
402 );
403 // having an additional reference
404 $this->createReference(
405 $this->createExtensionFileIdentifier('updated.yaml'),
406 'updated',
407 $finisherOverrides
408 );
409 $this->assertTrue(
410 $this->invokePerformUpdate()
411 );
412 }
413
414 /*
415 * --- HELPER FUNCTIONS ---
416 */
417
418 /**
419 * @param string $name
420 * @param bool $legacy
421 */
422 private function createStorageFormDefinition(
423 string $name,
424 bool $legacy = false
425 ) {
426 $content = implode(LF, [
427 'type: Form',
428 'identifier: ' . $name,
429 'prototypeName: standard'
430 ]);
431
432 $fileName = $name . '.' . ($legacy ? 'yaml' : 'form.yaml');
433 $fileIdentifier = $this->createStorageFileIdentifier($fileName);
434
435 if (!$legacy) {
436 $this->slot->allowInvocation(
437 FilePersistenceSlot::COMMAND_FILE_CREATE,
438 $fileIdentifier
439 );
440 $this->slot->allowInvocation(
441 FilePersistenceSlot::COMMAND_FILE_SET_CONTENTS,
442 $fileIdentifier,
443 $this->slot->getContentSignature($content)
444 );
445 }
446
447 $this->storageFolder->createFile($fileName)->setContents($content);
448 }
449
450 /**
451 * @param string $fileIdentifier
452 * @param string $formIdentifier
453 * @param array $finisherOverrides
454 */
455 private function createReference(
456 string $fileIdentifier,
457 string $formIdentifier,
458 array $finisherOverrides = []
459 ) {
460 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
461 $connection = $connectionPool->getConnectionForTable('tt_content');
462
463 $flexForm = [
464 'data' => [
465 'sDEF' => [
466 'lDEF' => [
467 'settings.persistenceIdentifier' => [
468 'vDEF' => $fileIdentifier,
469 ],
470 'settings.overrideFinishers' => [
471 'vDEF' => empty($finisherOverrides) ? '0' : '1',
472 ],
473 ]
474 ]
475 ]
476 ];
477
478 $sheetIdentifiers = $this->createFinisherOverridesSheetIdentifiers(
479 $fileIdentifier,
480 $formIdentifier,
481 $finisherOverrides
482 );
483 foreach ($finisherOverrides as $finisherIdentifier => $finisherValue) {
484 $sheetIdentifier = $sheetIdentifiers[$finisherIdentifier];
485 $propertyName = sprintf(
486 'settings.finishers.%s.value',
487 $finisherIdentifier
488 );
489 $flexForm['data'][$sheetIdentifier]['lDEF'] = [
490 $propertyName => [
491 'vDEF' => $finisherValue
492 ],
493 ];
494 }
495
496 $values = [
497 'pid' => 1,
498 'header' => sprintf(
499 'Form Content Element for "%s"',
500 $formIdentifier
501 ),
502 'CType' => 'form_formframework',
503 'pi_flexform' => $this->flexForm
504 ->flexArray2Xml($flexForm, true)
505 ];
506
507 $connection->insert('tt_content', $values);
508 $id = $connection->lastInsertId('tt_content');
509 $this->referenceIndex->updateRefIndexTable('tt_content', $id);
510 }
511
512 /**
513 * Sets up additional paths to allow using form definitions from extension.
514 */
515 private function setUpAllowedExtensionPaths()
516 {
517 ExtensionManagementUtility::addTypoScriptSetup(trim('
518 module.tx_form.settings.yamlConfigurations {
519 110 = EXT:test_resources/Configuration/Yaml/AllowedExtensionPaths.yaml
520 }
521 plugin.tx_form.settings.yamlConfigurations {
522 110 = EXT:test_resources/Configuration/Yaml/AllowedExtensionPaths.yaml
523 }
524 '));
525 }
526
527 /**
528 * @return array
529 */
530 private function retrieveAllFlexForms(): array
531 {
532 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
533 $connection = $connectionPool->getConnectionForTable('tt_content');
534
535 return array_map(
536 function (array $record) {
537 return GeneralUtility::xml2array($record['pi_flexform']);
538 },
539 $connection->select(['pi_flexform'], 'tt_content')
540 ->fetchAll(FetchMode::ASSOCIATIVE)
541 );
542 }
543
544 /**
545 * @param string $fileIdentifier
546 * @param string $formIdentifier
547 * @param array $finisherOverrides
548 * @return array
549 */
550 private function createFinisherOverridesSheetIdentifiers(
551 string $fileIdentifier,
552 string $formIdentifier,
553 array $finisherOverrides
554 ): array {
555 $sheetIdentifiers = [];
556 foreach (array_keys($finisherOverrides) as $finisherIdentifier) {
557 $sheetIdentifiers[$finisherIdentifier] = md5(
558 $fileIdentifier
559 . 'standard'
560 . $formIdentifier
561 . $finisherIdentifier
562 );
563 }
564 return $sheetIdentifiers;
565 }
566
567 /**
568 * @param string $fileName
569 * @return string
570 */
571 private function createStorageFileIdentifier(string $fileName): string
572 {
573 return $this->storageFolder->getCombinedIdentifier() . $fileName;
574 }
575
576 /**
577 * @param string $fileName
578 * @return string
579 */
580 private function createExtensionFileIdentifier(string $fileName): string
581 {
582 return 'EXT:test_resources/Configuration/Form/' . $fileName;
583 }
584 }