‪TYPO3CMS  ‪main
ContentObjectRendererTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use PHPUnit\Framework\Exception;
21 use PHPUnit\Framework\MockObject\MockObject;
22 use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
23 use Psr\EventDispatcher\EventDispatcherInterface;
24 use Psr\Http\Message\ServerRequestInterface;
25 use Psr\Log\NullLogger;
26 use Symfony\Component\DependencyInjection\Container;
28 use ‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface as CacheFrontendInterface;
46 use TYPO3\CMS\Core\LinkHandling\TypoLinkCodecService;
50 use TYPO3\CMS\Core\Package\PackageManager;
51 use TYPO3\CMS\Core\Page\PageRenderer;
77 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
96 use TYPO3\CMS\Frontend\Typolink\LinkResult;
98 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
99 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
100 
101 class ‪ContentObjectRendererTest extends UnitTestCase
102 {
104 
105  protected bool ‪$resetSingletonInstances = true;
106 
107  protected ContentObjectRenderer&MockObject&AccessibleObjectInterface ‪$subject;
108 
109  protected ‪TypoScriptFrontendController&MockObject&AccessibleObjectInterface ‪$frontendControllerMock;
110 
114  protected array ‪$contentObjectMap = [
115  'TEXT' => TextContentObject::class,
116  'CASE' => CaseContentObject::class,
117  'COBJ_ARRAY' => ContentObjectArrayContentObject::class,
118  'COA' => ContentObjectArrayContentObject::class,
119  'COA_INT' => ContentObjectArrayInternalContentObject::class,
120  'USER' => UserContentObject::class,
121  'USER_INT' => UserInternalContentObject::class,
122  'FILES' => FilesContentObject::class,
123  'IMAGE' => ImageContentObject::class,
124  'IMG_RESOURCE' => ImageResourceContentObject::class,
125  'CONTENT' => ContentContentObject::class,
126  'RECORDS' => RecordsContentObject::class,
127  'HMENU' => HierarchicalMenuContentObject::class,
128  'CASEFUNC' => CaseContentObject::class,
129  'LOAD_REGISTER' => LoadRegisterContentObject::class,
130  'RESTORE_REGISTER' => RestoreRegisterContentObject::class,
131  'FLUIDTEMPLATE' => FluidTemplateContentObject::class,
132  'SVG' => ScalableVectorGraphicsContentObject::class,
133  ];
134 
135  protected MockObject&‪CacheManager ‪$cacheManagerMock;
136 
137  protected bool ‪$backupEnvironment = true;
138 
139  protected function ‪setUp(): void
140  {
141  parent::setUp();
142 
143  $site = $this->‪createSiteWithLanguage([
144  'base' => '/',
145  'languageId' => 2,
146  'locale' => 'en_UK',
147  ]);
148 
149  ‪$GLOBALS['SIM_ACCESS_TIME'] = 1534278180;
150  $pageRepositoryMock =
151  $this->getAccessibleMock(PageRepository::class, ['getRawRecord', 'getMountPointInfo']);
152  $this->frontendControllerMock =
153  $this->getAccessibleMock(
154  TypoScriptFrontendController::class,
155  ['sL'],
156  [],
157  '',
158  false
159  );
160  $this->frontendControllerMock->_set('context', GeneralUtility::makeInstance(Context::class));
161  $this->frontendControllerMock->config = [];
162  $this->frontendControllerMock->page = [];
163  $this->frontendControllerMock->sys_page = $pageRepositoryMock;
164  $this->frontendControllerMock->_set('language', $site->getLanguageById(2));
166 
167  $this->cacheManagerMock = $this->getMockBuilder(CacheManager::class)->disableOriginalConstructor()->getMock();
168  GeneralUtility::setSingletonInstance(CacheManager::class, $this->cacheManagerMock);
169 
170  $this->subject = $this->getAccessibleMock(
171  ContentObjectRenderer::class,
172  ['getResourceFactory', 'getEnvironmentVariable'],
173  [$this->frontendControllerMock]
174  );
175 
176  $logger = new NullLogger();
177  $this->subject->setLogger($logger);
178  $request = new ‪ServerRequest();
179  $this->subject->setRequest($request);
180 
181  $contentObjectFactoryMock = $this->createContentObjectFactoryMock();
182  $cObj = ‪$this->subject;
183  foreach ($this->contentObjectMap as $name => $className) {
184  $contentObjectFactoryMock->addGetContentObjectCallback(
185  $name,
186  $className,
187  $request,
188  $cObj,
189  $this->getMockBuilder(DataProcessorRegistry::class)->disableOriginalConstructor()->getMock()
190  );
191  }
192  $container = new Container();
193  $container->set(ContentObjectFactory::class, $contentObjectFactoryMock);
194  GeneralUtility::setContainer($container);
195 
196  $this->subject->start([], 'tt_content');
197  }
198 
200  // Utility functions
202 
204  {
205  return ‪$GLOBALS['TSFE'];
206  }
207 
208  protected function ‪getLinkFactory(‪SiteConfiguration $siteConfiguration = null, $linkService = null): ‪LinkFactory
209  {
210  $linkService ??= new ‪LinkService();
211  $typoLinkCodecService = new TypoLinkCodecService();
212  if ($siteConfiguration) {
213  $siteFinder = new ‪SiteFinder($siteConfiguration);
214  } else {
215  $siteFinder = $this->getMockBuilder(SiteFinder::class)->disableOriginalConstructor()->getMock();
216  }
217  $linkFactory = new ‪LinkFactory(
218  $linkService,
219  new class () implements EventDispatcherInterface {
220  public function dispatch(object $event)
221  {
222  return $event;
223  }
224  },
225  $typoLinkCodecService,
226  new ‪NullFrontend('runtime'),
227  $siteFinder
228  );
229  $linkFactory->setLogger(new NullLogger());
230  return $linkFactory;
231  }
232 
239  protected function ‪handleCharset(string &‪$subject, string &$expected): void
240  {
241  ‪$subject = mb_convert_encoding(‪$subject, 'utf-8', 'iso-8859-1');
242  $expected = mb_convert_encoding($expected, 'utf-8', 'iso-8859-1');
243  }
244 
246  // Tests concerning the getImgResource hook
248 
252  {
253  ‪$cacheManagerMock = $this->getMockBuilder(CacheManager::class)->disableOriginalConstructor()->getMock();
254  $cacheMock = $this->getMockBuilder(CacheFrontendInterface::class)->disableOriginalConstructor()->getMock();
255  $cacheMock->method('get')->willReturn(false);
256  $cacheMock->method('set')->willReturn(false);
257  ‪$cacheManagerMock->method('getCache')->with('imagesizes')->willReturn($cacheMock);
258  GeneralUtility::setSingletonInstance(CacheManager::class, ‪$cacheManagerMock);
259 
260  $resourceFactory = $this->createMock(ResourceFactory::class);
261  $this->subject->method('getResourceFactory')->willReturn($resourceFactory);
262 
263  $className = ‪StringUtility::getUniqueId('tx_coretest');
264  $getImgResourceHookMock = $this->getMockBuilder(ContentObjectGetImageResourceHookInterface::class)
265  ->onlyMethods(['getImgResourcePostProcess'])
266  ->setMockClassName($className)
267  ->getMock();
268  $getImgResourceHookMock
269  ->expects(self::once())
270  ->method('getImgResourcePostProcess')
271  ->willReturnCallback([$this, 'isGetImgResourceHookCalledCallback']);
272  $getImgResourceHookObjects = [$getImgResourceHookMock];
273  $this->subject->_set('getImgResourceHookObjects', $getImgResourceHookObjects);
274  $this->subject->getImgResource('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', []);
275  }
276 
282  string $file,
283  array $fileArray,
284  array $imageResource,
285  ContentObjectRenderer $parent
286  ): array {
287  self::assertEquals('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', $file);
288  self::assertEquals('typo3/sysext/core/Tests/Unit/Utility/Fixtures/clear.gif', $imageResource['origFile']);
289  self::assertIsArray($fileArray);
290  self::assertInstanceOf(ContentObjectRenderer::class, $parent);
291  return $imageResource;
292  }
293 
295  // Tests related to getContentObject
297 
302  public function ‪willReturnNullForUnregisteredObject(): void
303  {
304  $object = $this->subject->getContentObject('FOO');
305  self::assertNull($object);
306  }
307 
309  // Tests concerning crop
311 
319  public function ‪cropIsMultibyteSafe(): void
320  {
321  self::assertEquals('бла', $this->subject->crop('бла', '3|...'));
322  }
323 
325 
327  // Tests concerning cropHTML
329 
337  public static function ‪cropHTMLDataProvider(): array
338  {
339  $plainText = 'Kasper Sk' . chr(229) . 'rh' . chr(248)
340  . 'j implemented the original version of the crop function.';
341  $textWithMarkup = '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
342  . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong> the '
343  . 'original version of the crop function.';
344  $textWithEntities = 'Kasper Sk&aring;rh&oslash;j implemented the; '
345  . 'original version of the crop function.';
346  $textWithLinebreaks = "Lorem ipsum dolor sit amet,\n"
347  . "consetetur sadipscing elitr,\n"
348  . 'sed diam nonumy eirmod tempor invidunt ut labore e'
349  . 't dolore magna aliquyam';
350  $textWith2000Chars = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ips &amp;. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vesti&amp;';
351  $textWith1000AmpHtmlEntity = str_repeat('&amp;', 1000);
352  $textWith2000AmpHtmlEntity = str_repeat('&amp;', 2000);
353 
354  return [
355  'plain text; 11|...' => [
356  'Kasper Sk' . chr(229) . 'r...',
357  $plainText,
358  '11|...',
359  ],
360  'plain text; -58|...' => [
361  '...h' . chr(248) . 'j implemented the original version of '
362  . 'the crop function.',
363  $plainText,
364  '-58|...',
365  ],
366  'plain text; 4|...|1' => [
367  'Kasp...',
368  $plainText,
369  '4|...|1',
370  ],
371  'plain text; 20|...|1' => [
372  'Kasper Sk' . chr(229) . 'rh' . chr(248) . 'j...',
373  $plainText,
374  '20|...|1',
375  ],
376  'plain text; -5|...|1' => [
377  '...tion.',
378  $plainText,
379  '-5|...|1',
380  ],
381  'plain text; -49|...|1' => [
382  '...the original version of the crop function.',
383  $plainText,
384  '-49|...|1',
385  ],
386  'text with markup; 11|...' => [
387  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
388  . chr(229) . 'r...</a></strong>',
389  $textWithMarkup,
390  '11|...',
391  ],
392  'text with markup; 13|...' => [
393  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
394  . chr(229) . 'rh' . chr(248) . '...</a></strong>',
395  $textWithMarkup,
396  '13|...',
397  ],
398  'text with markup; 14|...' => [
399  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
400  . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
401  $textWithMarkup,
402  '14|...',
403  ],
404  'text with markup; 15|...' => [
405  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
406  . chr(229) . 'rh' . chr(248) . 'j</a> ...</strong>',
407  $textWithMarkup,
408  '15|...',
409  ],
410  'text with markup; 29|...' => [
411  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
412  . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong> '
413  . 'th...',
414  $textWithMarkup,
415  '29|...',
416  ],
417  'text with markup; -58|...' => [
418  '<strong><a href="mailto:kasper@typo3.org">...h' . chr(248)
419  . 'j</a> implemented</strong> the original version of the crop '
420  . 'function.',
421  $textWithMarkup,
422  '-58|...',
423  ],
424  'text with markup 4|...|1' => [
425  '<strong><a href="mailto:kasper@typo3.org">Kasp...</a>'
426  . '</strong>',
427  $textWithMarkup,
428  '4|...|1',
429  ],
430  'text with markup; 11|...|1' => [
431  '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
432  . '</strong>',
433  $textWithMarkup,
434  '11|...|1',
435  ],
436  'text with markup; 13|...|1' => [
437  '<strong><a href="mailto:kasper@typo3.org">Kasper...</a>'
438  . '</strong>',
439  $textWithMarkup,
440  '13|...|1',
441  ],
442  'text with markup; 14|...|1' => [
443  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
444  . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
445  $textWithMarkup,
446  '14|...|1',
447  ],
448  'text with markup; 15|...|1' => [
449  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
450  . chr(229) . 'rh' . chr(248) . 'j</a>...</strong>',
451  $textWithMarkup,
452  '15|...|1',
453  ],
454  'text with markup; 29|...|1' => [
455  '<strong><a href="mailto:kasper@typo3.org">Kasper Sk'
456  . chr(229) . 'rh' . chr(248) . 'j</a> implemented</strong>...',
457  $textWithMarkup,
458  '29|...|1',
459  ],
460  'text with markup; -66|...|1' => [
461  '<strong><a href="mailto:kasper@typo3.org">...Sk' . chr(229)
462  . 'rh' . chr(248) . 'j</a> implemented</strong> the original v'
463  . 'ersion of the crop function.',
464  $textWithMarkup,
465  '-66|...|1',
466  ],
467  'text with entities 9|...' => [
468  'Kasper Sk...',
469  $textWithEntities,
470  '9|...',
471  ],
472  'text with entities 10|...' => [
473  'Kasper Sk&aring;...',
474  $textWithEntities,
475  '10|...',
476  ],
477  'text with entities 11|...' => [
478  'Kasper Sk&aring;r...',
479  $textWithEntities,
480  '11|...',
481  ],
482  'text with entities 13|...' => [
483  'Kasper Sk&aring;rh&oslash;...',
484  $textWithEntities,
485  '13|...',
486  ],
487  'text with entities 14|...' => [
488  'Kasper Sk&aring;rh&oslash;j...',
489  $textWithEntities,
490  '14|...',
491  ],
492  'text with entities 15|...' => [
493  'Kasper Sk&aring;rh&oslash;j ...',
494  $textWithEntities,
495  '15|...',
496  ],
497  'text with entities 16|...' => [
498  'Kasper Sk&aring;rh&oslash;j i...',
499  $textWithEntities,
500  '16|...',
501  ],
502  'text with entities -57|...' => [
503  '...j implemented the; original version of the crop function.',
504  $textWithEntities,
505  '-57|...',
506  ],
507  'text with entities -58|...' => [
508  '...&oslash;j implemented the; original version of the crop '
509  . 'function.',
510  $textWithEntities,
511  '-58|...',
512  ],
513  'text with entities -59|...' => [
514  '...h&oslash;j implemented the; original version of the crop '
515  . 'function.',
516  $textWithEntities,
517  '-59|...',
518  ],
519  'text with entities 4|...|1' => [
520  'Kasp...',
521  $textWithEntities,
522  '4|...|1',
523  ],
524  'text with entities 9|...|1' => [
525  'Kasper...',
526  $textWithEntities,
527  '9|...|1',
528  ],
529  'text with entities 10|...|1' => [
530  'Kasper...',
531  $textWithEntities,
532  '10|...|1',
533  ],
534  'text with entities 11|...|1' => [
535  'Kasper...',
536  $textWithEntities,
537  '11|...|1',
538  ],
539  'text with entities 13|...|1' => [
540  'Kasper...',
541  $textWithEntities,
542  '13|...|1',
543  ],
544  'text with entities 14|...|1' => [
545  'Kasper Sk&aring;rh&oslash;j...',
546  $textWithEntities,
547  '14|...|1',
548  ],
549  'text with entities 15|...|1' => [
550  'Kasper Sk&aring;rh&oslash;j...',
551  $textWithEntities,
552  '15|...|1',
553  ],
554  'text with entities 16|...|1' => [
555  'Kasper Sk&aring;rh&oslash;j...',
556  $textWithEntities,
557  '16|...|1',
558  ],
559  'text with entities -57|...|1' => [
560  '...implemented the; original version of the crop function.',
561  $textWithEntities,
562  '-57|...|1',
563  ],
564  'text with entities -58|...|1' => [
565  '...implemented the; original version of the crop function.',
566  $textWithEntities,
567  '-58|...|1',
568  ],
569  'text with entities -59|...|1' => [
570  '...implemented the; original version of the crop function.',
571  $textWithEntities,
572  '-59|...|1',
573  ],
574  'text with dash in html-element 28|...|1' => [
575  'Some text with a link to <link email.address@example.org - '
576  . 'mail "Open email window">my...</link>',
577  'Some text with a link to <link email.address@example.org - m'
578  . 'ail "Open email window">my email.address@example.org<'
579  . '/link> and text after it',
580  '28|...|1',
581  ],
582  'html elements with dashes in attributes' => [
583  '<em data-foo="x">foobar</em>foo',
584  '<em data-foo="x">foobar</em>foo',
585  '9',
586  ],
587  'html elements with iframe embedded 24|...|1' => [
588  'Text with iframe <iframe src="//what.ever/"></iframe> and...',
589  'Text with iframe <iframe src="//what.ever/">'
590  . '</iframe> and text after it',
591  '24|...|1',
592  ],
593  'html elements with script tag embedded 24|...|1' => [
594  'Text with script <script>alert(\'foo\');</script> and...',
595  'Text with script <script>alert(\'foo\');</script> '
596  . 'and text after it',
597  '24|...|1',
598  ],
599  'text with linebreaks' => [
600  "Lorem ipsum dolor sit amet,\nconsetetur sadipscing elitr,\ns"
601  . 'ed diam nonumy eirmod tempor invidunt ut labore e'
602  . 't dolore magna',
603  $textWithLinebreaks,
604  '121',
605  ],
606  'long text under the crop limit' => [
607  'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit' . ' ...',
608  $textWith2000Chars,
609  '962|...',
610  ],
611  'long text above the crop limit' => [
612  'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ips &amp;. N' . '...',
613  $textWith2000Chars,
614  '1000|...',
615  ],
616  'long text above the crop limit #2' => [
617  'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ips &amp;. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, metus. Nullam accumsan lorem in dui. Cras ultricies mi eu turpis hendrerit fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; In ac dui quis mi consectetuer lacinia. Nam pretium turpis et arcu. Duis arcu tortor, suscipit eget, imperdiet nec, imperdiet iaculis, ipsum. Sed aliquam ultrices mauris. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Praesent adipiscing. Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vesti&amp;' . '...',
618  $textWith2000Chars . $textWith2000Chars,
619  '2000|...',
620  ],
621  // ensure that large number of html entities do not break the the regexp splittin
622  'long text with large number of html entities' => [
623  $textWith1000AmpHtmlEntity . '...',
624  $textWith2000AmpHtmlEntity,
625  '1000|...',
626  ],
627  ];
628  }
629 
644  public function ‪cropHTML(string $expect, string $content, string $conf): void
645  {
646  $this->‪handleCharset($content, $expect);
647  self::assertSame(
648  $expect,
649  $this->subject->cropHTML($content, $conf)
650  );
651  }
652 
658  public static function ‪roundDataProvider(): array
659  {
660  return [
661  // floats
662  'down' => [1.0, 1.11, []],
663  'up' => [2.0, 1.51, []],
664  'rounds up from x.50' => [2.0, 1.50, []],
665  'down with decimals' => [0.12, 0.1231, ['decimals' => 2]],
666  'up with decimals' => [0.13, 0.1251, ['decimals' => 2]],
667  'ceil' => [1.0, 0.11, ['roundType' => 'ceil']],
668  'ceil does not accept decimals' => [
669  1.0,
670  0.111,
671  [
672  'roundType' => 'ceil',
673  'decimals' => 2,
674  ],
675  ],
676  'floor' => [2.0, 2.99, ['roundType' => 'floor']],
677  'floor does not accept decimals' => [
678  2.0,
679  2.999,
680  [
681  'roundType' => 'floor',
682  'decimals' => 2,
683  ],
684  ],
685  'round, down' => [1.0, 1.11, ['roundType' => 'round']],
686  'round, up' => [2.0, 1.55, ['roundType' => 'round']],
687  'round does accept decimals' => [
688  5.56,
689  5.5555,
690  [
691  'roundType' => 'round',
692  'decimals' => 2,
693  ],
694  ],
695  // strings
696  'emtpy string' => [0.0, '', []],
697  'word string' => [0.0, 'word', []],
698  'float string' => [1.0, '1.123456789', []],
699  // other types
700  'null' => [0.0, null, []],
701  'false' => [0.0, false, []],
702  'true' => [1.0, true, []],
703  ];
704  }
705 
723  public function ‪round(float $expect, mixed $content, array $conf): void
724  {
725  self::assertSame(
726  $expect,
727  $this->subject->_call('round', $content, $conf)
728  );
729  }
730 
734  public function ‪recursiveStdWrapProperlyRendersBasicString(): void
735  {
736  $stdWrapConfiguration = [
737  'noTrimWrap' => '|| 123|',
738  'stdWrap.' => [
739  'wrap' => '<b>|</b>',
740  ],
741  ];
742  self::assertSame(
743  '<b>Test</b> 123',
744  $this->subject->stdWrap('Test', $stdWrapConfiguration)
745  );
746  }
747 
751  public function ‪recursiveStdWrapIsOnlyCalledOnce(): void
752  {
753  $stdWrapConfiguration = [
754  'append' => 'TEXT',
755  'append.' => [
756  'data' => 'register:Counter',
757  ],
758  'stdWrap.' => [
759  'append' => 'LOAD_REGISTER',
760  'append.' => [
761  'Counter.' => [
762  'prioriCalc' => 'intval',
763  'cObject' => 'TEXT',
764  'cObject.' => [
765  'data' => 'register:Counter',
766  'wrap' => '|+1',
767  ],
768  ],
769  ],
770  ],
771  ];
772  self::assertSame(
773  'Counter:1',
774  $this->subject->stdWrap('Counter:', $stdWrapConfiguration)
775  );
776  }
777 
783  public static function ‪numberFormatDataProvider(): array
784  {
785  return [
786  'testing decimals' => [
787  '0.80',
788  0.8,
789  ['decimals' => 2],
790  ],
791  'testing decimals with input as string' => [
792  '0.80',
793  '0.8',
794  ['decimals' => 2],
795  ],
796  'testing dec_point' => [
797  '0,8',
798  0.8,
799  ['decimals' => 1, 'dec_point' => ','],
800  ],
801  'testing thousands_sep' => [
802  '1.000',
803  999.99,
804  [
805  'decimals' => 0,
806  'thousands_sep.' => ['char' => 46],
807  ],
808  ],
809  'testing mixture' => [
810  '1.281.731,5',
811  1281731.45,
812  [
813  'decimals' => 1,
814  'dec_point.' => ['char' => 44],
815  'thousands_sep.' => ['char' => 46],
816  ],
817  ],
818  ];
819  }
820 
827  public function ‪numberFormat(string $expects, mixed $content, array $conf): void
828  {
829  self::assertSame(
830  $expects,
831  $this->subject->numberFormat($content, $conf)
832  );
833  }
834 
840  public static function ‪replacementDataProvider(): array
841  {
842  return [
843  'multiple replacements, including regex' => [
844  'There is an animal, an animal and an animal around the block! Yeah!',
845  'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
846  [
847  '20.' => [
848  'search' => '_',
849  'replace.' => ['char' => '32'],
850  ],
851  '120.' => [
852  'search' => 'in da hood',
853  'replace' => 'around the block',
854  ],
855  '130.' => [
856  'search' => '#a (Cat|Dog|Tiger)#i',
857  'replace' => 'an animal',
858  'useRegExp' => '1',
859  ],
860  ],
861  ],
862  'replacement with optionSplit, normal pattern' => [
863  'There1is2a3cat,3a3dog3and3a3tiger3in3da3hood!3Yeah!',
864  'There_is_a_cat,_a_dog_and_a_tiger_in_da_hood!_Yeah!',
865  [
866  '10.' => [
867  'search' => '_',
868  'replace' => '1 || 2 || 3',
869  'useOptionSplitReplace' => '1',
870  'useRegExp' => '0',
871  ],
872  ],
873  ],
874  'replacement with optionSplit, using regex' => [
875  'There is a tiny cat, a midsized dog and a big tiger in da hood! Yeah!',
876  'There is a cat, a dog and a tiger in da hood! Yeah!',
877  [
878  '10.' => [
879  'search' => '#(a) (Cat|Dog|Tiger)#i',
880  'replace' => '${1} tiny ${2} || ${1} midsized ${2} || ${1} big ${2}',
881  'useOptionSplitReplace' => '1',
882  'useRegExp' => '1',
883  ],
884  ],
885  ],
886  ];
887  }
888 
898  public function ‪replacement(string $expects, string $content, array $conf): void
899  {
900  self::assertSame(
901  $expects,
902  $this->subject->_call('replacement', $content, $conf)
903  );
904  }
905 
911  public static function ‪calcAgeDataProvider(): array
912  {
913  return [
914  'minutes' => [
915  '2 min',
916  120,
917  ' min| hrs| days| yrs',
918  ],
919  'hours' => [
920  '2 hrs',
921  7200,
922  ' min| hrs| days| yrs',
923  ],
924  'days' => [
925  '7 days',
926  604800,
927  ' min| hrs| days| yrs',
928  ],
929  'day with provided singular labels' => [
930  '1 day',
931  86400,
932  ' min| hrs| days| yrs| min| hour| day| year',
933  ],
934  'years' => [
935  '45 yrs',
936  1417997800,
937  ' min| hrs| days| yrs',
938  ],
939  'different labels' => [
940  '2 Minutes',
941  120,
942  ' Minutes| Hrs| Days| Yrs',
943  ],
944  'negative values' => [
945  '-7 days',
946  -604800,
947  ' min| hrs| days| yrs',
948  ],
949  'default label values for wrong label input' => [
950  '2 min',
951  121,
952  '10',
953  ],
954  'default singular label values for wrong label input' => [
955  '1 year',
956  31536000,
957  '10',
958  ],
959  ];
960  }
961 
968  public function ‪calcAge(string $expect, int $timestamp, string $labels): void
969  {
970  self::assertSame(
971  $expect,
972  $this->subject->calcAge($timestamp, $labels)
973  );
974  }
975 
976  public static function ‪stdWrapReturnsExpectationDataProvider(): array
977  {
978  return [
979  'Prevent silent bool conversion' => [
980  '1+1',
981  [
982  'prioriCalc.' => [
983  'wrap' => '|',
984  ],
985  ],
986  '1+1',
987  ],
988  ];
989  }
990 
995  public function ‪stdWrapReturnsExpectation(string $content, array $configuration, string $expectation): void
996  {
997  self::assertSame($expectation, $this->subject->stdWrap($content, $configuration));
998  }
999 
1001  {
1002  return [
1003  'ifEmpty is not called if content is present as an non-empty string' => [
1004  'content' => 'some content',
1005  'ifEmptyShouldBeCalled' => false,
1006  ],
1007  'ifEmpty is not called if content is present as the string "1"' => [
1008  'content' => '1',
1009  'ifEmptyShouldBeCalled' => false,
1010  ],
1011  'ifEmpty is called if content is present as an empty string' => [
1012  'content' => '',
1013  'ifEmptyShouldBeCalled' => true,
1014  ],
1015  'ifEmpty is called if content is present as the string "0"' => [
1016  'content' => '0',
1017  'ifEmptyShouldBeCalled' => true,
1018  ],
1019  ];
1020  }
1021 
1026  public function ‪stdWrapDoesOnlyCallIfEmptyIfTheTrimmedContentIsEmptyOrZero(string $content, bool $ifEmptyShouldBeCalled): void
1027  {
1028  $conf = [
1029  'ifEmpty.' => [
1030  'cObject' => 'TEXT',
1031  ],
1032  ];
1033 
1034  ‪$subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['stdWrap_ifEmpty']);
1035  $request = new ‪ServerRequest();
1036  ‪$subject->setRequest($request);
1037  ‪$subject->expects(self::exactly(($ifEmptyShouldBeCalled ? 1 : 0)))
1038  ->method('stdWrap_ifEmpty');
1039 
1040  ‪$subject->stdWrap($content, $conf);
1041  }
1042 
1048  public static function ‪substringDataProvider(): array
1049  {
1050  return [
1051  'sub -1' => ['g', 'substring', '-1'],
1052  'sub -1,0' => ['g', 'substring', '-1,0'],
1053  'sub -1,-1' => ['', 'substring', '-1,-1'],
1054  'sub -1,1' => ['g', 'substring', '-1,1'],
1055  'sub 0' => ['substring', 'substring', '0'],
1056  'sub 0,0' => ['substring', 'substring', '0,0'],
1057  'sub 0,-1' => ['substrin', 'substring', '0,-1'],
1058  'sub 0,1' => ['s', 'substring', '0,1'],
1059  'sub 1' => ['ubstring', 'substring', '1'],
1060  'sub 1,0' => ['ubstring', 'substring', '1,0'],
1061  'sub 1,-1' => ['ubstrin', 'substring', '1,-1'],
1062  'sub 1,1' => ['u', 'substring', '1,1'],
1063  'sub' => ['substring', 'substring', ''],
1064  ];
1065  }
1066 
1076  public function ‪substring(string $expect, string $content, string $conf): void
1077  {
1078  self::assertSame($expect, $this->subject->substring($content, $conf));
1079  }
1081  public static function ‪getDataWithTypeGpDataProvider(): array
1082  {
1083  return [
1084  'Value in get-data' => ['onlyInGet', 'GetValue'],
1085  'Value in post-data' => ['onlyInPost', 'PostValue'],
1086  'Value in post-data overriding get-data' => ['inGetAndPost', 'ValueInPost'],
1087  ];
1088  }
1089 
1096  public function ‪getDataWithTypeGp(string $key, string $expectedValue): void
1097  {
1098  $queryArguments = [
1099  'onlyInGet' => 'GetValue',
1100  'inGetAndPost' => 'ValueInGet',
1101  ];
1102  $postParameters = [
1103  'onlyInPost' => 'PostValue',
1104  'inGetAndPost' => 'ValueInPost',
1105  ];
1106  $request = new ‪ServerRequest('https://example.com');
1107  $request = $request->withQueryParams($queryArguments)
1108  ->withParsedBody($postParameters);
1109  $this->subject->setRequest($request);
1110  self::assertEquals($expectedValue, $this->subject->getData('gp:' . $key));
1111  }
1112 
1118  public function ‪getDataWithTypeTsfe(): void
1119  {
1120  ‪$GLOBALS['TSFE']->linkVars = 'foo';
1121  self::assertEquals(‪$GLOBALS['TSFE']->linkVars, $this->subject->getData('tsfe:linkVars'));
1122  }
1123 
1129  public function ‪getDataWithTypeGetenv(): void
1130  {
1131  $envName = ‪StringUtility::getUniqueId('frontendtest');
1132  $value = ‪StringUtility::getUniqueId('someValue');
1133  putenv($envName . '=' . $value);
1134  self::assertEquals($value, $this->subject->getData('getenv:' . $envName));
1135  }
1136 
1142  public function ‪getDataWithTypeGetindpenv(): void
1143  {
1144  $this->subject->expects(self::once())->method('getEnvironmentVariable')
1145  ->with(self::equalTo('SCRIPT_FILENAME'))->willReturn('dummyPath');
1146  self::assertEquals('dummyPath', $this->subject->getData('getindpenv:SCRIPT_FILENAME'));
1147  }
1148 
1154  public function ‪getDataWithTypeField(): void
1155  {
1156  $key = 'someKey';
1157  $value = 'someValue';
1158  $field = [$key => $value];
1159 
1160  self::assertEquals($value, $this->subject->getData('field:' . $key, $field));
1161  }
1162 
1170  {
1171  $key = 'somekey|level1|level2';
1172  $value = 'somevalue';
1173  $field = ['somekey' => ['level1' => ['level2' => 'somevalue']]];
1174 
1175  self::assertEquals($value, $this->subject->getData('field:' . $key, $field));
1176  }
1178  public static function ‪getDataWithTypeFileReturnsUidOfFileObjectDataProvider(): array
1179  {
1180  return [
1181  'no whitespace' => [
1182  'typoScriptPath' => 'file:current:uid',
1183  ],
1184  'always whitespace' => [
1185  'typoScriptPath' => 'file : current : uid',
1186  ],
1187  'mixed whitespace' => [
1188  'typoScriptPath' => 'file:current : uid',
1189  ],
1190  ];
1191  }
1192 
1199  public function ‪getDataWithTypeFileReturnsUidOfFileObject(string $typoScriptPath): void
1200  {
1202  $file = $this->createMock(File::class);
1203  $file->expects(self::once())->method('getUid')->willReturn(‪$uid);
1204  $this->subject->setCurrentFile($file);
1205  self::assertEquals(‪$uid, $this->subject->getData($typoScriptPath));
1206  }
1207 
1213  public function ‪getDataWithTypeParameters(): void
1214  {
1215  $key = ‪StringUtility::getUniqueId('someKey');
1216  $value = ‪StringUtility::getUniqueId('someValue');
1217  $this->subject->parameters[$key] = $value;
1218 
1219  self::assertEquals($value, $this->subject->getData('parameters:' . $key));
1220  }
1221 
1227  public function ‪getDataWithTypeRegister(): void
1228  {
1229  $key = ‪StringUtility::getUniqueId('someKey');
1230  $value = ‪StringUtility::getUniqueId('someValue');
1231  ‪$GLOBALS['TSFE']->register[$key] = $value;
1232 
1233  self::assertEquals($value, $this->subject->getData('register:' . $key));
1234  }
1235 
1241  public function ‪getDataWithTypeSession(): void
1242  {
1243  $frontendUser = $this->getMockBuilder(FrontendUserAuthentication::class)
1244  ->onlyMethods(['getSessionData'])
1245  ->getMock();
1246  $frontendUser->expects(self::once())->method('getSessionData')->with('myext')->willReturn([
1247  'mydata' => [
1248  'someValue' => 42,
1249  ],
1250  ]);
1251  ‪$GLOBALS['TSFE']->fe_user = $frontendUser;
1252 
1253  self::assertEquals(42, $this->subject->getData('session:myext|mydata|someValue'));
1254  }
1255 
1261  public function ‪getDataWithTypeLevel(): void
1262  {
1263  $rootline = [
1264  0 => ['uid' => 1, 'title' => 'title1'],
1265  1 => ['uid' => 2, 'title' => 'title2'],
1266  2 => ['uid' => 3, 'title' => 'title3'],
1267  ];
1268  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1269  self::assertEquals(2, $this->subject->getData('level'));
1270  }
1271 
1277  public function ‪getDataWithTypeLeveltitle(): void
1278  {
1279  $rootline = [
1280  0 => ['uid' => 1, 'title' => 'title1'],
1281  1 => ['uid' => 2, 'title' => 'title2'],
1282  2 => ['uid' => 3, 'title' => ''],
1283  ];
1284  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1285  self::assertEquals('', $this->subject->getData('leveltitle:-1'));
1286  // since "title3" is not set, it will slide to "title2"
1287  self::assertEquals('title2', $this->subject->getData('leveltitle:-1,slide'));
1288  }
1289 
1295  public function ‪getDataWithTypeLevelmedia(): void
1296  {
1297  $rootline = [
1298  0 => ['uid' => 1, 'title' => 'title1', 'media' => 'media1'],
1299  1 => ['uid' => 2, 'title' => 'title2', 'media' => 'media2'],
1300  2 => ['uid' => 3, 'title' => 'title3', 'media' => ''],
1301  ];
1302  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1303  self::assertEquals('', $this->subject->getData('levelmedia:-1'));
1304  // since "title3" is not set, it will slide to "title2"
1305  self::assertEquals('media2', $this->subject->getData('levelmedia:-1,slide'));
1306  }
1307 
1313  public function ‪getDataWithTypeLeveluid(): void
1314  {
1315  $rootline = [
1316  0 => ['uid' => 1, 'title' => 'title1'],
1317  1 => ['uid' => 2, 'title' => 'title2'],
1318  2 => ['uid' => 3, 'title' => 'title3'],
1319  ];
1320  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1321  self::assertEquals(3, $this->subject->getData('leveluid:-1'));
1322  // every element will have a uid - so adding slide doesn't really make sense, just for completeness
1323  self::assertEquals(3, $this->subject->getData('leveluid:-1,slide'));
1324  }
1325 
1331  public function ‪getDataWithTypeLevelfield(): void
1332  {
1333  $rootline = [
1334  0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1335  1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1336  2 => ['uid' => 3, 'title' => 'title3', 'testfield' => ''],
1337  ];
1338  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1339  self::assertEquals('', $this->subject->getData('levelfield:-1,testfield'));
1340  self::assertEquals('field2', $this->subject->getData('levelfield:-1,testfield,slide'));
1341  }
1342 
1348  public function ‪getDataWithTypeFullrootline(): void
1349  {
1350  $rootline1 = [
1351  0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1352  ];
1353  $rootline2 = [
1354  0 => ['uid' => 1, 'title' => 'title1', 'testfield' => 'field1'],
1355  1 => ['uid' => 2, 'title' => 'title2', 'testfield' => 'field2'],
1356  2 => ['uid' => 3, 'title' => 'title3', 'testfield' => 'field3'],
1357  ];
1358 
1359  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline1;
1360  ‪$GLOBALS['TSFE']->rootLine = $rootline2;
1361  self::assertEquals('field2', $this->subject->getData('fullrootline:-1,testfield'));
1362  }
1363 
1369  public function ‪getDataWithTypeDate(): void
1370  {
1371  $format = 'Y-M-D';
1372  $defaultFormat = 'd/m Y';
1373 
1374  self::assertEquals(date($format, ‪$GLOBALS['EXEC_TIME']), $this->subject->getData('date:' . $format));
1375  self::assertEquals(date($defaultFormat, ‪$GLOBALS['EXEC_TIME']), $this->subject->getData('date'));
1376  }
1377 
1383  public function ‪getDataWithTypePage(): void
1384  {
1385  ‪$uid = random_int(0, mt_getrandmax());
1386  ‪$GLOBALS['TSFE']->page['uid'] = ‪$uid;
1387  self::assertEquals(‪$uid, $this->subject->getData('page:uid'));
1388  }
1389 
1395  public function ‪getDataWithTypeCurrent(): void
1396  {
1397  $key = ‪StringUtility::getUniqueId('someKey');
1398  $value = ‪StringUtility::getUniqueId('someValue');
1399  $this->subject->data[$key] = $value;
1400  $this->subject->currentValKey = $key;
1401  self::assertEquals($value, $this->subject->getData('current'));
1402  }
1403 
1407  public function ‪getDataWithTypeDbReturnsCorrectTitle()
1408  {
1409  $dummyRecord = ['uid' => 5, 'title' => 'someTitle'];
1410  ‪$GLOBALS['TSFE']->sys_page->expects(self::once())->method('getRawRecord')->with('tt_content', '106')->willReturn($dummyRecord);
1411  self::assertSame('someTitle', $this->subject->getData('db:tt_content:106:title'));
1412  }
1414  public static function ‪getDataWithTypeDbDataProvider(): array
1415  {
1416  return [
1417  'identifier with missing table, uid and column' => [
1418  'identifier' => 'db',
1419  'expectsMethodCall' => self::never(),
1420  ],
1421  'identifier with empty table, missing uid and column' => [
1422  'identifier' => 'db:',
1423  'expectsMethodCall' => self::never(),
1424  ],
1425  'identifier with missing table and column' => [
1426  'identifier' => 'db:tt_content',
1427  'expectsMethodCall' => self::never(),
1428  ],
1429  'identifier with empty table and missing uid and column' => [
1430  'identifier' => 'db:tt_content:',
1431  'expectsMethodCall' => self::never(),
1432  ],
1433  'identifier with empty uid and missing column' => [
1434  'identifier' => 'db:tt_content:106',
1435  'expectsMethodCall' => self::once(),
1436  ],
1437  'identifier with empty uid and column' => [
1438  'identifier' => 'db:tt_content:106:',
1439  'expectsMethodCall' => self::once(),
1440  ],
1441  'identifier with empty uid and not existing column' => [
1442  'identifier' => 'db:tt_content:106:not_existing_column',
1443  'expectsMethodCall' => self::once(),
1444  ],
1445  ];
1446  }
1447 
1454  public function ‪getDataWithTypeDbReturnsEmptyStringOnInvalidIdentifiers(string ‪$identifier, InvocationOrder $expectsMethodCall): void
1455  {
1456  $dummyRecord = ['uid' => 5, 'title' => 'someTitle'];
1457  ‪$GLOBALS['TSFE']->sys_page->expects($expectsMethodCall)->method('getRawRecord')->with('tt_content', '106')->willReturn($dummyRecord);
1458  self::assertSame('', $this->subject->getData(‪$identifier));
1459  }
1460 
1466  public function ‪getDataWithTypeLll(): void
1467  {
1468  $key = ‪StringUtility::getUniqueId('someKey');
1469  $value = ‪StringUtility::getUniqueId('someValue');
1470  $languageServiceFactory = $this->createMock(LanguageServiceFactory::class);
1471  $languageServiceMock = $this->createMock(LanguageService::class);
1472  $languageServiceFactory->expects(self::once())->method('createFromSiteLanguage')->with(self::anything())->willReturn($languageServiceMock);
1473  GeneralUtility::addInstance(LanguageServiceFactory::class, $languageServiceFactory);
1474  $languageServiceMock->expects(self::once())->method('sL')->with('LLL:' . $key)->willReturn($value);
1475  self::assertEquals($value, $this->subject->getData('lll:' . $key));
1476  }
1477 
1483  public function ‪getDataWithTypePath(): void
1484  {
1485  $filenameIn = 'typo3/sysext/frontend/Public/Icons/Extension.svg';
1486  self::assertEquals($filenameIn, $this->subject->getData('path:' . $filenameIn));
1487  }
1488 
1494  public function ‪getDataWithTypeContext(): void
1495  {
1496  $context = new ‪Context([
1497  'workspace' => new WorkspaceAspect(3),
1498  'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [0, -1]),
1499  ]);
1500  GeneralUtility::setSingletonInstance(Context::class, $context);
1501  self::assertEquals(3, $this->subject->getData('context:workspace:id'));
1502  self::assertEquals('0,-1', $this->subject->getData('context:frontend.user:groupIds'));
1503  self::assertFalse($this->subject->getData('context:frontend.user:isLoggedIn'));
1504  self::assertSame('', $this->subject->getData('context:frontend.user:foozball'));
1505  }
1506 
1512  public function ‪getDataWithTypeSite(): void
1513  {
1514  $site = new Site('my-site', 123, [
1515  'base' => 'http://example.com',
1516  'custom' => [
1517  'config' => [
1518  'nested' => 'yeah',
1519  ],
1520  ],
1521  ]);
1522  $this->frontendControllerMock->_set('site', $site);
1523  self::assertEquals('http://example.com', $this->subject->getData('site:base'));
1524  self::assertEquals('yeah', $this->subject->getData('site:custom.config.nested'));
1525  }
1526 
1532  public function ‪getDataWithTypeSiteWithBaseVariants(): void
1533  {
1534  $packageManager = new PackageManager(new DependencyOrderingService());
1535  GeneralUtility::addInstance(ProviderConfigurationLoader::class, new ProviderConfigurationLoader(
1536  $packageManager,
1537  new NullFrontend('core'),
1538  'ExpressionLanguageProviders'
1539  ));
1540  GeneralUtility::addInstance(DefaultProvider::class, new DefaultProvider(new Typo3Version(), new Context(), new Features()));
1541 
1542  putenv('LOCAL_DEVELOPMENT=1');
1543 
1544  $site = new Site('my-site', 123, [
1545  'base' => 'http://prod.com',
1546  'baseVariants' => [
1547  [
1548  'base' => 'http://staging.com',
1549  'condition' => 'applicationContext == "Production/Staging"',
1550  ],
1551  [
1552  'base' => 'http://dev.com',
1553  'condition' => 'getenv("LOCAL_DEVELOPMENT") == 1',
1554  ],
1555  ],
1556  ]);
1557 
1558  $this->frontendControllerMock->_set('site', $site);
1559  self::assertEquals('http://dev.com', $this->subject->getData('site:base'));
1560  }
1561 
1567  public function ‪getDataWithTypeSiteLanguage(): void
1568  {
1569  $site = $this->‪createSiteWithLanguage([
1570  'base' => '/',
1571  'languageId' => 1,
1572  'locale' => 'de_DE',
1573  'title' => 'languageTitle',
1574  'navigationTitle' => 'German',
1575  ]);
1576  $language = $site->getLanguageById(1);
1577  $this->frontendControllerMock->_set('language', $language);
1578  self::assertEquals('German', $this->subject->getData('siteLanguage:navigationTitle'));
1579  self::assertEquals('de', $this->subject->getData('siteLanguage:twoLetterIsoCode'));
1580  self::assertEquals('de', $this->subject->getData('siteLanguage:locale:languageCode'));
1581  self::assertEquals('de-DE', $this->subject->getData('siteLanguage:hreflang'));
1582  self::assertEquals('de-DE', $this->subject->getData('siteLanguage:locale:full'));
1583  }
1584 
1590  public function ‪getDataWithTypeParentRecordNumber(): void
1591  {
1592  $recordNumber = random_int(0, mt_getrandmax());
1593  $this->subject->parentRecordNumber = $recordNumber;
1594  self::assertEquals($recordNumber, $this->subject->getData('cobj:parentRecordNumber'));
1595  }
1596 
1602  public function ‪getDataWithTypeDebugRootline(): void
1603  {
1604  $rootline = [
1605  0 => ['uid' => 1, 'title' => 'title1'],
1606  1 => ['uid' => 2, 'title' => 'title2'],
1607  2 => ['uid' => 3, 'title' => ''],
1608  ];
1609  $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)';
1610  ‪$GLOBALS['TSFE']->config['rootLine'] = $rootline;
1611 
1613  $result = $this->subject->getData('debug:rootLine');
1614  $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1615 
1616  self::assertEquals($expectedResult, $cleanedResult);
1617  }
1618 
1624  public function ‪getDataWithTypeDebugFullRootline(): void
1625  {
1626  $rootline = [
1627  0 => ['uid' => 1, 'title' => 'title1'],
1628  1 => ['uid' => 2, 'title' => 'title2'],
1629  2 => ['uid' => 3, 'title' => ''],
1630  ];
1631  $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)';
1632  ‪$GLOBALS['TSFE']->rootLine = $rootline;
1633 
1635  $result = $this->subject->getData('debug:fullRootLine');
1636  $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1637 
1638  self::assertEquals($expectedResult, $cleanedResult);
1639  }
1640 
1646  public function ‪getDataWithTypeDebugData(): void
1647  {
1648  $key = ‪StringUtility::getUniqueId('someKey');
1649  $value = ‪StringUtility::getUniqueId('someValue');
1650  $this->subject->data = [$key => $value];
1651 
1652  $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1653 
1655  $result = $this->subject->getData('debug:data');
1656  $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1657 
1658  self::assertEquals($expectedResult, $cleanedResult);
1659  }
1660 
1666  public function ‪getDataWithTypeDebugRegister(): void
1667  {
1668  $key = ‪StringUtility::getUniqueId('someKey');
1669  $value = ‪StringUtility::getUniqueId('someValue');
1670  ‪$GLOBALS['TSFE']->register = [$key => $value];
1671 
1672  $expectedResult = 'array(1item)' . $key . '=>"' . $value . '"(' . strlen($value) . 'chars)';
1673 
1675  $result = $this->subject->getData('debug:register');
1676  $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1677 
1678  self::assertEquals($expectedResult, $cleanedResult);
1679  }
1680 
1686  public function ‪getDataWithTypeDebugPage(): void
1687  {
1688  ‪$uid = random_int(0, mt_getrandmax());
1689  ‪$GLOBALS['TSFE']->page = ['uid' => ‪$uid];
1690 
1691  $expectedResult = 'array(1item)uid=>' . ‪$uid . '(integer)';
1692 
1694  $result = $this->subject->getData('debug:page');
1695  $cleanedResult = str_replace(["\r", "\n", "\t", ' '], '', $result);
1696 
1697  self::assertEquals($expectedResult, $cleanedResult);
1698  }
1699 
1703  public function ‪renderingContentObjectThrowsException(): void
1704  {
1705  $this->expectException(\LogicException::class);
1706  $this->expectExceptionCode(1414513947);
1707  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture(false);
1708  $this->subject->render($contentObjectFixture, []);
1709  }
1710 
1715  {
1717  new ApplicationContext('Production'),
1718  true,
1719  false,
1724  ‪Environment::getPublicPath() . '/index.php',
1725  ‪Environment::isWindows() ? 'WINDOWS' : 'UNIX'
1726  );
1727  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1728  $this->subject->render($contentObjectFixture, []);
1729  }
1730 
1735  {
1736  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1737 
1738  $configuration = [
1739  'exceptionHandler' => '1',
1740  ];
1741  $this->subject->render($contentObjectFixture, $configuration);
1742  }
1743 
1748  {
1749  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1750 
1751  $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
1752  $this->subject->render($contentObjectFixture, []);
1753  }
1754 
1759  {
1760  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture(false);
1761  $this->expectException(\LogicException::class);
1762  $this->expectExceptionCode(1414513947);
1763  $this->frontendControllerMock->config['config']['contentObjectExceptionHandler'] = '1';
1764  $configuration = [
1765  'exceptionHandler' => '0',
1766  ];
1767  $this->subject->render($contentObjectFixture, $configuration);
1768  }
1769 
1773  public function ‪renderedErrorMessageCanBeCustomized(): void
1774  {
1775  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1776 
1777  $configuration = [
1778  'exceptionHandler' => '1',
1779  'exceptionHandler.' => [
1780  'errorMessage' => 'New message for testing',
1781  ],
1782  ];
1783 
1784  self::assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
1785  }
1786 
1791  {
1792  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1793 
1794  $this->frontendControllerMock
1795  ->config['config']['contentObjectExceptionHandler.'] = [
1796  'errorMessage' => 'Global message for testing',
1797  ];
1798  $configuration = [
1799  'exceptionHandler' => '1',
1800  'exceptionHandler.' => [
1801  'errorMessage' => 'New message for testing',
1802  ],
1803  ];
1804 
1805  self::assertSame('New message for testing', $this->subject->render($contentObjectFixture, $configuration));
1806  }
1807 
1812  {
1813  $contentObjectFixture = $this->‪createContentObjectThrowingExceptionFixture();
1814 
1815  $configuration = [
1816  'exceptionHandler' => '1',
1817  'exceptionHandler.' => [
1818  'ignoreCodes.' => ['10.' => '1414513947'],
1819  ],
1820  ];
1821  $this->expectException(\LogicException::class);
1822  $this->expectExceptionCode(1414513947);
1823  $this->subject->render($contentObjectFixture, $configuration);
1824  }
1826  protected function ‪createContentObjectThrowingExceptionFixture(bool $addProductionExceptionHandlerInstance = true): ‪AbstractContentObject&MockObject
1827  {
1828  $contentObjectFixture = $this->getMockBuilder(AbstractContentObject::class)
1829  ->getMock();
1830  $contentObjectFixture->expects(self::once())
1831  ->method('render')
1832  ->willReturnCallback(static function () {
1833  throw new \LogicException('Exception during rendering', 1414513947);
1834  });
1835  $contentObjectFixture->‪setContentObjectRenderer($this->subject);
1836  if ($addProductionExceptionHandlerInstance) {
1837  GeneralUtility::addInstance(
1838  ProductionExceptionHandler::class,
1839  new ‪ProductionExceptionHandler(new ‪Context(), new ‪Random(), new NullLogger())
1840  );
1841  }
1842  return $contentObjectFixture;
1843  }
1845  protected function ‪getLibParseFunc(): array
1846  {
1847  return [
1848  'makelinks' => '1',
1849  'makelinks.' => [
1850  'http.' => [
1851  'keep' => '{$styles.content.links.keep}',
1852  'extTarget' => '',
1853  'mailto.' => [
1854  'keep' => 'path',
1855  ],
1856  ],
1857  ],
1858  'tags' => [
1859  'link' => 'TEXT',
1860  'link.' => [
1861  'current' => '1',
1862  'typolink.' => [
1863  'parameter.' => [
1864  'data' => 'parameters : allParams',
1865  ],
1866  ],
1867  'parseFunc.' => [
1868  'constants' => '1',
1869  ],
1870  ],
1871  ],
1872 
1873  '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',
1874  'denyTags' => '*',
1875  'sword' => '<span class="csc-sword">|</span>',
1876  'constants' => '1',
1877  'nonTypoTagStdWrap.' => [
1878  'HTMLparser' => '1',
1879  'HTMLparser.' => [
1880  'keepNonMatchedTags' => '1',
1881  'htmlSpecialChars' => '2',
1882  ],
1883  ],
1884  ];
1885  }
1887  public function ‪_parseFuncReturnsCorrectHtmlDataProvider(): array
1888  {
1889  return [
1890  'Text without tag is wrapped with <p> tag' => [
1891  'Text without tag',
1892  $this->‪getLibParseFunc_RTE(),
1893  '<p class="bodytext">Text without tag</p>',
1894  false,
1895  ],
1896  'Text wrapped with <p> tag remains the same' => [
1897  '<p class="myclass">Text with &lt;p&gt; tag</p>',
1898  $this->‪getLibParseFunc_RTE(),
1899  '<p class="myclass">Text with &lt;p&gt; tag</p>',
1900  false,
1901  ],
1902  'Text with absolute external link' => [
1903  'Text with <link http://example.com/foo/>external link</link>',
1904  $this->‪getLibParseFunc_RTE(),
1905  '<p class="bodytext">Text with <a href="http://example.com/foo/">external link</a></p>',
1906  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/foo/'))->withLinkText('external link'),
1907  ],
1908  'Empty lines are not duplicated' => [
1909  LF,
1910  $this->‪getLibParseFunc_RTE(),
1911  '<p class="bodytext">&nbsp;</p>',
1912  false,
1913  ],
1914  'Multiple empty lines with no text' => [
1915  LF . LF . LF,
1916  $this->‪getLibParseFunc_RTE(),
1917  '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">&nbsp;</p>',
1918  false,
1919  ],
1920  'Empty lines are not duplicated at the end of content' => [
1921  'test' . LF . LF,
1922  $this->‪getLibParseFunc_RTE(),
1923  '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
1924  false,
1925  ],
1926  'Empty lines are not trimmed' => [
1927  LF . 'test' . LF,
1928  $this->‪getLibParseFunc_RTE(),
1929  '<p class="bodytext">&nbsp;</p>' . LF . '<p class="bodytext">test</p>' . LF . '<p class="bodytext">&nbsp;</p>',
1930  false,
1931  ],
1932  ];
1933  }
1934 
1939  public function ‪stdWrap_parseFuncReturnsParsedHtml(string $value, array $configuration, string $expectedResult, ‪LinkResultInterface|false $linkResult): void
1940  {
1941  if ($linkResult !== false) {
1942  $linkFactory = $this->getMockBuilder(LinkFactory::class)->disableOriginalConstructor()->getMock();
1943  $linkFactory->method('create')->willReturn($linkResult);
1944  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
1945  }
1946  self::assertEquals($expectedResult, $this->subject->stdWrap_parseFunc($value, $configuration));
1947  }
1948 
1955  public static function ‪_parseFuncParsesNestedTagsProperlyDataProvider(): array
1956  {
1957  $defaultListItemParseFunc = [
1958  'parseFunc' => '',
1959  'parseFunc.' => [
1960  'tags.' => [
1961  'li' => 'TEXT',
1962  'li.' => [
1963  'wrap' => '<li>LI:|</li>',
1964  'current' => '1',
1965  ],
1966  ],
1967  ],
1968  ];
1969 
1970  return [
1971  'parent & child tags with same beginning are processed' => [
1972  '<div><any data-skip><anyother data-skip>content</anyother></any></div>',
1973  [
1974  'parseFunc' => '',
1975  'parseFunc.' => [
1976  'tags.' => [
1977  'any' => 'TEXT',
1978  'any.' => [
1979  'wrap' => '<any data-processed>|</any>',
1980  'current' => 1,
1981  ],
1982  'anyother' => 'TEXT',
1983  'anyother.' => [
1984  'wrap' => '<anyother data-processed>|</anyother>',
1985  'current' => 1,
1986  ],
1987  ],
1988  'htmlSanitize' => true,
1989  'htmlSanitize.' => [
1990  'build' => TestSanitizerBuilder::class,
1991  ],
1992  ],
1993  ],
1994  '<div><any data-processed><anyother data-processed>content</anyother></any></div>',
1995  ],
1996  'list with empty and filled li' => [
1997  '<ul>
1998  <li></li>
1999  <li>second</li>
2000 </ul>',
2001  $defaultListItemParseFunc,
2002  '<ul>
2003  <li>LI:</li>
2004  <li>LI:second</li>
2005 </ul>',
2006  ],
2007  'list with filled li wrapped by a div containing text' => [
2008  '<div>text<ul><li></li><li>second</li></ul></div>',
2009  $defaultListItemParseFunc,
2010  '<div>text<ul><li>LI:</li><li>LI:second</li></ul></div>',
2011  ],
2012  'link list with empty li modification' => [
2013  '<ul>
2014  <li>
2015  <ul>
2016  <li></li>
2017  </ul>
2018  </li>
2019 </ul>',
2020  $defaultListItemParseFunc,
2021  '<ul>
2022  <li>LI:
2023  <ul>
2024  <li>LI:</li>
2025  </ul>
2026  </li>
2027 </ul>',
2028  ],
2029 
2030  'link list with li modifications' => [
2031  '<ul>
2032  <li>first</li>
2033  <li>second
2034  <ul>
2035  <li>first sub</li>
2036  <li>second sub</li>
2037  </ul>
2038  </li>
2039 </ul>',
2040  $defaultListItemParseFunc,
2041  '<ul>
2042  <li>LI:first</li>
2043  <li>LI:second
2044  <ul>
2045  <li>LI:first sub</li>
2046  <li>LI:second sub</li>
2047  </ul>
2048  </li>
2049 </ul>',
2050  ],
2051  'link list with li modifications and no text' => [
2052  '<ul>
2053  <li>first</li>
2054  <li>
2055  <ul>
2056  <li>first sub</li>
2057  <li>second sub</li>
2058  </ul>
2059  </li>
2060 </ul>',
2061  $defaultListItemParseFunc,
2062  '<ul>
2063  <li>LI:first</li>
2064  <li>LI:
2065  <ul>
2066  <li>LI:first sub</li>
2067  <li>LI:second sub</li>
2068  </ul>
2069  </li>
2070 </ul>',
2071  ],
2072  'link list with li modifications on third level' => [
2073  '<ul>
2074  <li>first</li>
2075  <li>second
2076  <ul>
2077  <li>first sub
2078  <ul>
2079  <li>first sub sub</li>
2080  <li>second sub sub</li>
2081  </ul>
2082  </li>
2083  <li>second sub</li>
2084  </ul>
2085  </li>
2086 </ul>',
2087  $defaultListItemParseFunc,
2088  '<ul>
2089  <li>LI:first</li>
2090  <li>LI:second
2091  <ul>
2092  <li>LI:first sub
2093  <ul>
2094  <li>LI:first sub sub</li>
2095  <li>LI:second sub sub</li>
2096  </ul>
2097  </li>
2098  <li>LI:second sub</li>
2099  </ul>
2100  </li>
2101 </ul>',
2102  ],
2103  'link list with li modifications on third level no text' => [
2104  '<ul>
2105  <li>first</li>
2106  <li>
2107  <ul>
2108  <li>
2109  <ul>
2110  <li>first sub sub</li>
2111  <li>first sub sub</li>
2112  </ul>
2113  </li>
2114  <li>second sub</li>
2115  </ul>
2116  </li>
2117 </ul>',
2118  $defaultListItemParseFunc,
2119  '<ul>
2120  <li>LI:first</li>
2121  <li>LI:
2122  <ul>
2123  <li>LI:
2124  <ul>
2125  <li>LI:first sub sub</li>
2126  <li>LI:first sub sub</li>
2127  </ul>
2128  </li>
2129  <li>LI:second sub</li>
2130  </ul>
2131  </li>
2132 </ul>',
2133  ],
2134  'link list with ul and li modifications' => [
2135  '<ul>
2136  <li>first</li>
2137  <li>second
2138  <ul>
2139  <li>first sub</li>
2140  <li>second sub</li>
2141  </ul>
2142  </li>
2143 </ul>',
2144  [
2145  'parseFunc' => '',
2146  'parseFunc.' => [
2147  'tags.' => [
2148  'ul' => 'TEXT',
2149  'ul.' => [
2150  'wrap' => '<ul><li>intro</li>|<li>outro</li></ul>',
2151  'current' => '1',
2152  ],
2153  'li' => 'TEXT',
2154  'li.' => [
2155  'wrap' => '<li>LI:|</li>',
2156  'current' => '1',
2157  ],
2158  ],
2159  ],
2160  ],
2161  '<ul><li>intro</li>
2162  <li>LI:first</li>
2163  <li>LI:second
2164  <ul><li>intro</li>
2165  <li>LI:first sub</li>
2166  <li>LI:second sub</li>
2167  <li>outro</li></ul>
2168  </li>
2169 <li>outro</li></ul>',
2170  ],
2171 
2172  'link list with li containing p tag and sub list' => [
2173  '<ul>
2174  <li>first</li>
2175  <li>
2176  <ul>
2177  <li>
2178  <span>
2179  <ul>
2180  <li>first sub sub</li>
2181  <li>first sub sub</li>
2182  </ul>
2183  </span>
2184  </li>
2185  <li>second sub</li>
2186  </ul>
2187  </li>
2188 </ul>',
2189  $defaultListItemParseFunc,
2190  '<ul>
2191  <li>LI:first</li>
2192  <li>LI:
2193  <ul>
2194  <li>LI:
2195  <span>
2196  <ul>
2197  <li>LI:first sub sub</li>
2198  <li>LI:first sub sub</li>
2199  </ul>
2200  </span>
2201  </li>
2202  <li>LI:second sub</li>
2203  </ul>
2204  </li>
2205 </ul>',
2206  ],
2207  ];
2208  }
2209 
2214  public function ‪parseFuncParsesNestedTagsProperly(string $value, array $configuration, string $expectedResult): void
2215  {
2216  self::assertEquals($expectedResult, $this->subject->stdWrap_parseFunc($value, $configuration));
2217  }
2219  public static function ‪httpMakelinksDataProvider(): array
2220  {
2221  return [
2222  'http link' => [
2223  'Some text with a link http://example.com',
2224  [
2225  ],
2226  'Some text with a link <a href="http://example.com">example.com</a>',
2227  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com'))->withLinkText('example.com'),
2228  ],
2229  'http link with path' => [
2230  'Some text with a link http://example.com/path/to/page',
2231  [
2232  ],
2233  'Some text with a link <a href="http://example.com/path/to/page">example.com</a>',
2234  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page'))->withLinkText('example.com'),
2235  ],
2236  'http link with query parameter' => [
2237  'Some text with a link http://example.com?foo=bar',
2238  [
2239  ],
2240  'Some text with a link <a href="http://example.com?foo=bar">example.com</a>',
2241  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com?foo=bar'))->withLinkText('example.com'),
2242  ],
2243  'http link with question mark' => [
2244  'Some text with a link http://example.com?',
2245  [
2246  ],
2247  'Some text with a link <a href="http://example.com">example.com</a>?',
2248  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com'))->withLinkText('example.com'),
2249  ],
2250  'http link with period' => [
2251  'Some text with a link http://example.com.',
2252  [
2253  ],
2254  'Some text with a link <a href="http://example.com">example.com</a>.',
2255  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com'))->withLinkText('example.com'),
2256  ],
2257  'http link with fragment' => [
2258  'Some text with a link http://example.com#',
2259  [
2260  ],
2261  'Some text with a link <a href="http://example.com#">example.com</a>',
2262  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com#'))->withLinkText('example.com'),
2263  ],
2264  'http link with query parameter and fragment' => [
2265  'Some text with a link http://example.com?foo=bar#top',
2266  [
2267  ],
2268  'Some text with a link <a href="http://example.com?foo=bar#top">example.com</a>',
2269  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com?foo=bar#top'))->withLinkText('example.com'),
2270  ],
2271  'http link with query parameter and keep scheme' => [
2272  'Some text with a link http://example.com/path/to/page?foo=bar',
2273  [
2274  'keep' => 'scheme',
2275  ],
2276  'Some text with a link <a href="http://example.com/path/to/page?foo=bar">http://example.com</a>',
2277  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page?foo=bar'))->withLinkText('http://example.com'),
2278  ],
2279  'http link with query parameter and keep path' => [
2280  'Some text with a link http://example.com/path/to/page?foo=bar',
2281  [
2282  'keep' => 'path',
2283  ],
2284  'Some text with a link <a href="http://example.com/path/to/page?foo=bar">example.com/path/to/page</a>',
2285  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page?foo=bar'))->withLinkText('example.com/path/to/page'),
2286  ],
2287  'http link with query parameter and keep path with trailing slash' => [
2288  'Some text with a link http://example.com/path/to/page/?foo=bar',
2289  [
2290  'keep' => 'path',
2291  ],
2292  'Some text with a link <a href="http://example.com/path/to/page/?foo=bar">example.com/path/to/page/</a>',
2293  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page/?foo=bar'))->withLinkText('example.com/path/to/page/'),
2294  ],
2295  'http link with trailing slash and keep path with trailing slash' => [
2296  'Some text with a link http://example.com/',
2297  [
2298  'keep' => 'path',
2299  ],
2300  'Some text with a link <a href="http://example.com/">example.com</a>',
2301  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/'))->withLinkText('example.com'),
2302  ],
2303  'http link with query parameter and keep scheme,path' => [
2304  'Some text with a link http://example.com/path/to/page?foo=bar',
2305  [
2306  'keep' => 'scheme,path',
2307  ],
2308  'Some text with a link <a href="http://example.com/path/to/page?foo=bar">http://example.com/path/to/page</a>',
2309  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page?foo=bar'))->withLinkText('http://example.com/path/to/page'),
2310  ],
2311  'http link with multiple query parameters' => [
2312  'Some text with a link http://example.com?foo=bar&fuz=baz',
2313  [
2314  'keep' => 'scheme,path,query',
2315  ],
2316  'Some text with a link <a href="http://example.com?foo=bar&amp;fuz=baz">http://example.com?foo=bar&fuz=baz</a>',
2317  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com?foo=bar&fuz=baz'))->withLinkText('http://example.com?foo=bar&fuz=baz'),
2318  ],
2319  'http link with query parameter and keep scheme,path,query' => [
2320  'Some text with a link http://example.com/path/to/page?foo=bar',
2321  [
2322  'keep' => 'scheme,path,query',
2323  ],
2324  'Some text with a link <a href="http://example.com/path/to/page?foo=bar">http://example.com/path/to/page?foo=bar</a>',
2325  (new LinkResult(‪LinkService::TYPE_URL, 'http://example.com/path/to/page?foo=bar'))->withLinkText('http://example.com/path/to/page?foo=bar'),
2326  ],
2327  'https link' => [
2328  'Some text with a link https://example.com',
2329  [
2330  ],
2331  'Some text with a link <a href="https://example.com">example.com</a>',
2332  (new LinkResult(‪LinkService::TYPE_URL, 'https://example.com'))->withLinkText('example.com'),
2333  ],
2334  'http link with www' => [
2335  'Some text with a link http://www.example.com',
2336  [
2337  ],
2338  'Some text with a link <a href="http://www.example.com">www.example.com</a>',
2339  (new LinkResult(‪LinkService::TYPE_URL, 'http://www.example.com'))->withLinkText('www.example.com'),
2340  ],
2341  'https link with www' => [
2342  'Some text with a link https://www.example.com',
2343  [
2344  ],
2345  'Some text with a link <a href="https://www.example.com">www.example.com</a>',
2346  (new LinkResult(‪LinkService::TYPE_URL, 'https://www.example.com'))->withLinkText('www.example.com'),
2347  ],
2348  ];
2349  }
2350 
2355  public function ‪httpMakelinksReturnsLink(string $data, array $configuration, string $expectedResult, LinkResult $linkResult): void
2356  {
2357  $linkFactory = $this->getMockBuilder(LinkFactory::class)->disableOriginalConstructor()->getMock();
2358  $linkFactory->method('create')->willReturn($linkResult);
2359  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
2360 
2361  self::assertSame($expectedResult, $this->subject->_call('http_makelinks', $data, $configuration));
2362  }
2364  public static function ‪invalidHttpMakelinksDataProvider(): array
2365  {
2366  return [
2367  'only http protocol' => [
2368  'http://',
2369  [
2370  ],
2371  'http://',
2372  ],
2373  'only https protocol' => [
2374  'https://',
2375  [
2376  ],
2377  'https://',
2378  ],
2379  'ftp link' => [
2380  'ftp://user@password:example.com',
2381  [
2382  ],
2383  'ftp://user@password:example.com',
2384  ],
2385  ];
2386  }
2387 
2392  public function ‪httpMakelinksReturnsNoLink(string $data, array $configuration, string $expectedResult): void
2393  {
2394  self::assertSame($expectedResult, $this->subject->_call('http_makelinks', $data, $configuration));
2395  }
2397  public static function ‪mailtoMakelinksDataProvider(): array
2398  {
2399  return [
2400  'mailto link' => [
2401  'Some text with an email address mailto:john@example.com',
2402  [
2403  ],
2404  'Some text with an email address <a href="mailto:john@example.com">john@example.com</a>',
2405  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withLinkText('john@example.com'),
2406  ],
2407  'mailto link with subject parameter' => [
2408  'Some text with an email address mailto:john@example.com?subject=hi',
2409  [
2410  ],
2411  'Some text with an email address <a href="mailto:john@example.com?subject=hi">john@example.com</a>',
2412  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com?subject=hi'))->withLinkText('john@example.com'),
2413  ],
2414  'mailto link with multiple parameters' => [
2415  'Some text with an email address mailto:john@example.com?subject=Greetings&body=Hi+John',
2416  [
2417  ],
2418  'Some text with an email address <a href="mailto:john@example.com?subject=Greetings&amp;body=Hi+John">john@example.com</a>',
2419  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com?subject=Greetings&body=Hi+John'))->withLinkText('john@example.com'),
2420  ],
2421  'mailto link with question mark' => [
2422  'Some text with an email address mailto:john@example.com?',
2423  [
2424  ],
2425  'Some text with an email address <a href="mailto:john@example.com">john@example.com</a>?',
2426  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withLinkText('john@example.com'),
2427  ],
2428  'mailto link with period' => [
2429  'Some text with an email address mailto:john@example.com.',
2430  [
2431  ],
2432  'Some text with an email address <a href="mailto:john@example.com">john@example.com</a>.',
2433  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withLinkText('john@example.com'),
2434  ],
2435  'mailto link with wrap' => [
2436  'Some text with an email address mailto:john@example.com.',
2437  [
2438  'wrap' => '<span>|</span>',
2439  ],
2440  'Some text with an email address <span><a href="mailto:john@example.com">john@example.com</a></span>.',
2441  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withLinkText('john@example.com'),
2442  ],
2443  'mailto link with ATagBeforeWrap' => [
2444  'Some text with an email address mailto:john@example.com.',
2445  [
2446  'wrap' => '<span>|</span>',
2447  'ATagBeforeWrap' => 1,
2448  ],
2449  'Some text with an email address <a href="mailto:john@example.com"><span>john@example.com</span></a>.',
2450  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withLinkText('john@example.com'),
2451  ],
2452  'mailto link with ATagParams' => [
2453  'Some text with an email address mailto:john@example.com.',
2454  [
2455  'ATagParams' => 'class="email"',
2456  ],
2457  'Some text with an email address <a href="mailto:john@example.com" class="email">john@example.com</a>.',
2458  (new LinkResult(‪LinkService::TYPE_EMAIL, 'mailto:john@example.com'))->withAttribute('class', 'email')->withLinkText('john@example.com'),
2459  ],
2460  ];
2461  }
2462 
2467  public function ‪mailtoMakelinksReturnsMailToLink(string $data, array $configuration, string $expectedResult, LinkResult $linkResult): void
2468  {
2469  $linkFactory = $this->getMockBuilder(LinkFactory::class)->disableOriginalConstructor()->getMock();
2470  $linkFactory->method('create')->willReturn($linkResult);
2471  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
2472 
2473  self::assertSame($expectedResult, $this->subject->_call('mailto_makelinks', $data, $configuration));
2474  }
2475 
2479  public function ‪mailtoMakelinksReturnsNoMailToLink(): void
2480  {
2481  self::assertSame('mailto:', $this->subject->_call('mailto_makelinks', 'mailto:', []));
2482  }
2484  public static function ‪typolinkReturnsCorrectLinksForEmailsAndUrlsDataProvider(): array
2485  {
2486  return [
2487  'Link to url' => [
2488  'TYPO3',
2489  [
2490  'directImageLink' => false,
2491  'parameter' => 'http://typo3.org',
2492  ],
2493  '<a href="http://typo3.org">TYPO3</a>',
2494  ],
2495  'Link to url without schema' => [
2496  'TYPO3',
2497  [
2498  'directImageLink' => false,
2499  'parameter' => 'typo3.org',
2500  ],
2501  '<a href="http://typo3.org">TYPO3</a>',
2502  ],
2503  'Link to url without link text' => [
2504  '',
2505  [
2506  'directImageLink' => false,
2507  'parameter' => 'http://typo3.org',
2508  ],
2509  '<a href="http://typo3.org">http://typo3.org</a>',
2510  ],
2511  'Link to url with attributes' => [
2512  'TYPO3',
2513  [
2514  'parameter' => 'http://typo3.org',
2515  'ATagParams' => 'class="url-class"',
2516  'extTarget' => '_blank',
2517  'title' => 'Open new window',
2518  ],
2519  '<a href="http://typo3.org" target="_blank" class="url-class" rel="noreferrer" title="Open new window">TYPO3</a>',
2520  ],
2521  'Link to url with attributes and custom target name' => [
2522  'TYPO3',
2523  [
2524  'parameter' => 'http://typo3.org',
2525  'ATagParams' => 'class="url-class"',
2526  'extTarget' => 'someTarget',
2527  'title' => 'Open new window',
2528  ],
2529  '<a href="http://typo3.org" target="someTarget" class="url-class" rel="noreferrer" title="Open new window">TYPO3</a>',
2530  ],
2531  'Link to url with attributes in parameter' => [
2532  'TYPO3',
2533  [
2534  'parameter' => 'http://typo3.org _blank url-class "Open new window"',
2535  ],
2536  '<a href="http://typo3.org" target="_blank" rel="noreferrer" title="Open new window" class="url-class">TYPO3</a>',
2537  ],
2538  'Link to url with attributes in parameter and custom target name' => [
2539  'TYPO3',
2540  [
2541  'parameter' => 'http://typo3.org someTarget url-class "Open new window"',
2542  ],
2543  '<a href="http://typo3.org" target="someTarget" rel="noreferrer" title="Open new window" class="url-class">TYPO3</a>',
2544  ],
2545  'Link to url with script tag' => [
2546  '',
2547  [
2548  'directImageLink' => false,
2549  'parameter' => 'http://typo3.org<script>alert(123)</script>',
2550  ],
2551  '<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>',
2552  ],
2553  'Link to email address' => [
2554  'Email address',
2555  [
2556  'parameter' => 'foo@bar.org',
2557  ],
2558  '<a href="mailto:foo@bar.org">Email address</a>',
2559  ],
2560  'Link to email address with subject + cc' => [
2561  'Email address',
2562  [
2563  'parameter' => 'foo@bar.org?subject=This%20is%20a%20test',
2564  ],
2565  '<a href="mailto:foo@bar.org?subject=This%20is%20a%20test">Email address</a>',
2566  ],
2567  'Link to email address without link text' => [
2568  '',
2569  [
2570  'parameter' => 'foo@bar.org',
2571  ],
2572  '<a href="mailto:foo@bar.org">foo@bar.org</a>',
2573  ],
2574  'Link to email with attributes' => [
2575  'Email address',
2576  [
2577  'parameter' => 'foo@bar.org',
2578  'ATagParams' => 'class="email-class"',
2579  'title' => 'Write an email',
2580  ],
2581  '<a href="mailto:foo@bar.org" class="email-class" title="Write an email">Email address</a>',
2582  ],
2583  'Link to email with attributes in parameter' => [
2584  'Email address',
2585  [
2586  'parameter' => 'foo@bar.org - email-class "Write an email"',
2587  ],
2588  '<a href="mailto:foo@bar.org" title="Write an email" class="email-class">Email address</a>',
2589  ],
2590  'Link url using stdWrap' => [
2591  'TYPO3',
2592  [
2593  'parameter' => 'http://typo3.org',
2594  'parameter.' => [
2595  'cObject' => 'TEXT',
2596  'cObject.' => [
2597  'value' => 'http://typo3.com',
2598  ],
2599  ],
2600  ],
2601  '<a href="http://typo3.com">TYPO3</a>',
2602  ],
2603  'Link url using stdWrap with class attribute in parameter' => [
2604  'TYPO3',
2605  [
2606  'parameter' => 'http://typo3.org - url-class',
2607  'parameter.' => [
2608  'cObject' => 'TEXT',
2609  'cObject.' => [
2610  'value' => 'http://typo3.com',
2611  ],
2612  ],
2613  ],
2614  '<a href="http://typo3.com" class="url-class">TYPO3</a>',
2615  ],
2616  'Link url using stdWrap with class attribute in parameter and overridden target' => [
2617  'TYPO3',
2618  [
2619  'parameter' => 'http://typo3.org default-target url-class',
2620  'parameter.' => [
2621  'cObject' => 'TEXT',
2622  'cObject.' => [
2623  'value' => 'http://typo3.com new-target different-url-class',
2624  ],
2625  ],
2626  ],
2627  '<a href="http://typo3.com" target="new-target" rel="noreferrer" class="different-url-class">TYPO3</a>',
2628  ],
2629  'Link url using stdWrap with class attribute in parameter and overridden target and returnLast' => [
2630  'TYPO3',
2631  [
2632  'parameter' => 'http://typo3.org default-target url-class',
2633  'parameter.' => [
2634  'cObject' => 'TEXT',
2635  'cObject.' => [
2636  'value' => 'http://typo3.com new-target different-url-class',
2637  ],
2638  ],
2639  'returnLast' => 'url',
2640  ],
2641  'http://typo3.com',
2642  ],
2643  ];
2644  }
2645 
2650  public function ‪typolinkReturnsCorrectLinksForEmailsAndUrls(string $linkText, array $configuration, string $expectedResult): void
2651  {
2652  $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
2653  $typoScriptFrontendControllerMockObject->config = [
2654  'config' => [],
2655  ];
2656  ‪$GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2657 
2658  $this->cacheManagerMock->method('getCache')->willReturnMap([
2659  ['runtime', new ‪NullFrontend('dummy')],
2660  ['core', new ‪NullFrontend('runtime')],
2661  ]);
2662 
2663  $siteConfiguration = new ‪SiteConfiguration(
2664  ‪Environment::getConfigPath() . '/sites',
2665  new class () implements EventDispatcherInterface {
2666  public function dispatch(object $event)
2667  {
2668  return $event;
2669  }
2670  },
2671  new NullFrontend('dummy')
2672  );
2673  $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2674  GeneralUtility::addInstance(ProviderConfigurationLoader::class, $this->createMock(ProviderConfigurationLoader::class));
2675  GeneralUtility::addInstance(DefaultProvider::class, new DefaultProvider(new Typo3Version(), new ‪Context(), new Features()));
2676  GeneralUtility::addInstance(LinkFactory::class, $this->‪getLinkFactory($siteConfiguration));
2677 
2678  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
2679  // This can vanish when the condition provider have been moved towards service provider setup
2680  GeneralUtility::purgeInstances();
2681  }
2682 
2688  array $settings,
2689  string $linkText,
2690  string $mailAddress,
2691  string $expected
2692  ): void {
2693  $this->‪getFrontendController()->config['config'] = $settings;
2694  $typoScript = ['parameter' => $mailAddress];
2695  GeneralUtility::addInstance(LinkFactory::class, $this->‪getLinkFactory());
2696  self::assertEquals($expected, $this->subject->typoLink($linkText, $typoScript));
2697  }
2699  public static function ‪typoLinkEncodesMailAddressForSpamProtectionDataProvider(): array
2700  {
2701  return [
2702  'plain mail without mailto scheme' => [
2703  [
2704  'spamProtectEmailAddresses' => 0,
2705  'spamProtectEmailAddresses_atSubst' => '',
2706  'spamProtectEmailAddresses_lastDotSubst' => '',
2707  ],
2708  'some.body@test.typo3.org',
2709  'some.body@test.typo3.org',
2710  '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2711  ],
2712  'plain mail with mailto scheme' => [
2713  [
2714  'spamProtectEmailAddresses' => 0,
2715  'spamProtectEmailAddresses_atSubst' => '',
2716  'spamProtectEmailAddresses_lastDotSubst' => '',
2717  ],
2718  'some.body@test.typo3.org',
2719  'mailto:some.body@test.typo3.org',
2720  '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2721  ],
2722  'plain with at and dot substitution' => [
2723  [
2724  'spamProtectEmailAddresses' => 0,
2725  'spamProtectEmailAddresses_atSubst' => '(at)',
2726  'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2727  ],
2728  'some.body@test.typo3.org',
2729  'mailto:some.body@test.typo3.org',
2730  '<a href="mailto:some.body@test.typo3.org">some.body@test.typo3.org</a>',
2731  ],
2732  'mono-alphabetic substitution offset +1' => [
2733  [
2734  'spamProtectEmailAddresses' => 1,
2735  'spamProtectEmailAddresses_atSubst' => '',
2736  'spamProtectEmailAddresses_lastDotSubst' => '',
2737  ],
2738  'some.body@test.typo3.org',
2739  'mailto:some.body@test.typo3.org',
2740  '<a href="#" data-mailto-token="nbjmup+tpnf/cpezAuftu/uzqp4/psh" data-mailto-vector="1">some.body(at)test.typo3.org</a>',
2741  ],
2742  'mono-alphabetic substitution offset +1 with at substitution' => [
2743  [
2744  'spamProtectEmailAddresses' => 1,
2745  'spamProtectEmailAddresses_atSubst' => '@',
2746  'spamProtectEmailAddresses_lastDotSubst' => '',
2747  ],
2748  'some.body@test.typo3.org',
2749  'mailto:some.body@test.typo3.org',
2750  '<a href="#" data-mailto-token="nbjmup+tpnf/cpezAuftu/uzqp4/psh" data-mailto-vector="1">some.body@test.typo3.org</a>',
2751  ],
2752  'mono-alphabetic substitution offset +1 with at and dot substitution' => [
2753  [
2754  'spamProtectEmailAddresses' => 1,
2755  'spamProtectEmailAddresses_atSubst' => '(at)',
2756  'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2757  ],
2758  'some.body@test.typo3.org',
2759  'mailto:some.body@test.typo3.org',
2760  '<a href="#" data-mailto-token="nbjmup+tpnf/cpezAuftu/uzqp4/psh" data-mailto-vector="1">some.body(at)test.typo3(dot)org</a>',
2761  ],
2762  'mono-alphabetic substitution offset -1 with at and dot substitution' => [
2763  [
2764  'spamProtectEmailAddresses' => -1,
2765  'spamProtectEmailAddresses_atSubst' => '(at)',
2766  'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2767  ],
2768  'some.body@test.typo3.org',
2769  'mailto:some.body@test.typo3.org',
2770  '<a href="#" data-mailto-token="lzhksn9rnld-ancxZsdrs-sxon2-nqf" data-mailto-vector="-1">some.body(at)test.typo3(dot)org</a>',
2771  ],
2772  'mono-alphabetic substitution offset -1 with at and dot markup substitution' => [
2773  [
2774  'spamProtectEmailAddresses' => -1,
2775  'spamProtectEmailAddresses_atSubst' => '<span class="at"></span>',
2776  'spamProtectEmailAddresses_lastDotSubst' => '<span class="dot"></span>',
2777  ],
2778  'some.body@test.typo3.org',
2779  'mailto:some.body@test.typo3.org',
2780  '<a href="#" data-mailto-token="lzhksn9rnld-ancxZsdrs-sxon2-nqf" data-mailto-vector="-1">some.body<span class="at"></span>test.typo3<span class="dot"></span>org</a>',
2781  ],
2782  'mono-alphabetic substitution offset 2 with at and dot substitution and encoded subject' => [
2783  [
2784  'spamProtectEmailAddresses' => 2,
2785  'spamProtectEmailAddresses_atSubst' => '(at)',
2786  'spamProtectEmailAddresses_lastDotSubst' => '(dot)',
2787  ],
2788  'some.body@test.typo3.org',
2789  'mailto:some.body@test.typo3.org?subject=foo%20bar',
2790  '<a href="#" data-mailto-token="ocknvq,uqog0dqfaBvguv0varq50qti?uwdlgev=hqq%42dct" data-mailto-vector="2">some.body(at)test.typo3(dot)org</a>',
2791  ],
2792  ];
2793  }
2795  public static function ‪typolinkReturnsCorrectLinksFilesDataProvider(): array
2796  {
2797  return [
2798  'Link to file' => [
2799  'My file',
2800  [
2801  'directImageLink' => false,
2802  'parameter' => 'fileadmin/foo.bar',
2803  ],
2804  '<a href="fileadmin/foo.bar">My file</a>',
2805  ],
2806  'Link to file without link text' => [
2807  '',
2808  [
2809  'directImageLink' => false,
2810  'parameter' => 'fileadmin/foo.bar',
2811  ],
2812  '<a href="fileadmin/foo.bar">fileadmin/foo.bar</a>',
2813  ],
2814  'Link to file with attributes' => [
2815  'My file',
2816  [
2817  'parameter' => 'fileadmin/foo.bar',
2818  'ATagParams' => 'class="file-class"',
2819  'fileTarget' => '_blank',
2820  'title' => 'Title of the file',
2821  ],
2822  '<a href="fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
2823  ],
2824  'Link to file with attributes and additional href' => [
2825  'My file',
2826  [
2827  'parameter' => 'fileadmin/foo.bar',
2828  'ATagParams' => 'href="foo-bar"',
2829  'fileTarget' => '_blank',
2830  'title' => 'Title of the file',
2831  ],
2832  '<a href="fileadmin/foo.bar" target="_blank" title="Title of the file">My file</a>',
2833  ],
2834  'Link to file with attributes and additional href and class' => [
2835  'My file',
2836  [
2837  'parameter' => 'fileadmin/foo.bar',
2838  'ATagParams' => 'href="foo-bar" class="file-class"',
2839  'fileTarget' => '_blank',
2840  'title' => 'Title of the file',
2841  ],
2842  '<a href="fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
2843  ],
2844  'Link to file with attributes and additional class and href' => [
2845  'My file',
2846  [
2847  'parameter' => 'fileadmin/foo.bar',
2848  'ATagParams' => 'class="file-class" href="foo-bar"',
2849  'fileTarget' => '_blank',
2850  'title' => 'Title of the file',
2851  ],
2852  '<a href="fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
2853  ],
2854  'Link to file with attributes and additional class and href and title' => [
2855  'My file',
2856  [
2857  'parameter' => 'fileadmin/foo.bar',
2858  'ATagParams' => 'class="file-class" href="foo-bar" title="foo-bar"',
2859  'fileTarget' => '_blank',
2860  'title' => 'Title of the file',
2861  ],
2862  '<a href="fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
2863  ],
2864  'Link to file with attributes and empty ATagParams' => [
2865  'My file',
2866  [
2867  'parameter' => 'fileadmin/foo.bar',
2868  'ATagParams' => '',
2869  'fileTarget' => '_blank',
2870  'title' => 'Title of the file',
2871  ],
2872  '<a href="fileadmin/foo.bar" target="_blank" title="Title of the file">My file</a>',
2873  ],
2874  'Link to file with attributes in parameter' => [
2875  'My file',
2876  [
2877  'parameter' => 'fileadmin/foo.bar _blank file-class "Title of the file"',
2878  ],
2879  '<a href="fileadmin/foo.bar" target="_blank" title="Title of the file" class="file-class">My file</a>',
2880  ],
2881  'Link to file with script tag in name' => [
2882  '',
2883  [
2884  'directImageLink' => false,
2885  'parameter' => 'fileadmin/<script>alert(123)</script>',
2886  ],
2887  '<a href="fileadmin/&lt;script&gt;alert(123)&lt;/script&gt;">fileadmin/&lt;script&gt;alert(123)&lt;/script&gt;</a>',
2888  ],
2889  ];
2890  }
2891 
2896  public function ‪typolinkReturnsCorrectLinksFiles(string $linkText, array $configuration, string $expectedResult): void
2897  {
2898  $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
2899  $typoScriptFrontendControllerMockObject->config = [
2900  'config' => [],
2901  ];
2902  ‪$GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
2903 
2904  $resourceFactory = $this->getMockBuilder(ResourceFactory::class)->disableOriginalConstructor()->getMock();
2905  GeneralUtility::setSingletonInstance(ResourceFactory::class, $resourceFactory);
2906 
2907  $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
2908  GeneralUtility::addInstance(LinkFactory::class, $this->‪getLinkFactory());
2909 
2910  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
2911  }
2914  {
2915  return [
2916  'Link to file' => [
2917  'My file',
2918  [
2919  'directImageLink' => false,
2920  'parameter' => 'fileadmin/foo.bar',
2921  ],
2922  '/',
2923  '<a href="/fileadmin/foo.bar">My file</a>',
2924  ],
2925  'Link to file with longer absRefPrefix' => [
2926  'My file',
2927  [
2928  'directImageLink' => false,
2929  'parameter' => 'fileadmin/foo.bar',
2930  ],
2931  '/sub/',
2932  '<a href="/sub/fileadmin/foo.bar">My file</a>',
2933  ],
2934  'Link to absolute file' => [
2935  'My file',
2936  [
2937  'directImageLink' => false,
2938  'parameter' => '/images/foo.bar',
2939  ],
2940  '/',
2941  '<a href="/images/foo.bar">My file</a>',
2942  ],
2943  'Link to absolute file with longer absRefPrefix' => [
2944  'My file',
2945  [
2946  'directImageLink' => false,
2947  'parameter' => '/images/foo.bar',
2948  ],
2949  '/sub/',
2950  '<a href="/images/foo.bar">My file</a>',
2951  ],
2952  'Link to absolute file with identical longer absRefPrefix' => [
2953  'My file',
2954  [
2955  'directImageLink' => false,
2956  'parameter' => '/sub/fileadmin/foo.bar',
2957  ],
2958  '/sub/',
2959  '<a href="/sub/fileadmin/foo.bar">My file</a>',
2960  ],
2961  'Link to file with empty absRefPrefix' => [
2962  'My file',
2963  [
2964  'directImageLink' => false,
2965  'parameter' => 'fileadmin/foo.bar',
2966  ],
2967  '',
2968  '<a href="fileadmin/foo.bar">My file</a>',
2969  ],
2970  'Link to absolute file with empty absRefPrefix' => [
2971  'My file',
2972  [
2973  'directImageLink' => false,
2974  'parameter' => '/fileadmin/foo.bar',
2975  ],
2976  '',
2977  '<a href="/fileadmin/foo.bar">My file</a>',
2978  ],
2979  'Link to file with attributes with absRefPrefix' => [
2980  'My file',
2981  [
2982  'parameter' => 'fileadmin/foo.bar',
2983  'ATagParams' => 'class="file-class"',
2984  'fileTarget' => '_blank',
2985  'title' => 'Title of the file',
2986  ],
2987  '/',
2988  '<a href="/fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
2989  ],
2990  'Link to file with attributes with longer absRefPrefix' => [
2991  'My file',
2992  [
2993  'parameter' => 'fileadmin/foo.bar',
2994  'ATagParams' => 'class="file-class"',
2995  'fileTarget' => '_blank',
2996  'title' => 'Title of the file',
2997  ],
2998  '/sub/',
2999  '<a href="/sub/fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
3000  ],
3001  'Link to absolute file with attributes with absRefPrefix' => [
3002  'My file',
3003  [
3004  'parameter' => '/images/foo.bar',
3005  'ATagParams' => 'class="file-class"',
3006  'fileTarget' => '_blank',
3007  'title' => 'Title of the file',
3008  ],
3009  '/',
3010  '<a href="/images/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
3011  ],
3012  'Link to absolute file with attributes with longer absRefPrefix' => [
3013  'My file',
3014  [
3015  'parameter' => '/images/foo.bar',
3016  'ATagParams' => 'class="file-class"',
3017  'fileTarget' => '_blank',
3018  'title' => 'Title of the file',
3019  ],
3020  '/sub/',
3021  '<a href="/images/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
3022  ],
3023  'Link to absolute file with attributes with identical longer absRefPrefix' => [
3024  'My file',
3025  [
3026  'parameter' => '/sub/fileadmin/foo.bar',
3027  'ATagParams' => 'class="file-class"',
3028  'fileTarget' => '_blank',
3029  'title' => 'Title of the file',
3030  ],
3031  '/sub/',
3032  '<a href="/sub/fileadmin/foo.bar" target="_blank" class="file-class" title="Title of the file">My file</a>',
3033  ],
3034  ];
3035  }
3036 
3041  public function ‪typolinkReturnsCorrectLinksForFilesWithAbsRefPrefix(string $linkText, array $configuration, string $absRefPrefix, string $expectedResult): void
3042  {
3043  $resourceFactory = $this->getMockBuilder(ResourceFactory::class)->disableOriginalConstructor()->getMock();
3044  GeneralUtility::setSingletonInstance(ResourceFactory::class, $resourceFactory);
3045 
3046  $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
3047  $typoScriptFrontendControllerMockObject->config = [
3048  'config' => [],
3049  ];
3050  ‪$GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
3051  ‪$GLOBALS['TSFE']->absRefPrefix = $absRefPrefix;
3052  $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
3053  GeneralUtility::addInstance(LinkFactory::class, $this->‪getLinkFactory());
3054 
3055  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
3056  }
3057 
3061  public function ‪typolinkOpensInNewWindow(): void
3062  {
3063  $siteConfiguration = new SiteConfiguration(
3064  ‪Environment::getConfigPath() . '/sites',
3065  new class () implements EventDispatcherInterface {
3066  public function dispatch(object $event)
3067  {
3068  return $event;
3069  }
3070  },
3071  new NullFrontend('dummy')
3072  );
3073  $this->cacheManagerMock->method('getCache')->willReturnMap([
3074  ['runtime', new NullFrontend('dummy')],
3075  ['core', new NullFrontend('runtime')],
3076  ]);
3077  GeneralUtility::addInstance(ProviderConfigurationLoader::class, $this->createMock(ProviderConfigurationLoader::class));
3078  GeneralUtility::addInstance(DefaultProvider::class, new DefaultProvider(new Typo3Version(), new ‪Context(), new Features()));
3079  $linkFactory = $this->‪getLinkFactory($siteConfiguration);
3080  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3081  $linkText = 'Nice Text';
3082  $configuration = [
3083  'parameter' => 'https://example.com 13x84:target=myexample',
3084  ];
3085  $expectedResult = '<a href="https://example.com" target="myexample" data-window-url="https://example.com" data-window-target="myexample" data-window-features="width=13,height=84" rel="noreferrer">Nice Text</a>';
3086  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
3087  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3088  $linkText = 'Nice Text with default window name';
3089  $configuration = [
3090  'parameter' => 'https://example.com 13x84',
3091  ];
3092  $expectedResult = '<a href="https://example.com" target="FEopenLink" data-window-url="https://example.com" data-window-target="FEopenLink" data-window-features="width=13,height=84" rel="noreferrer">Nice Text with default window name</a>';
3093  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
3094  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3095 
3096  $linkText = 'Nice Text with default window name';
3097  $configuration = [
3098  'parameter' => 'https://example.com 13x84',
3099  ];
3100  $expectedResult = '<a href="https://example.com" target="FEopenLink" data-window-url="https://example.com" data-window-target="FEopenLink" data-window-features="width=13,height=84" rel="noreferrer">Nice Text with default window name</a>';
3101  self::assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration));
3102  // This can vanish when the condition provider have been moved towards service provider setup
3103  GeneralUtility::purgeInstances();
3104  }
3105 
3110  {
3111  $linkService = $this->getMockBuilder(LinkService::class)->disableOriginalConstructor()->getMock();
3112  $linkService->method('resolve')->with('foo')->willThrowException(new InvalidPathException('', 1666303735));
3113  $linkFactory = $this->‪getLinkFactory(null, $linkService);
3114  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3115  self::assertSame('foo', $this->subject->typoLink('foo', ['parameter' => 'foo']));
3116  }
3117 
3121  public function ‪typoLinkLogsErrorIfNoLinkResolvingIsPossible(): void
3122  {
3123  $linkService = $this->getMockBuilder(LinkService::class)->disableOriginalConstructor()->getMock();
3124  $linkService->method('resolve')->with('foo')->willThrowException(new InvalidPathException('', 1666303765));
3125  $linkFactory = $this->‪getLinkFactory(null, $linkService);
3126  $logger = $this->getMockBuilder(Logger::class)->disableOriginalConstructor()->getMock();
3127  $logger->expects(self::atLeastOnce())->method('warning')->with('The link could not be generated', self::anything());
3128  $linkFactory->setLogger($logger);
3129  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3130 
3131  $this->subject->typoLink('foo', ['parameter' => 'foo']);
3132  }
3133 
3137  public function ‪typolinkLinkResult(): void
3138  {
3139  $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
3140  $typoScriptFrontendControllerMockObject->config = [
3141  'config' => [],
3142  ];
3143  ‪$GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
3144 
3145  $resourceFactory = $this->getMockBuilder(ResourceFactory::class)->disableOriginalConstructor()->getMock();
3146  GeneralUtility::setSingletonInstance(ResourceFactory::class, $resourceFactory);
3147 
3148  $this->cacheManagerMock->method('getCache')->willReturnMap([
3149  ['runtime', new NullFrontend('dummy')],
3150  ['core', new NullFrontend('runtime')],
3151  ]);
3152 
3153  $siteConfiguration = new SiteConfiguration(
3154  ‪Environment::getConfigPath() . '/sites',
3155  new class () implements EventDispatcherInterface {
3156  public function dispatch(object $event)
3157  {
3158  return $event;
3159  }
3160  },
3161  new NullFrontend('dummy')
3162  );
3163  GeneralUtility::addInstance(ProviderConfigurationLoader::class, $this->createMock(ProviderConfigurationLoader::class));
3164  GeneralUtility::addInstance(DefaultProvider::class, new DefaultProvider(new Typo3Version(), new Context(), new Features()));
3165  $linkFactory = $this->‪getLinkFactory($siteConfiguration);
3166  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3167 
3168  $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
3169 
3170  $linkResult = $this->subject->typoLink(
3171  '',
3172  [
3173  'parameter' => 'https://example.tld',
3174  'returnLast' => 'result',
3175  ]
3176  );
3177  self::assertInstanceOf(LinkResultInterface::class, $linkResult);
3178  self::assertEquals(json_encode([
3179  'href' => 'https://example.tld',
3180  'target' => null,
3181  'class' => null,
3182  'title' => null,
3183  'linkText' => 'https://example.tld',
3184  'additionalAttributes' => [], ]), json_encode($linkResult));
3185  self::assertEquals(json_encode([
3186  'href' => 'https://example.tld',
3187  'target' => null,
3188  'class' => null,
3189  'title' => null,
3190  'linkText' => 'https://example.tld',
3191  'additionalAttributes' => [], ]), (string)$linkResult);
3192  // This can vanish when the condition provider have been moved towards service provider setup
3193  GeneralUtility::purgeInstances();
3194  }
3195 
3200  public function ‪typoLinkProperlyEncodesLinkResult(string $linkText, array $configuration, string $expectedResult): void
3201  {
3202  $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class);
3203  $typoScriptFrontendControllerMockObject->config = [
3204  'config' => [],
3205  ];
3206  ‪$GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject;
3207 
3208  $resourceFactory = $this->getMockBuilder(ResourceFactory::class)->disableOriginalConstructor()->getMock();
3209  GeneralUtility::setSingletonInstance(ResourceFactory::class, $resourceFactory);
3210 
3211  $this->cacheManagerMock->method('getCache')->willReturnMap([
3212  ['runtime', new NullFrontend('dummy')],
3213  ['core', new NullFrontend('runtime')],
3214  ]);
3215 
3216  $siteConfiguration = new SiteConfiguration(
3217  ‪Environment::getConfigPath() . '/sites',
3218  new class () implements EventDispatcherInterface {
3219  public function dispatch(object $event)
3220  {
3221  return $event;
3222  }
3223  },
3224  new NullFrontend('dummy')
3225  );
3226  GeneralUtility::addInstance(ProviderConfigurationLoader::class, $this->createMock(ProviderConfigurationLoader::class));
3227  GeneralUtility::addInstance(DefaultProvider::class, new DefaultProvider(new Typo3Version(), new Context(), new Features()));
3228  $linkFactory = $this->‪getLinkFactory($siteConfiguration);
3229  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3230  $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject);
3231 
3232  self::assertEquals($expectedResult, (string)$this->subject->typoLink($linkText, $configuration));
3233  // This can vanish when the condition provider have been moved towards service provider setup
3234  GeneralUtility::purgeInstances();
3235  }
3237  public static function ‪typoLinkProperlyEncodesLinkResultDataProvider(): array
3238  {
3239  return [
3240  'Link to file' => [
3241  'My file',
3242  [
3243  'directImageLink' => false,
3244  'parameter' => '/fileadmin/foo.bar',
3245  'returnLast' => 'result',
3246  ],
3247  json_encode([
3248  'href' => '/fileadmin/foo.bar',
3249  'target' => null,
3250  'class' => null,
3251  'title' => null,
3252  'linkText' => 'My file',
3253  'additionalAttributes' => [],
3254  ]),
3255  ],
3256  'Link example' => [
3257  'My example',
3258  [
3259  'directImageLink' => false,
3260  'parameter' => 'https://example.tld',
3261  'returnLast' => 'result',
3262  ],
3263  json_encode([
3264  'href' => 'https://example.tld',
3265  'target' => null,
3266  'class' => null,
3267  'title' => null,
3268  'linkText' => 'My example',
3269  'additionalAttributes' => [],
3270  ]),
3271  ],
3272  'Link to file with attributes' => [
3273  'My file',
3274  [
3275  'parameter' => '/fileadmin/foo.bar',
3276  'fileTarget' => '_blank',
3277  'returnLast' => 'result',
3278  ],
3279  json_encode([
3280  'href' => '/fileadmin/foo.bar',
3281  'target' => '_blank',
3282  'class' => null,
3283  'title' => null,
3284  'linkText' => 'My file',
3285  'additionalAttributes' => [],
3286  ]),
3287  ],
3288  'Link parsing' => [
3289  'Url',
3290  [
3291  'parameter' => 'https://example.com _blank css-class "test title"',
3292  'returnLast' => 'result',
3293  ],
3294  json_encode([
3295  'href' => 'https://example.com',
3296  'target' => '_blank',
3297  'class' => 'css-class',
3298  'title' => 'test title',
3299  'linkText' => 'Url',
3300  'additionalAttributes' => ['rel' => 'noreferrer'],
3301  ]),
3302  ],
3303  ];
3304  }
3305 
3309  public function ‪stdWrap_splitObjReturnsCount(): void
3310  {
3311  $conf = [
3312  'token' => ',',
3313  'returnCount' => 1,
3314  ];
3315  $expectedResult = 5;
3316  $amountOfEntries = $this->subject->splitObj('1, 2, 3, 4, 5', $conf);
3317  self::assertSame(
3318  $expectedResult,
3319  $amountOfEntries
3320  );
3321  }
3322 
3328  public static function ‪calculateCacheKeyDataProvider(): array
3329  {
3330  $value = ‪StringUtility::getUniqueId('value');
3331  $wrap = [‪StringUtility::getUniqueId('wrap')];
3332  $valueConf = ['key' => $value];
3333  $wrapConf = ['key.' => $wrap];
3334  $conf = array_merge($valueConf, $wrapConf);
3335  $will = ‪StringUtility::getUniqueId('stdWrap');
3336 
3337  return [
3338  'no conf' => [
3339  '',
3340  [],
3341  0,
3342  null,
3343  null,
3344  null,
3345  ],
3346  'value conf only' => [
3347  $value,
3348  $valueConf,
3349  0,
3350  null,
3351  null,
3352  null,
3353  ],
3354  'wrap conf only' => [
3355  $will,
3356  $wrapConf,
3357  1,
3358  '',
3359  $wrap,
3360  $will,
3361  ],
3362  'full conf' => [
3363  $will,
3364  $conf,
3365  1,
3366  $value,
3367  $wrap,
3368  $will,
3369  ],
3370  ];
3371  }
3372 
3388  public function ‪calculateCacheKey(string $expect, array $conf, int $times, ?string $with, ?array $withWrap, ?string $will): void
3389  {
3390  ‪$subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['stdWrap']);
3391  ‪$subject->expects(self::exactly($times))
3392  ->method('stdWrap')
3393  ->with($with, $withWrap)
3394  ->willReturn($will);
3395 
3396  $result = ‪$subject->_call('calculateCacheKey', $conf);
3397  self::assertSame($expect, $result);
3398  }
3399 
3405  public static function ‪getFromCacheDataProvider(): array
3406  {
3407  $conf = [‪StringUtility::getUniqueId('conf')];
3408  return [
3409  'empty cache key' => [
3410  false,
3411  $conf,
3412  '',
3413  0,
3414  null,
3415  ],
3416  'non-empty cache key' => [
3417  'value',
3418  $conf,
3419  'non-empty-key',
3420  1,
3421  'value',
3422  ],
3423  ];
3424  }
3425 
3442  public function ‪getFromCache(string|bool $expect, array $conf, string $cacheKey, int $times, ?string $cached): void
3443  {
3444  ‪$subject = $this->getAccessibleMock(
3445  ContentObjectRenderer::class,
3446  ['calculateCacheKey']
3447  );
3448  ‪$subject
3449  ->expects(self::once())
3450  ->method('calculateCacheKey')
3451  ->with($conf)
3452  ->willReturn($cacheKey);
3453  $cacheFrontend = $this->createMock(CacheFrontendInterface::class);
3454  $cacheFrontend
3455  ->expects(self::exactly($times))
3456  ->method('get')
3457  ->with($cacheKey)
3458  ->willReturn($cached);
3459  $cacheManager = $this->createMock(CacheManager::class);
3460  $cacheManager
3461  ->method('getCache')
3462  ->willReturn($cacheFrontend);
3463  GeneralUtility::setSingletonInstance(
3464  CacheManager::class,
3465  $cacheManager
3466  );
3467  self::assertSame($expect, ‪$subject->_call('getFromCache', $conf));
3468  }
3469 
3475  public static function ‪getFieldValDataProvider(): array
3476  {
3477  return [
3478  'invalid single key' => [null, 'invalid'],
3479  'single key of null' => [null, 'null'],
3480  'single key of empty string' => ['', 'empty'],
3481  'single key of non-empty string' => ['string 1', 'string1'],
3482  'single key of boolean false' => [false, 'false'],
3483  'single key of boolean true' => [true, 'true'],
3484  'single key of integer 0' => [0, 'zero'],
3485  'single key of integer 1' => [1, 'one'],
3486  'single key to be trimmed' => ['string 1', ' string1 '],
3487 
3488  'split nothing' => ['', '//'],
3489  'split one before' => ['string 1', 'string1//'],
3490  'split one after' => ['string 1', '//string1'],
3491  'split two ' => ['string 1', 'string1//string2'],
3492  'split three ' => ['string 1', 'string1//string2//string3'],
3493  'split to be trimmed' => ['string 1', ' string1 // string2 '],
3494  '0 is not empty' => [0, '// zero'],
3495  '1 is not empty' => [1, '// one'],
3496  'true is not empty' => [true, '// true'],
3497  'false is empty' => ['', '// false'],
3498  'null is empty' => ['', '// null'],
3499  'empty string is empty' => ['', '// empty'],
3500  'string is not empty' => ['string 1', '// string1'],
3501  'first non-empty winns' => [0, 'false//empty//null//zero//one'],
3502  'empty string is fallback' => ['', 'false // empty // null'],
3503  ];
3504  }
3505 
3544  public function ‪getFieldVal(mixed $expect, string ‪$fields): void
3545  {
3546  $data = [
3547  'string1' => 'string 1',
3548  'string2' => 'string 2',
3549  'string3' => 'string 3',
3550  'empty' => '',
3551  'null' => null,
3552  'false' => false,
3553  'true' => true,
3554  'zero' => 0,
3555  'one' => 1,
3556  ];
3557  $this->subject->_set('data', $data);
3558  self::assertSame($expect, $this->subject->getFieldVal(‪$fields));
3559  }
3560 
3566  public static function ‪caseshiftDataProvider(): array
3567  {
3568  return [
3569  'lower' => ['x y', 'X Y', 'lower'],
3570  'upper' => ['X Y', 'x y', 'upper'],
3571  'capitalize' => ['One Two', 'one two', 'capitalize'],
3572  'ucfirst' => ['One two', 'one two', 'ucfirst'],
3573  'lcfirst' => ['oNE TWO', 'ONE TWO', 'lcfirst'],
3574  'uppercamelcase' => ['CamelCase', 'camel_case', 'uppercamelcase'],
3575  'lowercamelcase' => ['camelCase', 'camel_case', 'lowercamelcase'],
3576  ];
3577  }
3578 
3588  public function ‪caseshift(string $expect, string $content, string $case): void
3589  {
3590  self::assertSame(
3591  $expect,
3592  $this->subject->caseshift($content, $case)
3593  );
3594  }
3595 
3601  public static function ‪HTMLcaseshiftDataProvider(): array
3602  {
3603  $case = ‪StringUtility::getUniqueId('case');
3604  return [
3605  'simple text' => [
3606  'TEXT',
3607  'text',
3608  $case,
3609  [['text', $case]],
3610  ['TEXT'],
3611  ],
3612  'simple tag' => [
3613  '<i>TEXT</i>',
3614  '<i>text</i>',
3615  $case,
3616  [['', $case], ['text', $case]],
3617  ['', 'TEXT'],
3618  ],
3619  'multiple nested tags with classes' => [
3620  '<div class="typo3">'
3621  . '<p>A <b>BOLD<\b> WORD.</p>'
3622  . '<p>AN <i>ITALIC<\i> WORD.</p>'
3623  . '</div>',
3624  '<div class="typo3">'
3625  . '<p>A <b>bold<\b> word.</p>'
3626  . '<p>An <i>italic<\i> word.</p>'
3627  . '</div>',
3628  $case,
3629  [
3630  ['', $case],
3631  ['', $case],
3632  ['A ', $case],
3633  ['bold', $case],
3634  [' word.', $case],
3635  ['', $case],
3636  ['An ', $case],
3637  ['italic', $case],
3638  [' word.', $case],
3639  ['', $case],
3640  ],
3641  ['', '', 'A ', 'BOLD', ' WORD.', '', 'AN ', 'ITALIC', ' WORD.', ''],
3642  ],
3643  ];
3644  }
3645 
3662  public function ‪HTMLcaseshift(string $expect, string $content, string $case, array $with, array $will): void
3663  {
3664  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
3665  ->onlyMethods(['caseshift'])->getMock();
3666  ‪$subject
3667  ->expects(self::exactly(count($with)))
3668  ->method('caseshift')
3669  ->withConsecutive(...$with)
3670  ->will(self::onConsecutiveCalls(...$will));
3671  self::assertSame(
3672  $expect,
3673  ‪$subject->HTMLcaseshift($content, $case)
3674  );
3675  }
3676 
3677  /***************************************************************************
3678  * General tests for stdWrap_
3679  ***************************************************************************/
3680 
3692  public function ‪allStdWrapProcessorsAreCallable(): void
3693  {
3694  $callable = 0;
3695  $notCallable = 0;
3696  $processors = ['invalidProcessor'];
3697  foreach (array_keys($this->subject->_get('stdWrapOrder')) as $key) {
3698  $processors[] = strtr($key, ['.' => '']);
3699  }
3700  foreach (array_unique($processors) as $processor) {
3701  $method = [‪$this->subject, 'stdWrap_' . $processor];
3702  if (is_callable($method)) {
3703  ++$callable;
3704  } else {
3705  ++$notCallable;
3706  }
3707  }
3708  self::assertSame(1, $notCallable);
3709  self::assertSame(82, $callable);
3710  }
3711 
3732  {
3733  $timeTrackerMock = $this->getMockBuilder(TimeTracker::class)->disableOriginalConstructor()->getMock();
3734  GeneralUtility::setSingletonInstance(TimeTracker::class, $timeTrackerMock);
3735  $linkFactory = $this->getMockBuilder(LinkFactory::class)->disableOriginalConstructor()->getMock();
3736  $linkFactory->expects(self::atLeastOnce())->method('create')->with(self::anything())->willReturn(new LinkResult('', ''));
3737  GeneralUtility::addInstance(LinkFactory::class, $linkFactory);
3738  $pageRendererMock = $this->getMockBuilder(PageRenderer::class)->disableOriginalConstructor()->getMock();
3739  $pageRendererMock->method('getDocType')->willReturn(DocType::html5);
3740  GeneralUtility::setSingletonInstance(PageRenderer::class, $pageRendererMock);
3741 
3742  $expectExceptions = ['numRows'];
3743  $count = 0;
3744  $processors = [];
3745  $exceptions = [];
3746  foreach (array_keys($this->subject->_get('stdWrapOrder')) as $key) {
3747  $processors[] = strtr($key, ['.' => '']);
3748  }
3749  foreach (array_unique($processors) as $processor) {
3750  ++$count;
3751  try {
3752  $conf = [$processor => '', $processor . '.' => ['table' => 'tt_content']];
3753  $method = 'stdWrap_' . $processor;
3754  $this->subject->$method('', $conf);
3755  } catch (\Exception $e) {
3756  $exceptions[] = $processor;
3757  }
3758  }
3759  self::assertSame($expectExceptions, $exceptions);
3760  self::assertSame(82, $count);
3761  }
3762 
3763  /***************************************************************************
3764  * End general tests for stdWrap_
3765  ***************************************************************************/
3766 
3767  /***************************************************************************
3768  * Tests for stdWrap_ in alphabetical order (all uppercase before lowercase)
3769  ***************************************************************************/
3770 
3776  public static function ‪fourTypesOfStdWrapHookObjectProcessorsDataProvider(): array
3777  {
3778  return [
3779  'preProcess' => [
3780  'stdWrap_stdWrapPreProcess',
3781  'stdWrapPreProcess',
3782  ],
3783  'override' => [
3784  'stdWrap_stdWrapOverride',
3785  'stdWrapOverride',
3786  ],
3787  'process' => [
3788  'stdWrap_stdWrapProcess',
3789  'stdWrapProcess',
3790  ],
3791  'postProcess' => [
3792  'stdWrap_stdWrapPostProcess',
3793  'stdWrapPostProcess',
3794  ],
3795  ];
3796  }
3797 
3814  string $stdWrapMethod,
3815  string $hookObjectCall
3816  ): void {
3817  $conf = [‪StringUtility::getUniqueId('conf')];
3818  $content = ‪StringUtility::getUniqueId('content');
3819  $processed1 = ‪StringUtility::getUniqueId('processed1');
3820  $processed2 = ‪StringUtility::getUniqueId('processed2');
3821  $hookObject1 = $this->createMock(
3822  ContentObjectStdWrapHookInterface::class
3823  );
3824  $hookObject1->expects(self::once())
3825  ->method($hookObjectCall)
3826  ->with($content, $conf)
3827  ->willReturn($processed1);
3828  $hookObject2 = $this->createMock(
3829  ContentObjectStdWrapHookInterface::class
3830  );
3831  $hookObject2->expects(self::once())
3832  ->method($hookObjectCall)
3833  ->with($processed1, $conf)
3834  ->willReturn($processed2);
3835  $this->subject->_set(
3836  'stdWrapHookObjects',
3837  [$hookObject1, $hookObject2]
3838  );
3839  $result = $this->subject->$stdWrapMethod($content, $conf);
3840  self::assertSame($processed2, $result);
3841  }
3842 
3848  public static function ‪stdWrap_HTMLparserDataProvider(): array
3849  {
3850  $content = ‪StringUtility::getUniqueId('content');
3851  $parsed = ‪StringUtility::getUniqueId('parsed');
3852  return [
3853  'no config' => [
3854  $content,
3855  $content,
3856  [],
3857  0,
3858  $parsed,
3859  ],
3860  'no array' => [
3861  $content,
3862  $content,
3863  ['HTMLparser.' => 1],
3864  0,
3865  $parsed,
3866  ],
3867  'empty array' => [
3868  $parsed,
3869  $content,
3870  ['HTMLparser.' => []],
3871  1,
3872  $parsed,
3873  ],
3874  'non-empty array' => [
3875  $parsed,
3876  $content,
3877  ['HTMLparser.' => [true]],
3878  1,
3879  $parsed,
3880  ],
3881  ];
3882  }
3883 
3906  public function ‪stdWrap_HTMLparser(
3907  string $expect,
3908  string $content,
3909  array $conf,
3910  int $times,
3911  string $will
3912  ): void {
3913  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
3914  ->onlyMethods(['HTMLparser_TSbridge'])->getMock();
3915  ‪$subject
3916  ->expects(self::exactly($times))
3917  ->method('HTMLparser_TSbridge')
3918  ->with($content, $conf['HTMLparser.'] ?? [])
3919  ->willReturn($will);
3920  self::assertSame(
3921  $expect,
3922  ‪$subject->stdWrap_HTMLparser($content, $conf)
3923  );
3924  }
3926  public static function ‪stdWrap_addPageCacheTagsAddsPageTagsDataProvider(): array
3927  {
3928  return [
3929  'No Tag' => [
3930  [],
3931  ['addPageCacheTags' => ''],
3932  ],
3933  'Two expectedTags' => [
3934  ['tag1', 'tag2'],
3935  ['addPageCacheTags' => 'tag1,tag2'],
3936  ],
3937  'Two expectedTags plus one with stdWrap' => [
3938  ['tag1', 'tag2', 'tag3'],
3939  [
3940  'addPageCacheTags' => 'tag1,tag2',
3941  'addPageCacheTags.' => ['wrap' => '|,tag3'],
3942  ],
3943  ],
3944  ];
3945  }
3946 
3951  public function ‪stdWrap_addPageCacheTagsAddsPageTags(array $expectedTags, array $configuration): void
3952  {
3953  $this->subject->stdWrap_addPageCacheTags('', $configuration);
3954  self::assertEquals($expectedTags, $this->frontendControllerMock->_get('pageCacheTags'));
3955  }
3956 
3969  public function ‪stdWrap_age(): void
3970  {
3971  $now = 10;
3972  $content = '9';
3973  $conf = ['age' => ‪StringUtility::getUniqueId('age')];
3974  $return = ‪StringUtility::getUniqueId('return');
3975  $difference = $now - (int)$content;
3976  ‪$GLOBALS['EXEC_TIME'] = $now;
3977  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
3978  ->onlyMethods(['calcAge'])->getMock();
3979  ‪$subject
3980  ->expects(self::once())
3981  ->method('calcAge')
3982  ->with($difference, $conf['age'])
3983  ->willReturn($return);
3984  self::assertSame($return, ‪$subject->stdWrap_age($content, $conf));
3985  }
3986 
4000  public function ‪stdWrap_append(): void
4001  {
4002  $debugKey = '/stdWrap/.append';
4003  $content = ‪StringUtility::getUniqueId('content');
4004  $conf = [
4005  'append' => ‪StringUtility::getUniqueId('append'),
4006  'append.' => [‪StringUtility::getUniqueId('append.')],
4007  ];
4008  $return = ‪StringUtility::getUniqueId('return');
4009  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4010  ->onlyMethods(['cObjGetSingle'])->getMock();
4011  ‪$subject
4012  ->expects(self::once())
4013  ->method('cObjGetSingle')
4014  ->with($conf['append'], $conf['append.'], $debugKey)
4015  ->willReturn($return);
4016  self::assertSame(
4017  $content . $return,
4018  ‪$subject->stdWrap_append($content, $conf)
4019  );
4020  }
4021 
4027  public static function ‪stdWrapBrDataProvider(): array
4028  {
4029  return [
4030  'no xhtml with LF in between' => [
4031  'one<br>' . LF . 'two',
4032  'one' . LF . 'two',
4033  null,
4034  ],
4035  'no xhtml with LF in between and around' => [
4036  '<br>' . LF . 'one<br>' . LF . 'two<br>' . LF,
4037  LF . 'one' . LF . 'two' . LF,
4038  null,
4039  ],
4040  'xhtml with LF in between' => [
4041  'one<br />' . LF . 'two',
4042  'one' . LF . 'two',
4043  'xhtml_strict',
4044  ],
4045  'xhtml with LF in between and around' => [
4046  '<br />' . LF . 'one<br />' . LF . 'two<br />' . LF,
4047  LF . 'one' . LF . 'two' . LF,
4048  'xhtml_strict',
4049  ],
4050  ];
4051  }
4052 
4062  public function ‪stdWrap_br(string $expected, string $input, ?string $xhtmlDoctype): void
4063  {
4064  $pageRenderer = $this->getMockBuilder(PageRenderer::class)->disableOriginalConstructor()->addMethods(['dummy'])->getMock();
4065  $pageRenderer->setLanguage('en');
4066  $pageRenderer->setDocType(DocType::createFromConfigurationKey($xhtmlDoctype));
4067  GeneralUtility::setSingletonInstance(PageRenderer::class, $pageRenderer);
4068  self::assertSame($expected, $this->subject->stdWrap_br($input));
4069  }
4070 
4074  public static function ‪stdWrapBrTagDataProvider(): array
4075  {
4076  $noConfig = [];
4077  $config1 = ['brTag' => '<br/>'];
4078  $config2 = ['brTag' => '<br>'];
4079  return [
4080  'no config: one break at the beginning' => [LF . 'one' . LF . 'two', 'onetwo', $noConfig],
4081  'no config: multiple breaks at the beginning' => [LF . LF . 'one' . LF . 'two', 'onetwo', $noConfig],
4082  'no config: one break at the end' => ['one' . LF . 'two' . LF, 'onetwo', $noConfig],
4083  'no config: multiple breaks at the end' => ['one' . LF . 'two' . LF . LF, 'onetwo', $noConfig],
4084 
4085  'config1: one break at the beginning' => [LF . 'one' . LF . 'two', '<br/>one<br/>two', $config1],
4086  'config1: multiple breaks at the beginning' => [
4087  LF . LF . 'one' . LF . 'two',
4088  '<br/><br/>one<br/>two',
4089  $config1,
4090  ],
4091  'config1: one break at the end' => ['one' . LF . 'two' . LF, 'one<br/>two<br/>', $config1],
4092  'config1: multiple breaks at the end' => ['one' . LF . 'two' . LF . LF, 'one<br/>two<br/><br/>', $config1],
4093 
4094  'config2: one break at the beginning' => [LF . 'one' . LF . 'two', '<br>one<br>two', $config2],
4095  'config2: multiple breaks at the beginning' => [
4096  LF . LF . 'one' . LF . 'two',
4097  '<br><br>one<br>two',
4098  $config2,
4099  ],
4100  'config2: one break at the end' => ['one' . LF . 'two' . LF, 'one<br>two<br>', $config2],
4101  'config2: multiple breaks at the end' => ['one' . LF . 'two' . LF . LF, 'one<br>two<br><br>', $config2],
4102  ];
4103  }
4104 
4111  public function ‪stdWrap_brTag(string $input, string $expected, array $config): void
4112  {
4113  self::assertEquals($expected, $this->subject->stdWrap_brTag($input, $config));
4114  }
4115 
4121  public static function ‪stdWrap_bytesDataProvider(): array
4122  {
4123  return [
4124  'value 1234 default' => [
4125  '1.21 Ki',
4126  '1234',
4127  ['labels' => '', 'base' => 0],
4128  ],
4129  'value 1234 si' => [
4130  '1.23 k',
4131  '1234',
4132  ['labels' => 'si', 'base' => 0],
4133  ],
4134  'value 1234 iec' => [
4135  '1.21 Ki',
4136  '1234',
4137  ['labels' => 'iec', 'base' => 0],
4138  ],
4139  'value 1234 a-i' => [
4140  '1.23b',
4141  '1234',
4142  ['labels' => 'a|b|c|d|e|f|g|h|i', 'base' => 1000],
4143  ],
4144  'value 1234 a-i invalid base' => [
4145  '1.21b',
4146  '1234',
4147  ['labels' => 'a|b|c|d|e|f|g|h|i', 'base' => 54],
4148  ],
4149  'value 1234567890 default' => [
4150  '1.15 Gi',
4151  '1234567890',
4152  ['labels' => '', 'base' => 0],
4153  ],
4154  'value 1234 iec no base' => [
4155  '1.21 Ki',
4156  '1234',
4157  ['labels' => 'iec'],
4158  ],
4159  'value 1234 no labels base set' => [
4160  '1.21 Ki',
4161  '1234',
4162  ['base' => 0],
4163  ],
4164  ];
4165  }
4166 
4188  public function ‪stdWrap_bytes(string $expect, string $content, array $conf): void
4189  {
4190  $locale = 'en_US.UTF-8';
4191  try {
4192  $this->setLocale(LC_NUMERIC, $locale);
4193  } catch (Exception $e) {
4194  self::markTestSkipped('Locale ' . $locale . ' is not available.');
4195  }
4196  $conf = ['bytes.' => $conf];
4197  self::assertSame(
4198  $expect,
4199  $this->subject->stdWrap_bytes($content, $conf)
4200  );
4201  }
4202 
4216  public function ‪stdWrap_cObject(): void
4217  {
4218  $debugKey = '/stdWrap/.cObject';
4219  $content = ‪StringUtility::getUniqueId('content');
4220  $conf = [
4221  'cObject' => ‪StringUtility::getUniqueId('cObject'),
4222  'cObject.' => [‪StringUtility::getUniqueId('cObject.')],
4223  ];
4224  $return = ‪StringUtility::getUniqueId('return');
4225  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4226  ->onlyMethods(['cObjGetSingle'])->getMock();
4227  ‪$subject
4228  ->expects(self::once())
4229  ->method('cObjGetSingle')
4230  ->with($conf['cObject'], $conf['cObject.'], $debugKey)
4231  ->willReturn($return);
4232  self::assertSame(
4233  $return,
4234  ‪$subject->stdWrap_cObject($content, $conf)
4235  );
4236  }
4237 
4243  public static function ‪stdWrap_orderedStdWrapDataProvider(): array
4244  {
4245  $confA = [‪StringUtility::getUniqueId('conf A')];
4246  $confB = [‪StringUtility::getUniqueId('conf B')];
4247  return [
4248  'standard case: order 1, 2' => [
4249  $confA,
4250  $confB,
4251  ['1.' => $confA, '2.' => $confB],
4252  ],
4253  'inverted: order 2, 1' => [
4254  $confB,
4255  $confA,
4256  ['2.' => $confA, '1.' => $confB],
4257  ],
4258  '0 as integer: order 0, 2' => [
4259  $confA,
4260  $confB,
4261  ['0.' => $confA, '2.' => $confB],
4262  ],
4263  'negative integers: order 2, -2' => [
4264  $confB,
4265  $confA,
4266  ['2.' => $confA, '-2.' => $confB],
4267  ],
4268  'chars are casted to key 0, that is not in the array' => [
4269  null,
4270  $confB,
4271  ['2.' => $confB, 'xxx.' => $confA],
4272  ],
4273  ];
4274  }
4275 
4298  public function ‪stdWrap_orderedStdWrap(?array $firstConf, array $secondConf, array $conf): void
4299  {
4300  $content = ‪StringUtility::getUniqueId('content');
4301  $between = ‪StringUtility::getUniqueId('between');
4302  $expect = ‪StringUtility::getUniqueId('expect');
4303  $conf['orderedStdWrap.'] = $conf;
4304  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4305  ->onlyMethods(['stdWrap'])->getMock();
4306  ‪$subject
4307  ->expects(self::exactly(2))
4308  ->method('stdWrap')
4309  ->withConsecutive([$content, $firstConf], [$between, $secondConf])
4310  ->will(self::onConsecutiveCalls($between, $expect));
4311  self::assertSame(
4312  $expect,
4313  ‪$subject->stdWrap_orderedStdWrap($content, $conf)
4314  );
4315  }
4316 
4322  public static function ‪stdWrap_cacheReadDataProvider(): array
4323  {
4324  $cacheConf = [‪StringUtility::getUniqueId('cache.')];
4325  $conf = ['cache.' => $cacheConf];
4326  return [
4327  'no conf' => [
4328  'content',
4329  'content',
4330  [],
4331  0,
4332  null,
4333  null,
4334  ],
4335  'no cache. conf' => [
4336  'content',
4337  'content',
4338  ['otherConf' => 1],
4339  0,
4340  null,
4341  null,
4342  ],
4343  'non-cached simulation' => [
4344  'content',
4345  'content',
4346  $conf,
4347  1,
4348  $cacheConf,
4349  false,
4350  ],
4351  'cached simulation' => [
4352  'cachedContent',
4353  'content',
4354  $conf,
4355  1,
4356  $cacheConf,
4357  'cachedContent',
4358  ],
4359  ];
4360  }
4361 
4378  public function ‪stdWrap_cacheRead(
4379  string $expect,
4380  string $input,
4381  array $conf,
4382  int $times,
4383  ?array $with,
4384  string|bool|null $will
4385  ): void {
4386  ‪$subject = $this->getAccessibleMock(
4387  ContentObjectRenderer::class,
4388  ['getFromCache']
4389  );
4390  ‪$subject
4391  ->expects(self::exactly($times))
4392  ->method('getFromCache')
4393  ->with($with)
4394  ->willReturn($will);
4395  self::assertSame(
4396  $expect,
4397  ‪$subject->stdWrap_cacheRead($input, $conf)
4398  );
4399  }
4400 
4406  public static function ‪stdWrap_cacheStoreDataProvider(): array
4407  {
4408  $confCache = [‪StringUtility::getUniqueId('cache.')];
4409  $key = [‪StringUtility::getUniqueId('key')];
4410  return [
4411  'Return immediate with no conf' => [
4412  null,
4413  0,
4414  null,
4415  0,
4416  ],
4417  'Return immediate with empty key' => [
4418  $confCache,
4419  1,
4420  '0',
4421  0,
4422  ],
4423  'Call all methods' => [
4424  $confCache,
4425  1,
4426  $key,
4427  1,
4428  ],
4429  ];
4430  }
4431 
4453  public function ‪stdWrap_cacheStore(
4454  ?array $confCache,
4455  int $timesCCK,
4456  mixed $key,
4457  int $times
4458  ): void {
4459  $content = ‪StringUtility::getUniqueId('content');
4460  $conf = [];
4461  $conf['cache.'] = $confCache;
4462  $tags = [‪StringUtility::getUniqueId('tags')];
4463  $lifetime = ‪StringUtility::getUniqueId('lifetime');
4464  $params = [
4465  'key' => $key,
4466  'content' => $content,
4467  'lifetime' => $lifetime,
4468  'tags' => $tags,
4469  ];
4470  ‪$subject = $this->getAccessibleMock(
4471  ContentObjectRenderer::class,
4472  [
4473  'calculateCacheKey',
4474  'calculateCacheTags',
4475  'calculateCacheLifetime',
4476  ]
4477  );
4478  ‪$subject
4479  ->expects(self::exactly($timesCCK))
4480  ->method('calculateCacheKey')
4481  ->with($confCache)
4482  ->willReturn($key);
4483  ‪$subject
4484  ->expects(self::exactly($times))
4485  ->method('calculateCacheTags')
4486  ->with($confCache)
4487  ->willReturn($tags);
4488  ‪$subject
4489  ->expects(self::exactly($times))
4490  ->method('calculateCacheLifetime')
4491  ->with($confCache)
4492  ->willReturn($lifetime);
4493  $cacheFrontend = $this->createMock(CacheFrontendInterface::class);
4494  $cacheFrontend
4495  ->expects(self::exactly($times))
4496  ->method('set')
4497  ->with($key, $content, $tags, $lifetime)
4498  ->willReturn(null);
4499  $cacheManager = $this->createMock(CacheManager::class);
4500  $cacheManager
4501  ->method('getCache')
4502  ->willReturn($cacheFrontend);
4503  GeneralUtility::setSingletonInstance(
4504  CacheManager::class,
4505  $cacheManager
4506  );
4507  [$countCalls, $test] = [0, $this];
4508  $closure = static function ($par1, $par2) use (
4509  $test,
4510  ‪$subject,
4511  $params,
4512  &$countCalls
4513  ) {
4514  $test->assertSame($params, $par1);
4515  $test->assertSame(‪$subject, $par2);
4516  $countCalls++;
4517  };
4518  ‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['stdWrap_cacheStore'] = [
4519  $closure,
4520  $closure,
4521  $closure,
4522  ];
4523  self::assertSame(
4524  $content,
4525  ‪$subject->stdWrap_cacheStore($content, $conf)
4526  );
4527  self::assertSame($times * 3, $countCalls);
4528  }
4529 
4542  public function ‪stdWrap_case(): void
4543  {
4544  $content = ‪StringUtility::getUniqueId();
4545  $conf = [
4546  'case' => ‪StringUtility::getUniqueId('used'),
4547  'case.' => [‪StringUtility::getUniqueId('discarded')],
4548  ];
4549  $return = ‪StringUtility::getUniqueId();
4550  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4551  ->onlyMethods(['HTMLcaseshift'])->getMock();
4552  ‪$subject
4553  ->expects(self::once())
4554  ->method('HTMLcaseshift')
4555  ->with($content, $conf['case'])
4556  ->willReturn($return);
4557  self::assertSame(
4558  $return,
4559  ‪$subject->stdWrap_case($content, $conf)
4560  );
4561  }
4562 
4568  public function ‪stdWrap_char(): void
4569  {
4570  $input = 'discarded';
4571  $expected = 'C';
4572  self::assertEquals($expected, $this->subject->stdWrap_char($input, ['char' => '67']));
4573  }
4574 
4587  public function ‪stdWrap_crop(): void
4588  {
4589  $content = ‪StringUtility::getUniqueId('content');
4590  $conf = [
4591  'crop' => ‪StringUtility::getUniqueId('crop'),
4592  'crop.' => ‪StringUtility::getUniqueId('not used'),
4593  ];
4594  $return = ‪StringUtility::getUniqueId('return');
4595  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4596  ->onlyMethods(['crop'])->getMock();
4597  ‪$subject
4598  ->expects(self::once())
4599  ->method('crop')
4600  ->with($content, $conf['crop'])
4601  ->willReturn($return);
4602  self::assertSame(
4603  $return,
4604  ‪$subject->stdWrap_crop($content, $conf)
4605  );
4606  }
4607 
4620  public function ‪stdWrap_cropHTML(): void
4621  {
4622  $content = ‪StringUtility::getUniqueId('content');
4623  $conf = [
4624  'cropHTML' => ‪StringUtility::getUniqueId('cropHTML'),
4625  'cropHTML.' => ‪StringUtility::getUniqueId('not used'),
4626  ];
4627  $return = ‪StringUtility::getUniqueId('return');
4628  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4629  ->onlyMethods(['cropHTML'])->getMock();
4630  ‪$subject
4631  ->expects(self::once())
4632  ->method('cropHTML')
4633  ->with($content, $conf['cropHTML'])
4634  ->willReturn($return);
4635  self::assertSame(
4636  $return,
4637  ‪$subject->stdWrap_cropHTML($content, $conf)
4638  );
4639  }
4641  protected static function ‪stdWrap_formattedDateProvider(): \Generator
4642  {
4643  yield 'regular formatting - no locale' => [
4644  '2023.02.02 AD at 13:05:00 UTC',
4645  "yyyy.MM.dd G 'at' HH:mm:ss zzz",
4646  ];
4647  yield 'full - no locale' => [
4648  'Thursday, February 2, 2023 at 13:05:00 Coordinated Universal Time',
4649  'FULL',
4650  ];
4651  yield 'long - no locale' => [
4652  'February 2, 2023 at 13:05:00 UTC',
4653  'LONG',
4654  ];
4655  yield 'medium - no locale' => [
4656  'Feb 2, 2023, 13:05:00',
4657  'MEDIUM',
4658  ];
4659  yield 'medium with int - no locale' => [
4660  'Feb 2, 2023, 13:05:00',
4661  \IntlDateFormatter::MEDIUM,
4662  ];
4663  yield 'short - no locale' => [
4664  '2/2/23, 13:05',
4665  'SHORT',
4666  ];
4667  yield 'regular formatting - german locale' => [
4668  '2023.02.02 n. Chr. um 13:05:00 UTC',
4669  "yyyy.MM.dd G 'um' HH:mm:ss zzz",
4670  'de-DE',
4671  ];
4672  yield 'full - german locale' => [
4673  'Donnerstag, 2. Februar 2023 um 13:05:00 Koordinierte Weltzeit',
4674  'FULL',
4675  'de-DE',
4676  ];
4677  yield 'long - german locale' => [
4678  '2. Februar 2023 um 13:05:00 UTC',
4679  'LONG',
4680  'de-DE',
4681  ];
4682  yield 'medium - german locale' => [
4683  '02.02.2023, 13:05:00',
4684  'MEDIUM',
4685  'de-DE',
4686  ];
4687  yield 'short - german locale' => [
4688  '02.02.23, 13:05',
4689  'SHORT',
4690  'de-DE',
4691  ];
4692  yield 'custom date only - german locale' => [
4693  '02. Februar 2023',
4694  'dd. MMMM yyyy',
4695  'de-DE',
4696  ];
4697  yield 'custom time only - german locale' => [
4698  '13:05:00',
4699  'HH:mm:ss',
4700  'de-DE',
4701  ];
4702  yield 'given date and time - german locale' => [
4703  'Freitag, 20. Februar 1998 um 03:00:00 Koordinierte Weltzeit',
4704  'FULL',
4705  'de-DE',
4706  '1998-02-20 3:00:00',
4707  ];
4708  }
4709 
4714  public function ‪stdWrap_formattedDate(string $expected, mixed $pattern, string $locale = null, string $givenDate = null): void
4715  {
4716  $this->frontendControllerMock->getContext()->setAspect('date', new ‪DateTimeAspect(new \DateTimeImmutable('2023-02-02 13:05:00')));
4717  ‪$subject = new ContentObjectRenderer($this->frontendControllerMock);
4718  $conf = ['formattedDate' => $pattern];
4719  if ($locale !== null) {
4720  $conf['formattedDate.']['locale'] = $locale;
4721  }
4722  self::assertEquals($expected, ‪$subject->stdWrap_formattedDate((string)$givenDate, $conf));
4723  }
4724 
4730  public static function ‪stdWrap_csConvDataProvider(): array
4731  {
4732  return [
4733  'empty string from ISO-8859-15' => [
4734  '',
4735  mb_convert_encoding('', 'ISO-8859-15', 'UTF-8'),
4736  ['csConv' => 'ISO-8859-15'],
4737  ],
4738  'empty string from BIG-5' => [
4739  '',
4740  mb_convert_encoding('', 'BIG-5'),
4741  ['csConv' => 'BIG-5'],
4742  ],
4743  '"0" from ISO-8859-15' => [
4744  '0',
4745  mb_convert_encoding('0', 'ISO-8859-15', 'UTF-8'),
4746  ['csConv' => 'ISO-8859-15'],
4747  ],
4748  '"0" from BIG-5' => [
4749  '0',
4750  mb_convert_encoding('0', 'BIG-5'),
4751  ['csConv' => 'BIG-5'],
4752  ],
4753  'euro symbol from ISO-88859-15' => [
4754  '€',
4755  mb_convert_encoding('€', 'ISO-8859-15', 'UTF-8'),
4756  ['csConv' => 'ISO-8859-15'],
4757  ],
4758  'good morning from BIG-5' => [
4759  '早安',
4760  mb_convert_encoding('早安', 'BIG-5'),
4761  ['csConv' => 'BIG-5'],
4762  ],
4763  ];
4764  }
4765 
4775  public function ‪stdWrap_csConv(string $expected, string $input, array $conf): void
4776  {
4777  self::assertSame(
4778  $expected,
4779  $this->subject->stdWrap_csConv($input, $conf)
4780  );
4781  }
4782 
4794  public function ‪stdWrap_current(): void
4795  {
4796  $data = [
4797  'currentValue_kidjls9dksoje' => 'default',
4798  'currentValue_new' => 'new',
4799  ];
4800  $this->subject->_set('data', $data);
4801  self::assertSame(
4802  'currentValue_kidjls9dksoje',
4803  $this->subject->_get('currentValKey')
4804  );
4805  self::assertSame(
4806  'default',
4807  $this->subject->stdWrap_current('discarded', ['discarded'])
4808  );
4809  $this->subject->_set('currentValKey', 'currentValue_new');
4810  self::assertSame(
4811  'new',
4812  $this->subject->stdWrap_current('discarded', ['discarded'])
4813  );
4814  }
4815 
4821  public static function ‪stdWrap_dataDataProvider(): array
4822  {
4823  $data = [‪StringUtility::getUniqueId('data')];
4824  return [
4825  'default' => [$data, $data, ''],
4826  ];
4827  }
4828 
4843  public function ‪stdWrap_data(array $expect, array $data): void
4844  {
4845  $conf = ['data' => ‪StringUtility::getUniqueId('conf.data')];
4846  $return = ‪StringUtility::getUniqueId('return');
4847  ‪$subject = $this->getAccessibleMock(
4848  ContentObjectRenderer::class,
4849  ['getData']
4850  );
4851  ‪$subject->_set('data', $data);
4852  ‪$subject
4853  ->expects(self::once())
4854  ->method('getData')
4855  ->with($conf['data'], $expect)
4856  ->willReturn($return);
4857  self::assertSame($return, ‪$subject->stdWrap_data('discard', $conf));
4858  }
4859 
4872  public function ‪stdWrap_dataWrap(): void
4873  {
4874  $content = ‪StringUtility::getUniqueId('content');
4875  $conf = [
4876  'dataWrap' => ‪StringUtility::getUniqueId('dataWrap'),
4877  'dataWrap.' => [‪StringUtility::getUniqueId('not used')],
4878  ];
4879  $return = ‪StringUtility::getUniqueId('return');
4880  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
4881  ->onlyMethods(['dataWrap'])->getMock();
4882  ‪$subject
4883  ->expects(self::once())
4884  ->method('dataWrap')
4885  ->with($content, $conf['dataWrap'])
4886  ->willReturn($return);
4887  self::assertSame(
4888  $return,
4889  ‪$subject->stdWrap_dataWrap($content, $conf)
4890  );
4891  }
4892 
4898  public static function ‪stdWrap_dateDataProvider(): array
4899  {
4900  // Fictive execution time: 2015-10-02 12:00
4901  $now = 1443780000;
4902  return [
4903  'given timestamp' => [
4904  '02.10.2015',
4905  $now,
4906  ['date' => 'd.m.Y'],
4907  $now,
4908  ],
4909  'empty string' => [
4910  '02.10.2015',
4911  '',
4912  ['date' => 'd.m.Y'],
4913  $now,
4914  ],
4915  'testing null' => [
4916  '02.10.2015',
4917  null,
4918  ['date' => 'd.m.Y'],
4919  $now,
4920  ],
4921  'given timestamp return GMT' => [
4922  '02.10.2015 10:00:00',
4923  $now,
4924  [
4925  'date' => 'd.m.Y H:i:s',
4926  'date.' => ['GMT' => true],
4927  ],
4928  $now,
4929  ],
4930  ];
4931  }
4932 
4943  public function ‪stdWrap_date(string $expected, mixed $content, array $conf, int $now): void
4944  {
4945  ‪$GLOBALS['EXEC_TIME'] = $now;
4946  self::assertEquals(
4947  $expected,
4948  $this->subject->stdWrap_date($content, $conf)
4949  );
4950  }
4951 
4957  public function ‪stdWrap_debug(): void
4958  {
4959  $expect = '<pre>&lt;p class=&quot;class&quot;&gt;&lt;br/&gt;'
4960  . '&lt;/p&gt;</pre>';
4961  $content = '<p class="class"><br/></p>';
4962  self::assertSame($expect, $this->subject->stdWrap_debug($content));
4963  }
4964 
4985  public function ‪stdWrap_debugData(): void
4986  {
4987  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
4988  $content = ‪StringUtility::getUniqueId('content');
4989  $key = ‪StringUtility::getUniqueId('key');
4990  $value = ‪StringUtility::getUniqueId('value');
4991  $altValue = ‪StringUtility::getUniqueId('value alt');
4992  $this->subject->data = [$key => $value];
4993  ob_start();
4994  $result = $this->subject->stdWrap_debugData($content);
4995  $out = ob_get_clean();
4996  self::assertSame($result, $content);
4997  self::assertStringContainsString('$cObj->data', $out);
4998  self::assertStringContainsString($value, $out);
4999  self::assertStringNotContainsString($altValue, $out);
5000  }
5001 
5007  public static function ‪stdWrap_debugFuncDataProvider(): array
5008  {
5009  return [
5010  'expect array by string' => [true, '2'],
5011  'expect array by integer' => [true, 2],
5012  'do not expect array' => [false, ''],
5013  ];
5014  }
5015 
5037  public function ‪stdWrap_debugFunc(bool $expectArray, mixed $confDebugFunc): void
5038  {
5039  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
5040  $content = ‪StringUtility::getUniqueId('content');
5041  $conf = ['debugFunc' => $confDebugFunc];
5042  ob_start();
5043  $result = $this->subject->stdWrap_debugFunc($content, $conf);
5044  $out = ob_get_clean();
5045  self::assertSame($result, $content);
5046  self::assertStringContainsString($content, $out);
5047  if ($expectArray) {
5048  self::assertStringContainsString('=>', $out);
5049  } else {
5050  self::assertStringNotContainsString('=>', $out);
5051  }
5052  }
5053 
5059  public static function ‪stdWrapDoubleBrTagDataProvider(): array
5060  {
5061  return [
5062  'no config: void input' => [
5063  '',
5064  '',
5065  [],
5066  ],
5067  'no config: single break' => [
5068  'one' . LF . 'two',
5069  'one' . LF . 'two',
5070  [],
5071  ],
5072  'no config: double break' => [
5073  'onetwo',
5074  'one' . LF . LF . 'two',
5075  [],
5076  ],
5077  'no config: double break with whitespace' => [
5078  'onetwo',
5079  'one' . LF . "\t" . ' ' . "\t" . ' ' . LF . 'two',
5080  [],
5081  ],
5082  'no config: single break around' => [
5083  LF . 'one' . LF,
5084  LF . 'one' . LF,
5085  [],
5086  ],
5087  'no config: double break around' => [
5088  'one',
5089  LF . LF . 'one' . LF . LF,
5090  [],
5091  ],
5092  'empty string: double break around' => [
5093  'one',
5094  LF . LF . 'one' . LF . LF,
5095  ['doubleBrTag' => ''],
5096  ],
5097  'br tag: double break' => [
5098  'one<br/>two',
5099  'one' . LF . LF . 'two',
5100  ['doubleBrTag' => '<br/>'],
5101  ],
5102  'br tag: double break around' => [
5103  '<br/>one<br/>',
5104  LF . LF . 'one' . LF . LF,
5105  ['doubleBrTag' => '<br/>'],
5106  ],
5107  'double br tag: double break around' => [
5108  '<br/><br/>one<br/><br/>',
5109  LF . LF . 'one' . LF . LF,
5110  ['doubleBrTag' => '<br/><br/>'],
5111  ],
5112  ];
5113  }
5114 
5124  public function ‪stdWrap_doubleBrTag(string $expected, string $input, array $config): void
5125  {
5126  self::assertEquals($expected, $this->subject->stdWrap_doubleBrTag($input, $config));
5127  }
5128 
5141  public function ‪stdWrap_encapsLines(): void
5142  {
5143  $content = ‪StringUtility::getUniqueId('content');
5144  $conf = [
5145  'encapsLines' => [‪StringUtility::getUniqueId('not used')],
5146  'encapsLines.' => [‪StringUtility::getUniqueId('encapsLines.')],
5147  ];
5148  $return = ‪StringUtility::getUniqueId('return');
5149  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
5150  ->onlyMethods(['encaps_lineSplit'])->getMock();
5151  ‪$subject
5152  ->expects(self::once())
5153  ->method('encaps_lineSplit')
5154  ->with($content, $conf['encapsLines.'])
5155  ->willReturn($return);
5156  self::assertSame(
5157  $return,
5158  ‪$subject->stdWrap_encapsLines($content, $conf)
5159  );
5160  }
5161 
5170  public function ‪stdWrap_encapsLines_HTML5SelfClosingTags(string $input, string $expected): void
5171  {
5172  $rteParseFunc = $this->‪getLibParseFunc_RTE();
5173 
5174  $conf = [
5175  'encapsLines' => $rteParseFunc['parseFunc.']['nonTypoTagStdWrap.']['encapsLines'] ?? null,
5176  'encapsLines.' => $rteParseFunc['parseFunc.']['nonTypoTagStdWrap.']['encapsLines.'] ?? null,
5177  ];
5178  // don't add an &nbsp; to tag without content
5179  $conf['encapsLines.']['innerStdWrap_all.']['ifBlank'] = '';
5180  $additionalEncapsTags = ['a', 'b', 'span'];
5181 
5182  // We want to allow any tag to be an encapsulating tag
5183  // since this is possible and we don't want an additional tag to be wrapped around.
5184  $conf['encapsLines.']['encapsTagList'] .= ',' . implode(',', $additionalEncapsTags);
5185  $conf['encapsLines.']['encapsTagList'] .= ',' . implode(',', [$input]);
5186 
5187  // Check if we get a self-closing tag for
5188  // empty tags where this is allowed according to HTML5
5189  $content = '<' . $input . ' id="myId" class="bodytext" />';
5190  $result = $this->subject->stdWrap_encapsLines($content, $conf);
5191  self::assertSame($expected, $result);
5192  }
5194  public static function ‪html5SelfClosingTagsDataprovider(): array
5195  {
5196  return [
5197  'areaTag_selfclosing' => [
5198  'input' => 'area',
5199  'expected' => '<area id="myId" class="bodytext" />',
5200  ],
5201  'base_selfclosing' => [
5202  'input' => 'base',
5203  'expected' => '<base id="myId" class="bodytext" />',
5204  ],
5205  'br_selfclosing' => [
5206  'input' => 'br',
5207  'expected' => '<br id="myId" class="bodytext" />',
5208  ],
5209  'col_selfclosing' => [
5210  'input' => 'col',
5211  'expected' => '<col id="myId" class="bodytext" />',
5212  ],
5213  'embed_selfclosing' => [
5214  'input' => 'embed',
5215  'expected' => '<embed id="myId" class="bodytext" />',
5216  ],
5217  'hr_selfclosing' => [
5218  'input' => 'hr',
5219  'expected' => '<hr id="myId" class="bodytext" />',
5220  ],
5221  'img_selfclosing' => [
5222  'input' => 'img',
5223  'expected' => '<img id="myId" class="bodytext" />',
5224  ],
5225  'input_selfclosing' => [
5226  'input' => 'input',
5227  'expected' => '<input id="myId" class="bodytext" />',
5228  ],
5229  'keygen_selfclosing' => [
5230  'input' => 'keygen',
5231  'expected' => '<keygen id="myId" class="bodytext" />',
5232  ],
5233  'link_selfclosing' => [
5234  'input' => 'link',
5235  'expected' => '<link id="myId" class="bodytext" />',
5236  ],
5237  'meta_selfclosing' => [
5238  'input' => 'meta',
5239  'expected' => '<meta id="myId" class="bodytext" />',
5240  ],
5241  'param_selfclosing' => [
5242  'input' => 'param',
5243  'expected' => '<param id="myId" class="bodytext" />',
5244  ],
5245  'source_selfclosing' => [
5246  'input' => 'source',
5247  'expected' => '<source id="myId" class="bodytext" />',
5248  ],
5249  'track_selfclosing' => [
5250  'input' => 'track',
5251  'expected' => '<track id="myId" class="bodytext" />',
5252  ],
5253  'wbr_selfclosing' => [
5254  'input' => 'wbr',
5255  'expected' => '<wbr id="myId" class="bodytext" />',
5256  ],
5257  'p_notselfclosing' => [
5258  'input' => 'p',
5259  'expected' => '<p id="myId" class="bodytext"></p>',
5260  ],
5261  'a_notselfclosing' => [
5262  'input' => 'a',
5263  'expected' => '<a id="myId" class="bodytext"></a>',
5264  ],
5265  'strong_notselfclosing' => [
5266  'input' => 'strong',
5267  'expected' => '<strong id="myId" class="bodytext"></strong>',
5268  ],
5269  'span_notselfclosing' => [
5270  'input' => 'span',
5271  'expected' => '<span id="myId" class="bodytext"></span>',
5272  ],
5273  ];
5274  }
5275 
5281  public static function ‪stdWrap_encodeForJavaScriptValueDataProvider(): array
5282  {
5283  return [
5284  'double quote in string' => [
5285  '\'double\u0020quote\u0022\'',
5286  'double quote"',
5287  ],
5288  'backslash in string' => [
5289  '\'backslash\u0020\u005C\'',
5290  'backslash \\',
5291  ],
5292  'exclamation mark' => [
5293  '\'exclamation\u0021\'',
5294  'exclamation!',
5295  ],
5296  'whitespace tab, newline and carriage return' => [
5297  '\'white\u0009space\u000As\u000D\'',
5298  "white\tspace\ns\r",
5299  ],
5300  'single quote in string' => [
5301  '\'single\u0020quote\u0020\u0027\'',
5302  'single quote \'',
5303  ],
5304  'tag' => [
5305  '\'\u003Ctag\u003E\'',
5306  '<tag>',
5307  ],
5308  'ampersand in string' => [
5309  '\'amper\u0026sand\'',
5310  'amper&sand',
5311  ],
5312  ];
5313  }
5314 
5323  public function ‪stdWrap_encodeForJavaScriptValue(string $expect, string $content): void
5324  {
5325  self::assertSame(
5326  $expect,
5327  $this->subject->stdWrap_encodeForJavaScriptValue($content)
5328  );
5329  }
5330 
5336  public static function ‪stdWrap_expandListDataProvider(): array
5337  {
5338  return [
5339  'numbers' => ['1,2,3', '1,2,3'],
5340  'range' => ['3,4,5', '3-5'],
5341  'numbers and range' => ['1,3,4,5,7', '1,3-5,7'],
5342  ];
5343  }
5344 
5358  public function ‪stdWrap_expandList(string $expected, string $content): void
5359  {
5360  self::assertEquals(
5361  $expected,
5362  $this->subject->stdWrap_expandList($content)
5363  );
5364  }
5365 
5376  public function ‪stdWrap_field(): void
5377  {
5378  $expect = ‪StringUtility::getUniqueId('expect');
5379  $conf = ['field' => ‪StringUtility::getUniqueId('field')];
5380  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
5381  ->onlyMethods(['getFieldVal'])->getMock();
5382  ‪$subject
5383  ->expects(self::once())
5384  ->method('getFieldVal')
5385  ->with($conf['field'])
5386  ->willReturn($expect);
5387  self::assertSame(
5388  $expect,
5389  ‪$subject->stdWrap_field('discarded', $conf)
5390  );
5391  }
5392 
5398  public static function ‪stdWrap_fieldRequiredDataProvider(): array
5399  {
5400  $content = ‪StringUtility::getUniqueId('content');
5401  return [
5402  // resulting in boolean false
5403  'false is false' => [
5404  '',
5405  true,
5406  $content,
5407  ['fieldRequired' => 'false'],
5408  ],
5409  'null is false' => [
5410  '',
5411  true,
5412  $content,
5413  ['fieldRequired' => 'null'],
5414  ],
5415  'empty string is false' => [
5416  '',
5417  true,
5418  $content,
5419  ['fieldRequired' => 'empty'],
5420  ],
5421  'whitespace is false' => [
5422  '',
5423  true,
5424  $content,
5425  ['fieldRequired' => 'whitespace'],
5426  ],
5427  'string zero is false' => [
5428  '',
5429  true,
5430  $content,
5431  ['fieldRequired' => 'stringZero'],
5432  ],
5433  'string zero with whitespace is false' => [
5434  '',
5435  true,
5436  $content,
5437  ['fieldRequired' => 'stringZeroWithWhiteSpace'],
5438  ],
5439  'zero is false' => [
5440  '',
5441  true,
5442  $content,
5443  ['fieldRequired' => 'zero'],
5444  ],
5445  // resulting in boolean true
5446  'true is true' => [
5447  $content,
5448  false,
5449  $content,
5450  ['fieldRequired' => 'true'],
5451  ],
5452  'string is true' => [
5453  $content,
5454  false,
5455  $content,
5456  ['fieldRequired' => 'string'],
5457  ],
5458  'one is true' => [
5459  $content,
5460  false,
5461  $content,
5462  ['fieldRequired' => 'one'],
5463  ],
5464  ];
5465  }
5466 
5486  public function ‪stdWrap_fieldRequired(string $expect, bool $stop, string $content, array $conf): void
5487  {
5488  $data = [
5489  'null' => null,
5490  'false' => false,
5491  'empty' => '',
5492  'whitespace' => "\t" . ' ',
5493  'stringZero' => '0',
5494  'stringZeroWithWhiteSpace' => "\t" . ' 0 ' . "\t",
5495  'zero' => 0,
5496  'string' => 'string',
5497  'true' => true,
5498  'one' => 1,
5499  ];
5501  ‪$subject->_set('data', $data);
5502  ‪$subject->_set('stdWrapRecursionLevel', 1);
5503  ‪$subject->_set('stopRendering', [1 => false]);
5504  self::assertSame(
5505  $expect,
5506  ‪$subject->stdWrap_fieldRequired($content, $conf)
5507  );
5508  self::assertSame($stop, ‪$subject->_get('stopRendering')[1]);
5509  }
5510 
5516  public static function ‪hashDataProvider(): array
5517  {
5518  return [
5519  'md5' => [
5520  'bacb98acf97e0b6112b1d1b650b84971',
5521  'joh316',
5522  ['hash' => 'md5'],
5523  ],
5524  'sha1' => [
5525  '063b3d108bed9f88fa618c6046de0dccadcf3158',
5526  'joh316',
5527  ['hash' => 'sha1'],
5528  ],
5529  'stdWrap capability' => [
5530  'bacb98acf97e0b6112b1d1b650b84971',
5531  'joh316',
5532  [
5533  'hash' => '5',
5534  'hash.' => ['wrap' => 'md|'],
5535  ],
5536  ],
5537  'non-existing hashing algorithm' => [
5538  '',
5539  'joh316',
5540  ['hash' => 'non-existing'],
5541  ],
5542  ];
5543  }
5544 
5560  public function ‪stdWrap_hash(string $expect, string $content, array $conf): void
5561  {
5562  self::assertSame(
5563  $expect,
5564  $this->subject->stdWrap_hash($content, $conf)
5565  );
5566  }
5567 
5573  public static function ‪stdWrap_htmlSpecialCharsDataProvider(): array
5574  {
5575  return [
5576  'void conf' => [
5577  '&lt;span&gt;1 &amp;lt; 2&lt;/span&gt;',
5578  '<span>1 &lt; 2</span>',
5579  [],
5580  ],
5581  'void preserveEntities' => [
5582  '&lt;span&gt;1 &amp;lt; 2&lt;/span&gt;',
5583  '<span>1 &lt; 2</span>',
5584  ['htmlSpecialChars.' => []],
5585  ],
5586  'false preserveEntities' => [
5587  '&lt;span&gt;1 &amp;lt; 2&lt;/span&gt;',
5588  '<span>1 &lt; 2</span>',
5589  ['htmlSpecialChars.' => ['preserveEntities' => 0]],
5590  ],
5591  'true preserveEntities' => [
5592  '&lt;span&gt;1 &lt; 2&lt;/span&gt;',
5593  '<span>1 &lt; 2</span>',
5594  ['htmlSpecialChars.' => ['preserveEntities' => 1]],
5595  ],
5596  ];
5597  }
5598 
5608  public function ‪stdWrap_htmlSpecialChars(string $expected, string $input, array $conf): void
5609  {
5610  self::assertSame(
5611  $expected,
5612  $this->subject->stdWrap_htmlSpecialChars($input, $conf)
5613  );
5614  }
5615 
5621  public static function ‪stdWrap_ifDataProvider(): array
5622  {
5623  $content = ‪StringUtility::getUniqueId('content');
5624  $conf = ['if.' => [‪StringUtility::getUniqueId('if.')]];
5625  return [
5626  // evals to true
5627  'empty config' => [
5628  $content,
5629  false,
5630  $content,
5631  [],
5632  0,
5633  false,
5634  ],
5635  'if. is empty array' => [
5636  $content,
5637  false,
5638  $content,
5639  ['if.' => []],
5640  0,
5641  false,
5642  ],
5643  'if. is null' => [
5644  $content,
5645  false,
5646  $content,
5647  ['if.' => null],
5648  0,
5649  false,
5650  ],
5651  'if. is false' => [
5652  $content,
5653  false,
5654  $content,
5655  ['if.' => false],
5656  0,
5657  false,
5658  ],
5659  'if. is 0' => [
5660  $content,
5661  false,
5662  $content,
5663  ['if.' => false],
5664  0,
5665  false,
5666  ],
5667  'if. is "0"' => [
5668  $content,
5669  false,
5670  $content,
5671  ['if.' => '0'],
5672  0,
5673  false,
5674  ],
5675  'checkIf returning true' => [
5676  $content,
5677  false,
5678  $content,
5679  $conf,
5680  1,
5681  true,
5682  ],
5683  // evals to false
5684  'checkIf returning false' => [
5685  '',
5686  true,
5687  $content,
5688  $conf,
5689  1,
5690  false,
5691  ],
5692  ];
5693  }
5694 
5715  public function ‪stdWrap_if(string $expect, bool $stop, string $content, array $conf, int $times, bool $will): void
5716  {
5717  ‪$subject = $this->getAccessibleMock(
5718  ContentObjectRenderer::class,
5719  ['checkIf']
5720  );
5721  ‪$subject->_set('stdWrapRecursionLevel', 1);
5722  ‪$subject->_set('stopRendering', [1 => false]);
5723  ‪$subject
5724  ->expects(self::exactly($times))
5725  ->method('checkIf')
5726  ->with($conf['if.'] ?? null)
5727  ->willReturn($will);
5728  self::assertSame($expect, ‪$subject->stdWrap_if($content, $conf));
5729  self::assertSame($stop, ‪$subject->_get('stopRendering')[1]);
5730  }
5731 
5737  public static function ‪checkIfDataProvider(): array
5738  {
5739  return [
5740  'true bitAnd the same' => [true, ['bitAnd' => '4', 'value' => '4']],
5741  'true bitAnd included' => [true, ['bitAnd' => '6', 'value' => '4']],
5742  'false bitAnd' => [false, ['bitAnd' => '4', 'value' => '3']],
5743  'negate true bitAnd the same' => [false, ['bitAnd' => '4', 'value' => '4', 'negate' => '1']],
5744  'negate true bitAnd included' => [false, ['bitAnd' => '6', 'value' => '4', 'negate' => '1']],
5745  'negate false bitAnd' => [true, ['bitAnd' => '3', 'value' => '4', 'negate' => '1']],
5746  'contains matches' => [true, ['contains' => 'long text', 'value' => 'this is a long text']],
5747  'contains does not match' => [false, ['contains' => 'short text', 'value' => 'this is a long text']],
5748  'negate contains does not match' => [false, ['contains' => 'long text', 'value' => 'this is a long text', 'negate' => '1']],
5749  'negate contains does not match but matches' => [true, ['contains' => 'short text', 'value' => 'this is a long text', 'negate' => '1']],
5750  'startsWith matches' => [true, ['startsWith' => 'this is', 'value' => 'this is a long text']],
5751  'startsWith does not match' => [false, ['startsWith' => 'a long text', 'value' => 'this is a long text']],
5752  'negate startsWith does not match' => [false, ['startsWith' => 'this is', 'value' => 'this is a long text', 'negate' => '1']],
5753  'negate startsWith does not match but matches' => [true, ['startsWith' => 'a long text', 'value' => 'this is a long text', 'negate' => '1']],
5754  'endsWith matches' => [true, ['endsWith' => 'a long text', 'value' => 'this is a long text']],
5755  'endsWith does not match' => [false, ['endsWith' => 'this is', 'value' => 'this is a long text']],
5756  'negate endsWith does not match' => [false, ['endsWith' => 'a long text', 'value' => 'this is a long text', 'negate' => '1']],
5757  'negate endsWith does not match but matches' => [true, ['endsWith' => 'this is', 'value' => 'this is a long text', 'negate' => '1']],
5758  ];
5759  }
5760 
5769  public function ‪checkIf(bool $expect, array $conf): void
5770  {
5771  ‪$subject = $this->getAccessibleMock(
5772  ContentObjectRenderer::class,
5773  ['stdWrap']
5774  );
5775  self::assertSame($expect, ‪$subject->checkIf($conf));
5776  }
5777 
5783  public static function ‪stdWrap_ifBlankDataProvider(): array
5784  {
5785  $alt = ‪StringUtility::getUniqueId('alternative content');
5786  $conf = ['ifBlank' => $alt];
5787  return [
5788  // blank cases
5789  'null is blank' => [$alt, null, $conf],
5790  'false is blank' => [$alt, false, $conf],
5791  'empty string is blank' => [$alt, '', $conf],
5792  'whitespace is blank' => [$alt, "\t" . '', $conf],
5793  // non-blank cases
5794  'string is not blank' => ['string', 'string', $conf],
5795  'zero is not blank' => [0, 0, $conf],
5796  'zero string is not blank' => ['0', '0', $conf],
5797  'zero float is not blank' => [0.0, 0.0, $conf],
5798  'true is not blank' => [true, true, $conf],
5799  ];
5800  }
5801 
5818  public function ‪stdWrap_ifBlank(mixed $expect, mixed $content, array $conf): void
5819  {
5820  $result = $this->subject->stdWrap_ifBlank($content, $conf);
5821  self::assertSame($expect, $result);
5822  }
5823 
5829  public static function ‪stdWrap_ifEmptyDataProvider(): array
5830  {
5831  $alt = ‪StringUtility::getUniqueId('alternative content');
5832  $conf = ['ifEmpty' => $alt];
5833  return [
5834  // empty cases
5835  'null is empty' => [$alt, null, $conf],
5836  'false is empty' => [$alt, false, $conf],
5837  'zero is empty' => [$alt, 0, $conf],
5838  'float zero is empty' => [$alt, 0.0, $conf],
5839  'whitespace is empty' => [$alt, "\t" . ' ', $conf],
5840  'empty string is empty' => [$alt, '', $conf],
5841  'zero string is empty' => [$alt, '0', $conf],
5842  'zero string is empty with whitespace' => [
5843  $alt,
5844  "\t" . ' 0 ' . "\t",
5845  $conf,
5846  ],
5847  // non-empty cases
5848  'string is not empty' => ['string', 'string', $conf],
5849  '1 is not empty' => [1, 1, $conf],
5850  '-1 is not empty' => [-1, -1, $conf],
5851  '0.1 is not empty' => [0.1, 0.1, $conf],
5852  '-0.1 is not empty' => [-0.1, -0.1, $conf],
5853  'true is not empty' => [true, true, $conf],
5854  ];
5855  }
5856 
5872  public function ‪stdWrap_ifEmpty(mixed $expect, mixed $content, array $conf): void
5873  {
5874  $result = $this->subject->stdWrap_ifEmpty($content, $conf);
5875  self::assertSame($expect, $result);
5876  }
5877 
5883  public static function ‪stdWrap_ifNullDataProvider(): array
5884  {
5885  $alt = ‪StringUtility::getUniqueId('alternative content');
5886  $conf = ['ifNull' => $alt];
5887  return [
5888  'only null is null' => [$alt, null, $conf],
5889  'zero is not null' => [0, 0, $conf],
5890  'float zero is not null' => [0.0, 0.0, $conf],
5891  'false is not null' => [false, false, $conf],
5892  'zero string is not null' => ['0', '0', $conf],
5893  'empty string is not null' => ['', '', $conf],
5894  'whitespace is not null' => ["\t" . '', "\t" . '', $conf],
5895  ];
5896  }
5897 
5913  public function ‪stdWrap_ifNull(mixed $expect, mixed $content, array $conf): void
5914  {
5915  $result = $this->subject->stdWrap_ifNull($content, $conf);
5916  self::assertSame($expect, $result);
5917  }
5918 
5924  public static function ‪stdWrap_innerWrapDataProvider(): array
5925  {
5926  return [
5927  'no conf' => [
5928  'XXX',
5929  'XXX',
5930  [],
5931  ],
5932  'simple' => [
5933  '<wrap>XXX</wrap>',
5934  'XXX',
5935  ['innerWrap' => '<wrap>|</wrap>'],
5936  ],
5937  'missing pipe puts wrap before' => [
5938  '<pre>XXX',
5939  'XXX',
5940  ['innerWrap' => '<pre>'],
5941  ],
5942  'trims whitespace' => [
5943  '<wrap>XXX</wrap>',
5944  'XXX',
5945  ['innerWrap' => '<wrap>' . "\t" . ' | ' . "\t" . '</wrap>'],
5946  ],
5947  'split char change is not possible' => [
5948  '<wrap> # </wrap>XXX',
5949  'XXX',
5950  [
5951  'innerWrap' => '<wrap> # </wrap>',
5952  'innerWrap.' => ['splitChar' => '#'],
5953  ],
5954  ],
5955  ];
5956  }
5957 
5967  public function ‪stdWrap_innerWrap(string $expected, string $input, array $conf): void
5968  {
5969  self::assertSame(
5970  $expected,
5971  $this->subject->stdWrap_innerWrap($input, $conf)
5972  );
5973  }
5974 
5980  public static function ‪stdWrap_innerWrap2DataProvider(): array
5981  {
5982  return [
5983  'no conf' => [
5984  'XXX',
5985  'XXX',
5986  [],
5987  ],
5988  'simple' => [
5989  '<wrap>XXX</wrap>',
5990  'XXX',
5991  ['innerWrap2' => '<wrap>|</wrap>'],
5992  ],
5993  'missing pipe puts wrap before' => [
5994  '<pre>XXX',
5995  'XXX',
5996  ['innerWrap2' => '<pre>'],
5997  ],
5998  'trims whitespace' => [
5999  '<wrap>XXX</wrap>',
6000  'XXX',
6001  ['innerWrap2' => '<wrap>' . "\t" . ' | ' . "\t" . '</wrap>'],
6002  ],
6003  'split char change is not possible' => [
6004  '<wrap> # </wrap>XXX',
6005  'XXX',
6006  [
6007  'innerWrap2' => '<wrap> # </wrap>',
6008  'innerWrap2.' => ['splitChar' => '#'],
6009  ],
6010  ],
6011  ];
6012  }
6013 
6023  public function ‪stdWrap_innerWrap2(string $expected, string $input, array $conf): void
6024  {
6025  self::assertSame(
6026  $expected,
6027  $this->subject->stdWrap_innerWrap2($input, $conf)
6028  );
6029  }
6030 
6042  public function ‪stdWrap_insertData(): void
6043  {
6044  $content = ‪StringUtility::getUniqueId('content');
6045  $return = ‪StringUtility::getUniqueId('return');
6046  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6047  ->onlyMethods(['insertData'])->getMock();
6048  ‪$subject->expects(self::once())->method('insertData')
6049  ->with($content)->willReturn($return);
6050  self::assertSame(
6051  $return,
6052  ‪$subject->stdWrap_insertData($content)
6053  );
6054  }
6055 
6061  public static function ‪stdWrap_insertDataProvider(): array
6062  {
6063  return [
6064  'empty' => ['', ''],
6065  'notFoundData' => ['any=1', 'any{$string}=1'],
6066  'queryParameter' => ['any{#string}=1', 'any{#string}=1'],
6067  ];
6068  }
6069 
6078  public function ‪stdWrap_insertDataAndInputExamples(mixed $expect, string $content): void
6079  {
6080  self::assertSame($expect, $this->subject->stdWrap_insertData($content));
6081  }
6082 
6088  public static function ‪stdWrap_intvalDataProvider(): array
6089  {
6090  return [
6091  // numbers
6092  'int' => [123, 123],
6093  'float' => [123, 123.45],
6094  'float does not round up' => [123, 123.55],
6095  // negative numbers
6096  'negative int' => [-123, -123],
6097  'negative float' => [-123, -123.45],
6098  'negative float does not round down' => [-123, -123.55],
6099  // strings
6100  'word string' => [0, 'string'],
6101  'empty string' => [0, ''],
6102  'zero string' => [0, '0'],
6103  'int string' => [123, '123'],
6104  'float string' => [123, '123.55'],
6105  'negative float string' => [-123, '-123.55'],
6106  // other types
6107  'null' => [0, null],
6108  'true' => [1, true],
6109  'false' => [0, false],
6110  ];
6111  }
6112 
6132  public function ‪stdWrap_intval(int $expect, mixed $content): void
6133  {
6134  self::assertSame($expect, $this->subject->stdWrap_intval($content));
6135  }
6136 
6142  public static function ‪stdWrapKeywordsDataProvider(): array
6143  {
6144  return [
6145  'empty string' => ['', ''],
6146  'blank' => ['', ' '],
6147  'tab' => ['', "\t"],
6148  'single semicolon' => [',', ' ; '],
6149  'single comma' => [',', ' , '],
6150  'single nl' => [',', ' ' . PHP_EOL . ' '],
6151  'double semicolon' => [',,', ' ; ; '],
6152  'double comma' => [',,', ' , , '],
6153  'double nl' => [',,', ' ' . PHP_EOL . ' ' . PHP_EOL . ' '],
6154  'simple word' => ['one', ' one '],
6155  'simple word trimmed' => ['one', 'one'],
6156  ', separated' => ['one,two', ' one , two '],
6157  '; separated' => ['one,two', ' one ; two '],
6158  'nl separated' => ['one,two', ' one ' . PHP_EOL . ' two '],
6159  ', typical' => ['one,two,three', 'one, two, three'],
6160  '; typical' => ['one,two,three', ' one; two; three'],
6161  'nl typical' => [
6162  'one,two,three',
6163  'one' . PHP_EOL . 'two' . PHP_EOL . 'three',
6164  ],
6165  ', sourounded' => [',one,two,', ' , one , two , '],
6166  '; sourounded' => [',one,two,', ' ; one ; two ; '],
6167  'nl sourounded' => [
6168  ',one,two,',
6169  ' ' . PHP_EOL . ' one ' . PHP_EOL . ' two ' . PHP_EOL . ' ',
6170  ],
6171  'mixed' => [
6172  'one,two,three,four',
6173  ' one, two; three' . PHP_EOL . 'four',
6174  ],
6175  'keywods with blanks in words' => [
6176  'one plus,two minus',
6177  ' one plus , two minus ',
6178  ],
6179  ];
6180  }
6181 
6190  public function ‪stdWrap_keywords(string $expected, string $input): void
6191  {
6192  self::assertSame($expected, $this->subject->stdWrap_keywords($input));
6193  }
6194 
6200  public static function ‪stdWrap_langDataProvider(): array
6201  {
6202  return [
6203  'empty conf' => [
6204  'original',
6205  'original',
6206  [],
6207  'de_DE',
6208  ],
6209  'translation de' => [
6210  'Übersetzung',
6211  'original',
6212  [
6213  'lang.' => [
6214  'de' => 'Übersetzung',
6215  'it' => 'traduzione',
6216  ],
6217  ],
6218  'de_DE',
6219  ],
6220  'translation it' => [
6221  'traduzione',
6222  'original',
6223  [
6224  'lang.' => [
6225  'de' => 'Übersetzung',
6226  'it' => 'traduzione',
6227  ],
6228  ],
6229  'it_IT',
6230  ],
6231  'no translation' => [
6232  'original',
6233  'original',
6234  [
6235  'lang.' => [
6236  'de' => 'Übersetzung',
6237  'it' => 'traduzione',
6238  ],
6239  ],
6240  'en',
6241  ],
6242  'missing label' => [
6243  'original',
6244  'original',
6245  [
6246  'lang.' => [
6247  'de' => 'Übersetzung',
6248  'it' => 'traduzione',
6249  ],
6250  ],
6251  'fr_FR',
6252  ],
6253  ];
6254  }
6255 
6266  public function ‪stdWrap_langViaSiteLanguage(string $expected, string $input, array $conf, string $language): void
6267  {
6268  $site = $this->‪createSiteWithLanguage([
6269  'base' => '/',
6270  'languageId' => 2,
6271  'locale' => $language,
6272  ]);
6273  $this->frontendControllerMock->_set('language', $site->getLanguageById(2));
6274  self::assertSame(
6275  $expected,
6276  $this->subject->stdWrap_lang($input, $conf)
6277  );
6278  }
6279 
6293  public function ‪stdWrap_listNum(): void
6294  {
6295  $content = ‪StringUtility::getUniqueId('content');
6296  $conf = [
6297  'listNum' => ‪StringUtility::getUniqueId('listNum'),
6298  'listNum.' => [
6299  'splitChar' => ‪StringUtility::getUniqueId('splitChar'),
6300  ],
6301  ];
6302  $return = ‪StringUtility::getUniqueId('return');
6303  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6304  ->onlyMethods(['listNum'])->getMock();
6305  ‪$subject
6306  ->expects(self::once())
6307  ->method('listNum')
6308  ->with(
6309  $content,
6310  $conf['listNum'],
6311  $conf['listNum.']['splitChar']
6312  )
6313  ->willReturn($return);
6314  self::assertSame(
6315  $return,
6316  ‪$subject->stdWrap_listNum($content, $conf)
6317  );
6318  }
6319 
6325  public static function ‪stdWrap_noTrimWrapDataProvider(): array
6326  {
6327  return [
6328  'Standard case' => [
6329  ' left middle right ',
6330  'middle',
6331  [
6332  'noTrimWrap' => '| left | right |',
6333  ],
6334  ],
6335  'Tabs as whitespace' => [
6336  "\t" . 'left' . "\t" . 'middle' . "\t" . 'right' . "\t",
6337  'middle',
6338  [
6339  'noTrimWrap' =>
6340  '|' . "\t" . 'left' . "\t" . '|' . "\t" . 'right' . "\t" . '|',
6341  ],
6342  ],
6343  'Split char is 0' => [
6344  ' left middle right ',
6345  'middle',
6346  [
6347  'noTrimWrap' => '0 left 0 right 0',
6348  'noTrimWrap.' => ['splitChar' => '0'],
6349  ],
6350  ],
6351  'Split char is pipe (default)' => [
6352  ' left middle right ',
6353  'middle',
6354  [
6355  'noTrimWrap' => '| left | right |',
6356  'noTrimWrap.' => ['splitChar' => '|'],
6357  ],
6358  ],
6359  'Split char is a' => [
6360  ' left middle right ',
6361  'middle',
6362  [
6363  'noTrimWrap' => 'a left a right a',
6364  'noTrimWrap.' => ['splitChar' => 'a'],
6365  ],
6366  ],
6367  'Split char is a word (ab)' => [
6368  ' left middle right ',
6369  'middle',
6370  [
6371  'noTrimWrap' => 'ab left ab right ab',
6372  'noTrimWrap.' => ['splitChar' => 'ab'],
6373  ],
6374  ],
6375  'Split char accepts stdWrap' => [
6376  ' left middle right ',
6377  'middle',
6378  [
6379  'noTrimWrap' => 'abc left abc right abc',
6380  'noTrimWrap.' => [
6381  'splitChar' => 'b',
6382  'splitChar.' => ['wrap' => 'a|c'],
6383  ],
6384  ],
6385  ],
6386  ];
6387  }
6388 
6398  public function ‪stdWrap_noTrimWrap(string $expect, string $content, array $conf): void
6399  {
6400  self::assertSame(
6401  $expect,
6402  $this->subject->stdWrap_noTrimWrap($content, $conf)
6403  );
6404  }
6405 
6417  public function ‪stdWrap_numRows(): void
6418  {
6419  $conf = [
6420  'numRows' => ‪StringUtility::getUniqueId('numRows'),
6421  'numRows.' => [‪StringUtility::getUniqueId('numRows')],
6422  ];
6423  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6424  ->onlyMethods(['numRows'])->getMock();
6425  ‪$subject->expects(self::once())->method('numRows')
6426  ->with($conf['numRows.'])->willReturn('return');
6427  self::assertSame(
6428  'return',
6429  ‪$subject->stdWrap_numRows('discard', $conf)
6430  );
6431  }
6432 
6445  public function ‪stdWrap_numberFormat(): void
6446  {
6447  $content = ‪StringUtility::getUniqueId('content');
6448  $conf = [
6449  'numberFormat' => ‪StringUtility::getUniqueId('not used'),
6450  'numberFormat.' => [‪StringUtility::getUniqueId('numberFormat.')],
6451  ];
6452  $return = ‪StringUtility::getUniqueId('return');
6453  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6454  ->onlyMethods(['numberFormat'])->getMock();
6455  ‪$subject
6456  ->expects(self::once())
6457  ->method('numberFormat')
6458  ->with((float)$content, $conf['numberFormat.'])
6459  ->willReturn($return);
6460  self::assertSame(
6461  $return,
6462  ‪$subject->stdWrap_numberFormat($content, $conf)
6463  );
6464  }
6465 
6471  public static function ‪stdWrap_outerWrapDataProvider(): array
6472  {
6473  return [
6474  'no conf' => [
6475  'XXX',
6476  'XXX',
6477  [],
6478  ],
6479  'simple' => [
6480  '<wrap>XXX</wrap>',
6481  'XXX',
6482  ['outerWrap' => '<wrap>|</wrap>'],
6483  ],
6484  'missing pipe puts wrap before' => [
6485  '<pre>XXX',
6486  'XXX',
6487  ['outerWrap' => '<pre>'],
6488  ],
6489  'trims whitespace' => [
6490  '<wrap>XXX</wrap>',
6491  'XXX',
6492  ['outerWrap' => '<wrap>' . "\t" . ' | ' . "\t" . '</wrap>'],
6493  ],
6494  'split char change is not possible' => [
6495  '<wrap> # </wrap>XXX',
6496  'XXX',
6497  [
6498  'outerWrap' => '<wrap> # </wrap>',
6499  'outerWrap.' => ['splitChar' => '#'],
6500  ],
6501  ],
6502  ];
6503  }
6504 
6514  public function ‪stdWrap_outerWrap(string $expected, string $input, array $conf): void
6515  {
6516  self::assertSame(
6517  $expected,
6518  $this->subject->stdWrap_outerWrap($input, $conf)
6519  );
6520  }
6521 
6527  public static function ‪stdWrap_overrideDataProvider(): array
6528  {
6529  return [
6530  'standard case' => [
6531  'override',
6532  'content',
6533  ['override' => 'override'],
6534  ],
6535  'empty conf does not override' => [
6536  'content',
6537  'content',
6538  [],
6539  ],
6540  'empty string does not override' => [
6541  'content',
6542  'content',
6543  ['override' => ''],
6544  ],
6545  'whitespace does not override' => [
6546  'content',
6547  'content',
6548  ['override' => ' ' . "\t"],
6549  ],
6550  'zero does not override' => [
6551  'content',
6552  'content',
6553  ['override' => 0],
6554  ],
6555  'false does not override' => [
6556  'content',
6557  'content',
6558  ['override' => false],
6559  ],
6560  'null does not override' => [
6561  'content',
6562  'content',
6563  ['override' => null],
6564  ],
6565  'one does override' => [
6566  1,
6567  'content',
6568  ['override' => 1],
6569  ],
6570  'minus one does override' => [
6571  -1,
6572  'content',
6573  ['override' => -1],
6574  ],
6575  'float does override' => [
6576  -0.1,
6577  'content',
6578  ['override' => -0.1],
6579  ],
6580  'true does override' => [
6581  true,
6582  'content',
6583  ['override' => true],
6584  ],
6585  'the value is not trimmed' => [
6586  "\t" . 'override',
6587  'content',
6588  ['override' => "\t" . 'override'],
6589  ],
6590  ];
6591  }
6592 
6600  public function ‪stdWrap_override(mixed $expect, string $content, array $conf): void
6601  {
6602  self::assertSame(
6603  $expect,
6604  $this->subject->stdWrap_override($content, $conf)
6605  );
6606  }
6607 
6621  public function ‪stdWrap_parseFunc(): void
6622  {
6623  $content = ‪StringUtility::getUniqueId('content');
6624  $conf = [
6625  'parseFunc' => ‪StringUtility::getUniqueId('parseFunc'),
6626  'parseFunc.' => [‪StringUtility::getUniqueId('parseFunc.')],
6627  ];
6628  $return = ‪StringUtility::getUniqueId('return');
6629  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6630  ->onlyMethods(['parseFunc'])->getMock();
6631  ‪$subject
6632  ->expects(self::once())
6633  ->method('parseFunc')
6634  ->with($content, $conf['parseFunc.'], $conf['parseFunc'])
6635  ->willReturn($return);
6636  self::assertSame(
6637  $return,
6638  ‪$subject->stdWrap_parseFunc($content, $conf)
6639  );
6640  }
6641 
6655  public function ‪stdWrap_postCObject(): void
6656  {
6657  $debugKey = '/stdWrap/.postCObject';
6658  $content = ‪StringUtility::getUniqueId('content');
6659  $conf = [
6660  'postCObject' => ‪StringUtility::getUniqueId('postCObject'),
6661  'postCObject.' => [‪StringUtility::getUniqueId('postCObject.')],
6662  ];
6663  $return = ‪StringUtility::getUniqueId('return');
6664  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6665  ->onlyMethods(['cObjGetSingle'])->getMock();
6666  ‪$subject
6667  ->expects(self::once())
6668  ->method('cObjGetSingle')
6669  ->with($conf['postCObject'], $conf['postCObject.'], $debugKey)
6670  ->willReturn($return);
6671  self::assertSame(
6672  $content . $return,
6673  ‪$subject->stdWrap_postCObject($content, $conf)
6674  );
6675  }
6676 
6688  public function ‪stdWrap_postUserFunc(): void
6689  {
6690  $content = ‪StringUtility::getUniqueId('content');
6691  $conf = [
6692  'postUserFunc' => ‪StringUtility::getUniqueId('postUserFunc'),
6693  'postUserFunc.' => [‪StringUtility::getUniqueId('postUserFunc.')],
6694  ];
6695  $return = ‪StringUtility::getUniqueId('return');
6696  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6697  ->onlyMethods(['callUserFunction'])->getMock();
6698  ‪$subject
6699  ->expects(self::once())
6700  ->method('callUserFunction')
6701  ->with($conf['postUserFunc'], $conf['postUserFunc.'])
6702  ->willReturn($return);
6703  self::assertSame(
6704  $return,
6705  ‪$subject->stdWrap_postUserFunc($content, $conf)
6706  );
6707  }
6708 
6727  public function ‪stdWrap_postUserFuncInt(): void
6728  {
6729  $uniqueHash = ‪StringUtility::getUniqueId('uniqueHash');
6730  $substKey = 'INT_SCRIPT.' . $uniqueHash;
6731  $content = ‪StringUtility::getUniqueId('content');
6732  $conf = [
6733  'postUserFuncInt' => ‪StringUtility::getUniqueId('function'),
6734  'postUserFuncInt.' => [‪StringUtility::getUniqueId('function array')],
6735  ];
6736  $expect = '<!--' . $substKey . '-->';
6737  $frontend = $this->getMockBuilder(TypoScriptFrontendController::class)
6738  ->disableOriginalConstructor()->onlyMethods(['uniqueHash'])
6739  ->getMock();
6740  $frontend->expects(self::once())->method('uniqueHash')
6741  ->with()->willReturn($uniqueHash);
6742  $frontend->config = ['INTincScript' => []];
6743  ‪$subject = $this->getAccessibleMock(
6744  ContentObjectRenderer::class,
6745  null,
6746  [$frontend]
6747  );
6748  self::assertSame(
6749  $expect,
6750  ‪$subject->stdWrap_postUserFuncInt($content, $conf)
6751  );
6752  $array = [
6753  'content' => $content,
6754  'postUserFunc' => $conf['postUserFuncInt'],
6755  'conf' => $conf['postUserFuncInt.'],
6756  'type' => 'POSTUSERFUNC',
6757  'cObj' => serialize(‪$subject),
6758  ];
6759  self::assertSame(
6760  $array,
6761  $frontend->config['INTincScript'][$substKey]
6762  );
6763  }
6764 
6778  public function ‪stdWrap_preCObject(): void
6779  {
6780  $debugKey = '/stdWrap/.preCObject';
6781  $content = ‪StringUtility::getUniqueId('content');
6782  $conf = [
6783  'preCObject' => ‪StringUtility::getUniqueId('preCObject'),
6784  'preCObject.' => [‪StringUtility::getUniqueId('preCObject.')],
6785  ];
6786  $return = ‪StringUtility::getUniqueId('return');
6787  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6788  ->onlyMethods(['cObjGetSingle'])->getMock();
6789  ‪$subject
6790  ->expects(self::once())
6791  ->method('cObjGetSingle')
6792  ->with($conf['preCObject'], $conf['preCObject.'], $debugKey)
6793  ->willReturn($return);
6794  self::assertSame(
6795  $return . $content,
6796  ‪$subject->stdWrap_preCObject($content, $conf)
6797  );
6798  }
6799 
6813  public function ‪stdWrap_preIfEmptyListNum(): void
6814  {
6815  $content = ‪StringUtility::getUniqueId('content');
6816  $conf = [
6817  'preIfEmptyListNum' => ‪StringUtility::getUniqueId('preIfEmptyListNum'),
6818  'preIfEmptyListNum.' => [
6819  'splitChar' => ‪StringUtility::getUniqueId('splitChar'),
6820  ],
6821  ];
6822  $return = ‪StringUtility::getUniqueId('return');
6823  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6824  ->onlyMethods(['listNum'])->getMock();
6825  ‪$subject
6826  ->expects(self::once())
6827  ->method('listNum')
6828  ->with(
6829  $content,
6830  $conf['preIfEmptyListNum'],
6831  $conf['preIfEmptyListNum.']['splitChar']
6832  )
6833  ->willReturn($return);
6834  self::assertSame(
6835  $return,
6836  ‪$subject->stdWrap_preIfEmptyListNum($content, $conf)
6837  );
6838  }
6839 
6845  public static function ‪stdWrap_prefixCommentDataProvider(): array
6846  {
6847  $content = ‪StringUtility::getUniqueId('content');
6848  $will = ‪StringUtility::getUniqueId('will');
6849  $conf = [];
6850  $conf['prefixComment'] = ‪StringUtility::getUniqueId('prefixComment');
6851  $emptyConf1 = [];
6852  $emptyConf2 = [];
6853  $emptyConf2['prefixComment'] = '';
6854  return [
6855  'standard case' => [$will, $content, $conf, false, 1, $will],
6856  'emptyConf1' => [$content, $content, $emptyConf1, false, 0, $will],
6857  'emptyConf2' => [$content, $content, $emptyConf2, false, 0, $will],
6858  'disabled by bool' => [$content, $content, $conf, true, 0, $will],
6859  'disabled by int' => [$content, $content, $conf, 1, 0, $will],
6860  ];
6861  }
6862 
6880  public function ‪stdWrap_prefixComment(
6881  string $expect,
6882  string $content,
6883  array $conf,
6884  int|bool $disable,
6885  int $times,
6886  string $will
6887  ): void {
6888  $this->frontendControllerMock
6889  ->config['config']['disablePrefixComment'] = $disable;
6890  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6891  ->onlyMethods(['prefixComment'])->getMock();
6892  ‪$subject
6893  ->expects(self::exactly($times))
6894  ->method('prefixComment')
6895  ->with($conf['prefixComment'] ?? null, [], $content)
6896  ->willReturn($will);
6897  self::assertSame(
6898  $expect,
6899  ‪$subject->stdWrap_prefixComment($content, $conf)
6900  );
6901  }
6902 
6916  public function ‪stdWrap_prepend(): void
6917  {
6918  $debugKey = '/stdWrap/.prepend';
6919  $content = ‪StringUtility::getUniqueId('content');
6920  $conf = [
6921  'prepend' => ‪StringUtility::getUniqueId('prepend'),
6922  'prepend.' => [‪StringUtility::getUniqueId('prepend.')],
6923  ];
6924  $return = ‪StringUtility::getUniqueId('return');
6925  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
6926  ->onlyMethods(['cObjGetSingle'])->getMock();
6927  ‪$subject
6928  ->expects(self::once())
6929  ->method('cObjGetSingle')
6930  ->with($conf['prepend'], $conf['prepend.'], $debugKey)
6931  ->willReturn($return);
6932  self::assertSame(
6933  $return . $content,
6934  ‪$subject->stdWrap_prepend($content, $conf)
6935  );
6936  }
6937 
6943  public static function ‪stdWrap_prioriCalcDataProvider(): array
6944  {
6945  return [
6946  'priority of *' => ['7', '1 + 2 * 3', []],
6947  'priority of parentheses' => ['9', '(1 + 2) * 3', []],
6948  'float' => ['1.5', '3/2', []],
6949  'intval casts to int' => [1, '3/2', ['prioriCalc' => 'intval']],
6950  'intval does not round' => [2, '2.7', ['prioriCalc' => 'intval']],
6951  ];
6952  }
6953 
6974  public function ‪stdWrap_prioriCalc(mixed $expect, string $content, array $conf): void
6975  {
6976  $result = $this->subject->stdWrap_prioriCalc($content, $conf);
6977  self::assertSame($expect, $result);
6978  }
6979 
6993  public function ‪stdWrap_preUserFunc(): void
6994  {
6995  $content = ‪StringUtility::getUniqueId('content');
6996  $conf = [
6997  'preUserFunc' => ‪StringUtility::getUniqueId('preUserFunc'),
6998  'preUserFunc.' => [‪StringUtility::getUniqueId('preUserFunc.')],
6999  ];
7000  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
7001  ->onlyMethods(['callUserFunction'])->getMock();
7002  ‪$subject->expects(self::once())->method('callUserFunction')
7003  ->with($conf['preUserFunc'], $conf['preUserFunc.'], $content)
7004  ->willReturn('return');
7005  self::assertSame(
7006  'return',
7007  ‪$subject->stdWrap_preUserFunc($content, $conf)
7008  );
7009  }
7010 
7016  public static function ‪stdWrap_rawUrlEncodeDataProvider(): array
7017  {
7018  return [
7019  'https://typo3.org?id=10' => [
7020  'https%3A%2F%2Ftypo3.org%3Fid%3D10',
7021  'https://typo3.org?id=10',
7022  ],
7023  'https://typo3.org?id=10&foo=bar' => [
7024  'https%3A%2F%2Ftypo3.org%3Fid%3D10%26foo%3Dbar',
7025  'https://typo3.org?id=10&foo=bar',
7026  ],
7027  ];
7028  }
7029 
7038  public function ‪stdWrap_rawUrlEncode(string $expect, string $content): void
7039  {
7040  self::assertSame(
7041  $expect,
7042  $this->subject->stdWrap_rawUrlEncode($content)
7043  );
7044  }
7045 
7058  public function ‪stdWrap_replacement(): void
7059  {
7060  $content = ‪StringUtility::getUniqueId('content');
7061  $conf = [
7062  'replacement' => ‪StringUtility::getUniqueId('not used'),
7063  'replacement.' => [‪StringUtility::getUniqueId('replacement.')],
7064  ];
7065  $return = ‪StringUtility::getUniqueId('return');
7066  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
7067  ->onlyMethods(['replacement'])->getMock();
7068  ‪$subject
7069  ->expects(self::once())
7070  ->method('replacement')
7071  ->with($content, $conf['replacement.'])
7072  ->willReturn($return);
7073  self::assertSame(
7074  $return,
7075  ‪$subject->stdWrap_replacement($content, $conf)
7076  );
7077  }
7078 
7084  public static function ‪stdWrap_requiredDataProvider(): array
7085  {
7086  return [
7087  // empty content
7088  'empty string is empty' => ['', true, ''],
7089  'null is empty' => ['', true, null],
7090  'false is empty' => ['', true, false],
7091 
7092  // non-empty content
7093  'blank is not empty' => [' ', false, ' '],
7094  'tab is not empty' => ["\t", false, "\t"],
7095  'linebreak is not empty' => [PHP_EOL, false, PHP_EOL],
7096  '"0" is not empty' => ['0', false, '0'],
7097  '0 is not empty' => [0, false, 0],
7098  '1 is not empty' => [1, false, 1],
7099  'true is not empty' => [true, false, true],
7100  ];
7101  }
7102 
7118  public function ‪stdWrap_required(mixed $expect, bool $stop, mixed $content): void
7119  {
7121  ‪$subject->_set('stdWrapRecursionLevel', 1);
7122  ‪$subject->_set('stopRendering', [1 => false]);
7123  self::assertSame($expect, ‪$subject->stdWrap_required($content));
7124  self::assertSame($stop, ‪$subject->_get('stopRendering')[1]);
7125  }
7126 
7139  public function ‪stdWrap_round(): void
7140  {
7141  $content = ‪StringUtility::getUniqueId('content');
7142  $conf = [
7143  'round' => ‪StringUtility::getUniqueId('not used'),
7144  'round.' => [‪StringUtility::getUniqueId('round.')],
7145  ];
7146  $return = ‪StringUtility::getUniqueId('return');
7147  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
7148  ->onlyMethods(['round'])->getMock();
7149  ‪$subject
7150  ->expects(self::once())
7151  ->method('round')
7152  ->with($content, $conf['round.'])
7153  ->willReturn($return);
7154  self::assertSame($return, ‪$subject->stdWrap_round($content, $conf));
7155  }
7156 
7162  public function ‪stdWrap_setContentToCurrent(): void
7163  {
7164  $content = ‪StringUtility::getUniqueId('content');
7165  self::assertNotSame($content, $this->subject->getData('current'));
7166  self::assertSame(
7167  $content,
7168  $this->subject->stdWrap_setContentToCurrent($content)
7169  );
7170  self::assertSame($content, $this->subject->getData('current'));
7171  }
7172 
7178  public static function ‪stdWrap_setCurrentDataProvider(): array
7179  {
7180  return [
7181  'no conf' => [
7182  'content',
7183  [],
7184  ],
7185  'empty string' => [
7186  'content',
7187  ['setCurrent' => ''],
7188  ],
7189  'non-empty string' => [
7190  'content',
7191  ['setCurrent' => 'xxx'],
7192  ],
7193  'integer null' => [
7194  'content',
7195  ['setCurrent' => 0],
7196  ],
7197  'integer not null' => [
7198  'content',
7199  ['setCurrent' => 1],
7200  ],
7201  'boolean true' => [
7202  'content',
7203  ['setCurrent' => true],
7204  ],
7205  'boolean false' => [
7206  'content',
7207  ['setCurrent' => false],
7208  ],
7209  ];
7210  }
7211 
7220  public function ‪stdWrap_setCurrent(string $input, array $conf): void
7221  {
7222  if (isset($conf['setCurrent'])) {
7223  self::assertNotSame($conf['setCurrent'], $this->subject->getData('current'));
7224  }
7225  self::assertSame($input, $this->subject->stdWrap_setCurrent($input, $conf));
7226  if (isset($conf['setCurrent'])) {
7227  self::assertSame($conf['setCurrent'], $this->subject->getData('current'));
7228  }
7229  }
7230 
7243  public function ‪stdWrap_split(): void
7244  {
7245  $content = ‪StringUtility::getUniqueId('content');
7246  $conf = [
7247  'split' => ‪StringUtility::getUniqueId('not used'),
7248  'split.' => [‪StringUtility::getUniqueId('split.')],
7249  ];
7250  $return = ‪StringUtility::getUniqueId('return');
7251  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
7252  ->onlyMethods(['splitObj'])->getMock();
7253  ‪$subject
7254  ->expects(self::once())
7255  ->method('splitObj')
7256  ->with($content, $conf['split.'])
7257  ->willReturn($return);
7258  self::assertSame(
7259  $return,
7260  ‪$subject->stdWrap_split($content, $conf)
7261  );
7262  }
7263 
7275  public function ‪stdWrap_stdWrap(): void
7276  {
7277  $content = ‪StringUtility::getUniqueId('content');
7278  $conf = [
7279  'stdWrap' => ‪StringUtility::getUniqueId('not used'),
7280  'stdWrap.' => [‪StringUtility::getUniqueId('stdWrap.')],
7281  ];
7282  $return = ‪StringUtility::getUniqueId('return');
7283  ‪$subject = $this->getMockBuilder(ContentObjectRenderer::class)
7284  ->onlyMethods(['stdWrap'])->getMock();
7285  ‪$subject
7286  ->expects(self::once())
7287  ->method('stdWrap')
7288  ->with($content, $conf['stdWrap.'])
7289  ->willReturn($return);
7290  self::assertSame($return, ‪$subject->stdWrap_stdWrap($content, $conf));
7291  }
7292 
7296  public static function ‪stdWrap_stdWrapValueDataProvider(): array
7297  {
7298  return [
7299  'only key returns value' => [
7300  'ifNull',
7301  [
7302  'ifNull' => '1',
7303  ],
7304  '',
7305  '1',
7306  ],
7307  'array without key returns empty string' => [
7308  'ifNull',
7309  [
7310  'ifNull.' => '1',
7311  ],
7312  '',
7313  '',
7314  ],
7315  'array without key returns default' => [
7316  'ifNull',
7317  [
7318  'ifNull.' => '1',
7319  ],
7320  'default',
7321  'default',
7322  ],
7323  'non existing key returns default' => [
7324  'ifNull',
7325  [
7326  'noTrimWrap' => 'test',
7327  'noTrimWrap.' => '1',
7328  ],
7329  'default',
7330  'default',
7331  ],
7332  'default value null is returned' => [
7333  'ifNull',
7334  [],
7335  null,
7336  null,
7337  ],
7338  'existing key and array returns stdWrap' => [
7339  'test',
7340  [
7341  'test' => 'value',
7342  'test.' => ['case' => 'upper'],
7343  ],
7344  'default',
7345  'VALUE',
7346  ],
7347  'the string "0" from stdWrap will be returned' => [
7348  'test',
7349  [
7350  'test' => '',
7351  'test.' => [
7352  'wrap' => '|0',
7353  ],
7354  ],
7355  '100',
7356  '0',
7357  ],
7358  ];
7359  }
7360 
7365  public function ‪stdWrap_stdWrapValue(
7366  string $key,
7367  array $configuration,
7368  ?string $defaultValue,
7369  ?string $expected
7370  ): void {
7371  $result = $this->subject->stdWrapValue($key, $configuration, $defaultValue);
7372  self::assertSame($expected, $result);
7373  }
7374 
7380  public static function ‪stdWrap_strPadDataProvider(): array
7381  {
7382  return [
7383  'pad string with default settings and length 10' => [
7384  'Alien ',
7385  'Alien',
7386  [
7387  'length' => '10',
7388  ],
7389  ],
7390  'pad string with default settings and length 10 and multibyte character' => [
7391  'Älien ',
7392  'Älien',
7393  [
7394  'length' => '10',
7395  ],
7396  ],
7397  'pad string with padWith -= and type left and length 10' => [
7398  '-=-=-Alien',
7399  'Alien',
7400  [
7401  'length' => '10',
7402  'padWith' => '-=',
7403