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