TYPO3 CMS  TYPO3_8-7
FlexFormToolsTest.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
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 
49 
53 class FlexFormToolsTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
54 {
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
1062  {
1063  $this->expectException(InvalidIdentifierException::class);
1064  $this->expectExceptionCode(1478100828);
1065  (new FlexFormTools())->parseDataStructureByIdentifier('');
1066  }
1067 
1072  {
1073  $this->expectException(\RuntimeException::class);
1074  $this->expectExceptionCode(1478345642);
1075  (new FlexFormTools())->parseDataStructureByIdentifier('egon');
1076  }
1077 
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 
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 
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 
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 
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 
1160  {
1161  $this->expectException(InvalidIdentifierException::class);
1162  $this->expectExceptionCode(1478104554);
1163  (new FlexFormTools())->parseDataStructureByIdentifier('{"type":"bernd"}');
1164  }
1165 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 = '';
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 
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 = '';
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 }
static addInstance($className, $instance)
getDataStructureIdentifierReturnsValidNameForTwoFieldCombinations(array $row, array $ds, string $expected)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']