Passed
Branch master (6c65a4)
by Christian
16:31
created

NewRecordController::linkWrap()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 3
nop 4
1
<?php
2
namespace TYPO3\CMS\Backend\Controller;
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 Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Routing\UriBuilder;
20
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
21
use TYPO3\CMS\Backend\Template\ModuleTemplate;
22
use TYPO3\CMS\Backend\Tree\View\NewRecordPageTreeView;
23
use TYPO3\CMS\Backend\Tree\View\PagePositionMap;
24
use TYPO3\CMS\Backend\Utility\BackendUtility;
25
use TYPO3\CMS\Core\Database\ConnectionPool;
26
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
27
use TYPO3\CMS\Core\Imaging\Icon;
28
use TYPO3\CMS\Core\Type\Bitmask\Permission;
29
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Core\Utility\HttpUtility;
32
use TYPO3\CMS\Core\Utility\PathUtility;
33
use TYPO3\CMS\Frontend\Page\PageRepository;
34
35
/**
36
 * Script class for 'db_new'
37
 */
38
class NewRecordController
39
{
40
    /**
41
     * @var array
42
     */
43
    public $pageinfo;
44
45
    /**
46
     * @var array
47
     */
48
    public $pidInfo;
49
50
    /**
51
     * @var array
52
     */
53
    protected $newRecordSortList;
54
55
    /**
56
     * @var int
57
     */
58
    public $newPagesInto;
59
60
    /**
61
     * @var int
62
     */
63
    public $newContentInto;
64
65
    /**
66
     * @var int
67
     */
68
    public $newPagesAfter;
69
70
    /**
71
     * Determines, whether "Select Position" for new page should be shown
72
     *
73
     * @var bool
74
     */
75
    protected $newPagesSelectPosition = true;
76
77
    /**
78
     * @var array
79
     */
80
    public $web_list_modTSconfig;
81
82
    /**
83
     * @var array
84
     */
85
    public $allowedNewTables;
86
87
    /**
88
     * @var array
89
     */
90
    public $deniedNewTables;
91
92
    /**
93
     * @var array
94
     */
95
    public $web_list_modTSconfig_pid;
96
97
    /**
98
     * @var array
99
     */
100
    public $allowedNewTables_pid;
101
102
    /**
103
     * @var array
104
     */
105
    public $deniedNewTables_pid;
106
107
    /**
108
     * @var string
109
     */
110
    public $code;
111
112
    /**
113
     * @var string
114
     */
115
    public $R_URI;
116
117
    /**
118
     * @var int
119
     */
120
    public $id;
121
122
    /**
123
     * @var string
124
     */
125
    public $returnUrl;
126
127
    /**
128
     * pagesOnly flag.
129
     *
130
     * @var int
131
     */
132
    public $pagesOnly;
133
134
    /**
135
     * @var string
136
     */
137
    public $perms_clause;
138
139
    /**
140
     * Accumulated HTML output
141
     *
142
     * @var string
143
     */
144
    public $content;
145
146
    /**
147
     * @var array
148
     */
149
    public $tRows;
150
151
    /**
152
     * ModuleTemplate object
153
     *
154
     * @var ModuleTemplate
155
     */
156
    protected $moduleTemplate;
157
158
    /**
159
     * Constructor
160
     */
161
    public function __construct()
162
    {
163
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
164
        $GLOBALS['SOBE'] = $this;
165
        $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
166
        $this->init();
167
    }
168
169
    /**
170
     * Constructor function for the class
171
     */
172
    protected function init()
173
    {
174
        $beUser = $this->getBackendUserAuthentication();
175
        // Page-selection permission clause (reading)
176
        $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
177
        // This will hide records from display - it has nothing to do with user rights!!
178
        if ($pidList = $beUser->getTSConfigVal('options.hideRecords.pages')) {
179
            if (!empty($pidList)) {
180
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
181
                    ->getQueryBuilderForTable('pages');
182
                $this->perms_clause .= ' AND ' . $queryBuilder->expr()->notIn(
183
                    'pages.uid',
184
                    GeneralUtility::intExplode(',', $pidList)
185
                );
186
            }
187
        }
188
        // Setting GPvars:
189
        // The page id to operate from
190
        $this->id = (int)GeneralUtility::_GP('id');
191
        $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
192
        $this->pagesOnly = GeneralUtility::_GP('pagesOnly');
0 ignored issues
show
Documentation Bug introduced by
It seems like TYPO3\CMS\Core\Utility\G...ility::_GP('pagesOnly') can also be of type string. However, the property $pagesOnly is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
193
        // Setting up the context sensitive menu:
194
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
195
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
196
        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule(
197
            'TYPO3/CMS/Backend/Wizard/NewContentElement',
198
            'function(NewContentElement) {
199
                require([\'jquery\'], function($) {
200
                    $(function() {
201
                        $(\'.t3js-toggle-new-content-element-wizard\').click(function() {
202
                            var $me = $(this);
203
                            NewContentElement.wizard($me.data(\'url\'), $me.data(\'title\'));
204
                        });
205
                    });
206
                });
207
            }'
208
        );
209
        // Creating content
210
        $this->content = '';
211
        $this->content .= '<h1>'
212
            . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:db_new.php.pagetitle')
213
            . '</h1>';
214
        // Id a positive id is supplied, ask for the page record with permission information contained:
215
        if ($this->id > 0) {
216
            $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
217
        }
218
        // If a page-record was returned, the user had read-access to the page.
219
        if ($this->pageinfo['uid']) {
220
            // Get record of parent page
221
            $this->pidInfo = BackendUtility::getRecord('pages', $this->pageinfo['pid']);
222
            // Checking the permissions for the user with regard to the parent page: Can he create new pages, new
223
            // content record, new page after?
224
            if ($beUser->doesUserHaveAccess($this->pageinfo, 8)) {
225
                $this->newPagesInto = 1;
226
            }
227
            if ($beUser->doesUserHaveAccess($this->pageinfo, 16)) {
228
                $this->newContentInto = 1;
229
            }
230
            if (($beUser->isAdmin() || is_array($this->pidInfo)) && $beUser->doesUserHaveAccess($this->pidInfo, 8)) {
231
                $this->newPagesAfter = 1;
232
            }
233
        } elseif ($beUser->isAdmin()) {
234
            // Admins can do it all
235
            $this->newPagesInto = 1;
236
            $this->newContentInto = 1;
237
            $this->newPagesAfter = 0;
238
        } else {
239
            // People with no permission can do nothing
240
            $this->newPagesInto = 0;
241
            $this->newContentInto = 0;
242
            $this->newPagesAfter = 0;
243
        }
244
    }
245
246
    /**
247
     * Injects the request object for the current request or subrequest
248
     * As this controller goes only through the main() method, it is rather simple for now
249
     *
250
     * @param ServerRequestInterface $request the current request
251
     * @param ResponseInterface $response
252
     * @return ResponseInterface the response with the content
253
     */
254
    public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

254
    public function mainAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request, ResponseInterface $response)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
255
    {
256
        $this->main();
257
258
        $response->getBody()->write($this->moduleTemplate->renderContent());
259
        return $response;
260
    }
261
262
    /**
263
     * Main processing, creating the list of new record tables to select from
264
     */
265
    public function main()
266
    {
267
        // If there was a page - or if the user is admin (admins has access to the root) we proceed:
268
        if (!empty($this->pageinfo['uid']) || $this->getBackendUserAuthentication()->isAdmin()) {
269
            if (empty($this->pageinfo)) {
270
                // Explicitly pass an empty array to the docHeader
271
                $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation([]);
272
            } else {
273
                $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
274
            }
275
            // Acquiring TSconfig for this module/current page:
276
            $this->web_list_modTSconfig = BackendUtility::getModTSconfig($this->pageinfo['uid'], 'mod.web_list');
277
            $this->allowedNewTables = GeneralUtility::trimExplode(
278
                ',',
279
                $this->web_list_modTSconfig['properties']['allowedNewTables'],
280
                true
281
            );
282
            $this->deniedNewTables = GeneralUtility::trimExplode(
283
                ',',
284
                $this->web_list_modTSconfig['properties']['deniedNewTables'],
285
                true
286
            );
287
            // Acquiring TSconfig for this module/parent page:
288
            $this->web_list_modTSconfig_pid = BackendUtility::getModTSconfig($this->pageinfo['pid'], 'mod.web_list');
289
            $this->allowedNewTables_pid = GeneralUtility::trimExplode(
290
                ',',
291
                $this->web_list_modTSconfig_pid['properties']['allowedNewTables'],
292
                true
293
            );
294
            $this->deniedNewTables_pid = GeneralUtility::trimExplode(
295
                ',',
296
                $this->web_list_modTSconfig_pid['properties']['deniedNewTables'],
297
                true
298
            );
299
            // More init:
300
            if (!$this->showNewRecLink('pages')) {
301
                $this->newPagesInto = 0;
302
            }
303
            if (!$this->showNewRecLink('pages', $this->allowedNewTables_pid, $this->deniedNewTables_pid)) {
304
                $this->newPagesAfter = 0;
305
            }
306
            // Set header-HTML and return_url
307
            if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
308
                $title = strip_tags($this->pageinfo[$GLOBALS['TCA']['pages']['ctrl']['label']]);
309
            } else {
310
                $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
311
            }
312
            $this->moduleTemplate->setTitle($title);
313
            // GENERATE the HTML-output depending on mode (pagesOnly is the page wizard)
314
            // Regular new element:
315
            if (!$this->pagesOnly) {
316
                $this->regularNew();
317
            } elseif ($this->showNewRecLink('pages')) {
318
                // Pages only wizard
319
                $this->pagesOnly();
320
            }
321
            // Add all the content to an output section
322
            $this->content .= '<div>' . $this->code . '</div>';
323
            // Setting up the buttons and markers for docheader
324
            $this->getButtons();
325
            // Build the <body> for the module
326
            $this->moduleTemplate->setContent($this->content);
327
        }
328
    }
329
330
    /**
331
     * Create the panel of buttons for submitting the form or otherwise perform operations.
332
     */
333
    protected function getButtons()
334
    {
335
        $lang = $this->getLanguageService();
336
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
337
        // Regular new element:
338
        if (!$this->pagesOnly) {
339
            // New page
340
            if ($this->showNewRecLink('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->moduleTemplate->getIconFactory()->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->showNewRecLink('pages')) {
351
            // Pages only wizard
352
            // CSH
353
            $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'new_pages');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$buttons was never initialized. Although not strictly required by PHP, it is generally a good practice to add $buttons = array(); before regardless.
Loading history...
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:lang/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
362
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL));
363
            $buttonBar->addButton($returnButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
364
        }
365
366
        if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
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
                $viewButton = $buttonBar->makeLinkButton()
385
                    ->setHref('#')
386
                    ->setOnClick(BackendUtility::viewOnClick(
387
                        $this->pageinfo['uid'],
388
                        '',
389
                        BackendUtility::BEgetRootLine($this->pageinfo['uid'])
390
                    ))
391
                    ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
392
                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
393
                        'actions-view-page',
394
                        Icon::SIZE_SMALL
395
                    ));
396
                $buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 30);
397
            }
398
        }
399
    }
400
401
    /**
402
     * Creates the position map for pages wizard
403
     */
404
    public function pagesOnly()
405
    {
406
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
407
            ->getQueryBuilderForTable('sys_language');
408
        $queryBuilder->getRestrictions()
409
            ->removeAll()
410
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
411
        $numberOfPages = $queryBuilder
412
            ->count('*')
413
            ->from('pages')
414
            ->execute()
415
            ->fetchColumn(0);
416
417
        if ($numberOfPages > 0) {
418
            $this->code .= '<h3>' . htmlspecialchars($this->getLanguageService()->getLL('selectPosition')) . ':</h3>';
419
            $positionMap = GeneralUtility::makeInstance(PagePositionMap::class, NewRecordPageTreeView::class);
0 ignored issues
show
Bug introduced by
TYPO3\CMS\Backend\Tree\V...cordPageTreeView::class of type string is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

419
            $positionMap = GeneralUtility::makeInstance(PagePositionMap::class, /** @scrutinizer ignore-type */ NewRecordPageTreeView::class);
Loading history...
420
            /** @var $positionMap \TYPO3\CMS\Backend\Tree\View\PagePositionMap */
421
            $this->code .= $positionMap->positionTree(
422
                $this->id,
423
                $this->pageinfo,
424
                $this->perms_clause,
425
                $this->returnUrl
426
            );
427
        } else {
428
            /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
429
            $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
430
            // No pages yet, no need to prompt for position, redirect to page creation.
431
            $urlParameters = [
432
                'edit' => [
433
                    'pages' => [
434
                        0 => 'new'
435
                    ]
436
                ],
437
                'returnNewPageId' => 1,
438
                'returnUrl' => (string)$uriBuilder->buildUriFromRoute('db_new', ['id' => $this->id, 'pagesOnly' => '1'])
439
            ];
440
            $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
441
            @ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

441
            /** @scrutinizer ignore-unhandled */ @ob_end_clean();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
442
            HttpUtility::redirect($url);
443
        }
444
    }
445
446
    /**
447
     * Create a regular new element (pages and records)
448
     */
449
    public function regularNew()
450
    {
451
        $lang = $this->getLanguageService();
452
        // Initialize array for accumulating table rows:
453
        $this->tRows = [];
454
        // Get TSconfig for current page
455
        $pageTS = BackendUtility::getPagesTSconfig($this->id);
456
        // Finish initializing new pages options with TSconfig
457
        // Each new page option may be hidden by TSconfig
458
        // Enabled option for the position of a new page
459
        $this->newPagesSelectPosition = !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageSelectPosition']);
460
        // Pseudo-boolean (0/1) for backward compatibility
461
        $displayNewPagesIntoLink = $this->newPagesInto && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageInside']);
462
        $displayNewPagesAfterLink = $this->newPagesAfter && !empty($pageTS['mod.']['wizards.']['newRecord.']['pages.']['show.']['pageAfter']);
463
        // Slight spacer from header:
464
        $this->code .= '';
465
        // New Page
466
        $table = 'pages';
467
        $v = $GLOBALS['TCA'][$table];
468
        $pageIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord(
469
            $table,
470
            [],
471
            Icon::SIZE_SMALL
472
        )->render();
473
        $newPageIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL)->render();
474
        $rowContent = '';
475
        // New pages INSIDE this pages
476
        $newPageLinks = [];
477
        if ($displayNewPagesIntoLink
478
            && $this->isTableAllowedForThisPage($this->pageinfo, 'pages')
479
            && $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
480
            && $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ?: $this->id), 'pages')
481
        ) {
482
            // Create link to new page inside:
483
            $recordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
484
            $newPageLinks[] = $this->linkWrap(
485
                $recordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:db_new.php.inside')) . ')',
486
                $table,
487
                $this->id
488
            );
489
        }
490
        // New pages AFTER this pages
491
        if ($displayNewPagesAfterLink
492
            && $this->isTableAllowedForThisPage($this->pidInfo, 'pages')
493
            && $this->getBackendUserAuthentication()->check('tables_modify', 'pages')
494
            && $this->getBackendUserAuthentication()->workspaceCreateNewRecord($this->pidInfo['uid'], 'pages')
495
        ) {
496
            $newPageLinks[] = $this->linkWrap($pageIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])) . ' (' . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:db_new.php.after')) . ')', 'pages', -$this->id);
497
        }
498
        // New pages at selection position
499
        if ($this->newPagesSelectPosition && $this->showNewRecLink('pages')) {
500
            // Link to page-wizard:
501
            $newPageLinks[] = '<a href="' . htmlspecialchars(GeneralUtility::linkThisScript(['pagesOnly' => 1])) . '">' . $pageIcon . htmlspecialchars($lang->getLL('pageSelectPosition')) . '</a>';
502
        }
503
        // Assemble all new page links
504
        $numPageLinks = count($newPageLinks);
505
        for ($i = 0; $i < $numPageLinks; $i++) {
506
            $rowContent .= '<li>' . $newPageLinks[$i] . '</li>';
507
        }
508
        if ($this->showNewRecLink('pages')) {
509
            $rowContent = '<ul class="list-tree"><li>' . $newPageIcon . '<strong>' .
510
                $lang->getLL('createNewPage') . '</strong><ul>' . $rowContent . '</ul></li>';
511
        } else {
512
            $rowContent = '<ul class="list-tree"><li><ul>' . $rowContent . '</li></ul>';
513
        }
514
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
515
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
516
        // Compile table row
517
        $startRows = [$rowContent];
518
        $iconFile = [];
519
        // New tables (but not pages) INSIDE this pages
520
        $isAdmin = $this->getBackendUserAuthentication()->isAdmin();
521
        $newContentIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-document-new', Icon::SIZE_SMALL)->render();
522
        if ($this->newContentInto) {
523
            if (is_array($GLOBALS['TCA'])) {
524
                $groupName = '';
525
                foreach ($GLOBALS['TCA'] as $table => $v) {
526
                    $rootLevelConfiguration = isset($v['ctrl']['rootLevel']) ? (int)$v['ctrl']['rootLevel'] : 0;
527
                    if ($table !== 'pages'
528
                        && $this->showNewRecLink($table)
529
                        && $this->isTableAllowedForThisPage($this->pageinfo, $table)
530
                        && $this->getBackendUserAuthentication()->check('tables_modify', $table)
531
                        && ($rootLevelConfiguration === -1 || ($this->id xor $rootLevelConfiguration))
532
                        && $this->getBackendUserAuthentication()->workspaceCreateNewRecord(($this->pageinfo['_ORIG_uid'] ? $this->pageinfo['_ORIG_uid'] : $this->id), $table)
533
                    ) {
534
                        $newRecordIcon = $this->moduleTemplate->getIconFactory()->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
535
                        $rowContent = '';
536
                        $thisTitle = '';
537
                        // Create new link for record:
538
                        $newLink = $this->linkWrap($newRecordIcon . htmlspecialchars($lang->sL($v['ctrl']['title'])), $table, $this->id);
539
                        // If the table is 'tt_content', create link to wizard
540
                        if ($table === 'tt_content') {
541
                            $groupName = $lang->getLL('createNewContent');
542
                            $rowContent = $newContentIcon
543
                                . '<strong>' . $lang->getLL('createNewContent') . '</strong>'
544
                                . '<ul>';
545
                            // If mod.newContentElementWizard.override is set, use that extension's wizard instead:
546
                            $tsConfig = BackendUtility::getModTSconfig($this->id, 'mod');
547
                            $moduleName = $tsConfig['properties']['newContentElementWizard.']['override'] ?? 'new_content_element_wizard';
548
                            $url = (string)$uriBuilder->buildUriFromRoute($moduleName, ['id' => $this->id, 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')]);
549
                            $rowContent .= '<li>' . $newLink . ' ' . BackendUtility::wrapInHelp($table, '') . '</li>'
550
                                . '<li>'
551
                                . '<a href="#" data-url="' . htmlspecialchars($url) . '" data-title="' . htmlspecialchars($this->getLanguageService()->getLL('newContentElement')) . '" class="t3js-toggle-new-content-element-wizard">'
552
                                . $newContentIcon . htmlspecialchars($lang->getLL('clickForWizard'))
553
                                . '</a>'
554
                                . '</li>'
555
                                . '</ul>';
556
                        } else {
557
                            // Get the title
558
                            if ($v['ctrl']['readOnly'] || $v['ctrl']['hideTable'] || $v['ctrl']['is_static']) {
559
                                continue;
560
                            }
561
                            if ($v['ctrl']['adminOnly'] && !$isAdmin) {
562
                                continue;
563
                            }
564
                            $nameParts = explode('_', $table);
565
                            $thisTitle = '';
566
                            $_EXTKEY = '';
567
                            if ($nameParts[0] === 'tx' || $nameParts[0] === 'tt') {
568
                                // Try to extract extension name
569
                                if (substr($v['ctrl']['title'], 0, 8) === 'LLL:EXT:') {
570
                                    $_EXTKEY = substr($v['ctrl']['title'], 8);
571
                                    $_EXTKEY = substr($_EXTKEY, 0, strpos($_EXTKEY, '/'));
572
                                    if ($_EXTKEY !== '') {
573
                                        // First try to get localisation of extension title
574
                                        $temp = explode(':', substr($v['ctrl']['title'], 9 + strlen($_EXTKEY)));
575
                                        $langFile = $temp[0];
576
                                        $thisTitle = $lang->sL('LLL:EXT:' . $_EXTKEY . '/' . $langFile . ':extension.title');
577
                                        // If no localisation available, read title from ext_emconf.php
578
                                        $extPath = ExtensionManagementUtility::extPath($_EXTKEY);
579
                                        $extEmConfFile = $extPath . 'ext_emconf.php';
580
                                        if (!$thisTitle && is_file($extEmConfFile)) {
581
                                            $EM_CONF = [];
582
                                            include $extEmConfFile;
583
                                            $thisTitle = $EM_CONF[$_EXTKEY]['title'];
584
                                        }
585
                                        $iconFile[$_EXTKEY] = '<img src="' . PathUtility::getAbsoluteWebPath(ExtensionManagementUtility::getExtensionIcon($extPath, true)) . '" ' . 'width="16" height="16" ' . 'alt="' . $thisTitle . '" />';
586
                                    }
587
                                }
588
                                if (empty($thisTitle)) {
589
                                    $_EXTKEY = $nameParts[1];
590
                                    $thisTitle = $nameParts[1];
591
                                    $iconFile[$_EXTKEY] = '';
592
                                }
593
                            } else {
594
                                $_EXTKEY = 'system';
595
                                $thisTitle = $lang->getLL('system_records');
596
                                $iconFile['system'] = $this->moduleTemplate->getIconFactory()->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render();
597
                            }
598
599
                            if ($groupName === '' || $groupName !== $_EXTKEY) {
600
                                $groupName = empty($v['ctrl']['groupName']) ? $_EXTKEY : $v['ctrl']['groupName'];
601
                            }
602
                            $rowContent .= $newLink;
603
                        }
604
                        // Compile table row:
605
                        if ($table === 'tt_content') {
606
                            $startRows[] = '<li>' . $rowContent . '</li>';
607
                        } else {
608
                            $this->tRows[$groupName]['title'] = $thisTitle;
609
                            $this->tRows[$groupName]['html'][] = $rowContent;
610
                            $this->tRows[$groupName]['table'][] = $table;
611
                        }
612
                    }
613
                }
614
            }
615
        }
616
        // User sort
617
        if (isset($pageTS['mod.']['wizards.']['newRecord.']['order'])) {
618
            $this->newRecordSortList = GeneralUtility::trimExplode(',', $pageTS['mod.']['wizards.']['newRecord.']['order'], true);
619
        }
620
        uksort($this->tRows, [$this, 'sortNewRecordsByConfig']);
621
        // Compile table row:
622
        $finalRows = [];
623
        $finalRows[] = implode('', $startRows);
624
        foreach ($this->tRows as $key => $value) {
625
            $row = '<li>' . $iconFile[$key] . ' <strong>' . $value['title'] . '</strong><ul>';
626
            foreach ($value['html'] as $recordKey => $record) {
627
                $row .= '<li>' . $record . ' ' . BackendUtility::wrapInHelp($value['table'][$recordKey], '') . '</li>';
628
            }
629
            $row .= '</ul></li>';
630
            $finalRows[] = $row;
631
        }
632
633
        $finalRows[] = '</ul>';
634
        // Make table:
635
        $this->code .= implode('', $finalRows);
636
    }
637
638
    /**
639
     * User array sort function used by regularNew
640
     *
641
     * @param string $a First array element for compare
642
     * @param string $b First array element for compare
643
     * @return int -1 for lower, 0 for equal, 1 for greater
644
     */
645
    public function sortNewRecordsByConfig($a, $b)
646
    {
647
        if (!empty($this->newRecordSortList)) {
648
            if (in_array($a, $this->newRecordSortList) && in_array($b, $this->newRecordSortList)) {
649
                // Both are in the list, return relative to position in array
650
                $sub = array_search($a, $this->newRecordSortList) - array_search($b, $this->newRecordSortList);
651
                $ret = ($sub < 0 ? -1 : $sub == 0) ? 0 : 1;
652
            } elseif (in_array($a, $this->newRecordSortList)) {
653
                // First element is in array, put to top
654
                $ret = -1;
655
            } elseif (in_array($b, $this->newRecordSortList)) {
656
                // Second element is in array, put first to bottom
657
                $ret = 1;
658
            } else {
659
                // No element is in array, return alphabetic order
660
                $ret = strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
661
            }
662
            return $ret;
663
        }
664
        // Return alphabetic order
665
        return strnatcasecmp($this->tRows[$a]['title'], $this->tRows[$b]['title']);
666
    }
667
668
    /**
669
     * Links the string $code to a create-new form for a record in $table created on page $pid
670
     *
671
     * @param string $linkText Link text
672
     * @param string $table Table name (in which to create new record)
673
     * @param int $pid PID value for the "&edit['.$table.']['.$pid.']=new" command (positive/negative)
674
     * @param bool $addContentTable If $addContentTable is set, then a new tt_content record is created together with pages
675
     * @return string The link.
676
     */
677
    public function linkWrap($linkText, $table, $pid, $addContentTable = false)
678
    {
679
        $urlParameters = [
680
            'edit' => [
681
                $table => [
682
                    $pid => 'new'
683
                ]
684
            ],
685
            'returnUrl' => $this->returnUrl
686
        ];
687
        if ($table === 'pages' && $addContentTable) {
688
            $urlParameters['tt_content']['prev'] = 'new';
689
            $urlParameters['returnNewPageId'] = 1;
690
        } elseif ($table === 'pages') {
691
            $urlParameters['overrideVals']['pages']['doktype'] = (int)$this->pageinfo['doktype'];
692
        }
693
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
694
        $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
695
        return '<a href="' . htmlspecialchars($url) . '">' . $linkText . '</a>';
696
    }
697
698
    /**
699
     * Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
700
     *
701
     * @param array $pid_row Record for parent page.
702
     * @param string $checkTable Table name to check
703
     * @return bool Returns TRUE if the tablename $checkTable is allowed to be created on the page with record $pid_row
704
     */
705
    public function isTableAllowedForThisPage($pid_row, $checkTable)
706
    {
707
        if (!is_array($pid_row)) {
708
            return $this->getBackendUserAuthentication()->isAdmin();
709
        }
710
        // be_users and be_groups may not be created anywhere but in the root.
711
        if ($checkTable === 'be_users' || $checkTable === 'be_groups') {
712
            return false;
713
        }
714
        // Checking doktype:
715
        $doktype = (int)$pid_row['doktype'];
716
        if (!($allowedTableList = $GLOBALS['PAGES_TYPES'][$doktype]['allowedTables'])) {
717
            $allowedTableList = $GLOBALS['PAGES_TYPES']['default']['allowedTables'];
718
        }
719
        // If all tables or the table is listed as an allowed type, return TRUE
720
        if (strstr($allowedTableList, '*') || GeneralUtility::inList($allowedTableList, $checkTable)) {
721
            return true;
722
        }
723
724
        return false;
725
    }
726
727
    /**
728
     * Returns TRUE if:
729
     * - $allowedNewTables and $deniedNewTables are empty
730
     * - the table is not found in $deniedNewTables and $allowedNewTables is not set or the $table tablename is found in
731
     *   $allowedNewTables
732
     *
733
     * If $table tablename is found in $allowedNewTables and $deniedNewTables, $deniedNewTables
734
     * has priority over $allowedNewTables.
735
     *
736
     * @param string $table Table name to test if in allowedTables
737
     * @param array $allowedNewTables Array of new tables that are allowed.
738
     * @param array $deniedNewTables Array of new tables that are not allowed.
739
     *
740
     * @return bool Returns TRUE if a link for creating new records should be displayed for $table
741
     */
742
    public function showNewRecLink($table, array $allowedNewTables = [], array $deniedNewTables = [])
743
    {
744
        if (!$this->getBackendUserAuthentication()->check('tables_modify', $table)) {
745
            return false;
746
        }
747
748
        $allowedNewTables = $allowedNewTables ?: $this->allowedNewTables;
749
        $deniedNewTables = $deniedNewTables ?: $this->deniedNewTables;
750
        // No deny/allow tables are set:
751
        if (empty($allowedNewTables) && empty($deniedNewTables)) {
752
            return true;
753
        }
754
755
        return !in_array($table, $deniedNewTables) && (empty($allowedNewTables) || in_array($table, $allowedNewTables));
756
    }
757
758
    /**
759
     * Checks if sys_language records are present
760
     *
761
     * @return bool
762
     */
763
    protected function checkIfLanguagesExist()
764
    {
765
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
766
            ->getQueryBuilderForTable('sys_language');
767
        $queryBuilder->getRestrictions()->removeAll();
768
769
        $count = $queryBuilder
770
            ->count('uid')
771
            ->from('sys_language')
772
            ->execute()
773
            ->fetchColumn(0);
774
        return (bool)$count;
775
    }
776
777
    /**
778
     * Return language service instance
779
     *
780
     * @return \TYPO3\CMS\Core\Localization\LanguageService
781
     */
782
    protected function getLanguageService()
783
    {
784
        return $GLOBALS['LANG'];
785
    }
786
787
    /**
788
     * Returns the global BackendUserAuthentication object.
789
     *
790
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
791
     */
792
    protected function getBackendUserAuthentication()
793
    {
794
        return $GLOBALS['BE_USER'];
795
    }
796
}
797