[!!!][TASK] Deprecate useCacheHash/noCacheHash
[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 ],
375 [
376 'showAccessRestrictedPages' => false
377 ],
378 ['uid' => 1],
379 '',
380 0,
381 ''
382 ],
383 'standard parameter with access protected setting' => [
384 [
385 'parameter' => 10,
386 'linkAccessRestrictedPages' => true
387 ],
388 [
389 'showAccessRestrictedPages' => true
390 ],
391 ['uid' => 10],
392 '',
393 0,
394 ''
395 ],
396 'standard parameter with access protected setting "NONE" casts to boolean linkAccessRestrictedPages (delegates resolving to typoLink method internals)' => [
397 [
398 'parameter' => 10,
399 'linkAccessRestrictedPages' => true
400 ],
401 [
402 'showAccessRestrictedPages' => 'NONE'
403 ],
404 ['uid' => 10],
405 '',
406 0,
407 ''
408 ],
409 'standard parameter with access protected setting (int)67 casts to boolean linkAccessRestrictedPages (delegates resolving to typoLink method internals)' => [
410 [
411 'parameter' => 10,
412 'linkAccessRestrictedPages' => true
413 ],
414 [
415 'showAccessRestrictedPages' => 67
416 ],
417 ['uid' => 10],
418 '',
419 0,
420 ''
421 ],
422 'standard parameter with target' => [
423 [
424 'parameter' => 1,
425 'target' => '_blank',
426 'linkAccessRestrictedPages' => false
427 ],
428 [
429 'showAccessRestrictedPages' => false
430 ],
431 ['uid' => 1],
432 '_blank',
433 0,
434 ''
435 ],
436 'parameter with typeOverride=10' => [
437 [
438 'parameter' => '10,10',
439 'linkAccessRestrictedPages' => false
440 ],
441 [
442 'showAccessRestrictedPages' => false
443 ],
444 ['uid' => 10],
445 '',
446 '',
447 10
448 ],
449 'parameter with target and typeOverride=10' => [
450 [
451 'parameter' => '10,10',
452 'linkAccessRestrictedPages' => false,
453 'target' => '_self'
454 ],
455 [
456 'showAccessRestrictedPages' => false
457 ],
458 ['uid' => 10],
459 '_self',
460 '',
461 '10'
462 ],
463 'parameter with invalid value in typeOverride=foobar ignores typeOverride' => [
464 [
465 'parameter' => 20,
466 'linkAccessRestrictedPages' => false,
467 'target' => '_self'
468 ],
469 [
470 'showAccessRestrictedPages' => false
471 ],
472 ['uid' => 20],
473 '_self',
474 '',
475 'foobar',
476 20
477 ],
478 'standard parameter with section name' => [
479 [
480 'parameter' => 10,
481 'target' => '_blank',
482 'linkAccessRestrictedPages' => false,
483 'section' => 'section-name'
484 ],
485 [
486 'showAccessRestrictedPages' => false
487 ],
488 [
489 'uid' => 10,
490 'sectionIndex_uid' => 'section-name'
491 ],
492 '_blank',
493 '',
494 ''
495 ],
496 'standard parameter with additional parameters' => [
497 [
498 'parameter' => 10,
499 'linkAccessRestrictedPages' => false,
500 'section' => 'section-name',
501 'additionalParams' => '&test=foobar'
502 ],
503 [
504 'showAccessRestrictedPages' => false
505 ],
506 [
507 'uid' => 10,
508 'sectionIndex_uid' => 'section-name'
509 ],
510 '',
511 '&test=foobar',
512 '',
513 ],
514 'overridden page array uid value gets used as parameter' => [
515 [
516 'parameter' => 99,
517 'linkAccessRestrictedPages' => false,
518 'section' => 'section-name'
519 ],
520 [
521 'showAccessRestrictedPages' => false
522 ],
523 [
524 'uid' => 10,
525 'sectionIndex_uid' => 'section-name'
526 ],
527 '',
528 '',
529 '',
530 99
531 ],
532 ];
533 }
534
535 /**
536 * @test
537 * @dataProvider menuTypoLinkCreatesExpectedTypoLinkConfiurationDataProvider
538 * @param array $expected
539 * @param array $mconf
540 * @param array $page
541 * @param mixed $oTarget
542 * @param string $addParams
543 * @param string $typeOverride
544 * @param int $overrideId
545 */
546 public function menuTypoLinkCreatesExpectedTypoLinkConfiguration(array $expected, array $mconf, array $page, $oTarget, $addParams = '', $typeOverride = '', int $overrideId = null)
547 {
548 $cObject = $this->getMockBuilder(ContentObjectRenderer::class)
549 ->setMethods(['typoLink'])
550 ->getMock();
551 $cObject->expects($this->once())->method('typoLink')->with('|', $expected);
552 $this->subject->_set('parent_cObj', $cObject);
553 $this->subject->_set('mconf', $mconf);
554 $this->subject->_call('menuTypoLink', $page, $oTarget, $addParams, $typeOverride, $overrideId);
555 }
556 }