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