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