[BUGFIX] Make TypoScriptParser sortList more strict
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / TypoScript / Parser / TypoScriptParserTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Test case for \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
19 */
20 class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
21
22 /**
23 * @var \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
24 */
25 protected $typoScriptParser = NULL;
26
27 /**
28 * Set up
29 *
30 * @return void
31 */
32 protected function setUp() {
33 $accessibleClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class);
34 $this->typoScriptParser = new $accessibleClassName();
35 }
36
37 /**
38 * Data provider for executeValueModifierReturnsModifiedResult
39 *
40 * @return array modifier name, modifier arguments, current value, expected result
41 */
42 public function executeValueModifierDataProvider() {
43 return array(
44 'prependString with string' => array(
45 'prependString',
46 'abc',
47 '!',
48 '!abc'
49 ),
50 'prependString with empty string' => array(
51 'prependString',
52 'foo',
53 '',
54 'foo',
55 ),
56 'appendString with string' => array(
57 'appendString',
58 'abc',
59 '!',
60 'abc!',
61 ),
62 'appendString with empty string' => array(
63 'appendString',
64 'abc',
65 '',
66 'abc',
67 ),
68 'removeString removes simple string' => array(
69 'removeString',
70 'abcdef',
71 'bc',
72 'adef',
73 ),
74 'removeString removes nothing if no match' => array(
75 'removeString',
76 'abcdef',
77 'foo',
78 'abcdef',
79 ),
80 'removeString removes multiple matches' => array(
81 'removeString',
82 'FooBarFoo',
83 'Foo',
84 'Bar',
85 ),
86 'replaceString replaces simple match' => array(
87 'replaceString',
88 'abcdef',
89 'bc|123',
90 'a123def',
91 ),
92 'replaceString replaces simple match with nothing' => array(
93 'replaceString',
94 'abcdef',
95 'bc',
96 'adef',
97 ),
98 'replaceString replaces multiple matches' => array(
99 'replaceString',
100 'FooBarFoo',
101 'Foo|Bar',
102 'BarBarBar',
103 ),
104 'addToList adds at end of existing list' => array(
105 'addToList',
106 '123,456',
107 '789',
108 '123,456,789',
109 ),
110 'addToList adds at end of existing list including white-spaces' => array(
111 'addToList',
112 '123,456',
113 ' 789 , 32 , 12 ',
114 '123,456, 789 , 32 , 12 ',
115 ),
116 'addToList adds nothing' => array(
117 'addToList',
118 '123,456',
119 '',
120 '123,456,', // This result is probably not what we want (appended comma) ... fix it?
121 ),
122 'addToList adds to empty list' => array(
123 'addToList',
124 '',
125 'foo',
126 'foo',
127 ),
128 'removeFromList removes value from list' => array(
129 'removeFromList',
130 '123,456,789,abc',
131 '456',
132 '123,789,abc',
133 ),
134 'removeFromList removes value at beginning of list' => array(
135 'removeFromList',
136 '123,456,abc',
137 '123',
138 '456,abc',
139 ),
140 'removeFromList removes value at end of list' => array(
141 'removeFromList',
142 '123,456,abc',
143 'abc',
144 '123,456',
145 ),
146 'removeFromList removes multiple values from list' => array(
147 'removeFromList',
148 'foo,123,bar,123',
149 '123',
150 'foo,bar',
151 ),
152 'removeFromList removes empty values' => array(
153 'removeFromList',
154 'foo,,bar',
155 '',
156 'foo,bar',
157 ),
158 'uniqueList removes duplicates' => array(
159 'uniqueList',
160 '123,456,abc,456,456',
161 '',
162 '123,456,abc',
163 ),
164 'uniqueList removes duplicate empty list values' => array(
165 'uniqueList',
166 '123,,456,,abc',
167 '',
168 '123,,456,abc',
169 ),
170 'reverseList returns list reversed' => array(
171 'reverseList',
172 '123,456,abc,456',
173 '',
174 '456,abc,456,123',
175 ),
176 'reverseList keeps empty values' => array(
177 'reverseList',
178 ',123,,456,abc,,456',
179 '',
180 '456,,abc,456,,123,',
181 ),
182 'reverseList does not change single element' => array(
183 'reverseList',
184 '123',
185 '',
186 '123',
187 ),
188 'sortList sorts a list' => array(
189 'sortList',
190 '10,100,0,20,abc',
191 '',
192 '0,10,20,100,abc',
193 ),
194 'sortList sorts a list numeric' => array(
195 'sortList',
196 '10,0,100,-20',
197 'numeric',
198 '-20,0,10,100',
199 ),
200 'sortList sorts a list descending' => array(
201 'sortList',
202 '10,100,0,20,abc,-20',
203 'descending',
204 'abc,100,20,10,0,-20',
205 ),
206 'sortList sorts a list numeric descending' => array(
207 'sortList',
208 '10,100,0,20,-20',
209 'descending,numeric',
210 '100,20,10,0,-20',
211 ),
212 'sortList ignores invalid modifier arguments' => array(
213 'sortList',
214 '10,100,20',
215 'foo,descending,bar',
216 '100,20,10',
217 ),
218 );
219 }
220
221 /**
222 * @test
223 * @dataProvider executeValueModifierDataProvider
224 */
225 public function executeValueModifierReturnsModifiedResult($modifierName, $currentValue, $modifierArgument, $expected) {
226 $actualValue = $this->typoScriptParser->_call('executeValueModifier', $modifierName, $modifierArgument, $currentValue);
227 $this->assertEquals($expected, $actualValue);
228 }
229
230
231 /**
232 * Data provider for executeValueModifierThrowsException
233 *
234 * @return array modifier name, modifier arguments, current value, expected result
235 */
236 public function executeValueModifierInvalidDataProvider() {
237 return array(
238 'sortList sorts a list numeric' => array(
239 'sortList',
240 '10,0,100,-20,abc',
241 'numeric',
242 ),
243 'sortList sorts a list numeric descending' => array(
244 'sortList',
245 '10,100,0,20,abc,-20',
246 'descending,numeric',
247 ),
248 );
249 }
250
251 /**
252 * @test
253 * @dataProvider executeValueModifierInvalidDataProvider
254 */
255 public function executeValueModifierThrowsException($modifierName, $currentValue, $modifierArgument) {
256 $this->setExpectedException('InvalidArgumentException', 'The list "' . $currentValue . '" should be sorted numerically but contains a non-numeric value');
257 $this->typoScriptParser->_call('executeValueModifier', $modifierName, $modifierArgument, $currentValue);
258 }
259
260 /**
261 * @param string $typoScript
262 * @param array $expected
263 * @dataProvider typoScriptIsParsedToArrayDataProvider
264 * @test
265 */
266 public function typoScriptIsParsedToArray($typoScript, array $expected) {
267 $this->typoScriptParser->parse($typoScript);
268 $this->assertEquals($expected, $this->typoScriptParser->setup);
269 }
270
271 /**
272 * @return array
273 */
274 public function typoScriptIsParsedToArrayDataProvider() {
275 return array(
276 'simple assignment' => array(
277 'key = value',
278 array(
279 'key' => 'value',
280 )
281 ),
282 'simple assignment with escaped dot at the beginning' => array(
283 '\\.key = value',
284 array(
285 '.key' => 'value',
286 )
287 ),
288 'simple assignment with protected escaped dot at the beginning' => array(
289 '\\\\.key = value',
290 array(
291 '\\.' => array(
292 'key' => 'value',
293 ),
294 )
295 ),
296 'nested assignment' => array(
297 'lib.key = value',
298 array(
299 'lib.' => array(
300 'key' => 'value',
301 ),
302 ),
303 ),
304 'nested assignment with escaped key' => array(
305 'lib\\.key = value',
306 array(
307 'lib.key' => 'value',
308 ),
309 ),
310 'nested assignment with escaped key and escaped dot at the beginning' => array(
311 '\\.lib\\.key = value',
312 array(
313 '.lib.key' => 'value',
314 ),
315 ),
316 'nested assignment with protected escaped key' => array(
317 'lib\\\\.key = value',
318 array(
319 'lib\\.' => array('key' => 'value'),
320 ),
321 ),
322 'nested assignment with protected escaped key and protected escaped dot at the beginning' => array(
323 '\\\\.lib\\\\.key = value',
324 array(
325 '\\.' => array(
326 'lib\\.' => array('key' => 'value'),
327 ),
328 ),
329 ),
330 'assignment with escaped an non escaped keys' => array(
331 'firstkey.secondkey\\.thirdkey.setting = value',
332 array(
333 'firstkey.' => array(
334 'secondkey.thirdkey.' => array(
335 'setting' => 'value'
336 )
337 )
338 )
339 ),
340 'nested structured assignment' => array(
341 'lib {' . LF .
342 'key = value' . LF .
343 '}',
344 array(
345 'lib.' => array(
346 'key' => 'value',
347 ),
348 ),
349 ),
350 'nested structured assignment with escaped key inside' => array(
351 'lib {' . LF .
352 'key\\.nextkey = value' . LF .
353 '}',
354 array(
355 'lib.' => array(
356 'key.nextkey' => 'value',
357 ),
358 ),
359 ),
360 'nested structured assignment with escaped key inside and escaped dots at the beginning' => array(
361 '\\.lib {' . LF .
362 '\\.key\\.nextkey = value' . LF .
363 '}',
364 array(
365 '.lib.' => array(
366 '.key.nextkey' => 'value',
367 ),
368 ),
369 ),
370 'nested structured assignment with protected escaped key inside' => array(
371 'lib {' . LF .
372 'key\\\\.nextkey = value' . LF .
373 '}',
374 array(
375 'lib.' => array(
376 'key\\.' => array('nextkey' => 'value'),
377 ),
378 ),
379 ),
380 'nested structured assignment with protected escaped key inside and protected escaped dots at the beginning' => array(
381 '\\\\.lib {' . LF .
382 '\\\\.key\\\\.nextkey = value' . LF .
383 '}',
384 array(
385 '\\.' => array(
386 'lib.' => array(
387 '\\.' => array(
388 'key\\.' => array('nextkey' => 'value'),
389 ),
390 ),
391 ),
392 ),
393 ),
394 'nested structured assignment with escaped key' => array(
395 'lib\\.anotherkey {' . LF .
396 'key = value' . LF .
397 '}',
398 array(
399 'lib.anotherkey.' => array(
400 'key' => 'value',
401 ),
402 ),
403 ),
404 'nested structured assignment with protected escaped key' => array(
405 'lib\\\\.anotherkey {' . LF .
406 'key = value' . LF .
407 '}',
408 array(
409 'lib\\.' => array(
410 'anotherkey.' => array(
411 'key' => 'value',
412 ),
413 ),
414 ),
415 ),
416 'multiline assignment' => array(
417 'key (' . LF .
418 'first' . LF .
419 'second' . LF .
420 ')',
421 array(
422 'key' => 'first' . LF . 'second',
423 ),
424 ),
425 'multiline assignment with escaped key' => array(
426 'key\\.nextkey (' . LF .
427 'first' . LF .
428 'second' . LF .
429 ')',
430 array(
431 'key.nextkey' => 'first' . LF . 'second',
432 ),
433 ),
434 'multiline assignment with protected escaped key' => array(
435 'key\\\\.nextkey (' . LF .
436 'first' . LF .
437 'second' . LF .
438 ')',
439 array(
440 'key\\.' => array('nextkey' => 'first' . LF . 'second'),
441 ),
442 ),
443 'copying values' => array(
444 'lib.default = value' . LF .
445 'lib.copy < lib.default',
446 array(
447 'lib.' => array(
448 'default' => 'value',
449 'copy' => 'value',
450 ),
451 ),
452 ),
453 'copying values with escaped key' => array(
454 'lib\\.default = value' . LF .
455 'lib.copy < lib\\.default',
456 array(
457 'lib.default' => 'value',
458 'lib.' => array(
459 'copy' => 'value',
460 ),
461 ),
462 ),
463 'copying values with protected escaped key' => array(
464 'lib\\\\.default = value' . LF .
465 'lib.copy < lib\\\\.default',
466 array(
467 'lib\\.' => array('default' => 'value'),
468 'lib.' => array(
469 'copy' => 'value',
470 ),
471 ),
472 ),
473 'one-line hash comment' => array(
474 'first = 1' . LF .
475 '# ignore = me' . LF .
476 'second = 2',
477 array(
478 'first' => '1',
479 'second' => '2',
480 ),
481 ),
482 'one-line slash comment' => array(
483 'first = 1' . LF .
484 '// ignore = me' . LF .
485 'second = 2',
486 array(
487 'first' => '1',
488 'second' => '2',
489 ),
490 ),
491 'multi-line slash comment' => array(
492 'first = 1' . LF .
493 '/*' . LF .
494 'ignore = me' . LF .
495 '*/' . LF .
496 'second = 2',
497 array(
498 'first' => '1',
499 'second' => '2',
500 ),
501 ),
502 'nested assignment repeated segment names' => array(
503 'test.test.test = 1',
504 array(
505 'test.' => array(
506 'test.' => array(
507 'test' => '1',
508 ),
509 )
510 ),
511 ),
512 'simple assignment operator with tab character before "="' => array(
513 'test = someValue',
514 array(
515 'test' => 'someValue',
516 ),
517 ),
518 'simple assignment operator character as value "="' => array(
519 'test ==TEST=',
520 array(
521 'test' => '=TEST=',
522 ),
523 ),
524 'nested assignment operator character as value "="' => array(
525 'test.test ==TEST=',
526 array(
527 'test.' => array(
528 'test' => '=TEST=',
529 ),
530 ),
531 ),
532 'simple assignment character as value "<"' => array(
533 'test =<TEST>',
534 array(
535 'test' => '<TEST>',
536 ),
537 ),
538 'nested assignment character as value "<"' => array(
539 'test.test =<TEST>',
540 array(
541 'test.' => array(
542 'test' => '<TEST>',
543 ),
544 ),
545 ),
546 'simple assignment character as value ">"' => array(
547 'test =>TEST<',
548 array(
549 'test' => '>TEST<',
550 ),
551 ),
552 'nested assignment character as value ">"' => array(
553 'test.test =>TEST<',
554 array(
555 'test.' => array(
556 'test' => '>TEST<',
557 ),
558 ),
559 ),
560 'nested assignment repeated segment names with whitespaces' => array(
561 'test.test.test = 1' . " \t",
562 array(
563 'test.' => array(
564 'test.' => array(
565 'test' => '1',
566 ),
567 )
568 ),
569 ),
570 'simple assignment operator character as value "=" with whitespaces' => array(
571 'test = =TEST=' . " \t",
572 array(
573 'test' => '=TEST=',
574 ),
575 ),
576 'nested assignment operator character as value "=" with whitespaces' => array(
577 'test.test = =TEST=' . " \t",
578 array(
579 'test.' => array(
580 'test' => '=TEST=',
581 ),
582 ),
583 ),
584 'simple assignment character as value "<" with whitespaces' => array(
585 'test = <TEST>' . " \t",
586 array(
587 'test' => '<TEST>',
588 ),
589 ),
590 'nested assignment character as value "<" with whitespaces' => array(
591 'test.test = <TEST>' . " \t",
592 array(
593 'test.' => array(
594 'test' => '<TEST>',
595 ),
596 ),
597 ),
598 'simple assignment character as value ">" with whitespaces' => array(
599 'test = >TEST<' . " \t",
600 array(
601 'test' => '>TEST<',
602 ),
603 ),
604 'nested assignment character as value ">" with whitespaces' => array(
605 'test.test = >TEST<',
606 array(
607 'test.' => array(
608 'test' => '>TEST<',
609 ),
610 ),
611 ),
612 'CSC example #1' => array(
613 'linkParams.ATagParams.dataWrap = class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
614 array(
615 'linkParams.' => array(
616 'ATagParams.' => array(
617 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
618 ),
619 ),
620 ),
621 ),
622 'CSC example #2' => array(
623 'linkParams.ATagParams {' . LF .
624 'dataWrap = class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"' . LF .
625 '}',
626 array(
627 'linkParams.' => array(
628 'ATagParams.' => array(
629 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
630 ),
631 ),
632 ),
633 ),
634 'CSC example #3' => array(
635 'linkParams.ATagParams.dataWrap (' . LF .
636 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"' . LF .
637 ')',
638 array(
639 'linkParams.' => array(
640 'ATagParams.' => array(
641 'dataWrap' => 'class="{$styles.content.imgtext.linkWrap.lightboxCssClass}" rel="{$styles.content.imgtext.linkWrap.lightboxRelAttribute}"',
642 ),
643 ),
644 ),
645 ),
646 'key with colon' => array(
647 'some:key = is valid',
648 array(
649 'some:key' => 'is valid'
650 )
651 ),
652 'special operator' => array(
653 'some := addToList(a)',
654 array(
655 'some' => 'a'
656 )
657 ),
658 'special operator with white-spaces' => array(
659 'some := addToList (a)',
660 array(
661 'some' => 'a'
662 )
663 ),
664 'special operator with tabs' => array(
665 'some := addToList (a)',
666 array(
667 'some' => 'a'
668 )
669 ),
670 'special operator with white-spaces and tabs in value' => array(
671 'some := addToList( a, b, c )',
672 array(
673 'some' => 'a, b, c'
674 )
675 ),
676 'special operator and colon, no spaces' => array(
677 'some:key:=addToList(a)',
678 array(
679 'some:key' => 'a'
680 )
681 ),
682 'key with all special symbols' => array(
683 'someSpecial\\_:-\\.Chars = is valid',
684 array(
685 'someSpecial\\_:-.Chars' => 'is valid'
686 )
687 ),
688 );
689 }
690
691 /**
692 * @test
693 */
694 public function setValCanBeCalledWithArrayValueParameter() {
695 $string = '';
696 $setup = array();
697 $value = array();
698 $this->typoScriptParser->setVal($string, $setup, $value);
699 }
700
701 /**
702 * @test
703 */
704 public function setValCanBeCalledWithStringValueParameter() {
705 $string = '';
706 $setup = array();
707 $value = '';
708 $this->typoScriptParser->setVal($string, $setup, $value);
709 }
710
711 /**
712 * @test
713 * @dataProvider parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider
714 */
715 public function parseNextKeySegmentReturnsCorrectNextKeySegment($key, $expectedKeySegment, $expectedRemainingKey) {
716 list($keySegment, $remainingKey) = $this->typoScriptParser->_call('parseNextKeySegment', $key);
717 $this->assertSame($expectedKeySegment, $keySegment);
718 $this->assertSame($expectedRemainingKey, $remainingKey);
719 }
720
721 /**
722 * @return array
723 */
724 public function parseNextKeySegmentReturnsCorrectNextKeySegmentDataProvider() {
725 return array(
726 'key without separator' => array(
727 'testkey',
728 'testkey',
729 ''
730 ),
731 'key with normal separator' => array(
732 'test.key',
733 'test',
734 'key'
735 ),
736 'key with multiple normal separators' => array(
737 'test.key.subkey',
738 'test',
739 'key.subkey'
740 ),
741 'key with separator and escape character' => array(
742 'te\\st.test',
743 'te\\st',
744 'test'
745 ),
746 'key with escaped separators' => array(
747 'test\\.key\\.subkey',
748 'test.key.subkey',
749 ''
750 ),
751 'key with escaped and unescaped separator 1' => array(
752 'test.test\\.key',
753 'test',
754 'test\\.key'
755 ),
756 'key with escaped and unescaped separator 2' => array(
757 'test\\.test.key\\.key2',
758 'test.test',
759 'key\\.key2'
760 ),
761 'key with escaped escape character' => array(
762 'test\\\\.key',
763 'test\\',
764 'key'
765 ),
766 'key with escaped separator and additional escape character' => array(
767 'test\\\\\\.key',
768 'test\\\\',
769 'key'
770 ),
771
772 'multiple escape characters within the key are preserved' => array(
773 'te\\\\st\\\\.key',
774 'te\\\\st\\',
775 'key'
776 )
777 );
778 }
779
780 }