[!!!][TASK] Extract testing framework for TYPO3
[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\CMS\Components\TestingFramework\Core\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\CMS\Components\TestingFramework\Core\AccessibleObjectInterface|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
67 */
68 protected $subject = null;
69
70 /**
71 * @var \PHPUnit_Framework_MockObject_MockObject|TypoScriptFrontendController|\TYPO3\CMS\Components\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 overriden
356 'key2' => 'value2Overruled',
357 'key3' => [
358 'key32' => [
359 // Shouldn't be set: Parameter is excluded and not forced
360 'key321' => 'value321Overruled',
361 // Should be overriden: 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 overriden
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 * @return void
757 */
758 public function cropHTML($expect, $content, $conf)
759 {
760 $this->handleCharset($content, $expect);
761 $this->assertSame($expect,
762 $this->subject->cropHTML($content, $conf));
763 }
764
765 /**
766 * Data provider for round
767 *
768 * @return array [$expect, $contet, $conf]
769 */
770 public function roundDataProvider()
771 {
772 return [
773 // floats
774 'down' => [1.0, 1.11, []],
775 'up' => [2.0, 1.51, []],
776 'rounds up from x.50' => [2.0, 1.50, []],
777 'down with decimals' => [0.12, 0.1231, ['decimals' => 2]],
778 'up with decimals' => [0.13, 0.1251, ['decimals' => 2]],
779 'ceil' => [1.0, 0.11, ['roundType' => 'ceil']],
780 'ceil does not accept decimals' => [
781 1.0, 0.111, [
782 'roundType' => 'ceil',
783 'decimals' => 2,
784 ],
785 ],
786 'floor' => [2.0, 2.99, ['roundType' => 'floor']],
787 'floor does not accept decimals' => [
788 2.0, 2.999, [
789 'roundType' => 'floor',
790 'decimals' => 2,
791 ],
792 ],
793 'round, down' => [1.0, 1.11, ['roundType' => 'round']],
794 'round, up' => [2.0, 1.55, ['roundType' => 'round']],
795 'round does accept decimals' => [
796 5.56, 5.5555, [
797 'roundType' => 'round',
798 'decimals' => 2,
799 ],
800 ],
801 // strings
802 'emtpy string' => [0.0, '', []],
803 'word string' => [0.0, 'word', []],
804 'float string' => [1.0, '1.123456789', []],
805 // other types
806 'null' => [0.0, null, []],
807 'false' => [0.0, false, []],
808 'true' => [1.0, true, []]
809 ];
810 }
811
812 /**
813 * Check if round works properly
814 *
815 * Show:
816 *
817 * - Different types of input are casted to float.
818 * - Configuration ceil rounds like ceil().
819 * - Configuration floor rounds like floor().
820 * - Otherwise rounds like round() and decimals can be applied.
821 * - Always returns float.
822 *
823 * @param float $expected The expected output.
824 * @param mixed $content The given content.
825 * @param array $conf The given configuration of 'round.'.
826 * @return void
827 * @dataProvider roundDataProvider
828 * @test
829 */
830 public function round($expect, $content, $conf)
831 {
832 $this->assertSame($expect,
833 $this->subject->_call('round', $content, $conf));
834 }
835
836 /**
837 * @test
838 */
839 public function recursiveStdWrapProperlyRendersBasicString()
840 {
841 $stdWrapConfiguration = [
842 'noTrimWrap' => '|| 123|',
843 'stdWrap.' => [
844 'wrap' => '<b>|</b>'
845 ]
846 ];
847 $this->assertSame(
848 '<b>Test</b> 123',
849 $this->subject->stdWrap('Test', $stdWrapConfiguration)
850 );
851 }
852
853 /**
854 * @test
855 */
856 public function recursiveStdWrapIsOnlyCalledOnce()
857 {
858 $stdWrapConfiguration = [
859 'append' => 'TEXT',
860 'append.' => [
861 'data' => 'register:Counter'
862 ],
863 'stdWrap.' => [
864 'append' => 'LOAD_REGISTER',
865 'append.' => [
866 'Counter.' => [
867 'prioriCalc' => 'intval',
868 'cObject' => 'TEXT',
869 'cObject.' => [
870 'data' => 'register:Counter',
871 'wrap' => '|+1',
872 ]
873 ]
874 ]
875 ]
876 ];
877 $this->assertSame(
878 'Counter:1',
879 $this->subject->stdWrap('Counter:', $stdWrapConfiguration)
880 );
881 }
882
883 /**
884 * Data provider for numberFormat.
885 *
886 * @return array [$expect, $content, $conf]
887 */
888 public function numberFormatDataProvider()
889 {
890 return [
891 'testing decimals' => [
892 '0.80', 0.8,
893 ['decimals' => 2]
894 ],
895 'testing decimals with input as string' => [
896 '0.80', '0.8',
897 ['decimals' => 2]
898 ],
899 'testing dec_point' => [
900 '0,8', 0.8,
901 ['decimals' => 1, 'dec_point' => ',']
902 ],
903 'testing thousands_sep' => [
904 '1.000', 999.99,
905 [
906 'decimals' => 0,
907 'thousands_sep.' => ['char' => 46]
908 ]
909 ],
910 'testing mixture' => [
911 '1.281.731,5', 1281731.45,
912 [
913 'decimals' => 1,
914 'dec_point.' => ['char' => 44],
915 'thousands_sep.' => ['char' => 46]
916 ]
917 ]
918 ];
919 }
920
921 /**
922 * Check if numberFormat works properly.
923 *
924 * @dataProvider numberFormatDataProvider
925 * @test
926 */
927 public function numberFormat($expects, $content, $conf)
928 {
929 $this->assertSame($expects,
930 $this->subject->numberFormat($content, $conf));
931 }
932
933 /**
934 * Data provider replacement
935 *
936 * @return array [$expect, $content, $conf]
937 */
938 public function replacementDataProvider()
939 {
940 return [
941 'multiple replacements, including regex' => [
942 'There is an animal, an animal and an animal around the block! Yeah!',
943 'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
944 [
945 '20.' => [
946 'search' => '_',
947 'replace.' => ['char' => '32']
948 ],
949 '120.' => [
950 'search' => 'in da hood',
951 'replace' => 'around the block'
952 ],
953 '130.' => [
954 'search' => '#a (Cat|Dog|Tiger)#i',
955 'replace' => 'an animal',
956 'useRegExp' => '1'
957 ]
958 ]
959 ],
960 'replacement with optionSplit, normal pattern' => [
961 'There1is2a3cat,3a3dog3and3a3tiger3in3da3hood!3Yeah!',
962 'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
963 [
964 '10.' => [
965 'search' => '_',
966 'replace' => '1 || 2 || 3',
967 'useOptionSplitReplace' => '1'
968 ]
969 ]
970 ],
971 'replacement with optionSplit, using regex' => [
972 'There is a tiny cat, a midsized dog and a big tiger in da hood! Yeah!',
973 'There is a cat, a dog and a tiger in da hood! Yeah!',
974 [
975 '10.' => [
976 'search' => '#(a) (Cat|Dog|Tiger)#i',
977 'replace' => '${1} tiny ${2} || ${1} midsized ${2} || ${1} big ${2}',
978 'useOptionSplitReplace' => '1',
979 'useRegExp' => '1'
980 ]
981 ]
982 ]
983 ];
984 }
985
986 /**
987 * Check if stdWrap.replacement and all of its properties work properly
988 *
989 * @test
990 * @dataProvider replacementDataProvider
991 * @param string $content The given input.
992 * @param string $expects The expected result.
993 * @param array $conf The given configuration.
994 * @return void
995 */
996 public function replacement($expects, $content, $conf)
997 {
998 $this->assertSame($expects,
999 $this->subject->_call('replacement', $content, $conf));
1000 }
1001
1002 /**
1003 * Data provider for calcAge.
1004 *
1005 * @return array [$expect, $timestamp, $labels]
1006 */
1007 public function calcAgeDataProvider()
1008 {
1009 return [
1010 'minutes' => [
1011 '2 min', 120, ' min| hrs| days| yrs',
1012 ],
1013 'hours' => [
1014 '2 hrs', 7200, ' min| hrs| days| yrs',
1015 ],
1016 'days' => [
1017 '7 days', 604800, ' min| hrs| days| yrs',
1018 ],
1019 'day with provided singular labels' => [
1020 '1 day', 86400, ' min| hrs| days| yrs| min| hour| day| year',
1021 ],
1022 'years' => [
1023 '45 yrs', 1417997800, ' min| hrs| days| yrs',
1024 ],
1025 'different labels' => [
1026 '2 Minutes', 120, ' Minutes| Hrs| Days| Yrs',
1027 ],
1028 'negative values' => [
1029 '-7 days', -604800, ' min| hrs| days| yrs',
1030 ],
1031 'default label values for wrong label input' => [
1032 '2 min', 121, 10,
1033 ],
1034 'default singular label values for wrong label input' => [
1035 '1 year', 31536000, 10,
1036 ]
1037 ];
1038 }
1039
1040 /**
1041 * Check if calcAge works properly.
1042 *
1043 * @test
1044 * @dataProvider calcAgeDataProvider
1045 * @param int $expect
1046 * @param int $timestamp
1047 * @param string $labels
1048 * @return void
1049 */
1050 public function calcAge($expect, $timestamp, $labels)
1051 {
1052 $this->assertSame($expect,
1053 $this->subject->calcAge($timestamp, $labels));
1054 }
1055
1056 /**
1057 * @return array
1058 */
1059 public function stdWrapReturnsExpectationDataProvider()
1060 {
1061 return [
1062 'Prevent silent bool conversion' => [
1063 '1+1',
1064 [
1065 'prioriCalc.' => [
1066 'wrap' => '|',
1067 ],
1068 ],
1069 '1+1',
1070 ],
1071 ];
1072 }
1073
1074 /**
1075 * @param string $content
1076 * @param array $configuration
1077 * @param string $expectation
1078 * @dataProvider stdWrapReturnsExpectationDataProvider
1079 * @test
1080 */
1081 public function stdWrapReturnsExpectation($content, array $configuration, $expectation)
1082 {
1083 $this->assertSame($expectation, $this->subject->stdWrap($content, $configuration));
1084 }
1085
1086 /**
1087 * Data provider for substring
1088 *
1089 * @return array [$expect, $content, $conf]
1090 */
1091 public function substringDataProvider()
1092 {
1093 return [
1094 'sub -1' => ['g', 'substring', '-1'],
1095 'sub -1,0' => ['g', 'substring', '-1,0'],
1096 'sub -1,-1' => ['', 'substring', '-1,-1'],
1097 'sub -1,1' => ['g', 'substring', '-1,1'],
1098 'sub 0' => ['substring', 'substring', '0'],
1099 'sub 0,0' => ['substring', 'substring', '0,0'],
1100 'sub 0,-1' => ['substrin', 'substring', '0,-1'],
1101 'sub 0,1' => ['s', 'substring', '0,1'],
1102 'sub 1' => ['ubstring', 'substring', '1'],
1103 'sub 1,0' => ['ubstring', 'substring', '1,0'],
1104 'sub 1,-1' => ['ubstrin', 'substring', '1,-1'],
1105 'sub 1,1' => ['u', 'substring', '1,1'],
1106 'sub' => ['substring', 'substring', ''],
1107 ];
1108 }
1109
1110 /**
1111 * Check if substring works properly.
1112 *
1113 * @test
1114 * @dataProvider substringDataProvider
1115 * @param string $expect The expected output.
1116 * @param string $content The given input.
1117 * @param array $conf The given configutation.
1118 * @return void
1119 */
1120 public function substring($expect, $content, $conf)
1121 {
1122 $this->assertSame($expect, $this->subject->substring($content, $conf));
1123 }
1124
1125 ///////////////////////////////
1126 // Tests concerning getData()
1127 ///////////////////////////////
1128
1129 /**
1130 * @return array
1131 */
1132 public function getDataWithTypeGpDataProvider()
1133 {
1134 return [
1135 'Value in get-data' => ['onlyInGet', 'GetValue'],
1136 'Value in post-data' => ['onlyInPost', 'PostValue'],
1137 'Value in post-data overriding get-data' => ['inGetAndPost', 'ValueInPost'],
1138 ];
1139 }
1140
1141 /**
1142 * Checks if getData() works with type "gp"
1143 *
1144 * @test
1145 * @dataProvider getDataWithTypeGpDataProvider
1146 */
1147 public function getDataWithTypeGp($key, $expectedValue)
1148 {
1149 $_GET = [
1150 'onlyInGet' => 'GetValue',
1151 'inGetAndPost' => 'ValueInGet',
1152 ];
1153 $_POST = [
1154 'onlyInPost' => 'PostValue',
1155 'inGetAndPost' => 'ValueInPost',
1156 ];
1157 $this->assertEquals($expectedValue, $this->subject->getData('gp:' . $key));
1158 }
1159
1160 /**
1161 * Checks if getData() works with type "tsfe"
1162 *
1163 * @test
1164 */
1165 public function getDataWithTypeTsfe()
1166 {
1167 $this->assertEquals($GLOBALS['TSFE']->metaCharset, $this->subject->getData('tsfe:metaCharset'));
1168 }
1169
1170 /**
1171 * Checks if getData() works with type "getenv"
1172 *
1173 * @test
1174 */
1175 public function getDataWithTypeGetenv()
1176 {
1177 $envName = $this->getUniqueId('frontendtest');
1178 $value = $this->getUniqueId('someValue');
1179 putenv($envName . '=' . $value);
1180 $this->assertEquals($value, $this->subject->getData('getenv:' . $envName));
1181 }
1182
1183 /**
1184 * Checks if getData() works with type "getindpenv"
1185 *
1186 * @test
1187 */
1188 public function getDataWithTypeGetindpenv()
1189 {
1190 $this->subject->expects($this->once())->method('getEnvironmentVariable')
1191 ->with($this->equalTo('SCRIPT_FILENAME'))->will($this->returnValue('dummyPath'));
1192 $this->assertEquals('dummyPath', $this->subject->getData('getindpenv:SCRIPT_FILENAME'));
1193 }
1194
1195 /**
1196 * Checks if getData() works with type "field"
1197 *
1198 * @test
1199 */
1200 public function getDataWithTypeField()
1201 {
1202 $key = 'someKey';
1203 $value = 'someValue';
1204 $field = [$key => $value];
1205
1206 $this->assertEquals($value, $this->subject->getData('field:' . $key, $field));
1207 }
1208
1209 /**
1210 * Checks if getData() works with type "field" of the field content
1211 * is multi-dimensional (e.g. an array)
1212 *
1213 * @test
1214 */
1215 public function getDataWithTypeFieldAndFieldIsMultiDimensional()
1216 {
1217 $key = 'somekey|level1|level2';
1218 $value = 'somevalue';
1219 $field = ['somekey' => ['level1' => ['level2' => 'somevalue']]];
1220
1221 $this->assertEquals($value, $this->subject->getData('field:' . $key, $field));
1222 }
1223
1224 /**
1225 * Basic check if getData gets the uid of a file object
1226 *
1227 * @test
1228 */
1229 public function getDataWithTypeFileReturnsUidOfFileObject()
1230 {
1231 $uid = $this->getUniqueId();
1232 $file = $this->createMock(File::class);
1233 $file->expects($this->once())->method('getUid')->will($this->returnValue($uid));
1234 $this->subject->setCurrentFile($file);
1235 $this->assertEquals($uid, $this->subject->getData('file:current:uid'));
1236 }
1237
1238 /**
1239 * Checks if getData() works with type "parameters"
1240 *
1241 * @test
1242 */
1243 public function getDataWithTypeParameters()
1244 {
1245 $key = $this->getUniqueId('someKey');
1246 $value = $this->getUniqueId('someValue');
1247 $this->subject->parameters[$key] = $value;
1248
1249 $this->assertEquals($value, $this->subject->getData('parameters:' . $key));
1250 }
1251
1252 /**
1253 * Checks if getData() works with type "register"
1254 *
1255 * @test
1256 */
1257 public function getDataWithTypeRegister()
1258 {
1259 $key = $this->getUniqueId('someKey');
1260 $value = $this->getUniqueId('someValue');
1261 $GLOBALS['TSFE']->register[$key] = $value;
1262
1263 $this->assertEquals($value, $this->subject->getData('register:' . $key));
1264 }
1265
1266 /**
1267 * Checks if getData() works with type "level"
1268 *
1269 * @test
1270 */
1271 public function getDataWithTypeLevel()
1272 {
1273 $rootline = [
1274 0 => ['uid' => 1, 'title' => 'title1'],
1275 1 => ['uid' => 2, 'title' => 'title2'],
1276 2 => ['uid' => 3, 'title' => 'title3'],
1277 ];
1278
1279 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1280 $this->assertEquals(2, $this->subject->getData('level'));
1281 }
1282
1283 /**
1284 * Checks if getData() works with type "global"
1285 *
1286 * @test
1287 */
1288 public function getDataWithTypeGlobal()
1289 {
1290 $this->assertEquals($GLOBALS['TSFE']->metaCharset, $this->subject->getData('global:TSFE|metaCharset'));
1291 }
1292
1293 /**
1294 * Checks if getData() works with type "leveltitle"
1295 *
1296 * @test
1297 */
1298 public function getDataWithTypeLeveltitle()
1299 {
1300 $rootline = [
1301 0 => ['uid' => 1, 'title' => 'title1'],
1302 1 => ['uid' => 2, 'title' => 'title2'],
1303 2 => ['uid' => 3, 'title' => ''],
1304 ];
1305
1306 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1307 $this->assertEquals('', $this->subject->getData('leveltitle:-1'));
1308 // since "title3" is not set, it will slide to "title2"
1309 $this->assertEquals('title2', $this->subject->getData('leveltitle:-1,slide'));
1310 }
1311
1312 /**
1313 * Checks if getData() works with type "levelmedia"
1314 *
1315 * @test
1316 */
1317 public function getDataWithTypeLevelmedia()
1318 {
1319 $rootline = [
1320 0 => ['uid' => 1, 'title' => 'title1', 'media' => 'media1'],
1321 1 => ['uid' => 2, 'title' => 'title2', 'media' => 'media2'],
1322 2 => ['uid' => 3, 'title' => 'title3', 'media' => ''],
1323 ];
1324
1325 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1326 $this->assertEquals('', $this->subject->getData('levelmedia:-1'));
1327 // since "title3" is not set, it will slide to "title2"
1328 $this->assertEquals('media2', $this->subject->getData('levelmedia:-1,slide'));
1329 }
1330
1331 /**
1332 * Checks if getData() works with type "leveluid"
1333 *
1334 * @test
1335 */
1336 public function getDataWithTypeLeveluid()
1337 {
1338 $rootline = [
1339 0 => ['uid' => 1, 'title' => 'title1'],
1340 1 => ['uid' => 2, 'title' => 'title2'],
1341 2 => ['uid' => 3, 'title' => 'title3'],
1342 ];
1343
1344 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1345 $this->assertEquals(3, $this->subject->getData('leveluid:-1'));
1346 // every element will have a uid - so adding slide doesn't really make sense, just for completeness
1347 $this->assertEquals(3, $this->subject->getData('leveluid:-1,slide'));
1348 }
1349
1350 /**
1351 * Checks if getData() works with type "levelfield"
1352 *
1353 * @test
1354 */
1355 public function getDataWithTypeLevelfield()
1356 {
1357 $rootline = [
1358 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1359 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1360 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => ''],
1361 ];
1362
1363 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1364 $this->assertEquals('', $this->subject->getData('levelfield:-1,testfield'));
1365 $this->assertEquals('field2', $this->subject->getData('levelfield:-1,testfield,slide'));
1366 }
1367
1368 /**
1369 * Checks if getData() works with type "fullrootline"
1370 *
1371 * @test
1372 */
1373 public function getDataWithTypeFullrootline()
1374 {
1375 $rootline1 = [
1376 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1377 ];
1378 $rootline2 = [
1379 0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1380 1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1381 2 => ['uid' => 3, 'title' => 'title3', 'testfield' => 'field3'],
1382 ];
1383
1384 $GLOBALS['TSFE']->tmpl->rootLine = $rootline1;
1385 $GLOBALS['TSFE']->rootLine = $rootline2;
1386 $this->assertEquals('field2', $this->subject->getData('fullrootline:-1,testfield'));
1387 }
1388
1389 /**
1390 * Checks if getData() works with type "date"
1391 *
1392 * @test
1393 */
1394 public function getDataWithTypeDate()
1395 {
1396 $format = 'Y-M-D';
1397 $defaultFormat = 'd/m Y';
1398
1399 $this->assertEquals(date($format, $GLOBALS['EXEC_TIME']), $this->subject->getData('date:' . $format));
1400 $this->assertEquals(date($defaultFormat, $GLOBALS['EXEC_TIME']), $this->subject->getData('date'));
1401 }
1402
1403 /**
1404 * Checks if getData() works with type "page"
1405 *
1406 * @test
1407 */
1408 public function getDataWithTypePage()
1409 {
1410 $uid = rand();
1411 $GLOBALS['TSFE']->page['uid'] = $uid;
1412 $this->assertEquals($uid, $this->subject->getData('page:uid'));
1413 }
1414
1415 /**
1416 * Checks if getData() works with type "current"
1417 *
1418 * @test
1419 */
1420 public function getDataWithTypeCurrent()
1421 {
1422 $key = $this->getUniqueId('someKey');
1423 $value = $this->getUniqueId('someValue');
1424 $this->subject->data[$key] = $value;
1425 $this->subject->currentValKey = $key;
1426 $this->assertEquals($value, $this->subject->getData('current'));
1427 }
1428
1429 /**
1430 * Checks if getData() works with type "db"
1431 *
1432 * @test
1433 */
1434 public function getDataWithTypeDb()
1435 {
1436 $dummyRecord = ['uid' => 5, 'title' => 'someTitle'];
1437
1438 $GLOBALS['TSFE']->sys_page->expects($this->atLeastOnce())->method('getRawRecord')->with('tt_content', '106')->will($this->returnValue($dummyRecord));
1439 $this->assertEquals($dummyRecord['title'], $this->subject->getData('db:tt_content:106:title'));
1440 }
1441
1442 /**
1443 * Checks if getData() works with type "lll"
1444 *
1445 * @test
1446 */
1447 public function getDataWithTypeLll()
1448 {
1449 $key = $this->getUniqueId('someKey');
1450 $value = $this->getUniqueId('someValue');
1451 $language = $this->getUniqueId('someLanguage');
1452 $GLOBALS['TSFE']->LL_labels_cache[$language]['LLL:' . $key] = $value;
1453 $GLOBALS['TSFE']->lang = $language;
1454
1455 $this->assertEquals($value, $this->subject->getData('lll:' . $key));
1456 }
1457
1458 /**
1459 * Checks if getData() works with type "path"
1460 *
1461 * @test
1462 */
1463 public function getDataWithTypePath()
1464 {
1465 $filenameIn = $this->getUniqueId('someValue');
1466 $filenameOut = $this->getUniqueId('someValue');
1467 $this->templateServiceMock->expects($this->atLeastOnce())->method('getFileName')->with($filenameIn)->will($this->returnValue($filenameOut));
1468 $this->assertEquals($filenameOut, $this->subject->getData('path:' . $filenameIn));
1469 }
1470
1471 /**
1472 * Checks if getData() works with type "parentRecordNumber"
1473 *
1474 * @test
1475 */
1476 public function getDataWithTypeParentRecordNumber()
1477 {
1478 $recordNumber = rand();
1479 $this->subject->parentRecordNumber = $recordNumber;
1480 $this->assertEquals($recordNumber, $this->subject->getData('cobj:parentRecordNumber'));
1481 }
1482
1483 /**
1484 * Checks if getData() works with type "debug:rootLine"
1485 *
1486 * @test
1487 */
1488 public function getDataWithTypeDebugRootline()
1489 {
1490 $rootline = [
1491 0 => ['uid' => 1, 'title' => 'title1'],
1492 1 => ['uid' => 2, 'title' => 'title2'],
1493 2 => ['uid' => 3, 'title' => ''],
1494 ];
1495 $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)';
1496 $GLOBALS['TSFE']->tmpl->rootLine = $rootline;
1497
1498 DebugUtility::useAnsiColor(false);
1499 $result = $this->subject->getData('debug:rootLine');
1500 $cleanedResult = str_replace("\r", '', $result);
1501 $cleanedResult = str_replace("\n", '', $cleanedResult);
1502 $cleanedResult = str_replace("\t", '', $cleanedResult);
1503 $cleanedResult = str_replace(' ', '', $cleanedResult);
1504
1505 $this->assertEquals($expectedResult, $cleanedResult);
1506 }
1507
1508 /**
1509 * Checks if getData() works with type "debug:fullRootLine"
1510 *
1511 * @test
1512 */
1513 public function getDataWithTypeDebugFullRootline()
1514 {
1515 $rootline = [
1516 0 => ['uid' => 1, 'title' => 'title1'],
1517 1 => ['uid' => 2, 'title' => 'title2'],
1518 2 => ['uid' => 3, 'title' => ''],
1519 ];
1520 $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)';
1521 $GLOBALS['TSFE']->rootLine = $rootline;
1522
1523 DebugUtility::useAnsiColor(false);
1524 $result = $this->subject->getData('debug:fullRootLine');
1525 $cleanedResult = str_replace("\r", '', $result);
1526 $cleanedResult = str_replace("\n", '', $cleanedResult);
1527 $cleanedResult = str_replace("\t", '', $cleanedResult);
1528 $cleanedResult = str_replace(' ', '', $cleanedResult);
1529
1530 $this->assertEquals($expectedResult, $cleanedResult);
1531 }
1532
1533 /**
1534 * Checks if getData() works with type "debug:data"
1535 *
1536 * @test
1537 */
1538 public function getDataWithTypeDebugData()
1539 {
1540 $key = $this->getUniqueId('someKey');
1541 $value = $this->getUniqueId('someValue');
1542 $this->subject->data = [$key => $value];
1543
1544 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1545
1546 DebugUtility::useAnsiColor(false);
1547 $result = $this->subject->getData('debug:data');
1548 $cleanedResult = str_replace("\r", '', $result);
1549 $cleanedResult = str_replace("\n", '', $cleanedResult);
1550 $cleanedResult = str_replace("\t", '', $cleanedResult);
1551 $cleanedResult = str_replace(' ', '', $cleanedResult);
1552
1553 $this->assertEquals($expectedResult, $cleanedResult);
1554 }
1555
1556 /**
1557 * Checks if getData() works with type "debug:register"
1558 *
1559 * @test
1560 */
1561 public function getDataWithTypeDebugRegister()
1562 {
1563 $key = $this->getUniqueId('someKey');
1564 $value = $this->getUniqueId('someValue');
1565 $GLOBALS['TSFE']->register = [$key => $value];
1566
1567 $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1568
1569 DebugUtility::useAnsiColor(false);
1570 $result = $this->subject->getData('debug:register');
1571 $cleanedResult = str_replace("\r", '', $result);
1572 $cleanedResult = str_replace("\n", '', $cleanedResult);
1573 $cleanedResult = str_replace("\t", '', $cleanedResult);
1574 $cleanedResult = str_replace(' ', '', $cleanedResult);
1575
1576 $this->assertEquals($expectedResult, $cleanedResult);
1577 }
1578
1579 /**
1580 * Checks if getData() works with type "data:page"
1581 *
1582 * @test
1583 */
1584 public function getDataWithTypeDebugPage()
1585 {
1586 $uid = rand();
1587 $GLOBALS['TSFE']->page = ['uid' => $uid];
1588
1589 $expectedResult = 'array(1item)uid=>' . $uid . '(integer)';
1590
1591 DebugUtility::useAnsiColor(false);
1592 $result = $this->subject->getData('debug:page');
1593 $cleanedResult = str_replace("\r", '', $result);
1594 $cleanedResult = str_replace("\n", '', $cleanedResult);
1595 $cleanedResult = str_replace("\t", '', $cleanedResult);
1596 $cleanedResult = str_replace(' ', '', $cleanedResult);
1597
1598 $this->assertEquals($expectedResult, $cleanedResult);
1599 }
1600
1601 /**
1602 * @test
1603 */
1604 public function aTagParamsHasLeadingSpaceIfNotEmpty()
1605 {
1606 $aTagParams = $this->subject->getATagParams(['ATagParams' => 'data-test="testdata"']);
1607 $this->assertEquals(' data-test="testdata"', $aTagParams);
1608 }
1609
1610 /**
1611 * @test
1612 */
1613 public function aTagParamsHaveSpaceBetweenLocalAndGlobalParams()
1614 {
1615 $GLOBALS['TSFE']->ATagParams = 'data-global="dataglobal"';
1616 $aTagParams = $this->subject->getATagParams(['ATagParams' => 'data-test="testdata"']);
1617 $this->assertEquals(' data-global="dataglobal" data-test="testdata"', $aTagParams);
1618 }
1619
1620 /**
1621 * @test
1622 */
1623 public function aTagParamsHasNoLeadingSpaceIfEmpty()
1624 {
1625 // make sure global ATagParams are empty
1626 $GLOBALS['TSFE']->ATagParams = '';
1627 $aTagParams = $this->subject->getATagParams(['ATagParams' => '']);
1628 $this->assertEquals('', $aTagParams);
1629 }
1630
1631 /**
1632 * @return array
1633 */
1634 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider()
1635 {
1636 return [
1637 [null, null],
1638 ['', null],
1639 ['', []],
1640 ['fooo', ['foo' => 'bar']]
1641 ];
1642 }
1643
1644 /**
1645 * Make sure that the rendering falls back to the classic <img style if nothing else is found
1646 *
1647 * @test
1648 * @dataProvider getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider
1649 * @param string $key
1650 * @param array $configuration
1651 */
1652 public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFound($key, $configuration)
1653 {
1654 $defaultImgTagTemplate = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
1655 $result = $this->subject->getImageTagTemplate($key, $configuration);
1656 $this->assertEquals($result, $defaultImgTagTemplate);
1657 }
1658
1659 /**
1660 * @return array
1661 */
1662 public function getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider()
1663 {
1664 return [
1665 [
1666 'foo',
1667 [
1668 'layout.' => [
1669 'foo.' => [
1670 'element' => '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1671 ]
1672 ]
1673 ],
1674 '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
1675 ]
1676
1677 ];
1678 }
1679
1680 /**
1681 * Assure if a layoutKey and layout is given the selected layout is returned
1682 *
1683 * @test
1684 * @dataProvider getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider
1685 * @param string $key
1686 * @param array $configuration
1687 * @param string $expectation
1688 */
1689 public function getImageTagTemplateReturnTemplateElementIdentifiedByKey($key, $configuration, $expectation)
1690 {
1691 $result = $this->subject->getImageTagTemplate($key, $configuration);
1692 $this->assertEquals($result, $expectation);
1693 }
1694
1695 /**
1696 * @return array
1697 */
1698 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider()
1699 {
1700 return [
1701 [null, null, null],
1702 ['foo', null, null],
1703 ['foo', ['sourceCollection.' => 1], 'bar']
1704 ];
1705 }
1706
1707 /**
1708 * Make sure the source collection is empty if no valid configuration or source collection is defined
1709 *
1710 * @test
1711 * @dataProvider getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider
1712 * @param string $layoutKey
1713 * @param array $configuration
1714 * @param string $file
1715 */
1716 public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefined($layoutKey, $configuration, $file)
1717 {
1718 $result = $this->subject->getImageSourceCollection($layoutKey, $configuration, $file);
1719 $this->assertSame($result, '');
1720 }
1721
1722 /**
1723 * Make sure the generation of subimages calls the generation of the subimages and uses the layout -> source template
1724 *
1725 * @test
1726 */
1727 public function getImageSourceCollectionRendersDefinedSources()
1728 {
1729 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
1730 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
1731 ->setMethods(['stdWrap', 'getImgResource'])
1732 ->getMock();
1733
1734 $cObj->start([], 'tt_content');
1735
1736 $layoutKey = 'test';
1737
1738 $configuration = [
1739 'layoutKey' => 'test',
1740 'layout.' => [
1741 'test.' => [
1742 'element' => '<img ###SRC### ###SRCCOLLECTION### ###SELFCLOSINGTAGSLASH###>',
1743 'source' => '---###SRC###---'
1744 ]
1745 ],
1746 'sourceCollection.' => [
1747 '1.' => [
1748 'width' => '200'
1749 ]
1750 ]
1751 ];
1752
1753 $file = 'testImageName';
1754
1755 // Avoid calling of stdWrap
1756 $cObj
1757 ->expects($this->any())
1758 ->method('stdWrap')
1759 ->will($this->returnArgument(0));
1760
1761 // Avoid calling of imgResource
1762 $cObj
1763 ->expects($this->exactly(1))
1764 ->method('getImgResource')
1765 ->with($this->equalTo('testImageName'))
1766 ->will($this->returnValue([100, 100, null, 'bar']));
1767
1768 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
1769
1770 $this->assertEquals('---bar---', $result);
1771 }
1772
1773 /**
1774 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyDefault test
1775 *
1776 * @return array multi-dimensional array with the second level like this:
1777 * @see getImageSourceCollectionRendersDefinedLayoutKeyDefault
1778 */
1779 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider()
1780 {
1781 /**
1782 * @see css_styled_content/static/setup.txt
1783 */
1784 $sourceCollectionArray = [
1785 'small.' => [
1786 'width' => 200,
1787 'srcsetCandidate' => '600w',
1788 'mediaQuery' => '(max-device-width: 600px)',
1789 'dataKey' => 'small',
1790 ],
1791 'smallRetina.' => [
1792 'if.directReturn' => 0,
1793 'width' => 200,
1794 'pixelDensity' => '2',
1795 'srcsetCandidate' => '600w 2x',
1796 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
1797 'dataKey' => 'smallRetina',
1798 ]
1799 ];
1800 return [
1801 [
1802 'default',
1803 [
1804 'layoutKey' => 'default',
1805 'layout.' => [
1806 'default.' => [
1807 'element' => '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>',
1808 'source' => ''
1809 ]
1810 ],
1811 'sourceCollection.' => $sourceCollectionArray
1812 ]
1813 ],
1814 ];
1815 }
1816
1817 /**
1818 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
1819 *
1820 * @test
1821 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider
1822 * @param string $layoutKey
1823 * @param array $configuration
1824 */
1825 public function getImageSourceCollectionRendersDefinedLayoutKeyDefault($layoutKey, $configuration)
1826 {
1827 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
1828 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
1829 ->setMethods(['stdWrap', 'getImgResource'])
1830 ->getMock();
1831
1832 $cObj->start([], 'tt_content');
1833
1834 $file = 'testImageName';
1835
1836 // Avoid calling of stdWrap
1837 $cObj
1838 ->expects($this->any())
1839 ->method('stdWrap')
1840 ->will($this->returnArgument(0));
1841
1842 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
1843
1844 $this->assertEmpty($result);
1845 }
1846
1847 /**
1848 * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyData test
1849 *
1850 * @return array multi-dimensional array with the second level like this:
1851 * @see getImageSourceCollectionRendersDefinedLayoutKeyData
1852 */
1853 public function getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider()
1854 {
1855 /**
1856 * @see css_styled_content/static/setup.txt
1857 */
1858 $sourceCollectionArray = [
1859 'small.' => [
1860 'width' => 200,
1861 'srcsetCandidate' => '600w',
1862 'mediaQuery' => '(max-device-width: 600px)',
1863 'dataKey' => 'small',
1864 ],
1865 'smallRetina.' => [
1866 'if.directReturn' => 1,
1867 'width' => 200,
1868 'pixelDensity' => '2',
1869 'srcsetCandidate' => '600w 2x',
1870 'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
1871 'dataKey' => 'smallRetina',
1872 ]
1873 ];
1874 return [
1875 [
1876 'srcset',
1877 [
1878 'layoutKey' => 'srcset',
1879 'layout.' => [
1880 'srcset.' => [
1881 'element' => '<img src="###SRC###" srcset="###SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
1882 'source' => '|*|###SRC### ###SRCSETCANDIDATE###,|*|###SRC### ###SRCSETCANDIDATE###'
1883 ]
1884 ],
1885 'sourceCollection.' => $sourceCollectionArray
1886 ],
1887 'xhtml_strict',
1888 'bar-file.jpg 600w,bar-file.jpg 600w 2x',
1889 ],
1890 [
1891 'picture',
1892 [
1893 'layoutKey' => 'picture',
1894 'layout.' => [
1895 'picture.' => [
1896 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
1897 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
1898 ]
1899 ],
1900 'sourceCollection.' => $sourceCollectionArray,
1901 ],
1902 'xhtml_strict',
1903 '<source src="bar-file.jpg" media="(max-device-width: 600px)" /><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)" />',
1904 ],
1905 [
1906 'picture',
1907 [
1908 'layoutKey' => 'picture',
1909 'layout.' => [
1910 'picture.' => [
1911 'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
1912 'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
1913 ]
1914 ],
1915 'sourceCollection.' => $sourceCollectionArray,
1916 ],
1917 '',
1918 '<source src="bar-file.jpg" media="(max-device-width: 600px)"><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)">',
1919 ],
1920 [
1921 'data',
1922 [
1923 'layoutKey' => 'data',
1924 'layout.' => [
1925 'data.' => [
1926 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
1927 'source' => 'data-###DATAKEY###="###SRC###"'
1928 ]
1929 ],
1930 'sourceCollection.' => $sourceCollectionArray
1931 ],
1932 'xhtml_strict',
1933 'data-small="bar-file.jpg"data-smallRetina="bar-file.jpg"',
1934 ],
1935 ];
1936 }
1937
1938 /**
1939 * Make sure the generation of subimages renders the expected HTML Code for the sourceset
1940 *
1941 * @test
1942 * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider
1943 * @param string $layoutKey
1944 * @param array $configuration
1945 * @param string $xhtmlDoctype
1946 * @param string $expectedHtml
1947 */
1948 public function getImageSourceCollectionRendersDefinedLayoutKeyData($layoutKey, $configuration, $xhtmlDoctype, $expectedHtml)
1949 {
1950 /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
1951 $cObj = $this->getMockBuilder(ContentObjectRenderer::class)
1952 ->setMethods(['stdWrap', 'getImgResource'])
1953 ->getMock();
1954
1955 $cObj->start([], 'tt_content');
1956
1957 $file = 'testImageName';
1958
1959 $GLOBALS['TSFE']->xhtmlDoctype = $xhtmlDoctype;
1960
1961 // Avoid calling of stdWrap
1962 $cObj
1963 ->expects($this->any())
1964 ->method('stdWrap')
1965 ->will($this->returnArgument(0));
1966
1967 // Avoid calling of imgResource
1968 $cObj
1969 ->expects($this->exactly(2))
1970 ->method('getImgResource')
1971 ->with($this->equalTo('testImageName'))
1972 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
1973
1974 $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
1975
1976 $this->assertEquals($expectedHtml, $result);
1977 }
1978
1979 /**
1980 * Make sure the hook in get sourceCollection is called
1981 *
1982 * @test
1983 */
1984 public function getImageSourceCollectionHookCalled()
1985 {
1986 $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class,
1987 ['getResourceFactory', 'stdWrap', 'getImgResource']
1988 );
1989 $this->subject->start([], 'tt_content');
1990
1991 // Avoid calling stdwrap and getImgResource
1992 $this->subject->expects($this->any())
1993 ->method('stdWrap')
1994 ->will($this->returnArgument(0));
1995
1996 $this->subject->expects($this->any())
1997 ->method('getImgResource')
1998 ->will($this->returnValue([100, 100, null, 'bar-file.jpg']));
1999
2000 $resourceFactory = $this->createMock(ResourceFactory::class);
2001 $this->subject->expects($this->any())->method('getResourceFactory')->will($this->returnValue($resourceFactory));
2002
2003 $className = $this->getUniqueId('tx_coretest_getImageSourceCollectionHookCalled');
2004 $getImageSourceCollectionHookMock = $this->getMockBuilder(
2005 ContentObjectOneSourceCollectionHookInterface::class)
2006 ->setMethods(['getOneSourceCollection'])
2007 ->setMockClassName($className)
2008 ->getMock();
2009 GeneralUtility::addInstance($className, $getImageSourceCollectionHookMock);
2010 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'][] = $className;
2011
2012 $getImageSourceCollectionHookMock
2013 ->expects($this->exactly(1))
2014 ->method('getOneSourceCollection')
2015 ->will($this->returnCallback([$this, 'isGetOneSourceCollectionCalledCallback']));
2016
2017 $configuration = [
2018 'layoutKey' => 'data',
2019 'layout.' => [
2020 'data.' => [
2021 'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
2022 'source' => 'data-###DATAKEY###="###SRC###"'
2023 ]
2024 ],
2025 'sourceCollection.' => [
2026 'small.' => [
2027 'width' => 200,
2028 'srcsetCandidate' => '600w',
2029 'mediaQuery' => '(max-device-width: 600px)',
2030 'dataKey' => 'small',
2031 ],
2032 ],
2033 ];
2034
2035 $result = $this->subject->getImageSourceCollection('data', $configuration, $this->getUniqueId('testImage-'));
2036
2037 $this->assertSame($result, 'isGetOneSourceCollectionCalledCallback');
2038 }
2039
2040 /**
2041 * Handles the arguments that have been sent to the getImgResource hook.
2042 *
2043 * @param array $sourceRenderConfiguration
2044 * @param array $sourceConfiguration
2045 * @param $oneSourceCollection
2046 * @param $parent
2047 * @return string
2048 * @see getImageSourceCollectionHookCalled
2049 */
2050 public function isGetOneSourceCollectionCalledCallback($sourceRenderConfiguration, $sourceConfiguration, $oneSourceCollection, $parent)
2051 {
2052 $this->assertTrue(is_array($sourceRenderConfiguration));
2053 $this->assertTrue(is_array($sourceConfiguration));
2054 return 'isGetOneSourceCollectionCalledCallback';
2055 }
2056
2057 /**
2058 * @param string $expected The expected URL
2059 * @param string $url The URL to parse and manipulate
2060 * @param array $configuration The configuration array
2061 * @test
2062 * @dataProvider forceAbsoluteUrlReturnsCorrectAbsoluteUrlDataProvider
2063 */
2064 public function forceAbsoluteUrlReturnsCorrectAbsoluteUrl($expected, $url, array $configuration)
2065 {
2066 // Force hostname
2067 $this->subject->expects($this->any())->method('getEnvironmentVariable')->will($this->returnValueMap(
2068 [
2069 ['HTTP_HOST', 'localhost'],
2070 ['TYPO3_SITE_PATH', '/'],
2071 ]
2072 ));
2073 $GLOBALS['TSFE']->absRefPrefix = '';
2074
2075 $this->assertEquals($expected, $this->subject->_call('forceAbsoluteUrl', $url, $configuration));
2076 }
2077
2078 /**
2079 * @return array The test data for forceAbsoluteUrlReturnsAbsoluteUrl
2080 */
2081 public function forceAbsoluteUrlReturnsCorrectAbsoluteUrlDataProvider()
2082 {
2083 return [
2084 'Missing forceAbsoluteUrl leaves URL untouched' => [
2085 'foo',
2086 'foo',
2087 []
2088 ],
2089 'Absolute URL stays unchanged' => [
2090 'http://example.org/',
2091 'http://example.org/',
2092 [
2093 'forceAbsoluteUrl' => '1'
2094 ]
2095 ],
2096 'Absolute URL stays unchanged 2' => [
2097 'http://example.org/resource.html',
2098 'http://example.org/resource.html',
2099 [
2100 'forceAbsoluteUrl' => '1'
2101 ]
2102 ],
2103 'Scheme and host w/o ending slash stays unchanged' => [
2104 'http://example.org',
2105 'http://example.org',
2106 [
2107 'forceAbsoluteUrl' => '1'
2108 ]
2109 ],
2110 'Scheme can be forced' => [
2111 'typo3://example.org',
2112 'http://example.org',
2113 [
2114 'forceAbsoluteUrl' => '1',
2115 'forceAbsoluteUrl.' => [
2116 'scheme' => 'typo3'
2117 ]
2118 ]
2119 ],
2120 'Relative path old-style' => [
2121 'http://localhost/fileadmin/dummy.txt',
2122 '/fileadmin/dummy.txt',
2123 [
2124 'forceAbsoluteUrl' => '1',
2125 ]
2126 ],
2127 'Relative path' => [
2128 'http://localhost/fileadmin/dummy.txt',
2129 'fileadmin/dummy.txt',
2130 [
2131 'forceAbsoluteUrl' => '1',
2132 ]
2133 ],
2134 'Scheme can be forced with pseudo-relative path' => [
2135 'typo3://localhost/fileadmin/dummy.txt',
2136 '/fileadmin/dummy.txt',
2137 [
2138 'forceAbsoluteUrl' => '1',
2139 'forceAbsoluteUrl.' => [
2140 'scheme' => 'typo3'
2141 ]
2142 ]
2143 ],
2144 'Hostname only is not treated as valid absolute URL' => [
2145 'http://localhost/example.org',
2146 'example.org',
2147 [
2148 'forceAbsoluteUrl' => '1'
2149 ]
2150 ],
2151 'Scheme and host is added to local file path' => [
2152 'typo3://localhost/fileadmin/my.pdf',
2153 'fileadmin/my.pdf',
2154 [
2155 'forceAbsoluteUrl' => '1',
2156 'forceAbsoluteUrl.' => [
2157 'scheme' => 'typo3'
2158 ]
2159 ]
2160 ]
2161 ];
2162 }
2163
2164 /**
2165 * @test
2166 */
2167 public function renderingContentObjectThrowsException()
2168 {
2169 $this->expectException(\LogicException::class);
2170 $this->expectExceptionCode(1414513947);
2171 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2172 $this->subject->render($contentObjectFixture, []);
2173 }
2174
2175 /**
2176 * @test
2177 */
2178 public function exceptionHandlerIsEnabledByDefaultInProductionContext()
2179 {
2180 $backupApplicationContext = GeneralUtility::getApplicationContext();
2181 Fixtures\GeneralUtilityFixture::setApplicationContext(new ApplicationContext('Production'));
2182
2183 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2184 $this->subject->render($contentObjectFixture, []);
2185
2186 Fixtures\GeneralUtilityFixture::setApplicationContext($backupApplicationContext);
2187 }
2188
2189 /**
2190 * @test
2191 */
2192 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredLocally()
2193 {
2194 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2195
2196 $configuration = [
2197 'exceptionHandler' => '1'
2198 ];
2199 $this->subject->render($contentObjectFixture, $configuration);
2200 }
2201
2202 /**
2203 * @test
2204 */
2205 public function renderingContentObjectDoesNotThrowExceptionIfExceptionHandlerIsConfiguredGlobally()
2206 {
2207 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2208
2209 $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
2210 $this->subject->render($contentObjectFixture, []);
2211 }
2212
2213 /**
2214 * @test
2215 */
2216 public function globalExceptionHandlerConfigurationCanBeOverriddenByLocalConfiguration()
2217 {
2218 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2219 $this->expectException(\LogicException::class);
2220 $this->expectExceptionCode(1414513947);
2221 $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
2222 $configuration = [
2223 'exceptionHandler' => '0'
2224 ];
2225 $this->subject->render($contentObjectFixture, $configuration);
2226 }
2227
2228 /**
2229 * @test
2230 */
2231 public function renderedErrorMessageCanBeCustomized()
2232 {
2233 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2234
2235 $configuration = [
2236 'exceptionHandler' => '1',
2237 'exceptionHandler.' => [
2238 'errorMessage' => 'New message for testing',
2239 ]
2240 ];
2241
2242 $this->assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
2243 }
2244
2245 /**
2246 * @test
2247 */
2248 public function localConfigurationOverridesGlobalConfiguration()
2249 {
2250 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2251
2252 $this->frontendControllerMock
2253 ->config['config']['contentObjectExceptionHandler.'] = [
2254 'errorMessage' => 'Global message for testing',
2255 ];
2256 $configuration = [
2257 'exceptionHandler' => '1',
2258 'exceptionHandler.' => [
2259 'errorMessage' => 'New message for testing',
2260 ]
2261 ];
2262
2263 $this->assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
2264 }
2265
2266 /**
2267 * @test
2268 */
2269 public function specificExceptionsCanBeIgnoredByExceptionHandler()
2270 {
2271 $contentObjectFixture = $this->createContentObjectThrowingExceptionFixture();
2272
2273 $configuration = [
2274 'exceptionHandler' => '1',
2275 'exceptionHandler.' => [
2276 'ignoreCodes.' => ['10.' => '1414513947'],
2277 ]
2278 ];
2279 $this->expectException(\LogicException::class);
2280 $this->expectExceptionCode(1414513947);
2281 $this->subject->render($contentObjectFixture, $configuration);
2282 }
2283
2284 /**
2285 * @return \PHPUnit_Framework_MockObject_MockObject | AbstractContentObject
2286 */
2287 protected function createContentObjectThrowingExceptionFixture()
2288 {
2289 $contentObjectFixture = $this->getMockBuilder(AbstractContentObject::class)
2290 ->setConstructorArgs([$this->subject])
2291 ->getMock();
2292 $contentObjectFixture->expects($this->once())
2293 ->method('render')
2294 ->willReturnCallback(function () {
2295 throw new \LogicException('Exception during rendering', 1414513947);
2296 });
2297 return $contentObjectFixture;
2298 }
2299
2300 /**
2301 * @test
2302 */
2303 public function forceAbsoluteUrlReturnsCorrectAbsoluteUrlWithSubfolder()
2304 {
2305 // Force hostname and subfolder
2306 $this->subject->expects($this->any())->method('getEnvironmentVariable')->will($this->returnValueMap(
2307 [
2308 ['HTTP_HOST', 'localhost'],
2309 ['TYPO3_SITE_PATH', '/subfolder/'],
2310 ]
2311 ));
2312
2313 $expected = 'http://localhost/subfolder/fileadmin/my.pdf';
2314 $url = 'fileadmin/my.pdf';
2315 $configuration = [
2316 'forceAbsoluteUrl' => '1'
2317 ];
2318
2319 $this->assertEquals($expected, $this->subject->_call('forceAbsoluteUrl', $url, $configuration));
2320 }
2321
2322 /**
2323 * @return array
2324 */
2325 protected function getLibParseTarget()
2326 {
2327 return [
2328 'override' => '',
2329 'override.' => [
2330 'if.' => [
2331 'isTrue.' => [
2332 'data' => 'TSFE:dtdAllowsFrames',
2333 ],
2334 ],
2335 ],
2336 ];
2337 }
2338
2339 /**
2340 * @return array
2341 */
2342 protected function getLibParseFunc()
2343 {
2344 return [
2345 'makelinks' => '1',
2346 'makelinks.' => [
2347 'http.' => [
2348 'keep' => '{$styles.content.links.keep}',
2349 'extTarget' => '',
2350 'extTarget.' => $this->getLibParseTarget(),
2351 'mailto.' => [
2352 'keep' => 'path',
2353 ],
2354 ],
2355 ],
2356 'tags' => [
2357 'link' => 'TEXT',
2358 'link.' => [
2359 'current' => '1',
2360 'typolink.' => [
2361 'parameter.' => [
2362 'data' => 'parameters : allParams',
2363 ],
2364 'extTarget.' => $this->getLibParseTarget(),
2365 'target.' => $this->getLibParseTarget(),
2366 ],
2367 'parseFunc.' => [
2368 'constants' => '1',
2369 ],
2370 ],
2371 ],
2372
2373 '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',
2374 'denyTags' => '*',
2375 'sword' => '<span class="csc-sword">|</span>',
2376 'constants' => '1',
2377 'nonTypoTagStdWrap.' => [
2378 'HTMLparser' => '1',
2379 'HTMLparser.' => [
2380 'keepNonMatchedTags' => '1',
2381 'htmlSpecialChars' => '2',
2382 ],
2383 ],
2384 ];
2385 }
2386
2387 /**
2388 * @return array
2389 */
2390 protected function getLibParseFunc_RTE()
2391 {
2392 return [
2393 'parseFunc' => '',
2394 'parseFunc.' => [
2395 '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',
2396 'constants' => '1',
2397 'denyTags' => '*',
2398 'externalBlocks' => 'article, aside, blockquote, div, dd, dl, footer, header, nav, ol, section, table, ul, pre',
2399 'externalBlocks.' => [
2400 'article.' => [
2401 'callRecursive' => '1',
2402 'stripNL' => '1',
2403 ],
2404 'aside.' => [
2405 'callRecursive' => '1',
2406 'stripNL' => '1',
2407 ],
2408 'blockquote.' => [
2409 'callRecursive' => '1',
2410 'stripNL' => '1',
2411 ],
2412 'dd.' => [
2413 'callRecursive' => '1',
2414 'stripNL' => '1',
2415 ],
2416 'div.' => [
2417 'callRecursive' => '1',
2418 'stripNL' => '1',
2419 ],
2420 'dl.' => [
2421 'callRecursive' => '1',
2422 'stripNL' => '1',
2423 ],
2424 'footer.' => [
2425 'callRecursive' => '1',
2426 'stripNL' => '1',
2427 ],
2428 'header.' => [
2429 'callRecursive' => '1',
2430 'stripNL' => '1',
2431 ],
2432 'nav.' => [
2433 'callRecursive' => '1',
2434 'stripNL' => '1',
2435 ],
2436 'ol.' => [
2437 'callRecursive' => '1',
2438 'stripNL' => '1',
2439 ],
2440 'section.' => [
2441 'callRecursive' => '1',
2442 'stripNL' => '1',
2443 ],
2444 'table.' => [
2445 'HTMLtableCells' => '1',
2446 'HTMLtableCells.' => [
2447 'addChr10BetweenParagraphs' => '1',
2448 'default.' => [
2449 'stdWrap.' => [
2450 'parseFunc' => '=< lib.parseFunc_RTE',
2451 'parseFunc.' => [
2452 'nonTypoTagStdWrap.' => [
2453 'encapsLines.' => [
2454 'nonWrappedTag' => '',
2455 ],
2456 ],
2457 ],
2458 ],
2459 ],
2460 ],
2461 'stdWrap.' => [
2462 'HTMLparser' => '1',
2463 'HTMLparser.' => [
2464 'keepNonMatchedTags' => '1',
2465 'tags.' => [
2466 'table.' => [
2467 'fixAttrib.' => [
2468 'class.' => [
2469 'always' => '1',
2470 'default' => 'contenttable',
2471 'list' => 'contenttable',
2472 ],
2473 ],
2474 ],
2475 ],
2476 ],
2477 ],
2478 'stripNL' => '1',
2479 ],
2480 'ul.' => [
2481 'callRecursive' => '1',
2482 'stripNL' => '1',
2483 ],
2484 ],
2485 'makelinks' => '1',
2486 'makelinks.' => [
2487 'http.' => [
2488 'extTarget.' => [
2489 'override' => '_blank',
2490 'override.' => [
2491 'if.' => [
2492 'isTrue.' => [
2493 'data' => 'TSFE:dtdAllowsFrames',
2494 ],
2495 ],
2496 ],
2497 ],
2498 'keep' => 'path',
2499 ],
2500 ],
2501 'nonTypoTagStdWrap.' => [
2502 'encapsLines.' => [
2503 'addAttributes.' => [
2504 'P.' => [
2505 'class' => 'bodytext',
2506 'class.' => [
2507 'setOnly' => 'blank',
2508 ],
2509 ],
2510 ],
2511 'encapsTagList' => 'p,pre,h1,h2,h3,h4,h5,h6,hr,dt,li',
2512 'innerStdWrap_all.' => [
2513 'ifBlank' => '&nbsp;',
2514 ],
2515 'nonWrappedTag' => 'P',
2516 'remapTag.' => [
2517 'DIV' => 'P',
2518 ],
2519 ],
2520 'HTMLparser' => '1',
2521 'HTMLparser.' => [
2522 'htmlSpecialChars' => '2',
2523 'keepNonMatchedTags' => '1',
2524 ],
2525 ],
2526 'sword' => '<span class="csc-sword">|</span>',
2527 'tags.' => [
2528 'link' => 'TEXT',
2529 'link.' => [
2530 'current' => '1',
2531 'parseFunc.' => [
2532 'constants' => '1',
2533 ],
2534 'typolink.' => [
2535 'extTarget.' => [
2536 'override' => '',
2537 'override.' => [
2538 'if.' => [
2539 'isTrue.' => [
2540 'data' => 'TSFE:dtdAllowsFrames',
2541 ],
2542 ],
2543 ],
2544 ],
2545 'parameter.' => [
2546 'data' => 'parameters : allParams',
2547 ],
2548 'target.' => [
2549 'override' => '',
2550 'override.' => [
2551 'if.' => [
2552 'isTrue.' => [
2553 'data' => 'TSFE:dtdAllowsFrames',
2554 ],
2555 ],
2556 ],
2557 ],
2558 ],
2559 ],
2560 ],
2561 ],
2562 ];
2563 }
2564
2565 /**
2566 * @return array
2567 */
2568 public function _parseFuncReturnsCorrectHtmlDataProvider()
2569 {
2570 return [
2571 'Text without tag is wrapped with <p> tag' => [
2572 'Text without tag',
2573 $this->getLibParseFunc_RTE(),
2574 '<p class="bodytext">Text without tag</p>',
2575 ],
2576 'Text wrapped with <p> tag remains the same' => [
2577 '<p class="myclass">Text with &lt;p&gt; tag</p>',
2578 $this->getLibParseFunc_RTE(),
2579 '<p class="myclass">Text with &lt;p&gt; tag</p>',
2580 ],
2581 'Text with absolute external link' => [
2582 'Text with <link http://example.com/foo/>external link</link>',
2583 $this->getLibParseFunc_RTE(),
2584 '<p class="bodytext">Text with <a href="http://example.com/foo/">external link</a></p>',
2585 ],
2586 'Empty lines are not duplicated' => [
2587 LF,
2588 $this->getLibParseFunc_RTE(),
2589 '<p class="bodytext">&nbsp;</p>',
2590 ],
2591 'Multiple empty lines with no text' => [
2592 LF . LF . LF,
2593 $this->getLibParseFunc_RTE(),
2594 '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2595 ],
2596 'Empty lines are not duplicated at the end of content' => [
2597 'test' . LF . LF,
2598 $this->getLibParseFunc_RTE(),
2599 '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2600 ],
2601 'Empty lines are not trimmed' => [
2602 LF . 'test' . LF,
2603 $this->getLibParseFunc_RTE(),
2604 '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
2605 ],
2606 ];
2607 }
2608
2609 /**
2610 * @test
2611 * @dataProvider _parseFuncReturnsCorrectHtmlDataProvider
2612 * @param string $value
2613 * @param array $configuration
2614 * @param string $expectedResult
2615 */
2616 public function stdWrap_parseFuncReturnsParsedHtml($value, $configuration, $expectedResult)
2617 {
2618 $this->assertEquals($expectedResult, $this->subject->stdWrap_parseFunc($value, $configuration));
2619 }
2620
2621 /**
2622 * @return array
2623 */
2624 public function typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider()
2625 {
2626 return [
2627 'Link to url' => [
2628 'TYPO3',
2629 [
2630 'parameter' => 'http://typo3.org',
2631 ],
2632 '<a href="http://typo3.org">TYPO3</a>',
2633 ],
2634 'Link to url without schema' => [
2635 'TYPO3',
2636 [
2637 'parameter' => 'typo3.org',
2638 ],
2639 '<a href="http://typo3.org">TYPO3</a>',
2640 ],
2641 'Link to url without link text' => [
2642 '',
2643 [
2644 'parameter' => 'http://typo3.org',
2645 ],
2646 '<a href="http://typo3.org">http://typo3.org</a>',
2647 ],
2648 'Link to url with attributes' => [
2649 'TYPO3',
2650 [
2651 'parameter' => 'http://typo3.org',
2652 'ATagParams' => 'class="url-class"',
2653 'extTarget' => '_blank',
2654 'title' => 'Open new window',
2655 ],
2656 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2657 ],
2658 'Link to url with attributes in parameter' => [
2659 'TYPO3',
2660 [
2661 'parameter' => 'http://typo3.org _blank url-class "Open new window"',
2662 ],
2663 '<a href="http://typo3.org" title="Open new window" target="_blank" class="url-class">TYPO3</a>',
2664 ],
2665 'Link to url with script tag' => [
2666 '',
2667 [
2668 'parameter' => 'http://typo3.org<script>alert(123)</script>',
2669 ],
2670 '<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>',
2671 ],
2672 'Link to email address' => [
2673 'Email address',
2674 [
2675 'parameter' => 'foo@bar.org',
2676 ],
2677 '<a href="mailto:foo@bar.org">Email address</a>',
2678 ],
2679 'Link to email address without link text' => [
2680 '',
2681 [
2682 'parameter' => 'foo@bar.org',
2683 ],
2684 '<a href="mailto:foo@bar.org">foo@bar.org</a>',
2685 ],
2686 'Link to email with attributes' => [
2687 'Email address',
2688 [
2689 'parameter' => 'foo@bar.org',
2690 'ATagParams' => 'class="email-class"',
2691 'title' => 'Write an email',
2692 ],
2693 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2694 ],
2695 'Link to email with attributes in parameter' => [
2696 'Email address',
2697 [
2698 'parameter' => 'foo@bar.org - email-class "Write an email"',
2699 ],
2700 '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2701 ],
2702 ];
2703 }
2704
2705 /**
2706 * @test
2707 * @param string $linkText
2708 * @param array $configuration
2709 * @param string $expectedResult
2710 * @dataProvider typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider
2711 */
2712 public function typolinkReturnsCorrectLinksForEmailsAndUrls($linkText, $configuration, $expectedResult)
2713 {
2714 $templateServiceObjectMock = $this->getMockBuilder(TemplateService::class)
2715 ->setMethods(['dummy'])
2716 ->getMock();
2717 $templateServiceObjectMock->setup = [
2718 'lib.' => [
2719 'parseFunc.' => $this->getLibParseFunc(),
2720 ],
2721 ];
2722 $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
2723 $typoScriptFrontendControllerMockObject->config = [
2724 'config' => [],
2725 'mainScript' => 'index.php',
2726 ];
2727 $typoScriptFrontendControllerMockObject->tmpl = $templateServiceObjectMock;
2728 $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2729 $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2730
2731 $this->assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
2732 }
2733
2734 /**
2735 * @param array $settings
2736 * @param string $linkText
2737 * @param string $mailAddress
2738 * @param string $expected
2739 * @dataProvider typoLinkEncodesMailAddressForSpamProtectionDataProvider
2740 * @test
2741 */
2742 public function typoLinkEncodesMailAddressForSpamProtection(array $settings, $linkText, $mailAddress, $expected)
2743 {
2744 $this->getFrontendController()->spamProtectEmailAddresses = $settings['spamProtectEmailAddresses'];
2745 $this->getFrontendController()->config['config'] = $settings;
2746 $typoScript = ['parameter' => $mailAddress];
2747
2748 $this->assertEquals($expected, $this->subject->typoLink($linkText, $typoScript));
2749 }
2750
2751 /**
2752 * @return array
2753 */
2754 public function typoLinkEncodesMailAddressForSpamProtectionDataProvider()
2755 {
2756 return [
2757 'plain mail without mailto scheme' => [
2758 [
2759 'spamProtectEmailAddresses' => '',
2760 'spamProtectEmailAddresses_atSubst' => '',
2761 'spamProtectEmailAddresses_lastDotSubst' => '',
2762 ],
2763 'some.body@test.typo3.org',
2764 'some.body@test.typo3.org',
2765 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2766 ],
2767 'plain mail with mailto scheme' => [
2768 [
2769 'spamProtectEmailAddresses' => '',
2770 'spamProtectEmailAddresses_atSubst' => '',
2771 'spamProtectEmailAddresses_lastDotSubst' => '',
2772 ],
2773 'some.body@test.typo3.org',
2774 'mailto:some.body@test.typo3.org',
2775 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2776 ],
2777 'plain with at and dot substitution' => [
2778 [
2779 'spamProtectEmailAddresses' => '0',
2780 'spamProtectEmailAddresses_atSubst' => '(at)',
2781 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2782 ],
2783 'some.body@test.typo3.org',
2784 'mailto:some.body@test.typo3.org',
2785 '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2786 ],
2787 'mono-alphabetic substitution offset +1' => [
2788 [
2789 'spamProtectEmailAddresses' => '1',
2790 'spamProtectEmailAddresses_atSubst' => '',
2791 'spamProtectEmailAddresses_lastDotSubst' => '',
2792 ],
2793 'some.body@test.typo3.org',
2794 'mailto:some.body@test.typo3.org',
2795 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body(at)test.typo3.org</a>',
2796 ],
2797 'mono-alphabetic substitution offset +1 with at substitution' => [
2798 [
2799 'spamProtectEmailAddresses' => '1',
2800 'spamProtectEmailAddresses_atSubst' => '@',
2801 'spamProtectEmailAddresses_lastDotSubst' => '',
2802 ],
2803 'some.body@test.typo3.org',
2804 'mailto:some.body@test.typo3.org',
2805 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body@test.typo3.org</a>',
2806 ],
2807 'mono-alphabetic substitution offset +1 with at and dot substitution' => [
2808 [
2809 'spamProtectEmailAddresses' => '1',
2810 'spamProtectEmailAddresses_atSubst' => '(at)',
2811 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2812 ],
2813 'some.body@test.typo3.org',
2814 'mailto:some.body@test.typo3.org',
2815 '<a href="javascript:linkTo_UnCryptMailto(\'nbjmup+tpnf\/cpezAuftu\/uzqp4\/psh\');">some.body(at)test.typo3(dot)org</a>',
2816 ],
2817 'mono-alphabetic substitution offset -1 with at and dot substitution' => [
2818 [
2819 'spamProtectEmailAddresses' => '-1',
2820 'spamProtectEmailAddresses_atSubst' => '(at)',
2821 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2822 ],
2823 'some.body@test.typo3.org',
2824 'mailto:some.body@test.typo3.org',
2825 '<a href="javascript:linkTo_UnCryptMailto(\'lzhksn9rnld-ancxZsdrs-sxon2-nqf\');">some.body(at)test.typo3(dot)org</a>',
2826 ],
2827 'entity substitution with at and dot substitution' => [
2828 [
2829 'spamProtectEmailAddresses' => 'ascii',
2830 'spamProtectEmailAddresses_atSubst' => '',
2831 'spamProtectEmailAddresses_lastDotSubst' => '',
2832 ],
2833 'some.body@test.typo3.org',
2834 'mailto:some.body@test.typo3.org',
2835 '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#115;&#111;&#109;&#101;&#46;&#98;&#111;&#100;&#121;&#64;&#116;&#101;&#115;&#116;&#46;&#116;&#121;&#112;&#111;&#51;&#46;&#111;&#114;&#103;">some.body(at)test.typo3.org</a>',
2836 ],
2837 'entity substitution with at and dot substitution with at and dot substitution' => [
2838 [
2839 'spamProtectEmailAddresses' => 'ascii',
2840 'spamProtectEmailAddresses_atSubst' => '(at)',
2841 'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2842 ],
2843 'some.body@test.typo3.org',
2844 'mailto:some.body@test.typo3.org',
2845 '<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>',
2846 ],
2847 ];
2848 }
2849
2850 /**
2851 * @return array
2852 */
2853 public function typolinkReturnsCorrectLinksFilesDataProvider()
2854 {
2855 return [
2856 'Link to file' => [
2857 'My file',
2858 [
2859 'parameter' => 'fileadmin/foo.bar',
2860 ],
2861 '<a href="fileadmin/foo.bar">My file</a>',
2862 ],
2863 'Link to file without link text' => [
2864 '',
2865 [
2866 'parameter' => 'fileadmin/foo.bar',
2867 ],
2868 '<a href="fileadmin/foo.bar">fileadmin/foo.bar</a>',
2869 ],
2870 'Link to file with attributes' => [
2871 'My file',
2872 [
2873 'parameter' => 'fileadmin/foo.bar',
2874 'ATagParams' => 'class="file-class"',
2875 'fileTarget' => '_blank',
2876 'title' => 'Title of the file',
2877 ],
2878 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2879 ],
2880 'Link to file with attributes in parameter' => [
2881 'My file',
2882 [
2883 'parameter' => 'fileadmin/foo.bar _blank file-class "Title of the file"',
2884 ],
2885 '<a href="fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2886 ],
2887 'Link to file with script tag in name' => [
2888 '',
2889 [
2890 'parameter' => 'fileadmin/<script>alert(123)</script>',
2891 ],
2892 '<a href="fileadmin/&lt;script&gt;alert(123)&lt;/script&gt;">fileadmin/&lt;script&gt;alert(123)&lt;/script&gt;</a>',
2893 ],
2894 ];
2895 }
2896
2897 /**
2898 * @test
2899 * @param string $linkText
2900 * @param array $configuration
2901 * @param string $expectedResult
2902 * @dataProvider typolinkReturnsCorrectLinksFilesDataProvider
2903 */
2904 public function typolinkReturnsCorrectLinksFiles($linkText, $configuration, $expectedResult)
2905 {
2906 $templateServiceObjectMock = $this->getMockBuilder(TemplateService::class)
2907 ->setMethods(['dummy'])
2908 ->getMock();
2909 $templateServiceObjectMock->setup = [
2910 'lib.' => [
2911 'parseFunc.' => $this->getLibParseFunc(),
2912 ],
2913 ];
2914 $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
2915 $typoScriptFrontendControllerMockObject->config = [
2916 'config' => [],
2917 'mainScript' => 'index.php',
2918 ];
2919 $typoScriptFrontendControllerMockObject->tmpl = $templateServiceObjectMock;
2920 $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2921 $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2922
2923 $this->assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
2924 }
2925
2926 /**
2927 * @return array
2928 */
2929 public function typolinkReturnsCorrectLinksForFilesWithAbsRefPrefixDataProvider()
2930 {
2931 return [
2932 'Link to file' => [
2933 'My file',
2934 [
2935 'parameter' => 'fileadmin/foo.bar',
2936 ],
2937 '/',
2938 '<a href="/fileadmin/foo.bar">My file</a>',
2939 ],
2940 'Link to file with longer absRefPrefix' => [
2941 'My file',
2942 [
2943 'parameter' => 'fileadmin/foo.bar',
2944 ],
2945 '/sub/',
2946 '<a href="/sub/fileadmin/foo.bar">My file</a>',
2947 ],
2948 'Link to absolute file' => [
2949 'My file',
2950 [
2951 'parameter' => '/images/foo.bar',
2952 ],
2953 '/',
2954 '<a href="/images/foo.bar">My file</a>',
2955 ],
2956 'Link to absolute file with longer absRefPrefix' => [
2957 'My file',
2958 [
2959 'parameter' => '/images/foo.bar',
2960 ],
2961 '/sub/',
2962 '<a href="/images/foo.bar">My file</a>',
2963 ],
2964 'Link to absolute file with identical longer absRefPrefix' => [
2965 'My file',
2966 [
2967 'parameter' => '/sub/fileadmin/foo.bar',
2968 ],
2969 '/sub/',
2970 '<a href="/sub/fileadmin/foo.bar">My file</a>',
2971 ],
2972 'Link to file with empty absRefPrefix' => [
2973 'My file',
2974 [
2975 'parameter' => 'fileadmin/foo.bar',
2976 ],
2977 '',
2978 '<a href="fileadmin/foo.bar">My file</a>',
2979 ],
2980 'Link to absolute file with empty absRefPrefix' => [
2981 'My file',
2982 [
2983 'parameter' => '/fileadmin/foo.bar',
2984 ],
2985 '',
2986 '<a href="/fileadmin/foo.bar">My file</a>',
2987 ],
2988 'Link to file with attributes with absRefPrefix' => [
2989 'My file',
2990 [
2991 'parameter' => 'fileadmin/foo.bar',
2992 'ATagParams' => 'class="file-class"',
2993 'fileTarget' => '_blank',
2994 'title' => 'Title of the file',
2995 ],
2996 '/',
2997 '<a href="/fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
2998 ],
2999 'Link to file with attributes with longer absRefPrefix' => [
3000 'My file',
3001 [
3002 'parameter' => 'fileadmin/foo.bar',
3003 'ATagParams' => 'class="file-class"',
3004 'fileTarget' => '_blank',
3005 'title' => 'Title of the file',
3006 ],
3007 '/sub/',
3008 '<a href="/sub/fileadmin/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
3009 ],
3010 'Link to absolute file with attributes with absRefPrefix' => [
3011 'My file',
3012 [
3013 'parameter' => '/images/foo.bar',
3014 'ATagParams' => 'class="file-class"',
3015 'fileTarget' => '_blank',
3016 'title' => 'Title of the file',
3017 ],
3018 '/',
3019 '<a href="/images/foo.bar" title="Title of the file" target="_blank" class="file-class">My file</a>',
3020 ],
3021 'Link to absolute file with attributes with longer absRefPrefix' => [
3022 'My file',
3023 [
3024 'parameter' => '/images/foo.bar',
3025 'ATagParams' => 'class="file-class"',
3026 'fileTarget' => '_blank',
3027 'title' => 'Title of the file',
3028 ],
3029 '/sub/',
3030 '<a href="/images/foo.bar&qu