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