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