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