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