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