51a915d19d8a1ee5f6409781232b1f2f119b03da
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / ContentObject / ContentObjectRendererTest.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Tests\Functional\ContentObject;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Database\ConnectionPool;
18 use TYPO3\CMS\Core\TypoScript\TemplateService;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
21 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
22 use TYPO3\CMS\Frontend\Page\PageRepository;
23
24 /**
25 * Testcase for TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
26 */
27 class ContentObjectRendererTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
28 {
29 /**
30 * @var ContentObjectRenderer
31 */
32 protected $subject;
33
34 /**
35 * @var string
36 */
37 protected $quoteChar;
38
39 protected function setUp()
40 {
41 parent::setUp();
42
43 $typoScriptFrontendController = GeneralUtility::makeInstance(
44 TypoScriptFrontendController::class,
45 null,
46 1,
47 0
48 );
49 $typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class);
50 $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
51 $GLOBALS['TSFE'] = $typoScriptFrontendController;
52
53 $this->subject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
54 $this->quoteChar = GeneralUtility::makeInstance(ConnectionPool::class)
55 ->getConnectionForTable('tt_content')
56 ->getDatabasePlatform()
57 ->getIdentifierQuoteCharacter();
58 }
59
60 /**
61 * Data provider for the getQuery test
62 *
63 * @return array multi-dimensional array with the second level like this:
64 * @see getQuery
65 */
66 public function getQueryDataProvider(): array
67 {
68 $data = [
69 'testing empty conf' => [
70 'tt_content',
71 [],
72 [
73 'SELECT' => '*'
74 ]
75 ],
76 'testing #17284: adding uid/pid for workspaces' => [
77 'tt_content',
78 [
79 'selectFields' => 'header,bodytext'
80 ],
81 [
82 'SELECT' => 'header,bodytext, `tt_content`.`uid` AS `uid`, `tt_content`.`pid` AS `pid`, `tt_content`.`t3ver_state` AS `t3ver_state`'
83 ]
84 ],
85 'testing #17284: no need to add' => [
86 'tt_content',
87 [
88 'selectFields' => 'tt_content.*'
89 ],
90 [
91 'SELECT' => 'tt_content.*'
92 ]
93 ],
94 'testing #17284: no need to add #2' => [
95 'tt_content',
96 [
97 'selectFields' => '*'
98 ],
99 [
100 'SELECT' => '*'
101 ]
102 ],
103 'testing #29783: joined tables, prefix tablename' => [
104 'tt_content',
105 [
106 'selectFields' => 'tt_content.header,be_users.username',
107 'join' => 'be_users ON tt_content.cruser_id = be_users.uid'
108 ],
109 [
110 'SELECT' => 'tt_content.header,be_users.username, `tt_content`.`uid` AS `uid`, `tt_content`.`pid` AS `pid`, `tt_content`.`t3ver_state` AS `t3ver_state`'
111 ]
112 ],
113 'testing #34152: single count(*), add nothing' => [
114 'tt_content',
115 [
116 'selectFields' => 'count(*)'
117 ],
118 [
119 'SELECT' => 'count(*)'
120 ]
121 ],
122 'testing #34152: single max(crdate), add nothing' => [
123 'tt_content',
124 [
125 'selectFields' => 'max(crdate)'
126 ],
127 [
128 'SELECT' => 'max(crdate)'
129 ]
130 ],
131 'testing #34152: single min(crdate), add nothing' => [
132 'tt_content',
133 [
134 'selectFields' => 'min(crdate)'
135 ],
136 [
137 'SELECT' => 'min(crdate)'
138 ]
139 ],
140 'testing #34152: single sum(is_siteroot), add nothing' => [
141 'tt_content',
142 [
143 'selectFields' => 'sum(is_siteroot)'
144 ],
145 [
146 'SELECT' => 'sum(is_siteroot)'
147 ]
148 ],
149 'testing #34152: single avg(crdate), add nothing' => [
150 'tt_content',
151 [
152 'selectFields' => 'avg(crdate)'
153 ],
154 [
155 'SELECT' => 'avg(crdate)'
156 ]
157 ]
158 ];
159
160 return $data;
161 }
162
163 /**
164 * Check if sanitizeSelectPart works as expected
165 *
166 * @dataProvider getQueryDataProvider
167 * @test
168 * @param string $table
169 * @param array $conf
170 * @param array $expected
171 */
172 public function getQuery(string $table, array $conf, array $expected)
173 {
174 $GLOBALS['TCA'] = [
175 'pages' => [
176 'ctrl' => [
177 'enablecolumns' => [
178 'disabled' => 'hidden'
179 ]
180 ]
181 ],
182 'tt_content' => [
183 'ctrl' => [
184 'enablecolumns' => [
185 'disabled' => 'hidden'
186 ],
187 'versioningWS' => true
188 ]
189 ],
190 ];
191
192 $result = $this->subject->getQuery($table, $conf, true);
193 foreach ($expected as $field => $value) {
194 // Replace the MySQL backtick quote character with the actual quote character for the DBMS
195 if ($field === 'SELECT') {
196 $value = str_replace('`', $this->quoteChar, $value);
197 }
198 $this->assertEquals($value, $result[$field]);
199 }
200 }
201
202 /**
203 * @test
204 */
205 public function getQueryCallsGetTreeListWithNegativeValuesIfRecursiveIsSet()
206 {
207 $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']);
208 $this->subject->start([], 'tt_content');
209
210 $conf = [
211 'recursive' => '15',
212 'pidInList' => '16, -35'
213 ];
214
215 $this->subject->expects($this->at(0))
216 ->method('getTreeList')
217 ->with(-16, 15)
218 ->will($this->returnValue('15,16'));
219 $this->subject->expects($this->at(1))
220 ->method('getTreeList')
221 ->with(-35, 15)
222 ->will($this->returnValue('15,35'));
223
224 $this->subject->getQuery('tt_content', $conf, true);
225 }
226
227 /**
228 * @test
229 */
230 public function getQueryCallsGetTreeListWithCurrentPageIfThisIsSet()
231 {
232 $GLOBALS['TSFE']->id = 27;
233
234 $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']);
235 $this->subject->start([], 'tt_content');
236
237 $conf = [
238 'pidInList' => 'this',
239 'recursive' => '4'
240 ];
241
242 $this->subject->expects($this->once())
243 ->method('getTreeList')
244 ->with(-27)
245 ->will($this->returnValue('27'));
246
247 $this->subject->getQuery('tt_content', $conf, true);
248 }
249
250 /**
251 * @return array
252 */
253 public function getWhereReturnCorrectQueryDataProvider()
254 {
255 return [
256 [
257 [
258 'tt_content' => [
259 'ctrl' => [
260 ],
261 'columns' => [
262 ]
263 ],
264 ],
265 'tt_content',
266 [
267 'uidInList' => '42',
268 'pidInList' => 43,
269 'where' => 'tt_content.cruser_id=5',
270 'groupBy' => 'tt_content.title',
271 'orderBy' => 'tt_content.sorting',
272 ],
273 'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5) GROUP BY `tt_content`.`title` ORDER BY `tt_content`.`sorting`',
274 ],
275 [
276 [
277 'tt_content' => [
278 'ctrl' => [
279 'delete' => 'deleted',
280 'enablecolumns' => [
281 'disabled' => 'hidden',
282 'starttime' => 'startdate',
283 'endtime' => 'enddate',
284 ],
285 'languageField' => 'sys_language_uid',
286 'transOrigPointerField' => 'l18n_parent',
287 ],
288 'columns' => [
289 ]
290 ],
291 ],
292 'tt_content',
293 [
294 'uidInList' => 42,
295 'pidInList' => 43,
296 'where' => 'tt_content.cruser_id=5',
297 'groupBy' => 'tt_content.title',
298 'orderBy' => 'tt_content.sorting',
299 ],
300 'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5) AND (`tt_content`.`sys_language_uid` = 13) AND ((`tt_content`.`deleted` = 0) AND (`tt_content`.`hidden` = 0) AND (`tt_content`.`startdate` <= 4242) AND ((`tt_content`.`enddate` = 0) OR (`tt_content`.`enddate` > 4242))) GROUP BY `tt_content`.`title` ORDER BY `tt_content`.`sorting`',
301 ],
302 [
303 [
304 'tt_content' => [
305 'ctrl' => [
306 'languageField' => 'sys_language_uid',
307 'transOrigPointerField' => 'l18n_parent',
308 ],
309 'columns' => [
310 ]
311 ],
312 ],
313 'tt_content',
314 [
315 'uidInList' => 42,
316 'pidInList' => 43,
317 'where' => 'tt_content.cruser_id=5',
318 'languageField' => 0,
319 ],
320 'WHERE (`tt_content`.`uid` IN (42)) AND (`tt_content`.`pid` IN (43)) AND (tt_content.cruser_id=5)',
321 ],
322 ];
323 }
324
325 /**
326 * @return array
327 */
328 public function typolinkReturnsCorrectLinksForPagesDataProvider()
329 {
330 return [
331 'Link to page' => [
332 'My page',
333 [
334 'parameter' => 42,
335 ],
336 [
337 'uid' => 42,
338 'title' => 'Page title',
339 ],
340 '<a href="index.php?id=42">My page</a>',
341 ],
342 'Link to page without link text' => [
343 '',
344 [
345 'parameter' => 42,
346 ],
347 [
348 'uid' => 42,
349 'title' => 'Page title',
350 ],
351 '<a href="index.php?id=42">Page title</a>',
352 ],
353 'Link to page with attributes' => [
354 'My page',
355 [
356 'parameter' => '42',
357 'ATagParams' => 'class="page-class"',
358 'target' => '_self',
359 'title' => 'Link to internal page',
360 ],
361 [
362 'uid' => 42,
363 'title' => 'Page title',
364 ],
365 '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>',
366 ],
367 'Link to page with attributes in parameter' => [
368 'My page',
369 [
370 'parameter' => '42 _self page-class "Link to internal page"',
371 ],
372 [
373 'uid' => 42,
374 'title' => 'Page title',
375 ],
376 '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>',
377 ],
378 'Link to page with bold tag in title' => [
379 '',
380 [
381 'parameter' => 42,
382 ],
383 [
384 'uid' => 42,
385 'title' => 'Page <b>title</b>',
386 ],
387 '<a href="index.php?id=42">Page <b>title</b></a>',
388 ],
389 'Link to page with script tag in title' => [
390 '',
391 [
392 'parameter' => 42,
393 ],
394 [
395 'uid' => 42,
396 'title' => '<script>alert(123)</script>Page title',
397 ],
398 '<a href="index.php?id=42">&lt;script&gt;alert(123)&lt;/script&gt;Page title</a>',
399 ],
400 ];
401 }
402
403 /**
404 * @test
405 * @param string $linkText
406 * @param array $configuration
407 * @param array $pageArray
408 * @param string $expectedResult
409 * @dataProvider typolinkReturnsCorrectLinksForPagesDataProvider
410 */
411 public function typolinkReturnsCorrectLinksForPages($linkText, $configuration, $pageArray, $expectedResult)
412 {
413 $pageRepositoryMockObject = $this->getMockBuilder(PageRepository::class)
414 ->setMethods(['getPage'])
415 ->getMock();
416 $pageRepositoryMockObject->expects($this->any())->method('getPage')->willReturn($pageArray);
417
418 $typoScriptFrontendController = GeneralUtility::makeInstance(
419 TypoScriptFrontendController::class,
420 null,
421 1,
422 0
423 );
424 $typoScriptFrontendController->config = [
425 'config' => [],
426 ];
427 $typoScriptFrontendController->sys_page = $pageRepositoryMockObject;
428 $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class);
429 $typoScriptFrontendController->tmpl->setup = [
430 'lib.' => [
431 'parseFunc.' => $this->getLibParseFunc(),
432 ],
433 ];
434 $GLOBALS['TSFE'] = $typoScriptFrontendController;
435
436 $subject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
437 $this->assertEquals($expectedResult, $subject->typoLink($linkText, $configuration));
438 }
439
440 /**
441 * @test
442 */
443 public function typolinkReturnsCorrectLinkForSectionToHomePageWithUrlRewriting()
444 {
445 $pageRepositoryMockObject = $this->getMockBuilder(PageRepository::class)
446 ->setMethods(['getPage'])
447 ->getMock();
448 $pageRepositoryMockObject->expects($this->any())->method('getPage')->willReturn([
449 'uid' => 1,
450 'title' => 'Page title',
451 ]);
452
453 $templateServiceMockObject = $this->getMockBuilder(TemplateService::class)
454 ->setMethods(['linkData'])
455 ->getMock();
456 $templateServiceMockObject->setup = [
457 'lib.' => [
458 'parseFunc.' => $this->getLibParseFunc(),
459 ],
460 ];
461 $templateServiceMockObject->expects($this->once())->method('linkData')->willReturn([
462 'url' => '/index.php?id=1',
463 'target' => '',
464 'type' => '',
465 'orig_type' => '',
466 'no_cache' => '',
467 'linkVars' => '',
468 'sectionIndex' => '',
469 'totalURL' => '/',
470 ]);
471
472 $typoScriptFrontendController = GeneralUtility::makeInstance(
473 TypoScriptFrontendController::class,
474 null,
475 1,
476 0
477 );
478 $typoScriptFrontendController->config = [
479 'config' => [],
480 ];
481 $typoScriptFrontendController->sys_page = $pageRepositoryMockObject;
482 $typoScriptFrontendController->tmpl = $templateServiceMockObject;
483 $GLOBALS['TSFE'] = $typoScriptFrontendController;
484
485 $configuration = [
486 'parameter' => 1,
487 'section' => 'content',
488 ];
489
490 $subject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
491 $this->assertEquals('<a href="#content">Page title</a>', $subject->typoLink('', $configuration));
492 }
493
494 /**
495 * @return array
496 */
497 protected function getLibParseFunc()
498 {
499 return [
500 'makelinks' => '1',
501 'makelinks.' => [
502 'http.' => [
503 'keep' => '{$styles.content.links.keep}',
504 'extTarget' => '',
505 'mailto.' => [
506 'keep' => 'path',
507 ],
508 ],
509 ],
510 'tags' => [
511 'link' => 'TEXT',
512 'link.' => [
513 'current' => '1',
514 'typolink.' => [
515 'parameter.' => [
516 'data' => 'parameters : allParams',
517 ],
518 ],
519 'parseFunc.' => [
520 'constants' => '1',
521 ],
522 ],
523 ],
524
525 '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',
526 'denyTags' => '*',
527 'sword' => '<span class="csc-sword">|</span>',
528 'constants' => '1',
529 'nonTypoTagStdWrap.' => [
530 'HTMLparser' => '1',
531 'HTMLparser.' => [
532 'keepNonMatchedTags' => '1',
533 'htmlSpecialChars' => '2',
534 ],
535 ],
536 ];
537 }
538 }