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