Passed
Push — master ( b28ab0...3a93bc )
by
unknown
17:12
created

NewRecordController::init()   B

Complexity

Conditions 11
Paths 40

Size

Total Lines 62
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 62
rs 7.3166
c 0
b 0
f 0
cc 11
nc 40
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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