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