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