ba616912df2c088fc9df49f91e5b66e757061f20
[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 integer cast' => [
1327 'AND fTable.uid=###CURRENT_PID###',
1328 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=431',
1329 [
1330 'effectivePid' => '431string',
1331 ],
1332 ],
1333 'replace THIS_UID' => [
1334 'AND fTable.uid=###THIS_UID###',
1335 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=42',
1336 [],
1337 ],
1338 'replace THIS_UID integer cast' => [
1339 'AND fTable.uid=###THIS_UID###',
1340 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=421',
1341 [
1342 'databaseRow' => [
1343 'uid' => '421string',
1344 ],
1345 ],
1346 ],
1347 'replace SITEROOT' => [
1348 'AND fTable.uid=###SITEROOT###',
1349 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=44',
1350 [],
1351 ],
1352 'replace SITEROOT integer cast' => [
1353 'AND fTable.uid=###SITEROOT###',
1354 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=441',
1355 [
1356 'rootline' => [
1357 1 => [
1358 'uid' => '441string',
1359 ],
1360 ],
1361 ],
1362 ],
1363 'replace PAGE_TSCONFIG_ID' => [
1364 'AND fTable.uid=###PAGE_TSCONFIG_ID###',
1365 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=45',
1366 [
1367 'pageTsConfig' => [
1368 'TCEFORM.' => [
1369 'aTable.' => [
1370 'aField.' => [
1371 'PAGE_TSCONFIG_ID' => '45',
1372 ],
1373 ],
1374 ],
1375 ],
1376 ],
1377 ],
1378 'replace PAGE_TSCONFIG_ID integer cast' => [
1379 'AND fTable.uid=###PAGE_TSCONFIG_ID###',
1380 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=451',
1381 [
1382 'pageTsConfig' => [
1383 'TCEFORM.' => [
1384 'aTable.' => [
1385 'aField.' => [
1386 'PAGE_TSCONFIG_ID' => '451string'
1387 ],
1388 ],
1389 ],
1390 ],
1391 ],
1392 ],
1393 'replace PAGE_TSCONFIG_STR' => [
1394 'AND fTable.uid=\'###PAGE_TSCONFIG_STR###\'',
1395 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=\'46\'',
1396 [
1397 'pageTsConfig' => [
1398 'TCEFORM.' => [
1399 'aTable.' => [
1400 'aField.' => [
1401 'PAGE_TSCONFIG_STR' => '46',
1402 ],
1403 ],
1404 ],
1405 ],
1406 ],
1407 ],
1408 'replace PAGE_TSCONFIG_IDLIST' => [
1409 'AND fTable.uid IN (###PAGE_TSCONFIG_IDLIST###)',
1410 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid IN (47,48)',
1411 [
1412 'pageTsConfig' => [
1413 'TCEFORM.' => [
1414 'aTable.' => [
1415 'aField.' => [
1416 'PAGE_TSCONFIG_IDLIST' => '47,48',
1417 ],
1418 ],
1419 ],
1420 ],
1421 ],
1422 ],
1423 'replace PAGE_TSCONFIG_IDLIST cleans list' => [
1424 'AND fTable.uid IN (###PAGE_TSCONFIG_IDLIST###)',
1425 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid IN (471,481)',
1426 [
1427 'pageTsConfig' => [
1428 'TCEFORM.' => [
1429 'aTable.' => [
1430 'aField.' => [
1431 'PAGE_TSCONFIG_IDLIST' => 'a, 471, b, 481, c',
1432 ],
1433 ],
1434 ],
1435 ],
1436 ],
1437 ],
1438 'deprecated flexHack PAGE_TSCONFIG_ID is substituted' => [
1439 'AND fTable.uid=###PAGE_TSCONFIG_ID###',
1440 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=123',
1441 [
1442 'pageTsConfig' => [
1443 'flexHack.' => [
1444 'PAGE_TSCONFIG_ID' => '123',
1445 ],
1446 ],
1447 ],
1448 ],
1449 'deprecated flexHack PAGE_TSCONFIG_IDLIST is substituted' => [
1450 'AND fTable.uid IN (###PAGE_TSCONFIG_IDLIST###)',
1451 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid IN (123,124)',
1452 [
1453 'pageTsConfig' => [
1454 'flexHack.' => [
1455 'PAGE_TSCONFIG_IDLIST' => '123,124',
1456 ],
1457 ],
1458 ],
1459 ],
1460 'deprecated flexHack PAGE_TSCONFIG_STR is substituted' => [
1461 'AND fTable.uid=\'###PAGE_TSCONFIG_STR###\'',
1462 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND fTable.uid=\'aString\'',
1463 [
1464 'pageTsConfig' => [
1465 'flexHack.' => [
1466 'PAGE_TSCONFIG_STR' => 'aString',
1467 ],
1468 ],
1469 ],
1470 ],
1471 ];
1472 }
1473
1474 /**
1475 * @test
1476 * @dataProvider addDataReplacesMarkersInForeignTableClauseDataProvider
1477 */
1478 public function addDataReplacesMarkersInForeignTableClause($foreignTableWhere, $expectedWhere, array $inputOverride)
1479 {
1480 $input = [
1481 'tableName' => 'aTable',
1482 'effectivePid' => 43,
1483 'databaseRow' => [
1484 'uid' => 42,
1485 'rowField' => 'rowFieldValue',
1486 'rowFieldTwo' => 'rowFieldTwoValue',
1487 ],
1488 'processedTca' => [
1489 'columns' => [
1490 'aField' => [
1491 'config' => [
1492 'type' => 'select',
1493 'renderType' => 'selectSingle',
1494 'foreign_table' => 'fTable',
1495 'foreign_table_where' => $foreignTableWhere,
1496 ],
1497 ],
1498 ]
1499 ],
1500 'rootline' => [
1501 2 => [
1502 'uid' => 999,
1503 'is_siteroot' => 0,
1504 ],
1505 1 => [
1506 'uid' => 44,
1507 'is_siteroot' => 1,
1508 ],
1509 0 => [
1510 'uid' => 0,
1511 'is_siteroot' => null,
1512 ],
1513 ],
1514 'pageTsConfig' => [],
1515 ];
1516 ArrayUtility::mergeRecursiveWithOverrule($input, $inputOverride);
1517
1518 $GLOBALS['TCA']['fTable'] = [];
1519
1520 $expectedQueryArray = [
1521 'SELECT' => 'fTable.uid',
1522 'FROM' => 'fTable, pages',
1523 'WHERE' => $expectedWhere,
1524 'GROUPBY' => '',
1525 'ORDERBY' => '',
1526 'LIMIT' => '',
1527 ];
1528
1529 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1530 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1531 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1532 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1533
1534 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1535 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1536 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1537 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1538 $databaseProphecy->quoteStr(Argument::cetera())->willReturnArgument(0);
1539 $databaseProphecy->fullQuoteStr(Argument::cetera())->will(function ($args) {
1540 return '\'' . $args[0] . '\'';
1541 });
1542 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->willReturn(false);
1543 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1544
1545 $databaseProphecy->exec_SELECT_queryArray($expectedQueryArray)->shouldBeCalled()->willReturn(false);
1546
1547 $this->subject->addData($input);
1548 }
1549
1550 /**
1551 * @test
1552 */
1553 public function addDataThrowsExceptionIfForeignTableIsNotDefinedInTca()
1554 {
1555 $input = [
1556 'tableName' => 'aTable',
1557 'processedTca' => [
1558 'columns' => [
1559 'aField' => [
1560 'config' => [
1561 'type' => 'select',
1562 'renderType' => 'selectSingle',
1563 'foreign_table' => 'fTable',
1564 ],
1565 ],
1566 ]
1567 ],
1568 ];
1569
1570 $this->setExpectedException(\UnexpectedValueException::class, $this->anything(), 1439569743);
1571
1572 $this->subject->addData($input);
1573 }
1574
1575 /**
1576 * @test
1577 */
1578 public function addDataForeignTableSplitsGroupOrderAndLimit()
1579 {
1580 $input = [
1581 'tableName' => 'aTable',
1582 'databaseRow' => [],
1583 'processedTca' => [
1584 'columns' => [
1585 'aField' => [
1586 'config' => [
1587 'type' => 'select',
1588 'renderType' => 'selectSingle',
1589 'foreign_table' => 'fTable',
1590 'foreign_table_where' => 'AND ftable.uid=1 GROUP BY groupField ORDER BY orderField LIMIT 1,2',
1591 ],
1592 ],
1593 ]
1594 ],
1595 'rootline' => [],
1596 ];
1597
1598 $GLOBALS['TCA']['fTable'] = [];
1599
1600 $expectedQueryArray = [
1601 'SELECT' => 'fTable.uid',
1602 'FROM' => 'fTable, pages',
1603 'WHERE' => 'pages.uid=fTable.pid AND pages.deleted=0 AND 1=1 AND ftable.uid=1',
1604 'GROUPBY' => 'groupField',
1605 'ORDERBY' => 'orderField',
1606 'LIMIT' => '1,2',
1607 ];
1608
1609 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1610 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1611 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1612 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1613
1614 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1615 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1616 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1617 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1618 $databaseProphecy->quoteStr(Argument::cetera())->willReturnArgument(0);
1619 $databaseProphecy->fullQuoteStr(Argument::cetera())->will(function ($args) {
1620 return '\'' . $args[0] . '\'';
1621 });
1622 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->willReturn(false);
1623 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1624
1625 $databaseProphecy->exec_SELECT_queryArray($expectedQueryArray)->shouldBeCalled()->willReturn(false);
1626
1627 $this->subject->addData($input);
1628 }
1629
1630 /**
1631 * @test
1632 */
1633 public function addDataForeignTableQueuesFlashMessageOnDatabaseError()
1634 {
1635 $input = [
1636 'databaseRow' => [
1637 'aField' => '',
1638 ],
1639 'tableName' => 'aTable',
1640 'processedTca' => [
1641 'columns' => [
1642 'aField' => [
1643 'config' => [
1644 'type' => 'select',
1645 'renderType' => 'selectSingle',
1646 'foreign_table' => 'fTable',
1647 'items' => [
1648 0 => [
1649 0 => 'itemLabel',
1650 1 => 'itemValue',
1651 2 => null,
1652 3 => null,
1653 ],
1654 ],
1655 'maxitems' => 1,
1656 ],
1657 ],
1658 ]
1659 ],
1660 'rootline' => [],
1661 ];
1662
1663 $GLOBALS['TCA']['fTable'] = [];
1664
1665 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1666 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1667 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1668 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1669
1670 /** @var LanguageService|ObjectProphecy $languageServiceProphecy */
1671 $languageServiceProphecy = $this->prophesize(LanguageService::class);
1672 $GLOBALS['LANG'] = $languageServiceProphecy->reveal();
1673 $languageServiceProphecy->sL(Argument::cetera())->willReturnArgument(0);
1674
1675 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1676 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1677 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1678 $databaseProphecy->exec_SELECT_queryArray(Argument::cetera())->willReturn(false);
1679
1680 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn('anError');
1681 $databaseProphecy->sql_free_result(Argument::cetera())->shouldBeCalled()->willReturn(null);
1682
1683 /** @var FlashMessage|ObjectProphecy $flashMessage */
1684 $flashMessage = $this->prophesize(FlashMessage::class);
1685 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
1686 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
1687 $flashMessageService = $this->prophesize(FlashMessageService::class);
1688 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
1689 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
1690 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
1691 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
1692
1693 $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
1694
1695 $expected = $input;
1696 $expected['databaseRow']['aField'] = [];
1697
1698 $this->assertEquals($expected, $this->subject->addData($input));
1699 }
1700
1701 /**
1702 * @test
1703 */
1704 public function addDataForeignTableHandlesForeignTableRows()
1705 {
1706 $input = [
1707 'databaseRow' => [
1708 'aField' => '',
1709 ],
1710 'tableName' => 'aTable',
1711 'processedTca' => [
1712 'columns' => [
1713 'aField' => [
1714 'config' => [
1715 'type' => 'select',
1716 'renderType' => 'selectSingle',
1717 'foreign_table' => 'fTable',
1718 'foreign_table_prefix' => 'aPrefix',
1719 'items' => [],
1720 'maxitems' => 1,
1721 ],
1722 ],
1723 ]
1724 ],
1725 'rootline' => [],
1726 ];
1727
1728 $GLOBALS['TCA']['fTable'] = [];
1729
1730 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1731 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1732 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1733 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1734
1735 /** @var LanguageService|ObjectProphecy $languageServiceProphecy */
1736 $languageServiceProphecy = $this->prophesize(LanguageService::class);
1737 $GLOBALS['LANG'] = $languageServiceProphecy->reveal();
1738 $languageServiceProphecy->sL(Argument::cetera())->willReturnArgument(0);
1739
1740 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1741 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1742 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1743 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1744 $databaseProphecy->sql_free_result(Argument::cetera())->willReturn(null);
1745 $databaseProphecy->exec_SELECT_queryArray(Argument::cetera())->willReturn(true);
1746
1747 $counter = 0;
1748 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->will(function ($args) use (&$counter) {
1749 $counter++;
1750 if ($counter >= 3) {
1751 return false;
1752 }
1753 return [
1754 'uid' => $counter,
1755 'aValue' => 'bar,',
1756 ];
1757 });
1758
1759 $expected = $input;
1760 $expected['processedTca']['columns']['aField']['config']['items'] = [
1761 0 => [
1762 0 => 'aPrefix[LLL:EXT:lang/locallang_core.xlf:labels.no_title]',
1763 1 => 1,
1764 2 => 'default-not-found',
1765 3 => null,
1766 ],
1767 1 => [
1768 0 => 'aPrefix[LLL:EXT:lang/locallang_core.xlf:labels.no_title]',
1769 1 => 2,
1770 2 => 'default-not-found',
1771 3 => null,
1772 ],
1773 ];
1774
1775 $expected['databaseRow']['aField'] = [];
1776
1777 $this->assertEquals($expected, $this->subject->addData($input));
1778 }
1779
1780 /**
1781 * @test
1782 */
1783 public function addDataForeignTableResolvesIconFromSelicon()
1784 {
1785 $input = [
1786 'databaseRow' => [
1787 'aField' => '',
1788 ],
1789 'tableName' => 'aTable',
1790 'processedTca' => [
1791 'columns' => [
1792 'aField' => [
1793 'config' => [
1794 'type' => 'select',
1795 'renderType' => 'selectSingle',
1796 'foreign_table' => 'fTable',
1797 'maxitems' => 1,
1798 ],
1799 ],
1800 ]
1801 ],
1802 'rootline' => [],
1803 ];
1804
1805 // Fake the foreign_table
1806 $GLOBALS['TCA']['fTable'] = [
1807 'ctrl' => [
1808 'label' => 'icon',
1809 'selicon_field' => 'icon',
1810 'selicon_field_path' => 'uploads/media',
1811 ],
1812 'columns' =>[
1813 'icon' => [
1814 'config' => [
1815 'type' => 'group',
1816 ],
1817 ],
1818 ],
1819 ];
1820
1821 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
1822 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
1823 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
1824 $backendUserProphecy->getPagePermsClause(1)->shouldBeCalled()->willReturn(' 1=1');
1825
1826 /** @var LanguageService|ObjectProphecy $languageServiceProphecy */
1827 $languageServiceProphecy = $this->prophesize(LanguageService::class);
1828 $GLOBALS['LANG'] = $languageServiceProphecy->reveal();
1829 $languageServiceProphecy->sL(Argument::cetera())->willReturnArgument(0);
1830
1831 /** @var DatabaseConnection|ObjectProphecy $databaseProphecy */
1832 $databaseProphecy = $this->prophesize(DatabaseConnection::class);
1833 $GLOBALS['TYPO3_DB'] = $databaseProphecy->reveal();
1834 $databaseProphecy->sql_error()->shouldBeCalled()->willReturn(false);
1835 $databaseProphecy->sql_free_result(Argument::cetera())->willReturn(null);
1836 // Query on foreign table is successful
1837 $databaseProphecy->exec_SELECT_queryArray(Argument::cetera())->willReturn(true);
1838 // Query returns one row, then false on second call
1839 $foreignTableRowResultOne = [
1840 'uid' => 1,
1841 'icon' => 'foo.jpg',
1842 ];
1843 $databaseProphecy->sql_fetch_assoc(Argument::cetera())->shouldBeCalled()->willReturn($foreignTableRowResultOne, false);
1844
1845 $expected = $input;
1846 $expected['processedTca']['columns']['aField']['config']['items'] = [
1847 0 => [
1848 0 => 'foo.jpg',
1849 1 => 1,
1850 2 => '../uploads/media/foo.jpg', // combination of selicon_field_path and the row value of field 'icon'
1851 3 => null,
1852 ],
1853 ];
1854 $expected['databaseRow']['aField'] = [];
1855
1856 $this->assertEquals($expected, $this->subject->addData($input));
1857 }
1858
1859 /**
1860 * @test
1861 */
1862 public function addDataRemovesItemsByKeepItemsPageTsConfig()
1863 {
1864 $input = [
1865 'databaseRow' => [
1866 'aField' => '',
1867 ],
1868 'tableName' => 'aTable',
1869 'processedTca' => [
1870 'columns' => [
1871 'aField' => [
1872 'config' => [
1873 'type' => 'select',
1874 'renderType' => 'selectSingle',
1875 'items' => [
1876 0 => [
1877 0 => 'keepMe',
1878 1 => 'keep',
1879 null,
1880 null,
1881 ],
1882 1 => [
1883 0 => 'removeMe',
1884 1 => 'remove',
1885 ],
1886 ],
1887 'maxitems' => 1,
1888 ],
1889 ],
1890 ]
1891 ],
1892 'pageTsConfig' => [
1893 'TCEFORM.' => [
1894 'aTable.' => [
1895 'aField.' => [
1896 'keepItems' => 'keep',
1897 ],
1898 ],
1899 ],
1900 ],
1901 ];
1902
1903 /** @var LanguageService|ObjectProphecy $languageService */
1904 $languageService = $this->prophesize(LanguageService::class);
1905 $GLOBALS['LANG'] = $languageService->reveal();
1906 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1907
1908 $expected = $input;
1909 $expected['databaseRow']['aField'] = [];
1910 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
1911
1912 $this->assertEquals($expected, $this->subject->addData($input));
1913 }
1914
1915 /**
1916 * @test
1917 */
1918 public function addDataRemovesAllItemsByEmptyKeepItemsPageTsConfig()
1919 {
1920 $input = [
1921 'databaseRow' => [
1922 'aField' => '',
1923 ],
1924 'tableName' => 'aTable',
1925 'processedTca' => [
1926 'columns' => [
1927 'aField' => [
1928 'config' => [
1929 'type' => 'select',
1930 'renderType' => 'selectSingle',
1931 'items' => [
1932 0 => [
1933 0 => 'keepMe',
1934 1 => 'keep',
1935 null,
1936 null,
1937 ],
1938 1 => [
1939 0 => 'removeMe',
1940 1 => 'remove',
1941 ],
1942 ],
1943 'maxitems' => 1,
1944 ],
1945 ],
1946 ]
1947 ],
1948 'pageTsConfig' => [
1949 'TCEFORM.' => [
1950 'aTable.' => [
1951 'aField.' => [
1952 'keepItems' => '',
1953 ],
1954 ],
1955 ],
1956 ],
1957 ];
1958
1959 /** @var LanguageService|ObjectProphecy $languageService */
1960 $languageService = $this->prophesize(LanguageService::class);
1961 $GLOBALS['LANG'] = $languageService->reveal();
1962 $languageService->sL(Argument::cetera())->willReturnArgument(0);
1963
1964 $expected = $input;
1965 $expected['databaseRow']['aField'] = [];
1966 $expected['processedTca']['columns']['aField']['config']['items'] = [];
1967
1968 $this->assertEquals($expected, $this->subject->addData($input));
1969 }
1970
1971 /**
1972 * @test
1973 */
1974 public function addDataEvaluatesKeepItemsBeforeAddItemsFromPageTsConfig()
1975 {
1976 $input = [
1977 'databaseRow' => [
1978 'aField' => '',
1979 ],
1980 'tableName' => 'aTable',
1981 'processedTca' => [
1982 'columns' => [
1983 'aField' => [
1984 'config' => [
1985 'type' => 'select',
1986 'renderType' => 'selectSingle',
1987 'items' => [
1988 0 => [
1989 0 => 'keepMe',
1990 1 => '1',
1991 null,
1992 null,
1993 ],
1994 1 => [
1995 0 => 'removeMe',
1996 1 => 'remove',
1997 ],
1998 ],
1999 'maxitems' => 1,
2000 ],
2001 ],
2002 ]
2003 ],
2004 'pageTsConfig' => [
2005 'TCEFORM.' => [
2006 'aTable.' => [
2007 'aField.' => [
2008 'keepItems' => '1',
2009 'addItems.' => [
2010 '1' => 'addItem #1',
2011 '12' => 'addItem #12',
2012 ],
2013 ],
2014 ],
2015 ],
2016 ],
2017 ];
2018
2019 /** @var LanguageService|ObjectProphecy $languageService */
2020 $languageService = $this->prophesize(LanguageService::class);
2021 $GLOBALS['LANG'] = $languageService->reveal();
2022 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2023
2024 $expected = $input;
2025 $expected['databaseRow']['aField'] = [];
2026 $expected['processedTca']['columns']['aField']['config']['items'] = [
2027 0 => [
2028 0 => 'keepMe',
2029 1 => '1',
2030 null,
2031 null,
2032 ],
2033 1 => [
2034 0 => 'addItem #1',
2035 1 => '1',
2036 null,
2037 null,
2038 ],
2039 2 => [
2040 0 => 'addItem #12',
2041 1 => '12',
2042 null,
2043 null,
2044 ],
2045 ];
2046
2047 $this->assertEquals($expected, $this->subject->addData($input));
2048 }
2049
2050 /**
2051 * @test
2052 */
2053 public function addDataRemovesItemsByRemoveItemsPageTsConfig()
2054 {
2055 $input = [
2056 'databaseRow' => [
2057 'aField' => ''
2058 ],
2059 'tableName' => 'aTable',
2060 'processedTca' => [
2061 'columns' => [
2062 'aField' => [
2063 'config' => [
2064 'type' => 'select',
2065 'renderType' => 'selectSingle',
2066 'items' => [
2067 0 => [
2068 0 => 'keepMe',
2069 1 => 'keep',
2070 null,
2071 null,
2072 ],
2073 1 => [
2074 0 => 'removeMe',
2075 1 => 'remove',
2076 ],
2077 ],
2078 'maxitems' => 1,
2079 ],
2080 ],
2081 ]
2082 ],
2083 'pageTsConfig' => [
2084 'TCEFORM.' => [
2085 'aTable.' => [
2086 'aField.' => [
2087 'removeItems' => 'remove',
2088 ],
2089 ],
2090 ],
2091 ],
2092 ];
2093
2094 /** @var LanguageService|ObjectProphecy $languageService */
2095 $languageService = $this->prophesize(LanguageService::class);
2096 $GLOBALS['LANG'] = $languageService->reveal();
2097 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2098
2099 $expected = $input;
2100 $expected['databaseRow']['aField'] = [];
2101 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
2102
2103 $this->assertEquals($expected, $this->subject->addData($input));
2104 }
2105
2106 /**
2107 * @test
2108 */
2109 public function addDataRemovesItemsAddedByAddItemsFromPageTsConfigByRemoveItemsPageTsConfig()
2110 {
2111 $input = [
2112 'databaseRow' => [
2113 'aField' => ''
2114 ],
2115 'tableName' => 'aTable',
2116 'processedTca' => [
2117 'columns' => [
2118 'aField' => [
2119 'config' => [
2120 'type' => 'select',
2121 'renderType' => 'selectSingle',
2122 'items' => [
2123 0 => [
2124 0 => 'keepMe',
2125 1 => 'keep',
2126 null,
2127 null,
2128 ],
2129 1 => [
2130 0 => 'removeMe',
2131 1 => 'remove',
2132 ],
2133 ],
2134 'maxitems' => 1,
2135 ],
2136 ],
2137 ]
2138 ],
2139 'pageTsConfig' => [
2140 'TCEFORM.' => [
2141 'aTable.' => [
2142 'aField.' => [
2143 'removeItems' => 'remove,add',
2144 'addItems.' => [
2145 'add' => 'addMe'
2146 ]
2147 ],
2148 ],
2149 ],
2150 ],
2151 ];
2152
2153 /** @var LanguageService|ObjectProphecy $languageService */
2154 $languageService = $this->prophesize(LanguageService::class);
2155 $GLOBALS['LANG'] = $languageService->reveal();
2156 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2157
2158 $expected = $input;
2159 $expected['databaseRow']['aField'] = [];
2160 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
2161
2162 $this->assertEquals($expected, $this->subject->addData($input));
2163 }
2164
2165 /**
2166 * @test
2167 */
2168 public function addDataRemovesItemsByLanguageFieldUserRestriction()
2169 {
2170 $input = [
2171 'databaseRow' => [
2172 'aField' => 'aValue,remove'
2173 ],
2174 'tableName' => 'aTable',
2175 'processedTca' => [
2176 'ctrl' => [
2177 'languageField' => 'aField',
2178 ],
2179 'columns' => [
2180 'aField' => [
2181 'config' => [
2182 'type' => 'select',
2183 'renderType' => 'selectSingle',
2184 'items' => [
2185 0 => [
2186 0 => 'keepMe',
2187 1 => 'keep',
2188 null,
2189 null,
2190 ],
2191 1 => [
2192 0 => 'removeMe',
2193 1 => 'remove',
2194 ],
2195 ],
2196 'maxitems' => 1,
2197 ],
2198 ],
2199 ]
2200 ],
2201 ];
2202
2203 /** @var LanguageService|ObjectProphecy $languageService */
2204 $languageService = $this->prophesize(LanguageService::class);
2205 $GLOBALS['LANG'] = $languageService->reveal();
2206 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
2207 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2208
2209 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2210 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2211 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2212 $backendUserProphecy->checkLanguageAccess('keep')->shouldBeCalled()->willReturn(true);
2213 $backendUserProphecy->checkLanguageAccess('remove')->shouldBeCalled()->willReturn(false);
2214
2215 $expected = $input;
2216 $expected['databaseRow']['aField'] = [];
2217 $expected['processedTca']['columns']['aField']['config']['items'] = [
2218 [ '[ INVALID VALUE "aValue" ]', 'aValue', null, null ],
2219 [ 'keepMe', 'keep', null, null ],
2220 ];
2221
2222 $this->assertEquals($expected, $this->subject->addData($input));
2223 }
2224
2225 /**
2226 * @test
2227 */
2228 public function addDataRemovesItemsByUserAuthModeRestriction()
2229 {
2230 $input = [
2231 'databaseRow' => [
2232 'aField' => 'keep,remove'
2233 ],
2234 'tableName' => 'aTable',
2235 'processedTca' => [
2236 'columns' => [
2237 'aField' => [
2238 'config' => [
2239 'type' => 'select',
2240 'renderType' => 'selectSingle',
2241 'authMode' => 'explicitAllow',
2242 'items' => [
2243 0 => [
2244 0 => 'keepMe',
2245 1 => 'keep',
2246 null,
2247 null,
2248 ],
2249 1 => [
2250 0 => 'removeMe',
2251 1 => 'remove',
2252 ],
2253 ],
2254 'maxitems' => 1,
2255 ],
2256 ],
2257 ]
2258 ],
2259 ];
2260
2261 /** @var LanguageService|ObjectProphecy $languageService */
2262 $languageService = $this->prophesize(LanguageService::class);
2263 $GLOBALS['LANG'] = $languageService->reveal();
2264 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2265
2266 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2267 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2268 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2269 $backendUserProphecy->checkAuthMode('aTable', 'aField', 'keep', 'explicitAllow')->shouldBeCalled()->willReturn(true);
2270 $backendUserProphecy->checkAuthMode('aTable', 'aField', 'remove', 'explicitAllow')->shouldBeCalled()->willReturn(false);
2271
2272 $expected = $input;
2273 $expected['databaseRow']['aField'] = ['keep'];
2274 unset($expected['processedTca']['columns']['aField']['config']['items'][1]);
2275
2276 $this->assertEquals($expected, $this->subject->addData($input));
2277 }
2278
2279 /**
2280 * @test
2281 */
2282 public function addDataKeepsAllPagesDoktypesForAdminUser()
2283 {
2284 $input = [
2285 'databaseRow' => [
2286 'doktype' => 'keep'
2287 ],
2288 'tableName' => 'pages',
2289 'processedTca' => [
2290 'columns' => [
2291 'doktype' => [
2292 'config' => [
2293 'type' => 'select',
2294 'renderType' => 'selectSingle',
2295 'items' => [
2296 0 => [
2297 0 => 'keepMe',
2298 1 => 'keep',
2299 null,
2300 null,
2301 ],
2302 ],
2303 'maxitems' => 1,
2304 ],
2305 ],
2306 ],
2307 ],
2308 ];
2309
2310 /** @var LanguageService|ObjectProphecy $languageService */
2311 $languageService = $this->prophesize(LanguageService::class);
2312 $GLOBALS['LANG'] = $languageService->reveal();
2313 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2314
2315 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2316 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2317 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2318 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(true);
2319
2320 $expected = $input;
2321 $expected['databaseRow']['doktype'] = ['keep'];
2322
2323 $this->assertEquals($expected, $this->subject->addData($input));
2324 }
2325
2326 /**
2327 * @test
2328 */
2329 public function addDataKeepsAllowedPageTypesForNonAdminUser()
2330 {
2331 $input = [
2332 'databaseRow' => [
2333 'doktype' => 'keep',
2334 ],
2335 'tableName' => 'pages',
2336 'processedTca' => [
2337 'columns' => [
2338 'doktype' => [
2339 'config' => [
2340 'type' => 'select',
2341 'renderType' => 'selectSingle',
2342 'items' => [
2343 0 => [
2344 0 => 'keepMe',
2345 1 => 'keep',
2346 null,
2347 null,
2348 ],
2349 1 => [
2350 0 => 'removeMe',
2351 1 => 'remove',
2352 ],
2353 ],
2354 'maxitems' => 1,
2355 ],
2356 ],
2357 ],
2358 ],
2359 ];
2360
2361 /** @var LanguageService|ObjectProphecy $languageService */
2362 $languageService = $this->prophesize(LanguageService::class);
2363 $GLOBALS['LANG'] = $languageService->reveal();
2364 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2365
2366 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2367 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2368 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2369 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(false);
2370 $backendUserProphecy->groupData = [
2371 'pagetypes_select' => 'foo,keep,anotherAllowedDoktype',
2372 ];
2373
2374 $expected = $input;
2375 $expected['databaseRow']['doktype'] = ['keep'];
2376 unset($expected['processedTca']['columns']['doktype']['config']['items'][1]);
2377
2378 $this->assertEquals($expected, $this->subject->addData($input));
2379 }
2380
2381 /**
2382 * @test
2383 */
2384 public function addDataCallsItemsProcFunc()
2385 {
2386 $input = [
2387 'tableName' => 'aTable',
2388 'databaseRow' => [
2389 'aField' => 'aValue'
2390 ],
2391 'processedTca' => [
2392 'columns' => [
2393 'aField' => [
2394 'config' => [
2395 'type' => 'select',
2396 'renderType' => 'selectSingle',
2397 'items' => [],
2398 'itemsProcFunc' => function (array $parameters, $pObj) {
2399 $parameters['items'] = [
2400 0 => [
2401 0 => 'aLabel',
2402 1 => 'aValue',
2403 2 => null,
2404 3 => null,
2405 ],
2406 ];
2407 },
2408 ],
2409 ],
2410 ],
2411 ],
2412 ];
2413
2414 /** @var LanguageService|ObjectProphecy $languageService */
2415 $languageService = $this->prophesize(LanguageService::class);
2416 $GLOBALS['LANG'] = $languageService->reveal();
2417 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2418
2419 $expected = $input;
2420 $expected['databaseRow']['aField'] = ['aValue'];
2421 $expected['processedTca']['columns']['aField']['config'] = [
2422 'type' => 'select',
2423 'renderType' => 'selectSingle',
2424 'items' => [
2425 0 => [
2426 0 => 'aLabel',
2427 1 => 'aValue',
2428 2 => null,
2429 3 => null,
2430 ],
2431 ],
2432 'maxitems' => 1,
2433 ];
2434
2435 $this->assertSame($expected, $this->subject->addData($input));
2436 }
2437
2438 /**
2439 * @test
2440 */
2441 public function addDataItemsProcFuncReceivesParameters()
2442 {
2443 $input = [
2444 'tableName' => 'aTable',
2445 'databaseRow' => [
2446 'aField' => 'aValue',
2447 ],
2448 'pageTsConfig' => [
2449 'TCEFORM.' => [
2450 'aTable.' => [
2451 'aField.' => [
2452 'itemsProcFunc.' => [
2453 'itemParamKey' => 'itemParamValue',
2454 ],
2455 ]
2456 ],
2457 ],
2458 ],
2459 'processedTca' => [
2460 'columns' => [
2461 'aField' => [
2462 'config' => [
2463 'type' => 'select',
2464 'renderType' => 'selectSingle',
2465 'aKey' => 'aValue',
2466 'items' => [
2467 0 => [
2468 0 => 'aLabel',
2469 1 => 'aValue',
2470 ],
2471 ],
2472 'itemsProcFunc' => function (array $parameters, $pObj) {
2473 if ($parameters['items'] !== [ 0 => [ 'aLabel', 'aValue'] ]
2474 || $parameters['config']['aKey'] !== 'aValue'
2475 || $parameters['TSconfig'] !== [ 'itemParamKey' => 'itemParamValue' ]
2476 || $parameters['table'] !== 'aTable'
2477 || $parameters['row'] !== [ 'aField' => 'aValue' ]
2478 || $parameters['field'] !== 'aField'
2479 ) {
2480 throw new \UnexpectedValueException('broken', 1438604329);
2481 }
2482 },
2483 ],
2484 ],
2485 ],
2486 ],
2487 ];
2488
2489 $languageService = $this->prophesize(LanguageService::class);
2490 $GLOBALS['LANG'] = $languageService->reveal();
2491 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2492 /** @var FlashMessage|ObjectProphecy $flashMessage */
2493 $flashMessage = $this->prophesize(FlashMessage::class);
2494 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
2495 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
2496 $flashMessageService = $this->prophesize(FlashMessageService::class);
2497 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
2498 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
2499 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
2500 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
2501
2502 // itemsProcFunc must NOT have raised an exception
2503 $flashMessageQueue->enqueue($flashMessage)->shouldNotBeCalled();
2504
2505 $this->subject->addData($input);
2506 }
2507
2508 /**
2509 * @test
2510 */
2511 public function addDataItemsProcFuncEnqueuesFlashMessageOnException()
2512 {
2513 $input = [
2514 'tableName' => 'aTable',
2515 'databaseRow' => [
2516 'aField' => 'aValue',
2517 ],
2518 'pageTsConfig' => [
2519 'TCEFORM.' => [
2520 'aTable.' => [
2521 'aField.' => [
2522 'itemsProcFunc.' => [
2523 'itemParamKey' => 'itemParamValue',
2524 ],
2525 ]
2526 ],
2527 ],
2528 ],
2529 'processedTca' => [
2530 'columns' => [
2531 'aField' => [
2532 'config' => [
2533 'type' => 'select',
2534 'renderType' => 'selectSingle',
2535 'aKey' => 'aValue',
2536 'items' => [
2537 0 => [
2538 0 => 'aLabel',
2539 1 => 'aValue',
2540 ],
2541 ],
2542 'itemsProcFunc' => function (array $parameters, $pObj) {
2543 throw new \UnexpectedValueException('anException', 1438604329);
2544 },
2545 ],
2546 ],
2547 ],
2548 ],
2549 ];
2550
2551 $languageService = $this->prophesize(LanguageService::class);
2552 $GLOBALS['LANG'] = $languageService->reveal();
2553 /** @var FlashMessage|ObjectProphecy $flashMessage */
2554 $flashMessage = $this->prophesize(FlashMessage::class);
2555 GeneralUtility::addInstance(FlashMessage::class, $flashMessage->reveal());
2556 /** @var FlashMessageService|ObjectProphecy $flashMessageService */
2557 $flashMessageService = $this->prophesize(FlashMessageService::class);
2558 GeneralUtility::setSingletonInstance(FlashMessageService::class, $flashMessageService->reveal());
2559 /** @var FlashMessageQueue|ObjectProphecy $flashMessageQueue */
2560 $flashMessageQueue = $this->prophesize(FlashMessageQueue::class);
2561 $flashMessageService->getMessageQueueByIdentifier(Argument::cetera())->willReturn($flashMessageQueue->reveal());
2562
2563 $flashMessageQueue->enqueue($flashMessage)->shouldBeCalled();
2564
2565 $this->subject->addData($input);
2566 }
2567
2568 /**
2569 * @test
2570 */
2571 public function addDataTranslatesItemLabelsFromPageTsConfig()
2572 {
2573 $input = [
2574 'databaseRow' => [
2575 'aField' => 'aValue',
2576 ],
2577 'tableName' => 'aTable',
2578 'processedTca' => [
2579 'columns' => [
2580 'aField' => [
2581 'config' => [
2582 'type' => 'select',
2583 'renderType' => 'selectSingle',
2584 'items' => [
2585 0 => [
2586 0 => 'aLabel',
2587 1 => 'aValue',
2588 null,
2589 null,
2590 ],
2591 ],
2592 'maxitems' => 1,
2593 ],
2594 ],
2595 ],
2596 ],
2597 'pageTsConfig' => [
2598 'TCEFORM.' => [
2599 'aTable.' => [
2600 'aField.' => [
2601 'altLabels.' => [
2602 'aValue' => 'labelOverride',
2603 ],
2604 ]
2605 ],
2606 ],
2607 ],
2608 ];
2609
2610 /** @var LanguageService|ObjectProphecy $languageService */
2611 $languageService = $this->prophesize(LanguageService::class);
2612 $GLOBALS['LANG'] = $languageService->reveal();
2613 $languageService->sL('aLabel')->willReturnArgument(0);
2614 $languageService->sL('labelOverride')->shouldBeCalled()->willReturnArgument(0);
2615 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
2616
2617 $expected = $input;
2618 $expected['databaseRow']['aField'] = ['aValue'];
2619 $expected['processedTca']['columns']['aField']['config']['items'][0][0] = 'labelOverride';
2620
2621 $this->assertSame($expected, $this->subject->addData($input));
2622 $this->subject->addData($input);
2623 }
2624
2625 /**
2626 * @test
2627 */
2628 public function processSelectFieldValueSetsMmForeignRelationValues()
2629 {
2630 $GLOBALS['TCA']['foreignTable'] = [];
2631
2632 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2633 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2634 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2635
2636 /** @var DatabaseConnection|ObjectProphecy $database */
2637 $database = $this->prophesize(DatabaseConnection::class);
2638 $GLOBALS['TYPO3_DB'] = $database->reveal();
2639
2640 $input = [
2641 'tableName' => 'aTable',
2642 'databaseRow' => [
2643 'uid' => 42,
2644 // Two connected rows
2645 'aField' => 2,
2646 ],
2647 'processedTca' => [
2648 'columns' => [
2649 'aField' => [
2650 'config' => [
2651 'type' => 'select',
2652 'renderType' => 'selectSingle',
2653 'maxitems' => 999,
2654 'foreign_table' => 'foreignTable',
2655 'MM' => 'aTable_foreignTable_mm',
2656 'items' => [],
2657 ],
2658 ],
2659 ],
2660 ],
2661 ];
2662 $fieldConfig = $input['processedTca']['columns']['aField']['config'];
2663 /** @var RelationHandler|ObjectProphecy $relationHandlerProphecy */
2664 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2665 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2666
2667 $relationHandlerUids = [
2668 23,
2669 24
2670 ];
2671
2672 $relationHandlerProphecy->start(2, 'foreignTable', 'aTable_foreignTable_mm', 42, 'aTable', $fieldConfig)->shouldBeCalled();
2673 $relationHandlerProphecy->getValueArray()->shouldBeCalled()->willReturn($relationHandlerUids);
2674
2675 $expected = $input;
2676 $expected['databaseRow']['aField'] = $relationHandlerUids;
2677
2678 $this->assertEquals($expected, $this->subject->addData($input));
2679 }
2680
2681 /**
2682 * @test
2683 */
2684 public function processSelectFieldValueSetsForeignRelationValues()
2685 {
2686 $GLOBALS['TCA']['foreignTable'] = [];
2687
2688 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2689 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2690 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2691
2692 /** @var DatabaseConnection|ObjectProphecy $database */
2693 $database = $this->prophesize(DatabaseConnection::class);
2694 $GLOBALS['TYPO3_DB'] = $database->reveal();
2695
2696 $input = [
2697 'tableName' => 'aTable',
2698 'databaseRow' => [
2699 'uid' => 42,
2700 // Two connected rows
2701 'aField' => '22,23,24,25',
2702 ],
2703 'processedTca' => [
2704 'columns' => [
2705 'aField' => [
2706 'config' => [
2707 'type' => 'select',
2708 'renderType' => 'selectSingle',
2709 'maxitems' => 999,
2710 'foreign_table' => 'foreignTable',
2711 'items' => [],
2712 ],
2713 ],
2714 ],
2715 ],
2716 ];
2717 $fieldConfig = $input['processedTca']['columns']['aField']['config'];
2718 /** @var RelationHandler|ObjectProphecy $relationHandlerProphecy */
2719 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2720 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2721
2722 $relationHandlerUids = [
2723 23,
2724 24
2725 ];
2726
2727 $relationHandlerProphecy->start('22,23,24,25', 'foreignTable', '', 42, 'aTable', $fieldConfig)->shouldBeCalled();
2728 $relationHandlerProphecy->getValueArray()->shouldBeCalled()->willReturn($relationHandlerUids);
2729
2730 $expected = $input;
2731 $expected['databaseRow']['aField'] = $relationHandlerUids;
2732
2733 $this->assertEquals($expected, $this->subject->addData($input));
2734 }
2735
2736 /**
2737 * @test
2738 */
2739 public function processSelectFieldValueRemovesInvalidDynamicValues()
2740 {
2741 $languageService = $this->prophesize(LanguageService::class);
2742 $GLOBALS['LANG'] = $languageService->reveal();
2743 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2744
2745 $GLOBALS['TCA']['foreignTable'] = [];
2746
2747 /** @var BackendUserAuthentication|ObjectProphecy $backendUserProphecy */
2748 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
2749 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
2750
2751 /** @var DatabaseConnection|ObjectProphecy $database */
2752 $database = $this->prophesize(DatabaseConnection::class);
2753 $GLOBALS['TYPO3_DB'] = $database->reveal();
2754
2755 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2756 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2757 $relationHandlerProphecy->start(Argument::cetera())->shouldBeCalled();
2758 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldBeCalled()->willReturn([1]);
2759
2760 $input = [
2761 'tableName' => 'aTable',
2762 'databaseRow' => [
2763 'aField' => '1,2,bar,foo',
2764 ],
2765 'processedTca' => [
2766 'columns' => [
2767 'aField' => [
2768 'config' => [
2769 'type' => 'select',
2770 'renderType' => 'selectSingleBox',
2771 'foreign_table' => 'foreignTable',
2772 'maxitems' => 999,
2773 'items' => [
2774 ['foo', 'foo', null, null],
2775 ],
2776 ],
2777 ],
2778 ],
2779 ],
2780 ];
2781
2782 $expected = $input;
2783 $expected['databaseRow']['aField'] = ['foo', 1];
2784
2785 $this->assertEquals($expected, $this->subject->addData($input));
2786 }
2787
2788 /**
2789 * @test
2790 */
2791 public function processSelectFieldValueKeepsValuesFromStaticItems()
2792 {
2793 $languageService = $this->prophesize(LanguageService::class);
2794 $GLOBALS['LANG'] = $languageService->reveal();
2795 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2796
2797 $input = [
2798 'tableName' => 'aTable',
2799 'databaseRow' => [
2800 'aField' => 'foo,bar',
2801 ],
2802 'processedTca' => [
2803 'columns' => [
2804 'aField' => [
2805 'config' => [
2806 'type' => 'select',
2807 'renderType' => 'selectSingle',
2808 'maxitems' => 999,
2809 'items' => [
2810 ['foo', 'foo', null, null],
2811 ['bar', 'bar', null, null],
2812 ],
2813 ],
2814 ],
2815 ],
2816 ],
2817 ];
2818
2819 $expected = $input;
2820 $expected['databaseRow']['aField'] = [
2821 'foo',
2822 'bar'
2823 ];
2824
2825 $this->assertEquals($expected, $this->subject->addData($input));
2826 }
2827
2828 /**
2829 * @test
2830 */
2831 public function processSelectFieldValueReturnsEmptyValueForSingleSelect()
2832 {
2833 $languageService = $this->prophesize(LanguageService::class);
2834 $GLOBALS['LANG'] = $languageService->reveal();
2835 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2836
2837 $input = [
2838 'tableName' => 'aTable',
2839 'databaseRow' => [
2840 'aField' => '',
2841 ],
2842 'processedTca' => [
2843 'columns' => [
2844 'aField' => [
2845 'config' => [
2846 'type' => 'select',
2847 'renderType' => 'selectSingle',
2848 'maxitems' => 1,
2849 'items' => [],
2850 ],
2851 ],
2852 ],
2853 ],
2854 ];
2855
2856 $expected = $input;
2857 $expected['databaseRow']['aField'] = [];
2858
2859 $this->assertEquals($expected, $this->subject->addData($input));
2860 }
2861
2862 /**
2863 * @test
2864 */
2865 public function processSelectFieldValueTrimsEmptyValueForMultiValueSelect()
2866 {
2867 $languageService = $this->prophesize(LanguageService::class);
2868 $GLOBALS['LANG'] = $languageService->reveal();
2869 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2870
2871 $input = [
2872 'tableName' => 'aTable',
2873 'databaseRow' => [
2874 'aField' => 'b,,c',
2875 ],
2876 'processedTca' => [
2877 'columns' => [
2878 'aField' => [
2879 'config' => [
2880 'type' => 'select',
2881 'renderType' => 'selectSingle',
2882 'maxitems' => 999,
2883 'items' => [
2884 ['a', '', null, null],
2885 ['b', 'b', null, null],
2886 ['c', 'c', null, null],
2887 ],
2888 ],
2889 ],
2890 ],
2891 ],
2892 ];
2893
2894 $expected = $input;
2895 $expected['databaseRow']['aField'] = [
2896 'b',
2897 'c',
2898 ];
2899
2900 $this->assertEquals($expected, $this->subject->addData($input));
2901 }
2902
2903 /**
2904 * @test
2905 */
2906 public function processSelectFieldValueDoesNotCallRelationManagerForStaticOnlyItems()
2907 {
2908 $languageService = $this->prophesize(LanguageService::class);
2909 $GLOBALS['LANG'] = $languageService->reveal();
2910 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2911
2912 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2913 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2914 $relationHandlerProphecy->start(Argument::cetera())->shouldNotBeCalled();
2915 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldNotBeCalled();
2916
2917 $input = [
2918 'tableName' => 'aTable',
2919 'databaseRow' => [
2920 'aField' => 'foo',
2921 ],
2922 'processedTca' => [
2923 'columns' => [
2924 'aField' => [
2925 'config' => [
2926 'type' => 'select',
2927 'renderType' => 'selectSingle',
2928 'maxitems' => 999,
2929 'items' => [
2930 ['foo', 'foo', null, null],
2931 ],
2932 ],
2933 ],
2934 ],
2935 ],
2936 ];
2937
2938 $expected = $input;
2939 $expected['databaseRow']['aField'] = ['foo'];
2940
2941 $this->assertEquals($expected, $this->subject->addData($input));
2942 }
2943
2944 /**
2945 * @test
2946 */
2947 public function processSelectFieldValueAddsInvalidValuesToItemsForSingleSelects()
2948 {
2949 $languageService = $this->prophesize(LanguageService::class);
2950 $GLOBALS['LANG'] = $languageService->reveal();
2951 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue')->willReturn('INVALID VALUE "%s"');
2952 $languageService->sL(Argument::cetera())->willReturnArgument(0);
2953
2954 $relationHandlerProphecy = $this->prophesize(RelationHandler::class);
2955 GeneralUtility::addInstance(RelationHandler::class, $relationHandlerProphecy->reveal());
2956 $relationHandlerProphecy->start(Argument::cetera())->shouldNotBeCalled();
2957 $relationHandlerProphecy->getValueArray(Argument::cetera())->shouldNotBeCalled();
2958
2959 $input = [
2960 'tableName' => 'aTable',
2961 'databaseRow' => [
2962 'aField' => '1,2,bar,foo',
2963 ],
2964 'processedTca' => [
2965 'columns' => [
2966 'aField' => [
2967 'config' => [
2968 'type' => 'select',
2969 'renderType' => 'selectSingle',
2970 'maxitems' => 1,
2971 'items' => [
2972 ['foo', 'foo', null, null],
2973 ],
2974 ],
2975 ],
2976 ],
2977 ],
2978 ];
2979
2980 $expected = $input;
2981 $expected['databaseRow']['aField'] = ['foo'];
2982 $expected['processedTca']['columns']['aField']['config']['items'] = [
2983 ['[ INVALID VALUE "bar" ]', 'bar', null, null],
2984 ['[ INVALID VALUE "2" ]', '2', null, null],
2985 ['[ INVALID VALUE "1" ]', '1', null, null],
2986 ['foo', 'foo', null, null],
2987 ];
2988 $this->assertEquals($expected, $this->subject->addData($input));
2989 }
2990
2991 /**
2992 * Data Provider
2993 *
2994 * @return array
2995 */
2996 public function processSelectFieldSetsCorrectValuesForMmRelationsDataProvider()
2997 {
2998 return array(
2999 'Relation with MM table and new status with default values' => [
3000 [
3001 'tableName' => 'aTable',
3002 'command' => 'new',
3003 'databaseRow' => [
3004 'uid' => 'NEW1234',
3005 'aField' => '24,35',
3006 ],
3007 'processedTca' => [
3008 'columns' => [
3009 'aField' => [
3010 'config' => [
3011 'type' => 'select',
3012 'renderType' => 'selectSingle',
3013 'maxitems' => 999,
3014 'MM' => 'mm_aTable_foreignTable',
3015 'foreign_table' => 'foreignTable',
3016 'items' => [],
3017 ],
3018 ],
3019 ],
3020 ],
3021 ],
3022 [
3023 'MM' => ''
3024 ],
3025 [
3026 24, 35
3027 ]
3028 ],
3029 'Relation with MM table and item array in list but no new status' => [
3030 [
3031 'tableName' => 'aTable',
3032 'databaseRow' => [
3033 'uid' => 'NEW1234',
3034 'aField' => '24,35',
3035 ],
3036 'processedTca' => [
3037 'columns' => [
3038 'aField' => [
3039 'config' => [
3040 'type' => 'select',
3041 'renderType' => 'selectSingle',
3042 'maxitems' => 999,
3043 'MM' => 'mm_aTable_foreignTabl