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