4deb48c942dba1080f6deb3231873a0760e3d4a8
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / NewRecordController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Routing\UriBuilder;
21 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
22 use TYPO3\CMS\Backend\Template\ModuleTemplate;
23 use TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView;
24 use TYPO3\CMS\Backend\Tree\View\PagePositionMap;
25 use TYPO3\CMS\Backend\Utility\BackendUtility;
26 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
27 use TYPO3\CMS\Core\Database\ConnectionPool;
28 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
29 use TYPO3\CMS\Core\Http\HtmlResponse;
30 use TYPO3\CMS\Core\Http\RedirectResponse;
31 use TYPO3\CMS\Core\Imaging\Icon;
32 use TYPO3\CMS\Core\Localization\LanguageService;
33 use TYPO3\CMS\Core\Type\Bitmask\Permission;
34 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
35 use TYPO3\CMS\Core\Utility\GeneralUtility;
36 use TYPO3\CMS\Core\Utility\PathUtility;
37 use TYPO3\CMS\Frontend\Page\PageRepository;
38
39 /**
40 * Script class for 'db_new'
41 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
42 */
43 class NewRecordController
44 {
45 /**
46 * @var array
47 */
48 protected $pageinfo = [];
49
50 /**
51 * @var array
52 */
53 protected $pidInfo = [];
54
55 /**
56 * @var array
57 */
58 protected $newRecordSortList;
59
60 /**
61 * @var int
62 */
63 protected $newPagesInto;
64
65 /**
66 * @var int
67 */
68 protected $newContentInto;
69
70 /**
71 * @var int
72 */
73 protected $newPagesAfter;
74
75 /**
76 * Determines, whether "Select Position" for new page should be shown
77 *
78 * @var bool
79 */
80 protected $newPagesSelectPosition = true;
81
82 /**
83 * @var array
84 */
85 protected $web_list_modTSconfig;
86
87 /**
88 * @var array
89 */
90 protected $allowedNewTables;
91
92 /**
93 * @var array
94 */
95 protected $deniedNewTables;
96
97 /**
98 * @var array
99 */
100 protected $web_list_modTSconfig_pid;
101
102 /**
103 * @var array
104 */
105 protected $allowedNewTables_pid;
106
107 /**
108 * @var array
109 */
110 protected $deniedNewTables_pid;
111
112 /**
113 * @var string
114 */
115 protected $code;
116
117 /**
118 * @var string
119 */
120 protected $R_URI;
121
122 /**
123 * @var int
124 *
125 * @see \TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView::expandNext()
126 * @internal
127 */
128 public $id;
129
130 /**
131 * @var string
132 */
133 protected $returnUrl;
134
135 /**
136 * pagesOnly flag.
137 *
138 * @var int
139 */
140 protected $pagesOnly;
141
142 /**
143 * @var string
144 */
145 protected $perms_clause;
146
147 /**
148 * Accumulated HTML output
149 *
150 * @var string
151 */
152 protected $content;
153
154 /**
155 * @var array
156 */
157 protected $tRows;
158
159 /**
160 * ModuleTemplate object
161 *
162 * @var ModuleTemplate
163 */
164 protected $moduleTemplate;
165
166 /**
167 * Constructor
168 */
169 public function __construct()
170 {
171 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
172 $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
173
174 // @see \TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView::expandNext()
175 $GLOBALS['SOBE'] = $this;
176 }
177
178 /**
179 * Injects the request object for the current request or subrequest
180 * As this controller goes only through the main() method, it is rather simple for now
181 *
182 * @param ServerRequestInterface $request the current request
183 * @return ResponseInterface the response with the content
184 */
185 public function mainAction(ServerRequestInterface $request): ResponseInterface
186 {
187 $this->init($request);
188 $response = $this->renderContent($request);
189
190 if (empty($response)) {
191 $response = new HtmlResponse($this->moduleTemplate->renderContent());
192 }
193
194 return $response;
195 }
196
197 /**
198 * Constructor function for the class
199 *
200 * @param ServerRequestInterface $request
201 */
202 protected function init(ServerRequestInterface $request): void
203 {
204 $beUser = $this->getBackendUserAuthentication();
205 // Page-selection permission clause (reading)
206 $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
207 // This will hide records from display - it has nothing to do with user rights!!
208 $pidList = $beUser->getTSConfig()['options.']['hideRecords.']['pages'] ?? '';
209 if (!empty($pidList)) {
210 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
211 ->getQueryBuilderForTable('pages');
212 $this->perms_clause .= ' AND ' . $queryBuilder->expr()->notIn(
213 'pages.uid',
214 GeneralUtility::intExplode(',', $pidList)
215 );
216 }
217 // Setting GPvars:
218 $parsedBody = $request->getParsedBody();
219 $queryParams = $request->getQueryParams();
220 // The page id to operate from
221 $this->id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
222 $this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
223 $this->pagesOnly = $parsedBody['pagesOnly'] ?? $queryParams['pagesOnly'] ?? null;
224 // Setting up the context sensitive menu:
225 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
226 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
227 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/PageActions');
228 // Creating content
229 $this->content = '';
230 $this->content .= '<h1>'
231 . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.pagetitle')
232 . '</h1>';
233 // Id a positive id is supplied, ask for the page record with permission information contained:
234 if ($this->id > 0) {
235 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
236 }
237 // If a page-record was returned, the user had read-access to the page.
238 if ($this->pageinfo['uid']) {
239 // Get record of parent page
240 $this->pidInfo = BackendUtility::getRecord('pages', $this->pageinfo['pid']) ?: [];
241 // Checking the permissions for the user with regard to the parent page: Can he create new pages, new
242 // content record, new page after?
243 if ($beUser->doesUserHaveAccess($this->pageinfo, 8)) {
244 $this->newPagesInto = 1;
245 }
246 if ($beUser->doesUserHaveAccess($this->pageinfo, 16)) {
247 $this->newContentInto = 1;
248 }
249 if (($beUser->isAdmin() || !empty($this->pidInfo)) && $beUser->doesUserHaveAccess($this->pidInfo, 8)) {
250 $this->newPagesAfter = 1;
251 }
252 } elseif ($beUser->isAdmin()) {
253 // Admins can do it all
254 $this->newPagesInto = 1;
255 $this->newContentInto = 1;
256 $this->newPagesAfter = 0;
257 } else {
258 // People with no permission can do nothing
259 $this->newPagesInto = 0;
260 $this->newContentInto = 0;
261 $this->newPagesAfter = 0;
262 }
263 }
264
265 /**
266 * Main processing, creating the list of new record tables to select from
267 *
268 * @param ServerRequestInterface $request
269 * @return ResponseInterface|null
270 */
271 protected function renderContent(ServerRequestInterface $request): ?ResponseInterface
272 {
273 // If there was a page - or if the user is admin (admins has access to the root) we proceed:
274 if (!empty($this->pageinfo['uid']) || $this->getBackendUserAuthentication()->isAdmin()) {
275 if (empty($this->pageinfo)) {
276 // Explicitly pass an empty array to the docHeader
277 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation([]);
278 } else {
279 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
280 }
281 // Acquiring TSconfig for this module/current page:
282 $this->web_list_modTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid'])['mod.']['web_list.'] ?? [];
283 $this->allowedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['allowedNewTables'] ?? '', true);
284 $this->deniedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['deniedNewTables'] ?? '', true);
285 // Acquiring TSconfig for this module/parent page:
286 $this->web_list_modTSconfig_pid = BackendUtility::getPagesTSconfig($this->pageinfo['pid'])['mod.']['web_list.'] ?? [];
287 $this->allowedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['allowedNewTables'] ?? '', true);
288 $this->deniedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['deniedNewTables'] ?? '', true);
289 // More init:
290 if (!$this->isRecordCreationAllowedForTable('pages')) {
291 $this->newPagesInto = 0;
292 }
293 if (!$this->isRecordCreationAllowedForTable('pages', $this->allowedNewTables_pid, $this->deniedNewTables_pid)) {
294 $this->newPagesAfter = 0;
295 }
296 // Set header-HTML and return_url
297 if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
298 $title = strip_tags($this->pageinfo[$GLOBALS['TCA']['pages']['ctrl']['label']]);
299 } else {
300 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
301 }
302 $this->moduleTemplate->setTitle($title);
303 // GENERATE the HTML-output depending on mode (pagesOnly is the page wizard)
304 // Regular new element:
305 if (!$this->pagesOnly) {
306 $this->renderNewRecordControls($request);
307 } elseif ($this->isRecordCreationAllowedForTable('pages')) {
308 // Pages only wizard
309 $response = $this->renderPositionTree();
310
311 if (!empty($response)) {
312 return $response;
313 }
314 }
315 // Add all the content to an output section
316 $this->content .= '<div>' . $this->code . '</div>';
317 // Setting up the buttons and markers for docheader
318 $this->getButtons();
319 // Build the <body> for the module
320 $this->moduleTemplate->setContent($this->content);
321 }
322
323 return null;
324 }
325
326 /**
327 * Create the panel of buttons for submitting the form or otherwise perform operations.
328 */
329 protected function getButtons(): void
330 {
331 $lang = $this->getLanguageService();
332 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
333 // Regular new element:
334 if (!$this->pagesOnly) {
335 // New page
336 if ($this->isRecordCreationAllowedForTable('pages')) {
337 $newPageButton = $buttonBar->makeLinkButton()
338 ->setHref(GeneralUtility::linkThisScript(['pagesOnly' => '1']))
339 ->setTitle($lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:newPage'))
340 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
341 $buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 20);
342 }
343 // CSH
344 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_regular');
345 $buttonBar->addButton($cshButton);
346 } elseif ($this->isRecordCreationAllowedForTable('pages')) {
347 // Pages only wizard
348 // CSH
349 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'new_pages');
350 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('new_pages');
351 $buttonBar->addButton($cshButton);
352 }
353 // Back
354 if ($this->returnUrl) {
355 $returnButton = $buttonBar->makeLinkButton()
356 ->setHref($this->returnUrl)
357 ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
358 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
359 $buttonBar->addButton($returnButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
360 }
361
362 if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
363 // View
364 $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
365 if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
366 $excludeDokTypes = GeneralUtility::intExplode(
367 ',',
368 $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
369 true
370 );
371 } else {
372 // exclude sysfolders and recycler by default
373 $excludeDokTypes = [
374 PageRepository::DOKTYPE_RECYCLER,
375 PageRepository::DOKTYPE_SYSFOLDER,
376 PageRepository::DOKTYPE_SPACER
377 ];
378 }
379 if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)) {
380 $viewButton = $buttonBar->makeLinkButton()
381 ->setHref('#')
382 ->setOnClick(BackendUtility::viewOnClick(
383 $this->pageinfo['uid'],
384 '',
385 BackendUtility::BEgetRootLine($this->pageinfo['uid'])
386 ))
387 ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
388 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
389 'actions-view-page',
390 Icon::SIZE_SMALL
391 ));
392 $buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 30);
393 }
394 }
395 }
396
397 /**
398 * Renders the position map for pages wizard
399 *
400 * @return ResponseInterface|null
401 */
402 protected function renderPositionTree(): ?ResponseInterface
403 {
404 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
405 ->getQueryBuilderForTable('pages');
406 $queryBuilder->getRestrictions()
407 ->removeAll()
408 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
409 $numberOfPages = $queryBuilder
410 ->count('*')
411 ->from('pages')
412 ->execute()
413 ->fetchColumn(0);
414
415 if ($numberOfPages > 0) {
416 $this->code .= '<h3>' . htmlspecialchars($this->getLanguageService()->getLL('selectPosition')) . ':</h3>';
417 /** @var \TYPO3\CMS\Backend\Tree\View\PagePositionMap $positionMap */
418 $positionMap = GeneralUtility::makeInstance(PagePositionMap::class, NewRecordPageTreeView::class);
419 $this->code .= $positionMap->positionTree(
420 $this->id,
421 $this->pageinfo,
422 $this->perms_clause,
423 $this->returnUrl
424 );
425 } else {
426 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
427 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
428 // No pages yet, no need to prompt for position, redirect to page creation.
429 $urlParameters = [
430 'edit' => [
431 'pages' => [
432 0 => 'new'
433 ]
434 ],
435 'returnNewPageId' => 1,
436 'returnUrl' => (string)$uriBuilder->buildUriFromRoute('db_new', ['id' => $this->id, 'pagesOnly' => '1'])
437 ];
438 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
439 @ob_end_clean();
440
441 return new RedirectResponse($url);
442 }
443
444 return null;
445 }
446
447 /**
448 * Render controls for creating a regular new element (pages or records)
449 *
450 * @param ServerRequestInterface $request
451 */
452 protected function renderNewRecordControls(ServerRequestInterface $request): void
453 {
454 $lang = $this->getLanguageService();
455 // Initialize array for accumulating table rows:
456 $this->tRows = [];
457 // Get TSconfig for current page
458 $pageTS = BackendUtility::getPagesTSconfig($this->id);
459 // Finish initializing new pages options with TSconfig
460 // Each new page option may be hidden by TSconfig
461 // Enabled option for the position of a new page
462 $this->newPagesSelectPosition = !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageSelectPosition']);
463 // Pseudo-boolean (0/1) for backward compatibility
464 $displayNewPagesIntoLink = $this->newPagesInto && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageInside']);
465 $displayNewPagesAfterLink = $this->newPagesAfter && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageAfter']);
466 // Slight spacer from header:
467 $this->code .= '';
468 // New Page
469 $table = 'pages';
470 $v = $GLOBALS['TCA'][$table];
471 $pageIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord(
472 $table,
473 [],
474 Icon::SIZE_SMALL
475 )->render();
476 $newPageIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL)->render();
477 $rowContent = '';
478 // New pages INSIDE this pages
479 $newPageLinks = [];
480 if ($displayNewPagesIntoLink
481 && $this->isTableAllowedOnPage('pages', $this->pageinfo)
482 && $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
483 && $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ?: $this->id), 'pages')
484 ) {
485 // Create link to new page inside:
486 $recordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
487 $newPageLinks[] = $this->renderLink(
488 $recordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.inside')) . ')',
489 $table,
490 $this->id
491 );
492 }
493 // New pages AFTER this pages
494 if ($displayNewPagesAfterLink
495 && $this->isTableAllowedOnPage('pages', $this->pidInfo)
496 && $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
497 && $this->getBackendUserAuthentication()->workspaceCreateNewRecord($this->pidInfo['uid'], 'pages')
498 ) {
499 $newPageLinks[] = $this->renderLink(
500 $pageIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:db_new.php.after')) . ')',
501 'pages',
502 -$this->id
503 );
504 }
505 // New pages at selection position
506 if ($this->newPagesSelectPosition && $this->isRecordCreationAllowedForTable('pages')) {
507 // Link to page-wizard:
508 $newPageLinks[] = '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['pagesOnly' => 1])) . '">' . $pageIcon . htmlspecialchars($lang->getLL('pageSelectPosition')) . '</a>';
509 }
510 // Assemble all new page links
511 $numPageLinks = count($newPageLinks);
512 for ($i = 0; $i < $numPageLinks; $i++) {
513 $rowContent .= '<li>' . $newPageLinks[$i] . '</li>';
514 }
515 if ($this->isRecordCreationAllowedForTable('pages')) {
516 $rowContent = '<ul class="list-tree"><li>' . $newPageIcon . '<strong>' .
517 $lang->getLL('createNewPage') . '</strong><ul>' . $rowContent . '</ul></li>';
518 } else {
519 $rowContent = '<ul class="list-tree"><li><ul>' . $rowContent . '</li></ul>';
520 }
521 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
522 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
523 // Compile table row
524 $startRows = [$rowContent];
525 $iconFile = [];
526 // New tables (but not pages) INSIDE this pages
527 $isAdmin = $this->getBackendUserAuthentication()->isAdmin();
528 $newContentIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-document-new', Icon::SIZE_SMALL)->render();
529 if ($this->newContentInto) {
530 if (is_array($GLOBALS['TCA'])) {
531 $groupName = '';
532 foreach ($GLOBALS['TCA'] as $table => $v) {
533 $rootLevelConfiguration = isset($v['ctrl']['rootLevel']) ? (int)$v['ctrl']['rootLevel'] : 0;
534 if ($table !== 'pages'
535 && $this->isRecordCreationAllowedForTable($table)
536 && $this->isTableAllowedOnPage($table, $this->pageinfo)
537 && $this->getBackendUserAuthentication()->check('tables_modify', $table)
538 && ($rootLevelConfiguration === -1 || ($this->id xor $rootLevelConfiguration))
539 && $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ? $this->pageinfo['_ORIG_uid'] : $this->id), $table)
540 ) {
541 $newRecordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
542 $rowContent = '';
543 $thisTitle = '';
544 // Create new link for record:
545 $newLink = $this->renderLink(
546 $newRecordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])),
547 $table,
548 $this->id
549 );
550 // If the table is 'tt_content', add link to wizard
551 if ($table === 'tt_content') {
552 $groupName = $lang->getLL('createNewContent');
553 $rowContent = $newContentIcon
554 . '<strong>' . $lang->getLL('createNewContent') . '</strong>'
555 . '<ul>';
556 // If mod.newContentElementWizard.override is set, use that extension's wizard instead:
557 $moduleName = BackendUtility::getPagesTSconfig($this->id)['mod.']['newContentElementWizard.']['override']
558 ?? 'new_content_element_wizard';
559 /** @var \TYPO3\CMS\Core\Http\NormalizedParams */
560 $normalizedParams = $request->getAttribute('normalizedParams');
561 $url = (string)$uriBuilder->buildUriFromRoute($moduleName, ['id' => $this->id, 'returnUrl' => $normalizedParams->getRequestUri()]);
562 $rowContent .= '<li>' . $newLink . ' ' . BackendUtility::wrapInHelp($table, '') . '</li>'
563 . '<li>'
564 . '<a href="' . htmlspecialchars($url) . '" data-title="' . htmlspecialchars($this->getLanguageService()->getLL('newContentElement')) . '" class="t3js-toggle-new-content-element-wizard">'
565 . $newContentIcon . htmlspecialchars($lang->getLL('clickForWizard'))
566 . '</a>'
567 . '</li>'
568 . '</ul>';
569 } else {
570 // Get the title
571 if ($v['ctrl']['readOnly'] || $v['ctrl']['hideTable'] || $v['ctrl']['is_static']) {
572 continue;
573 }
574 if ($v['ctrl']['adminOnly'] && !$isAdmin) {
575 continue;
576 }
577 $nameParts = explode('_', $table);
578 $thisTitle = '';
579 $_EXTKEY = '';
580 if ($nameParts[0] === 'tx' || $nameParts[0] === 'tt') {
581 // Try to extract extension name
582 if (strpos($v['ctrl']['title'], 'LLL:EXT:') === 0) {
583 $_EXTKEY = substr($v['ctrl']['title'], 8);
584 $_EXTKEY = substr($_EXTKEY, 0, strpos($_EXTKEY, '/'));
585 if ($_EXTKEY !== '') {
586 // First try to get localisation of extension title
587 $temp = explode(':', substr($v['ctrl']['title'], 9 + strlen($_EXTKEY)));
588 $langFile = $temp[0];
589 $thisTitle = $lang->sL('LLL:EXT:' . $_EXTKEY . '/' . $langFile . ':extension.title');
590 // If no localisation available, read title from ext_emconf.php
591 $extPath = ExtensionManagementUtility::extPath($_EXTKEY);
592 $extEmConfFile = $extPath . 'ext_emconf.php';
593 if (!$thisTitle && is_file($extEmConfFile)) {
594 $EM_CONF = [];
595 include $extEmConfFile;
596 $thisTitle = $EM_CONF[$_EXTKEY]['title'];
597 }
598 $iconFile[$_EXTKEY] = '<img src="' . PathUtility::getAbsoluteWebPath(ExtensionManagementUtility::getExtensionIcon($extPath, true)) . '" width="16" height="16" alt="' . $thisTitle . '" />';
599 }
600 }
601 if (empty($thisTitle)) {
602 $_EXTKEY = $nameParts[1];
603 $thisTitle = $nameParts[1];
604 $iconFile[$_EXTKEY] = '';
605 }
606 } else {
607 $_EXTKEY = 'system';
608 $thisTitle = $lang->getLL('system_records');
609 $iconFile['system'] = $this->moduleTemplate->getIconFactory()->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render();
610 }
611
612 if ($groupName === '' || $groupName !== $_EXTKEY) {
613 $groupName = empty($v['ctrl']['groupName']) ? $_EXTKEY : $v['ctrl']['groupName'];
614 }
615 $rowContent .= $newLink;
616 }
617 // Compile table row:
618 if ($table === 'tt_content') {
619 $startRows[] = '<li>' . $rowContent . '</li>';
620 } else {
621 $this->tRows[$groupName]['title'] = $thisTitle;
622 $this->tRows[$groupName]['html'][] = $rowContent;
623 $this->tRows[$groupName]['table'][] = $table;
624 }
625 }
626 }
627 }
628 }
629 // User sort
630 if (isset($pageTS['mod.']['wizards.']['newRecord.']['order'])) {
631 $this->newRecordSortList = GeneralUtility::trimExplode(',', $pageTS['mod.']['wizards.']['newRecord.']['order'], true);
632 }
633 uksort($this->tRows, [$this, 'sortTableRows']);
634 // Compile table row:
635 $finalRows = [];
636 $finalRows[] = implode('', $startRows);
637 foreach ($this->tRows as $key => $value) {
638 $row = '<li>' . $iconFile[$key] . ' <strong>' . $value['title'] . '</strong><ul>';
639 foreach ($value['html'] as $recordKey => $record) {
640 $row .= '<li>' . $record . ' ' . BackendUtility::wrapInHelp($value['table'][$recordKey], '') . '</li>';
641 }
642 $row .= '</ul></li>';
643 $finalRows[] = $row;
644 }
645
646 $finalRows[] = '</ul>';
647 // Make table:
648 $this->code .= implode('', $finalRows);
649 }
650
651 /**
652 * User array sort function used by renderNewRecordControls
653 *
654 * @param string $a First array element for compare
655 * @param string $b First array element for compare
656 * @return int -1 for lower, 0 for equal, 1 for greater
657 */
658 protected function sortTableRows(string $a, string $b): int
659 {
660 if (!empty($this->newRecordSortList)) {
661 if (in_array($a, $this->newRecordSortList) && in_array($b, $this->newRecordSortList)) {
662 // Both are in the list, return relative to position in array
663 $sub = array_search($a, $this->newRecordSortList) - array_search($b, $this->newRecordSortList);
664 $ret = ($sub < 0 ? -1 : $sub == 0) ? 0 : 1;
665 } elseif (in_array($a, $this->newRecordSortList)) {
666 // First element is in array, put to top
667 $ret = -1;
668 } elseif (in_array($b, $this->newRecordSortList)) {
669 // Second element is in array, put first to bottom
670 $ret = 1;
671 } else {
672 // No element is in array, return alphabetic order
673 $ret = strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
674 }
675 return $ret;
676 }
677 // Return alphabetic order
678 return strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
679 }
680
681 /**
682 * Links the string $code to a create-new form for a record in $table created on page $pid
683 *
684 * @param string $linkText Link text
685 * @param string $table Table name (in which to create new record)
686 * @param int $pid PID value for the "&edit['.$table.']['.$pid.']=new" command (positive/negative)
687 * @param bool $addContentTable If $addContentTable is set, then a new tt_content record is created together with pages
688 * @return string The link.
689 */
690 protected function renderLink(string $linkText, string $table, int $pid, bool $addContentTable = false): string
691 {
692 $urlParameters = [
693 'edit' => [
694 $table => [
695 $pid => 'new'
696 ]
697 ],
698 'returnUrl' => $this->returnUrl
699 ];
700
701 if ($table === 'pages' && $addContentTable) {
702 $urlParameters['tt_content']['prev'] = 'new';
703 $urlParameters['returnNewPageId'] = 1;
704 }
705
706 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
707 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
708
709 return '<a href="' . htmlspecialchars($url) . '">' . $linkText . '</a>';
710 }
711
712 /**
713 * Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
714 *
715 * @param string $table Table name to check
716 * @param array $page Potential parent page
717 * @return bool Returns TRUE if the tablename $table is allowed to be created on the $page
718 */
719 protected function isTableAllowedOnPage(string $table, array $page): bool
720 {
721 if (empty($page)) {
722 return $this->getBackendUserAuthentication()->isAdmin();
723 }
724 // be_users and be_groups may not be created anywhere but in the root.
725 if ($table === 'be_users' || $table === 'be_groups') {
726 return false;
727 }
728 // Checking doktype:
729 $doktype = (int)$page['doktype'];
730 if (!($allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'])) {
731 $allowedTableList = $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
732 }
733 // If all tables or the table is listed as an allowed type, return TRUE
734 if (strstr($allowedTableList, '*') || GeneralUtility::inList($allowedTableList, $table)) {
735 return true;
736 }
737
738 return false;
739 }
740
741 /**
742 * Returns whether the record link should be shown for a table
743 *
744 * Returns TRUE if:
745 * - $allowedNewTables and $deniedNewTables are empty
746 * - the table is not found in $deniedNewTables and $allowedNewTables is not set or the $table tablename is found in
747 * $allowedNewTables
748 *
749 * If $table tablename is found in $allowedNewTables and $deniedNewTables,
750 * $deniedNewTables has priority over $allowedNewTables.
751 *
752 * @param string $table Table name to test if in allowedTables
753 * @param array $allowedNewTables Array of new tables that are allowed.
754 * @param array $deniedNewTables Array of new tables that are not allowed.
755 * @return bool Returns TRUE if a link for creating new records should be displayed for $table
756 */
757 protected function isRecordCreationAllowedForTable(string $table, array $allowedNewTables = [], array $deniedNewTables = []): bool
758 {
759 if (!$this->getBackendUserAuthentication()->check('tables_modify', $table)) {
760 return false;
761 }
762
763 $allowedNewTables = $allowedNewTables ?: $this->allowedNewTables;
764 $deniedNewTables = $deniedNewTables ?: $this->deniedNewTables;
765 // No deny/allow tables are set:
766 if (empty($allowedNewTables) && empty($deniedNewTables)) {
767 return true;
768 }
769
770 return !in_array($table, $deniedNewTables) && (empty($allowedNewTables) || in_array($table, $allowedNewTables));
771 }
772
773 /**
774 * Checks if sys_language records are present
775 *
776 * @return bool
777 */
778 protected function checkIfLanguagesExist(): bool
779 {
780 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
781 ->getQueryBuilderForTable('sys_language');
782 $queryBuilder->getRestrictions()->removeAll();
783
784 $count = $queryBuilder
785 ->count('uid')
786 ->from('sys_language')
787 ->execute()
788 ->fetchColumn(0);
789 return (bool)$count;
790 }
791
792 /**
793 * @return LanguageService
794 */
795 protected function getLanguageService(): LanguageService
796 {
797 return $GLOBALS['LANG'];
798 }
799
800 /**
801 * @return BackendUserAuthentication
802 */
803 protected function getBackendUserAuthentication(): BackendUserAuthentication
804 {
805 return $GLOBALS['BE_USER'];
806 }
807 }