[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / TypoScript / Parser / TypoScriptParserTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Prophecy\Argument;
20 use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
21 use TYPO3\CMS\Core\Cache\CacheManager;
22 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
23 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
24 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
27 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
28
29 /**
30 * Test case
31 */
32 class TypoScriptParserTest extends UnitTestCase
33 {
34 /**
35 * @var TypoScriptParser|AccessibleObjectInterface
36 */
37 protected $typoScriptParser;
38
39 /**
40 * Set up
41 */
42 protected function setUp(): void
43 {
44 parent::setUp();
45 $accessibleClassName = $this->buildAccessibleProxy(TypoScriptParser::class);
46 $this->typoScriptParser = new $accessibleClassName();
47 }
48
49 /**
50 * Tear down
51 */
52 protected function tearDown(): void
53 {
54 GeneralUtility::purgeInstances();
55 parent::tearDown();
56 }
57
58 /**
59 * Data provider for executeValueModifierReturnsModifiedResult
60 *
61 * @return array modifier name, modifier arguments, current value, expected result
62 */
63 public function executeValueModifierDataProvider(): array
64 {
65 return [
66 'prependString with string' => [
67 'prependString',
68 'abc',
69 '!',
70 '!abc'
71 ],
72 'prependString with empty string' => [
73 'prependString',
74 'foo',
75 '',
76 'foo',
77 ],
78 'appendString with string' => [
79 'appendString',
80 'abc',
81 '!',
82 'abc!',
83 ],
84 'appendString with empty string' => [
85 'appendString',
86 'abc',
87 '',
88 'abc',
89 ],
90 'removeString removes simple string' => [
91 'removeString',
92 'abcdef',
93 'bc',
94 'adef',
95 ],
96 'removeString removes nothing if no match' => [
97 'removeString',
98 'abcdef',
99 'foo',
100 'abcdef',
101 ],
102 'removeString removes multiple matches' => [
103 'removeString',
104 'FooBarFoo',
105 'Foo',
106 'Bar',
107 ],
108 'replaceString replaces simple match' => [
109 'replaceString',
110 'abcdef',
111 'bc|123',
112 'a123def',
113 ],
114 'replaceString replaces simple match with nothing' => [
115 'replaceString',
116 'abcdef',
117 'bc',
118 'adef',
119 ],
120 'replaceString replaces multiple matches' => [
121 'replaceString',
122 'FooBarFoo',
123 'Foo|Bar',
124 'BarBarBar',
125 ],
126 'addToList adds at end of existing list' => [
127 'addToList',
128 '123,456',
129 '789',
130 '123,456,789',
131 ],
132 'addToList adds at end of existing list including white-spaces' => [
133 'addToList',
134 '123,456',
135 ' 789 , 32 , 12 ',
136 '123,456, 789 , 32 , 12 ',
137 ],
138 'addToList adds nothing' => [
139 'addToList',
140 '123,456',
141 '',
142 '123,456,', // This result is probably not what we want (appended comma) ... fix it?
143 ],
144 'addToList adds to empty list' => [
145 'addToList',
146 '',
147 'foo',
148 'foo',
149 ],
150 'removeFromList removes value from list' => [
151 'removeFromList',
152 '123,456,789,abc',
153 '456',
154 '123,789,abc',
155 ],
156 'removeFromList removes value at beginning of list' => [
157 'removeFromList',
158 '123,456,abc',
159 '123',
160 '456,abc',
161 ],
162 'removeFromList removes value at end of list' => [
163 'removeFromList',
164 '123,456,abc',
165 'abc',
166 '123,456',
167 ],
168 'removeFromList removes multiple values from list' => [
169 'removeFromList',
170 'foo,123,bar,123',
171 '123',
172 'foo,bar',
173 ],
174 'removeFromList removes empty values' => [
175 'removeFromList',
176 'foo,,bar',
177 '',
178 'foo,bar',
179 ],
180 'uniqueList removes duplicates' => [
181 'uniqueList',
182 '123,456,abc,456,456',
183 '',
184 '123,456,abc',
185 ],
186 'uniqueList removes duplicate empty list values' => [
187 'uniqueList',
188 '123,,456,,abc',
189 '',
190 '123,,456,abc',
191 ],
192 'reverseList returns list reversed' => [
193 'reverseList',
194 '123,456,abc,456',
195 '',
196 '456,abc,456,123',
197 ],
198 'reverseList keeps empty values' => [
199 'reverseList',
200 ',123,,456,abc,,456',
201 '',
202 '456,,abc,456,,123,',
203 ],
204 'reverseList does not change single element' => [
205 'reverseList',
206 '123',
207 '',
208 '123',
209 ],
210 'sortList sorts a list' => [
211 'sortList',
212 '10,100,0,20,abc',
213 '',
214 '0,10,20,100,abc',
215 ],
216 'sortList sorts a list numeric' => [
217 'sortList',
218 '10,0,100,-20',
219 'numeric',
220 '-20,0,10,100',
221 ],
222 'sortList sorts a list descending' => [
223 'sortList',
224 '10,100,0,20,abc,-20',
225 'descending',
226 'abc,100,20,10,0,-20',
227 ],
228 'sortList sorts a list numeric descending' => [
229 'sortList',
230 '10,100,0,20,-20',
231 'descending,numeric',
232 '100,20,10,0,-20',
233 ],
234 'sortList ignores invalid modifier arguments' => [
235 'sortList',
236 '10,100,20',
237 'foo,descending,bar',
238 '100,20,10',
239 ],
240 ];
241 }
242
243 /**
244 * @test
245 * @dataProvider executeValueModifierDataProvider
246 * @param string $modifierName
247 * @param string $currentValue
248 * @param string $modifierArgument
249 * @param string $expected
250 */
251 public function executeValueModifierReturnsModifiedResult(
252 string $modifierName,
253 string $currentValue,
254 string $modifierArgument,
255 string $expected
256 ): void {
257 $actualValue = $this->typoScriptParser->_call(
258 'executeValueModifier',
259 $modifierName,
260 $modifierArgument,
261 $currentValue
262 );
263 $this->assertEquals($expected, $actualValue);
264 }
265
266 public function executeGetEnvModifierDataProvider(): array
267 {
268 return [
269 'environment variable not set' => [
270 [],
271 'bar',
272 'FOO',
273 null,
274 ],
275 'empty environment variable' => [
276 ['FOO' => ''],
277 'bar',
278 'FOO',
279 '',
280 ],
281 'empty current value' => [
282 ['FOO' => 'baz'],
283 null,
284 'FOO',
285 'baz',
286 ],
287 'environment variable and current value set' => [
288 ['FOO' => 'baz'],
289 'bar',
290 'FOO',
291 'baz',
292 ],
293 'neither environment variable nor current value set' => [
294 [],
295 null,
296 'FOO',
297 null,
298 ],
299 'empty environment variable name' => [
300 ['FOO' => 'baz'],
301 'bar',
302 '',
303 null,
304 ],
305 ];
306 }
307
308 /**
309 * @test
310 * @dataProvider executeGetEnvModifierDataProvider
311 * @param string $modifierName
312 * @param string $currentValue
313 * @param string $modifierArgument
314 * @param string $expected
315 */
316 public function executeGetEnvModifierReturnsModifiedResult(
317 array $environmentVariables,
318 ?string $currentValue,
319 string $modifierArgument,
320 ?string $expected
321 ): void {
322 foreach ($environmentVariables as $environmentVariable => $value) {
323 putenv($environmentVariable . '=' . $value);
324 }
325 $actualValue = $this->typoScriptParser->_call(
326 'executeValueModifier',
327 'getEnv',
328 $modifierArgument,
329 $currentValue
330 );
331 $this->assertEquals($expected, $actualValue);
332 foreach ($environmentVariables as $environmentVariable => $_) {
333 putenv($environmentVariable);
334 }
335 }
336
337 /**
338 * Data provider for executeValueModifierThrowsException
339 *
340 * @return array modifier name, modifier arguments, current value, expected result
341 */
342 public function executeValueModifierInvalidDataProvider(): array
343 {
344 return [
345 'sortList sorts a list numeric' => [
346 'sortList',
347 '10,0,100,-20,abc',
348 'numeric',
349 ],
350 'sortList sorts a list numeric descending' => [
351 'sortList',
352 '10,100,0,20,abc,-20',
353 'descending,numeric',
354 ],
355 ];
356 }
357
358 /**
359 * @test
360 * @dataProvider executeValueModifierInvalidDataProvider
361 * @param string $modifierName
362 * @param string $currentValue
363 * @param string $modifierArgument
364 */
365 public function executeValueModifierThrowsException(
366 string $modifierName,
367 string $currentValue,
368 string $modifierArgument
369 ): void {
370 $this->expectException(\InvalidArgumentException::class);
371 $this->expectExceptionCode(1438191758);
372 $this->typoScriptParser->_call('executeValueModifier', $modifierName, $modifierArgument, $currentValue);
373 }
374
375 /**
376 * @test
377 */
378 public function invalidCharactersInObjectNamesAreReported(): void
379 {
380 $timeTrackerProphecy = $this->prophesize(TimeTracker::class);
381 GeneralUtility::setSingletonInstance(TimeTracker::class, $timeTrackerProphecy->reveal());
382
383 $typoScript = '$.10 = invalid';
384 $this->typoScriptParser->parse($typoScript);
385 $expected = 'Line 0: Object Name String, "$.10" contains invalid character "$". Must be alphanumeric or one of: "_:-\."';
386 $this->assertEquals($expected, $this->typoScriptParser->errors[0][0]);
387 }
388
389 /**
390 * @test
391 */
392 public function emptyConditionIsReported(): void
393 {
394 $timeTrackerProphecy = $this->prophesize(TimeTracker::class);
395 GeneralUtility::setSingletonInstance(TimeTracker::class, $timeTrackerProphecy->reveal());
396
397 $typoScript = '[]';
398 $this->typoScriptParser->parse($typoScript);
399 $expected = 'Empty condition is always false, this does not make sense. At line 0';
400 $this->assertEquals($expected, $this->typoScriptParser->errors[0][0]);
401 }
402
403 /**
404 * @return array
405 */
406 public function doubleSlashCommentsDataProvider(): array
407 {
408 return [
409 'valid, without spaces' => ['// valid, without spaces'],
410 'valid, with one space' => [' // valid, with one space'],
411 'valid, with multiple spaces' => [' // valid, with multiple spaces'],
412 ];
413 }
414
415 /**
416 * @test
417 * @dataProvider doubleSlashCommentsDataProvider
418 * @param string $typoScript
419 */
420 public function doubleSlashCommentsAreValid(string $typoScript): void
421 {
422 $this->typoScriptParser->parse($typoScript);
423 $this->assertEmpty($this->typoScriptParser->errors);
424 }
425
426 /**
427 * @return array
428 */
429 public function includeFileDataProvider(): array
430 {
431 return [
432 'TS code before not matching include' => [
433 '
434 foo = bar
435 <INCLUDE_TYPOSCRIPT: source="FILE:dev.ts" condition="applicationContext matches \"/^NotMatched/\"">
436 '
437 ],
438 'TS code after not matching include' => [
439 '
440 <INCLUDE_TYPOSCRIPT: source="FILE:dev.ts" condition="applicationContext matches \"/^NotMatched/\"">
441 foo = bar
442 '
443 ],
444 ];
445 }
446
447 /**
448 * @test
449 * @dataProvider includeFileDataProvider
450 * @param string $typoScript
451 */
452 public function includeFilesWithConditions(string $typoScript): void
453 {
454 // This test triggers a BackendUtility::BEgetRootLine() down below, we need to suppress the cache call
455 $cacheManagerProphecy = $this->prophesize(CacheManager::class);
456 $cacheProphecy = $this->prophesize(FrontendInterface::class);
457 $cacheManagerProphecy->getCache('runtime')->willReturn($cacheProphecy->reveal());
458 $cacheProphecy->get(Argument::cetera())->willReturn(false);
459 $cacheProphecy->set(Argument::cetera())->willReturn(false);
460 GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
461
462 $p = $this->prophesize(ConditionMatcher::class);
463 $p->match(Argument::cetera())->willReturn(false);
464 GeneralUtility::addInstance(ConditionMatcher::class, $p->reveal());
465
466 $resolvedIncludeLines = TypoScriptParser::checkIncludeLines($typoScript);
467 $this->assertStringContainsString('foo = bar', $resolvedIncludeLines);
468 $this->assertStringNotContainsString('INCLUDE_TYPOSCRIPT', $resolvedIncludeLines);
469 }
470
471 /**
472 * @return array
473 */
474 public function importFilesDataProvider(): array
475 {
476 return [
477 'Found include file as single file is imported' => [
478 // Input TypoScript
479 '@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt"'
480 ,
481 // Expected
482 '
483 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' begin ###
484 test.Core.TypoScript = 1
485 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' end ###
486 '
487 ],
488 'Found include file is imported' => [
489 // Input TypoScript
490 'bennilove = before
491 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt"
492 '
493 ,
494 // Expected
495 '
496 bennilove = before
497
498 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' begin ###
499 test.Core.TypoScript = 1
500 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' end ###
501 '
502 ],
503 'Not found file is not imported' => [
504 // Input TypoScript
505 'bennilove = before
506 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.txt"
507 '
508 ,
509 // Expected
510 '
511 bennilove = before
512
513 ###
514 ### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.txt".
515 ###
516 '
517 ],
518 'All files with glob are imported' => [
519 // Input TypoScript
520 'bennilove = before
521 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/*.txt"
522 '
523 ,
524 // Expected
525 '
526 bennilove = before
527
528 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' begin ###
529 test.Core.TypoScript = 1
530 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' end ###
531 '
532 ],
533 'Specific file with typoscript ending is imported' => [
534 // Input TypoScript
535 'bennilove = before
536 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript"
537 '
538 ,
539 // Expected
540 '
541 bennilove = before
542
543 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
544 test.TYPO3Forever.TypoScript = 1
545
546 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
547 '
548 ],
549 'All files in folder are imported, sorted by name' => [
550 // Input TypoScript
551 'bennilove = before
552 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/"
553 '
554 ,
555 // Expected
556 '
557 bennilove = before
558
559 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/badfilename.php\' begin ###
560
561 ###
562 ### ERROR: File "EXT:core/Tests/Unit/TypoScript/Fixtures/badfilename.php" was not included since it is not allowed due to fileDenyPattern.
563 ###
564
565 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/badfilename.php\' end ###
566
567
568 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' begin ###
569 test.Core.TypoScript = 1
570 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.txt\' end ###
571
572
573 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
574
575 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
576 test.TYPO3Forever.TypoScript = 1
577
578 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
579
580 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
581
582
583 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
584 test.TYPO3Forever.TypoScript = 1
585
586 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
587 '
588 ],
589 'All files ending with typoscript in folder are imported' => [
590 // Input TypoScript
591 'bennilove = before
592 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/*typoscript"
593 '
594 ,
595 // Expected
596 '
597 bennilove = before
598
599 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
600
601 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
602 test.TYPO3Forever.TypoScript = 1
603
604 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
605
606 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
607
608
609 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
610 test.TYPO3Forever.TypoScript = 1
611
612 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
613 '
614 ],
615 'All typoscript files in folder are imported' => [
616 // Input TypoScript
617 'bennilove = before
618 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/*.typoscript"
619 '
620 ,
621 // Expected
622 '
623 bennilove = before
624
625 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
626
627 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
628 test.TYPO3Forever.TypoScript = 1
629
630 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
631
632 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
633
634
635 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
636 test.TYPO3Forever.TypoScript = 1
637
638 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
639 '
640 ],
641 'All typoscript files in folder with glob are not imported due to recursion level=0' => [
642 // Input TypoScript
643 'bennilove = before
644 @import "EXT:core/Tests/Unit/**/*.typoscript"
645 '
646 ,
647 // Expected
648 '
649 bennilove = before
650
651 ###
652 ### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/**/*.typoscript".
653 ###
654 '
655 ],
656 'TypoScript file ending is automatically added' => [
657 // Input TypoScript
658 'bennilove = before
659 @import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup"
660 '
661 ,
662 // Expected
663 '
664 bennilove = before
665
666 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
667 test.TYPO3Forever.TypoScript = 1
668
669 ### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
670 '
671 ],
672 ];
673 }
674
675 /**
676 * @test
677 * @dataProvider importFilesDataProvider
678 * @param string $typoScript
679 * @param string $expected
680 */
681 public function importFiles(string $typoScript, string $expected): void
682 {
683 $resolvedIncludeLines = TypoScriptParser::checkIncludeLines($typoScript);
684 $this->assertEquals($expected, $resolvedIncludeLines);
685 }
686
687 /**
688 * @param string $typoScript
689 * @param array $expected
690 * @dataProvider typoScriptIsParsedToArrayDataProvider
691 * @test
692 */
693 public function typoScriptIsParsedToArray(string $typoScript, array $expected): void
694 {
695 $this->typoScriptParser->parse($typoScript);
696 $this->assertEquals($expected, $this->typoScriptParser->setup);
697 }
698
699 /**
700 * @return array
701 */
702 public function typoScriptIsParsedToArrayDataProvider(): array
703 {
704 return [
705 'simple assignment' => [
706 'key = value',
707 [
708 'key' => 'value',
709 ]
710 ],
711 'simple assignment with escaped dot at the beginning' => [
712 '\\.key = value',
713 [
714 '.key' => 'value',
715 ]
716 ],
717 'simple assignment with protected escaped dot at the beginning' => [
718 '\\\\.key = value',
719 [
720 '\\.' => [
721 'key' => 'value',
722 ],
723 ]
724 ],
725 'nested assignment' => [
726 'lib.key = value',
727 [
728 'lib.' => [
729 'key' => 'value',
730 ],
731 ],
732 ],
733 'nested assignment with escaped key' => [
734 'lib\\.key = value',
735 [
736 'lib.key' => 'value',
737 ],
738 ],
739 'nested assignment with escaped key and escaped dot at the beginning' => [
740 '\\.lib\\.key = value',
741 [
742 '.lib.key' => 'value',
743 ],
744 ],
745 'nested assignment with protected escaped key' => [
746 'lib\\\\.key = value',
747 [
748 'lib\\.' => ['key' => 'value'],
749 ],
750 ],
751 'nested assignment with protected escaped key and protected escaped dot at the beginning' => [
752 '\\\\.lib\\\\.key = value',
753 [
754 '\\.' => [
755 'lib\\.' => ['key' => 'value'],
756 ],
757 ],
758 ],
759 'assignment with escaped an non escaped keys' => [
760 'firstkey.secondkey\\.thirdkey.setting = value',
761 [
762 'firstkey.' => [
763 'secondkey.thirdkey.' => [
764 'setting' => 'value'
765 ]
766 ]
767 ]
768 ],
769 'nested structured assignment' => [
770 'lib {' . LF .
771 'key = value' . LF .
772 '}',
773 [
774 'lib.' => [
775 'key' => 'value',
776 ],
777 ],
778 ],
779 'nested structured assignment with escaped key inside' => [
780 'lib {' . LF .
781 'key\\.nextkey = value' . LF .
782 '}',
783 [
784 'lib.' => [
785 'key.nextkey' => 'value',
786 ],
787 ],
788 ],
789 'nested structured assignment with escaped key inside and escaped dots at the beginning' => [
790 '\\.lib {' . LF .
791 '\\.key\\.nextkey = value' . LF .
792 '}',
793 [
794 '.lib.' => [
795 '.key.nextkey' => 'value',
796 ],
797 ],
798 ],
799 'nested structured assignment with protected escaped key inside' => [
800 'lib {' . LF .
801 'key\\\\.nextkey = value' . LF .
802 '}',
803 [
804 'lib.' => [
805 'key\\.' => ['nextkey' => 'value'],
806 ],
807 ],
808 ],
809 'nested structured assignment with protected escaped key inside and protected escaped dots at the beginning' => [
810 '\\\\.lib {' . LF .
811 '\\\\.key\\\\.nextkey = value' . LF .
812 '}',
813 [
814 '\\.' => [
815 'lib.' => [
816 '\\.' => [
817 'key\\.' => ['nextkey' => 'value'],
818 ],
819 ],
820 ],
821 ],
822 ],
823 'nested structured assignment with escaped key' => [
824 'lib\\.anotherkey {' . LF .
825 'key = value' . LF .
826 '}',
827 [
828 'lib.anotherkey.' => [
829 'key' => 'value',
830 ],
831 ],
832 ],
833 'nested structured assignment with protected escaped key' => [
834 'lib\\\\.anotherkey {' . LF .
835 'key = value' . LF .
836 '}',
837 [
838 'lib\\.' => [
839 'anotherkey.' => [
840 'key' => 'value',
841 ],
842 ],
843 ],
844 ],
845 'multiline assignment' => [
846 'key (' . LF .
847 'first' . LF .
848 'second' . LF .
849 ')',
850 [
851 'key' => 'first' . LF . 'second',
852 ],
853 ],
854 'multiline assignment with escaped key' => [
855 'key\\.nextkey (' . LF .
856 'first' . LF .
857 'second' . LF .
858 ')',
859 [
860 'key.nextkey' => 'first' . LF . 'second',
861 ],
862 ],
863 'multiline assignment with protected escaped key' => [
864 'key\\\\.nextkey (' . LF .
865 'first' . LF .
866 'second' . LF .
867 ')',
868 [
869 'key\\.' => ['nextkey' => 'first' . LF . 'second'],
870 ],
871 ],
872 'copying values' => [
873 'lib.default = value' . LF .
874 'lib.copy < lib.default',
875 [
876 'lib.' => [
877 'default' => 'value',
878 'copy' => 'value',
879 ],
880 ],
881 ],
882 'copying values with escaped key' => [
883 'lib\\.default = value' . LF .
884 'lib.copy < lib\\.default',
885 [
886 'lib.default' => 'value',
887 'lib.' => [
888 'copy' => 'value',
889 ],
890 ],
891 ],
892 'copying values with protected escaped key' => [
893 'lib\\\\.default = value' . LF .
894 'lib.copy < lib\\\\.default',
895 [
896 'lib\\.' => ['default' => 'value'],
897 'lib.' => [
898 'copy' => 'value',
899 ],
900 ],
901 ],
902 'one-line hash comment' => [
903 'first = 1' . LF .
904 '# ignore = me' . LF .
905 'second = 2',
906 [
907 'first' => '1',
908 'second' => '2',
909 ],
910 ],
911 'one-line slash comment' => [
912 'first = 1' . LF .
913 '// ignore = me' . LF .
914 'second = 2',
915 [
916 'first' => '1',
917 'second' => '2',
918 ],
919 ],
920 'multi-line slash comment' => [
921 'first = 1' . LF .
922 '/*' . LF .
923 'ignore = me' . LF .
924 '*/' . LF .
925 'second = 2',
926 [
927 'first' => '1',
928 'second' => '2',
929 ],
930 ],
931 'multi-line slash comment in one line' => [
932 'first = 1' . LF .
933 '/* ignore = me */' . LF .
934 '/**** ignore = me **/' . LF .
935 'second = 2',
936 [
937 'first' => '1',
938 'second' => '2',
939 ],
940 ],
941 'nested assignment repeated segment names' => [
942 'test.test.test = 1',
943 [
944 'test.' => [
945 'test.' => [
946 'test' => '1',
947 ],
948 ]
949 ],
950 ],
951 'simple assignment operator with tab character before "="' => [
952 'test = someValue',
953 [
954 'test' => 'someValue',
955 ],
956 ],
957 'simple assignment operator character as value "="' => [
958 'test ==TEST=',
959 [
960 'test' => '=TEST=',
961 ],
962 ],
963 'nested assignment operator character as value "="' => [
964 'test.test ==TEST=',
965 [
966 'test.' => [
967 'test' => '=TEST=',
968 ],
969 ],
970 ],
971 'simple assignment character as value "<"' => [
972 'test =<TEST>',
973 [
974 'test' => '<TEST>',
975 ],
976 ],
977 'nested assignment character as value "<"' => [
978 'test.test =<TEST>',
979 [
980 'test.' => [
981 'test' => '<TEST>',
982 ],
983 ],
984 ],
985 'simple assignment character as value ">"' => [
986 'test =>TEST<',
987 [
988 'test' => '>TEST<',
989 ],
990 ],
991 'nested assignment character as value ">"' => [
992 'test.test =>TEST<',
993 [
994 'test.' => [
995 'test' => '>TEST<',
996 ],
997 ],
998 ],
999 'nested assignment repeated segment names with whitespaces' => [
1000 'test.test.test = 1' . " \t",
1001 [
1002 'test.' => [
1003 'test.' => [
1004 'test' => '1',
1005 ],
1006 ]
1007 ],
1008 ],
1009 'simple assignment operator character as value "=" with whitespaces' => [
1010 'test = =TEST=' . " \t",
1011 [
1012 'test' => '=TEST=',
1013 ],
1014 ],
1015 'nested assignment operator character as value "=" with whitespaces' => [
1016 'test.test = =TEST=' . " \t",
1017 [
1018 'test.' => [
1019 'test' => '=TEST=',
1020 ],
1021 ],
1022 ],
1023 'simple assignment character as value "<" with whitespaces' => [
1024 'test = <TEST>' . " \t",
1025 [
1026 'test' => '<TEST>',
1027 ],
1028 ],
1029 'nested assignment character as value "<" with whitespaces' => [
1030 'test.test = <TEST>' . " \t",
1031 [
1032 'test.' => [
1033 'test' => '<TEST>',
1034 ],
1035 ],
1036 ],
1037 'simple assignment character as value ">" with whitespaces' => [
1038 'test = >TEST<' . " \t",
1039 [
1040 'test' => '>TEST<',
1041 ],
1042 ],
1043 'nested assignment character as value ">" with whitespaces' => [
1044 'test.test = >TEST<',
1045 [
1046 'test.' => [
1047 'test' => '>TEST<',
1048 ],
1049 ],
1050 ],
1051 'CSC example #1' => [
1052 'linkParams.ATagParams.dataWrap = class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
1053 [
1054 'linkParams.' => [
1055 'ATagParams.' => [
1056 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
1057 ],
1058 ],
1059 ],
1060 ],
1061 'CSC example #2' => [
1062 'linkParams.ATagParams {' . LF .
1063 'dataWrap = class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"' . LF .
1064 '}',
1065 [
1066 'linkParams.' => [
1067 'ATagParams.' => [
1068 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
1069 ],
1070 ],
1071 ],
1072 ],
1073 'CSC example #3' => [
1074 'linkParams.ATagParams.dataWrap (' . LF .
1075 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"' . LF .
1076 ')',
1077 [
1078 'linkParams.' => [
1079 'ATagParams.' => [
1080 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
1081 ],
1082 ],
1083 ],
1084 ],
1085 'key with colon' => [
1086 'some:key = is valid',
1087 [
1088 'some:key' => 'is valid'
1089 ]
1090 ],
1091 'special operator' => [
1092 'some := addToList(a)',
1093 [
1094 'some' => 'a'
1095 ]
1096 ],
1097 'special operator with white-spaces' => [
1098 'some := addToList (a)',
1099 [
1100 'some' => 'a'
1101 ]
1102 ],
1103 'special operator with tabs' => [
1104 'some := addToList (a)',
1105 [
1106 'some' => 'a'
1107 ]
1108 ],
1109 'special operator with white-spaces and tabs in value' => [
1110 'some := addToList( a, b, c )',
1111 [
1112 'some' => 'a, b, c'
1113 ]
1114 ],
1115 'special operator and colon, no spaces' => [
1116 'some:key:=addToList(a)',
1117 [
1118 'some:key' => 'a'
1119 ]
1120 ],
1121 'key with all special symbols' => [
1122 'someSpecial\\_:-\\.Chars = is valid',
1123 [
1124 'someSpecial\\_:-.Chars' => 'is valid'
1125 ]
1126 ],
1127 ];
1128 }
1129
1130 /**
1131 * @test
1132 */
1133 public function setValCanBeCalledWithArrayValueParameter(): void
1134 {
1135 $string = '';
1136 $setup = [];
1137 $value = [];
1138 $this->typoScriptParser->_callRef('setVal', $string, $setup, $value);
1139 }
1140
1141 /**
1142 * @test
1143 */
1144 public function setValCanBeCalledWithStringValueParameter(): void
1145 {
1146 $string = '';
1147 $setup = [];
1148 $value = '';
1149 $this->typoScriptParser->_callRef('setVal', $string, $setup, $value);
1150 }
1151
1152 /**
1153 * @test
1154 * @dataProvider parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider
1155 * @param string $key
1156 * @param string $expectedKeySegment
1157 * @param string $expectedRemainingKey
1158 */
1159 public function parseNextKeySegmentReturnsCorrectNextKeySegment(
1160 string $key,
1161 string $expectedKeySegment,
1162 string $expectedRemainingKey
1163 ): void {
1164 [$keySegment, $remainingKey] = $this->typoScriptParser->_call('parseNextKeySegment', $key);
1165 $this->assertSame($expectedKeySegment, $keySegment);
1166 $this->assertSame($expectedRemainingKey, $remainingKey);
1167 }
1168
1169 /**
1170 * @return array
1171 */
1172 public function parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider(): array
1173 {
1174 return [
1175 'key without separator' => [
1176 'testkey',
1177 'testkey',
1178 ''
1179 ],
1180 'key with normal separator' => [
1181 'test.key',
1182 'test',
1183 'key'
1184 ],
1185 'key with multiple normal separators' => [
1186 'test.key.subkey',
1187 'test',
1188 'key.subkey'
1189 ],
1190 'key with separator and escape character' => [
1191 'te\\st.test',
1192 'te\\st',
1193 'test'
1194 ],
1195 'key with escaped separators' => [
1196 'test\\.key\\.subkey',
1197 'test.key.subkey',
1198 ''
1199 ],
1200 'key with escaped and unescaped separator 1' => [
1201 'test.test\\.key',
1202 'test',
1203 'test\\.key'
1204 ],
1205 'key with escaped and unescaped separator 2' => [
1206 'test\\.test.key\\.key2',
1207 'test.test',
1208 'key\\.key2'
1209 ],
1210 'key with escaped escape character' => [
1211 'test\\\\.key',
1212 'test\\',
1213 'key'
1214 ],
1215 'key with escaped separator and additional escape character' => [
1216 'test\\\\\\.key',
1217 'test\\\\',
1218 'key'
1219 ],
1220
1221 'multiple escape characters within the key are preserved' => [
1222 'te\\\\st\\\\.key',
1223 'te\\\\st\\',
1224 'key'
1225 ]
1226 ];
1227 }
1228 }