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