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