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