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