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