2 declare(strict_types
= 1);
3 namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject
;
6 * This file is part of the TYPO3 CMS project.
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.
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
15 * The TYPO3 project - inspiring people to share!
18 use PHPUnit\Framework\Exception
;
19 use Prophecy\Argument
;
20 use Psr\Http\Message\ServerRequestInterface
;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication
;
22 use TYPO3\CMS\Core\Cache\CacheManager
;
23 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
as CacheFrontendInterface
;
24 use TYPO3\CMS\Core\Context\Context
;
25 use TYPO3\CMS\Core\Context\UserAspect
;
26 use TYPO3\CMS\Core\Context\WorkspaceAspect
;
27 use TYPO3\CMS\Core\Core\ApplicationContext
;
28 use TYPO3\CMS\Core\LinkHandling\LinkService
;
29 use TYPO3\CMS\Core\Log\Logger
;
30 use TYPO3\CMS\Core\Package\PackageManager
;
31 use TYPO3\CMS\Core\
Resource\Exception\InvalidPathException
;
32 use TYPO3\CMS\Core\
Resource\File
;
33 use TYPO3\CMS\Core\
Resource\ResourceFactory
;
34 use TYPO3\CMS\Core\
Resource\ResourceStorage
;
35 use TYPO3\CMS\Core\Site\Entity\Site
;
36 use TYPO3\CMS\Core\TimeTracker\TimeTracker
;
37 use TYPO3\CMS\Core\TypoScript\TemplateService
;
38 use TYPO3\CMS\Core\Utility\DebugUtility
;
39 use TYPO3\CMS\Core\Utility\GeneralUtility
;
40 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication
;
41 use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject
;
42 use TYPO3\CMS\Frontend\ContentObject\CaseContentObject
;
43 use TYPO3\CMS\Frontend\ContentObject\ContentContentObject
;
44 use TYPO3\CMS\Frontend\ContentObject\ContentObjectArrayContentObject
;
45 use TYPO3\CMS\Frontend\ContentObject\ContentObjectArrayInternalContentObject
;
46 use TYPO3\CMS\Frontend\ContentObject\ContentObjectGetImageResourceHookInterface
;
47 use TYPO3\CMS\Frontend\ContentObject\ContentObjectOneSourceCollectionHookInterface
;
48 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
;
49 use TYPO3\CMS\Frontend\ContentObject\ContentObjectStdWrapHookInterface
;
50 use TYPO3\CMS\Frontend\ContentObject\EditPanelContentObject
;
51 use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException
;
52 use TYPO3\CMS\Frontend\ContentObject\FileContentObject
;
53 use TYPO3\CMS\Frontend\ContentObject\FilesContentObject
;
54 use TYPO3\CMS\Frontend\ContentObject\FluidTemplateContentObject
;
55 use TYPO3\CMS\Frontend\ContentObject\HierarchicalMenuContentObject
;
56 use TYPO3\CMS\Frontend\ContentObject\ImageContentObject
;
57 use TYPO3\CMS\Frontend\ContentObject\ImageResourceContentObject
;
58 use TYPO3\CMS\Frontend\ContentObject\LoadRegisterContentObject
;
59 use TYPO3\CMS\Frontend\ContentObject\RecordsContentObject
;
60 use TYPO3\CMS\Frontend\ContentObject\RestoreRegisterContentObject
;
61 use TYPO3\CMS\Frontend\ContentObject\ScalableVectorGraphicsContentObject
;
62 use TYPO3\CMS\Frontend\ContentObject\TemplateContentObject
;
63 use TYPO3\CMS\Frontend\ContentObject\TextContentObject
;
64 use TYPO3\CMS\Frontend\ContentObject\UserContentObject
;
65 use TYPO3\CMS\Frontend\ContentObject\UserInternalContentObject
;
66 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
;
67 use TYPO3\CMS\Frontend\Page\PageRepository
;
68 use TYPO3\TestingFramework\Core\AccessibleObjectInterface
;
69 use TYPO3\TestingFramework\Core\Unit\UnitTestCase
;
74 class ContentObjectRendererTest
extends UnitTestCase
77 * @var bool Reset singletons created by subject
79 protected $resetSingletonInstances = true;
82 * @var \PHPUnit_Framework_MockObject_MockObject|AccessibleObjectInterface|ContentObjectRenderer
87 * @var \PHPUnit_Framework_MockObject_MockObject|TypoScriptFrontendController|AccessibleObjectInterface
89 protected $frontendControllerMock;
92 * @var \PHPUnit_Framework_MockObject_MockObject|TemplateService
94 protected $templateServiceMock;
97 * Default content object name -> class name map, shipped with TYPO3 CMS
101 protected $contentObjectMap = [
102 'TEXT' => TextContentObject
::class,
103 'CASE' => CaseContentObject
::class,
104 'COBJ_ARRAY' => ContentObjectArrayContentObject
::class,
105 'COA' => ContentObjectArrayContentObject
::class,
106 'COA_INT' => ContentObjectArrayInternalContentObject
::class,
107 'USER' => UserContentObject
::class,
108 'USER_INT' => UserInternalContentObject
::class,
109 'FILE' => FileContentObject
::class,
110 'FILES' => FilesContentObject
::class,
111 'IMAGE' => ImageContentObject
::class,
112 'IMG_RESOURCE' => ImageResourceContentObject
::class,
113 'CONTENT' => ContentContentObject
::class,
114 'RECORDS' => RecordsContentObject
::class,
115 'HMENU' => HierarchicalMenuContentObject
::class,
116 'CASEFUNC' => CaseContentObject
::class,
117 'LOAD_REGISTER' => LoadRegisterContentObject
::class,
118 'RESTORE_REGISTER' => RestoreRegisterContentObject
::class,
119 'TEMPLATE' => TemplateContentObject
::class,
120 'FLUIDTEMPLATE' => FluidTemplateContentObject
::class,
121 'SVG' => ScalableVectorGraphicsContentObject
::class,
122 'EDITPANEL' => EditPanelContentObject
::class
128 protected function setUp(): void
130 $GLOBALS['SIM_ACCESS_TIME'] = 1534278180;
131 $packageManagerMock = $this->getMockBuilder(PackageManager
::class)
132 ->disableOriginalConstructor()
134 $this->templateServiceMock
=
135 $this->getMockBuilder(TemplateService
::class)
136 ->setConstructorArgs([null, $packageManagerMock])
137 ->setMethods(['linkData'])
139 $pageRepositoryMock =
140 $this->getAccessibleMock(PageRepository
::class, ['getRawRecord', 'getMountPointInfo']);
141 $this->frontendControllerMock
=
142 $this->getAccessibleMock(
143 TypoScriptFrontendController
::class,
149 $this->frontendControllerMock
->_set('context', GeneralUtility
::makeInstance(Context
::class));
150 $this->frontendControllerMock
->tmpl
= $this->templateServiceMock
;
151 $this->frontendControllerMock
->config
= [];
152 $this->frontendControllerMock
->page
= [];
153 $this->frontendControllerMock
->sys_page
= $pageRepositoryMock;
154 $GLOBALS['TSFE'] = $this->frontendControllerMock
;
156 $this->subject
= $this->getAccessibleMock(
157 ContentObjectRenderer
::class,
158 ['getResourceFactory', 'getEnvironmentVariable'],
159 [$this->frontendControllerMock
]
162 $logger = $this->prophesize(Logger
::class);
163 $this->subject
->setLogger($logger->reveal());
164 $this->subject
->setContentObjectClassMap($this->contentObjectMap
);
165 $this->subject
->start([], 'tt_content');
168 //////////////////////
170 //////////////////////
173 * @return TypoScriptFrontendController
175 protected function getFrontendController(): TypoScriptFrontendController
177 return $GLOBALS['TSFE'];
181 * Converts the subject and the expected result into utf-8.
183 * @param string $subject the subject, will be modified
184 * @param string $expected the expected result, will be modified
186 protected function handleCharset(string &$subject, string &$expected): void
188 $subject = mb_convert_encoding($subject, 'utf-8', 'iso-8859-1');
189 $expected = mb_convert_encoding($expected, 'utf-8', 'iso-8859-1');
192 /////////////////////////////////////////////
193 // Tests concerning the getImgResource hook
194 /////////////////////////////////////////////
198 public function getImgResourceCallsGetImgResourcePostProcessHook(): void
200 $cacheManagerProphecy = $this->prophesize(CacheManager
::class);
201 $cacheProphecy = $this->prophesize(CacheFrontendInterface
::class);
202 $cacheManagerProphecy->getCache('cache_imagesizes')->willReturn($cacheProphecy->reveal());
203 $cacheProphecy->get(Argument
::cetera())->willReturn(false);
204 $cacheProphecy->set(Argument
::cetera(), null)->willReturn(false);
205 GeneralUtility
::setSingletonInstance(CacheManager
::class, $cacheManagerProphecy->reveal());
207 $resourceFactory = $this->createMock(ResourceFactory
::class);
208 $this->subject
->expects($this->any())->method('getResourceFactory')->will($this->returnValue($resourceFactory));
210 $className = $this->getUniqueId('tx_coretest');
211 $getImgResourceHookMock = $this->getMockBuilder(ContentObjectGetImageResourceHookInterface
::class)
212 ->setMethods(['getImgResourcePostProcess'])
213 ->setMockClassName($className)
215 $getImgResourceHookMock
216 ->expects($this->once())
217 ->method('getImgResourcePostProcess')
218 ->will($this->returnCallback([$this, 'isGetImgResourceHookCalledCallback']));
219 $getImgResourceHookObjects = [$getImgResourceHookMock];
220 $this->subject
->_setRef('getImgResourceHookObjects', $getImgResourceHookObjects);
221 $this->subject
->getImgResource('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', []);
225 * Handles the arguments that have been sent to the getImgResource hook.
227 * @param string $file
228 * @param array $fileArray
229 * @param $imageResource
230 * @param ContentObjectRenderer $parent
232 * @see getImgResourceHookGetsCalled
234 public function isGetImgResourceHookCalledCallback(
238 ContentObjectRenderer
$parent
240 $this->assertEquals('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', $file);
241 $this->assertEquals('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', $imageResource['origFile']);
242 $this->assertTrue(is_array($fileArray));
243 $this->assertTrue($parent instanceof ContentObjectRenderer
);
244 return $imageResource;
247 //////////////////////////////////////
248 // Tests related to getContentObject
249 //////////////////////////////////////
252 * Show registration of a class for a TypoScript object name and getting
253 * the registered content object is working.
255 * Prove is done by successfully creating an object based on the mapping.
256 * Note two conditions in contrast to other tests, where the creation
259 * 1. The type must be of AbstractContentObject.
260 * 2. Registration can only be done by public methods.
264 public function canRegisterAContentObjectClassForATypoScriptName(): void
266 $className = TextContentObject
::class;
267 $contentObjectName = 'TEST_TEXT';
268 $this->subject
->registerContentObjectClass(
272 $object = $this->subject
->getContentObject($contentObjectName);
273 $this->assertInstanceOf($className, $object);
277 * Show that setting of the class map and getting a registered content
280 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
283 public function canSetTheContentObjectClassMapAndGetARegisteredContentObject(): void
285 $className = TextContentObject
::class;
286 $contentObjectName = 'TEST_TEXT';
287 $classMap = [$contentObjectName => $className];
288 $this->subject
->setContentObjectClassMap($classMap);
289 $object = $this->subject
->getContentObject($contentObjectName);
290 $this->assertInstanceOf($className, $object);
294 * Show that the map is not set as an externally accessible reference.
296 * Prove is done by missing success when trying to use it this way.
298 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
301 public function canNotAccessInternalContentObjectMapByReference(): void
303 $className = TextContentObject
::class;
304 $contentObjectName = 'TEST_TEXT';
306 $this->subject
->setContentObjectClassMap($classMap);
307 $classMap[$contentObjectName] = $className;
308 $object = $this->subject
->getContentObject($contentObjectName);
309 $this->assertNull($object);
313 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
316 public function willReturnNullForUnregisteredObject(): void
318 $object = $this->subject
->getContentObject('FOO');
319 $this->assertNull($object);
323 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
326 public function willThrowAnExceptionForARegisteredNonContentObject(): void
328 $this->expectException(ContentRenderingException
::class);
329 $this->subject
->registerContentObjectClass(
333 $this->subject
->getContentObject('STDCLASS');
337 * @return string[][] [[$name, $fullClassName],]
339 public function registersAllDefaultContentObjectsDataProvider(): array
342 foreach ($this->contentObjectMap
as $name => $className) {
343 $dataProvider[] = [$name, $className];
345 return $dataProvider;
349 * Prove that all content objects are registered and a class is available
353 * @dataProvider registersAllDefaultContentObjectsDataProvider
354 * @param string $objectName TypoScript name of content object
355 * @param string $className Expected class name
357 public function registersAllDefaultContentObjects(
362 is_subclass_of($className, AbstractContentObject
::class)
364 $object = $this->subject
->getContentObject($objectName);
365 $this->assertInstanceOf($className, $object);
368 /////////////////////////////////////////
369 // Tests concerning getQueryArguments()
370 /////////////////////////////////////////
374 public function getQueryArgumentsExcludesParameters(): void
376 $this->subject
->expects($this->any())->method('getEnvironmentVariable')->with($this->equalTo('QUERY_STRING'))->will(
377 $this->returnValue('key1=value1&key2=value2&key3[key31]=value31&key3[key32][key321]=value321&key3[key32][key322]=value322')
379 $getQueryArgumentsConfiguration = [];
380 $getQueryArgumentsConfiguration['exclude'] = [];
381 $getQueryArgumentsConfiguration['exclude'][] = 'key1';
382 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key31]';
383 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key32][key321]';
384 $getQueryArgumentsConfiguration['exclude'] = implode(',', $getQueryArgumentsConfiguration['exclude']);
385 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2&key3[key32][key322]=value322');
386 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration);
387 $this->assertEquals($expectedResult, $actualResult);
393 public function getQueryArgumentsExcludesGetParameters(): void
399 'key31' => 'value31',
401 'key321' => 'value321',
402 'key322' => 'value322'
406 $getQueryArgumentsConfiguration = [];
407 $getQueryArgumentsConfiguration['method'] = 'GET';
408 $getQueryArgumentsConfiguration['exclude'] = [];
409 $getQueryArgumentsConfiguration['exclude'][] = 'key1';
410 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key31]';
411 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key32][key321]';
412 $getQueryArgumentsConfiguration['exclude'] = implode(',', $getQueryArgumentsConfiguration['exclude']);
413 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2&key3[key32][key322]=value322');
414 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration);
415 $this->assertEquals($expectedResult, $actualResult);
421 public function getQueryArgumentsOverrulesSingleParameter(): void
423 $this->subject
->expects($this->any())->method('getEnvironmentVariable')->with($this->equalTo('QUERY_STRING'))->will(
424 $this->returnValue('key1=value1')
426 $getQueryArgumentsConfiguration = [];
427 $overruleArguments = [
428 // Should be overridden
429 'key1' => 'value1Overruled',
430 // Shouldn't be set: Parameter doesn't exist in source array and is not forced
431 'key2' => 'value2Overruled'
433 $expectedResult = '&key1=value1Overruled';
434 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments);
435 $this->assertEquals($expectedResult, $actualResult);
441 public function getQueryArgumentsOverrulesMultiDimensionalParameters(): void
447 'key31' => 'value31',
449 'key321' => 'value321',
450 'key322' => 'value322'
454 $getQueryArgumentsConfiguration = [];
455 $getQueryArgumentsConfiguration['method'] = 'POST';
456 $getQueryArgumentsConfiguration['exclude'] = [];
457 $getQueryArgumentsConfiguration['exclude'][] = 'key1';
458 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key31]';
459 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key32][key321]';
460 $getQueryArgumentsConfiguration['exclude'] = implode(',', $getQueryArgumentsConfiguration['exclude']);
461 $overruleArguments = [
462 // Should be overridden
463 'key2' => 'value2Overruled',
466 // Shouldn't be set: Parameter is excluded and not forced
467 'key321' => 'value321Overruled',
468 // Should be overridden: Parameter is not excluded
469 'key322' => 'value322Overruled',
470 // Shouldn't be set: Parameter doesn't exist in source array and is not forced
471 'key323' => 'value323Overruled'
475 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2Overruled&key3[key32][key322]=value322Overruled');
476 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments);
477 $this->assertEquals($expectedResult, $actualResult);
483 public function getQueryArgumentsOverrulesMultiDimensionalForcedParameters(): void
485 $this->subject
->expects($this->any())->method('getEnvironmentVariable')->with($this->equalTo('QUERY_STRING'))->will(
486 $this->returnValue('key1=value1&key2=value2&key3[key31]=value31&key3[key32][key321]=value321&key3[key32][key322]=value322')
492 'key31' => 'value31',
494 'key321' => 'value321',
495 'key322' => 'value322'
499 $getQueryArgumentsConfiguration = [];
500 $getQueryArgumentsConfiguration['exclude'] = [];
501 $getQueryArgumentsConfiguration['exclude'][] = 'key1';
502 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key31]';
503 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key32][key321]';
504 $getQueryArgumentsConfiguration['exclude'][] = 'key3[key32][key322]';
505 $getQueryArgumentsConfiguration['exclude'] = implode(',', $getQueryArgumentsConfiguration['exclude']);
506 $overruleArguments = [
507 // Should be overridden
508 'key2' => 'value2Overruled',
511 // Should be set: Parameter is excluded but forced
512 'key321' => 'value321Overruled',
513 // Should be set: Parameter doesn't exist in source array but is forced
514 'key323' => 'value323Overruled'
518 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2Overruled&key3[key32][key321]=value321Overruled&key3[key32][key323]=value323Overruled');
519 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments, true);
520 $this->assertEquals($expectedResult, $actualResult);
521 $getQueryArgumentsConfiguration['method'] = 'POST';
522 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments, true);
523 $this->assertEquals($expectedResult, $actualResult);
529 public function getQueryArgumentsWithMethodPostGetMergesParameters(): void
538 'key331' => 'POST331',
539 'key332' => 'POST332',
548 'key331' => 'GET331',
552 $getQueryArgumentsConfiguration = [];
553 $getQueryArgumentsConfiguration['method'] = 'POST,GET';
554 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key1=POST1&key2=GET2&key3[key31]=POST31&key3[key32]=GET32&key3[key33][key331]=GET331&key3[key33][key332]=POST332');
555 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration);
556 $this->assertEquals($expectedResult, $actualResult);
562 public function getQueryArgumentsWithMethodGetPostMergesParameters(): void
571 'key331' => 'GET331',
572 'key332' => 'GET332',
581 'key331' => 'POST331',
585 $getQueryArgumentsConfiguration = [];
586 $getQueryArgumentsConfiguration['method'] = 'GET,POST';
587 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key1=GET1&key2=POST2&key3[key31]=GET31&key3[key32]=POST32&key3[key33][key331]=POST331&key3[key33][key332]=GET332');
588 $actualResult = $this->subject
->getQueryArguments($getQueryArgumentsConfiguration);
589 $this->assertEquals($expectedResult, $actualResult);
593 * Encodes square brackets in URL.
595 * @param string $string
598 private function rawUrlEncodeSquareBracketsInUrl(string $string): string
600 return str_replace(['[', ']'], ['%5B', '%5D'], $string);
603 //////////////////////////
604 // Tests concerning crop
605 //////////////////////////
609 public function cropIsMultibyteSafe(): void
611 $this->assertEquals('бла', $this->subject
->crop('бла', '3|...'));
614 //////////////////////////////
616 //////////////////////////////
617 // Tests concerning cropHTML
618 //////////////////////////////
621 * Data provider for cropHTML.
623 * Provides combinations of text type and configuration.
625 * @return array [$expect, $conf, $content]
627 public function cropHTMLDataProvider(): array
629 $plainText = 'Kasper Sk' . chr(229) . 'rh' . chr(248)
630 . 'j implemented the original version of the crop function.';
631 $textWithMarkup = '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
632 . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong> the '
633 . 'original version of the crop function.';
634 $textWithEntities = 'Kasper Skårhøj implemented the; '
635 . 'original ' . 'version of the crop function.';
636 $textWithLinebreaks = "Lorem ipsum dolor sit amet,\n"
637 . "consetetur sadipscing elitr,\n"
638 . 'sed diam nonumy eirmod tempor invidunt ut labore e'
639 . 't dolore magna aliquyam';
642 'plain text; 11|...' => [
643 'Kasper Sk' . chr(229) . 'r...',
647 'plain text; -58|...' => [
648 '...h' . chr(248) . 'j implemented the original version of '
649 . 'the crop function.',
653 'plain text; 4|...|1' => [
658 'plain text; 20|...|1' => [
659 'Kasper Sk' . chr(229) . 'rh' . chr(248) . 'j...',
663 'plain text; -5|...|1' => [
668 'plain text; -49|...|1' => [
669 '...the original version of the crop function.',
673 'text with markup; 11|...' => [
674 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
675 . chr(229) . 'r...</a></strong>',
679 'text with markup; 13|...' => [
680 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
681 . chr(229) . 'rh' . chr(248) . '...</a></strong>',
685 'text with markup; 14|...' => [
686 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
687 . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
691 'text with markup; 15|...' => [
692 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
693 . chr(229) . 'rh' . chr(248) . 'j</a> ...</strong>',
697 'text with markup; 29|...' => [
698 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
699 . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong> '
704 'text with markup; -58|...' => [
705 '<strong><a href="mailto:kasper@typo3.org">...h' . chr(248)
706 . 'j</a> implemented</strong> the original version of the crop '
711 'text with markup 4|...|1' => [
712 '<strong><a href="mailto:kasper@typo3.org">Kasp...</a>'
717 'text with markup; 11|...|1' => [
718 '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
723 'text with markup; 13|...|1' => [
724 '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
729 'text with markup; 14|...|1' => [
730 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
731 . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
735 'text with markup; 15|...|1' => [
736 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
737 . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
741 'text with markup; 29|...|1' => [
742 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
743 . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong>...',
747 'text with markup; -66|...|1' => [
748 '<strong><a href="mailto:kasper@typo3.org">...Sk' . chr(229)
749 . 'rh' . chr(248) . 'j</a> implemented</strong> the original v'
750 . 'ersion of the crop function.',
754 'text with entities 9|...' => [
759 'text with entities 10|...' => [
760 'Kasper Skå...',
764 'text with entities 11|...' => [
765 'Kasper Skår...',
769 'text with entities 13|...' => [
770 'Kasper Skårhø...',
774 'text with entities 14|...' => [
775 'Kasper Skårhøj...',
779 'text with entities 15|...' => [
780 'Kasper Skårhøj ...',
784 'text with entities 16|...' => [
785 'Kasper Skårhøj i...',
789 'text with entities -57|...' => [
790 '...j implemented the; original version of the crop function.',
794 'text with entities -58|...' => [
795 '...øj implemented the; original version of the crop '
800 'text with entities -59|...' => [
801 '...høj implemented the; original version of the crop '
806 'text with entities 4|...|1' => [
811 'text with entities 9|...|1' => [
816 'text with entities 10|...|1' => [
821 'text with entities 11|...|1' => [
826 'text with entities 13|...|1' => [
831 'text with entities 14|...|1' => [
832 'Kasper Skårhøj...',
836 'text with entities 15|...|1' => [
837 'Kasper Skårhøj...',
841 'text with entities 16|...|1' => [
842 'Kasper Skårhøj...',
846 'text with entities -57|...|1' => [
847 '...implemented the; original version of the crop function.',
851 'text with entities -58|...|1' => [
852 '...implemented the; original version of the crop function.',
856 'text with entities -59|...|1' => [
857 '...implemented the; original version of the crop function.',
861 'text with dash in html-element 28|...|1' => [
862 'Some text with a link to <link email.address@example.org - '
863 . 'mail "Open email window">my...</link>',
864 'Some text with a link to <link email.address@example.org - m'
865 . 'ail "Open email window">my email.address@example.org<'
866 . '/link> and text after it',
869 'html elements with dashes in attributes' => [
870 '<em data-foo="x">foobar</em>foo',
871 '<em data-foo="x">foobar</em>foobaz',
874 'html elements with iframe embedded 24|...|1' => [
875 'Text with iframe <iframe src="//what.ever/"></iframe> and...',
876 'Text with iframe <iframe src="//what.ever/">'
877 . '</iframe> and text after it',
880 'html elements with script tag embedded 24|...|1' => [
881 'Text with script <script>alert(\'foo\');</script> and...',
882 'Text with script <script>alert(\'foo\');</script> '
883 . 'and text after it',
886 'text with linebreaks' => [
887 "Lorem ipsum dolor sit amet,\nconsetetur sadipscing elitr,\ns"
888 . 'ed diam nonumy eirmod tempor invidunt ut labore e'
897 * Check if cropHTML works properly.
900 * @dataProvider cropHTMLDataProvider
901 * @param string $expect The expected cropped output.
902 * @param string $content The given input.
903 * @param string $conf The given configuration.
905 public function cropHTML(string $expect, string $content, string $conf): void
907 $this->handleCharset($content, $expect);
910 $this->subject
->cropHTML($content, $conf)
915 * Data provider for round
917 * @return array [$expect, $contet, $conf]
919 public function roundDataProvider(): array
923 'down' => [1.0, 1.11, []],
924 'up' => [2.0, 1.51, []],
925 'rounds up from x.50' => [2.0, 1.50, []],
926 'down with decimals' => [0.12, 0.1231, ['decimals' => 2]],
927 'up with decimals' => [0.13, 0.1251, ['decimals' => 2]],
928 'ceil' => [1.0, 0.11, ['roundType' => 'ceil']],
929 'ceil does not accept decimals' => [
933 'roundType' => 'ceil',
937 'floor' => [2.0, 2.99, ['roundType' => 'floor']],
938 'floor does not accept decimals' => [
942 'roundType' => 'floor',
946 'round, down' => [1.0, 1.11, ['roundType' => 'round']],
947 'round, up' => [2.0, 1.55, ['roundType' => 'round']],
948 'round does accept decimals' => [
952 'roundType' => 'round',
957 'emtpy string' => [0.0, '', []],
958 'word string' => [0.0, 'word', []],
959 'float string' => [1.0, '1.123456789', []],
961 'null' => [0.0, null, []],
962 'false' => [0.0, false, []],
963 'true' => [1.0, true, []]
968 * Check if round works properly
972 * - Different types of input are casted to float.
973 * - Configuration ceil rounds like ceil().
974 * - Configuration floor rounds like floor().
975 * - Otherwise rounds like round() and decimals can be applied.
976 * - Always returns float.
978 * @param float $expect The expected output.
979 * @param mixed $content The given content.
980 * @param array $conf The given configuration of 'round.'.
981 * @dataProvider roundDataProvider
984 public function round(float $expect, $content, array $conf): void
988 $this->subject
->_call('round', $content, $conf)
995 public function recursiveStdWrapProperlyRendersBasicString(): void
997 $stdWrapConfiguration = [
998 'noTrimWrap' => '|| 123|',
1000 'wrap' => '<b>|</b>'
1005 $this->subject
->stdWrap('Test', $stdWrapConfiguration)
1012 public function recursiveStdWrapIsOnlyCalledOnce(): void
1014 $stdWrapConfiguration = [
1017 'data' => 'register:Counter'
1020 'append' => 'LOAD_REGISTER',
1023 'prioriCalc' => 'intval',
1024 'cObject' => 'TEXT',
1026 'data' => 'register:Counter',
1035 $this->subject
->stdWrap('Counter:', $stdWrapConfiguration)
1040 * Data provider for numberFormat.
1042 * @return array [$expect, $content, $conf]
1044 public function numberFormatDataProvider(): array
1047 'testing decimals' => [
1052 'testing decimals with input as string' => [
1057 'testing dec_point' => [
1060 ['decimals' => 1, 'dec_point' => ',']
1062 'testing thousands_sep' => [
1067 'thousands_sep.' => ['char' => 46]
1070 'testing mixture' => [
1075 'dec_point.' => ['char' => 44],
1076 'thousands_sep.' => ['char' => 46]
1083 * Check if numberFormat works properly.
1085 * @dataProvider numberFormatDataProvider
1087 * @param string $expects
1088 * @param mixed $content
1089 * @param array $conf
1091 public function numberFormat(string $expects, $content, array $conf): void
1095 $this->subject
->numberFormat($content, $conf)
1100 * Data provider replacement
1102 * @return array [$expect, $content, $conf]
1104 public function replacementDataProvider(): array
1107 'multiple replacements, including regex' => [
1108 'There is an animal, an animal and an animal around the block! Yeah!',
1109 'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
1113 'replace.' => ['char' => '32']
1116 'search' => 'in da hood',
1117 'replace' => 'around the block'
1120 'search' => '#a (Cat|Dog|Tiger)#i',
1121 'replace' => 'an animal',
1126 'replacement with optionSplit, normal pattern' => [
1127 'There1is2a3cat,3a3dog3and3a3tiger3in3da3hood!3Yeah!',
1128 'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
1132 'replace' => '1 || 2 || 3',
1133 'useOptionSplitReplace' => '1'
1137 'replacement with optionSplit, using regex' => [
1138 'There is a tiny cat, a midsized dog and a big tiger in da hood! Yeah!',
1139 'There is a cat, a dog and a tiger in da hood! Yeah!',
1142 'search' => '#(a) (Cat|Dog|Tiger)#i',
1143 'replace' => '${1} tiny ${2} || ${1} midsized ${2} || ${1} big ${2}',
1144 'useOptionSplitReplace' => '1',
1153 * Check if stdWrap.replacement and all of its properties work properly
1156 * @dataProvider replacementDataProvider
1157 * @param string $content The given input.
1158 * @param string $expects The expected result.
1159 * @param array $conf The given configuration.
1161 public function replacement(string $expects, string $content, array $conf): void
1165 $this->subject
->_call('replacement', $content, $conf)
1170 * Data provider for calcAge.
1172 * @return array [$expect, $timestamp, $labels]
1174 public function calcAgeDataProvider(): array
1180 ' min| hrs| days| yrs',
1185 ' min| hrs| days| yrs',
1190 ' min| hrs| days| yrs',
1192 'day with provided singular labels' => [
1195 ' min| hrs| days| yrs| min| hour| day| year',
1200 ' min| hrs| days| yrs',
1202 'different labels' => [
1205 ' Minutes| Hrs| Days| Yrs',
1207 'negative values' => [
1210 ' min| hrs| days| yrs',
1212 'default label values for wrong label input' => [
1217 'default singular label values for wrong label input' => [
1226 * Check if calcAge works properly.
1229 * @dataProvider calcAgeDataProvider
1230 * @param string $expect
1231 * @param int $timestamp
1232 * @param string $labels
1234 public function calcAge(string $expect, int $timestamp, string $labels): void
1238 $this->subject
->calcAge($timestamp, $labels)
1245 public function stdWrapReturnsExpectationDataProvider(): array
1248 'Prevent silent bool conversion' => [
1261 * @param string $content
1262 * @param array $configuration
1263 * @param string $expectation
1264 * @dataProvider stdWrapReturnsExpectationDataProvider
1267 public function stdWrapReturnsExpectation(string $content, array $configuration, string $expectation): void
1269 $this->assertSame($expectation, $this->subject
->stdWrap($content, $configuration));
1273 * Data provider for substring
1275 * @return array [$expect, $content, $conf]
1277 public function substringDataProvider(): array
1280 'sub -1' => ['g', 'substring', '-1'],
1281 'sub -1,0' => ['g', 'substring', '-1,0'],
1282 'sub -1,-1' => ['', 'substring', '-1,-1'],
1283 'sub -1,1' => ['g', 'substring', '-1,1'],
1284 'sub 0' => ['substring', 'substring', '0'],
1285 'sub 0,0' => ['substring', 'substring', '0,0'],
1286 'sub 0,-1' => ['substrin', 'substring', '0,-1'],
1287 'sub 0,1' => ['s', 'substring', '0,1'],
1288 'sub 1' => ['ubstring', 'substring', '1'],
1289 'sub 1,0' => ['ubstring', 'substring', '1,0'],
1290 'sub 1,-1' => ['ubstrin', 'substring', '1,-1'],
1291 'sub 1,1' => ['u', 'substring', '1,1'],
1292 'sub' => ['substring', 'substring', ''],
1297 * Check if substring works properly.
1300 * @dataProvider substringDataProvider
1301 * @param string $expect The expected output.
1302 * @param string $content The given input.
1303 * @param string $conf The given configurationn.
1305 public function substring(string $expect, string $content, string $conf): void
1307 $this->assertSame($expect, $this->subject
->substring($content, $conf));
1310 ///////////////////////////////
1311 // Tests concerning getData()
1312 ///////////////////////////////
1317 public function getDataWithTypeGpDataProvider(): array
1320 'Value in get-data' => ['onlyInGet', 'GetValue'],
1321 'Value in post-data' => ['onlyInPost', 'PostValue'],
1322 'Value in post-data overriding get-data' => ['inGetAndPost', 'ValueInPost'],
1327 * Checks if getData() works with type "gp"
1330 * @dataProvider getDataWithTypeGpDataProvider
1331 * @param string $key
1332 * @param string $expectedValue
1334 public function getDataWithTypeGp(string $key, string $expectedValue): void
1337 'onlyInGet' => 'GetValue',
1338 'inGetAndPost' => 'ValueInGet',
1341 'onlyInPost' => 'PostValue',
1342 'inGetAndPost' => 'ValueInPost',
1344 $this->assertEquals($expectedValue, $this->subject
->getData('gp:' . $key));
1348 * Checks if getData() works with type "tsfe"
1352 public function getDataWithTypeTsfe(): void
1354 $this->assertEquals($GLOBALS['TSFE']->metaCharset
, $this->subject
->getData('tsfe:metaCharset'));
1358 * Checks if getData() works with type "getenv"
1362 public function getDataWithTypeGetenv(): void
1364 $envName = $this->getUniqueId('frontendtest');
1365 $value = $this->getUniqueId('someValue');
1366 putenv($envName . '=' . $value);
1367 $this->assertEquals($value, $this->subject
->getData('getenv:' . $envName));
1371 * Checks if getData() works with type "getindpenv"
1375 public function getDataWithTypeGetindpenv(): void
1377 $this->subject
->expects($this->once())->method('getEnvironmentVariable')
1378 ->with($this->equalTo('SCRIPT_FILENAME'))->will($this->returnValue('dummyPath'));
1379 $this->assertEquals('dummyPath', $this->subject
->getData('getindpenv:SCRIPT_FILENAME'));
1383 * Checks if getData() works with type "field"
1387 public function getDataWithTypeField(): void
1390 $value = 'someValue';
1391 $field = [$key => $value];
1393 $this->assertEquals($value, $this->subject
->getData('field:' . $key, $field));
1397 * Checks if getData() works with type "field" of the field content
1398 * is multi-dimensional (e.g. an array)
1402 public function getDataWithTypeFieldAndFieldIsMultiDimensional(): void
1404 $key = 'somekey|level1|level2';
1405 $value = 'somevalue';
1406 $field = ['somekey' => ['level1' => ['level2' => 'somevalue']]];
1408 $this->assertEquals($value, $this->subject
->getData('field:' . $key, $field));
1412 * Basic check if getData gets the uid of a file object
1416 public function getDataWithTypeFileReturnsUidOfFileObject(): void
1418 $uid = $this->getUniqueId();
1419 $file = $this->createMock(File
::class);
1420 $file->expects($this->once())->method('getUid')->will($this->returnValue($uid));
1421 $this->subject
->setCurrentFile($file);
1422 $this->assertEquals($uid, $this->subject
->getData('file:current:uid'));
1426 * Checks if getData() works with type "parameters"
1430 public function getDataWithTypeParameters(): void
1432 $key = $this->getUniqueId('someKey');
1433 $value = $this->getUniqueId('someValue');
1434 $this->subject
->parameters
[$key] = $value;
1436 $this->assertEquals($value, $this->subject
->getData('parameters:' . $key));
1440 * Checks if getData() works with type "register"
1444 public function getDataWithTypeRegister(): void
1446 $key = $this->getUniqueId('someKey');
1447 $value = $this->getUniqueId('someValue');
1448 $GLOBALS['TSFE']->register
[$key] = $value;
1450 $this->assertEquals($value, $this->subject
->getData('register:' . $key));
1454 * Checks if getData() works with type "session"
1458 public function getDataWithTypeSession(): void
1460 $frontendUser = $this->getMockBuilder(FrontendUserAuthentication
::class)
1461 ->setMethods(['getSessionData'])
1463 $frontendUser->expects($this->once())->method('getSessionData')->with('myext')->willReturn([
1468 $GLOBALS['TSFE']->fe_user
= $frontendUser;
1470 $this->assertEquals(42, $this->subject
->getData('session:myext|mydata|someValue'));
1474 * Checks if getData() works with type "level"
1478 public function getDataWithTypeLevel(): void
1481 0 => ['uid' => 1, 'title' => 'title1'],
1482 1 => ['uid' => 2, 'title' => 'title2'],
1483 2 => ['uid' => 3, 'title' => 'title3'],
1486 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1487 $this->assertEquals(2, $this->subject
->getData('level'));
1491 * Checks if getData() works with type "global"
1495 public function getDataWithTypeGlobal(): void
1497 $this->assertEquals($GLOBALS['TSFE']->metaCharset
, $this->subject
->getData('global:TSFE|metaCharset'));
1501 * Checks if getData() works with type "leveltitle"
1505 public function getDataWithTypeLeveltitle(): void
1508 0 => ['uid' => 1, 'title' => 'title1'],
1509 1 => ['uid' => 2, 'title' => 'title2'],
1510 2 => ['uid' => 3, 'title' => ''],
1513 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1514 $this->assertEquals('', $this->subject
->getData('leveltitle:-1'));
1515 // since "title3" is not set, it will slide to "title2"
1516 $this->assertEquals('title2', $this->subject
->getData('leveltitle:-1,slide'));
1520 * Checks if getData() works with type "levelmedia"
1524 public function getDataWithTypeLevelmedia(): void
1527 0 => ['uid' => 1, 'title' => 'title1', 'media' => 'media1'],
1528 1 => ['uid' => 2, 'title' => 'title2', 'media' => 'media2'],
1529 2 => ['uid' => 3, 'title' => 'title3', 'media' => ''],
1532 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1533 $this->assertEquals('', $this->subject
->getData('levelmedia:-1'));
1534 // since "title3" is not set, it will slide to "title2"
1535 $this->assertEquals('media2', $this->subject
->getData('levelmedia:-1,slide'));
1539 * Checks if getData() works with type "leveluid"
1543 public function getDataWithTypeLeveluid(): void
1546 0 => ['uid' => 1, 'title' => 'title1'],
1547 1 => ['uid' => 2, 'title' => 'title2'],
1548 2 => ['uid' => 3, 'title' => 'title3'],
1551 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1552 $this->assertEquals(3, $this->subject
->getData('leveluid:-1'));
1553 // every element will have a uid - so adding slide doesn't really make sense, just for completeness
1554 $this->assertEquals(3, $this->subject
->getData('leveluid:-1,slide'));
1558 * Checks if getData() works with type "levelfield"
1562 public function getDataWithTypeLevelfield(): void
1565 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1566 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1567 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => ''],
1570 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1571 $this->assertEquals('', $this->subject
->getData('levelfield:-1,testfield'));
1572 $this->assertEquals('field2', $this->subject
->getData('levelfield:-1,testfield,slide'));
1576 * Checks if getData() works with type "fullrootline"
1580 public function getDataWithTypeFullrootline(): void
1583 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1586 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1587 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1588 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => 'field3'],
1591 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline1;
1592 $GLOBALS['TSFE']->rootLine
= $rootline2;
1593 $this->assertEquals('field2', $this->subject
->getData('fullrootline:-1,testfield'));
1597 * Checks if getData() works with type "date"
1601 public function getDataWithTypeDate(): void
1604 $defaultFormat = 'd/m Y';
1606 $this->assertEquals(date($format, $GLOBALS['EXEC_TIME']), $this->subject
->getData('date:' . $format));
1607 $this->assertEquals(date($defaultFormat, $GLOBALS['EXEC_TIME']), $this->subject
->getData('date'));
1611 * Checks if getData() works with type "page"
1615 public function getDataWithTypePage(): void
1618 $GLOBALS['TSFE']->page
['uid'] = $uid;
1619 $this->assertEquals($uid, $this->subject
->getData('page:uid'));
1623 * Checks if getData() works with type "current"
1627 public function getDataWithTypeCurrent(): void
1629 $key = $this->getUniqueId('someKey');
1630 $value = $this->getUniqueId('someValue');
1631 $this->subject
->data
[$key] = $value;
1632 $this->subject
->currentValKey
= $key;
1633 $this->assertEquals($value, $this->subject
->getData('current'));
1637 * Checks if getData() works with type "db"
1641 public function getDataWithTypeDb(): void
1643 $dummyRecord = ['uid' => 5, 'title' => 'someTitle'];
1645 $GLOBALS['TSFE']->sys_page
->expects($this->atLeastOnce())->method('getRawRecord')->with(
1648 )->will($this->returnValue($dummyRecord));
1649 $this->assertEquals($dummyRecord['title'], $this->subject
->getData('db:tt_content:106:title'));
1653 * Checks if getData() works with type "lll"
1657 public function getDataWithTypeLll(): void
1659 $key = $this->getUniqueId('someKey');
1660 $value = $this->getUniqueId('someValue');
1661 $GLOBALS['TSFE']->expects($this->once())->method('sL')->with('LLL:' . $key)->will($this->returnValue($value));
1662 $this->assertEquals($value, $this->subject
->getData('lll:' . $key));
1666 * Checks if getData() works with type "path"
1670 public function getDataWithTypePath(): void
1672 $filenameIn = 'typo3/sysext/frontend/Public/Icons/Extension.svg';
1673 $this->assertEquals($filenameIn, $this->subject
->getData('path:' . $filenameIn));
1677 * Checks if getData() works with type "context"
1681 public function getDataWithTypeContext(): void
1683 $context = new Context([
1684 'workspace' => new WorkspaceAspect(3),
1685 'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [0, -1])
1687 GeneralUtility
::setSingletonInstance(Context
::class, $context);
1688 $this->assertEquals(3, $this->subject
->getData('context:workspace:id'));
1689 $this->assertEquals('0,-1', $this->subject
->getData('context:frontend.user:groupIds'));
1690 $this->assertEquals(false, $this->subject
->getData('context:frontend.user:isLoggedIn'));
1691 $this->assertEquals(false, $this->subject
->getData('context:frontend.user:foozball'));
1695 * Checks if getData() works with type "context"
1699 public function getDataWithTypeSite(): void
1701 $site = new Site('my-site', 123, [
1702 'base' => 'http://example.com',
1709 $serverRequest = $this->prophesize(ServerRequestInterface
::class);
1710 $serverRequest->getAttribute('site')->willReturn($site);
1711 $GLOBALS['TYPO3_REQUEST'] = $serverRequest->reveal();
1712 $this->assertEquals('http://example.com', $this->subject
->getData('site:base'));
1713 $this->assertEquals('yeah', $this->subject
->getData('site:custom.config.nested'));
1717 * Checks if getData() works with type "parentRecordNumber"
1721 public function getDataWithTypeParentRecordNumber(): void
1723 $recordNumber = mt_rand();
1724 $this->subject
->parentRecordNumber
= $recordNumber;
1725 $this->assertEquals($recordNumber, $this->subject
->getData('cobj:parentRecordNumber'));
1729 * Checks if getData() works with type "debug:rootLine"
1733 public function getDataWithTypeDebugRootline(): void
1736 0 => ['uid' => 1, 'title' => 'title1'],
1737 1 => ['uid' => 2, 'title' => 'title2'],
1738 2 => ['uid' => 3, 'title' => ''],
1740 $expectedResult = 'array(3items)0=>array(2items)uid=>1(integer)title=>"title1"(6chars)1=>array(2items)uid=>2(integer)title=>"title2"(6chars)2=>array(2items)uid=>3(integer)title=>""(0chars)';
1741 $GLOBALS['TSFE']->tmpl
->rootLine
= $rootline;
1743 DebugUtility
::useAnsiColor(false);
1744 $result = $this->subject
->getData('debug:rootLine');
1745 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1747 $this->assertEquals($expectedResult, $cleanedResult);
1751 * Checks if getData() works with type "debug:fullRootLine"
1755 public function getDataWithTypeDebugFullRootline(): void
1758 0 => ['uid' => 1, 'title' => 'title1'],
1759 1 => ['uid' => 2, 'title' => 'title2'],
1760 2 => ['uid' => 3, 'title' => ''],
1762 $expectedResult = 'array(3items)0=>array(2items)uid=>1(integer)title=>"title1"(6chars)1=>array(2items)uid=>2(integer)title=>"title2"(6chars)2=>array(2items)uid=>3(integer)title=>""(0chars)';
1763 $GLOBALS['TSFE']->rootLine
= $rootline;
1765 DebugUtility
::useAnsiColor(false);
1766 $result = $this->subject
->getData('debug:fullRootLine');
1767 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1769 $this->assertEquals($expectedResult, $cleanedResult);
1773 * Checks if getData() works with type "debug:data"
1777 public function getDataWithTypeDebugData(): void
1779 $key = $this->getUniqueId('someKey');
1780 $value = $this->getUniqueId('someValue');
1781 $this->subject
->data
= [$key => $value];
1783 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1785 DebugUtility
::useAnsiColor(false);
1786 $result = $this->subject
->getData('debug:data');
1787 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1789 $this->assertEquals($expectedResult, $cleanedResult);
1793 * Checks if getData() works with type "debug:register"
1797 public function getDataWithTypeDebugRegister(): void
1799 $key = $this->getUniqueId('someKey');
1800 $value = $this->getUniqueId('someValue');
1801 $GLOBALS['TSFE']->register
= [$key => $value];
1803 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1805 DebugUtility
::useAnsiColor(false);
1806 $result = $this->subject
->getData('debug:register');
1807 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1809 $this->assertEquals($expectedResult, $cleanedResult);
1813 * Checks if getData() works with type "data:page"
1817 public function getDataWithTypeDebugPage(): void
1820 $GLOBALS['TSFE']->page
= ['uid' => $uid];
1822 $expectedResult = 'array(1item)uid=>' . $uid . '(integer)';
1824 DebugUtility
::useAnsiColor(false);
1825 $result = $this->subject
->getData('debug:page');
1826 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1828 $this->assertEquals($expectedResult, $cleanedResult);
1834 public function aTagParamsHasLeadingSpaceIfNotEmpty(): void
1836 $aTagParams = $this->subject
->getATagParams(['ATagParams' => 'data-test="testdata"']);
1837 $this->assertEquals(' data-test="testdata"', $aTagParams);
1843 public function aTagParamsHaveSpaceBetweenLocalAndGlobalParams(): void
1845 $GLOBALS['TSFE']->ATagParams
= 'data-global="dataglobal"';
1846 $aTagParams = $this->subject
->getATagParams(['ATagParams' => 'data-test="testdata"']);
1847 $this->assertEquals(' data-global="dataglobal" data-test="testdata"', $aTagParams);
1853 public function aTagParamsHasNoLeadingSpaceIfEmpty(): void
1855 // make sure global ATagParams are empty
1856 $GLOBALS['TSFE']->ATagParams
= '';
1857 $aTagParams = $this->subject
->getATagParams(['ATagParams' => '']);
1858 $this->assertEquals('', $aTagParams);
1864 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider(): array
1870 ['fooo', ['foo' => 'bar']]
1875 * Make sure that the rendering falls back to the classic <img style if nothing else is found
1878 * @dataProvider getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider
1879 * @param string $key
1880 * @param array $configuration
1882 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFound($key, $configuration): void
1884 $defaultImgTagTemplate = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1885 $result = $this->subject
->getImageTagTemplate($key, $configuration);
1886 $this->assertEquals($result, $defaultImgTagTemplate);
1892 public function getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider(): array
1900 'element' => '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1904 '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1911 * Assure if a layoutKey and layout is given the selected layout is returned
1914 * @dataProvider getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider
1915 * @param string $key
1916 * @param array $configuration
1917 * @param string $expectation
1919 public function getImageTagTemplateReturnTemplateElementIdentifiedByKey($key, $configuration, $expectation): void
1921 $result = $this->subject
->getImageTagTemplate($key, $configuration);
1922 $this->assertEquals($result, $expectation);
1928 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider(): array
1932 ['foo', null, null],
1933 ['foo', ['sourceCollection.' => 1], 'bar']
1938 * Make sure the source collection is empty if no valid configuration or source collection is defined
1941 * @dataProvider getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider
1942 * @param string $layoutKey
1943 * @param array $configuration
1944 * @param string $file
1946 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefined(
1951 $result = $this->subject
->getImageSourceCollection($layoutKey, $configuration, $file);
1952 $this->assertSame($result, '');
1956 * Make sure the generation of subimages calls the generation of the subimages and uses the layout -> source template
1960 public function getImageSourceCollectionRendersDefinedSources(): void
1962 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
1963 $cObj = $this->getMockBuilder(ContentObjectRenderer
::class)
1964 ->setMethods(['stdWrap', 'getImgResource'])
1967 $cObj->start([], 'tt_content');
1969 $layoutKey = 'test';
1972 'layoutKey' => 'test',
1975 'element' => '<img ###SRC### ###SRCCOLLECTION### ###SELFCLOSINGTAGSLASH###>',
1976 'source' => '---###SRC###---'
1979 'sourceCollection.' => [
1986 $file = 'testImageName';
1988 // Avoid calling of stdWrap
1990 ->expects($this->any())
1992 ->will($this->returnArgument(0));
1994 // Avoid calling of imgResource
1996 ->expects($this->exactly(1))
1997 ->method('getImgResource')
1998 ->with($this->equalTo('testImageName'))
1999 ->will($this->returnValue([100, 100, null, 'bar']));
2001 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2003 $this->assertEquals('---bar---', $result);
2007 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyDefault test
2009 * @return array multi-dimensional array with the second level like this:
2010 * @see getImageSourceCollectionRendersDefinedLayoutKeyDefault
2012 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider(): array
2014 $sourceCollectionArray = [
2017 'srcsetCandidate' => '600w',
2018 'mediaQuery' => '(max-device-width: 600px)',
2019 'dataKey' => 'small',
2022 'if.directReturn' => 0,
2024 'pixelDensity' => '2',
2025 'srcsetCandidate' => '600w 2x',
2026 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
2027 'dataKey' => 'smallRetina',
2034 'layoutKey' => 'default',
2037 'element' => '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>',
2041 'sourceCollection.' => $sourceCollectionArray
2048 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
2051 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider
2052 * @param string $layoutKey
2053 * @param array $configuration
2055 public function getImageSourceCollectionRendersDefinedLayoutKeyDefault($layoutKey, $configuration): void
2057 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
2058 $cObj = $this->getMockBuilder(ContentObjectRenderer
::class)
2059 ->setMethods(['stdWrap', 'getImgResource'])
2062 $cObj->start([], 'tt_content');
2064 $file = 'testImageName';
2066 // Avoid calling of stdWrap
2068 ->expects($this->any())
2070 ->will($this->returnArgument(0));
2072 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2074 $this->assertEmpty($result);
2078 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyData test
2080 * @return array multi-dimensional array with the second level like this:
2081 * @see getImageSourceCollectionRendersDefinedLayoutKeyData
2083 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider(): array
2085 $sourceCollectionArray = [
2088 'srcsetCandidate' => '600w',
2089 'mediaQuery' => '(max-device-width: 600px)',
2090 'dataKey' => 'small',
2093 'if.directReturn' => 1,
2095 'pixelDensity' => '2',
2096 'srcsetCandidate' => '600w 2x',
2097 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
2098 'dataKey' => 'smallRetina',
2105 'layoutKey' => 'srcset',
2108 'element' => '<img src="###SRC###" srcset="###SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2109 'source' => '|*|###SRC### ###SRCSETCANDIDATE###,|*|###SRC### ###SRCSETCANDIDATE###'
2112 'sourceCollection.' => $sourceCollectionArray
2115 'bar-file.jpg 600w,bar-file.jpg 600w 2x',
2120 'layoutKey' => 'picture',
2123 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
2124 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
2127 'sourceCollection.' => $sourceCollectionArray,
2130 '<source src="bar-file.jpg" media="(max-device-width: 600px)" /><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)" />',
2135 'layoutKey' => 'picture',
2138 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
2139 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
2142 'sourceCollection.' => $sourceCollectionArray,
2145 '<source src="bar-file.jpg" media="(max-device-width: 600px)"><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)">',
2150 'layoutKey' => 'data',
2153 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2154 'source' => 'data-###DATAKEY###="###SRC###"'
2157 'sourceCollection.' => $sourceCollectionArray
2160 'data-small="bar-file.jpg"data-smallRetina="bar-file.jpg"',
2166 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
2169 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider
2170 * @param string $layoutKey
2171 * @param array $configuration
2172 * @param string $xhtmlDoctype
2173 * @param string $expectedHtml
2175 public function getImageSourceCollectionRendersDefinedLayoutKeyData(
2181 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
2182 $cObj = $this->getMockBuilder(ContentObjectRenderer
::class)
2183 ->setMethods(['stdWrap', 'getImgResource'])
2186 $cObj->start([], 'tt_content');
2188 $file = 'testImageName';
2190 $GLOBALS['TSFE']->xhtmlDoctype
= $xhtmlDoctype;
2192 // Avoid calling of stdWrap
2194 ->expects($this->any())
2196 ->will($this->returnArgument(0));
2198 // Avoid calling of imgResource
2200 ->expects($this->exactly(2))
2201 ->method('getImgResource')
2202 ->with($this->equalTo('testImageName'))
2203 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
2205 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2207 $this->assertEquals($expectedHtml, $result);
2211 * Make sure the hook in get sourceCollection is called
2215 public function getImageSourceCollectionHookCalled(): void
2217 $this->subject
= $this->getAccessibleMock(
2218 ContentObjectRenderer
::class,
2219 ['getResourceFactory', 'stdWrap', 'getImgResource']
2221 $this->subject
->start([], 'tt_content');
2223 // Avoid calling stdwrap and getImgResource
2224 $this->subject
->expects($this->any())
2226 ->will($this->returnArgument(0));
2228 $this->subject
->expects($this->any())
2229 ->method('getImgResource')
2230 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
2232 $resourceFactory = $this->createMock(ResourceFactory
::class);
2233 $this->subject
->expects($this->any())->method('getResourceFactory')->will($this->returnValue($resourceFactory));
2235 $className = $this->getUniqueId('tx_coretest_getImageSourceCollectionHookCalled');
2236 $getImageSourceCollectionHookMock = $this->getMockBuilder(
2237 ContentObjectOneSourceCollectionHookInterface
::class
2239 ->setMethods(['getOneSourceCollection'])
2240 ->setMockClassName($className)
2242 GeneralUtility
::addInstance($className, $getImageSourceCollectionHookMock);
2243 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'][] = $className;
2245 $getImageSourceCollectionHookMock
2246 ->expects($this->exactly(1))
2247 ->method('getOneSourceCollection')
2248 ->will($this->returnCallback([$this, 'isGetOneSourceCollectionCalledCallback']));
2251 'layoutKey' => 'data',
2254 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2255 'source' => 'data-###DATAKEY###="###SRC###"'
2258 'sourceCollection.' => [
2261 'srcsetCandidate' => '600w',
2262 'mediaQuery' => '(max-device-width: 600px)',
2263 'dataKey' => 'small',
2268 $result = $this->subject
->getImageSourceCollection('data', $configuration, $this->getUniqueId('testImage-'));
2270 $this->assertSame($result, 'isGetOneSourceCollectionCalledCallback');
2274 * Handles the arguments that have been sent to the getImgResource hook.
2276 * @param array $sourceRenderConfiguration
2277 * @param array $sourceConfiguration
2278 * @param $oneSourceCollection
2281 * @see getImageSourceCollectionHookCalled
2283 public function isGetOneSourceCollectionCalledCallback(
2284 array $sourceRenderConfiguration,
2285 array $sourceConfiguration,
2286 $oneSourceCollection,
2289 $this->assertTrue(is_array($sourceRenderConfiguration));
2290 $this->assertTrue(is_array($sourceConfiguration));
2291 return 'isGetOneSourceCollectionCalledCallback';
2297 public function renderingContentObjectThrowsException(): void
2299 $this->expectException(\LogicException
::class);
2300 $this->expectExceptionCode(1414513947);
2301 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2302 $this->subject
->render($contentObjectFixture, []);
2308 public function exceptionHandlerIsEnabledByDefaultInProductionContext(): void
2310 $backupApplicationContext = GeneralUtility
::getApplicationContext();
2311 Fixtures\GeneralUtilityFixture
::setApplicationContext(new ApplicationContext('Production'));
2313 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2314 $this->subject
->render($contentObjectFixture, []);
2316 Fixtures\GeneralUtilityFixture
::setApplicationContext($backupApplicationContext);
2322 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredLocally(): void
2324 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2327 'exceptionHandler' => '1'
2329 $this->subject
->render($contentObjectFixture, $configuration);
2335 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredGlobally(): void
2337 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2339 $this->frontendControllerMock
->config
['config']['contentObjectExceptionHandler'] = '1';
2340 $this->subject
->render($contentObjectFixture, []);
2346 public function globalExceptionHandlerConfigurationCanBeOverriddenByLocalConfiguration(): void
2348 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2349 $this->expectException(\LogicException
::class);
2350 $this->expectExceptionCode(1414513947);
2351 $this->frontendControllerMock
->config
['config']['contentObjectExceptionHandler'] = '1';
2353 'exceptionHandler' => '0'
2355 $this->subject
->render($contentObjectFixture, $configuration);
2361 public function renderedErrorMessageCanBeCustomized(): void
2363 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2366 'exceptionHandler' => '1',
2367 'exceptionHandler.' => [
2368 'errorMessage' => 'New message for testing',
2372 $this->assertSame('New message for testing', $this->subject
->render($contentObjectFixture, $configuration));
2378 public function localConfigurationOverridesGlobalConfiguration(): void
2380 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2382 $this->frontendControllerMock
2383 ->config
['config']['contentObjectExceptionHandler.'] = [
2384 'errorMessage' => 'Global message for testing',
2387 'exceptionHandler' => '1',
2388 'exceptionHandler.' => [
2389 'errorMessage' => 'New message for testing',
2393 $this->assertSame('New message for testing', $this->subject
->render($contentObjectFixture, $configuration));
2399 public function specificExceptionsCanBeIgnoredByExceptionHandler(): void
2401 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2404 'exceptionHandler' => '1',
2405 'exceptionHandler.' => [
2406 'ignoreCodes.' => ['10.' => '1414513947'],
2409 $this->expectException(\LogicException
::class);
2410 $this->expectExceptionCode(1414513947);
2411 $this->subject
->render($contentObjectFixture, $configuration);
2415 * @return \PHPUnit_Framework_MockObject_MockObject | AbstractContentObject
2417 protected function createContentObjectThrowingExceptionFixture()
2419 $contentObjectFixture = $this->getMockBuilder(AbstractContentObject
::class)
2420 ->setConstructorArgs([$this->subject
])
2422 $contentObjectFixture->expects($this->once())
2424 ->willReturnCallback(function () {
2425 throw new \
LogicException('Exception during rendering', 1414513947);
2427 return $contentObjectFixture;
2433 protected function getLibParseFunc(): array
2439 'keep' => '{$styles.content.links.keep}',
2452 'data' => 'parameters : allParams',
2461 'allowTags' => 'a, abbr, acronym, address, article, aside, b, bdo, big, blockquote, br, caption, center, cite, code, col, colgroup, dd, del, dfn, dl, div, dt, em, font, footer, header, h1, h2, h3, h4, h5, h6, hr, i, img, ins, kbd, label, li, link, meta, nav, ol, p, pre, q, samp, sdfield, section, small, span, strike, strong, style, sub, sup, table, thead, tbody, tfoot, td, th, tr, title, tt, u, ul, var',
2463 'sword' => '<span class="csc-sword">|</span>',
2465 'nonTypoTagStdWrap.' => [
2466 'HTMLparser' => '1',
2468 'keepNonMatchedTags' => '1',
2469 'htmlSpecialChars' => '2',
2478 protected function getLibParseFunc_RTE(): array
2483 'allowTags' => 'a, abbr, acronym, address, article, aside, b, bdo, big, blockquote, br, caption, center, cite, code, col, colgroup, dd, del, dfn, dl, div, dt, em, font, footer, header, h1, h2, h3, h4, h5, h6, hr, i, img, ins, kbd, label, li, link, meta, nav, ol, p, pre, q, samp, sdfield, section, small, span, strike, strong, style, sub, sup, table, thead, tbody, tfoot, td, th, tr, title, tt, u, ul, var',
2486 'externalBlocks' => 'article, aside, blockquote, div, dd, dl, footer, header, nav, ol, section, table, ul, pre',
2487 'externalBlocks.' => [
2489 'callRecursive' => '1',
2493 'callRecursive' => '1',
2497 'callRecursive' => '1',
2501 'callRecursive' => '1',
2505 'callRecursive' => '1',
2509 'callRecursive' => '1',
2513 'callRecursive' => '1',
2517 'callRecursive' => '1',
2521 'callRecursive' => '1',
2525 'callRecursive' => '1',
2529 'callRecursive' => '1',
2533 'HTMLtableCells' => '1',
2534 'HTMLtableCells.' => [
2535 'addChr10BetweenParagraphs' => '1',
2538 'parseFunc' => '=< lib.parseFunc_RTE',
2540 'nonTypoTagStdWrap.' => [
2542 'nonWrappedTag' => '',
2550 'HTMLparser' => '1',
2552 'keepNonMatchedTags' => '1',
2558 'default' => 'contenttable',
2559 'list' => 'contenttable',
2569 'callRecursive' => '1',
2577 'override' => '_blank',
2582 'nonTypoTagStdWrap.' => [
2584 'addAttributes.' => [
2586 'class' => 'bodytext',
2588 'setOnly' => 'blank',
2592 'encapsTagList' => 'p,pre,h1,h2,h3,h4,h5,h6,hr,dt,li',
2593 'innerStdWrap_all.' => [
2594 'ifBlank' => ' ',
2596 'nonWrappedTag' => 'P',
2601 'HTMLparser' => '1',
2603 'htmlSpecialChars' => '2',
2604 'keepNonMatchedTags' => '1',
2607 'sword' => '<span class="csc-sword">|</span>',
2620 'data' => 'parameters : allParams',
2635 public function _parseFuncReturnsCorrectHtmlDataProvider(): array
2638 'Text without tag is wrapped with <p> tag' => [
2640 $this->getLibParseFunc_RTE(),
2641 '<p class="bodytext">Text without tag</p>',
2643 'Text wrapped with <p> tag remains the same' => [
2644 '<p class="myclass">Text with <p> tag</p>',
2645 $this->getLibParseFunc_RTE(),
2646 '<p class="myclass">Text with <p> tag</p>',
2648 'Text with absolute external link' => [
2649 'Text with <link http://example.com/foo/>external link</link>',
2650 $this->getLibParseFunc_RTE(),
2651 '<p class="bodytext">Text with <a href="http://example.com/foo/">external link</a></p>',
2653 'Empty lines are not duplicated' => [
2655 $this->getLibParseFunc_RTE(),
2656 '<p class="bodytext"> </p>',
2658 'Multiple empty lines with no text' => [
2660 $this->getLibParseFunc_RTE(),
2661 '<p class="bodytext"> </p>' . LF
. '<p class="bodytext"> </p>' . LF
. '<p class="bodytext"> </p>',
2663 'Empty lines are not duplicated at the end of content' => [
2665 $this->getLibParseFunc_RTE(),
2666 '<p class="bodytext">test</p>' . LF
. '<p class="bodytext"> </p>',
2668 'Empty lines are not trimmed' => [
2670 $this->getLibParseFunc_RTE(),
2671 '<p class="bodytext"> </p>' . LF
. '<p class="bodytext">test</p>' . LF
. '<p class="bodytext"> </p>',
2678 * @dataProvider _parseFuncReturnsCorrectHtmlDataProvider
2679 * @param string $value
2680 * @param array $configuration
2681 * @param string $expectedResult
2683 public function stdWrap_parseFuncReturnsParsedHtml($value, $configuration, $expectedResult): void
2685 $this->assertEquals($expectedResult, $this->subject
->stdWrap_parseFunc($value, $configuration));
2691 public function typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider(): array
2697 'parameter' => 'http://typo3.org',
2699 '<a href="http://typo3.org">TYPO3</a>',
2701 'Link to url without schema' => [
2704 'parameter' => 'typo3.org',
2706 '<a href="http://typo3.org">TYPO3</a>',
2708 'Link to url without link text' => [
2711 'parameter' => 'http://typo3.org',
2713 '<a href="http://typo3.org">http://typo3.org</a>',
2715 'Link to url with attributes' => [
2718 'parameter' => 'http://typo3.org',
2719 'ATagParams' => 'class="url-class"',
2720 'extTarget' => '_blank',
2721 'title' => 'Open new window',
2723 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2725 'Link to url with attributes in parameter' => [
2728 'parameter' => 'http://typo3.org _blank url-class "Open new window"',
2730 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2732 'Link to url with script tag' => [
2735 'parameter' => 'http://typo3.org<script>alert(123)</script>',
2737 '<a href="http://typo3.org<script>alert(123)</script>">http://typo3.org<script>alert(123)</script></a>',
2739 'Link to email address' => [
2742 'parameter' => 'foo@bar.org',
2744 '<a href="mailto:foo@bar.org">Email address</a>',
2746 'Link to email address without link text' => [
2749 'parameter' => 'foo@bar.org',
2751 '<a href="mailto:foo@bar.org">foo@bar.org</a>',
2753 'Link to email with attributes' => [
2756 'parameter' => 'foo@bar.org',
2757 'ATagParams' => 'class="email-class"',
2758 'title' => 'Write an email',
2760 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2762 'Link to email with attributes in parameter' => [
2765 'parameter' => 'foo@bar.org - email-class "Write an email"',
2767 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2774 * @param string $linkText
2775 * @param array $configuration
2776 * @param string $expectedResult
2777 * @dataProvider typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider
2779 public function typolinkReturnsCorrectLinksForEmailsAndUrls($linkText, $configuration, $expectedResult): void
2781 $packageManagerMock = $this->getMockBuilder(PackageManager
::class)
2782 ->disableOriginalConstructor()
2784 $templateServiceObjectMock = $this->getMockBuilder(TemplateService
::class)
2785 ->setConstructorArgs([null, $packageManagerMock])
2786 ->setMethods(['dummy'])
2788 $templateServiceObjectMock->setup
= [
2790 'parseFunc.' => $this->getLibParseFunc(),
2793 $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController
::class);
2794 $typoScriptFrontendControllerMockObject->config
= [
2797 $typoScriptFrontendControllerMockObject->tmpl
= $templateServiceObjectMock;
2798 $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2799 $this->subject
->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2801 $this->assertEquals($expectedResult, $this->subject
->typoLink($linkText, $configuration));
2805 * @param array $settings
2806 * @param string $linkText
2807 * @param string $mailAddress
2808 * @param string $expected
2809 * @dataProvider typoLinkEncodesMailAddressForSpamProtectionDataProvider
2812 public function typoLinkEncodesMailAddressForSpamProtection(
2818 $this->getFrontendController()->spamProtectEmailAddresses
= $settings['spamProtectEmailAddresses'];
2819 $this->getFrontendController()->config
['config'] = $settings;
2820 $typoScript = ['parameter' => $mailAddress];
2822 $this->assertEquals($expected, $this->subject
->typoLink($linkText, $typoScript));
2828 public function typoLinkEncodesMailAddressForSpamProtectionDataProvider(): array
2831 'plain mail without mailto scheme' => [
2833 'spamProtectEmailAddresses' => '',
2834 'spamProtectEmailAddresses_atSubst' => '',
2835 'spamProtectEmailAddresses_lastDotSubst' => '',
2837 'some.body@test.typo3.org',
2838 'some.body@test.typo3.org',
2839 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2841 'plain mail with mailto scheme' => [
2843 'spamProtectEmailAddresses' => '',
2844 'spamProtectEmailAddresses_atSubst' => '',
2845 'spamProtectEmailAddresses_lastDotSubst' => '',
2847 'some.body@test.typo3.org',
2848 'mailto:some.body@test.typo3.org',
2849 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2851 'plain with at and dot substitution' => [
2853 'spamProtectEmailAddresses' => '0',
2854 'spamProtectEmailAddresses_atSubst' => '(at)',
2855 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2857 'some.body@test.typo3.org',
2858 'mailto:some.body@test.typo3.org',
2859 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2861 'mono-alphabetic substitution offset +1' => [
2863 'spamProtectEmailAddresses' => '1',
2864 'spamProtectEmailAddresses_atSubst' => '',
2865 'spamProtectEmailAddresses_lastDotSubst' => '',
2867 'some.body@test.typo3.org',
2868 'mailto:some.body@test.typo3.org',
2869 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body(at)test.typo3.org</a>',
2871 'mono-alphabetic substitution offset +1 with at substitution' => [
2873 'spamProtectEmailAddresses' => '1',
2874 'spamProtectEmailAddresses_atSubst' => '@',
2875 'spamProtectEmailAddresses_lastDotSubst' => '',
2877 'some.body@test.typo3.org',
2878 'mailto:some.body@test.typo3.org',
2879 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body@test.typo3.org</a>',
2881 'mono-alphabetic substitution offset +1 with at and dot substitution' => [
2883 'spamProtectEmailAddresses' => '1',
2884 'spamProtectEmailAddresses_atSubst' => '(at)',
2885 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2887 'some.body@test.typo3.org',
2888 'mailto:some.body@test.typo3.org',
2889 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body(at)test.typo3(dot)org</a>',
2891 'mono-alphabetic substitution offset -1 with at and dot substitution' => [
2893 'spamProtectEmailAddresses' => '-1',
2894 'spamProtectEmailAddresses_atSubst' => '(at)',
2895 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2897 'some.body@test.typo3.org',
2898 'mailto:some.body@test.typo3.org',
2899 '<a href="javascript:linkTo_UnCryptMailto(\'lzhksn9rnld-ancxZsdrs-sxon2-nqf\');">some.body(at)test.typo3(dot)org</a>',
2901 'entity substitution with at and dot substitution' => [
2903 'spamProtectEmailAddresses' => 'ascii',
2904 'spamProtectEmailAddresses_atSubst' => '',
2905 'spamProtectEmailAddresses_lastDotSubst' => '',
2907 'some.body@test.typo3.org',
2908 'mailto:some.body@test.typo3.org',
2909 '<a href="mailto:some.body@test.typo3.org">some.body(at)test.typo3.org</a>',
2911 'entity substitution with at and dot substitution with at and dot substitution' => [
2913 'spamProtectEmailAddresses' => 'ascii',
2914 'spamProtectEmailAddresses_atSubst' => '(at)',
2915 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2917 'some.body@test.typo3.org',
2918 'mailto:some.body@test.typo3.org',
2919 '<a href="mailto:some.body@test.typo3.org">some.body(at)test.typo3(dot)org</a>',
2927 public function typolinkReturnsCorrectLinksFilesDataProvider(): array
2933 'parameter' => 'fileadmin/foo.bar',
2935 '<a href="fileadmin/foo.bar">My file</a>',
2937 'Link to file without link text' => [
2940 'parameter' => 'fileadmin/foo.bar',
2942 '<a href="fileadmin/foo.bar">fileadmin/foo.bar</a>',
2944 'Link to file with attributes' => [
2947 'parameter' => 'fileadmin/foo.bar',
2948 'ATagParams' => 'class="file-class"',
2949 'fileTarget' => '_blank',
2950 'title' => 'Title of the file',
2952 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2954 'Link to file with attributes and additional href' => [
2957 'parameter' => 'fileadmin/foo.bar',
2958 'ATagParams' => 'href="foo-bar"',
2959 'fileTarget' => '_blank',
2960 'title' => 'Title of the file',
2962 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank">My file</a>',
2964 'Link to file with attributes and additional href and class' => [
2967 'parameter' => 'fileadmin/foo.bar',
2968 'ATagParams' => 'href="foo-bar" class="file-class"',
2969 'fileTarget' => '_blank',
2970 'title' => 'Title of the file',
2972 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2974 'Link to file with attributes and additional class and href' => [
2977 'parameter' => 'fileadmin/foo.bar',
2978 'ATagParams' => 'class="file-class" href="foo-bar"',
2979 'fileTarget' => '_blank',
2980 'title' => 'Title of the file',
2982 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2984 'Link to file with attributes and additional class and href and title' => [
2987 'parameter' => 'fileadmin/foo.bar',
2988 'ATagParams' => 'class="file-class" href="foo-bar" title="foo-bar"',
2989 'fileTarget' => '_blank',
2990 'title' => 'Title of the file',