[BUGFIX] Add missing namespace parts
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Configuration / FlexForm / FlexFormToolsTest.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Doctrine\DBAL\Driver\Statement;
19 use Prophecy\Argument;
20 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidCombinedPointerFieldException;
21 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
22 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowException;
23 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowLoopException;
24 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidParentRowRootException;
25 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidPointerFieldValueException;
26 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidSinglePointerFieldException;
27 use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidTcaException;
28 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
29 use TYPO3\CMS\Core\Database\ConnectionPool;
30 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
31 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32 use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
33 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPostProcessHookReturnArray;
34 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPostProcessHookReturnEmptyArray;
35 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPostProcessHookReturnString;
36 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPostProcessHookThrowException;
37 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPreProcessHookReturnArray;
38 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPreProcessHookReturnEmptyArray;
39 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPreProcessHookReturnString;
40 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureIdentifierPreProcessHookThrowException;
41 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePostProcessHookReturnArray;
42 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePostProcessHookReturnString;
43 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePostProcessHookThrowException;
44 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePreProcessHookReturnEmptyString;
45 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePreProcessHookReturnObject;
46 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePreProcessHookReturnString;
47 use TYPO3\CMS\Core\Tests\Unit\Configuration\FlexForm\Fixtures\DataStructureParsePreProcessHookThrowException;
48 use TYPO3\CMS\Core\Utility\GeneralUtility;
49
50 /**
51 * Test case
52 */
53 class FlexFormToolsTest extends \TYPO3\Components\TestingFramework\Core\Unit\UnitTestCase
54 {
55 /**
56 * @test
57 */
58 public function getDataStructureIdentifierCallsRegisteredPreProcessHook()
59 {
60 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
61 DataStructureIdentifierPreProcessHookThrowException::class,
62 ];
63 $this->expectException(\RuntimeException::class);
64 $this->expectExceptionCode(1478098527);
65 (new FlexFormTools())->getDataStructureIdentifier([], 'aTableName', 'aFieldName', []);
66 }
67
68 /**
69 * @test
70 */
71 public function getDataStructureIdentifierThrowsExceptionIfPreProcessHookReturnsNoArray()
72 {
73 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
74 DataStructureIdentifierPreProcessHookReturnString::class
75 ];
76 $this->expectException(\RuntimeException::class);
77 $this->expectExceptionCode(1478096535);
78 (new FlexFormTools())->getDataStructureIdentifier([], 'aTableName', 'aFieldName', []);
79 }
80
81 /**
82 * @test
83 */
84 public function getDataStructureIdentifierUsesCasualLogicIfPreProcessHookReturnsNoIdentifier()
85 {
86 $fieldTca = [
87 'config' => [
88 'ds' => [
89 'default' => '<T3DataStructure>...'
90 ],
91 ],
92 ];
93 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
94 DataStructureIdentifierPreProcessHookReturnEmptyArray::class
95 ];
96 $expected = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
97 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []));
98 }
99
100 /**
101 * @test
102 */
103 public function getDataStructureIdentifierReturnsStringFromPreProcessHook()
104 {
105 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
106 DataStructureIdentifierPreProcessHookReturnArray::class
107 ];
108 $expected = '{"type":"myExtension","further":"data"}';
109 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier([], 'aTableName', 'aFieldName', []));
110 }
111
112 /**
113 * @test
114 */
115 public function getDataStructureIdentifierReturnsStringFromFirstMatchingPreProcessHook()
116 {
117 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
118 DataStructureIdentifierPreProcessHookReturnEmptyArray::class,
119 DataStructureIdentifierPreProcessHookReturnArray::class,
120 DataStructureIdentifierPreProcessHookThrowException::class
121 ];
122 $expected = '{"type":"myExtension","further":"data"}';
123 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier([], 'aTableName', 'aFieldName', []));
124 }
125
126 /**
127 * @test
128 */
129 public function getDataStructureIdentifierCallsRegisteredPostProcessHook()
130 {
131 $fieldTca = [
132 'config' => [
133 'ds' => [
134 'default' => '<T3DataStructure>...'
135 ],
136 ],
137 ];
138 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
139 DataStructureIdentifierPostProcessHookThrowException::class,
140 ];
141 $this->expectException(\RuntimeException::class);
142 $this->expectExceptionCode(1478342067);
143 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []);
144 }
145
146 /**
147 * @test
148 */
149 public function getDataStructureIdentifierThrowsExceptionIfPostProcessHookReturnsNoArray()
150 {
151 $fieldTca = [
152 'config' => [
153 'ds' => [
154 'default' => '<T3DataStructure>...'
155 ],
156 ],
157 ];
158 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
159 DataStructureIdentifierPostProcessHookReturnString::class
160 ];
161 $this->expectException(\RuntimeException::class);
162 $this->expectExceptionCode(1478350835);
163 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []);
164 }
165
166 /**
167 * @test
168 */
169 public function getDataStructureIdentifierThrowsExceptionIfPostProcessHookReturnsEmptyArray()
170 {
171 $fieldTca = [
172 'config' => [
173 'ds' => [
174 'default' => '<T3DataStructure>...'
175 ],
176 ],
177 ];
178 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
179 DataStructureIdentifierPostProcessHookReturnEmptyArray::class
180 ];
181 $this->expectException(\RuntimeException::class);
182 $this->expectExceptionCode(1478350835);
183 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []);
184 }
185
186 /**
187 * @test
188 */
189 public function getDataStructureIdentifierPostProcessHookCanEnrichIdentifier()
190 {
191 $fieldTca = [
192 'config' => [
193 'ds' => [
194 'default' => '<T3DataStructure>...'
195 ],
196 ],
197 ];
198 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
199 DataStructureIdentifierPostProcessHookReturnArray::class
200 ];
201 $expected = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default","myExtensionData":"foo"}';
202 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []));
203 }
204
205 /**
206 * @test
207 */
208 public function getDataStructureIdentifierThrowsExceptionIfDsIsNotAnArrayAndNoDsPointerField()
209 {
210 $fieldTca = [
211 'config' => [
212 'ds' => 'someStringOnly',
213 // no ds_pointerField,
214 ],
215 ];
216 $this->expectException(\RuntimeException::class);
217 $this->expectExceptionCode(1463826960);
218 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []);
219 }
220
221 /**
222 * @test
223 */
224 public function getDataStructureIdentifierReturnsDefaultIfDsIsSetButNoDsPointerField()
225 {
226 $fieldTca = [
227 'config' => [
228 'ds' => [
229 'default' => '<T3DataStructure>...'
230 ],
231 ],
232 ];
233 $expected = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
234 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []));
235 }
236
237 /**
238 * @test
239 */
240 public function getDataStructureIdentifierThrowsExceptionsIfNoDsPointerFieldIsSetAndDefaultDoesNotExist()
241 {
242 $fieldTca = [
243 'config' => [
244 'ds' => [],
245 ],
246 ];
247 $this->expectException(InvalidTcaException::class);
248 $this->expectExceptionCode(1463652560);
249 $this->assertSame('default', (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []));
250 }
251
252 /**
253 * @test
254 */
255 public function getDataStructureIdentifierThrowsExceptionIfPointerFieldStringHasMoreThanTwoFields()
256 {
257 $fieldTca = [
258 'config' => [
259 'ds' => [],
260 'ds_pointerField' => 'first,second,third',
261 ],
262 ];
263 $this->expectException(\RuntimeException::class);
264 $this->expectExceptionCode(1463577497);
265 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', []);
266 }
267
268 /**
269 * @test
270 */
271 public function getDataStructureIdentifierThrowsExceptionIfPointerFieldWithStringSingleFieldDoesNotExist()
272 {
273 $fieldTca = [
274 'config' => [
275 'ds' => [],
276 'ds_pointerField' => 'notExist',
277 ],
278 ];
279 $row = [
280 'foo' => '',
281 ];
282 $this->expectException(\RuntimeException::class);
283 $this->expectExceptionCode(1463578899);
284 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
285 }
286
287 /**
288 * @test
289 */
290 public function getDataStructureIdentifierThrowsExceptionIfPointerFieldSWithTwoFieldsFirstDoesNotExist()
291 {
292 $fieldTca = [
293 'config' => [
294 'ds' => [],
295 'ds_pointerField' => 'notExist,second',
296 ],
297 ];
298 $row = [
299 'second' => '',
300 ];
301 $this->expectException(\RuntimeException::class);
302 $this->expectExceptionCode(1463578899);
303 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
304 }
305
306 /**
307 * @test
308 */
309 public function getDataStructureIdentifierThrowsExceptionIfPointerFieldSWithTwoFieldsSecondDoesNotExist()
310 {
311 $fieldTca = [
312 'config' => [
313 'ds' => [],
314 'ds_pointerField' => 'first,notExist',
315 ],
316 ];
317 $row = [
318 'first' => '',
319 ];
320 $this->expectException(\RuntimeException::class);
321 $this->expectExceptionCode(1463578900);
322 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
323 }
324
325 /**
326 * @test
327 */
328 public function getDataStructureIdentifierReturnsPointerFieldValueIfDataStructureExists()
329 {
330 $fieldTca = [
331 'config' => [
332 'ds' => [
333 'thePointerValue' => 'FILE:...'
334 ],
335 'ds_pointerField' => 'aField'
336 ],
337 ];
338 $row = [
339 'aField' => 'thePointerValue',
340 ];
341 $expected = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"thePointerValue"}';
342 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row));
343 }
344
345 /**
346 * @test
347 */
348 public function getDataStructureIdentifierReturnsDefaultIfPointerFieldValueDoesNotExist()
349 {
350 $fieldTca = [
351 'config' => [
352 'ds' => [
353 'default' => 'theDataStructure'
354 ],
355 'ds_pointerField' => 'aField'
356 ],
357 ];
358 $row = [
359 'aField' => 'thePointerValue',
360 ];
361 $expected = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
362 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row));
363 }
364
365 /**
366 * @test
367 */
368 public function getDataStructureIdentifierThrowsExceptionIfPointerFieldValueDoesNotExistAndDefaultToo()
369 {
370 $fieldTca = [
371 'config' => [
372 'ds' => [
373 'aDifferentDataStructure' => 'aDataStructure'
374 ],
375 'ds_pointerField' => 'aField'
376 ],
377 ];
378 $row = [
379 'aField' => 'aNotDefinedDataStructure',
380 ];
381 $this->expectException(InvalidSinglePointerFieldException::class);
382 $this->expectExceptionCode(1463653197);
383 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
384 }
385
386 /**
387 * Data provider for getDataStructureIdentifierReturnsValidNameForTwoFieldCombinations
388 */
389 public function getDataStructureIdentifierReturnsValidNameForTwoFieldCombinationsDataProvider()
390 {
391 return [
392 'direct match of two fields' => [
393 [
394 // $row
395 'firstField' => 'firstValue',
396 'secondField' => 'secondValue',
397 ],
398 [
399 // registered data structure names
400 'firstValue,secondValue' => '',
401 ],
402 // expected name
403 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"firstValue,secondValue"}'
404 ],
405 'match on first field, * for second' => [
406 [
407 'firstField' => 'firstValue',
408 'secondField' => 'secondValue',
409 ],
410 [
411 'firstValue,*' => '',
412 ],
413 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"firstValue,*"}'
414 ],
415 '@deprecated match on second field, * for first' => [
416 [
417 'firstField' => 'firstValue',
418 'secondField' => 'secondValue',
419 ],
420 [
421 'secondValue,*' => '',
422 ],
423 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"secondValue,*"}'
424 ],
425 'match on second field, * for first' => [
426 [
427 'firstField' => 'firstValue',
428 'secondField' => 'secondValue',
429 ],
430 [
431 '*,secondValue' => '',
432 ],
433 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"*,secondValue"}'
434 ],
435 'match on first field only' => [
436 [
437 'firstField' => 'firstValue',
438 'secondField' => 'secondValue',
439 ],
440 [
441 'firstValue' => '',
442 ],
443 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"firstValue"}'
444 ],
445 'fallback to default' => [
446 [
447 'firstField' => 'firstValue',
448 'secondField' => 'secondValue',
449 ],
450 [
451 'default' => '',
452 ],
453 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}'
454 ],
455 'chain falls through with no match on second value to *' => [
456 [
457 'firstField' => 'firstValue',
458 'secondField' => 'noMatch',
459 ],
460 [
461 'firstValue,secondValue' => '',
462 'firstValue,*' => '',
463 ],
464 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"firstValue,*"}'
465 ],
466 'chain falls through with no match on first value to *' => [
467 [
468 'firstField' => 'noMatch',
469 'secondField' => 'secondValue',
470 ],
471 [
472 'firstValue,secondValue' => '',
473 '*,secondValue' => '',
474 ],
475 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"*,secondValue"}'
476 ],
477 '@deprecated chain falls through with no match on first value to *' => [
478 [
479 'firstField' => 'noMatch',
480 'secondField' => 'secondValue',
481 ],
482 [
483 'firstValue,secondValue' => '',
484 'secondValue,*' => '',
485 ],
486 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"secondValue,*"}'
487 ],
488 'chain falls through with no match on any field to default' => [
489 [
490 'firstField' => 'noMatch',
491 'secondField' => 'noMatchToo',
492 ],
493 [
494 'firstValue,secondValue' => '',
495 'secondValue,*' => '',
496 'default' => '',
497 ],
498 '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}'
499 ],
500 ];
501 }
502
503 /**
504 * @test
505 * @dataProvider getDataStructureIdentifierReturnsValidNameForTwoFieldCombinationsDataProvider
506 * @param array $row
507 * @param array $ds
508 * @param $expected
509 */
510 public function getDataStructureIdentifierReturnsValidNameForTwoFieldCombinations(array $row, array $ds, string $expected)
511 {
512 $fieldTca = [
513 'config' => [
514 'ds' => $ds,
515 'ds_pointerField' => 'firstField,secondField'
516 ],
517 ];
518 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row));
519 }
520
521 /**
522 * @test
523 */
524 public function getDataStructureIdentifierThrowsExceptionForTwoFieldsWithNoMatchAndNoDefault()
525 {
526 $fieldTca = [
527 'config' => [
528 'ds' => [
529 'firstValue,secondValue' => '',
530 ],
531 'ds_pointerField' => 'firstField,secondField'
532 ],
533 ];
534 $row = [
535 'firstField' => 'noMatch',
536 'secondField' => 'noMatchToo',
537 ];
538 $this->expectException(InvalidCombinedPointerFieldException::class);
539 $this->expectExceptionCode(1463678524);
540 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
541 }
542
543 /**
544 * @test
545 */
546 public function getDataStructureIdentifierThrowsExceptionIfParentRowLookupFails()
547 {
548 $fieldTca = [
549 'config' => [
550 'ds_pointerField' => 'tx_templavoila_ds',
551 'ds_pointerField_searchParent' => 'pid',
552 ]
553 ];
554 $row = [
555 'pid' => 42,
556 'tx_templavoila_ds' => null,
557 ];
558
559 // Prophecies and revelations for a lot of the database stack classes
560 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
561 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
562 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
563 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
564 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
565 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
566 $statementProphecy = $this->prophesize(Statement::class);
567
568 // Register connection pool revelation in framework, this is the entry point used by system under test
569 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
570
571 // Simulate method call flow on database objects and verify correct query is built
572 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
573 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
574 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
575 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
576 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
577 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
578 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
579 $queryBuilderProphecy->createNamedParameter(42, 1)->willReturn(42);
580 $expressionBuilderProphecy->eq('uid', 42)->shouldBeCalled()->willReturn('uid = 42');
581 $queryBuilderProphecy->where('uid = 42')->shouldBeCalled()->willReturn($queryBuilderRevelation);
582 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
583
584 // Error case that is tested here: Do not return a valid parent row from db -> exception should be thrown
585 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(0);
586 $this->expectException(InvalidParentRowException::class);
587 $this->expectExceptionCode(1463833794);
588 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
589 }
590
591 /**
592 * @test
593 */
594 public function getDataStructureIdentifierThrowsExceptionIfParentRowsFormALoop()
595 {
596 $fieldTca = [
597 'config' => [
598 'ds_pointerField' => 'tx_templavoila_ds',
599 'ds_pointerField_searchParent' => 'pid',
600 ]
601 ];
602 $initialRow = [
603 'uid' => 3,
604 'pid' => 2,
605 'tx_templavoila_ds' => null,
606 ];
607 $secondRow = [
608 'uid' => 2,
609 'pid' => 1,
610 'tx_templavoila_ds' => null,
611 ];
612 $thirdRow = [
613 'uid' => 1,
614 'pid' => 3,
615 'tx_templavoila_ds' => null,
616 ];
617
618 // Prophecies and revelations for a lot of the database stack classes
619 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
620 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
621 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
622 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
623 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
624 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
625 $statementProphecy = $this->prophesize(Statement::class);
626
627 // Register connection pool revelation in framework, this is the entry point used by system under test
628 // Two queries are done, so we need two instances
629 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
630 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
631
632 // Simulate method call flow on database objects and verify correct query is built
633 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
634 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
635 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
636 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
637 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
638 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
639 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
640 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
641 $queryBuilderProphecy->createNamedParameter(1, 1)->willReturn(1);
642 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
643 $expressionBuilderProphecy->eq('uid', 1)->shouldBeCalled()->willReturn('uid = 1');
644 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
645 $queryBuilderProphecy->where('uid = 1')->shouldBeCalled()->willReturn($queryBuilderRevelation);
646 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
647 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
648
649 // First db call returns $secondRow, second returns $thirdRow, which points back to $initialRow -> exception
650 $statementProphecy->fetch()->willReturn($secondRow, $thirdRow);
651
652 $this->expectException(InvalidParentRowLoopException::class);
653 $this->expectExceptionCode(1464110956);
654 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow);
655 }
656
657 /**
658 * @test
659 */
660 public function getDataStructureIdentifierThrowsExceptionIfNoValidPointerFoundUntilRoot()
661 {
662 $fieldTca = [
663 'config' => [
664 'ds_pointerField' => 'tx_templavoila_ds',
665 'ds_pointerField_searchParent' => 'pid',
666 ]
667 ];
668 $initialRow = [
669 'uid' => 3,
670 'pid' => 2,
671 'tx_templavoila_ds' => null,
672 ];
673 $secondRow = [
674 'uid' => 2,
675 'pid' => 1,
676 'tx_templavoila_ds' => null,
677 ];
678 $thirdRow = [
679 'uid' => 1,
680 'pid' => 0,
681 'tx_templavoila_ds' => null,
682 ];
683
684 // Prophecies and revelations for a lot of the database stack classes
685 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
686 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
687 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
688 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
689 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
690 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
691 $statementProphecy = $this->prophesize(Statement::class);
692
693 // Register connection pool revelation in framework, this is the entry point used by system under test
694 // Two queries are done, so we need two instances
695 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
696 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
697
698 // Simulate method call flow on database objects and verify correct query is built
699 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
700 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
701 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
702 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
703 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
704 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
705 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
706 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
707 $queryBuilderProphecy->createNamedParameter(1, 1)->willReturn(1);
708 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
709 $expressionBuilderProphecy->eq('uid', 1)->shouldBeCalled()->willReturn('uid = 1');
710 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
711 $queryBuilderProphecy->where('uid = 1')->shouldBeCalled()->willReturn($queryBuilderRevelation);
712 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
713 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
714
715 // First db call returns $secondRow, second returns $thirdRow. $thirdRow has pid 0 and still no ds -> exception
716 $statementProphecy->fetch()->willReturn($secondRow, $thirdRow);
717
718 $this->expectException(InvalidParentRowRootException::class);
719 $this->expectExceptionCode(1464112555);
720 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow);
721 }
722
723 /**
724 * @test
725 */
726 public function getDataStructureIdentifierThrowsExceptionIfNoValidPointerValueFound()
727 {
728 $fieldTca = [
729 'config' => [
730 'ds_pointerField' => 'aPointerField',
731 ]
732 ];
733 $row = [
734 'aPointerField' => null,
735 ];
736 $this->expectException(InvalidPointerFieldValueException::class);
737 $this->expectExceptionCode(1464114011);
738 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
739 }
740
741 /**
742 * @test
743 */
744 public function getDataStructureIdentifierThrowsExceptionIfResorvedPointerValueIsIntegerButDsFieldNameIsNotConfigured()
745 {
746 $fieldTca = [
747 'config' => [
748 'ds_pointerField' => 'aPointerField',
749 ]
750 ];
751 $row = [
752 'aPointerField' => 3,
753 ];
754 $this->expectException(InvalidTcaException::class);
755 $this->expectExceptionCode(1464115639);
756 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
757 }
758
759 /**
760 * @test
761 */
762 public function getDataStructureIdentifierThrowsExceptionIfDsTableFieldIsMisconfigured()
763 {
764 $fieldTca = [
765 'config' => [
766 'ds_pointerField' => 'aPointerField',
767 'ds_tableField' => 'misconfigured',
768 ]
769 ];
770 $row = [
771 'aPointerField' => 3,
772 ];
773 $this->expectException(InvalidTcaException::class);
774 $this->expectExceptionCode(1464116002);
775 (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row);
776 }
777
778 /**
779 * @test
780 */
781 public function getDataStructureIdentifierReturnsValidIdentifierForPointerField()
782 {
783 $fieldTca = [
784 'config' => [
785 'ds_pointerField' => 'aPointerField',
786 ]
787 ];
788 $row = [
789 'uid' => 42,
790 'aPointerField' => '<T3DataStructure>...',
791 ];
792 $expected = '{"type":"record","tableName":"aTableName","uid":42,"fieldName":"aPointerField"}';
793 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row));
794 }
795
796 /**
797 * @test
798 */
799 public function getDataStructureIdentifierReturnsValidIdentifierForParentLookup()
800 {
801 $fieldTca = [
802 'config' => [
803 'ds_pointerField' => 'tx_templavoila_ds',
804 'ds_pointerField_searchParent' => 'pid',
805 ]
806 ];
807 $initialRow = [
808 'uid' => 3,
809 'pid' => 2,
810 'tx_templavoila_ds' => null,
811 ];
812 $secondRow = [
813 'uid' => 2,
814 'pid' => 1,
815 'tx_templavoila_ds' => 0,
816 ];
817 $thirdRow = [
818 'uid' => 1,
819 'pid' => 0,
820 'tx_templavoila_ds' => '<T3DataStructure>...',
821 ];
822
823 // Prophecies and revelations for a lot of the database stack classes
824 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
825 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
826 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
827 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
828 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
829 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
830 $statementProphecy = $this->prophesize(Statement::class);
831
832 // Register connection pool revelation in framework, this is the entry point used by system under test
833 // Two queries are done, so we need two instances
834 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
835 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
836
837 // Simulate method call flow on database objects and verify correct query is built
838 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
839 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
840 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
841 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
842 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
843 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
844 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
845 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
846 $queryBuilderProphecy->createNamedParameter(1, 1)->willReturn(1);
847 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
848 $expressionBuilderProphecy->eq('uid', 1)->shouldBeCalled()->willReturn('uid = 1');
849 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
850 $queryBuilderProphecy->where('uid = 1')->shouldBeCalled()->willReturn($queryBuilderRevelation);
851 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
852 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
853
854 // First db call returns $secondRow, second returns $thirdRow. $thirdRow resolves ds
855 $statementProphecy->fetch()->willReturn($secondRow, $thirdRow);
856
857 $expected = '{"type":"record","tableName":"aTableName","uid":1,"fieldName":"tx_templavoila_ds"}';
858 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow));
859 }
860
861 /**
862 * @test
863 */
864 public function getDataStructureIdentifierReturnsValidIdentifierForParentLookupAndBreaksLoop()
865 {
866 $fieldTca = [
867 'config' => [
868 'ds_pointerField' => 'tx_templavoila_ds',
869 'ds_pointerField_searchParent' => 'pid',
870 ]
871 ];
872 $initialRow = [
873 'uid' => 3,
874 'pid' => 2,
875 'tx_templavoila_ds' => null,
876 ];
877 $secondRow = [
878 'uid' => 2,
879 'pid' => 1,
880 'tx_templavoila_ds' => '<T3DataStructure>...',
881 ];
882
883 // Prophecies and revelations for a lot of the database stack classes
884 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
885 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
886 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
887 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
888 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
889 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
890 $statementProphecy = $this->prophesize(Statement::class);
891
892 // Register connection pool revelation in framework, this is the entry point used by system under test
893 // Two queries are done, so we need two instances
894 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
895
896 // Simulate method call flow on database objects and verify correct query is built
897 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
898 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
899 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
900 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
901 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
902 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
903 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
904 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
905 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
906 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
907 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
908 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
909
910 // First db call returns $secondRow. $secendRow resolves DS and does not look further up
911 $statementProphecy->fetch()->willReturn($secondRow);
912
913 $expected = '{"type":"record","tableName":"aTableName","uid":2,"fieldName":"tx_templavoila_ds"}';
914 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow));
915 }
916
917 /**
918 * @test
919 */
920 public function getDataStructureIdentifierReturnsValidIdentifierForParentLookupAndPrefersSubField()
921 {
922 $fieldTca = [
923 'config' => [
924 'ds_pointerField' => 'tx_templavoila_ds',
925 'ds_pointerField_searchParent' => 'pid',
926 'ds_pointerField_searchParent_subField' => 'tx_templavoila_next_ds',
927 ]
928 ];
929 $initialRow = [
930 'uid' => 3,
931 'pid' => 2,
932 'tx_templavoila_ds' => null,
933 'tx_templavoila_next_ds' => null,
934 ];
935 $secondRow = [
936 'uid' => 2,
937 'pid' => 1,
938 'tx_templavoila_ds' => '<T3DataStructure>...',
939 'tx_templavoila_next_ds' => 'anotherDataStructure',
940 ];
941
942 // Prophecies and revelations for a lot of the database stack classes
943 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
944 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
945 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
946 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
947 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
948 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
949 $statementProphecy = $this->prophesize(Statement::class);
950
951 // Register connection pool revelation in framework, this is the entry point used by system under test
952 // Two queries are done, so we need two instances
953 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
954
955 // Simulate method call flow on database objects and verify correct query is built
956 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
957 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
958 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
959 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
960 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
961 $queryBuilderProphecy->addSelect('tx_templavoila_next_ds')->shouldBeCalled();
962 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
963 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
964 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
965 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
966 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
967 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
968 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
969
970 // First db call returns $secondRow. $secendRow resolves DS and does not look further up
971 $statementProphecy->fetch()->willReturn($secondRow);
972
973 $expected = '{"type":"record","tableName":"aTableName","uid":2,"fieldName":"tx_templavoila_next_ds"}';
974 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow));
975 }
976
977 /**
978 * @test
979 */
980 public function getDataStructureIdentifierReturnsValidIdentifierForTableAndFieldPointer()
981 {
982 $fieldTca = [
983 'config' => [
984 'ds_pointerField' => 'aPointerField',
985 'ds_tableField' => 'foreignTableName:foreignTableField',
986 ]
987 ];
988 $row = [
989 'uid' => 3,
990 'pid' => 2,
991 'aPointerField' => 42,
992 ];
993 $expected = '{"type":"record","tableName":"foreignTableName","uid":42,"fieldName":"foreignTableField"}';
994 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $row));
995 }
996
997 /**
998 * @test
999 */
1000 public function getDataStructureIdentifierReturnsValidIdentifierForTableAndFieldPointerWithParentLookup()
1001 {
1002 $fieldTca = [
1003 'config' => [
1004 'ds_pointerField' => 'tx_templavoila_ds',
1005 'ds_pointerField_searchParent' => 'pid',
1006 'ds_pointerField_searchParent_subField' => 'tx_templavoila_next_ds',
1007 'ds_tableField' => 'foreignTableName:foreignTableField',
1008 ]
1009 ];
1010 $initialRow = [
1011 'uid' => 3,
1012 'pid' => 2,
1013 'tx_templavoila_ds' => null,
1014 'tx_templavoila_next_ds' => null,
1015 ];
1016 $secondRow = [
1017 'uid' => 2,
1018 'pid' => 1,
1019 'tx_templavoila_ds' => '<T3DataStructure>...',
1020 'tx_templavoila_next_ds' => '42',
1021 ];
1022
1023 // Prophecies and revelations for a lot of the database stack classes
1024 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
1025 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
1026 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
1027 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
1028 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
1029 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
1030 $statementProphecy = $this->prophesize(Statement::class);
1031
1032 // Register connection pool revelation in framework, this is the entry point used by system under test
1033 // Two queries are done, so we need two instances
1034 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
1035
1036 // Simulate method call flow on database objects and verify correct query is built
1037 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1038 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
1039 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
1040 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
1041 $queryBuilderProphecy->select('uid', 'pid', 'tx_templavoila_ds')->shouldBeCalled();
1042 $queryBuilderProphecy->addSelect('tx_templavoila_next_ds')->shouldBeCalled();
1043 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1044 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
1045 $queryBuilderProphecy->createNamedParameter(2, 1)->willReturn(2);
1046 $expressionBuilderProphecy->eq('uid', 2)->shouldBeCalled()->willReturn('uid = 2');
1047 $queryBuilderProphecy->where('uid = 2')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1048 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
1049 $statementProphecy->rowCount()->shouldBeCalled()->willReturn(1);
1050
1051 // First db call returns $secondRow. $secendRow resolves DS and does not look further up
1052 $statementProphecy->fetch()->willReturn($secondRow);
1053
1054 $expected = '{"type":"record","tableName":"foreignTableName","uid":42,"fieldName":"foreignTableField"}';
1055 $this->assertSame($expected, (new FlexFormTools())->getDataStructureIdentifier($fieldTca, 'aTableName', 'aFieldName', $initialRow));
1056 }
1057
1058 /**
1059 * @test
1060 */
1061 public function parseDataStructureByIdentifierThrowsExceptionWithEmptyString()
1062 {
1063 $this->expectException(InvalidIdentifierException::class);
1064 $this->expectExceptionCode(1478100828);
1065 (new FlexFormTools())->parseDataStructureByIdentifier('');
1066 }
1067
1068 /**
1069 * @test
1070 */
1071 public function parseDataStructureByIdentifierIfIdentifierDoesNotResolveToArray()
1072 {
1073 $this->expectException(\RuntimeException::class);
1074 $this->expectExceptionCode(1478345642);
1075 (new FlexFormTools())->parseDataStructureByIdentifier('egon');
1076 }
1077
1078 /**
1079 * @test
1080 */
1081 public function parseDataStructureByIdentifierCallsRegisteredHook()
1082 {
1083 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1084 DataStructureParsePreProcessHookThrowException::class,
1085 ];
1086 $this->expectException(\RuntimeException::class);
1087 $this->expectExceptionCode(1478112411);
1088 (new FlexFormTools())->parseDataStructureByIdentifier('{"some":"input"}');
1089 }
1090
1091 /**
1092 * @test
1093 */
1094 public function parseDataStructureByIdentifierThrowsExceptionIfHookReturnsNoString()
1095 {
1096 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1097 DataStructureParsePreProcessHookReturnObject::class
1098 ];
1099 $this->expectException(\RuntimeException::class);
1100 $this->expectExceptionCode(1478168512);
1101 (new FlexFormTools())->parseDataStructureByIdentifier('{"some":"input"}');
1102 }
1103
1104 /**
1105 * @test
1106 */
1107 public function parseDataStructureByIdentifierUsesCasualLogicIfHookReturnsNoIdentifier()
1108 {
1109 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1110 DataStructureParsePreProcessHookReturnEmptyString::class
1111 ];
1112 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1113 <T3DataStructure>
1114 <sheets></sheets>
1115 </T3DataStructure>
1116 ';
1117 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1118 $expected = [
1119 'sheets' => '',
1120 ];
1121 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1122 }
1123
1124 /**
1125 * @test
1126 */
1127 public function parseDataStructureByIdentifierParsesDataStructureReturnedByHook()
1128 {
1129 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1130 DataStructureParsePreProcessHookReturnString::class
1131 ];
1132 $identifier = '{"type":"myExtension"}';
1133 $expected = [
1134 'sheets' => '',
1135 ];
1136 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1137 }
1138
1139 /**
1140 * @test
1141 */
1142 public function parseDataStructureByIdentifierParsesDataStructureFromFirstMatchingHook()
1143 {
1144 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1145 DataStructureParsePreProcessHookReturnEmptyString::class,
1146 DataStructureParsePreProcessHookReturnString::class,
1147 DataStructureParsePreProcessHookThrowException::class
1148 ];
1149 $identifier = '{"type":"myExtension"}';
1150 $expected = [
1151 'sheets' => '',
1152 ];
1153 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1154 }
1155
1156 /**
1157 * @test
1158 */
1159 public function parseDataStructureByIdentifierThrowsExceptionForInvalidSyntax()
1160 {
1161 $this->expectException(InvalidIdentifierException::class);
1162 $this->expectExceptionCode(1478104554);
1163 (new FlexFormTools())->parseDataStructureByIdentifier('{"type":"bernd"}');
1164 }
1165
1166 /**
1167 * @test
1168 */
1169 public function parseDataStructureByIdentifierThrowsExceptionForIncompleteTcaSyntax()
1170 {
1171 $this->expectException(\RuntimeException::class);
1172 $this->expectExceptionCode(1478113471);
1173 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName"}';
1174 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1175 }
1176
1177 /**
1178 * @test
1179 */
1180 public function parseDataStructureByIdentifierThrowsExceptionForInvalidTcaSyntaxPointer()
1181 {
1182 $this->expectException(InvalidIdentifierException::class);
1183 $this->expectExceptionCode(1478105491);
1184 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1185 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1186 }
1187
1188 /**
1189 * @test
1190 */
1191 public function parseDataStructureByIdentifierResolvesTcaSyntaxPointer()
1192 {
1193 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1194 <T3DataStructure>
1195 <sheets></sheets>
1196 </T3DataStructure>
1197 ';
1198 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1199 $expected = [
1200 'sheets' => '',
1201 ];
1202 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1203 }
1204
1205 /**
1206 * @test
1207 */
1208 public function parseDataStructureByIdentifierThrowsExceptionForIncompleteRecordSyntax()
1209 {
1210 $this->expectException(\RuntimeException::class);
1211 $this->expectExceptionCode(1478113873);
1212 $identifier = '{"type":"record","tableName":"foreignTableName","uid":42}';
1213 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1214 }
1215
1216 /**
1217 * @test
1218 */
1219 public function parseDataStructureByIdentifierResolvesRecordSyntaxPointer()
1220 {
1221 // Prophecies and revelations for a lot of the database stack classes
1222 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
1223 $queryBuilderRevelation = $queryBuilderProphecy->reveal();
1224 $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
1225 $queryRestrictionContainerProphecy = $this->prophesize(QueryRestrictionContainerInterface::class);
1226 $queryRestrictionContainerRevelation = $queryRestrictionContainerProphecy->reveal();
1227 $expressionBuilderProphecy = $this->prophesize(ExpressionBuilder::class);
1228 $statementProphecy = $this->prophesize(Statement::class);
1229
1230 // Register connection pool revelation in framework, this is the entry point used by system under test
1231 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
1232
1233 // Simulate method call flow on database objects and verify correct query is built
1234 $connectionPoolProphecy->getQueryBuilderForTable('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1235 $queryRestrictionContainerProphecy->removeAll()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
1236 $queryRestrictionContainerProphecy->add(Argument::cetera())->shouldBeCalled();
1237 $queryBuilderProphecy->getRestrictions()->shouldBeCalled()->willReturn($queryRestrictionContainerRevelation);
1238 $queryBuilderProphecy->select('dataprot')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1239 $queryBuilderProphecy->from('aTableName')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1240 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionBuilderProphecy->reveal());
1241 $queryBuilderProphecy->createNamedParameter(42, 1)->willReturn(42);
1242 $expressionBuilderProphecy->eq('uid', 42)->shouldBeCalled()->willReturn('uid = 42');
1243 $queryBuilderProphecy->where('uid = 42')->shouldBeCalled()->willReturn($queryBuilderRevelation);
1244 $queryBuilderProphecy->execute()->shouldBeCalled()->willReturn($statementProphecy->reveal());
1245 $statementProphecy->fetchColumn(0)->willReturn('
1246 <T3DataStructure>
1247 <sheets></sheets>
1248 </T3DataStructure>
1249 ');
1250 $identifier = '{"type":"record","tableName":"aTableName","uid":42,"fieldName":"dataprot"}';
1251 $expected = [
1252 'sheets' => '',
1253 ];
1254 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1255 }
1256
1257 /**
1258 * @test
1259 */
1260 public function parseDataStructureByIdentifierThrowsExceptionIfDataStructureFileDoesNotExist()
1261 {
1262 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default']
1263 = 'FILE:EXT:core/Does/Not/Exist.xml';
1264 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1265 $this->expectException(\RuntimeException::class);
1266 $this->expectExceptionCode(1478105826);
1267 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1268 }
1269
1270 /**
1271 * @test
1272 */
1273 public function parseDataStructureByIdentifierFetchesFromFile()
1274 {
1275 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default']
1276 = ' FILE:EXT:core/Tests/Unit/Configuration/FlexForm/Fixtures/DataStructureWithSheet.xml ';
1277 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1278 $expected = [
1279 'sheets' => [
1280 'sDEF' => [
1281 'ROOT' => [
1282 'type' => 'array',
1283 'el' => [
1284 'aFlexField' => [
1285 'TCEforms' => [
1286 'label' => 'aFlexFieldLabel',
1287 'config' => [
1288 'type' => 'input',
1289 ],
1290 ],
1291 ],
1292 ],
1293 'TCEforms' => [
1294 'sheetTitle' => 'aTitle',
1295 ],
1296 ],
1297 ],
1298 ]
1299 ];
1300 $this->assertEquals($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1301 }
1302
1303 /**
1304 * @test
1305 */
1306 public function parseDataStructureByIdentifierThrowsExceptionForInvalidXmlStructure()
1307 {
1308 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1309 <T3DataStructure>
1310 <sheets>
1311 <bar>
1312 </sheets>
1313 </T3DataStructure>
1314 ';
1315 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1316 $this->expectException(InvalidIdentifierException::class);
1317 $this->expectExceptionCode(1478106090);
1318 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1319 }
1320
1321 /**
1322 * @test
1323 */
1324 public function parseDataStructureByIdentifierThrowsExceptionIfStructureHasBothSheetAndRoot()
1325 {
1326 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1327 <T3DataStructure>
1328 <ROOT></ROOT>
1329 <sheets></sheets>
1330 </T3DataStructure>
1331 ';
1332 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1333 $this->expectException(\RuntimeException::class);
1334 $this->expectExceptionCode(1440676540);
1335 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1336 }
1337
1338 /**
1339 * @test
1340 */
1341 public function parseDataStructureByIdentifierCreatesDefaultSheet()
1342 {
1343 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1344 <T3DataStructure>
1345 <ROOT>
1346 <TCEforms>
1347 <sheetTitle>aTitle</sheetTitle>
1348 </TCEforms>
1349 <type>array</type>
1350 <el>
1351 <aFlexField>
1352 <TCEforms>
1353 <label>aFlexFieldLabel</label>
1354 <config>
1355 <type>input</type>
1356 </config>
1357 </TCEforms>
1358 </aFlexField>
1359 </el>
1360 </ROOT>
1361 </T3DataStructure>
1362 ';
1363 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1364 $expected = [
1365 'sheets' => [
1366 'sDEF' => [
1367 'ROOT' => [
1368 'type' => 'array',
1369 'el' => [
1370 'aFlexField' => [
1371 'TCEforms' => [
1372 'label' => 'aFlexFieldLabel',
1373 'config' => [
1374 'type' => 'input',
1375 ],
1376 ],
1377 ],
1378 ],
1379 'TCEforms' => [
1380 'sheetTitle' => 'aTitle',
1381 ],
1382 ],
1383 ],
1384 ]
1385 ];
1386 $this->assertEquals($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1387 }
1388
1389 /**
1390 * @test
1391 */
1392 public function parseDataStructureByIdentifierResolvesExtReferenceForSingleSheets()
1393 {
1394 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1395 <T3DataStructure>
1396 <sheets>
1397 <aSheet>
1398 EXT:core/Tests/Unit/Configuration/FlexForm/Fixtures/DataStructureOfSingleSheet.xml
1399 </aSheet>
1400 </sheets>
1401 </T3DataStructure>
1402 ';
1403 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1404 $expected = [
1405 'sheets' => [
1406 'aSheet' => [
1407 'ROOT' => [
1408 'type' => 'array',
1409 'el' => [
1410 'aFlexField' => [
1411 'TCEforms' => [
1412 'label' => 'aFlexFieldLabel',
1413 'config' => [
1414 'type' => 'input',
1415 ],
1416 ],
1417 ],
1418 ],
1419 'TCEforms' => [
1420 'sheetTitle' => 'aTitle',
1421 ],
1422 ],
1423 ],
1424 ]
1425 ];
1426 $this->assertEquals($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1427 }
1428
1429 /**
1430 * @test
1431 */
1432 public function parseDataStructureByIdentifierResolvesExtReferenceForSingleSheetsWithFilePrefix()
1433 {
1434 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1435 <T3DataStructure>
1436 <sheets>
1437 <aSheet>
1438 FILE:EXT:core/Tests/Unit/Configuration/FlexForm/Fixtures/DataStructureOfSingleSheet.xml
1439 </aSheet>
1440 </sheets>
1441 </T3DataStructure>
1442 ';
1443 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1444 $expected = [
1445 'sheets' => [
1446 'aSheet' => [
1447 'ROOT' => [
1448 'type' => 'array',
1449 'el' => [
1450 'aFlexField' => [
1451 'TCEforms' => [
1452 'label' => 'aFlexFieldLabel',
1453 'config' => [
1454 'type' => 'input',
1455 ],
1456 ],
1457 ],
1458 ],
1459 'TCEforms' => [
1460 'sheetTitle' => 'aTitle',
1461 ],
1462 ],
1463 ],
1464 ]
1465 ];
1466 $this->assertEquals($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1467 }
1468
1469 /**
1470 * @test
1471 */
1472 public function parseDataStructureByIdentifierCallsPostProcessHook()
1473 {
1474 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1475 <T3DataStructure>
1476 <sheets></sheets>
1477 </T3DataStructure>
1478 ';
1479 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1480 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1481 DataStructureParsePostProcessHookThrowException::class,
1482 ];
1483 $this->expectException(\RuntimeException::class);
1484 $this->expectExceptionCode(1478351691);
1485 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1486 }
1487
1488 /**
1489 * @test
1490 */
1491 public function parseDataStructureByIdentifierThrowsExceptionIfPostProcessHookReturnsNoArray()
1492 {
1493 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1494 <T3DataStructure>
1495 <sheets></sheets>
1496 </T3DataStructure>
1497 ';
1498 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1499 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1500 DataStructureParsePostProcessHookReturnString::class,
1501 ];
1502 $this->expectException(\RuntimeException::class);
1503 $this->expectExceptionCode(1478350806);
1504 (new FlexFormTools())->parseDataStructureByIdentifier($identifier);
1505 }
1506
1507 /**
1508 * @test
1509 */
1510 public function parseDataStructureByIdentifierPostProcessHookManipulatesDataStructure()
1511 {
1512 $GLOBALS['TCA']['aTableName']['columns']['aFieldName']['config']['ds']['default'] = '
1513 <T3DataStructure>
1514 <sheets></sheets>
1515 </T3DataStructure>
1516 ';
1517 $identifier = '{"type":"tca","tableName":"aTableName","fieldName":"aFieldName","dataStructureKey":"default"}';
1518 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][FlexFormTools::class]['flexParsing'] = [
1519 DataStructureParsePostProcessHookReturnArray::class,
1520 ];
1521 $expected = [
1522 'sheets' => [
1523 'foo' => 'bar'
1524 ]
1525 ];
1526 $this->assertSame($expected, (new FlexFormTools())->parseDataStructureByIdentifier($identifier));
1527 }
1528
1529 /**
1530 * @test
1531 */
1532 public function traverseFlexFormXmlDataRecurseDoesNotFailOnNotExistingField()
1533 {
1534 $dataStruct = [
1535 'dummy_field' => [
1536 'TCEforms' => [
1537 'config' => [],
1538 ],
1539 ],
1540 ];
1541 $pA = [
1542 'vKeys' => ['ES'],
1543 'callBackMethod_value' => 'dummy',
1544 ];
1545 $editData = '';
1546 /** @var \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools|\PHPUnit_Framework_MockObject_MockObject $subject */
1547 $subject = $this->getMockBuilder(FlexFormTools::class)
1548 ->setMethods(['executeCallBackMethod'])
1549 ->getMock();
1550 $subject->expects($this->never())->method('executeCallBackMethod');
1551 $subject->traverseFlexFormXMLData_recurse($dataStruct, $editData, $pA);
1552 }
1553
1554 /**
1555 * @test
1556 */
1557 public function traverseFlexFormXmlDataRecurseDoesNotFailOnNotExistingArrayField()
1558 {
1559 $dataStruct = [
1560 'dummy_field' => [
1561 'type' => 'array',
1562 'el' => 'field_not_in_data',
1563 ],
1564 ];
1565 $pA = [
1566 'vKeys' => ['ES'],
1567 'callBackMethod_value' => 'dummy',
1568 ];
1569 $editData = [
1570 'field' => [
1571 'el' => 'dummy',
1572 ],
1573 ];
1574 $editData2 = '';
1575 /** @var \TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools|\PHPUnit_Framework_MockObject_MockObject $subject */
1576 $subject = $this->createMock(FlexFormTools::class);
1577 $this->assertEquals(
1578 $subject->traverseFlexFormXMLData_recurse($dataStruct, $editData, $pA),
1579 $subject->traverseFlexFormXMLData_recurse($dataStruct, $editData2, $pA)
1580 );
1581 }
1582 }