[BUGFIX] FormEngine: Fix broken invalid value handling for selects
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Tests / Unit / Form / FormDataProvider / TcaSelectItemsTest.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\Configuration\TranslationConfigurationProvider;
20 use TYPO3\CMS\Backend\Module\ModuleLoader;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22 use TYPO3\CMS\Core\Database\DatabaseConnection;
23 use TYPO3\CMS\Core\Database\RelationHandler;
24 use TYPO3\CMS\Core\Tests\UnitTestCase;
25 use TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems;
26 use TYPO3\CMS\Core\Utility\ArrayUtility;
27 use TYPO3\CMS\Lang\LanguageService;
28 use TYPO3\CMS\Core\Messaging\FlashMessage;
29 use TYPO3\CMS\Core\Messaging\FlashMessageService;
30 use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32
33 /**
34 * Test case
35 */
36 class TcaSelectItemsTest extends UnitTestCase
37 {
38 /**
39 * @var TcaSelectItems
40 */
41 protected $subject;
42
43 /**
44 * @var array A backup of registered singleton instances
45 */
46 protected $singletonInstances = [];
47
48 protected function setUp()
49 {
50 $this->singletonInstances = GeneralUtility::getSingletonInstances();
51 $this->subject = new TcaSelectItems();
52 }
53
54 protected function tearDown()
55 {
56 GeneralUtility::purgeInstances();
57 GeneralUtility::resetSingletonInstances($this->singletonInstances);
58 parent::tearDown();
59 }
60
61 /**
62 * @test
63 */
64 public function addDataKeepExistingItems()
65 {
66 $input = [
67 'processedTca' => [
68 'columns' => [
69 'aField' => [
70 'config' => [
71 'type' => 'radio',
72 'items' => [
73 0 => [
74 'foo',
75 'bar',
76 ],
77 ],
78 ],
79 ],
80 'anotherField' => [
81 'config' => [
82 'type' => 'group',
83 'items' => [
84 0 => [
85 'foo',
86 'bar',
87 ],
88 ],
89 ],
90 ],
91 ],
92 ],
93 ];
94 $languageService = $this->prophesize(LanguageService::class);
95 $GLOBALS['LANG'] = $languageService->reveal();
96 $languageService->sL(Argument::cetera())->willReturnArgument(0);
97
98 $expected = $input;
99 $this->assertSame($expected, $this->subject->addData($input));
100 }
101
102 /**
103 * @test
104 */
105 public function addDataThrowsExceptionIfAnItemIsNotAnArray()
106 {
107 $input = [
108 'processedTca' => [
109 'columns' => [
110 'aField' => [
111 'config' => [
112 'type' => 'select',
113 'renderType' => 'selectSingle',
114 'items' => [
115 0 => 'foo',
116 ],
117 ],
118 ],
119 ],
120 ],
121 ];
122
123 $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1439288036);
124
125 $this->subject->addData($input);
126 }
127
128 /**
129 * @test
130 */
131 public function addDataTranslatesItemLabels()
132 {
133 $input = [
134 'databaseRow' => [
135 'aField' => 'aValue',
136 ],
137 'processedTca' => [
138 'columns' => [
139 'aField' => [
140 'config' => [
141 'type' => 'select',
142 'renderType' => 'selectSingle',
143 'items' => [
144 0 => [
145 0 => 'aLabel',
146 1 => 'aValue',
147 ],
148 ],
149 'maxitems' => 1
150 ],
151 ],
152 ],
153 ],
154 ];
155
156 /** @var LanguageService|ObjectProphecy $languageService */
157 $languageService = $this->prophesize(LanguageService::class);
158 $GLOBALS['LANG'] = $languageService->reveal();
159 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
160
161 $languageService->sL('aLabel')->shouldBeCalled()->willReturn('translated');
162
163 $expected = $input;
164 $expected['processedTca']['columns']['aField']['config']['items'][0][0] = 'translated';
165 $expected['processedTca']['columns']['aField']['config']['items'][0][2] = null;
166 $expected['processedTca']['columns']['aField']['config']['items'][0][3] = null;
167
168 $expected['databaseRow']['aField'] = ['aValue'];
169
170 $this->assertSame($expected, $this->subject->addData($input));
171 }
172
173 /**
174 * @test
175 */
176 public function addDataKeepsIconFromItem()
177 {
178 $input = [
179 'databaseRow' => [
180 'aField' => 'aValue',
181 ],
182 'processedTca' => [
183 'columns' => [
184 'aField' => [
185 'config' => [
186 'type' => 'select',
187 'renderType' => 'selectSingle',
188 'items' => [
189 0 => [
190 0 => 'aLabel',
191 1 => 'aValue',
192 2 => 'an-icon-reference',
193 3 => null,
194 ],
195 ],
196 'maxitems' => 1,
197 ],
198 ],
199 ],
200 ],
201 ];
202
203 /** @var LanguageService|ObjectProphecy $languageService */
204 $languageService = $this->prophesize(LanguageService::class);
205 $GLOBALS['LANG'] = $languageService->reveal();
206 $languageService->sL(Argument::cetera())->willReturnArgument(0);
207
208 $expected = $input;
209 $expected['databaseRow']['aField'] = ['aValue'];
210
211 $this->assertSame($expected, $this->subject->addData($input));
212 }
213
214 /**
215 * @test
216 */
217 public function addDataThrowsExceptionWithUnknownSpecialValue()
218 {
219 $input = [
220 'tableName' => 'aTable',
221 'processedTca' => [
222 'columns' => [
223 'aField' => [
224 'config' => [
225 'type' => 'select',
226 'renderType' => 'selectSingle',
227 'special' => 'anUnknownValue',
228 ],
229 ],
230 ],
231 ],
232 ];
233
234 $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1439298496);
235
236 $this->subject->addData($input);
237 }
238
239 /**
240 * @test
241 */
242 public function addDataAddsTablesWithSpecialTables()
243 {
244 $input = [
245 'databaseRow' => [
246 'aField' => '',
247 ],
248 'tableName' => 'aTable',
249 'processedTca' => [
250 'columns' => [
251 'aField' => [
252 'config' => [
253 'type' => 'select',
254 'renderType' => 'selectSingle',
255 'special' => 'tables',
256 'maxitems' => 1,
257 ],
258 ],
259 ],
260 ],
261 ];
262 $GLOBALS['TCA'] = [
263 'notInResult' => [
264 'ctrl' => [
265 'adminOnly' => true,
266 ],
267 ],
268 'aTable' => [
269 'ctrl' => [
270 'title' => 'aTitle',
271 ],
272 ],
273 ];
274 $GLOBALS['TCA_DESCR']['aTable']['columns']['']['description'] = 'aDescription';
275
276 /** @var LanguageService|ObjectProphecy $languageService */
277 $languageService = $this->prophesize(LanguageService::class);
278 $GLOBALS['LANG'] = $languageService->reveal();
279 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
280 $languageService->sL(Argument::containingString('INVALID VALUE'))->willReturnArgument(0);
281
282 $languageService->sL('aTitle')->shouldBeCalled()->willReturnArgument(0);
283 $languageService->loadSingleTableDescription('aTable')->shouldBeCalled();
284
285 $expected = $input;
286 $expected['databaseRow']['aField'] = [];
287 $expected['processedTca']['columns']['aField']['config']['items'] = [
288 0 => [
289 0 => 'aTitle',
290 1 => 'aTable',
291 2 => 'default-not-found',
292 3 => [
293 'description' => 'aDescription',
294 ],
295 ]
296 ];
297
298 $this->assertSame($expected, $this->subject->addData($input));
299 }
300
301 /**
302 * @test
303 */
304 public function addDataAddsTablesWithSpecialPageTypes()
305 {
306 $input = [
307 'databaseRow' => [
308 'aField' => 'aValue',
309 ],
310 'tableName' => 'aTable',
311 'processedTca' => [
312 'columns' => [
313 'aField' => [
314 'config' => [
315 'type' => 'select',
316 'renderType' => 'selectSingle',
317 'special' => 'pagetypes',
318 'items' => [],
319 'maxitems' => 1,
320 ],
321 ],
322 ],
323 ],
324 ];
325 $GLOBALS['TCA'] = [
326 'pages' => [
327 'columns' => [
328 'doktype' => [
329 'config' => [
330 'items' => [
331 0 => [
332 0 => 'aLabel',
333 1 => 'aValue',
334 ],
335 ],
336 ],
337 ],
338 ],
339 ],
340 ];
341
342 /** @var LanguageService|ObjectProphecy $languageService */
343 $languageService = $this->prophesize(LanguageService::class);
344 $GLOBALS['LANG'] = $languageService->reveal();
345 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
346
347 $languageService->sL('aLabel')->shouldBeCalled()->willReturnArgument(0);
348
349 $expected = $input;
350 $expected['databaseRow']['aField'] = ['aValue'];
351 $expected['processedTca']['columns']['aField']['config']['items'] = [
352 0 => [
353 0 => 'aLabel',
354 1 => 'aValue',
355 2 => 'default-not-found',
356 3 => null,
357 ]
358 ];
359
360 $this->assertSame($expected, $this->subject->addData($input));
361 }
362
363 /**
364 * Data provider
365 */
366 public function addDataAddsExcludeFieldsWithSpecialExcludeDataProvider()
367 {
368 return [
369 'Table with exclude and non exclude field returns exclude item' => [
370 [
371 // input tca
372 'fooTable' => [
373 'ctrl' => [
374 'title' => 'fooTableTitle',
375 ],
376 'columns' => [
377 'bar' => [
378 'label' => 'barColumnTitle',
379 'exclude' => 1
380 ],
381 'baz' => [
382 'label' => 'bazColumnTitle',
383 ],
384 ],
385 ],
386 ],
387 [
388 // expected items
389 0 => [
390 0 => 'fooTableTitle',
391 1 => '--div--',
392 2 => 'default-not-found',
393 3 => null,
394 ],
395 1 => [
396 0 => 'barColumnTitle (bar)',
397 1 => 'fooTable:bar',
398 2 => 'empty-empty',
399 3 => null,
400 ],
401 ],
402 ],
403 'Root level table with ignored root level restriction returns exclude item' => [
404 [
405 // input tca
406 'fooTable' => [
407 'ctrl' => [
408 'title' => 'fooTableTitle',
409 'rootLevel' => true,
410 'security' => [
411 'ignoreRootLevelRestriction' => true,
412 ],
413 ],
414 'columns' => [
415 'bar' => [
416 'label' => 'barColumnTitle',
417 'exclude' => 1,
418 ],
419 ],
420 ],
421 ],
422 [
423 // expected items
424 0 => [
425 0 => 'fooTableTitle',
426 1 => '--div--',
427 2 => 'default-not-found',
428 3 => null,
429 ],
430 1 => [
431 0 => 'barColumnTitle (bar)',
432 1 => 'fooTable:bar',
433 2 => 'empty-empty',
434 3 => null,
435 ],
436 ],
437 ],
438 'Root level table without ignored root level restriction returns no item' => [
439 [
440 // input tca
441 'fooTable' => [
442 'ctrl' => [
443 'title' => 'fooTableTitle',
444 'rootLevel' => true,
445 ],
446 'columns' => [
447 'bar' => [
448 'label' => 'barColumnTitle',
449 'exclude' => 1,
450 ],
451 ],
452 ],
453 ],
454 [
455 // no items
456 ],
457 ],
458 'Admin table returns no item' => [
459 [
460 // input tca
461 'fooTable' => [
462 'ctrl' => [
463 'title' => 'fooTableTitle',
464 'adminOnly' => true,
465 ],
466 'columns' => [
467 'bar' => [
468 'label' => 'barColumnTitle',
469 'exclude' => 1,
470 ],
471 ],
472 ],
473 ],
474 [
475 // no items
476 ],
477 ],
478 ];
479 }
480
481 /**
482 * @test
483 * @dataProvider addDataAddsExcludeFieldsWithSpecialExcludeDataProvider
484 */
485 public function addDataAddsExcludeFieldsWithSpecialExclude($tca, $expectedItems)
486 {
487 $input = [
488 'tableName' => 'aTable',
489 'databaseRow' => [],
490 'processedTca' => [
491 'columns' => [
492 'aField' => [
493 'config' => [
494 'type' => 'select',
495 'renderType' => 'selectSingle',
496 'special' => 'exclude',
497 ],
498 ],
499 ],
500 ],
501 ];
502 $GLOBALS['TCA'] = $tca;
503
504 /** @var LanguageService|ObjectProphecy $languageService */
505 $languageService = $this->prophesize(LanguageService::class);
506 $GLOBALS['LANG'] = $languageService->reveal();
507 $languageService->loadSingleTableDescription(Argument::cetera())->willReturn(null);
508 $languageService->sL(Argument::cetera())->willReturnArgument(0);
509
510 $result = $this->subject->addData($input);
511
512 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
513 }
514
515 /**
516 * @test
517 */
518 public function addDataAddsExcludeFieldsFromFlexWithSpecialExclude()
519 {
520 $input = [
521 'tableName' => 'aTable',
522 'databaseRow' => [],
523 'processedTca' => [
524 'columns' => [
525 'aField' => [
526 'config' => [
527 'type' => 'select',
528 'renderType' => 'selectSingle',
529 'special' => 'exclude',
530 ],
531 ],
532 ],
533 ],
534 ];
535
536 $GLOBALS['TCA'] = [
537 'fooTable' => [
538 'ctrl' => [
539 'title' => 'fooTableTitle',
540 ],
541 'columns' => [
542 'aFlexField' => [
543 'label' => 'aFlexFieldTitle',
544 'config' => [
545 'type' => 'flex',
546 'title' => 'title',
547 'ds' => [
548 'dummy' => '
549 <T3DataStructure>
550 <ROOT>
551 <type>array</type>
552 <el>
553 <input1>
554 <TCEforms>
555 <label>flexInputLabel</label>
556 <exclude>1</exclude>
557 <config>
558 <type>input</type>
559 <size>23</size>
560 </config>
561 </TCEforms>
562 </input1>
563 </el>
564 </ROOT>
565 </T3DataStructure>
566 ',
567 ],
568 ],
569 ],
570 ],
571 ],
572 ];
573
574 /** @var LanguageService|ObjectProphecy $languageService */
575 $languageService = $this->prophesize(LanguageService::class);
576 $GLOBALS['LANG'] = $languageService->reveal();
577 $languageService->loadSingleTableDescription(Argument::cetera())->willReturn(null);
578 $languageService->sL(Argument::cetera())->willReturnArgument(0);
579
580 // Needed to suppress a cache in xml2array
581 /** @var DatabaseConnection|ObjectProphecy $database */
582 $database = $this->prophesize(DatabaseConnection::class);
583 $GLOBALS['TYPO3_DB'] = $database->reveal();
584
585 $expectedItems = [
586 0 => [
587 0 => 'fooTableTitle',
588 1 => '--div--',
589 2 => 'default-not-found',
590 3 => null,
591 ],
592 1 => [
593 0 => '(input1)',
594 1 => 'fooTable:aFlexField;dummy;sDEF;input1',
595 2 => 'empty-empty',
596 3 => null,
597 ],
598 ];
599
600 $result = $this->subject->addData($input);
601
602 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
603 }
604
605 /**
606 * @test
607 */
608 public function addDataAddsExplicitAllowFieldsWithSpecialExplicitValues()
609 {
610 $input = [
611 'tableName' => 'aTable',
612 'databaseRow' => [],
613 'processedTca' => [
614 'columns' => [
615 'aField' => [
616 'config' => [
617 'type' => 'select',
618 'renderType' => 'selectSingle',
619 'special' => 'explicitValues',
620 ],
621 ],
622 ],
623 ],
624 ];
625
626 $GLOBALS['TCA'] = [
627 'fooTable' => [
628 'ctrl' => [
629 'title' => 'fooTableTitle',
630 ],
631 'columns' => [
632 'aField' => [
633 'label' => 'aFieldTitle',
634 'config' => [
635 'type' => 'select',
636 'renderType' => 'selectSingle',
637 'authMode' => 'explicitAllow',
638 'items' => [
639 0 => [
640 'anItemTitle',
641 'anItemValue',
642 ],
643 ]
644 ],
645 ],
646 ],
647 ],
648 ];
649
650 /** @var LanguageService|ObjectProphecy $languageService */
651 $languageService = $this->prophesize(LanguageService::class);
652 $GLOBALS['LANG'] = $languageService->reveal();
653 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.allow')->shouldBeCalled()->willReturn('allowMe');
654 $languageService->sL(Argument::cetera())->willReturnArgument(0);
655
656 $expectedItems = [
657 0 => [
658 0 => 'fooTableTitle: aFieldTitle',
659 1 => '--div--',
660 2 => null,
661 3 => null,
662 ],
663 1 => [
664 0 => '[allowMe] anItemTitle',
665 1 => 'fooTable:aField:anItemValue:ALLOW',
666 2 => 'status-status-permission-granted',
667 3 => null,
668 ],
669 ];
670
671 $result = $this->subject->addData($input);
672
673 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
674 }
675
676 /**
677 * @test
678 */
679 public function addDataAddsExplicitDenyFieldsWithSpecialExplicitValues()
680 {
681 $input = [
682 'tableName' => 'aTable',
683 'databaseRow' => [],
684 'processedTca' => [
685 'columns' => [
686 'aField' => [
687 'config' => [
688 'type' => 'select',
689 'renderType' => 'selectSingle',
690 'special' => 'explicitValues',
691 ],
692 ],
693 ],
694 ],
695 ];
696
697 $GLOBALS['TCA'] = [
698 'fooTable' => [
699 'ctrl' => [
700 'title' => 'fooTableTitle',
701 ],
702 'columns' => [
703 'aField' => [
704 'label' => 'aFieldTitle',
705 'config' => [
706 'type' => 'select',
707 'renderType' => 'selectSingle',
708 'authMode' => 'explicitDeny',
709 'items' => [
710 0 => [
711 'anItemTitle',
712 'anItemValue',
713 ],
714 ]
715 ],
716 ],
717 ],
718 ],
719 ];
720
721 /** @var LanguageService|ObjectProphecy $languageService */
722 $languageService = $this->prophesize(LanguageService::class);
723 $GLOBALS['LANG'] = $languageService->reveal();
724 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.deny')->shouldBeCalled()->willReturn('denyMe');
725 $languageService->sL(Argument::cetera())->willReturnArgument(0);
726
727 $expectedItems = [
728 0 => [
729 0 => 'fooTableTitle: aFieldTitle',
730 1 => '--div--',
731 2 => null,
732 3 => null,
733 ],
734 1 => [
735 0 => '[denyMe] anItemTitle',
736 1 => 'fooTable:aField:anItemValue:DENY',
737 2 => 'status-status-permission-denied',
738 3 => null,
739 ],
740 ];
741
742 $result = $this->subject->addData($input);
743
744 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
745 }
746
747 /**
748 * @test
749 */
750 public function addDataAddsExplicitIndividualAllowFieldsWithSpecialExplicitValues()
751 {
752 $input = [
753 'tableName' => 'aTable',
754 'databaseRow' => [],
755 'processedTca' => [
756 'columns' => [
757 'aField' => [
758 'config' => [
759 'type' => 'select',
760 'renderType' => 'selectSingle',
761 'special' => 'explicitValues',
762 ],
763 ],
764 ],
765 ],
766 ];
767
768 $GLOBALS['TCA'] = [
769 'fooTable' => [
770 'ctrl' => [
771 'title' => 'fooTableTitle',
772 ],
773 'columns' => [
774 'aField' => [
775 'label' => 'aFieldTitle',
776 'config' => [
777 'type' => 'select',
778 'renderType' => 'selectSingle',
779 'authMode' => 'individual',
780 'items' => [
781 0 => [
782 'aItemTitle',
783 'aItemValue',
784 null,
785 null,
786 'EXPL_ALLOW',
787 ],
788 // 1 is not selectable as allow and is always allowed
789 1 => [
790 'bItemTitle',
791 'bItemValue',
792 ],
793 2 => [
794 'cItemTitle',
795 'cItemValue',
796 null,
797 null,
798 'EXPL_ALLOW',
799 ],
800 ]
801 ],
802 ],
803 ],
804 ],
805 ];
806
807 /** @var LanguageService|ObjectProphecy $languageService */
808 $languageService = $this->prophesize(LanguageService::class);
809 $GLOBALS['LANG'] = $languageService->reveal();
810 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.allow')->shouldBeCalled()->willReturn('allowMe');
811 $languageService->sL(Argument::cetera())->willReturnArgument(0);
812
813 $expectedItems = [
814 0 => [
815 0 => 'fooTableTitle: aFieldTitle',
816 1 => '--div--',
817 2 => null,
818 3 => null,
819 ],
820 1 => [
821 0 => '[allowMe] aItemTitle',
822 1 => 'fooTable:aField:aItemValue:ALLOW',
823 2 => 'status-status-permission-granted',
824 3 => null,
825 ],
826 2 => [
827 0 => '[allowMe] cItemTitle',
828 1 => 'fooTable:aField:cItemValue:ALLOW',
829 2 => 'status-status-permission-granted',
830 3 => null,
831 ],
832 ];
833
834 $result = $this->subject->addData($input);
835
836 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
837 }
838
839 /**
840 * @test
841 */
842 public function addDataAddsExplicitIndividualDenyFieldsWithSpecialExplicitValues()
843 {
844 $input = [
845 'tableName' => 'aTable',
846 'databaseRow' => [],
847 'processedTca' => [
848 'columns' => [
849 'aField' => [
850 'config' => [
851 'type' => 'select',
852 'renderType' => 'selectSingle',
853 'special' => 'explicitValues',
854 ],
855 ],
856 ],
857 ],
858 ];
859
860 $GLOBALS['TCA'] = [
861 'fooTable' => [
862 'ctrl' => [
863 'title' => 'fooTableTitle',
864 ],
865 'columns' => [
866 'aField' => [
867 'label' => 'aFieldTitle',
868 'config' => [
869 'type' => 'select',
870 'renderType' => 'selectSingle',
871 'authMode' => 'individual',
872 'items' => [
873 0 => [
874 'aItemTitle',
875 'aItemValue',
876 null,
877 null,
878 'EXPL_DENY',
879 ],
880 // 1 is not selectable as allow and is always allowed
881 1 => [
882 'bItemTitle',
883 'bItemValue',
884 ],
885 2 => [
886 'cItemTitle',
887 'cItemValue',
888 null,
889 null,
890 'EXPL_DENY',
891 ],
892 ]
893 ],
894 ],
895 ],
896 ],
897 ];
898
899 /** @var LanguageService|ObjectProphecy $languageService */
900 $languageService = $this->prophesize(LanguageService::class);
901 $GLOBALS['LANG'] = $languageService->reveal();
902 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.deny')->shouldBeCalled()->willReturn('denyMe');
903 $languageService->sL(Argument::cetera())->willReturnArgument(0);
904
905 $expectedItems = [
906 0 => [
907 0 => 'fooTableTitle: aFieldTitle',
908 1 => '--div--',
909 2 => null,
910 3 => null,
911 ],
912 1 => [
913 0 => '[denyMe] aItemTitle',
914 1 => 'fooTable:aField:aItemValue:DENY',
915 2 => 'status-status-permission-denied',
916 3 => null,
917 ],
918 2 => [
919 0 => '[denyMe] cItemTitle',
920 1 => 'fooTable:aField:cItemValue:DENY',
921 2 => 'status-status-permission-denied',
922 3 => null,
923 ],
924 ];
925
926 $result = $this->subject->addData($input);
927
928 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
929 }
930
931 /**
932 * @test
933 */
934 public function addDataAddsLanguagesWithSpecialLanguages()
935 {
936 $input = [
937 'tableName' => 'aTable',
938 'databaseRow' => [],
939 'processedTca' => [
940 'columns' => [
941 'aField' => [
942 'config' => [
943 'type' => 'select',
944 'renderType' => 'selectSingle',
945 'special' => 'languages',
946 ],
947 ],
948 ],
949 ],
950 'systemLanguageRows' => [
951 0 => [
952 'title' => 'aLangTitle',
953 'uid' => 42,
954 'flagIconIdentifier' => 'aFlag.gif',
955 ],
956 ],
957 ];
958
959 /** @var LanguageService|ObjectProphecy $languageService */
960 $languageService = $this->prophesize(LanguageService::class);
961 $GLOBALS['LANG'] = $languageService->reveal();
962 $languageService->sL(Argument::cetera())->willReturnArgument(0);
963
964 $expectedItems = [
965 0 => [
966 0 => 'aLangTitle [42]',
967 1 => 42,
968 2 => 'aFlag.gif',
969 3 => null,
970 ],
971 ];
972
973 $result = $this->subject->addData($input);
974
975 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
976 }
977
978 /**
979 * @test
980 */
981 public function addDataAddsCustomOptionsWithSpecialCustom()
982 {
983 $input = [
984 'tableName' => 'aTable',
985 'databaseRow' => [],
986 'processedTca' => [
987 'columns' => [
988 'aField' => [
989 'config' => [
990 'type' => 'select',
991 'renderType' => 'selectSingle',
992 'special' => 'custom',
993 ],
994 ],
995 ],
996 ],
997 ];
998
999 /** @var LanguageService|ObjectProphecy $languageService */
1000 $languageService = $this->prophesize(LanguageService::class);
1001 $GLOBALS['LANG'] = $languageService->reveal();
1002 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1003
1004 $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] = [
1005 'aKey' => [
1006 'header' => 'aHeader',
1007 'items' => [
1008 'anItemKey' => [
1009 0 => 'anItemTitle',
1010 ],
1011 ],
1012 ]
1013 ];
1014
1015 $expectedItems = [
1016 0 => [
1017 0 => 'aHeader',
1018 1 => '--div--',
1019 null,
1020 null,
1021 ],
1022 1 => [
1023 0 => 'anItemTitle',
1024 1 => 'aKey:anItemKey',
1025 2 => 'empty-empty',
1026 3 => null,
1027 ],
1028 ];
1029
1030 $result = $this->subject->addData($input);
1031
1032 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
1033 }
1034
1035 /**
1036 * @test
1037 */
1038 public function addDataAddsGroupItemsWithSpecialModListGroup()
1039 {
1040 $input = [
1041 'tableName' => 'aTable',
1042 'databaseRow' => [],
1043 'processedTca' => [
1044 'columns' => [
1045 'aField' => [
1046 'config' => [
1047 'type' => 'select',
1048 'renderType' => 'selectSingle',
1049 'special' => 'modListGroup',
1050 ],
1051 ],
1052 ],
1053 ],
1054 ];
1055
1056 $GLOBALS['TBE_MODULES'] = [];
1057
1058 /** @var LanguageService|ObjectProphecy $languageService */
1059 $languageService = $this->prophesize(LanguageService::class);
1060 $GLOBALS['LANG'] = $languageService->reveal();
1061 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1062 $languageService->moduleLabels = [
1063 'tabs_images' => [
1064 'aModule_tab' => PATH_site . 'aModuleTabIcon.gif',
1065 ],
1066 'labels' => [
1067 'aModule_tablabel' => 'aModuleTabLabel',
1068 'aModule_tabdescr' => 'aModuleTabDescription',
1069 ],
1070 'tabs' => [
1071 'aModule_tab' => 'aModuleLabel',
1072 ]
1073 ];
1074
1075 /** @var ModuleLoader|ObjectProphecy $moduleLoaderProphecy */
1076 $moduleLoaderProphecy = $this->prophesize(ModuleLoader::class);
1077 GeneralUtility::addInstance(ModuleLoader::class, $moduleLoaderProphecy->reveal());
1078 $moduleLoaderProphecy->load([])->shouldBeCalled();
1079 $moduleLoaderProphecy->modListGroup = [
1080 'aModule',
1081 ];
1082
1083 $expectedItems = [
1084 0 => [
1085 0 => 'aModuleLabel',
1086 1 => 'aModule',
1087 2 => '../aModuleTabIcon.gif',
1088 3 => [
1089 'title' => 'aModuleTabLabel',
1090 'description' => 'aModuleTabDescription',
1091 ],
1092 ],
1093 ];
1094
1095 $result = $this->subject->addData($input);
1096
1097 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
1098 }
1099
1100 /**
1101 * @test
1102 */
1103 public function addDataAddsFileItemsWithConfiguredFileFolder()
1104 {
1105 $directory = $this->getUniqueId('typo3temp/test-') . '/';
1106 $input = [
1107 'tableName' => 'aTable',
1108 'databaseRow' => [],
1109 'processedTca' => [
1110 'columns' => [
1111 'aField' => [
1112 'config' => [
1113 'type' => 'select',
1114 'renderType' => 'selectSingle',
1115 'fileFolder' => $directory,
1116 'fileFolder_extList' => 'gif',
1117 'fileFolder_recursions' => 1,
1118 ],
1119 ],
1120 ],
1121 ],
1122 ];
1123
1124 /** @var LanguageService|ObjectProphecy $languageService */
1125 $languageService = $this->prophesize(LanguageService::class);
1126 $GLOBALS['LANG'] = $languageService->reveal();
1127 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1128
1129 mkdir(PATH_site . $directory);
1130 $this->testFilesToDelete[] = PATH_site . $directory;
1131 touch(PATH_site . $directory . 'anImage.gif');
1132 touch(PATH_site . $directory . 'aFile.txt');
1133 mkdir(PATH_site . $directory . '/subdir');
1134 touch(PATH_site . $directory . '/subdir/anotherImage.gif');
1135
1136 $expectedItems = [
1137 0 => [
1138 0 => 'anImage.gif',
1139 1 => 'anImage.gif',
1140 2 => '../' . $directory . 'anImage.gif',
1141 3 => null,
1142 ],
1143 1 => [
1144 0 => 'subdir/anotherImage.gif',
1145 1 => 'subdir/anotherImage.gif',
1146 2 => '../' . $directory . 'subdir/anotherImage.gif',
1147 3 => null,
1148 ],
1149 ];
1150
1151 $result = $this->subject->addData($input);
1152
1153 $this->assertSame($expectedItems, $result['processedTca']['columns']['aField']['config']['items']);
1154 }
1155
1156 /**
1157 * Data provider
1158 */
1159 public function addDataReplacesMarkersInForeignTableClauseDataProvider()
1160 {
1161 return [
1162 'replace REC_FIELD' => [
1163 'AND fTable.title=\'###REC_FIELD_rowField###\'',
1164 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.title=\'rowFieldValue\'',
1165 [],
1166 ],
1167 'replace REC_FIELD fullQuote' => [
1168 'AND fTable.title=###REC_FIELD_rowField###',
1169 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.title=\'rowFieldValue\'',
1170 [],
1171 ],
1172 'replace REC_FIELD fullQuoteWithArray' => [
1173 'AND fTable.title=###REC_FIELD_rowFieldThree###',
1174 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.title=\'rowFieldThreeValue\'',
1175 [
1176 'databaseRow' => [
1177 'rowFieldThree' => [
1178 0 => 'rowFieldThreeValue'
1179 ]
1180 ],
1181 ],
1182 ],
1183 'replace REC_FIELD multiple markers' => [
1184 'AND fTable.title=\'###REC_FIELD_rowField###\' AND fTable.pid=###REC_FIELD_rowFieldTwo###',
1185 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.title=\'rowFieldValue\' AND fTable.pid=\'rowFieldTwoValue\'',
1186 [],
1187 ],
1188 'replace CURRENT_PID' => [
1189 'AND fTable.uid=###CURRENT_PID###',
1190 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=43',
1191 [],
1192 ],
1193 'replace CURRENT_PID integer cast' => [
1194 'AND fTable.uid=###CURRENT_PID###',
1195 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=431',
1196 [
1197 'effectivePid' => '431string',
1198 ],
1199 ],
1200 'replace THIS_UID' => [
1201 'AND fTable.uid=###THIS_UID###',
1202 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=42',
1203 [],
1204 ],
1205 'replace THIS_UID integer cast' => [
1206 'AND fTable.uid=###THIS_UID###',
1207 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=421',
1208 [
1209 'databaseRow' => [
1210 'uid' => '421string',
1211 ],
1212 ],
1213 ],
1214 'replace SITEROOT' => [
1215 'AND fTable.uid=###SITEROOT###',
1216 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=44',
1217 [],
1218 ],
1219 'replace SITEROOT integer cast' => [
1220 'AND fTable.uid=###SITEROOT###',
1221 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=441',
1222 [
1223 'rootline' => [
1224 1 => [
1225 'uid' => '441string',
1226 ],
1227 ],
1228 ],
1229 ],
1230 'replace PAGE_TSCONFIG_ID' => [
1231 'AND fTable.uid=###PAGE_TSCONFIG_ID###',
1232 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=45',
1233 [],
1234 ],
1235 'replace PAGE_TSCONFIG_ID integer cast' => [
1236 'AND fTable.uid=###PAGE_TSCONFIG_ID###',
1237 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=451',
1238 [
1239 'pageTsConfig' => [
1240 'TCEFORM.' => [
1241 'aTable.' => [
1242 'aField.' => [
1243 'PAGE_TSCONFIG_ID' => '451string'
1244 ],
1245 ],
1246 ],
1247 ],
1248 ],
1249 ],
1250 'replace PAGE_TSCONFIG_STR' => [
1251 'AND fTable.uid=\'###PAGE_TSCONFIG_STR###\'',
1252 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=\'46\'',
1253 [],
1254 ],
1255 'replace PAGE_TSCONFIG_IDLIST' => [
1256 'AND fTable.uid IN (###PAGE_TSCONFIG_IDLIST###)',
1257 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid IN (47,48)',
1258 [],
1259 ],
1260 'replace PAGE_TSCONFIG_IDLIST cleans list' => [
1261 'AND fTable.uid IN (###PAGE_TSCONFIG_IDLIST###)',
1262 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid IN (471,481)',
1263 [
1264 'pageTsConfig' => [
1265 'TCEFORM.' => [
1266 'aTable.' => [
1267 'aField.' => [
1268 'PAGE_TSCONFIG_IDLIST' => 'a, 471, b, 481, c',
1269 ],
1270 ],
1271 ],
1272 ],
1273 ],
1274 ],
1275 ];
1276 }
1277
1278 /**
1279 * @test
1280 * @dataProvider addDataReplacesMarkersInForeignTableClauseDataProvider
1281 */
1282 public function addDataReplacesMarkersInForeignTableClause($foreignTableWhere, $expectedWhere, array $inputOverride)
1283 {
1284 $input = [
1285 'tableName' => 'aTable',
1286 'effectivePid' => 43,
1287 'databaseRow' => [
1288 'uid' => 42,
1289 'rowField' => 'rowFieldValue',
1290 'rowFieldTwo' => 'rowFieldTwoValue',
1291 ],
1292 'processedTca' => [
1293 'columns' => [
1294 'aField' => [
1295 'config' => [
1296 'type' => 'select',
1297 'renderType' => 'selectSingle',
1298 'foreign_table' => 'fTable',
1299 'foreign_table_where' => $foreignTableWhere,
1300 ],
1301 ],
1302 ]
1303 ],
1304 'rootline' => [
1305 2 => [
1306 'uid' => 999,
1307 'is_siteroot' => 0,
1308 ],
1309 1 => [
1310 'uid' => 44,
1311 'is_siteroot' => 1,
1312 ],
1313 0 => [
1314 'uid' => 0,
1315 'is_siteroot' => null,
1316 ],
1317 ],
1318 'pageTsConfig' => [
1319 'TCEFORM.' => [
1320 'aTable.' => [
1321 'aField.' => [
1322 'PAGE_TSCONFIG_ID' => 45,
1323 'PAGE_TSCONFIG_STR' => '46',
1324 'PAGE_TSCONFIG_IDLIST' => '47, 48',
1325 ],
1326 ],
1327 ],
1328 ],
1329 ];
1330 ArrayUtility::mergeRecursiveWithOverrule($input, $inputOverride);
1331
1332 $GLOBALS['TCA']['fTable'] = [];
1333
1334 $expectedQueryArray = [
1335 'SELECT' => 'fTable.uid',
1336 'FROM' => 'fTable, pages',
1337 'WHERE' => $expectedWhere,
1338 'GROUPBY' => '',
1339 'ORDERBY' => '',
1340 'LIMIT' => '',
1341 ];
1342
1343 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1344 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1345 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1346 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1347
1348 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1349 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1350 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1351 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1352 $databaseProphecy->quoteStr(Argument::cetera())->willReturnArgument(0);
1353 $databaseProphecy->fullQuoteStr(Argument::cetera())->will(function ($args) {
1354 return '\'' . $args[0] . '\'';
1355 });
1356 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->willReturn(false);
1357 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1358
1359 $databaseProphecy->exec_SELECT_queryArray($expectedQueryArray)->shouldBeCalled()->willReturn(false);
1360
1361 $this->subject->addData($input);
1362 }
1363
1364 /**
1365 * @test
1366 */
1367 public function addDataThrowsExceptionIfForeignTableIsNotDefinedInTca()
1368 {
1369 $input = [
1370 'tableName' => 'aTable',
1371 'processedTca' => [
1372 'columns' => [
1373 'aField' => [
1374 'config' => [
1375 'type' => 'select',
1376 'renderType' => 'selectSingle',
1377 'foreign_table' => 'fTable',
1378 ],
1379 ],
1380 ]
1381 ],
1382 ];
1383
1384 $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1439569743);
1385
1386 $this->subject->addData($input);
1387 }
1388
1389 /**
1390 * @test
1391 */
1392 public function addDataForeignTableSplitsGroupOrderAndLimit()
1393 {
1394 $input = [
1395 'tableName' => 'aTable',
1396 'databaseRow' => [],
1397 'processedTca' => [
1398 'columns' => [
1399 'aField' => [
1400 'config' => [
1401 'type' => 'select',
1402 'renderType' => 'selectSingle',
1403 'foreign_table' => 'fTable',
1404 'foreign_table_where' => 'AND ftable.uid=1 GROUP BY groupField ORDER BY orderField LIMIT 1,2',
1405 ],
1406 ],
1407 ]
1408 ],
1409 'rootline' => [],
1410 ];
1411
1412 $GLOBALS['TCA']['fTable'] = [];
1413
1414 $expectedQueryArray = [
1415 'SELECT' => 'fTable.uid',
1416 'FROM' => 'fTable, pages',
1417 'WHERE' => 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND ftable.uid=1',
1418 'GROUPBY' => 'groupField',
1419 'ORDERBY' => 'orderField',
1420 'LIMIT' => '1,2',
1421 ];
1422
1423 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1424 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1425 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1426 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1427
1428 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1429 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1430 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1431 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1432 $databaseProphecy->quoteStr(Argument::cetera())->willReturnArgument(0);
1433 $databaseProphecy->fullQuoteStr(Argument::cetera())->will(function ($args) {
1434 return '\'' . $args[0] . '\'';
1435 });
1436 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->willReturn(false);
1437 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1438
1439 $databaseProphecy->exec_SELECT_queryArray($expectedQueryArray)->shouldBeCalled()->willReturn(false);
1440
1441 $this->subject->addData($input);
1442 }
1443
1444 /**
1445 * @test
1446 */
1447 public function addDataForeignTableQueuesFlashMessageOnDatabaseError()
1448 {
1449 $input = [
1450 'databaseRow' => [
1451 'aField' => '',
1452 ],
1453 'tableName' => 'aTable',
1454 'processedTca' => [
1455 'columns' => [
1456 'aField' => [
1457 'config' => [
1458 'type' => 'select',
1459 'renderType' => 'selectSingle',
1460 'foreign_table' => 'fTable',
1461 'items' => [
1462 0 => [
1463 0 => 'itemLabel',
1464 1 => 'itemValue',
1465 2 => null,
1466 3 => null,
1467 ],
1468 ],
1469 'maxitems' => 1,
1470 ],
1471 ],
1472 ]
1473 ],
1474 'rootline' => [],
1475 ];
1476
1477 $GLOBALS['TCA']['fTable'] = [];
1478
1479 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1480 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1481 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1482 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1483
1484 /** @var LanguageService|ObjectProphecy $languageServiceProphecy */
1485 $languageServiceProphecy = $this->prophesize(LanguageService::class);
1486 $GLOBALS['LANG'] = $languageServiceProphecy->reveal();
1487 $languageServiceProphecy->sL(Argument::cetera())->willReturnArgument(0);
1488
1489 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1490 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1491 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1492 $databaseProphecy->exec_SELECT_queryArray(Argument::cetera())->willReturn(false);
1493
1494 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn('anError');
1495 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1496
1497 /** @var FlashMessage|ObjectProphecy $flashMessage */
1498 $flashMessage = $this->prophesize(FlashMessage::class);
1499 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
1500 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
1501 $flashMessageService = $this->prophesize(FlashMessageService::class);
1502 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
1503 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
1504 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
1505 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
1506
1507 $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
1508
1509 $expected = $input;
1510 $expected['databaseRow']['aField'] = [];
1511
1512 $this->assertEquals($expected, $this->subject->addData($input));
1513 }
1514
1515 /**
1516 * @test
1517 */
1518 public function addDataForeignTableHandlesForeignTableRows()
1519 {
1520 $input = [
1521 'databaseRow' => [
1522 'aField' => '',
1523 ],
1524 'tableName' => 'aTable',
1525 'processedTca' => [
1526 'columns' => [
1527 'aField' => [
1528 'config' => [
1529 'type' => 'select',
1530 'renderType' => 'selectSingle',
1531 'foreign_table' => 'fTable',
1532 'foreign_table_prefix' => 'aPrefix',
1533 'items' => [],
1534 'maxitems' => 1,
1535 ],
1536 ],
1537 ]
1538 ],
1539 'rootline' => [],
1540 ];
1541
1542 $GLOBALS['TCA']['fTable'] = [];
1543
1544 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1545 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1546 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1547 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1548
1549 /** @var LanguageService|ObjectProphecy $languageServiceProphecy */
1550 $languageServiceProphecy = $this->prophesize(LanguageService::class);
1551 $GLOBALS['LANG'] = $languageServiceProphecy->reveal();
1552 $languageServiceProphecy->sL(Argument::cetera())->willReturnArgument(0);
1553
1554 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1555 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1556 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1557 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1558 $databaseProphecy->sql_free_result(Argument::cetera())->willReturn(null);
1559 $databaseProphecy->exec_SELECT_queryArray(Argument::cetera())->willReturn(true);
1560
1561 $counter = 0;
1562 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->will(function ($args) use (&$counter) {
1563 $counter++;
1564 if ($counter >= 3) {
1565 return false;
1566 }
1567 return [
1568 'uid' => $counter,
1569 'aValue' => 'bar,',
1570 ];
1571 });
1572
1573 $expected = $input;
1574 $expected['processedTca']['columns']['aField']['config']['items'] = [
1575 0 => [
1576 0 => 'aPrefix[LLL:EXT:lang/locallang_core.xlf:labels.no_title]',
1577 1 => 1,
1578 2 => 'default-not-found',
1579 3 => null,
1580 ],
1581 1 => [
1582 0 => 'aPrefix[LLL:EXT:lang/locallang_core.xlf:labels.no_title]',
1583 1 => 2,
1584 2 => 'default-not-found',
1585 3 => null,
1586 ],
1587 ];
1588
1589 $expected['databaseRow']['aField'] = [];
1590
1591 $this->assertEquals($expected, $this->subject->addData($input));
1592 }
1593
1594 /**
1595 * @test
1596 */
1597 public function addDataRemovesItemsByKeepItemsPageTsConfig()
1598 {
1599 $input = [
1600 'databaseRow' => [
1601 'aField' => '',
1602 ],
1603 'tableName' => 'aTable',
1604 'processedTca' => [
1605 'columns' => [
1606 'aField' => [
1607 'config' => [
1608 'type' => 'select',
1609 'renderType' => 'selectSingle',
1610 'items' => [
1611 0 => [
1612 0 => 'keepMe',
1613 1 => 'keep',
1614 null,
1615 null,
1616 ],
1617 1 => [
1618 0 => 'removeMe',
1619 1 => 'remove',
1620 ],
1621 ],
1622 'maxitems' => 1,
1623 ],
1624 ],
1625 ]
1626 ],
1627 'pageTsConfig' => [
1628 'TCEFORM.' => [
1629 'aTable.' => [
1630 'aField.' => [
1631 'keepItems' => 'keep',
1632 ],
1633 ],
1634 ],
1635 ],
1636 ];
1637
1638 /** @var LanguageService|ObjectProphecy $languageService */
1639 $languageService = $this->prophesize(LanguageService::class);
1640 $GLOBALS['LANG'] = $languageService->reveal();
1641 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1642
1643 $expected = $input;
1644 $expected['databaseRow']['aField'] = [];
1645 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
1646
1647 $this->assertEquals($expected, $this->subject->addData($input));
1648 }
1649
1650 /**
1651 * @test
1652 */
1653 public function addDataRemovesItemsByRemoveItemsPageTsConfig()
1654 {
1655 $input = [
1656 'databaseRow' => [
1657 'aField' => ''
1658 ],
1659 'tableName' => 'aTable',
1660 'processedTca' => [
1661 'columns' => [
1662 'aField' => [
1663 'config' => [
1664 'type' => 'select',
1665 'renderType' => 'selectSingle',
1666 'items' => [
1667 0 => [
1668 0 => 'keepMe',
1669 1 => 'keep',
1670 null,
1671 null,
1672 ],
1673 1 => [
1674 0 => 'removeMe',
1675 1 => 'remove',
1676 ],
1677 ],
1678 'maxitems' => 1,
1679 ],
1680 ],
1681 ]
1682 ],
1683 'pageTsConfig' => [
1684 'TCEFORM.' => [
1685 'aTable.' => [
1686 'aField.' => [
1687 'removeItems' => 'remove',
1688 ],
1689 ],
1690 ],
1691 ],
1692 ];
1693
1694 /** @var LanguageService|ObjectProphecy $languageService */
1695 $languageService = $this->prophesize(LanguageService::class);
1696 $GLOBALS['LANG'] = $languageService->reveal();
1697 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1698
1699 $expected = $input;
1700 $expected['databaseRow']['aField'] = [];
1701 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
1702
1703 $this->assertEquals($expected, $this->subject->addData($input));
1704 }
1705
1706 /**
1707 * @test
1708 */
1709 public function addDataRemovesItemsByLanguageFieldUserRestriction()
1710 {
1711 $input = [
1712 'databaseRow' => [
1713 'aField' => 'aValue,remove'
1714 ],
1715 'tableName' => 'aTable',
1716 'processedTca' => [
1717 'ctrl' => [
1718 'languageField' => 'aField',
1719 ],
1720 'columns' => [
1721 'aField' => [
1722 'config' => [
1723 'type' => 'select',
1724 'renderType' => 'selectSingle',
1725 'items' => [
1726 0 => [
1727 0 => 'keepMe',
1728 1 => 'keep',
1729 null,
1730 null,
1731 ],
1732 1 => [
1733 0 => 'removeMe',
1734 1 => 'remove',
1735 ],
1736 ],
1737 'maxitems' => 1,
1738 ],
1739 ],
1740 ]
1741 ],
1742 ];
1743
1744 /** @var LanguageService|ObjectProphecy $languageService */
1745 $languageService = $this->prophesize(LanguageService::class);
1746 $GLOBALS['LANG'] = $languageService->reveal();
1747 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
1748 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1749
1750 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1751 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1752 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1753 $backendUserProphecy->checkLanguageAccess('keep')->shouldBeCalled()->willReturn(true);
1754 $backendUserProphecy->checkLanguageAccess('remove')->shouldBeCalled()->willReturn(false);
1755
1756 $expected = $input;
1757 $expected['databaseRow']['aField'] = [];
1758 $expected['processedTca']['columns']['aField']['config']['items'] = [
1759 [ '[ INVALID VALUE "aValue" ]', 'aValue', null, null ],
1760 [ 'keepMe', 'keep', null, null ],
1761 ];
1762
1763 $this->assertEquals($expected, $this->subject->addData($input));
1764 }
1765
1766 /**
1767 * @test
1768 */
1769 public function addDataRemovesItemsByUserAuthModeRestriction()
1770 {
1771 $input = [
1772 'databaseRow' => [
1773 'aField' => 'keep,remove'
1774 ],
1775 'tableName' => 'aTable',
1776 'processedTca' => [
1777 'columns' => [
1778 'aField' => [
1779 'config' => [
1780 'type' => 'select',
1781 'renderType' => 'selectSingle',
1782 'authMode' => 'explicitAllow',
1783 'items' => [
1784 0 => [
1785 0 => 'keepMe',
1786 1 => 'keep',
1787 null,
1788 null,
1789 ],
1790 1 => [
1791 0 => 'removeMe',
1792 1 => 'remove',
1793 ],
1794 ],
1795 'maxitems' => 1,
1796 ],
1797 ],
1798 ]
1799 ],
1800 ];
1801
1802 /** @var LanguageService|ObjectProphecy $languageService */
1803 $languageService = $this->prophesize(LanguageService::class);
1804 $GLOBALS['LANG'] = $languageService->reveal();
1805 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1806
1807 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1808 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1809 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1810 $backendUserProphecy->checkAuthMode('aTable', 'aField', 'keep', 'explicitAllow')->shouldBeCalled()->willReturn(true);
1811 $backendUserProphecy->checkAuthMode('aTable', 'aField', 'remove', 'explicitAllow')->shouldBeCalled()->willReturn(false);
1812
1813 $expected = $input;
1814 $expected['databaseRow']['aField'] = ['keep'];
1815 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
1816
1817 $this->assertEquals($expected, $this->subject->addData($input));
1818 }
1819
1820 /**
1821 * @test
1822 */
1823 public function addDataKeepsAllPagesDoktypesForAdminUser()
1824 {
1825 $input = [
1826 'databaseRow' => [
1827 'doktype' => 'keep'
1828 ],
1829 'tableName' => 'pages',
1830 'processedTca' => [
1831 'columns' => [
1832 'doktype' => [
1833 'config' => [
1834 'type' => 'select',
1835 'renderType' => 'selectSingle',
1836 'items' => [
1837 0 => [
1838 0 => 'keepMe',
1839 1 => 'keep',
1840 null,
1841 null,
1842 ],
1843 ],
1844 'maxitems' => 1,
1845 ],
1846 ],
1847 ],
1848 ],
1849 ];
1850
1851 /** @var LanguageService|ObjectProphecy $languageService */
1852 $languageService = $this->prophesize(LanguageService::class);
1853 $GLOBALS['LANG'] = $languageService->reveal();
1854 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1855
1856 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1857 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1858 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1859 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(true);
1860
1861 $expected = $input;
1862 $expected['databaseRow']['doktype'] = ['keep'];
1863
1864 $this->assertEquals($expected, $this->subject->addData($input));
1865 }
1866
1867 /**
1868 * @test
1869 */
1870 public function addDataKeepsAllowedPageTypesForNonAdminUser()
1871 {
1872 $input = [
1873 'databaseRow' => [
1874 'doktype' => 'keep',
1875 ],
1876 'tableName' => 'pages',
1877 'processedTca' => [
1878 'columns' => [
1879 'doktype' => [
1880 'config' => [
1881 'type' => 'select',
1882 'renderType' => 'selectSingle',
1883 'items' => [
1884 0 => [
1885 0 => 'keepMe',
1886 1 => 'keep',
1887 null,
1888 null,
1889 ],
1890 1 => [
1891 0 => 'removeMe',
1892 1 => 'remove',
1893 ],
1894 ],
1895 'maxitems' => 1,
1896 ],
1897 ],
1898 ],
1899 ],
1900 ];
1901
1902 /** @var LanguageService|ObjectProphecy $languageService */
1903 $languageService = $this->prophesize(LanguageService::class);
1904 $GLOBALS['LANG'] = $languageService->reveal();
1905 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1906
1907 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1908 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1909 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1910 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(false);
1911 $backendUserProphecy->groupData = [
1912 'pagetypes_select' => 'foo,keep,anotherAllowedDoktype',
1913 ];
1914
1915 $expected = $input;
1916 $expected['databaseRow']['doktype'] = ['keep'];
1917 unset($expected['processedTca']['columns']['doktype']['config']['items'][1]);
1918
1919 $this->assertEquals($expected, $this->subject->addData($input));
1920 }
1921
1922 /**
1923 * @test
1924 */
1925 public function addDataCallsItemsProcFunc()
1926 {
1927 $input = [
1928 'tableName' => 'aTable',
1929 'databaseRow' => [
1930 'aField' => 'aValue'
1931 ],
1932 'processedTca' => [
1933 'columns' => [
1934 'aField' => [
1935 'config' => [
1936 'type' => 'select',
1937 'renderType' => 'selectSingle',
1938 'items' => [],
1939 'itemsProcFunc' => function (array $parameters, $pObj) {
1940 $parameters['items'] = [
1941 0 => [
1942 0 => 'aLabel',
1943 1 => 'aValue',
1944 2 => null,
1945 3 => null,
1946 ],
1947 ];
1948 },
1949 ],
1950 ],
1951 ],
1952 ],
1953 ];
1954
1955 /** @var LanguageService|ObjectProphecy $languageService */
1956 $languageService = $this->prophesize(LanguageService::class);
1957 $GLOBALS['LANG'] = $languageService->reveal();
1958 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1959
1960 $expected = $input;
1961 $expected['databaseRow']['aField'] = ['aValue'];
1962 $expected['processedTca']['columns']['aField']['config'] = [
1963 'type' => 'select',
1964 'renderType' => 'selectSingle',
1965 'items' => [
1966 0 => [
1967 0 => 'aLabel',
1968 1 => 'aValue',
1969 2 => null,
1970 3 => null,
1971 ],
1972 ],
1973 'maxitems' => 1,
1974 ];
1975
1976 $this->assertSame($expected, $this->subject->addData($input));
1977 }
1978
1979 /**
1980 * @test
1981 */
1982 public function addDataItemsProcFuncReceivesParameters()
1983 {
1984 $input = [
1985 'tableName' => 'aTable',
1986 'databaseRow' => [
1987 'aField' => 'aValue',
1988 ],
1989 'pageTsConfig' => [
1990 'TCEFORM.' => [
1991 'aTable.' => [
1992 'aField.' => [
1993 'itemsProcFunc.' => [
1994 'itemParamKey' => 'itemParamValue',
1995 ],
1996 ]
1997 ],
1998 ],
1999 ],
2000 'processedTca' => [
2001 'columns' => [
2002 'aField' => [
2003 'config' => [
2004 'type' => 'select',
2005 'renderType' => 'selectSingle',
2006 'aKey' => 'aValue',
2007 'items' => [
2008 0 => [
2009 0 => 'aLabel',
2010 1 => 'aValue',
2011 ],
2012 ],
2013 'itemsProcFunc' => function (array $parameters, $pObj) {
2014 if ($parameters['items'] !== [ 0 => [ 'aLabel', 'aValue'] ]
2015 || $parameters['config']['aKey'] !== 'aValue'
2016 || $parameters['TSconfig'] !== [ 'itemParamKey' => 'itemParamValue' ]
2017 || $parameters['table'] !== 'aTable'
2018 || $parameters['row'] !== [ 'aField' => 'aValue' ]
2019 || $parameters['field'] !== 'aField'
2020 ) {
2021 throw new \UnexpectedValueException('broken', 1438604329);
2022 }
2023 },
2024 ],
2025 ],
2026 ],
2027 ],
2028 ];
2029
2030 $languageService = $this->prophesize(LanguageService::class);
2031 $GLOBALS['LANG'] = $languageService->reveal();
2032 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2033 /** @var FlashMessage|ObjectProphecy $flashMessage */
2034 $flashMessage = $this->prophesize(FlashMessage::class);
2035 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
2036 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
2037 $flashMessageService = $this->prophesize(FlashMessageService::class);
2038 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
2039 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
2040 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
2041 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
2042
2043 // itemsProcFunc must NOT have raised an exception
2044 $flashMessageQueue->enqueue($flashMessage)->shouldNotBeCalled();
2045
2046 $this->subject->addData($input);
2047 }
2048
2049 /**
2050 * @test
2051 */
2052 public function addDataItemsProcFuncEnqueuesFlashMessageOnException()
2053 {
2054 $input = [
2055 'tableName' => 'aTable',
2056 'databaseRow' => [
2057 'aField' => 'aValue',
2058 ],
2059 'pageTsConfig' => [
2060 'TCEFORM.' => [
2061 'aTable.' => [
2062 'aField.' => [
2063 'itemsProcFunc.' => [
2064 'itemParamKey' => 'itemParamValue',
2065 ],
2066 ]
2067 ],
2068 ],
2069 ],
2070 'processedTca' => [
2071 'columns' => [
2072 'aField' => [
2073 'config' => [
2074 'type' => 'select',
2075 'renderType' => 'selectSingle',
2076 'aKey' => 'aValue',
2077 'items' => [
2078 0 => [
2079 0 => 'aLabel',
2080 1 => 'aValue',
2081 ],
2082 ],
2083 'itemsProcFunc' => function (array $parameters, $pObj) {
2084 throw new \UnexpectedValueException('anException', 1438604329);
2085 },
2086 ],
2087 ],
2088 ],
2089 ],
2090 ];
2091
2092 $languageService = $this->prophesize(LanguageService::class);
2093 $GLOBALS['LANG'] = $languageService->reveal();
2094 /** @var FlashMessage|ObjectProphecy $flashMessage */
2095 $flashMessage = $this->prophesize(FlashMessage::class);
2096 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
2097 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
2098 $flashMessageService = $this->prophesize(FlashMessageService::class);
2099 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
2100 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
2101 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
2102 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
2103
2104 $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
2105
2106 $this->subject->addData($input);
2107 }
2108
2109 /**
2110 * @test
2111 */
2112 public function addDataTranslatesItemLabelsFromPageTsConfig()
2113 {
2114 $input = [
2115 'databaseRow' => [
2116 'aField' => 'aValue',
2117 ],
2118 'tableName' => 'aTable',
2119 'processedTca' => [
2120 'columns' => [
2121 'aField' => [
2122 'config' => [
2123 'type' => 'select',
2124 'renderType' => 'selectSingle',
2125 'items' => [
2126 0 => [
2127 0 => 'aLabel',
2128 1 => 'aValue',
2129 null,
2130 null,
2131 ],
2132 ],
2133 'maxitems' => 1,
2134 ],
2135 ],
2136 ],
2137 ],
2138 'pageTsConfig' => [
2139 'TCEFORM.' => [
2140 'aTable.' => [
2141 'aField.' => [
2142 'altLabels.' => [
2143 'aValue' => 'labelOverride',
2144 ],
2145 ]
2146 ],
2147 ],
2148 ],
2149 ];
2150
2151 /** @var LanguageService|ObjectProphecy $languageService */
2152 $languageService = $this->prophesize(LanguageService::class);
2153 $GLOBALS['LANG'] = $languageService->reveal();
2154 $languageService->sL('aLabel')->willReturnArgument(0);
2155 $languageService->sL('labelOverride')->shouldBeCalled()->willReturnArgument(0);
2156 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
2157
2158 $expected = $input;
2159 $expected['databaseRow']['aField'] = ['aValue'];
2160 $expected['processedTca']['columns']['aField']['config']['items'][0][0] = 'labelOverride';
2161
2162 $this->assertSame($expected, $this->subject->addData($input));
2163 $this->subject->addData($input);
2164 }
2165
2166 /**
2167 * @test
2168 */
2169 public function processSelectFieldValueSetsMmForeignRelationValues()
2170 {
2171 $GLOBALS['TCA']['foreignTable'] = [];
2172
2173 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2174 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2175 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2176
2177 /** @var DatabaseConnection|ObjectProphecy $database */
2178 $database = $this->prophesize(DatabaseConnection::class);
2179 $GLOBALS['TYPO3_DB'] = $database->reveal();
2180
2181 $input = [
2182 'tableName' => 'aTable',
2183 'databaseRow' => [
2184 'uid' => 42,
2185 // Two connected rows
2186 'aField' => 2,
2187 ],
2188 'processedTca' => [
2189 'columns' => [
2190 'aField' => [
2191 'config' => [
2192 'type' => 'select',
2193 'renderType' => 'selectSingle',
2194 'maxitems' => 999,
2195 'foreign_table' => 'foreignTable',
2196 'MM' => 'aTable_foreignTable_mm',
2197 'items' => [],
2198 ],
2199 ],
2200 ],
2201 ],
2202 ];
2203 $fieldConfig = $input['processedTca']['columns']['aField']['config'];
2204 /** @var RelationHandler|ObjectProphecy $relationHandlerProphecy */
2205 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2206 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2207
2208 $relationHandlerUids = [
2209 23,
2210 24
2211 ];
2212
2213 $relationHandlerProphecy->start(2, 'foreignTable', 'aTable_foreignTable_mm', 42, 'aTable', $fieldConfig)->shouldBeCalled();
2214 $relationHandlerProphecy->getValueArray()->shouldBeCalled()->willReturn($relationHandlerUids);
2215
2216 $expected = $input;
2217 $expected['databaseRow']['aField'] = $relationHandlerUids;
2218
2219 $this->assertEquals($expected, $this->subject->addData($input));
2220 }
2221
2222 /**
2223 * @test
2224 */
2225 public function processSelectFieldValueSetsForeignRelationValues()
2226 {
2227 $GLOBALS['TCA']['foreignTable'] = [];
2228
2229 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2230 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2231 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2232
2233 /** @var DatabaseConnection|ObjectProphecy $database */
2234 $database = $this->prophesize(DatabaseConnection::class);
2235 $GLOBALS['TYPO3_DB'] = $database->reveal();
2236
2237 $input = [
2238 'tableName' => 'aTable',
2239 'databaseRow' => [
2240 'uid' => 42,
2241 // Two connected rows
2242 'aField' => '22,23,24,25',
2243 ],
2244 'processedTca' => [
2245 'columns' => [
2246 'aField' => [
2247 'config' => [
2248 'type' => 'select',
2249 'renderType' => 'selectSingle',
2250 'maxitems' => 999,
2251 'foreign_table' => 'foreignTable',
2252 'items' => [],
2253 ],
2254 ],
2255 ],
2256 ],
2257 ];
2258 $fieldConfig = $input['processedTca']['columns']['aField']['config'];
2259 /** @var RelationHandler|ObjectProphecy $relationHandlerProphecy */
2260 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2261 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2262
2263 $relationHandlerUids = [
2264 23,
2265 24
2266 ];
2267
2268 $relationHandlerProphecy->start('22,23,24,25', 'foreignTable', '', 42, 'aTable', $fieldConfig)->shouldBeCalled();
2269 $relationHandlerProphecy->getValueArray()->shouldBeCalled()->willReturn($relationHandlerUids);
2270
2271 $expected = $input;
2272 $expected['databaseRow']['aField'] = $relationHandlerUids;
2273
2274 $this->assertEquals($expected, $this->subject->addData($input));
2275 }
2276
2277 /**
2278 * @test
2279 */
2280 public function processSelectFieldValueRemovesInvalidDynamicValues()
2281 {
2282 $languageService = $this->prophesize(LanguageService::class);
2283 $GLOBALS['LANG'] = $languageService->reveal();
2284 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2285
2286 $GLOBALS['TCA']['foreignTable'] = [];
2287
2288 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2289 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2290 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2291
2292 /** @var DatabaseConnection|ObjectProphecy $database */
2293 $database = $this->prophesize(DatabaseConnection::class);
2294 $GLOBALS['TYPO3_DB'] = $database->reveal();
2295
2296 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2297 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2298 $relationHandlerProphecy->start(Argument::cetera())->shouldBeCalled();
2299 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldBeCalled()->willReturn([1]);
2300
2301 $input = [
2302 'tableName' => 'aTable',
2303 'databaseRow' => [
2304 'aField' => '1,2,bar,foo',
2305 ],
2306 'processedTca' => [
2307 'columns' => [
2308 'aField' => [
2309 'config' => [
2310 'type' => 'select',
2311 'renderType' => 'selectSingleBox',
2312 'foreign_table' => 'foreignTable',
2313 'maxitems' => 999,
2314 'items' => [
2315 ['foo', 'foo', null, null],
2316 ],
2317 ],
2318 ],
2319 ],
2320 ],
2321 ];
2322
2323 $expected = $input;
2324 $expected['databaseRow']['aField'] = ['foo', 1];
2325
2326 $this->assertEquals($expected, $this->subject->addData($input));
2327 }
2328
2329 /**
2330 * @test
2331 */
2332 public function processSelectFieldValueKeepsValuesFromStaticItems()
2333 {
2334 $languageService = $this->prophesize(LanguageService::class);
2335 $GLOBALS['LANG'] = $languageService->reveal();
2336 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2337
2338 $input = [
2339 'tableName' => 'aTable',
2340 'databaseRow' => [
2341 'aField' => 'foo,bar',
2342 ],
2343 'processedTca' => [
2344 'columns' => [
2345 'aField' => [
2346 'config' => [
2347 'type' => 'select',
2348 'renderType' => 'selectSingle',
2349 'maxitems' => 999,
2350 'items' => [
2351 ['foo', 'foo', null, null],
2352 ['bar', 'bar', null, null],
2353 ],
2354 ],
2355 ],
2356 ],
2357 ],
2358 ];
2359
2360 $expected = $input;
2361 $expected['databaseRow']['aField'] = [
2362 'foo',
2363 'bar'
2364 ];
2365
2366 $this->assertEquals($expected, $this->subject->addData($input));
2367 }
2368
2369 /**
2370 * @test
2371 */
2372 public function processSelectFieldValueReturnsEmptyValueForSingleSelect()
2373 {
2374 $languageService = $this->prophesize(LanguageService::class);
2375 $GLOBALS['LANG'] = $languageService->reveal();
2376 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2377
2378 $input = [
2379 'tableName' => 'aTable',
2380 'databaseRow' => [
2381 'aField' => '',
2382 ],
2383 'processedTca' => [
2384 'columns' => [
2385 'aField' => [
2386 'config' => [
2387 'type' => 'select',
2388 'renderType' => 'selectSingle',
2389 'maxitems' => 1,
2390 'items' => [],
2391 ],
2392 ],
2393 ],
2394 ],
2395 ];
2396
2397 $expected = $input;
2398 $expected['databaseRow']['aField'] = [];
2399
2400 $this->assertEquals($expected, $this->subject->addData($input));
2401 }
2402
2403 /**
2404 * @test
2405 */
2406 public function processSelectFieldValueTrimsEmptyValueForMultiValueSelect()
2407 {
2408 $languageService = $this->prophesize(LanguageService::class);
2409 $GLOBALS['LANG'] = $languageService->reveal();
2410 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2411
2412 $input = [
2413 'tableName' => 'aTable',
2414 'databaseRow' => [
2415 'aField' => 'b,,c',
2416 ],
2417 'processedTca' => [
2418 'columns' => [
2419 'aField' => [
2420 'config' => [
2421 'type' => 'select',
2422 'renderType' => 'selectSingle',
2423 'maxitems' => 999,
2424 'items' => [
2425 ['a', '', null, null],
2426 ['b', 'b', null, null],
2427 ['c', 'c', null, null],
2428 ],
2429 ],
2430 ],
2431 ],
2432 ],
2433 ];
2434
2435 $expected = $input;
2436 $expected['databaseRow']['aField'] = [
2437 'b',
2438 'c',
2439 ];
2440
2441 $this->assertEquals($expected, $this->subject->addData($input));
2442 }
2443
2444 /**
2445 * @test
2446 */
2447 public function processSelectFieldValueDoesNotCallRelationManagerForStaticOnlyItems()
2448 {
2449 $languageService = $this->prophesize(LanguageService::class);
2450 $GLOBALS['LANG'] = $languageService->reveal();
2451 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2452
2453 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2454 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2455 $relationHandlerProphecy->start(Argument::cetera())->shouldNotBeCalled();
2456 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldNotBeCalled();
2457
2458 $input = [
2459 'tableName' => 'aTable',
2460 'databaseRow' => [
2461 'aField' => 'foo',
2462 ],
2463 'processedTca' => [
2464 'columns' => [
2465 'aField' => [
2466 'config' => [
2467 'type' => 'select',
2468 'renderType' => 'selectSingle',
2469 'maxitems' => 999,
2470 'items' => [
2471 ['foo', 'foo', null, null],
2472 ],
2473 ],
2474 ],
2475 ],
2476 ],
2477 ];
2478
2479 $expected = $input;
2480 $expected['databaseRow']['aField'] = ['foo'];
2481
2482 $this->assertEquals($expected, $this->subject->addData($input));
2483 }
2484
2485 /**
2486 * @test
2487 */
2488 public function processSelectFieldValueAddsInvalidValuesToItemsForSingleSelects()
2489 {
2490 $languageService = $this->prophesize(LanguageService::class);
2491 $GLOBALS['LANG'] = $languageService->reveal();
2492 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
2493 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2494
2495 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2496 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2497 $relationHandlerProphecy->start(Argument::cetera())->shouldNotBeCalled();
2498 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldNotBeCalled();
2499
2500 $input = [
2501 'tableName' => 'aTable',
2502 'databaseRow' => [
2503 'aField' => '1,2,bar,foo',
2504 ],
2505 'processedTca' => [
2506 'columns' => [
2507 'aField' => [
2508 'config' => [
2509 'type' => 'select',
2510 'renderType' => 'selectSingle',
2511 'maxitems' => 1,
2512 'items' => [
2513 ['foo', 'foo', null, null],
2514 ],
2515 ],
2516 ],
2517 ],
2518 ],
2519 ];
2520
2521 $expected = $input;
2522 $expected['databaseRow']['aField'] = ['foo'];
2523 $expected['processedTca']['columns']['aField']['config']['items'] = [
2524 ['[ INVALID VALUE "bar" ]', 'bar', null, null],
2525 ['[ INVALID VALUE "2" ]', '2', null, null],
2526 ['[ INVALID VALUE "1" ]', '1', null, null],
2527 ['foo', 'foo', null, null],
2528 ];
2529 $this->assertEquals($expected, $this->subject->addData($input));
2530 }
2531
2532
2533 /**
2534 * Data Provider
2535 *
2536 * @return array
2537 */
2538 public function processSelectFieldSetsCorrectValuesForMmRelationsDataProvider()
2539 {
2540 return array(
2541 'Relation with MM table and new status with default values' => [
2542 [
2543 'tableName' => 'aTable',
2544 'command' => 'new',
2545 'databaseRow' => [
2546 'uid' => 'NEW1234',
2547 'aField' => '24,35',
2548 ],
2549 'processedTca' => [
2550 'columns' => [
2551 'aField' => [
2552 'config' => [
2553 'type' => 'select',
2554 'renderType' => 'selectSingle',
2555 'maxitems' => 999,
2556 'MM' => 'mm_aTable_foreignTable',
2557 'foreign_table' => 'foreignTable',
2558 'items' => [],
2559 ],
2560 ],
2561 ],
2562 ],
2563 ],
2564 [
2565 'MM' => ''
2566 ],
2567 [
2568 24, 35
2569 ]
2570 ],
2571 'Relation with MM table and item array in list but no new status' => [
2572 [
2573 'tableName' => 'aTable',
2574 'databaseRow' => [
2575 'uid' => 'NEW1234',
2576 'aField' => '24,35',
2577 ],
2578 'processedTca' => [
2579 'columns' => [
2580 'aField' => [
2581 'config' => [
2582 'type' => 'select',
2583 'renderType' => 'selectSingle',
2584 'maxitems' => 999,
2585 'MM' => 'mm_aTable_foreignTable',
2586 'foreign_table' => 'foreignTable',
2587 'items' => [],
2588 ],
2589 ],
2590 ],
2591 ],
2592 ],
2593 [],
2594 []
2595 ],
2596 'Relation with MM table and maxitems = 1 processes field value (item count)' => [
2597 [
2598 'tableName' => 'aTable',
2599 'databaseRow' => [
2600 'uid' => 42,
2601 // MM relation with one item has 1 in field value
2602 'aField' => 1,
2603 ],
2604 'processedTca' => [
2605 'columns' => [
2606 'aField' => [
2607 'config' => [
2608 'type' => 'select',
2609 'renderType' => 'selectSingle',
2610 'maxitems' => 1,
2611 'MM' => 'mm_aTable_foreignTable',
2612 'foreign_table' => 'foreignTable',
2613 'items' => [],
2614 ],
2615 ],
2616 ],
2617 ],
2618 ],
2619 [],
2620 [
2621 24
2622 ]
2623 ],
2624 'Relation with MM table and maxitems = 1 results in empty array if no items are set' => [
2625 [
2626 'tableName' => 'aTable',
2627 'databaseRow' => [
2628 'uid' => 58,
2629 // MM relation with no items has 0 in field value
2630 'aField' => 0,
2631 ],
2632 'processedTca' => [
2633 'columns' => [
2634 'aField' => [
2635 'config' => [
2636 'type' => 'select',
2637 'renderType' => 'selectSingle',
2638 'maxitems' => 1,
2639 'MM' => 'mm_aTable_foreignTable',
2640 'foreign_table' => 'foreignTable',
2641 'items' => [],
2642 ],
2643 ],
2644 ],
2645 ],
2646 ],
2647 [],
2648 []
2649 ]
2650 );
2651 }
2652
2653 /**
2654 * @test
2655 * @dataProvider processSelectFieldSetsCorrectValuesForMmRelationsDataProvider
2656 *
2657 * @param array $input
2658 * @param array $overrideRelationHandlerSettings
2659 * @param array $relationHandlerUids
2660 */
2661 public function processSelectFieldSetsCorrectValuesForMmRelations(array $input, array $overrideRelationHandlerSettings, array $relationHandlerUids)
2662 {
2663 $field = $input['databaseRow']['aField'];
2664 $foreignTable = isset($overrideRelationHandlerSettings['foreign_table']) ? $overrideRelationHandlerSettings['foreign_table'] : $input['processedTca']['columns']['aField']['config']['foreign_table'];
2665 $mmTable = isset($overrideRelationHandlerSettings['MM']) ? $overrideRelationHandlerSettings['MM'] : $input['processedTca']['columns']['aField']['config']['MM'];
2666 $uid = $input['databaseRow']['uid'];
2667 $tableName = $input['tableName'];
2668 $fieldConfig = $input['processedTca']['columns']['aField']['config'];
2669
2670 $GLOBALS['TCA'][$foreignTable] = [];
2671
2672 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2673 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2674 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2675
2676 /** @var DatabaseConnection|ObjectProphecy $database */
2677 $database = $this->prophesize(DatabaseConnection::class);
2678 $GLOBALS['TYPO3_DB'] = $database->reveal();
2679
2680 /** @var RelationHandler|ObjectProphecy $relationHandlerProphecy */
2681 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2682 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2683
2684 $relationHandlerProphecy->start($field, $foreignTable, $mmTable, $uid, $tableName, $fieldConfig)->shouldBeCalled();
2685 $relationHandlerProphecy->getValueArray()->shouldBeCalled()->willReturn($relationHandlerUids);
2686
2687 $expected = $input;
2688 $expected['databaseRow']['aField'] = $relationHandlerUids;
2689
2690 $this->assertEquals($expected, $this->subject->addData($input));
2691 }
2692 }