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