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