[TASK] Use BE Routing / PSR-7 instead of BackendUtility::getModuleUrl
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / ContextMenu / ItemProviders / PageProvider.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Backend\ContextMenu\ItemProviders;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Backend\Routing\UriBuilder;
19 use TYPO3\CMS\Core\Type\Bitmask\Permission;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Context menu item provider for pages table
24 */
25 class PageProvider extends RecordProvider
26 {
27 /**
28 * @var string
29 */
30 protected $table = 'pages';
31
32 /**
33 * @var array
34 */
35 protected $itemsConfiguration = [
36 'view' => [
37 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.view',
38 'iconIdentifier' => 'actions-view-page',
39 'callbackAction' => 'viewRecord'
40 ],
41 'edit' => [
42 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit',
43 'iconIdentifier' => 'actions-page-open',
44 'callbackAction' => 'editRecord'
45 ],
46 'new' => [
47 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new',
48 'iconIdentifier' => 'actions-page-new',
49 'callbackAction' => 'newRecord'
50 ],
51 'info' => [
52 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info',
53 'iconIdentifier' => 'actions-document-info',
54 'callbackAction' => 'openInfoPopUp'
55 ],
56 'divider1' => [
57 'type' => 'divider'
58 ],
59 'copy' => [
60 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
61 'iconIdentifier' => 'actions-edit-copy',
62 'callbackAction' => 'copy'
63 ],
64 'copyRelease' => [
65 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
66 'iconIdentifier' => 'actions-edit-copy-release',
67 'callbackAction' => 'clipboardRelease'
68 ],
69 'cut' => [
70 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
71 'iconIdentifier' => 'actions-edit-cut',
72 'callbackAction' => 'cut'
73 ],
74 'cutRelease' => [
75 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cutrelease',
76 'iconIdentifier' => 'actions-edit-cut-release',
77 'callbackAction' => 'clipboardRelease'
78 ],
79 'pasteAfter' => [
80 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteafter',
81 'iconIdentifier' => 'actions-document-paste-after',
82 'callbackAction' => 'pasteAfter'
83 ],
84 'pasteInto' => [
85 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
86 'iconIdentifier' => 'actions-document-paste-into',
87 'callbackAction' => 'pasteInto'
88 ],
89 'divider2' => [
90 'type' => 'divider'
91 ],
92 'more' => [
93 'type' => 'submenu',
94 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.more',
95 'iconIdentifier' => '',
96 'callbackAction' => 'openSubmenu',
97 'childItems' => [
98 'newWizard' => [
99 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_newWizard',
100 'iconIdentifier' => 'actions-page-new',
101 'callbackAction' => 'newPageWizard',
102 ],
103 'pagesSort' => [
104 'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_sort.xlf:title',
105 'iconIdentifier' => 'actions-page-move',
106 'callbackAction' => 'pagesSort',
107 ],
108 'pagesNewMultiple' => [
109 'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_pages_new.xlf:title',
110 'iconIdentifier' => 'apps-pagetree-drag-move-between',
111 'callbackAction' => 'pagesNewMultiple',
112 ],
113 'openListModule' => [
114 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_db_list',
115 'iconIdentifier' => 'actions-system-list-open',
116 'callbackAction' => 'openListModule',
117 ],
118 'mountAsTreeRoot' => [
119 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.tempMountPoint',
120 'iconIdentifier' => 'actions-pagetree-mountroot',
121 'callbackAction' => 'mountAsTreeRoot',
122 ],
123 ],
124 ],
125 'divider3' => [
126 'type' => 'divider'
127 ],
128 'enable' => [
129 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enable',
130 'iconIdentifier' => 'actions-edit-unhide',
131 'callbackAction' => 'enableRecord',
132 ],
133 'disable' => [
134 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable',
135 'iconIdentifier' => 'actions-edit-hide',
136 'callbackAction' => 'disableRecord',
137 ],
138 'delete' => [
139 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete',
140 'iconIdentifier' => 'actions-edit-delete',
141 'callbackAction' => 'deleteRecord',
142 ],
143 'history' => [
144 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:CM_history',
145 'iconIdentifier' => 'actions-document-history-open',
146 'callbackAction' => 'openHistoryPopUp',
147 ],
148 'clearCache' => [
149 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.clear_cache',
150 'iconIdentifier' => 'actions-system-cache-clear',
151 'callbackAction' => 'clearCache',
152 ],
153 ];
154
155 /**
156 * Checks if the provider can add items to the menu
157 *
158 * @return bool
159 */
160 public function canHandle(): bool
161 {
162 return $this->table === 'pages';
163 }
164
165 /**
166 * @return int
167 */
168 public function getPriority(): int
169 {
170 return 100;
171 }
172
173 /**
174 * @param string $itemName
175 * @param string $type
176 * @return bool
177 */
178 protected function canRender(string $itemName, string $type): bool
179 {
180 if (in_array($type, ['divider', 'submenu'], true)) {
181 return true;
182 }
183 if (in_array($itemName, $this->disabledItems, true)) {
184 return false;
185 }
186 $canRender = false;
187 switch ($itemName) {
188 case 'view':
189 $canRender = $this->canBeViewed();
190 break;
191 case 'edit':
192 $canRender = $this->canBeEdited();
193 break;
194 case 'new':
195 case 'newWizard':
196 case 'pagesNewMultiple':
197 $canRender = $this->canBeCreated();
198 break;
199 case 'info':
200 $canRender = $this->canShowInfo();
201 break;
202 case 'enable':
203 $canRender = $this->canBeEnabled();
204 break;
205 case 'disable':
206 $canRender = $this->canBeDisabled();
207 break;
208 case 'delete':
209 $canRender = $this->canBeDeleted();
210 break;
211 case 'history':
212 $canRender = $this->canShowHistory();
213 break;
214 case 'openListModule':
215 $canRender = $this->canOpenListModule();
216 break;
217 case 'pagesSort':
218 $canRender = $this->canBeSorted();
219 break;
220 case 'mountAsTreeRoot':
221 $canRender = !$this->isRoot();
222 break;
223 case 'copy':
224 $canRender = $this->canBeCopied();
225 break;
226 case 'copyRelease':
227 $canRender = $this->isRecordInClipboard('copy');
228 break;
229 case 'cut':
230 $canRender = $this->canBeCut() && !$this->isRecordInClipboard('cut');
231 break;
232 case 'cutRelease':
233 $canRender = $this->isRecordInClipboard('cut');
234 break;
235 case 'pasteAfter':
236 $canRender = $this->canBePastedAfter();
237 break;
238 case 'pasteInto':
239 $canRender = $this->canBePastedInto();
240 break;
241 case 'clearCache':
242 $canRender = $this->canClearCache();
243 break;
244 }
245 return $canRender;
246 }
247
248 /**
249 * Saves calculated permissions for a page to speed things up
250 */
251 protected function initPermissions()
252 {
253 $this->pagePermissions = $this->backendUser->calcPerms($this->record);
254 }
255
256 /**
257 * Checks if the user may create pages below the given page
258 *
259 * @return bool
260 */
261 protected function canBeCreated(): bool
262 {
263 return $this->hasPagePermission(Permission::PAGE_NEW);
264 }
265
266 /**
267 * Checks if the user has editing rights
268 *
269 * @return bool
270 */
271 protected function canBeEdited(): bool
272 {
273 if ($this->isRoot()) {
274 return false;
275 }
276 if (isset($GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['readOnly']) {
277 return false;
278 }
279 if ($this->backendUser->isAdmin()) {
280 return true;
281 }
282 if (isset($GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) && $GLOBALS['TCA'][$this->table]['ctrl']['adminOnly']) {
283 return false;
284 }
285 return !$this->isRecordLocked() && $this->hasPagePermission(Permission::PAGE_EDIT);
286 }
287
288 /**
289 * Check if a page is locked
290 *
291 * @return bool
292 */
293 protected function isRecordLocked(): bool
294 {
295 return (bool)$this->record['editlock'];
296 }
297
298 /**
299 * Checks if the page is allowed to can be cut
300 *
301 * @return bool
302 */
303 protected function canBeCut(): bool
304 {
305 return !$this->isWebMount()
306 && $this->canBeEdited()
307 && !$this->isDeletePlaceholder();
308 }
309
310 /**
311 * Checks if the page is allowed to be copied
312 *
313 * @return bool
314 */
315 protected function canBeCopied(): bool
316 {
317 return !$this->isRoot()
318 && !$this->isWebMount()
319 && !$this->isRecordInClipboard('copy')
320 && $this->hasPagePermission(Permission::PAGE_SHOW)
321 && !$this->isDeletePlaceholder();
322 }
323
324 /**
325 * Checks if something can be pasted into the node
326 *
327 * @return bool
328 */
329 protected function canBePastedInto(): bool
330 {
331 $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
332
333 return $clipboardElementCount
334 && $this->canBeCreated()
335 && !$this->isDeletePlaceholder();
336 }
337
338 /**
339 * Checks if something can be pasted after the node
340 *
341 * @return bool
342 */
343 protected function canBePastedAfter(): bool
344 {
345 $clipboardElementCount = count($this->clipboard->elFromTable($this->table));
346 return $clipboardElementCount
347 && $this->canBeCreated()
348 && !$this->isDeletePlaceholder();
349 }
350
351 /**
352 * Check if sub pages of given page can be sorted
353 *
354 * @return bool
355 */
356 protected function canBeSorted(): bool
357 {
358 return $this->backendUser->check('tables_modify', $this->table)
359 && $this->hasPagePermission(Permission::CONTENT_EDIT)
360 && !$this->isDeletePlaceholder()
361 && $this->backendUser->workspace === 0;
362 }
363
364 /**
365 * Checks if the page is allowed to be removed
366 *
367 * @return bool
368 */
369 protected function canBeRemoved(): bool
370 {
371 return !$this->isDeletePlaceholder()
372 && !$this->isRecordLocked()
373 && $this->hasPagePermission(Permission::PAGE_DELETE);
374 }
375
376 /**
377 * Checks if the page is allowed to be viewed in frontend
378 *
379 * @return bool
380 */
381 protected function canBeViewed(): bool
382 {
383 return !$this->isRoot() && !$this->isDeleted();
384 }
385
386 /**
387 * Checks if the page is allowed to show info
388 *
389 * @return bool
390 */
391 protected function canShowInfo(): bool
392 {
393 return !$this->isRoot();
394 }
395
396 /**
397 * Checks if the user has clear cache rights
398 *
399 * @return bool
400 */
401 protected function canClearCache(): bool
402 {
403 return !$this->isRoot()
404 && ($this->backendUser->isAdmin() || $this->backendUser->getTSConfigVal('options.clearCache.pages'));
405 }
406
407 /**
408 * Determines whether this node is deleted.
409 *
410 * @return bool
411 */
412 protected function isDeleted(): bool
413 {
414 return !empty($this->record['deleted']) || $this->isDeletePlaceholder();
415 }
416
417 /**
418 * Returns true if current record is a root page
419 *
420 * @return bool
421 */
422 protected function isRoot()
423 {
424 return (int)$this->identifier === 0;
425 }
426
427 /**
428 * Returns true if current record is a web mount
429 *
430 * @return bool
431 */
432 protected function isWebMount()
433 {
434 return in_array($this->identifier, $this->backendUser->returnWebmounts());
435 }
436
437 /**
438 * @param string $itemName
439 * @return array
440 */
441 protected function getAdditionalAttributes(string $itemName): array
442 {
443 $attributes = [];
444 if ($itemName === 'view') {
445 $attributes += $this->getViewAdditionalAttributes();
446 }
447 if ($itemName === 'delete') {
448 $attributes += $this->getDeleteAdditionalAttributes();
449 }
450 if ($itemName === 'pasteInto') {
451 $attributes += $this->getPasteAdditionalAttributes('into');
452 }
453 if ($itemName === 'pasteAfter') {
454 $attributes += $this->getPasteAdditionalAttributes('after');
455 }
456 if ($itemName === 'pagesSort') {
457 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
458 $attributes += [
459 'data-pages-sort-url' => (string)$uriBuilder->buildUriFromRoute('pages_sort', ['id' => $this->record['uid']]),
460 ];
461 }
462 if ($itemName === 'pagesNewMultiple') {
463 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
464 $attributes += [
465 'data-pages-new-multiple-url' => (string)$uriBuilder->buildUriFromRoute('pages_new', ['id' => $this->record['uid']]),
466 ];
467 }
468 return $attributes;
469 }
470
471 /**
472 * @return int
473 */
474 protected function getPreviewPid(): int
475 {
476 return (int)$this->record['uid'];
477 }
478 }