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