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