Passed
Push — master ( f5ad35...df6e06 )
by
unknown
15:14
created

PageProvider::isExcludedDoktype()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 9
rs 10
cc 1
nc 1
nop 0
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\ContextMenu\ItemProviders;
19
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
23
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
24
use TYPO3\CMS\Core\Type\Bitmask\Permission;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27
/**
28
 * Context menu item provider for pages table
29
 */
30
class PageProvider extends RecordProvider
31
{
32
33
    /**
34
     * @var string
35
     */
36
    protected $table = 'pages';
37
38
    /**
39
     * @var array
40
     */
41
    protected $itemsConfiguration = [
42
        'view' => [
43
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.view',
44
            'iconIdentifier' => 'actions-view-page',
45
            'callbackAction' => 'viewRecord'
46
        ],
47
        'edit' => [
48
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.edit',
49
            'iconIdentifier' => 'actions-page-open',
50
            'callbackAction' => 'editRecord'
51
        ],
52
        'new' => [
53
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.new',
54
            'iconIdentifier' => 'actions-page-new',
55
            'callbackAction' => 'newRecord'
56
        ],
57
        'info' => [
58
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.info',
59
            'iconIdentifier' => 'actions-document-info',
60
            'callbackAction' => 'openInfoPopUp'
61
        ],
62
        'divider1' => [
63
            'type' => 'divider'
64
        ],
65
        'copy' => [
66
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
67
            'iconIdentifier' => 'actions-edit-copy',
68
            'callbackAction' => 'copy'
69
        ],
70
        'copyRelease' => [
71
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy',
72
            'iconIdentifier' => 'actions-edit-copy-release',
73
            'callbackAction' => 'clipboardRelease'
74
        ],
75
        'cut' => [
76
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut',
77
            'iconIdentifier' => 'actions-edit-cut',
78
            'callbackAction' => 'cut'
79
        ],
80
        'cutRelease' => [
81
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
82
            'iconIdentifier' => 'actions-edit-cut-release',
83
            'callbackAction' => 'clipboardRelease'
84
        ],
85
        'pasteAfter' => [
86
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteafter',
87
            'iconIdentifier' => 'actions-document-paste-after',
88
            'callbackAction' => 'pasteAfter'
89
        ],
90
        'pasteInto' => [
91
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
92
            'iconIdentifier' => 'actions-document-paste-into',
93
            'callbackAction' => 'pasteInto'
94
        ],
95
        'divider2' => [
96
            'type' => 'divider'
97
        ],
98
        'more' => [
99
            'type' => 'submenu',
100
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.more',
101
            'iconIdentifier' => '',
102
            'callbackAction' => 'openSubmenu',
103
            'childItems' => [
104
                'newWizard' => [
105
                    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_newWizard',
106
                    'iconIdentifier' => 'actions-page-new',
107
                    'callbackAction' => 'newPageWizard',
108
                ],
109
                'pagesSort' => [
110
                    'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_sort.xlf:title',
111
                    'iconIdentifier' => 'actions-page-move',
112
                    'callbackAction' => 'pagesSort',
113
                ],
114
                'pagesNewMultiple' => [
115
                    'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_new.xlf:title',
116
                    'iconIdentifier' => 'apps-pagetree-drag-move-between',
117
                    'callbackAction' => 'pagesNewMultiple',
118
                ],
119
                'openListModule' => [
120
                    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_db_list',
121
                    'iconIdentifier' => 'actions-system-list-open',
122
                    'callbackAction' => 'openListModule',
123
                ],
124
                'mountAsTreeRoot' => [
125
                    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.tempMountPoint',
126
                    'iconIdentifier' => 'actions-pagetree-mountroot',
127
                    'callbackAction' => 'mountAsTreeRoot',
128
                ],
129
                'showInMenus' => [
130
                    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_showInMenus',
131
                    'iconIdentifier' => 'actions-view',
132
                    'callbackAction' => 'showInMenus',
133
                ],
134
                'hideInMenus' => [
135
                    'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_hideInMenus',
136
                    'iconIdentifier' => 'actions-ban',
137
                    'callbackAction' => 'hideInMenus',
138
                ],
139
            ],
140
        ],
141
        'divider3' => [
142
            'type' => 'divider'
143
        ],
144
        'enable' => [
145
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:enable',
146
            'iconIdentifier' => 'actions-edit-unhide',
147
            'callbackAction' => 'enableRecord',
148
        ],
149
        'disable' => [
150
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:disable',
151
            'iconIdentifier' => 'actions-edit-hide',
152
            'callbackAction' => 'disableRecord',
153
        ],
154
        'delete' => [
155
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete',
156
            'iconIdentifier' => 'actions-edit-delete',
157
            'callbackAction' => 'deleteRecord',
158
        ],
159
        'history' => [
160
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:CM_history',
161
            'iconIdentifier' => 'actions-document-history-open',
162
            'callbackAction' => 'openHistoryPopUp',
163
        ],
164
        'clearCache' => [
165
            'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.clear_cache',
166
            'iconIdentifier' => 'actions-system-cache-clear',
167
            'callbackAction' => 'clearCache',
168
        ],
169
    ];
170
171
    /**
172
     * @var bool
173
     */
174
    protected $languageAccess = false;
175
176
    /**
177
     * Checks if the provider can add items to the menu
178
     *
179
     * @return bool
180
     */
181
    public function canHandle(): bool
182
    {
183
        return $this->table === 'pages';
184
    }
185
186
    /**
187
     * @return int
188
     */
189
    public function getPriority(): int
190
    {
191
        return 100;
192
    }
193
194
    /**
195
     * @param string $itemName
196
     * @param string $type
197
     * @return bool
198
     */
199
    protected function canRender(string $itemName, string $type): bool
200
    {
201
        if (in_array($type, ['divider', 'submenu'], true)) {
202
            return true;
203
        }
204
        if (in_array($itemName, $this->disabledItems, true)) {
205
            return false;
206
        }
207
        $canRender = false;
208
        switch ($itemName) {
209
            case 'view':
210
                $canRender = $this->canBeViewed();
211
                break;
212
            case 'edit':
213
                $canRender = $this->canBeEdited();
214
                break;
215
            case 'new':
216
            case 'newWizard':
217
            case 'pagesNewMultiple':
218
                $canRender = $this->canBeCreated();
219
                break;
220
            case 'info':
221
                $canRender = $this->canShowInfo();
222
                break;
223
            case 'enable':
224
                $canRender = $this->canBeEnabled();
225
                break;
226
            case 'disable':
227
                $canRender = $this->canBeDisabled();
228
                break;
229
            case 'showInMenus':
230
                $canRender = $this->canBeToggled('nav_hide', 1);
231
                break;
232
            case 'hideInMenus':
233
                $canRender = $this->canBeToggled('nav_hide', 0);
234
                break;
235
            case 'delete':
236
                $canRender = $this->canBeDeleted();
237
                break;
238
            case 'history':
239
                $canRender = $this->canShowHistory();
240
                break;
241
            case 'openListModule':
242
                $canRender = $this->canOpenListModule();
243
                break;
244
            case 'pagesSort':
245
                $canRender = $this->canBeSorted();
246
                break;
247
            case 'mountAsTreeRoot':
248
                $canRender = !$this->isRoot();
249
                break;
250
            case 'copy':
251
                $canRender = $this->canBeCopied();
252
                break;
253
            case 'copyRelease':
254
                $canRender = $this->isRecordInClipboard('copy');
255
                break;
256
            case 'cut':
257
                $canRender = $this->canBeCut() && !$this->isRecordInClipboard('cut');
258
                break;
259
            case 'cutRelease':
260
                $canRender = $this->isRecordInClipboard('cut');
261
                break;
262
            case 'pasteAfter':
263
                $canRender = $this->canBePastedAfter();
264
                break;
265
            case 'pasteInto':
266
                $canRender = $this->canBePastedInto();
267
                break;
268
            case 'clearCache':
269
                $canRender = $this->canClearCache();
270
                break;
271
        }
272
        return $canRender;
273
    }
274
275
    /**
276
     * Saves calculated permissions for a page to speed things up
277
     */
278
    protected function initPermissions()
279
    {
280
        $this->pagePermissions = new Permission($this->backendUser->calcPerms($this->record));
281
        $this->languageAccess = $this->hasLanguageAccess();
282
    }
283
284
    /**
285
     * Checks if the user may create pages below the given page
286
     *
287
     * @return bool
288
     */
289
    protected function canBeCreated(): bool
290
    {
291
        if (!$this->backendUser->checkLanguageAccess(0)) {
292
            return false;
293
        }
294
        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['languageField'])
295
            && !in_array($this->record[$GLOBALS['TCA'][$this->table]['ctrl']['languageField']], [0, -1])
296
        ) {
297
            return false;
298
        }
299
        return $this->hasPagePermission(Permission::PAGE_NEW);
300
    }
301
302
    /**
303
     * Checks if the user has editing rights
304
     *
305
     * @return bool
306
     */
307
    protected function canBeEdited(): bool
308
    {
309
        if (!$this->languageAccess) {
310
            return false;
311
        }
312
        if ($this->isRoot()) {
313
            return false;
314
        }
315
        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) {
316
            return false;
317
        }
318
        if ($this->backendUser->isAdmin()) {
319
            return true;
320
        }
321
        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) {
322
            return false;
323
        }
324
        return !$this->isRecordLocked() && $this->hasPagePermission(Permission::PAGE_EDIT);
325
    }
326
327
    /**
328
     * Check if a page is locked
329
     *
330
     * @return bool
331
     */
332
    protected function isRecordLocked(): bool
333
    {
334
        return (bool)$this->record['editlock'];
335
    }
336
337
    /**
338
     * Checks if the page is allowed to can be cut
339
     *
340
     * @return bool
341
     */
342
    protected function canBeCut(): bool
343
    {
344
        if (!$this->languageAccess) {
345
            return false;
346
        }
347
        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['languageField'])
348
            && !in_array($this->record[$GLOBALS['TCA'][$this->table]['ctrl']['languageField']], [0, -1])
349
        ) {
350
            return false;
351
        }
352
        return !$this->isWebMount()
353
            && $this->canBeEdited()
354
            && !$this->isDeletePlaceholder();
355
    }
356
357
    /**
358
     * Checks if the page is allowed to be copied
359
     *
360
     * @return bool
361
     */
362
    protected function canBeCopied(): bool
363
    {
364
        if (!$this->languageAccess) {
365
            return false;
366
        }
367
        if (isset($GLOBALS['TCA'][$this->table]['ctrl']['languageField'])
368
            && !in_array($this->record[$GLOBALS['TCA'][$this->table]['ctrl']['languageField']], [0, -1])
369
        ) {
370
            return false;
371
        }
372
        return !$this->isRoot()
373
            && !$this->isWebMount()
374
            && !$this->isRecordInClipboard('copy')
375
            && $this->hasPagePermission(Permission::PAGE_SHOW)
376
            && !$this->isDeletePlaceholder();
377
    }
378
379
    /**
380
     * Checks if something can be pasted into the node
381
     *
382
     * @return bool
383
     */
384
    protected function canBePastedInto(): bool
385
    {
386
        if (!$this->languageAccess) {
387
            return false;
388
        }
389
        $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
390
391
        return $clipboardElementCount
392
            && $this->canBeCreated()
393
            && !$this->isDeletePlaceholder();
394
    }
395
396
    /**
397
     * Checks if something can be pasted after the node
398
     *
399
     * @return bool
400
     */
401
    protected function canBePastedAfter(): bool
402
    {
403
        if (!$this->languageAccess) {
404
            return false;
405
        }
406
        $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
407
        return $clipboardElementCount
408
            && $this->canBeCreated()
409
            && !$this->isDeletePlaceholder();
410
    }
411
412
    /**
413
     * Check if sub pages of given page can be sorted
414
     *
415
     * @return bool
416
     */
417
    protected function canBeSorted(): bool
418
    {
419
        if (!$this->languageAccess) {
420
            return false;
421
        }
422
        return $this->backendUser->check('tables_modify', $this->table)
423
            && $this->hasPagePermission(Permission::CONTENT_EDIT)
424
            && !$this->isDeletePlaceholder()
425
            && $this->backendUser->workspace === 0;
426
    }
427
428
    /**
429
     * Checks if the page is allowed to be removed
430
     *
431
     * @return bool
432
     */
433
    protected function canBeDeleted(): bool
434
    {
435
        if (!$this->languageAccess) {
436
            return false;
437
        }
438
        return !$this->isDeletePlaceholder()
439
            && !$this->isRecordLocked()
440
            && !$this->isDeletionDisabledInTS()
441
            && $this->hasPagePermission(Permission::PAGE_DELETE);
442
    }
443
444
    /**
445
     * Checks if the page is allowed to be viewed in frontend
446
     *
447
     * @return bool
448
     */
449
    protected function canBeViewed(): bool
450
    {
451
        return !$this->isRoot()
452
            && !$this->isDeleted()
453
            && !$this->isExcludedDoktype();
454
    }
455
456
    /**
457
     * Checks if the page is allowed to show info
458
     *
459
     * @return bool
460
     */
461
    protected function canShowInfo(): bool
462
    {
463
        return !$this->isRoot();
464
    }
465
466
    /**
467
     * Checks if the user has clear cache rights
468
     *
469
     * @return bool
470
     */
471
    protected function canClearCache(): bool
472
    {
473
        return !$this->isRoot()
474
            && ($this->backendUser->isAdmin() || $this->backendUser->getTSConfig()['options.']['clearCache.']['pages'] ?? false);
475
    }
476
477
    /**
478
     * Determines whether this node is deleted.
479
     *
480
     * @return bool
481
     */
482
    protected function isDeleted(): bool
483
    {
484
        return !empty($this->record['deleted']) || $this->isDeletePlaceholder();
485
    }
486
487
    /**
488
     * Returns true if current record is a root page
489
     *
490
     * @return bool
491
     */
492
    protected function isRoot()
493
    {
494
        return (int)$this->identifier === 0;
495
    }
496
497
    /**
498
     * Returns true if current record is a web mount
499
     *
500
     * @return bool
501
     */
502
    protected function isWebMount()
503
    {
504
        return in_array($this->identifier, $this->backendUser->returnWebmounts());
505
    }
506
507
    /**
508
     * @param string $itemName
509
     * @return array
510
     */
511
    protected function getAdditionalAttributes(string $itemName): array
512
    {
513
        $attributes = [];
514
        if ($itemName === 'view') {
515
            $attributes += $this->getViewAdditionalAttributes();
516
        }
517
        if ($itemName === 'enable' || $itemName === 'disable') {
518
            $attributes += $this->getEnableDisableAdditionalAttributes();
519
        }
520
        if ($itemName === 'delete') {
521
            $attributes += $this->getDeleteAdditionalAttributes();
522
        }
523
        if ($itemName === 'pasteInto') {
524
            $attributes += $this->getPasteAdditionalAttributes('into');
525
        }
526
        if ($itemName === 'pasteAfter') {
527
            $attributes += $this->getPasteAdditionalAttributes('after');
528
        }
529
        if ($itemName === 'pagesSort') {
530
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
531
            $attributes += [
532
                'data-pages-sort-url' => (string)$uriBuilder->buildUriFromRoute('pages_sort', ['id' => $this->record['uid']]),
533
            ];
534
        }
535
        if ($itemName === 'pagesNewMultiple') {
536
            $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
537
            $attributes += [
538
                'data-pages-new-multiple-url' => (string)$uriBuilder->buildUriFromRoute('pages_new', ['id' => $this->record['uid']]),
539
            ];
540
        }
541
        if ($itemName === 'edit') {
542
            $attributes = [
543
                'data-pages-language-uid' => $this->record['sys_language_uid']
544
            ];
545
        }
546
        return $attributes;
547
    }
548
549
    /**
550
     * @return int
551
     */
552
    protected function getPreviewPid(): int
553
    {
554
        return (int)$this->record['sys_language_uid'] === 0 ? (int)$this->record['uid'] : (int)$this->record['l10n_parent'];
555
    }
556
557
    /**
558
     * Returns the view link
559
     *
560
     * @return string
561
     */
562
    protected function getViewLink(): string
563
    {
564
        $language = (int)$this->record['sys_language_uid'];
565
        $additionalParams = ($language > 0) ? '&L=' . $language : '';
566
567
        try {
568
            return BackendUtility::getPreviewUrl(
569
                $this->getPreviewPid(),
570
                '',
571
                null,
572
                '',
573
                '',
574
                $additionalParams
575
            );
576
        } catch (UnableToLinkToPageException $e) {
577
            return '';
578
        }
579
    }
580
581
    /**
582
     * Checks if user has access to this column
583
     * and the page doktype is lower than 200 (exclude sys_folder, ...)
584
     * and it contains given value
585
     *
586
     * @param string $fieldName
587
     * @param int $value
588
     * @return bool
589
     */
590
    protected function canBeToggled(string $fieldName, int $value): bool
591
    {
592
        if (!$this->languageAccess) {
593
            return false;
594
        }
595
        if (!empty($GLOBALS['TCA'][$this->table]['columns'][$fieldName]['exclude'])
596
            && $this->record['doktype'] <= PageRepository::DOKTYPE_SPACER
597
            && $this->backendUser->check('non_exclude_fields', $this->table . ':' . $fieldName)
598
        ) {
599
            return (int)$this->record[$fieldName] === $value;
600
        }
601
        return false;
602
    }
603
604
    /**
605
     * Returns true if a current user has access to the language of the record
606
     *
607
     * @see BackendUserAuthentication::checkLanguageAccess()
608
     * @return bool
609
     */
610
    protected function hasLanguageAccess(): bool
611
    {
612
        if ($this->backendUser->isAdmin()) {
613
            return true;
614
        }
615
        $languageField = $GLOBALS['TCA'][$this->table]['ctrl']['languageField'] ?? '';
616
        if ($languageField !== '' && isset($this->record[$languageField])) {
617
            return $this->backendUser->checkLanguageAccess((int)$this->record[$languageField]);
618
        }
619
        return true;
620
    }
621
622
    /**
623
     * Returns true if the page doktype is excluded
624
     *
625
     * @return bool
626
     */
627
    protected function isExcludedDoktype(): bool
628
    {
629
        $excludeDoktypes = [
630
            PageRepository::DOKTYPE_RECYCLER,
631
            PageRepository::DOKTYPE_SYSFOLDER,
632
            PageRepository::DOKTYPE_SPACER
633
        ];
634
635
        return in_array((int)($this->record['doktype'] ?? 0), $excludeDoktypes, true);
636
    }
637
}
638