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