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