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