[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Tests / Unit / Form / FormDataProvider / TcaRecordTitleTest.php
1 <?php
2 namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Prophecy\Argument;
18 use Prophecy\Prophecy\ObjectProphecy;
19 use TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle;
20 use TYPO3\CMS\Lang\LanguageService;
21
22 /**
23 * Test case
24 */
25 class TcaRecordTitleTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitTestCase
26 {
27 /**
28 * @var TcaRecordTitle
29 */
30 protected $subject;
31
32 /**
33 * @var string
34 */
35 protected $timeZone;
36
37 public function setUp()
38 {
39 $this->subject = new TcaRecordTitle();
40 $this->timeZone = date_default_timezone_get();
41 date_default_timezone_set('UTC');
42 }
43
44 protected function tearDown()
45 {
46 date_default_timezone_set($this->timeZone);
47 }
48
49 /**
50 * @test
51 */
52 public function addDataThrowsExceptionWithMissingLabel()
53 {
54 $input = [
55 'tableName' => 'aTable',
56 'databaseRew' => [],
57 'processedTca' => [
58 'ctrl' => [],
59 ],
60 ];
61 $this->expectException(\UnexpectedValueException::class);
62 $this->expectExceptionCode(1443706103);
63 $this->subject->addData($input);
64 }
65
66 /**
67 * @test
68 */
69 public function addDataReturnsRecordTitleForLabelUserFunction()
70 {
71 $input = [
72 'tableName' => 'aTable',
73 'databaseRow' => [],
74 'processedTca' => [
75 'ctrl' => [
76 'label' => 'uid',
77 'label_userFunc' => function (&$parameters) {
78 $parameters['title'] = 'Test';
79 }
80 ],
81 'columns' => [],
82 ],
83 ];
84
85 $expected = $input;
86 $expected['recordTitle'] = 'Test';
87
88 $this->assertSame($expected, $this->subject->addData($input));
89 }
90
91 /**
92 * @test
93 */
94 public function addDataReturnsRecordTitleForFormattedLabelUserFunction()
95 {
96 $input = [
97 'tableName' => 'aTable',
98 'databaseRow' => [],
99 'isInlineChild' => true,
100 'processedTca' => [
101 'ctrl' => [
102 'label' => 'uid',
103 'formattedLabel_userFunc' => function (&$parameters) {
104 $parameters['title'] = 'Test';
105 }
106 ],
107 'columns' => [],
108 ],
109 ];
110
111 $expected = $input;
112 $expected['recordTitle'] = 'Test';
113
114 $this->assertSame($expected, $this->subject->addData($input));
115 }
116
117 /**
118 * @test
119 */
120 public function addDataReturnsRecordTitleForInlineChildWithForeignLabel()
121 {
122 $input = [
123 'tableName' => 'aTable',
124 'databaseRow' => [
125 'aField' => 'aValue',
126 ],
127 'processedTca' => [
128 'ctrl' => [
129 'label' => 'foo',
130 'label_userFunc' => function (&$parameters) {
131 $parameters['title'] = 'Value that MUST NOT be used, otherwise the code is broken.';
132 }
133 ],
134 'columns' => [
135 'aField' => [
136 'config' => [
137 'type' => 'input',
138 ],
139 ],
140 ],
141 ],
142 'isInlineChild' => true,
143 'inlineParentConfig' => [
144 'foreign_label' => 'aField',
145 ],
146 ];
147 $expected = $input;
148 $expected['recordTitle'] = 'aValue';
149 $this->assertSame($expected, $this->subject->addData($input));
150 }
151
152 /**
153 * @test
154 */
155 public function addDataOverridesRecordTitleWithFormattedLabelUserFuncForInlineChildWithForeignLabel()
156 {
157 $input = [
158 'tableName' => 'aTable',
159 'databaseRow' => [
160 'aField' => 'aValue',
161 ],
162 'processedTca' => [
163 'ctrl' => [
164 'label' => 'foo',
165 'formattedLabel_userFunc' => function (&$parameters) {
166 $parameters['title'] = 'aFormattedLabel';
167 },
168 ],
169 'columns' => [
170 'aField' => [
171 'config' => [
172 'type' => 'input',
173 ],
174 ],
175 ],
176 ],
177 'isInlineChild' => true,
178 'inlineParentConfig' => [
179 'foreign_label' => 'aField',
180 ],
181 ];
182 $expected = $input;
183 $expected['recordTitle'] = 'aFormattedLabel';
184 $this->assertSame($expected, $this->subject->addData($input));
185 }
186
187 /**
188 * @test
189 */
190 public function addDataReturnsRecordTitleForInlineChildWithSymmetricLabel()
191 {
192 $input = [
193 'tableName' => 'aTable',
194 'databaseRow' => [
195 'aField' => 'aValue',
196 ],
197 'processedTca' => [
198 'ctrl' => [
199 'label' => 'foo',
200 ],
201 'columns' => [
202 'aField' => [
203 'config' => [
204 'type' => 'input',
205 ],
206 ],
207 ],
208 ],
209 'isInlineChild' => true,
210 'inlineParentConfig' => [
211 'symmetric_label' => 'aField',
212 ],
213 'isOnSymmetricSide' => true,
214 ];
215 $expected = $input;
216 $expected['recordTitle'] = 'aValue';
217 $this->assertSame($expected, $this->subject->addData($input));
218 }
219
220 /**
221 * @test
222 */
223 public function addDataReturnsRecordTitleForUid()
224 {
225 $input = [
226 'tableName' => 'aTable',
227 'databaseRow' => [
228 'uid' => 'NEW56017ee37d10e587251374',
229 ],
230 'processedTca' => [
231 'ctrl' => [
232 'label' => 'uid'
233 ],
234 'columns' => [],
235 ]
236 ];
237
238 /** @var LanguageService|ObjectProphecy $languageService */
239 $languageService = $this->prophesize(LanguageService::class);
240 $GLOBALS['LANG'] = $languageService->reveal();
241 $languageService->sL(Argument::cetera())->willReturnArgument(0);
242
243 $expected = $input;
244 $expected['recordTitle'] = 'NEW56017ee37d10e587251374';
245 $this->assertSame($expected, $this->subject->addData($input));
246 }
247
248 /**
249 * Data provider for addDataReturnsRecordTitleForInputType
250 * Each data set is an array with the following elements:
251 * - TCA field ['config'] section
252 * - Database value for field
253 * - expected title to be generated
254 *
255 * @returns array
256 */
257 public function addDataReturnsRecordTitleForInputTypeDataProvider()
258 {
259 return [
260 'new record' => [
261 [
262 'type' => 'input',
263 ],
264 '',
265 '',
266 ],
267 'plain text input' => [
268 [
269 'type' => 'input',
270 ],
271 'aValue',
272 'aValue',
273 ],
274 'date input' => [
275 [
276 'type' => 'input',
277 'eval' => 'date'
278 ],
279 '978307261',
280 '01-01-01 (-7 days)',
281 ],
282 'date input (dbType: date)' => [
283 [
284 'type' => 'input',
285 'eval' => 'date',
286 'dbType' => 'date'
287 ],
288 '2001-01-01',
289 '01-01-01 (-7 days)',
290 ],
291 'date input (disableAgeDisplay: TRUE)' => [
292 [
293 'type' => 'input',
294 'eval' => 'date',
295 'disableAgeDisplay' => true
296 ],
297 '978307261',
298 '01-01-01',
299 ],
300 'time input' => [
301 [
302 'type' => 'input',
303 'eval' => 'time',
304 ],
305 '44100',
306 '12:15',
307 ],
308 'timesec input' => [
309 [
310 'type' => 'input',
311 'eval' => 'timesec',
312 ],
313 '44130',
314 '12:15:30',
315 ],
316 'datetime input' => [
317 [
318 'type' => 'input',
319 'eval' => 'datetime',
320 'dbType' => 'date'
321 ],
322 '978307261',
323 '01-01-01 00:01',
324 ],
325 'datetime input (dbType: datetime)' => [
326 [
327 'type' => 'input',
328 'eval' => 'datetime',
329 'dbType' => 'datetime'
330 ],
331 '2014-12-31 23:59:59',
332 '31-12-14 23:59',
333 ],
334 ];
335 }
336
337 /**
338 * @test
339 * @dataProvider addDataReturnsRecordTitleForInputTypeDataProvider
340 *
341 * @param array $fieldConfig
342 * @param string $fieldValue
343 * @param string $expectedTitle
344 */
345 public function addDataReturnsRecordTitleForInputType($fieldConfig, $fieldValue, $expectedTitle)
346 {
347 $input = [
348 'tableName' => 'aTable',
349 'databaseRow' => [
350 'uid' => '1',
351 'aField' => $fieldValue,
352 ],
353 'processedTca' => [
354 'ctrl' => [
355 'label' => 'aField'
356 ],
357 'columns' => [
358 'aField' => [
359 'config' => $fieldConfig,
360 ]
361 ],
362 ]
363 ];
364
365 /** @var LanguageService|ObjectProphecy $languageService */
366 $languageService = $this->prophesize(LanguageService::class);
367 $GLOBALS['LANG'] = $languageService->reveal();
368 $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
369 ->willReturn(' min| hrs| days| yrs| min| hour| day| year');
370 $languageService->sL(Argument::cetera())->willReturnArgument(0);
371 $GLOBALS['EXEC_TIME'] = 978912061;
372
373 $expected = $input;
374 $expected['recordTitle'] = $expectedTitle;
375 $this->assertSame($expected, $this->subject->addData($input));
376 }
377
378 /**
379 * @test
380 */
381 public function addDataReturnsRecordTitleWithAlternativeLabel()
382 {
383 $input = [
384 'tableName' => 'aTable',
385 'databaseRow' => [
386 'uid' => '1',
387 'aField' => '',
388 'anotherField' => 'anotherValue',
389 ],
390 'processedTca' => [
391 'ctrl' => [
392 'label' => 'aField',
393 'label_alt' => 'anotherField',
394 ],
395 'columns' => [
396 'aField' => [
397 'config' => [
398 'type' => 'input'
399 ]
400 ],
401 'anotherField' => [
402 'config' => [
403 'type' => 'input'
404 ]
405 ]
406 ],
407 ]
408 ];
409
410 $expected = $input;
411 $expected['recordTitle'] = 'anotherValue';
412 $this->assertSame($expected, $this->subject->addData($input));
413 }
414
415 /**
416 * @test
417 */
418 public function addDataReturnsRecordTitleWithMultipleAlternativeLabels()
419 {
420 $input = [
421 'tableName' => 'aTable',
422 'databaseRow' => [
423 'uid' => '1',
424 'aField' => '',
425 'anotherField' => '',
426 'additionalField' => 'additionalValue'
427 ],
428 'processedTca' => [
429 'ctrl' => [
430 'label' => 'aField',
431 'label_alt' => 'anotherField,additionalField',
432 ],
433 'columns' => [
434 'aField' => [
435 'config' => [
436 'type' => 'input'
437 ]
438 ],
439 'anotherField' => [
440 'config' => [
441 'type' => 'input'
442 ]
443 ],
444 'additionalField' => [
445 'config' => [
446 'type' => 'input'
447 ]
448 ],
449 ],
450 ]
451 ];
452
453 $expected = $input;
454 $expected['recordTitle'] = 'additionalValue';
455 $this->assertSame($expected, $this->subject->addData($input));
456 }
457
458 /**
459 * @test
460 */
461 public function addDataReturnsRecordTitleWithForcedAlternativeLabel()
462 {
463 $input = [
464 'tableName' => 'aTable',
465 'databaseRow' => [
466 'uid' => '1',
467 'aField' => 'aField',
468 'anotherField' => 'anotherField'
469 ],
470 'processedTca' => [
471 'ctrl' => [
472 'label' => 'aField',
473 'label_alt' => 'anotherField',
474 'label_alt_force' => true,
475 ],
476 'columns' => [
477 'aField' => [
478 'config' => [
479 'type' => 'input'
480 ]
481 ],
482 'anotherField' => [
483 'config' => [
484 'type' => 'input'
485 ]
486 ],
487 ],
488 ]
489 ];
490
491 $expected = $input;
492 $expected['recordTitle'] = 'aField, anotherField';
493 $this->assertSame($expected, $this->subject->addData($input));
494 }
495
496 /**
497 * @test
498 */
499 public function addDataReturnsRecordTitleWithMultipleForcedAlternativeLabels()
500 {
501 $input = [
502 'tableName' => 'aTable',
503 'databaseRow' => [
504 'uid' => '1',
505 'aField' => 'aField',
506 'anotherField' => 'anotherField',
507 'additionalField' => 'additionalValue'
508 ],
509 'processedTca' => [
510 'ctrl' => [
511 'label' => 'aField',
512 'label_alt' => 'anotherField,additionalField',
513 'label_alt_force' => true,
514 ],
515 'columns' => [
516 'aField' => [
517 'config' => [
518 'type' => 'input'
519 ]
520 ],
521 'anotherField' => [
522 'config' => [
523 'type' => 'input'
524 ]
525 ],
526 'additionalField' => [
527 'config' => [
528 'type' => 'input'
529 ]
530 ],
531 ],
532 ]
533 ];
534
535 $expected = $input;
536 $expected['recordTitle'] = 'aField, anotherField, additionalValue';
537 $this->assertSame($expected, $this->subject->addData($input));
538 }
539
540 /**
541 * @test
542 */
543 public function addDataReturnsRecordTitleIgnoresEmptyAlternativeLabels()
544 {
545 $input = [
546 'tableName' => 'aTable',
547 'databaseRow' => [
548 'uid' => '1',
549 'aField' => 'aField',
550 'anotherField' => '',
551 'additionalField' => 'additionalValue'
552 ],
553 'processedTca' => [
554 'ctrl' => [
555 'label' => 'aField',
556 'label_alt' => 'anotherField,additionalField',
557 'label_alt_force' => true,
558 ],
559 'columns' => [
560 'aField' => [
561 'config' => [
562 'type' => 'input'
563 ]
564 ],
565 'anotherField' => [
566 'config' => [
567 'type' => 'input'
568 ]
569 ],
570 'additionalField' => [
571 'config' => [
572 'type' => 'input'
573 ]
574 ],
575 ],
576 ]
577 ];
578
579 $expected = $input;
580 $expected['recordTitle'] = 'aField, additionalValue';
581 $this->assertSame($expected, $this->subject->addData($input));
582 }
583
584 /**
585 * @test
586 */
587 public function addDataReturnsRecordTitleForRadioType()
588 {
589 $input = [
590 'tableName' => 'aTable',
591 'databaseRow' => [
592 'uid' => '1',
593 'aField' => '2',
594 ],
595 'processedTca' => [
596 'ctrl' => [
597 'label' => 'aField'
598 ],
599 'columns' => [
600 'aField' => [
601 'config' => [
602 'type' => 'radio',
603 'items' => [
604 ['foo', 1],
605 ['bar', 2],
606 ['baz', 3],
607 ]
608 ]
609 ]
610 ],
611 ]
612 ];
613
614 $expected = $input;
615 $expected['recordTitle'] = 'bar';
616 $this->assertSame($expected, $this->subject->addData($input));
617 }
618
619 /**
620 * Data provider for addDataReturnsRecordTitleForGroupType
621 * Each data set is an array with the following elements:
622 * - TCA field configuration (merged with base config)
623 * - Database value for field
624 * - expected title to be generated
625 *
626 * @returns array
627 */
628 public function addDataReturnsRecordTitleForGroupTypeDataProvider()
629 {
630 return [
631 'new record' => [
632 [
633 'internal_type' => 'db',
634 ],
635 [],
636 ''
637 ],
638 'internal_type: file' => [
639 [
640 'internal_type' => 'file',
641 ],
642 [
643 [
644 'uidOrPath' => 'somePath/aFile.jpg',
645 ],
646 [
647 'uidOrPath' => 'someOtherPath/anotherFile.png',
648 ],
649 ],
650 'somePath/aFile.jpg, someOtherPath/anotherFile.png',
651 ],
652 'internal_type: db, single table, single record' => [
653 [
654 'internal_type' => 'db',
655 'allowed' => 'aTable'
656 ],
657 [
658 [
659 'title' => 'aValue',
660 ],
661 ],
662 'aValue',
663 ],
664 'internal_type: db, single table, multiple records' => [
665 [
666 'internal_type' => 'db',
667 'allowed' => 'aTable'
668 ],
669 [
670 [
671 'title' => 'aValue',
672 ],
673 [
674 'title' => 'anotherValue',
675 ],
676 ],
677 'aValue, anotherValue',
678 ],
679 'internal_type: db, multiple tables, single record' => [
680 [
681 'internal_type' => 'db',
682 'allowed' => 'aTable,anotherTable'
683 ],
684 [
685 [
686 'uid' => 1,
687 'table' => 'anotherTable',
688 'title' => 'anotherValue',
689 ],
690 ],
691 'anotherValue',
692 ],
693 'internal_type: db, multiple tables, multiple records' => [
694 [
695 'internal_type' => 'db',
696 'allowed' => 'aTable,anotherTable'
697 ],
698 [
699 [
700 'uid' => 1,
701 'table' => 'aTable',
702 'title' => 'aValue',
703 ],
704 [
705 'uid' => 2,
706 'table' => 'anotherTable',
707 'title' => 'anotherValue',
708 ],
709 ],
710 'aValue, anotherValue',
711 ],
712 ];
713 }
714
715 /**
716 * @test
717 * @dataProvider addDataReturnsRecordTitleForGroupTypeDataProvider
718 *
719 * @param array $fieldConfig
720 * @param string $fieldValue
721 * @param string $expectedTitle
722 */
723 public function addDataReturnsRecordTitleForGroupType($fieldConfig, $fieldValue, $expectedTitle)
724 {
725 $input = [
726 'tableName' => 'aTable',
727 'databaseRow' => [
728 'uid' => '1',
729 'aField' => $fieldValue,
730 ],
731 'processedTca' => [
732 'ctrl' => [
733 'label' => 'aField'
734 ],
735 'columns' => [
736 'aField' => [
737 'config' => array_merge(
738 [
739 'type' => 'group',
740 ],
741 $fieldConfig
742 ),
743 ]
744 ],
745 ]
746 ];
747
748 /** @var LanguageService|ObjectProphecy $languageService */
749 $languageService = $this->prophesize(LanguageService::class);
750 $GLOBALS['LANG'] = $languageService->reveal();
751 $languageService->sL(Argument::cetera())->willReturnArgument(0);
752
753 $expected = $input;
754 $expected['recordTitle'] = $expectedTitle;
755 $this->assertSame($expected, $this->subject->addData($input));
756 }
757
758 /**
759 * @test
760 */
761 public function addDataReturnsRecordTitleForGroupTypeWithInternalTypeDb()
762 {
763 $input = [
764 'tableName' => 'aTable',
765 'databaseRow' => [
766 'uid' => '1',
767 'aField' => [
768 [
769 'uid' => 1,
770 'table' => 'aTable',
771 'title' => 'aValue',
772 ],
773 [
774 'uid' => 2,
775 'table' => 'anotherTable',
776 'title' => 'anotherValue',
777 ],
778 ],
779 ],
780 'processedTca' => [
781 'ctrl' => [
782 'label' => 'aField'
783 ],
784 'columns' => [
785 'aField' => [
786 'config' => [
787 'type' => 'group',
788 'internal_type' => 'db',
789 'allowed' => 'aTable,anotherTable',
790 ]
791 ]
792 ],
793 ]
794 ];
795
796 $expected = $input;
797 $expected['recordTitle'] = 'aValue, anotherValue';
798 $this->assertSame($expected, $this->subject->addData($input));
799 }
800
801 /**
802 * @test
803 */
804 public function addDataReturnsRecordTitleForSingleCheckboxType()
805 {
806 $input = [
807 'tableName' => 'aTable',
808 'databaseRow' => [
809 'aField' => 1,
810 ],
811 'processedTca' => [
812 'ctrl' => [
813 'label' => 'aField'
814 ],
815 'columns' => [
816 'aField' => [
817 'config' => [
818 'type' => 'check',
819 ]
820 ]
821 ],
822 ]
823 ];
824
825 /** @var LanguageService|ObjectProphecy $languageService */
826 $languageService = $this->prophesize(LanguageService::class);
827 $GLOBALS['LANG'] = $languageService->reveal();
828 $languageService->sL(Argument::cetera())->willReturnArgument(0)->shouldBeCalled();
829
830 $expected = $input;
831 $expected['recordTitle'] = 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes';
832 $this->assertSame($expected, $this->subject->addData($input));
833 }
834
835 /**
836 * @test
837 */
838 public function addDataReturnsRecordTitleForArrayCheckboxType()
839 {
840 $input = [
841 'tableName' => 'aTable',
842 'databaseRow' => [
843 'aField' => '5'
844 ],
845 'processedTca' => [
846 'ctrl' => [
847 'label' => 'aField'
848 ],
849 'columns' => [
850 'aField' => [
851 'config' => [
852 'type' => 'check',
853 'items' => [
854 ['foo', ''],
855 ['bar', ''],
856 ['baz', ''],
857 ]
858 ]
859 ]
860 ],
861 ]
862 ];
863
864 $expected = $input;
865 $expected['recordTitle'] = 'foo, baz';
866 $this->assertSame($expected, $this->subject->addData($input));
867 }
868
869 /**
870 * @test
871 */
872 public function addDataReturnsEmptyRecordTitleForFlexType()
873 {
874 $input = [
875 'tableName' => 'aTable',
876 'databaseRow' => [
877 'aField' => [
878 'data' => [
879 'sDEF' => [
880 'lDEF' => [
881 'aFlexField' => [
882 'vDEF' => 'aFlexValue',
883 ]
884 ]
885 ]
886 ]
887 ]
888 ],
889 'processedTca' => [
890 'ctrl' => [
891 'label' => 'aField'
892 ],
893 'columns' => [
894 'aField' => [
895 'config' => [
896 'type' => 'flex',
897 'ds' => [
898 'sheets' => [
899 'sDEF' => [
900 'ROOT' => [
901 'type' => 'array',
902 'el' => [
903 'aFlexField' => [
904 'label' => 'Some input field',
905 'config' => [
906 'type' => 'input',
907 ],
908 ],
909 ],
910 ],
911 ],
912 ],
913 ]
914
915 ]
916 ]
917 ],
918 ]
919 ];
920
921 $expected = $input;
922 $expected['recordTitle'] = '';
923 $this->assertSame($expected, $this->subject->addData($input));
924 }
925
926 /**
927 * @test
928 */
929 public function addDataReturnsRecordTitleForSelectType()
930 {
931 $input = [
932 'tableName' => 'aTable',
933 'databaseRow' => [
934 'aField' => [
935 '1',
936 '2'
937 ]
938 ],
939 'processedTca' => [
940 'ctrl' => [
941 'label' => 'aField'
942 ],
943 'columns' => [
944 'aField' => [
945 'config' => [
946 'type' => 'select',
947 'items' => [
948 ['foo', 1, null, null],
949 ['bar', 2, null, null],
950 ['baz', 4, null, null],
951 ]
952 ]
953 ]
954 ],
955 ]
956 ];
957
958 $expected = $input;
959 $expected['recordTitle'] = 'foo, bar';
960 $this->assertSame($expected, $this->subject->addData($input));
961 }
962
963 /**
964 * @test
965 */
966 public function addDataReturnsStrippedAndTrimmedValueForTextType()
967 {
968 $input = [
969 'tableName' => 'aTable',
970 'databaseRow' => [
971 'aField' => '<p> text </p>',
972 ],
973 'processedTca' => [
974 'ctrl' => [
975 'label' => 'aField',
976 ],
977 'columns' => [
978 'aField' => [
979 'config' => [
980 'type' => 'text',
981 ],
982 ],
983 ],
984 ],
985 ];
986
987 $expected = $input;
988 $expected['recordTitle'] = 'text';
989 $this->assertSame($expected, $this->subject->addData($input));
990 }
991 }