c82032db8941513feb37636f34ce7a5b1ddf419a
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / ContentObject / Menu / AbstractMenuContentObjectTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Menu;
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 use Doctrine\DBAL\Driver\Statement;
18 use Prophecy\Argument;
19 use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
20 use TYPO3\CMS\Core\Context\Context;
21 use TYPO3\CMS\Core\Context\LanguageAspect;
22 use TYPO3\CMS\Core\Database\Connection;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
27 use TYPO3\CMS\Frontend\ContentObject\Menu\AbstractMenuContentObject;
28 use TYPO3\CMS\Frontend\Page\PageRepository;
29 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
30
31 /**
32 * Test case
33 */
34 class AbstractMenuContentObjectTest extends UnitTestCase
35 {
36 /**
37 * @var AbstractMenuContentObject
38 */
39 protected $subject;
40
41 /**
42 * Set up this testcase
43 */
44 protected function setUp(): void
45 {
46 parent::setUp();
47 $proxyClassName = $this->buildAccessibleProxy(AbstractMenuContentObject::class);
48 $this->subject = $this->getMockForAbstractClass($proxyClassName);
49 $GLOBALS['TSFE'] = $this->getMockBuilder(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class)
50 ->setConstructorArgs([$GLOBALS['TYPO3_CONF_VARS'], 1, 1])
51 ->setMethods(['initCaches'])
52 ->getMock();
53 $GLOBALS['TSFE']->cObj = new ContentObjectRenderer();
54 $GLOBALS['TSFE']->page = [];
55 }
56
57 /**
58 * Reset singleton instances
59 */
60 protected function tearDown(): void
61 {
62 GeneralUtility::purgeInstances();
63 parent::tearDown();
64 }
65
66 ////////////////////////////////
67 // Tests concerning sectionIndex
68 ////////////////////////////////
69 /**
70 * Prepares a test for the method sectionIndex
71 */
72 protected function prepareSectionIndexTest()
73 {
74 $connectionProphet = $this->prophesize(Connection::class);
75 $connectionProphet->getExpressionBuilder()->willReturn(new ExpressionBuilder($connectionProphet->reveal()));
76 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
77
78 $connectionPoolProphet = $this->prophesize(ConnectionPool::class);
79 $connectionPoolProphet->getConnectionForTable('tt_content')->willReturn($connectionProphet->reveal());
80 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
81 }
82
83 /**
84 * @test
85 */
86 public function sectionIndexReturnsEmptyArrayIfTheRequestedPageCouldNotBeFetched()
87 {
88 $this->prepareSectionIndexTest();
89 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
90 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue(null));
91 $this->subject->_set('sys_page', $pageRepository);
92 $result = $this->subject->_call('sectionIndex', 'field');
93 $this->assertEquals($result, []);
94 }
95
96 /**
97 * @test
98 */
99 public function sectionIndexUsesTheInternalIdIfNoPageIdWasGiven()
100 {
101 $this->prepareSectionIndexTest();
102 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
103 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue(null))->with(10);
104 $this->subject->_set('sys_page', $pageRepository);
105 $this->subject->_set('id', 10);
106 $result = $this->subject->_call('sectionIndex', 'field');
107 $this->assertEquals($result, []);
108 }
109
110 /**
111 * @test
112 */
113 public function sectionIndexThrowsAnExceptionIfTheInternalQueryFails()
114 {
115 $this->expectException(\UnexpectedValueException::class);
116 $this->expectExceptionCode(1337334849);
117 $this->prepareSectionIndexTest();
118 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
119 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue([]));
120 $this->subject->_set('sys_page', $pageRepository);
121 $this->subject->_set('id', 10);
122
123 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)->getMock();
124 $cObject->expects($this->once())->method('exec_getQuery')->will($this->returnValue(0));
125 $this->subject->_set('parent_cObj', $cObject);
126
127 $this->subject->_call('sectionIndex', 'field');
128 }
129
130 /**
131 * @test
132 */
133 public function sectionIndexReturnsOverlaidRowBasedOnTheLanguageOfTheGivenPage()
134 {
135 $statementProphet = $this->prophesize(Statement::class);
136 $statementProphet->fetch()->shouldBeCalledTimes(2)->willReturn(['uid' => 0, 'header' => 'NOT_OVERLAID'], false);
137
138 $this->prepareSectionIndexTest();
139 $this->subject->_set('mconf', [
140 'sectionIndex.' => [
141 'type' => 'all'
142 ]
143 ]);
144 $context = GeneralUtility::makeInstance(Context::class);
145 $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_MIXED));
146
147 $pageRepository = $this->getMockBuilder(PageRepository::class)->setConstructorArgs([$context])->getMock();
148 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1]));
149 $pageRepository->expects($this->once())->method('getRecordOverlay')->will($this->returnValue(['uid' => 0, 'header' => 'OVERLAID']));
150 $this->subject->_set('sys_page', $pageRepository);
151
152 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)->getMock();
153 $cObject->expects($this->once())->method('exec_getQuery')->willReturn($statementProphet->reveal());
154 $this->subject->_set('parent_cObj', $cObject);
155
156 $result = $this->subject->_call('sectionIndex', 'field');
157 $this->assertEquals($result[0]['title'], 'OVERLAID');
158 }
159
160 /**
161 * @return array
162 */
163 public function sectionIndexFiltersDataProvider()
164 {
165 return [
166 'unfiltered fields' => [
167 1,
168 [
169 'sectionIndex' => 1,
170 'header' => 'foo',
171 'header_layout' => 1
172 ]
173 ],
174 'with unset section index' => [
175 0,
176 [
177 'sectionIndex' => 0,
178 'header' => 'foo',
179 'header_layout' => 1
180 ]
181 ],
182 'with unset header' => [
183 0,
184 [
185 'sectionIndex' => 1,
186 'header' => '',
187 'header_layout' => 1
188 ]
189 ],
190 'with header layout 100' => [
191 0,
192 [
193 'sectionIndex' => 1,
194 'header' => 'foo',
195 'header_layout' => 100
196 ]
197 ]
198 ];
199 }
200
201 /**
202 * @test
203 * @dataProvider sectionIndexFiltersDataProvider
204 * @param int $expectedAmount
205 * @param array $dataRow
206 */
207 public function sectionIndexFilters($expectedAmount, array $dataRow)
208 {
209 $statementProphet = $this->prophesize(Statement::class);
210 $statementProphet->fetch()->willReturn($dataRow, false);
211
212 $this->prepareSectionIndexTest();
213 $this->subject->_set('mconf', [
214 'sectionIndex.' => [
215 'type' => 'header'
216 ]
217 ]);
218
219 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
220 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue(['_PAGES_OVERLAY_LANGUAGE' => 1]));
221 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue([]));
222 $this->subject->_set('sys_page', $pageRepository);
223
224 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)->getMock();
225 $cObject->expects($this->once())->method('exec_getQuery')->willReturn($statementProphet->reveal());
226 $this->subject->_set('parent_cObj', $cObject);
227
228 $result = $this->subject->_call('sectionIndex', 'field');
229 $this->assertCount($expectedAmount, $result);
230 }
231
232 /**
233 * @return array
234 */
235 public function sectionIndexQueriesWithDifferentColPosDataProvider()
236 {
237 return [
238 'no configuration' => [
239 [],
240 'colPos = 0'
241 ],
242 'with useColPos 2' => [
243 ['useColPos' => 2],
244 'colPos = 2'
245 ],
246 'with useColPos -1' => [
247 ['useColPos' => -1],
248 ''
249 ],
250 'with stdWrap useColPos' => [
251 [
252 'useColPos.' => [
253 'wrap' => '2|'
254 ]
255 ],
256 'colPos = 2'
257 ]
258 ];
259 }
260
261 /**
262 * @test
263 * @dataProvider sectionIndexQueriesWithDifferentColPosDataProvider
264 * @param array $configuration
265 * @param string $whereClausePrefix
266 */
267 public function sectionIndexQueriesWithDifferentColPos($configuration, $whereClausePrefix)
268 {
269 $statementProphet = $this->prophesize(Statement::class);
270 $statementProphet->fetch()->willReturn([]);
271
272 $this->prepareSectionIndexTest();
273 $this->subject->_set('mconf', ['sectionIndex.' => $configuration]);
274
275 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
276 $pageRepository->expects($this->once())->method('getPage')->will($this->returnValue([]));
277 $this->subject->_set('sys_page', $pageRepository);
278
279 $queryConfiguration = [
280 'pidInList' => 12,
281 'orderBy' => 'field',
282 'languageField' => 'sys_language_uid',
283 'where' => $whereClausePrefix
284 ];
285
286 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)->getMock();
287 $cObject->expects($this->once())->method('exec_getQuery')
288 ->with('tt_content', $queryConfiguration)->willReturn($statementProphet->reveal());
289 $this->subject->_set('parent_cObj', $cObject);
290
291 $this->subject->_call('sectionIndex', 'field', 12);
292 }
293
294 ////////////////////////////////////
295 // Tests concerning menu item states
296 ////////////////////////////////////
297 /**
298 * @return array
299 */
300 public function ifsubHasToCheckExcludeUidListDataProvider()
301 {
302 return [
303 'none excluded' => [
304 [12, 34, 56],
305 '1, 23, 456',
306 true
307 ],
308 'one excluded' => [
309 [1, 234, 567],
310 '1, 23, 456',
311 true
312 ],
313 'three excluded' => [
314 [1, 23, 456],
315 '1, 23, 456',
316 false
317 ],
318 'empty excludeList' => [
319 [1, 123, 45],
320 '',
321 true
322 ],
323 'empty menu' => [
324 [],
325 '1, 23, 456',
326 false
327 ],
328 ];
329 }
330
331 /**
332 * @test
333 * @dataProvider ifsubHasToCheckExcludeUidListDataProvider
334 * @param array $menuItems
335 * @param string $excludeUidList
336 * @param bool $expectedResult
337 */
338 public function ifsubHasToCheckExcludeUidList($menuItems, $excludeUidList, $expectedResult)
339 {
340 $menu = [];
341 foreach ($menuItems as $page) {
342 $menu[] = ['uid' => $page];
343 }
344 $runtimeCacheMock = $this->getMockBuilder(VariableFrontend::class)->setMethods(['get', 'set'])->disableOriginalConstructor()->getMock();
345 $runtimeCacheMock->expects($this->once())->method('get')->with($this->anything())->willReturn(false);
346 $runtimeCacheMock->expects($this->once())->method('set')->with($this->anything(), ['result' => $expectedResult]);
347
348 $proxyClassName = $this->buildAccessibleProxy(AbstractMenuContentObject::class);
349 $this->subject = $this->getMockForAbstractClass($proxyClassName, [], '', true, true, true, ['getRuntimeCache']);
350 $this->subject->expects($this->once())->method('getRuntimeCache')->willReturn($runtimeCacheMock);
351 $this->prepareSectionIndexTest();
352
353 $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock();
354 $pageRepository->expects($this->once())->method('getMenu')->will($this->returnValue($menu));
355 $this->subject->_set('sys_page', $pageRepository);
356 $this->subject->_set('menuArr', [
357 0 => ['uid' => 1]
358 ]);
359 $this->subject->_set('conf', ['excludeUidList' => $excludeUidList]);
360
361 $this->assertEquals($expectedResult, $this->subject->_call('isItemState', 'IFSUB', 0));
362 }
363
364 /**
365 * @return array
366 */
367 public function menuTypoLinkCreatesExpectedTypoLinkConfiurationDataProvider()
368 {
369 return [
370 'standard parameter without access protected setting' => [
371 [
372 'parameter' => 1,
373 'linkAccessRestrictedPages' => false,
374 'useCacheHash' => true
375 ],
376 [
377 'showAccessRestrictedPages' => false
378 ],
379 true,
380 ['uid' => 1],
381 '',
382 0,
383 ''
384 ],
385 'standard parameter with access protected setting' => [
386 [
387 'parameter' => 10,
388 'linkAccessRestrictedPages' => true,
389 'useCacheHash' => true
390 ],
391 [
392 'showAccessRestrictedPages' => true
393 ],
394 true,
395 ['uid' => 10],
396 '',
397 0,
398 ''
399 ],
400 'standard parameter with access protected setting "NONE" casts to boolean linkAccessRestrictedPages (delegates resolving to typoLink method internals)' => [
401 [
402 'parameter' => 10,
403 'linkAccessRestrictedPages' => true,
404 'useCacheHash' => true
405 ],
406 [
407 'showAccessRestrictedPages' => 'NONE'
408 ],
409 true,
410 ['uid' => 10],
411 '',
412 0,
413 ''
414 ],
415 'standard parameter with access protected setting (int)67 casts to boolean linkAccessRestrictedPages (delegates resolving to typoLink method internals)' => [
416 [
417 'parameter' => 10,
418 'linkAccessRestrictedPages' => true,
419 'useCacheHash' => true
420 ],
421 [
422 'showAccessRestrictedPages' => 67
423 ],
424 true,
425 ['uid' => 10],
426 '',
427 0,
428 ''
429 ],
430 'standard parameter with target' => [
431 [
432 'parameter' => 1,
433 'target' => '_blank',
434 'linkAccessRestrictedPages' => false,
435 'useCacheHash' => true
436 ],
437 [
438 'showAccessRestrictedPages' => false
439 ],
440 true,
441 ['uid' => 1],
442 '_blank',
443 0,
444 ''
445 ],
446 'parameter with typeOverride=10' => [
447 [
448 'parameter' => '10,10',
449 'linkAccessRestrictedPages' => false,
450 'useCacheHash' => true
451 ],
452 [
453 'showAccessRestrictedPages' => false
454 ],
455 true,
456 ['uid' => 10],
457 '',
458 '',
459 10
460 ],
461 'parameter with target and typeOverride=10' => [
462 [
463 'parameter' => '10,10',
464 'linkAccessRestrictedPages' => false,
465 'useCacheHash' => true,
466 'target' => '_self'
467 ],
468 [
469 'showAccessRestrictedPages' => false
470 ],
471 true,
472 ['uid' => 10],
473 '_self',
474 '',
475 '10'
476 ],
477 'parameter with invalid value in typeOverride=foobar ignores typeOverride' => [
478 [
479 'parameter' => 20,
480 'linkAccessRestrictedPages' => false,
481 'useCacheHash' => true,
482 'target' => '_self'
483 ],
484 [
485 'showAccessRestrictedPages' => false
486 ],
487 true,
488 ['uid' => 20],
489 '_self',
490 '',
491 'foobar',
492 20
493 ],
494 'standard parameter with section name' => [
495 [
496 'parameter' => 10,
497 'target' => '_blank',
498 'linkAccessRestrictedPages' => false,
499 'section' => 'section-name'
500 ],
501 [
502 'showAccessRestrictedPages' => false
503 ],
504 false,
505 [
506 'uid' => 10,
507 'sectionIndex_uid' => 'section-name'
508 ],
509 '_blank',
510 '',
511 ''
512 ],
513 'standard parameter with additional parameters' => [
514 [
515 'parameter' => 10,
516 'linkAccessRestrictedPages' => false,
517 'section' => 'section-name',
518 'additionalParams' => '&test=foobar'
519 ],
520 [
521 'showAccessRestrictedPages' => false
522 ],
523 false,
524 [
525 'uid' => 10,
526 'sectionIndex_uid' => 'section-name'
527 ],
528 '',
529 '&test=foobar',
530 '',
531 ],
532 'overridden page array uid value gets used as parameter' => [
533 [
534 'parameter' => 99,
535 'linkAccessRestrictedPages' => false,
536 'section' => 'section-name'
537 ],
538 [
539 'showAccessRestrictedPages' => false
540 ],
541 false,
542 [
543 'uid' => 10,
544 'sectionIndex_uid' => 'section-name'
545 ],
546 '',
547 '',
548 '',
549 99
550 ],
551 ];
552 }
553
554 /**
555 * @test
556 * @dataProvider menuTypoLinkCreatesExpectedTypoLinkConfiurationDataProvider
557 * @param array $expected
558 * @param array $mconf
559 * @param bool $useCacheHash
560 * @param array $page
561 * @param mixed $oTarget
562 * @param string $addParams
563 * @param string $typeOverride
564 * @param int $overrideId
565 */
566 public function menuTypoLinkCreatesExpectedTypoLinkConfiguration(array $expected, array $mconf, $useCacheHash, array $page, $oTarget, $addParams = '', $typeOverride = '', int $overrideId = null)
567 {
568 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)
569 ->setMethods(['typoLink'])
570 ->getMock();
571 $cObject->expects($this->once())->method('typoLink')->with('|', $expected);
572 $this->subject->_set('parent_cObj', $cObject);
573 $this->subject->_set('mconf', $mconf);
574 $this->subject->_set('useCacheHash', $useCacheHash);
575 $this->subject->_call('menuTypoLink', $page, $oTarget, $addParams, $typeOverride, $overrideId);
576 }
577 }