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