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