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