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