[BUGFIX] Accept Unicode characters in email spam protection
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / ContentObject / ContentObjectRendererTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
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;
70
71 /**
72 * Test case
73 */
74 class ContentObjectRendererTest extends UnitTestCase
75 {
76 /**
77 * @var bool Reset singletons created by subject
78 */
79 protected $resetSingletonInstances = true;
80
81 /**
82 * @var \PHPUnit_Framework_MockObject_MockObject|AccessibleObjectInterface|ContentObjectRenderer
83 */
84 protected $subject;
85
86 /**
87 * @var \PHPUnit_Framework_MockObject_MockObject|TypoScriptFrontendController|AccessibleObjectInterface
88 */
89 protected $frontendControllerMock;
90
91 /**
92 * @var \PHPUnit_Framework_MockObject_MockObject|TemplateService
93 */
94 protected $templateServiceMock;
95
96 /**
97 * Default content object name -> class name map, shipped with TYPO3 CMS
98 *
99 * @var array
100 */
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
123 ];
124
125 /**
126 * Set up
127 */
128 protected function setUp(): void
129 {
130 $GLOBALS['SIM_ACCESS_TIME'] = 1534278180;
131 $packageManagerMock = $this->getMockBuilder(PackageManager::class)
132 ->disableOriginalConstructor()
133 ->getMock();
134 $this->templateServiceMock =
135 $this->getMockBuilder(TemplateService::class)
136 ->setConstructorArgs([null, $packageManagerMock])
137 ->setMethods(['linkData'])
138 ->getMock();
139 $pageRepositoryMock =
140 $this->getAccessibleMock(PageRepository::class, ['getRawRecord', 'getMountPointInfo']);
141 $this->frontendControllerMock =
142 $this->getAccessibleMock(
143 TypoScriptFrontendController::class,
144 ['sL'],
145 [],
146 '',
147 false
148 );
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;
155
156 $this->subject = $this->getAccessibleMock(
157 ContentObjectRenderer::class,
158 ['getResourceFactory', 'getEnvironmentVariable'],
159 [$this->frontendControllerMock]
160 );
161
162 $logger = $this->prophesize(Logger::class);
163 $this->subject->setLogger($logger->reveal());
164 $this->subject->setContentObjectClassMap($this->contentObjectMap);
165 $this->subject->start([], 'tt_content');
166 }
167
168 //////////////////////
169 // Utility functions
170 //////////////////////
171
172 /**
173 * @return TypoScriptFrontendController
174 */
175 protected function getFrontendController(): TypoScriptFrontendController
176 {
177 return $GLOBALS['TSFE'];
178 }
179
180 /**
181 * Converts the subject and the expected result into utf-8.
182 *
183 * @param string $subject the subject, will be modified
184 * @param string $expected the expected result, will be modified
185 */
186 protected function handleCharset(string &$subject, string &$expected): void
187 {
188 $subject = mb_convert_encoding($subject, 'utf-8', 'iso-8859-1');
189 $expected = mb_convert_encoding($expected, 'utf-8', 'iso-8859-1');
190 }
191
192 /////////////////////////////////////////////
193 // Tests concerning the getImgResource hook
194 /////////////////////////////////////////////
195 /**
196 * @test
197 */
198 public function getImgResourceCallsGetImgResourcePostProcessHook(): void
199 {
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());
206
207 $resourceFactory = $this->createMock(ResourceFactory::class);
208 $this->subject->expects($this->any())->method('getResourceFactory')->will($this->returnValue($resourceFactory));
209
210 $className = $this->getUniqueId('tx_coretest');
211 $getImgResourceHookMock = $this->getMockBuilder(ContentObjectGetImageResourceHookInterface::class)
212 ->setMethods(['getImgResourcePostProcess'])
213 ->setMockClassName($className)
214 ->getMock();
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', []);
222 }
223
224 /**
225 * Handles the arguments that have been sent to the getImgResource hook.
226 *
227 * @param string $file
228 * @param array $fileArray
229 * @param $imageResource
230 * @param ContentObjectRenderer $parent
231 * @return array
232 * @see getImgResourceHookGetsCalled
233 */
234 public function isGetImgResourceHookCalledCallback(
235 string $file,
236 array $fileArray,
237 $imageResource,
238 ContentObjectRenderer $parent
239 ): array {
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;
245 }
246
247 //////////////////////////////////////
248 // Tests related to getContentObject
249 //////////////////////////////////////
250
251 /**
252 * Show registration of a class for a TypoScript object name and getting
253 * the registered content object is working.
254 *
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
257 * fails.
258 *
259 * 1. The type must be of AbstractContentObject.
260 * 2. Registration can only be done by public methods.
261 *
262 * @test
263 */
264 public function canRegisterAContentObjectClassForATypoScriptName(): void
265 {
266 $className = TextContentObject::class;
267 $contentObjectName = 'TEST_TEXT';
268 $this->subject->registerContentObjectClass(
269 $className,
270 $contentObjectName
271 );
272 $object = $this->subject->getContentObject($contentObjectName);
273 $this->assertInstanceOf($className, $object);
274 }
275
276 /**
277 * Show that setting of the class map and getting a registered content
278 * object is working.
279 *
280 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
281 * @test
282 */
283 public function canSetTheContentObjectClassMapAndGetARegisteredContentObject(): void
284 {
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);
291 }
292
293 /**
294 * Show that the map is not set as an externally accessible reference.
295 *
296 * Prove is done by missing success when trying to use it this way.
297 *
298 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
299 * @test
300 */
301 public function canNotAccessInternalContentObjectMapByReference(): void
302 {
303 $className = TextContentObject::class;
304 $contentObjectName = 'TEST_TEXT';
305 $classMap = [];
306 $this->subject->setContentObjectClassMap($classMap);
307 $classMap[$contentObjectName] = $className;
308 $object = $this->subject->getContentObject($contentObjectName);
309 $this->assertNull($object);
310 }
311
312 /**
313 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
314 * @test
315 */
316 public function willReturnNullForUnregisteredObject(): void
317 {
318 $object = $this->subject->getContentObject('FOO');
319 $this->assertNull($object);
320 }
321
322 /**
323 * @see ContentObjectRendererTest::canRegisterAContentObjectClassForATypoScriptName
324 * @test
325 */
326 public function willThrowAnExceptionForARegisteredNonContentObject(): void
327 {
328 $this->expectException(ContentRenderingException::class);
329 $this->subject->registerContentObjectClass(
330 \stdClass::class,
331 'STDCLASS'
332 );
333 $this->subject->getContentObject('STDCLASS');
334 }
335
336 /**
337 * @return string[][] [[$name, $fullClassName],]
338 */
339 public function registersAllDefaultContentObjectsDataProvider(): array
340 {
341 $dataProvider = [];
342 foreach ($this->contentObjectMap as $name => $className) {
343 $dataProvider[] = [$name, $className];
344 }
345 return $dataProvider;
346 }
347
348 /**
349 * Prove that all content objects are registered and a class is available
350 * for each of them.
351 *
352 * @test
353 * @dataProvider registersAllDefaultContentObjectsDataProvider
354 * @param string $objectName TypoScript name of content object
355 * @param string $className Expected class name
356 */
357 public function registersAllDefaultContentObjects(
358 string $objectName,
359 string $className
360 ): void {
361 $this->assertTrue(
362 is_subclass_of($className, AbstractContentObject::class)
363 );
364 $object = $this->subject->getContentObject($objectName);
365 $this->assertInstanceOf($className, $object);
366 }
367
368 /////////////////////////////////////////
369 // Tests concerning getQueryArguments()
370 /////////////////////////////////////////
371 /**
372 * @test
373 */
374 public function getQueryArgumentsExcludesParameters(): void
375 {
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')
378 );
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);
388 }
389
390 /**
391 * @test
392 */
393 public function getQueryArgumentsExcludesGetParameters(): void
394 {
395 $_GET = [
396 'key1' => 'value1',
397 'key2' => 'value2',
398 'key3' => [
399 'key31' => 'value31',
400 'key32' => [
401 'key321' => 'value321',
402 'key322' => 'value322'
403 ]
404 ]
405 ];
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);
416 }
417
418 /**
419 * @test
420 */
421 public function getQueryArgumentsOverrulesSingleParameter(): void
422 {
423 $this->subject->expects($this->any())->method('getEnvironmentVariable')->with($this->equalTo('QUERY_STRING'))->will(
424 $this->returnValue('key1=value1')
425 );
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'
432 ];
433 $expectedResult = '&key1=value1Overruled';
434 $actualResult = $this->subject->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments);
435 $this->assertEquals($expectedResult, $actualResult);
436 }
437
438 /**
439 * @test
440 */
441 public function getQueryArgumentsOverrulesMultiDimensionalParameters(): void
442 {
443 $_POST = [
444 'key1' => 'value1',
445 'key2' => 'value2',
446 'key3' => [
447 'key31' => 'value31',
448 'key32' => [
449 'key321' => 'value321',
450 'key322' => 'value322'
451 ]
452 ]
453 ];
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',
464 'key3' => [
465 'key32' => [
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'
472 ]
473 ]
474 ];
475 $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2Overruled&key3[key32][key322]=value322Overruled');
476 $actualResult = $this->subject->getQueryArguments($getQueryArgumentsConfiguration, $overruleArguments);
477 $this->assertEquals($expectedResult, $actualResult);
478 }
479
480 /**
481 * @test
482 */
483 public function getQueryArgumentsOverrulesMultiDimensionalForcedParameters(): void
484 {
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')
487 );
488 $_POST = [
489 'key1' => 'value1',
490 'key2' => 'value2',
491 'key3' => [
492 'key31' => 'value31',
493 'key32' => [
494 'key321' => 'value321',
495 'key322' => 'value322'
496 ]
497 ]
498 ];
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',
509 'key3' => [
510 'key32' => [
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'
515 ]
516 ]
517 ];
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);
524 }
525
526 /**
527 * @test
528 */
529 public function getQueryArgumentsWithMethodPostGetMergesParameters(): void
530 {
531 $_POST = [
532 'key1' => 'POST1',
533 'key2' => 'POST2',
534 'key3' => [
535 'key31' => 'POST31',
536 'key32' => 'POST32',
537 'key33' => [
538 'key331' => 'POST331',
539 'key332' => 'POST332',
540 ]
541 ]
542 ];
543 $_GET = [
544 'key2' => 'GET2',
545 'key3' => [
546 'key32' => 'GET32',
547 'key33' => [
548 'key331' => 'GET331',
549 ]
550 ]
551 ];
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);
557 }
558
559 /**
560 * @test
561 */
562 public function getQueryArgumentsWithMethodGetPostMergesParameters(): void
563 {
564 $_GET = [
565 'key1' => 'GET1',
566 'key2' => 'GET2',
567 'key3' => [
568 'key31' => 'GET31',
569 'key32' => 'GET32',
570 'key33' => [
571 'key331' => 'GET331',
572 'key332' => 'GET332',
573 ]
574 ]
575 ];
576 $_POST = [
577 'key2' => 'POST2',
578 'key3' => [
579 'key32' => 'POST32',
580 'key33' => [
581 'key331' => 'POST331',
582 ]
583 ]
584 ];
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);
590 }
591
592 /**
593 * Encodes square brackets in URL.
594 *
595 * @param string $string
596 * @return string
597 */
598 private function rawUrlEncodeSquareBracketsInUrl(string $string): string
599 {
600 return str_replace(['[', ']'], ['%5B', '%5D'], $string);
601 }
602
603 //////////////////////////
604 // Tests concerning crop
605 //////////////////////////
606 /**
607 * @test
608 */
609 public function cropIsMultibyteSafe(): void
610 {
611 $this->assertEquals('бла', $this->subject->crop('бла', '3|...'));
612 }
613
614 //////////////////////////////
615
616 //////////////////////////////
617 // Tests concerning cropHTML
618 //////////////////////////////
619
620 /**
621 * Data provider for cropHTML.
622 *
623 * Provides combinations of text type and configuration.
624 *
625 * @return array [$expect, $conf, $content]
626 */
627 public function cropHTMLDataProvider(): array
628 {
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&aring;rh&oslash;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';
640
641 return [
642 'plain text; 11|...' => [
643 'Kasper Sk' . chr(229) . 'r...',
644 $plainText,
645 '11|...',
646 ],
647 'plain text; -58|...' => [
648 '...h' . chr(248) . 'j implemented the original version of '
649 . 'the crop function.',
650 $plainText,
651 '-58|...',
652 ],
653 'plain text; 4|...|1' => [
654 'Kasp...',
655 $plainText,
656 '4|...|1',
657 ],
658 'plain text; 20|...|1' => [
659 'Kasper Sk' . chr(229) . 'rh' . chr(248) . 'j...',
660 $plainText,
661 '20|...|1',
662 ],
663 'plain text; -5|...|1' => [
664 '...tion.',
665 $plainText,
666 '-5|...|1',
667 ],
668 'plain text; -49|...|1' => [
669 '...the original version of the crop function.',
670 $plainText,
671 '-49|...|1',
672 ],
673 'text with markup; 11|...' => [
674 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
675 . chr(229) . 'r...</a></strong>',
676 $textWithMarkup,
677 '11|...',
678 ],
679 'text with markup; 13|...' => [
680 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
681 . chr(229) . 'rh' . chr(248) . '...</a></strong>',
682 $textWithMarkup,
683 '13|...',
684 ],
685 'text with markup; 14|...' => [
686 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
687 . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
688 $textWithMarkup,
689 '14|...',
690 ],
691 'text with markup; 15|...' => [
692 '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
693 . chr(229) . 'rh' . chr(248) . 'j</a> ...</strong>',
694 $textWithMarkup,
695 '15|...',
696 ],
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> '
700 . 'th...',
701 $textWithMarkup,
702 '29|...',
703 ],
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 '
707 . 'function.',
708 $textWithMarkup,
709 '-58|...',
710 ],
711 'text with markup 4|...|1' => [
712 '<strong><a href="mailto:kasper@typo3.org">Kasp...</a>'
713 . '</strong>',
714 $textWithMarkup,
715 '4|...|1',
716 ],
717 'text with markup; 11|...|1' => [
718 '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
719 . '</strong>',
720 $textWithMarkup,
721 '11|...|1',
722 ],
723 'text with markup; 13|...|1' => [
724 '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
725 . '</strong>',
726 $textWithMarkup,
727 '13|...|1',
728 ],
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>',
732 $textWithMarkup,
733 '14|...|1',
734 ],
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>',
738 $textWithMarkup,
739 '15|...|1',
740 ],
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>...',
744 $textWithMarkup,
745 '29|...|1',
746 ],
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.',
751 $textWithMarkup,
752 '-66|...|1',
753 ],
754 'text with entities 9|...' => [
755 'Kasper Sk...',
756 $textWithEntities,
757 '9|...',
758 ],
759 'text with entities 10|...' => [
760 'Kasper Sk&aring;...',
761 $textWithEntities,
762 '10|...',
763 ],
764 'text with entities 11|...' => [
765 'Kasper Sk&aring;r...',
766 $textWithEntities,
767 '11|...',
768 ],
769 'text with entities 13|...' => [
770 'Kasper Sk&aring;rh&oslash;...',
771 $textWithEntities,
772 '13|...',
773 ],
774 'text with entities 14|...' => [
775 'Kasper Sk&aring;rh&oslash;j...',
776 $textWithEntities,
777 '14|...',
778 ],
779 'text with entities 15|...' => [
780 'Kasper Sk&aring;rh&oslash;j ...',
781 $textWithEntities,
782 '15|...',
783 ],
784 'text with entities 16|...' => [
785 'Kasper Sk&aring;rh&oslash;j i...',
786 $textWithEntities,
787 '16|...',
788 ],
789 'text with entities -57|...' => [
790 '...j implemented the; original version of the crop function.',
791 $textWithEntities,
792 '-57|...',
793 ],
794 'text with entities -58|...' => [
795 '...&oslash;j implemented the; original version of the crop '
796 . 'function.',
797 $textWithEntities,
798 '-58|...',
799 ],
800 'text with entities -59|...' => [
801 '...h&oslash;j implemented the; original version of the crop '
802 . 'function.',
803 $textWithEntities,
804 '-59|...',
805 ],
806 'text with entities 4|...|1' => [
807 'Kasp...',
808 $textWithEntities,
809 '4|...|1',
810 ],
811 'text with entities 9|...|1' => [
812 'Kasper...',
813 $textWithEntities,
814 '9|...|1',
815 ],
816 'text with entities 10|...|1' => [
817 'Kasper...',
818 $textWithEntities,
819 '10|...|1',
820 ],
821 'text with entities 11|...|1' => [
822 'Kasper...',
823 $textWithEntities,
824 '11|...|1',
825 ],
826 'text with entities 13|...|1' => [
827 'Kasper...',
828 $textWithEntities,
829 '13|...|1',
830 ],
831 'text with entities 14|...|1' => [
832 'Kasper Sk&aring;rh&oslash;j...',
833 $textWithEntities,
834 '14|...|1',
835 ],
836 'text with entities 15|...|1' => [
837 'Kasper Sk&aring;rh&oslash;j...',
838 $textWithEntities,
839 '15|...|1',
840 ],
841 'text with entities 16|...|1' => [
842 'Kasper Sk&aring;rh&oslash;j...',
843 $textWithEntities,
844 '16|...|1',
845 ],
846 'text with entities -57|...|1' => [
847 '...implemented the; original version of the crop function.',
848 $textWithEntities,
849 '-57|...|1',
850 ],
851 'text with entities -58|...|1' => [
852 '...implemented the; original version of the crop function.',
853 $textWithEntities,
854 '-58|...|1',
855 ],
856 'text with entities -59|...|1' => [
857 '...implemented the; original version of the crop function.',
858 $textWithEntities,
859 '-59|...|1',
860 ],
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',
867 '28|...|1',
868 ],
869 'html elements with dashes in attributes' => [
870 '<em data-foo="x">foobar</em>foo',
871 '<em data-foo="x">foobar</em>foobaz',
872 '9',
873 ],
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',
878 '24|...|1',
879 ],
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',
884 '24|...|1',
885 ],
886 'text with linebreaks' => [
887 "Lorem ipsum dolor sit amet,\nconsetetur sadipscing elitr,\ns"
888 . 'ed diam nonumy eirmod tempor invidunt ut labore e'
889 . 't dolore magna',
890 $textWithLinebreaks,
891 '121',
892 ],
893 ];
894 }
895
896 /**
897 * Check if cropHTML works properly.
898 *
899 * @test
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.
904 */
905 public function cropHTML(string $expect, string $content, string $conf): void
906 {
907 $this->handleCharset($content, $expect);
908 $this->assertSame(
909 $expect,
910 $this->subject->cropHTML($content, $conf)
911 );
912 }
913
914 /**
915 * Data provider for round
916 *
917 * @return array [$expect, $contet, $conf]
918 */
919 public function roundDataProvider(): array
920 {
921 return [
922 // floats
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' => [
930 1.0,
931 0.111,
932 [
933 'roundType' => 'ceil',
934 'decimals' => 2,
935 ],
936 ],
937 'floor' => [2.0, 2.99, ['roundType' => 'floor']],
938 'floor does not accept decimals' => [
939 2.0,
940 2.999,
941 [
942 'roundType' => 'floor',
943 'decimals' => 2,
944 ],
945 ],
946 'round, down' => [1.0, 1.11, ['roundType' => 'round']],
947 'round, up' => [2.0, 1.55, ['roundType' => 'round']],
948 'round does accept decimals' => [
949 5.56,
950 5.5555,
951 [
952 'roundType' => 'round',
953 'decimals' => 2,
954 ],
955 ],
956 // strings
957 'emtpy string' => [0.0, '', []],
958 'word string' => [0.0, 'word', []],
959 'float string' => [1.0, '1.123456789', []],
960 // other types
961 'null' => [0.0, null, []],
962 'false' => [0.0, false, []],
963 'true' => [1.0, true, []]
964 ];
965 }
966
967 /**
968 * Check if round works properly
969 *
970 * Show:
971 *
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.
977 *
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
982 * @test
983 */
984 public function round(float $expect, $content, array $conf): void
985 {
986 $this->assertSame(
987 $expect,
988 $this->subject->_call('round', $content, $conf)
989 );
990 }
991
992 /**
993 * @test
994 */
995 public function recursiveStdWrapProperlyRendersBasicString(): void
996 {
997 $stdWrapConfiguration = [
998 'noTrimWrap' => '|| 123|',
999 'stdWrap.' => [
1000 'wrap' => '<b>|</b>'
1001 ]
1002 ];
1003 $this->assertSame(
1004 '<b>Test</b> 123',
1005 $this->subject->stdWrap('Test', $stdWrapConfiguration)
1006 );
1007 }
1008
1009 /**
1010 * @test
1011 */
1012 public function recursiveStdWrapIsOnlyCalledOnce(): void
1013 {
1014 $stdWrapConfiguration = [
1015 'append' => 'TEXT',
1016 'append.' => [
1017 'data' => 'register:Counter'
1018 ],
1019 'stdWrap.' => [
1020 'append' => 'LOAD_REGISTER',
1021 'append.' => [
1022 'Counter.' => [
1023 'prioriCalc' => 'intval',
1024 'cObject' => 'TEXT',
1025 'cObject.' => [
1026 'data' => 'register:Counter',
1027 'wrap' => '|+1',
1028 ]
1029 ]
1030 ]
1031 ]
1032 ];
1033 $this->assertSame(
1034 'Counter:1',
1035 $this->subject->stdWrap('Counter:', $stdWrapConfiguration)
1036 );
1037 }
1038
1039 /**
1040 * Data provider for numberFormat.
1041 *
1042 * @return array [$expect, $content, $conf]
1043 */
1044 public function numberFormatDataProvider(): array
1045 {
1046 return [
1047 'testing decimals' => [
1048 '0.80',
1049 0.8,
1050 ['decimals' => 2]
1051 ],
1052 'testing decimals with input as string' => [
1053 '0.80',
1054 '0.8',
1055 ['decimals' => 2]
1056 ],
1057 'testing dec_point' => [
1058 '0,8',
1059 0.8,
1060 ['decimals' => 1, 'dec_point' => ',']
1061 ],
1062 'testing thousands_sep' => [
1063 '1.000',
1064 999.99,
1065 [
1066 'decimals' => 0,
1067 'thousands_sep.' => ['char' => 46]
1068 ]
1069 ],
1070 'testing mixture' => [
1071 '1.281.731,5',
1072 1281731.45,
1073 [
1074 'decimals' => 1,
1075 'dec_point.' => ['char' => 44],
1076 'thousands_sep.' => ['char' => 46]
1077 ]
1078 ]
1079 ];
1080 }
1081
1082 /**
1083 * Check if numberFormat works properly.
1084 *
1085 * @dataProvider numberFormatDataProvider
1086 * @test
1087 * @param string $expects
1088 * @param mixed $content
1089 * @param array $conf
1090 */
1091 public function numberFormat(string $expects, $content, array $conf): void
1092 {
1093 $this->assertSame(
1094 $expects,
1095 $this->subject->numberFormat($content, $conf)
1096 );
1097 }
1098
1099 /**
1100 * Data provider replacement
1101 *
1102 * @return array [$expect, $content, $conf]
1103 */
1104 public function replacementDataProvider(): array
1105 {
1106 return [
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!',
1110 [
1111 '20.' => [
1112 'search' => '_',
1113 'replace.' => ['char' => '32']
1114 ],
1115 '120.' => [
1116 'search' => 'in da hood',
1117 'replace' => 'around the block'
1118 ],
1119 '130.' => [
1120 'search' => '#a (Cat|Dog|Tiger)#i',
1121 'replace' => 'an animal',
1122 'useRegExp' => '1'
1123 ]
1124 ]
1125 ],
1126 'replacement with optionSplit, normal pattern' => [
1127 'There1is2a3cat,3a3dog3and3a3tiger3in3da3hood!3Yeah!',
1128 'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
1129 [
1130 '10.' => [
1131 'search' => '_',
1132 'replace' => '1 || 2 || 3',
1133 'useOptionSplitReplace' => '1'
1134 ]
1135 ]
1136 ],
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!',
1140 [
1141 '10.' => [
1142 'search' => '#(a) (Cat|Dog|Tiger)#i',
1143 'replace' => '${1} tiny ${2} || ${1} midsized ${2} || ${1} big ${2}',
1144 'useOptionSplitReplace' => '1',
1145 'useRegExp' => '1'
1146 ]
1147 ]
1148 ]
1149 ];
1150 }
1151
1152 /**
1153 * Check if stdWrap.replacement and all of its properties work properly
1154 *
1155 * @test
1156 * @dataProvider replacementDataProvider
1157 * @param string $content The given input.
1158 * @param string $expects The expected result.
1159 * @param array $conf The given configuration.
1160 */
1161 public function replacement(string $expects, string $content, array $conf): void
1162 {
1163 $this->assertSame(
1164 $expects,
1165 $this->subject->_call('replacement', $content, $conf)
1166 );
1167 }
1168
1169 /**
1170 * Data provider for calcAge.
1171 *
1172 * @return array [$expect, $timestamp, $labels]
1173 */
1174 public function calcAgeDataProvider(): array
1175 {
1176 return [
1177 'minutes' => [
1178 '2 min',
1179 120,
1180 ' min| hrs| days| yrs',
1181 ],
1182 'hours' => [
1183 '2 hrs',
1184 7200,
1185 ' min| hrs| days| yrs',
1186 ],
1187 'days' => [
1188 '7 days',
1189 604800,
1190 ' min| hrs| days| yrs',
1191 ],
1192 'day with provided singular labels' => [
1193 '1 day',
1194 86400,
1195 ' min| hrs| days| yrs| min| hour| day| year',
1196 ],
1197 'years' => [
1198 '45 yrs',
1199 1417997800,
1200 ' min| hrs| days| yrs',
1201 ],
1202 'different labels' => [
1203 '2 Minutes',
1204 120,
1205 ' Minutes| Hrs| Days| Yrs',
1206 ],
1207 'negative values' => [
1208 '-7 days',
1209 -604800,
1210 ' min| hrs| days| yrs',
1211 ],
1212 'default label values for wrong label input' => [
1213 '2 min',
1214 121,
1215 10,
1216 ],
1217 'default singular label values for wrong label input' => [
1218 '1 year',
1219 31536000,
1220 10,
1221 ]
1222 ];
1223 }
1224
1225 /**
1226 * Check if calcAge works properly.
1227 *
1228 * @test
1229 * @dataProvider calcAgeDataProvider
1230 * @param string $expect
1231 * @param int $timestamp
1232 * @param string $labels
1233 */
1234 public function calcAge(string $expect, int $timestamp, string $labels): void
1235 {
1236 $this->assertSame(
1237 $expect,
1238 $this->subject->calcAge($timestamp, $labels)
1239 );
1240 }
1241
1242 /**
1243 * @return array
1244 */
1245 public function stdWrapReturnsExpectationDataProvider(): array
1246 {
1247 return [
1248 'Prevent silent bool conversion' => [
1249 '1+1',
1250 [
1251 'prioriCalc.' => [
1252 'wrap' => '|',
1253 ],
1254 ],
1255 '1+1',
1256 ],
1257 ];
1258 }
1259
1260 /**
1261 * @param string $content
1262 * @param array $configuration
1263 * @param string $expectation
1264 * @dataProvider stdWrapReturnsExpectationDataProvider
1265 * @test
1266 */
1267 public function stdWrapReturnsExpectation(string $content, array $configuration, string $expectation): void
1268 {
1269 $this->assertSame($expectation, $this->subject->stdWrap($content, $configuration));
1270 }
1271
1272 /**
1273 * Data provider for substring
1274 *
1275 * @return array [$expect, $content, $conf]
1276 */
1277 public function substringDataProvider(): array
1278 {
1279 return [
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', ''],
1293 ];
1294 }
1295
1296 /**
1297 * Check if substring works properly.
1298 *
1299 * @test
1300 * @dataProvider substringDataProvider
1301 * @param string $expect The expected output.
1302 * @param string $content The given input.
1303 * @param string $conf The given configurationn.
1304 */
1305 public function substring(string $expect, string $content, string $conf): void
1306 {
1307 $this->assertSame($expect, $this->subject->substring($content, $conf));
1308 }
1309
1310 ///////////////////////////////
1311 // Tests concerning getData()
1312 ///////////////////////////////
1313
1314 /**
1315 * @return array
1316 */
1317 public function getDataWithTypeGpDataProvider(): array
1318 {
1319 return [
1320 'Value in get-data' => ['onlyInGet', 'GetValue'],
1321 'Value in post-data' => ['onlyInPost', 'PostValue'],
1322 'Value in post-data overriding get-data' => ['inGetAndPost', 'ValueInPost'],
1323 ];
1324 }
1325
1326 /**
1327 * Checks if getData() works with type "gp"
1328 *
1329 * @test
1330 * @dataProvider getDataWithTypeGpDataProvider
1331 * @param string $key
1332 * @param string $expectedValue
1333 */
1334 public function getDataWithTypeGp(string $key, string $expectedValue): void
1335 {
1336 $_GET = [
1337 'onlyInGet' => 'GetValue',
1338 'inGetAndPost' => 'ValueInGet',
1339 ];
1340 $_POST = [
1341 'onlyInPost' => 'PostValue',
1342 'inGetAndPost' => 'ValueInPost',
1343 ];
1344 $this->assertEquals($expectedValue, $this->subject->getData('gp:' . $key));
1345 }
1346
1347 /**
1348 * Checks if getData() works with type "tsfe"
1349 *
1350 * @test
1351 */
1352 public function getDataWithTypeTsfe(): void
1353 {
1354 $this->assertEquals($GLOBALS['TSFE']->metaCharset, $this->subject->getData('tsfe:metaCharset'));
1355 }
1356
1357 /**
1358 * Checks if getData() works with type "getenv"
1359 *
1360 * @test
1361 */
1362 public function getDataWithTypeGetenv(): void
1363 {
1364 $envName = $this->getUniqueId('frontendtest');
1365 $value = $this->getUniqueId('someValue');
1366 putenv($envName . '=' . $value);
1367 $this->assertEquals($value, $this->subject->getData('getenv:' . $envName));
1368 }
1369
1370 /**
1371 * Checks if getData() works with type "getindpenv"
1372 *
1373 * @test
1374 */
1375 public function getDataWithTypeGetindpenv(): void
1376 {
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'));
1380 }
1381
1382 /**
1383 * Checks if getData() works with type "field"
1384 *
1385 * @test
1386 */
1387 public function getDataWithTypeField(): void
1388 {
1389 $key = 'someKey';
1390 $value = 'someValue';
1391 $field = [$key => $value];
1392
1393 $this->assertEquals($value, $this->subject->getData('field:' . $key, $field));
1394 }
1395
1396 /**
1397 * Checks if getData() works with type "field" of the field content
1398 * is multi-dimensional (e.g. an array)
1399 *
1400 * @test
1401 */
1402 public function getDataWithTypeFieldAndFieldIsMultiDimensional(): void
1403 {
1404 $key = 'somekey|level1|level2';
1405 $value = 'somevalue';
1406 $field = ['somekey' => ['level1' => ['level2' => 'somevalue']]];
1407
1408 $this->assertEquals($value, $this->subject->getData('field:' . $key, $field));
1409 }
1410
1411 /**
1412 * Basic check if getData gets the uid of a file object
1413 *
1414 * @test
1415 */
1416 public function getDataWithTypeFileReturnsUidOfFileObject(): void
1417 {
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'));
1423 }
1424
1425 /**
1426 * Checks if getData() works with type "parameters"
1427 *
1428 * @test
1429 */
1430 public function getDataWithTypeParameters(): void
1431 {
1432 $key = $this->getUniqueId('someKey');
1433 $value = $this->getUniqueId('someValue');
1434 $this->subject->parameters[$key] = $value;
1435
1436 $this->assertEquals($value, $this->subject->getData('parameters:' . $key));
1437 }
1438
1439 /**
1440 * Checks if getData() works with type "register"
1441 *
1442 * @test
1443 */
1444 public function getDataWithTypeRegister(): void
1445 {
1446 $key = $this->getUniqueId('someKey');
1447 $value = $this->getUniqueId('someValue');
1448 $GLOBALS['TSFE']->register[$key] = $value;
1449
1450 $this->assertEquals($value, $this->subject->getData('register:' . $key));
1451 }
1452
1453 /**
1454 * Checks if getData() works with type "session"
1455 *
1456 * @test
1457 */
1458 public function getDataWithTypeSession(): void
1459 {
1460 $frontendUser = $this->getMockBuilder(FrontendUserAuthentication::class)
1461 ->setMethods(['getSessionData'])
1462 ->getMock();
1463 $frontendUser->expects($this->once())->method('getSessionData')->with('myext')->willReturn([
1464 'mydata' => [
1465 'someValue' => 42,
1466 ],
1467 ]);
1468 $GLOBALS['TSFE']->fe_user = $frontendUser;
1469
1470 $this->assertEquals(42, $this->subject->getData('session:myext|mydata|someValue'));
1471 }
1472
1473 /**
1474 * Checks if getData() works with type "level"
1475 *
1476 * @test
1477 */
1478 public function getDataWithTypeLevel(): void
1479 {
1480 $rootline = [
1481 0 => ['uid' => 1, 'title' => 'title1'],
1482 1 => ['uid' => 2, 'title' => 'title2'],
1483 2 => ['uid' => 3, 'title' => 'title3'],
1484 ];
1485
1486 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1487 $this->assertEquals(2, $this->subject->getData('level'));
1488 }
1489
1490 /**
1491 * Checks if getData() works with type "global"
1492 *
1493 * @test
1494 */
1495 public function getDataWithTypeGlobal(): void
1496 {
1497 $this->assertEquals($GLOBALS['TSFE']->metaCharset, $this->subject->getData('global:TSFE|metaCharset'));
1498 }
1499
1500 /**
1501 * Checks if getData() works with type "leveltitle"
1502 *
1503 * @test
1504 */
1505 public function getDataWithTypeLeveltitle(): void
1506 {
1507 $rootline = [
1508 0 => ['uid' => 1, 'title' => 'title1'],
1509 1 => ['uid' => 2, 'title' => 'title2'],
1510 2 => ['uid' => 3, 'title' => ''],
1511 ];
1512
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'));
1517 }
1518
1519 /**
1520 * Checks if getData() works with type "levelmedia"
1521 *
1522 * @test
1523 */
1524 public function getDataWithTypeLevelmedia(): void
1525 {
1526 $rootline = [
1527 0 => ['uid' => 1, 'title' => 'title1', 'media' => 'media1'],
1528 1 => ['uid' => 2, 'title' => 'title2', 'media' => 'media2'],
1529 2 => ['uid' => 3, 'title' => 'title3', 'media' => ''],
1530 ];
1531
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'));
1536 }
1537
1538 /**
1539 * Checks if getData() works with type "leveluid"
1540 *
1541 * @test
1542 */
1543 public function getDataWithTypeLeveluid(): void
1544 {
1545 $rootline = [
1546 0 => ['uid' => 1, 'title' => 'title1'],
1547 1 => ['uid' => 2, 'title' => 'title2'],
1548 2 => ['uid' => 3, 'title' => 'title3'],
1549 ];
1550
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'));
1555 }
1556
1557 /**
1558 * Checks if getData() works with type "levelfield"
1559 *
1560 * @test
1561 */
1562 public function getDataWithTypeLevelfield(): void
1563 {
1564 $rootline = [
1565 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1566 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1567 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => ''],
1568 ];
1569
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'));
1573 }
1574
1575 /**
1576 * Checks if getData() works with type "fullrootline"
1577 *
1578 * @test
1579 */
1580 public function getDataWithTypeFullrootline(): void
1581 {
1582 $rootline1 = [
1583 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1584 ];
1585 $rootline2 = [
1586 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1587 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1588 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => 'field3'],
1589 ];
1590
1591 $GLOBALS['TSFE']->tmpl->rootLine = $rootline1;
1592 $GLOBALS['TSFE']->rootLine = $rootline2;
1593 $this->assertEquals('field2', $this->subject->getData('fullrootline:-1,testfield'));
1594 }
1595
1596 /**
1597 * Checks if getData() works with type "date"
1598 *
1599 * @test
1600 */
1601 public function getDataWithTypeDate(): void
1602 {
1603 $format = 'Y-M-D';
1604 $defaultFormat = 'd/m Y';
1605
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'));
1608 }
1609
1610 /**
1611 * Checks if getData() works with type "page"
1612 *
1613 * @test
1614 */
1615 public function getDataWithTypePage(): void
1616 {
1617 $uid = mt_rand();
1618 $GLOBALS['TSFE']->page['uid'] = $uid;
1619 $this->assertEquals($uid, $this->subject->getData('page:uid'));
1620 }
1621
1622 /**
1623 * Checks if getData() works with type "current"
1624 *
1625 * @test
1626 */
1627 public function getDataWithTypeCurrent(): void
1628 {
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'));
1634 }
1635
1636 /**
1637 * Checks if getData() works with type "db"
1638 *
1639 * @test
1640 */
1641 public function getDataWithTypeDb(): void
1642 {
1643 $dummyRecord = ['uid' => 5, 'title' => 'someTitle'];
1644
1645 $GLOBALS['TSFE']->sys_page->expects($this->atLeastOnce())->method('getRawRecord')->with(
1646 'tt_content',
1647 '106'
1648 )->will($this->returnValue($dummyRecord));
1649 $this->assertEquals($dummyRecord['title'], $this->subject->getData('db:tt_content:106:title'));
1650 }
1651
1652 /**
1653 * Checks if getData() works with type "lll"
1654 *
1655 * @test
1656 */
1657 public function getDataWithTypeLll(): void
1658 {
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));
1663 }
1664
1665 /**
1666 * Checks if getData() works with type "path"
1667 *
1668 * @test
1669 */
1670 public function getDataWithTypePath(): void
1671 {
1672 $filenameIn = 'typo3/sysext/frontend/Public/Icons/Extension.svg';
1673 $this->assertEquals($filenameIn, $this->subject->getData('path:' . $filenameIn));
1674 }
1675
1676 /**
1677 * Checks if getData() works with type "context"
1678 *
1679 * @test
1680 */
1681 public function getDataWithTypeContext(): void
1682 {
1683 $context = new Context([
1684 'workspace' => new WorkspaceAspect(3),
1685 'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [0, -1])
1686 ]);
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'));
1692 }
1693
1694 /**
1695 * Checks if getData() works with type "context"
1696 *
1697 * @test
1698 */
1699 public function getDataWithTypeSite(): void
1700 {
1701 $site = new Site('my-site', 123, [
1702 'base' => 'http://example.com',
1703 'custom' => [
1704 'config' => [
1705 'nested' => 'yeah'
1706 ]
1707 ]
1708 ]);
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'));
1714 }
1715
1716 /**
1717 * Checks if getData() works with type "parentRecordNumber"
1718 *
1719 * @test
1720 */
1721 public function getDataWithTypeParentRecordNumber(): void
1722 {
1723 $recordNumber = mt_rand();
1724 $this->subject->parentRecordNumber = $recordNumber;
1725 $this->assertEquals($recordNumber, $this->subject->getData('cobj:parentRecordNumber'));
1726 }
1727
1728 /**
1729 * Checks if getData() works with type "debug:rootLine"
1730 *
1731 * @test
1732 */
1733 public function getDataWithTypeDebugRootline(): void
1734 {
1735 $rootline = [
1736 0 => ['uid' => 1, 'title' => 'title1'],
1737 1 => ['uid' => 2, 'title' => 'title2'],
1738 2 => ['uid' => 3, 'title' => ''],
1739 ];
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;
1742
1743 DebugUtility::useAnsiColor(false);
1744 $result = $this->subject->getData('debug:rootLine');
1745 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1746
1747 $this->assertEquals($expectedResult, $cleanedResult);
1748 }
1749
1750 /**
1751 * Checks if getData() works with type "debug:fullRootLine"
1752 *
1753 * @test
1754 */
1755 public function getDataWithTypeDebugFullRootline(): void
1756 {
1757 $rootline = [
1758 0 => ['uid' => 1, 'title' => 'title1'],
1759 1 => ['uid' => 2, 'title' => 'title2'],
1760 2 => ['uid' => 3, 'title' => ''],
1761 ];
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;
1764
1765 DebugUtility::useAnsiColor(false);
1766 $result = $this->subject->getData('debug:fullRootLine');
1767 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1768
1769 $this->assertEquals($expectedResult, $cleanedResult);
1770 }
1771
1772 /**
1773 * Checks if getData() works with type "debug:data"
1774 *
1775 * @test
1776 */
1777 public function getDataWithTypeDebugData(): void
1778 {
1779 $key = $this->getUniqueId('someKey');
1780 $value = $this->getUniqueId('someValue');
1781 $this->subject->data = [$key => $value];
1782
1783 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1784
1785 DebugUtility::useAnsiColor(false);
1786 $result = $this->subject->getData('debug:data');
1787 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1788
1789 $this->assertEquals($expectedResult, $cleanedResult);
1790 }
1791
1792 /**
1793 * Checks if getData() works with type "debug:register"
1794 *
1795 * @test
1796 */
1797 public function getDataWithTypeDebugRegister(): void
1798 {
1799 $key = $this->getUniqueId('someKey');
1800 $value = $this->getUniqueId('someValue');
1801 $GLOBALS['TSFE']->register = [$key => $value];
1802
1803 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1804
1805 DebugUtility::useAnsiColor(false);
1806 $result = $this->subject->getData('debug:register');
1807 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1808
1809 $this->assertEquals($expectedResult, $cleanedResult);
1810 }
1811
1812 /**
1813 * Checks if getData() works with type "data:page"
1814 *
1815 * @test
1816 */
1817 public function getDataWithTypeDebugPage(): void
1818 {
1819 $uid = mt_rand();
1820 $GLOBALS['TSFE']->page = ['uid' => $uid];
1821
1822 $expectedResult = 'array(1item)uid=>' . $uid . '(integer)';
1823
1824 DebugUtility::useAnsiColor(false);
1825 $result = $this->subject->getData('debug:page');
1826 $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1827
1828 $this->assertEquals($expectedResult, $cleanedResult);
1829 }
1830
1831 /**
1832 * @test
1833 */
1834 public function aTagParamsHasLeadingSpaceIfNotEmpty(): void
1835 {
1836 $aTagParams = $this->subject->getATagParams(['ATagParams' => 'data-test="testdata"']);
1837 $this->assertEquals(' data-test="testdata"', $aTagParams);
1838 }
1839
1840 /**
1841 * @test
1842 */
1843 public function aTagParamsHaveSpaceBetweenLocalAndGlobalParams(): void
1844 {
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);
1848 }
1849
1850 /**
1851 * @test
1852 */
1853 public function aTagParamsHasNoLeadingSpaceIfEmpty(): void
1854 {
1855 // make sure global ATagParams are empty
1856 $GLOBALS['TSFE']->ATagParams = '';
1857 $aTagParams = $this->subject->getATagParams(['ATagParams' => '']);
1858 $this->assertEquals('', $aTagParams);
1859 }
1860
1861 /**
1862 * @return array
1863 */
1864 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider(): array
1865 {
1866 return [
1867 [null, null],
1868 ['', null],
1869 ['', []],
1870 ['fooo', ['foo' => 'bar']]
1871 ];
1872 }
1873
1874 /**
1875 * Make sure that the rendering falls back to the classic <img style if nothing else is found
1876 *
1877 * @test
1878 * @dataProvider getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider
1879 * @param string $key
1880 * @param array $configuration
1881 */
1882 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFound($key, $configuration): void
1883 {
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);
1887 }
1888
1889 /**
1890 * @return array
1891 */
1892 public function getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider(): array
1893 {
1894 return [
1895 [
1896 'foo',
1897 [
1898 'layout.' => [
1899 'foo.' => [
1900 'element' => '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1901 ]
1902 ]
1903 ],
1904 '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1905 ]
1906
1907 ];
1908 }
1909
1910 /**
1911 * Assure if a layoutKey and layout is given the selected layout is returned
1912 *
1913 * @test
1914 * @dataProvider getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider
1915 * @param string $key
1916 * @param array $configuration
1917 * @param string $expectation
1918 */
1919 public function getImageTagTemplateReturnTemplateElementIdentifiedByKey($key, $configuration, $expectation): void
1920 {
1921 $result = $this->subject->getImageTagTemplate($key, $configuration);
1922 $this->assertEquals($result, $expectation);
1923 }
1924
1925 /**
1926 * @return array
1927 */
1928 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider(): array
1929 {
1930 return [
1931 [null, null, null],
1932 ['foo', null, null],
1933 ['foo', ['sourceCollection.' => 1], 'bar']
1934 ];
1935 }
1936
1937 /**
1938 * Make sure the source collection is empty if no valid configuration or source collection is defined
1939 *
1940 * @test
1941 * @dataProvider getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider
1942 * @param string $layoutKey
1943 * @param array $configuration
1944 * @param string $file
1945 */
1946 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefined(
1947 $layoutKey,
1948 $configuration,
1949 $file
1950 ): void {
1951 $result = $this->subject->getImageSourceCollection($layoutKey, $configuration, $file);
1952 $this->assertSame($result, '');
1953 }
1954
1955 /**
1956 * Make sure the generation of subimages calls the generation of the subimages and uses the layout -> source template
1957 *
1958 * @test
1959 */
1960 public function getImageSourceCollectionRendersDefinedSources(): void
1961 {
1962 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
1963 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
1964 ->setMethods(['stdWrap', 'getImgResource'])
1965 ->getMock();
1966
1967 $cObj->start([], 'tt_content');
1968
1969 $layoutKey = 'test';
1970
1971 $configuration = [
1972 'layoutKey' => 'test',
1973 'layout.' => [
1974 'test.' => [
1975 'element' => '<img ###SRC### ###SRCCOLLECTION### ###SELFCLOSINGTAGSLASH###>',
1976 'source' => '---###SRC###---'
1977 ]
1978 ],
1979 'sourceCollection.' => [
1980 '1.' => [
1981 'width' => '200'
1982 ]
1983 ]
1984 ];
1985
1986 $file = 'testImageName';
1987
1988 // Avoid calling of stdWrap
1989 $cObj
1990 ->expects($this->any())
1991 ->method('stdWrap')
1992 ->will($this->returnArgument(0));
1993
1994 // Avoid calling of imgResource
1995 $cObj
1996 ->expects($this->exactly(1))
1997 ->method('getImgResource')
1998 ->with($this->equalTo('testImageName'))
1999 ->will($this->returnValue([100, 100, null, 'bar']));
2000
2001 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2002
2003 $this->assertEquals('---bar---', $result);
2004 }
2005
2006 /**
2007 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyDefault test
2008 *
2009 * @return array multi-dimensional array with the second level like this:
2010 * @see getImageSourceCollectionRendersDefinedLayoutKeyDefault
2011 */
2012 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider(): array
2013 {
2014 $sourceCollectionArray = [
2015 'small.' => [
2016 'width' => 200,
2017 'srcsetCandidate' => '600w',
2018 'mediaQuery' => '(max-device-width: 600px)',
2019 'dataKey' => 'small',
2020 ],
2021 'smallRetina.' => [
2022 'if.directReturn' => 0,
2023 'width' => 200,
2024 'pixelDensity' => '2',
2025 'srcsetCandidate' => '600w 2x',
2026 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
2027 'dataKey' => 'smallRetina',
2028 ]
2029 ];
2030 return [
2031 [
2032 'default',
2033 [
2034 'layoutKey' => 'default',
2035 'layout.' => [
2036 'default.' => [
2037 'element' => '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>',
2038 'source' => ''
2039 ]
2040 ],
2041 'sourceCollection.' => $sourceCollectionArray
2042 ]
2043 ],
2044 ];
2045 }
2046
2047 /**
2048 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
2049 *
2050 * @test
2051 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider
2052 * @param string $layoutKey
2053 * @param array $configuration
2054 */
2055 public function getImageSourceCollectionRendersDefinedLayoutKeyDefault($layoutKey, $configuration): void
2056 {
2057 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
2058 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
2059 ->setMethods(['stdWrap', 'getImgResource'])
2060 ->getMock();
2061
2062 $cObj->start([], 'tt_content');
2063
2064 $file = 'testImageName';
2065
2066 // Avoid calling of stdWrap
2067 $cObj
2068 ->expects($this->any())
2069 ->method('stdWrap')
2070 ->will($this->returnArgument(0));
2071
2072 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2073
2074 $this->assertEmpty($result);
2075 }
2076
2077 /**
2078 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyData test
2079 *
2080 * @return array multi-dimensional array with the second level like this:
2081 * @see getImageSourceCollectionRendersDefinedLayoutKeyData
2082 */
2083 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider(): array
2084 {
2085 $sourceCollectionArray = [
2086 'small.' => [
2087 'width' => 200,
2088 'srcsetCandidate' => '600w',
2089 'mediaQuery' => '(max-device-width: 600px)',
2090 'dataKey' => 'small',
2091 ],
2092 'smallRetina.' => [
2093 'if.directReturn' => 1,
2094 'width' => 200,
2095 'pixelDensity' => '2',
2096 'srcsetCandidate' => '600w 2x',
2097 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
2098 'dataKey' => 'smallRetina',
2099 ]
2100 ];
2101 return [
2102 [
2103 'srcset',
2104 [
2105 'layoutKey' => 'srcset',
2106 'layout.' => [
2107 'srcset.' => [
2108 'element' => '<img src="###SRC###" srcset="###SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2109 'source' => '|*|###SRC### ###SRCSETCANDIDATE###,|*|###SRC### ###SRCSETCANDIDATE###'
2110 ]
2111 ],
2112 'sourceCollection.' => $sourceCollectionArray
2113 ],
2114 'xhtml_strict',
2115 'bar-file.jpg 600w,bar-file.jpg 600w 2x',
2116 ],
2117 [
2118 'picture',
2119 [
2120 'layoutKey' => 'picture',
2121 'layout.' => [
2122 'picture.' => [
2123 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
2124 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
2125 ]
2126 ],
2127 'sourceCollection.' => $sourceCollectionArray,
2128 ],
2129 'xhtml_strict',
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)" />',
2131 ],
2132 [
2133 'picture',
2134 [
2135 'layoutKey' => 'picture',
2136 'layout.' => [
2137 'picture.' => [
2138 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
2139 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
2140 ]
2141 ],
2142 'sourceCollection.' => $sourceCollectionArray,
2143 ],
2144 '',
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)">',
2146 ],
2147 [
2148 'data',
2149 [
2150 'layoutKey' => 'data',
2151 'layout.' => [
2152 'data.' => [
2153 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2154 'source' => 'data-###DATAKEY###="###SRC###"'
2155 ]
2156 ],
2157 'sourceCollection.' => $sourceCollectionArray
2158 ],
2159 'xhtml_strict',
2160 'data-small="bar-file.jpg"data-smallRetina="bar-file.jpg"',
2161 ],
2162 ];
2163 }
2164
2165 /**
2166 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
2167 *
2168 * @test
2169 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider
2170 * @param string $layoutKey
2171 * @param array $configuration
2172 * @param string $xhtmlDoctype
2173 * @param string $expectedHtml
2174 */
2175 public function getImageSourceCollectionRendersDefinedLayoutKeyData(
2176 $layoutKey,
2177 $configuration,
2178 $xhtmlDoctype,
2179 $expectedHtml
2180 ): void {
2181 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer */
2182 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
2183 ->setMethods(['stdWrap', 'getImgResource'])
2184 ->getMock();
2185
2186 $cObj->start([], 'tt_content');
2187
2188 $file = 'testImageName';
2189
2190 $GLOBALS['TSFE']->xhtmlDoctype = $xhtmlDoctype;
2191
2192 // Avoid calling of stdWrap
2193 $cObj
2194 ->expects($this->any())
2195 ->method('stdWrap')
2196 ->will($this->returnArgument(0));
2197
2198 // Avoid calling of imgResource
2199 $cObj
2200 ->expects($this->exactly(2))
2201 ->method('getImgResource')
2202 ->with($this->equalTo('testImageName'))
2203 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
2204
2205 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
2206
2207 $this->assertEquals($expectedHtml, $result);
2208 }
2209
2210 /**
2211 * Make sure the hook in get sourceCollection is called
2212 *
2213 * @test
2214 */
2215 public function getImageSourceCollectionHookCalled(): void
2216 {
2217 $this->subject = $this->getAccessibleMock(
2218 ContentObjectRenderer::class,
2219 ['getResourceFactory', 'stdWrap', 'getImgResource']
2220 );
2221 $this->subject->start([], 'tt_content');
2222
2223 // Avoid calling stdwrap and getImgResource
2224 $this->subject->expects($this->any())
2225 ->method('stdWrap')
2226 ->will($this->returnArgument(0));
2227
2228 $this->subject->expects($this->any())
2229 ->method('getImgResource')
2230 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
2231
2232 $resourceFactory = $this->createMock(ResourceFactory::class);
2233 $this->subject->expects($this->any())->method('getResourceFactory')->will($this->returnValue($resourceFactory));
2234
2235 $className = $this->getUniqueId('tx_coretest_getImageSourceCollectionHookCalled');
2236 $getImageSourceCollectionHookMock = $this->getMockBuilder(
2237 ContentObjectOneSourceCollectionHookInterface::class
2238 )
2239 ->setMethods(['getOneSourceCollection'])
2240 ->setMockClassName($className)
2241 ->getMock();
2242 GeneralUtility::addInstance($className, $getImageSourceCollectionHookMock);
2243 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'][] = $className;
2244
2245 $getImageSourceCollectionHookMock
2246 ->expects($this->exactly(1))
2247 ->method('getOneSourceCollection')
2248 ->will($this->returnCallback([$this, 'isGetOneSourceCollectionCalledCallback']));
2249
2250 $configuration = [
2251 'layoutKey' => 'data',
2252 'layout.' => [
2253 'data.' => [
2254 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2255 'source' => 'data-###DATAKEY###="###SRC###"'
2256 ]
2257 ],
2258 'sourceCollection.' => [
2259 'small.' => [
2260 'width' => 200,
2261 'srcsetCandidate' => '600w',
2262 'mediaQuery' => '(max-device-width: 600px)',
2263 'dataKey' => 'small',
2264 ],
2265 ],
2266 ];
2267
2268 $result = $this->subject->getImageSourceCollection('data', $configuration, $this->getUniqueId('testImage-'));
2269
2270 $this->assertSame($result, 'isGetOneSourceCollectionCalledCallback');
2271 }
2272
2273 /**
2274 * Handles the arguments that have been sent to the getImgResource hook.
2275 *
2276 * @param array $sourceRenderConfiguration
2277 * @param array $sourceConfiguration
2278 * @param $oneSourceCollection
2279 * @param $parent
2280 * @return string
2281 * @see getImageSourceCollectionHookCalled
2282 */
2283 public function isGetOneSourceCollectionCalledCallback(
2284 array $sourceRenderConfiguration,
2285 array $sourceConfiguration,
2286 $oneSourceCollection,
2287 $parent
2288 ): string {
2289 $this->assertTrue(is_array($sourceRenderConfiguration));
2290 $this->assertTrue(is_array($sourceConfiguration));
2291 return 'isGetOneSourceCollectionCalledCallback';
2292 }
2293
2294 /**
2295 * @test
2296 */
2297 public function renderingContentObjectThrowsException(): void
2298 {
2299 $this->expectException(\LogicException::class);
2300 $this->expectExceptionCode(1414513947);
2301 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2302 $this->subject->render($contentObjectFixture, []);
2303 }
2304
2305 /**
2306 * @test
2307 */
2308 public function exceptionHandlerIsEnabledByDefaultInProductionContext(): void
2309 {
2310 $backupApplicationContext = GeneralUtility::getApplicationContext();
2311 Fixtures\GeneralUtilityFixture::setApplicationContext(new ApplicationContext('Production'));
2312
2313 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2314 $this->subject->render($contentObjectFixture, []);
2315
2316 Fixtures\GeneralUtilityFixture::setApplicationContext($backupApplicationContext);
2317 }
2318
2319 /**
2320 * @test
2321 */
2322 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredLocally(): void
2323 {
2324 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2325
2326 $configuration = [
2327 'exceptionHandler' => '1'
2328 ];
2329 $this->subject->render($contentObjectFixture, $configuration);
2330 }
2331
2332 /**
2333 * @test
2334 */
2335 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredGlobally(): void
2336 {
2337 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2338
2339 $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
2340 $this->subject->render($contentObjectFixture, []);
2341 }
2342
2343 /**
2344 * @test
2345 */
2346 public function globalExceptionHandlerConfigurationCanBeOverriddenByLocalConfiguration(): void
2347 {
2348 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2349 $this->expectException(\LogicException::class);
2350 $this->expectExceptionCode(1414513947);
2351 $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
2352 $configuration = [
2353 'exceptionHandler' => '0'
2354 ];
2355 $this->subject->render($contentObjectFixture, $configuration);
2356 }
2357
2358 /**
2359 * @test
2360 */
2361 public function renderedErrorMessageCanBeCustomized(): void
2362 {
2363 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2364
2365 $configuration = [
2366 'exceptionHandler' => '1',
2367 'exceptionHandler.' => [
2368 'errorMessage' => 'New message for testing',
2369 ]
2370 ];
2371
2372 $this->assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
2373 }
2374
2375 /**
2376 * @test
2377 */
2378 public function localConfigurationOverridesGlobalConfiguration(): void
2379 {
2380 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2381
2382 $this->frontendControllerMock
2383 ->config['config']['contentObjectExceptionHandler.'] = [
2384 'errorMessage' => 'Global message for testing',
2385 ];
2386 $configuration = [
2387 'exceptionHandler' => '1',
2388 'exceptionHandler.' => [
2389 'errorMessage' => 'New message for testing',
2390 ]
2391 ];
2392
2393 $this->assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
2394 }
2395
2396 /**
2397 * @test
2398 */
2399 public function specificExceptionsCanBeIgnoredByExceptionHandler(): void
2400 {
2401 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2402
2403 $configuration = [
2404 'exceptionHandler' => '1',
2405 'exceptionHandler.' => [
2406 'ignoreCodes.' => ['10.' => '1414513947'],
2407 ]
2408 ];
2409 $this->expectException(\LogicException::class);
2410 $this->expectExceptionCode(1414513947);
2411 $this->subject->render($contentObjectFixture, $configuration);
2412 }
2413
2414 /**
2415 * @return \PHPUnit_Framework_MockObject_MockObject | AbstractContentObject
2416 */
2417 protected function createContentObjectThrowingExceptionFixture()
2418 {
2419 $contentObjectFixture = $this->getMockBuilder(AbstractContentObject::class)
2420 ->setConstructorArgs([$this->subject])
2421 ->getMock();
2422 $contentObjectFixture->expects($this->once())
2423 ->method('render')
2424 ->willReturnCallback(function () {
2425 throw new \LogicException('Exception during rendering', 1414513947);
2426 });
2427 return $contentObjectFixture;
2428 }
2429
2430 /**
2431 * @return array
2432 */
2433 protected function getLibParseFunc(): array
2434 {
2435 return [
2436 'makelinks' => '1',
2437 'makelinks.' => [
2438 'http.' => [
2439 'keep' => '{$styles.content.links.keep}',
2440 'extTarget' => '',
2441 'mailto.' => [
2442 'keep' => 'path',
2443 ],
2444 ],
2445 ],
2446 'tags' => [
2447 'link' => 'TEXT',
2448 'link.' => [
2449 'current' => '1',
2450 'typolink.' => [
2451 'parameter.' => [
2452 'data' => 'parameters : allParams',
2453 ],
2454 ],
2455 'parseFunc.' => [
2456 'constants' => '1',
2457 ],
2458 ],
2459 ],
2460
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',
2462 'denyTags' => '*',
2463 'sword' => '<span class="csc-sword">|</span>',
2464 'constants' => '1',
2465 'nonTypoTagStdWrap.' => [
2466 'HTMLparser' => '1',
2467 'HTMLparser.' => [
2468 'keepNonMatchedTags' => '1',
2469 'htmlSpecialChars' => '2',
2470 ],
2471 ],
2472 ];
2473 }
2474
2475 /**
2476 * @return array
2477 */
2478 protected function getLibParseFunc_RTE(): array
2479 {
2480 return [
2481 'parseFunc' => '',
2482 'parseFunc.' => [
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',
2484 'constants' => '1',
2485 'denyTags' => '*',
2486 'externalBlocks' => 'article, aside, blockquote, div, dd, dl, footer, header, nav, ol, section, table, ul, pre',
2487 'externalBlocks.' => [
2488 'article.' => [
2489 'callRecursive' => '1',
2490 'stripNL' => '1',
2491 ],
2492 'aside.' => [
2493 'callRecursive' => '1',
2494 'stripNL' => '1',
2495 ],
2496 'blockquote.' => [
2497 'callRecursive' => '1',
2498 'stripNL' => '1',
2499 ],
2500 'dd.' => [
2501 'callRecursive' => '1',
2502 'stripNL' => '1',
2503 ],
2504 'div.' => [
2505 'callRecursive' => '1',
2506 'stripNL' => '1',
2507 ],
2508 'dl.' => [
2509 'callRecursive' => '1',
2510 'stripNL' => '1',
2511 ],
2512 'footer.' => [
2513 'callRecursive' => '1',
2514 'stripNL' => '1',
2515 ],
2516 'header.' => [
2517 'callRecursive' => '1',
2518 'stripNL' => '1',
2519 ],
2520 'nav.' => [
2521 'callRecursive' => '1',
2522 'stripNL' => '1',
2523 ],
2524 'ol.' => [
2525 'callRecursive' => '1',
2526 'stripNL' => '1',
2527 ],
2528 'section.' => [
2529 'callRecursive' => '1',
2530 'stripNL' => '1',
2531 ],
2532 'table.' => [
2533 'HTMLtableCells' => '1',
2534 'HTMLtableCells.' => [
2535 'addChr10BetweenParagraphs' => '1',
2536 'default.' => [
2537 'stdWrap.' => [
2538 'parseFunc' => '=< lib.parseFunc_RTE',
2539 'parseFunc.' => [
2540 'nonTypoTagStdWrap.' => [
2541 'encapsLines.' => [
2542 'nonWrappedTag' => '',
2543 ],
2544 ],
2545 ],
2546 ],
2547 ],
2548 ],
2549 'stdWrap.' => [
2550 'HTMLparser' => '1',
2551 'HTMLparser.' => [
2552 'keepNonMatchedTags' => '1',
2553 'tags.' => [
2554 'table.' => [
2555 'fixAttrib.' => [
2556 'class.' => [
2557 'always' => '1',
2558 'default' => 'contenttable',
2559 'list' => 'contenttable',
2560 ],
2561 ],
2562 ],
2563 ],
2564 ],
2565 ],
2566 'stripNL' => '1',
2567 ],
2568 'ul.' => [
2569 'callRecursive' => '1',
2570 'stripNL' => '1',
2571 ],
2572 ],
2573 'makelinks' => '1',
2574 'makelinks.' => [
2575 'http.' => [
2576 'extTarget.' => [
2577 'override' => '_blank',
2578 ],
2579 'keep' => 'path',
2580 ],
2581 ],
2582 'nonTypoTagStdWrap.' => [
2583 'encapsLines.' => [
2584 'addAttributes.' => [
2585 'P.' => [
2586 'class' => 'bodytext',
2587 'class.' => [
2588 'setOnly' => 'blank',
2589 ],
2590 ],
2591 ],
2592 'encapsTagList' => 'p,pre,h1,h2,h3,h4,h5,h6,hr,dt,li',
2593 'innerStdWrap_all.' => [
2594 'ifBlank' => '&nbsp;',
2595 ],
2596 'nonWrappedTag' => 'P',
2597 'remapTag.' => [
2598 'DIV' => 'P',
2599 ],
2600 ],
2601 'HTMLparser' => '1',
2602 'HTMLparser.' => [
2603 'htmlSpecialChars' => '2',
2604 'keepNonMatchedTags' => '1',
2605 ],
2606 ],
2607 'sword' => '<span class="csc-sword">|</span>',
2608 'tags.' => [
2609 'link' => 'TEXT',
2610 'link.' => [
2611 'current' => '1',
2612 'parseFunc.' => [
2613 'constants' => '1',
2614 ],
2615 'typolink.' => [
2616 'extTarget.' => [
2617 'override' => '',
2618 ],
2619 'parameter.' => [
2620 'data' => 'parameters : allParams',
2621 ],
2622 'target.' => [
2623 'override' => '',
2624 ],
2625 ],
2626 ],
2627 ],
2628 ],
2629 ];
2630 }
2631
2632 /**
2633 * @return array
2634 */
2635 public function _parseFuncReturnsCorrectHtmlDataProvider(): array
2636 {
2637 return [
2638 'Text without tag is wrapped with <p> tag' => [
2639 'Text without tag',
2640 $this->getLibParseFunc_RTE(),
2641 '<p class="bodytext">Text without tag</p>',
2642 ],
2643 'Text wrapped with <p> tag remains the same' => [
2644 '<p class="myclass">Text with &lt;p&gt; tag</p>',
2645 $this->getLibParseFunc_RTE(),
2646 '<p class="myclass">Text with &lt;p&gt; tag</p>',
2647 ],
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>',
2652 ],
2653 'Empty lines are not duplicated' => [
2654 LF,
2655 $this->getLibParseFunc_RTE(),
2656 '<p class="bodytext">&nbsp;</p>',
2657 ],
2658 'Multiple empty lines with no text' => [
2659 LF . LF . LF,
2660 $this->getLibParseFunc_RTE(),
2661 '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2662 ],
2663 'Empty lines are not duplicated at the end of content' => [
2664 'test' . LF . LF,
2665 $this->getLibParseFunc_RTE(),
2666 '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2667 ],
2668 'Empty lines are not trimmed' => [
2669 LF . 'test' . LF,
2670 $this->getLibParseFunc_RTE(),
2671 '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2672 ],
2673 ];
2674 }
2675
2676 /**
2677 * @test
2678 * @dataProvider _parseFuncReturnsCorrectHtmlDataProvider
2679 * @param string $value
2680 * @param array $configuration
2681 * @param string $expectedResult
2682 */
2683 public function stdWrap_parseFuncReturnsParsedHtml($value, $configuration, $expectedResult): void
2684 {
2685 $this->assertEquals($expectedResult, $this->subject->stdWrap_parseFunc($value, $configuration));
2686 }
2687
2688 /**
2689 * @return array
2690 */
2691 public function typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider(): array
2692 {
2693 return [
2694 'Link to url' => [
2695 'TYPO3',
2696 [
2697 'parameter' => 'http://typo3.org',
2698 ],
2699 '<a href="http://typo3.org">TYPO3</a>',
2700 ],
2701 'Link to url without schema' => [
2702 'TYPO3',
2703 [
2704 'parameter' => 'typo3.org',
2705 ],
2706 '<a href="http://typo3.org">TYPO3</a>',
2707 ],
2708 'Link to url without link text' => [
2709 '',
2710 [
2711 'parameter' => 'http://typo3.org',
2712 ],
2713 '<a href="http://typo3.org">http://typo3.org</a>',
2714 ],
2715 'Link to url with attributes' => [
2716 'TYPO3',
2717 [
2718 'parameter' => 'http://typo3.org',
2719 'ATagParams' => 'class="url-class"',
2720 'extTarget' => '_blank',
2721 'title' => 'Open new window',
2722 ],
2723 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2724 ],
2725 'Link to url with attributes in parameter' => [
2726 'TYPO3',
2727 [
2728 'parameter' => 'http://typo3.org _blank url-class "Open new window"',
2729 ],
2730 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2731 ],
2732 'Link to url with script tag' => [
2733 '',
2734 [
2735 'parameter' => 'http://typo3.org<script>alert(123)</script>',
2736 ],
2737 '<a href="http://typo3.org&lt;script&gt;alert(123)&lt;/script&gt;">http://typo3.org&lt;script&gt;alert(123)&lt;/script&gt;</a>',
2738 ],
2739 'Link to email address' => [
2740 'Email address',
2741 [
2742 'parameter' => 'foo@bar.org',
2743 ],
2744 '<a href="mailto:foo@bar.org">Email address</a>',
2745 ],
2746 'Link to email address without link text' => [
2747 '',
2748 [
2749 'parameter' => 'foo@bar.org',
2750 ],
2751 '<a href="mailto:foo@bar.org">foo@bar.org</a>',
2752 ],
2753 'Link to email with attributes' => [
2754 'Email address',
2755 [
2756 'parameter' => 'foo@bar.org',
2757 'ATagParams' => 'class="email-class"',
2758 'title' => 'Write an email',
2759 ],
2760 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2761 ],
2762 'Link to email with attributes in parameter' => [
2763 'Email address',
2764 [
2765 'parameter' => 'foo@bar.org - email-class "Write an email"',
2766 ],
2767 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2768 ],
2769 ];
2770 }
2771
2772 /**
2773 * @test
2774 * @param string $linkText
2775 * @param array $configuration
2776 * @param string $expectedResult
2777 * @dataProvider typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider
2778 */
2779 public function typolinkReturnsCorrectLinksForEmailsAndUrls($linkText, $configuration, $expectedResult): void
2780 {
2781 $packageManagerMock = $this->getMockBuilder(PackageManager::class)
2782 ->disableOriginalConstructor()
2783 ->getMock();
2784 $templateServiceObjectMock = $this->getMockBuilder(TemplateService::class)
2785 ->setConstructorArgs([null, $packageManagerMock])
2786 ->setMethods(['dummy'])
2787 ->getMock();
2788 $templateServiceObjectMock->setup = [
2789 'lib.' => [
2790 'parseFunc.' => $this->getLibParseFunc(),
2791 ],
2792 ];
2793 $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
2794 $typoScriptFrontendControllerMockObject->config = [
2795 'config' => [],
2796 ];
2797 $typoScriptFrontendControllerMockObject->tmpl = $templateServiceObjectMock;
2798 $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2799 $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2800
2801 $this->assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
2802 }
2803
2804 /**
2805 * @param array $settings
2806 * @param string $linkText
2807 * @param string $mailAddress
2808 * @param string $expected
2809 * @dataProvider typoLinkEncodesMailAddressForSpamProtectionDataProvider
2810 * @test
2811 */
2812 public function typoLinkEncodesMailAddressForSpamProtection(
2813 array $settings,
2814 $linkText,
2815 $mailAddress,
2816 $expected
2817 ): void {
2818 $this->getFrontendController()->spamProtectEmailAddresses = $settings['spamProtectEmailAddresses'];
2819 $this->getFrontendController()->config['config'] = $settings;
2820 $typoScript = ['parameter' => $mailAddress];
2821
2822 $this->assertEquals($expected, $this->subject->typoLink($linkText, $typoScript));
2823 }
2824
2825 /**
2826 * @return array
2827 */
2828 public function typoLinkEncodesMailAddressForSpamProtectionDataProvider(): array
2829 {
2830 return [
2831 'plain mail without mailto scheme' => [
2832 [
2833 'spamProtectEmailAddresses' => '',
2834 'spamProtectEmailAddresses_atSubst' => '',
2835 'spamProtectEmailAddresses_lastDotSubst' => '',
2836 ],
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>',
2840 ],
2841 'plain mail with mailto scheme' => [
2842 [
2843 'spamProtectEmailAddresses' => '',
2844 'spamProtectEmailAddresses_atSubst' => '',
2845 'spamProtectEmailAddresses_lastDotSubst' => '',
2846 ],
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>',
2850 ],
2851 'plain with at and dot substitution' => [
2852 [
2853 'spamProtectEmailAddresses' => '0',
2854 'spamProtectEmailAddresses_atSubst' => '(at)',
2855 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2856 ],
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>',
2860 ],
2861 'mono-alphabetic substitution offset +1' => [
2862 [
2863 'spamProtectEmailAddresses' => '1',
2864 'spamProtectEmailAddresses_atSubst' => '',
2865 'spamProtectEmailAddresses_lastDotSubst' => '',
2866 ],
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>',
2870 ],
2871 'mono-alphabetic substitution offset +1 with at substitution' => [
2872 [
2873 'spamProtectEmailAddresses' => '1',
2874 'spamProtectEmailAddresses_atSubst' => '@',
2875 'spamProtectEmailAddresses_lastDotSubst' => '',
2876 ],
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>',
2880 ],
2881 'mono-alphabetic substitution offset +1 with at and dot substitution' => [
2882 [
2883 'spamProtectEmailAddresses' => '1',
2884 'spamProtectEmailAddresses_atSubst' => '(at)',
2885 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2886 ],
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>',
2890 ],
2891 'mono-alphabetic substitution offset -1 with at and dot substitution' => [
2892 [
2893 'spamProtectEmailAddresses' => '-1',
2894 'spamProtectEmailAddresses_atSubst' => '(at)',
2895 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2896 ],
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>',
2900 ],
2901 'entity substitution with at and dot substitution' => [
2902 [
2903 'spamProtectEmailAddresses' => 'ascii',
2904 'spamProtectEmailAddresses_atSubst' => '',
2905 'spamProtectEmailAddresses_lastDotSubst' => '',
2906 ],
2907 'some.body@test.typo3.org',
2908 'mailto:some.body@test.typo3.org',
2909 '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#111;&#109;&#101;&#46;&#98;&#111;&#100;&#121;&#64;&#116;&#101;&#115;&#116;&#46;&#116;&#121;&#112;&#111;&#51;&#46;&#111;&#114;&#103;">some.body(at)test.typo3.org</a>',
2910 ],
2911 'entity substitution with at and dot substitution with at and dot substitution' => [
2912 [
2913 'spamProtectEmailAddresses' => 'ascii',
2914 'spamProtectEmailAddresses_atSubst' => '(at)',
2915 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2916 ],
2917 'some.body@test.typo3.org',
2918 'mailto:some.body@test.typo3.org',
2919 '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#111;&#109;&#101;&#46;&#98;&#111;&#100;&#121;&#64;&#116;&#101;&#115;&#116;&#46;&#116;&#121;&#112;&#111;&#51;&#46;&#111;&#114;&#103;">some.body(at)test.typo3(dot)org</a>',
2920 ],
2921 ];
2922 }
2923
2924 /**
2925 * @return array
2926 */
2927 public function typolinkReturnsCorrectLinksFilesDataProvider(): array
2928 {
2929 return [
2930 'Link to file' => [
2931 'My file',
2932 [
2933 'parameter' => 'fileadmin/foo.bar',
2934 ],
2935 '<a href="fileadmin/foo.bar">My file</a>',
2936 ],
2937 'Link to file without link text' => [
2938 '',
2939 [
2940 'parameter' => 'fileadmin/foo.bar',
2941 ],
2942 '<a href="fileadmin/foo.bar">fileadmin/foo.bar</a>',
2943 ],
2944 'Link to file with attributes' => [
2945 'My file',
2946 [
2947 'parameter' => 'fileadmin/foo.bar',
2948 'ATagParams' => 'class="file-class"',
2949 'fileTarget' => '_blank',
2950 'title' => 'Title of the file',
2951 ],
2952 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2953 ],
2954 'Link to file with attributes and additional href' => [
2955 'My file',
2956 [
2957 'parameter' => 'fileadmin/foo.bar',
2958 'ATagParams' => 'href="foo-bar"',
2959 'fileTarget' => '_blank',
2960 'title' => 'Title of the file',
2961 ],
2962 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank">My file</a>',
2963 ],
2964 'Link to file with attributes and additional href and class' => [
2965 'My file',
2966 [
2967 'parameter' => 'fileadmin/foo.bar',
2968 'ATagParams' => 'href="foo-bar" class="file-class"',
2969 'fileTarget' => '_blank',
2970 'title' => 'Title of the file',
2971 ],
2972 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2973 ],
2974 'Link to file with attributes and additional class and href' => [
2975 'My file',
2976 [
2977 'parameter' => 'fileadmin/foo.bar',
2978 'ATagParams' => 'class="file-class" href="foo-bar"',
2979 'fileTarget' => '_blank',
2980 'title' => 'Title of the file',
2981 ],
2982 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2983 ],
2984 'Link to file with attributes and additional class and href and title' => [
2985 'My file',
2986 [
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',
2991 ],
2992 '<a href="fileadmin/foo.bar" title="foo-bar" target="_blank" class="file-class">My file</a>',
2993 ],
2994 'Link to file with attributes and empty ATagParams' => [
2995 'My file',