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