[!!!][FEATURE] Refactor and streamline click menu / context menu
[Packages/TYPO3.CMS.git] / typo3 / sysext / filelist / Classes / ContextMenu / ItemProviders / FileProvider.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Filelist\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\Resource\File;
20 use TYPO3\CMS\Core\Resource\Folder;
21 use TYPO3\CMS\Core\Resource\ResourceFactory;
22 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25 /**
26 * Provides click menu items for files and folders
27 */
28 class FileProvider extends \TYPO3\CMS\Backend\ContextMenu\ItemProviders\AbstractProvider
29 {
30 /**
31 * @var File|Folder
32 */
33 protected $record;
34
35 /**
36 * @var array
37 */
38 protected $itemsConfiguration = [
39 'edit' => [
40 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.edit',
41 'iconIdentifier' => 'actions-open',
42 'callbackAction' => 'editFile'
43 ],
44 'rename' => [
45 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.rename',
46 'iconIdentifier' => 'actions-edit-rename',
47 'callbackAction' => 'renameFile'
48 ],
49 'upload' => [
50 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.upload',
51 'iconIdentifier' => 'actions-edit-upload',
52 'callbackAction' => 'uploadFile'
53 ],
54 'new' => [
55 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.new',
56 'iconIdentifier' => 'actions-document-new',
57 'callbackAction' => 'createFile'
58 ],
59 'info' => [
60 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.info',
61 'iconIdentifier' => 'actions-document-info',
62 'callbackAction' => 'openInfoPopUp'
63 ],
64 'divider' => [
65 'type' => 'divider'
66 ],
67 'copy' => [
68 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
69 'iconIdentifier' => 'actions-edit-copy',
70 'callbackAction' => 'copyFile'
71 ],
72 'copyRelease' => [
73 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.copy',
74 'iconIdentifier' => 'actions-edit-copy-release',
75 'callbackAction' => 'copyReleaseFile'
76 ],
77 'cut' => [
78 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
79 'iconIdentifier' => 'actions-edit-cut',
80 'callbackAction' => 'cutFile'
81 ],
82 'cutRelease' => [
83 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.cut',
84 'iconIdentifier' => 'actions-edit-cut-release',
85 'callbackAction' => 'cutReleaseFile'
86 ],
87 'pasteInto' => [
88 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.pasteinto',
89 'iconIdentifier' => 'actions-document-paste-into',
90 'callbackAction' => 'pasteFileInto'
91 ],
92 'divider2' => [
93 'type' => 'divider'
94 ],
95 'delete' => [
96 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:cm.delete',
97 'iconIdentifier' => 'actions-edit-delete',
98 'callbackAction' => 'deleteFile'
99 ],
100 ];
101
102 /**
103 * @return bool
104 */
105 public function canHandle(): bool
106 {
107 return $this->table === 'sys_file';
108 }
109
110 /**
111 * Initialize file object
112 */
113 protected function initialize()
114 {
115 parent::initialize();
116 $fileObject = ResourceFactory::getInstance()
117 ->retrieveFileOrFolderObject($this->identifier);
118 $this->record = $fileObject;
119 }
120
121 /**
122 * Checks whether certain item can be rendered (e.g. check for disabled items or permissions)
123 *
124 * @param string $itemName
125 * @param string $type
126 * @return bool
127 */
128 protected function canRender(string $itemName, string $type): bool
129 {
130 if (in_array($type, ['divider', 'submenu'], true)) {
131 return true;
132 }
133 if (in_array($itemName, $this->disabledItems, true)) {
134 return false;
135 }
136 $canRender = false;
137 switch ($itemName) {
138 //just for files
139 case 'edit':
140 $canRender = $this->canBeEdited();
141 break;
142 case 'info':
143 $canRender = $this->canShowInfo();
144 break;
145
146 //just for folders
147 case 'upload':
148 case 'new':
149 $canRender = $this->canCreateNew();
150 break;
151 case 'pasteInto':
152 $canRender = $this->canBePastedInto();
153 break;
154
155 //for both files and folders
156 case 'rename':
157 $canRender = $this->canBeRenamed();
158 break;
159 case 'copy':
160 $canRender = $this->canBeCopied();
161 break;
162 case 'copyRelease':
163 $canRender = $this->isRecordInClipboard('copy');
164 break;
165 case 'cut':
166 $canRender = $this->canBeCut();
167 break;
168 case 'cutRelease':
169 $canRender = $this->isRecordInClipboard('cut');
170 break;
171 case 'delete':
172 $canRender = $this->canBeDeleted();
173 break;
174 }
175 return $canRender;
176 }
177
178 /**
179 * @return bool
180 */
181 protected function canBeEdited(): bool
182 {
183 return $this->isFile()
184 && $this->record->checkActionPermission('write')
185 && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $this->record->getExtension());
186 }
187
188 /**
189 * @return bool
190 */
191 protected function canBeRenamed(): bool
192 {
193 return $this->record->checkActionPermission('rename');
194 }
195
196 /**
197 * @return bool
198 */
199 protected function canBeDeleted(): bool
200 {
201 return $this->record->checkActionPermission('delete');
202 }
203
204 /**
205 * @return bool
206 */
207 protected function canShowInfo(): bool
208 {
209 return $this->isFile();
210 }
211
212 /**
213 * @return bool
214 */
215 protected function canCreateNew(): bool
216 {
217 return $this->isFolder() && $this->record->checkActionPermission('write');
218 }
219
220 /**
221 * @return bool
222 */
223 protected function canBeCopied(): bool
224 {
225 return $this->record->checkActionPermission('read') && !$this->isRecordInClipboard('copy');
226 }
227
228 /**
229 * @return bool
230 */
231 protected function canBeCut(): bool
232 {
233 return $this->record->checkActionPermission('move') && !$this->isRecordInClipboard('cut');
234 }
235
236 /**
237 * @return bool
238 */
239 protected function canBePastedInto(): bool
240 {
241 $elArr = $this->clipboard->elFromTable('_FILE');
242 if (empty($elArr)) {
243 return false;
244 }
245 $selItem = reset($elArr);
246 $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem);
247
248 return $this->isFolder()
249 && $this->record->checkActionPermission('write')
250 && (
251 !$fileOrFolderInClipBoard instanceof Folder
252 || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $this->record)
253 )
254 && $this->isFoldersAreInTheSameRoot($fileOrFolderInClipBoard);
255 }
256
257 /**
258 * Checks if folder and record are in the same filemount
259 * Cannot copy folders between filemounts
260 *
261 * @param File|Folder $fileOrFolderInClipBoard
262 * @return bool
263 */
264 protected function isFoldersAreInTheSameRoot($fileOrFolderInClipBoard): bool {
265 return (!$fileOrFolderInClipBoard instanceof Folder)
266 || (
267 $this->record->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
268 == $fileOrFolderInClipBoard->getStorage()->getRootLevelFolder()->getCombinedIdentifier()
269 );
270 }
271
272 /**
273 * Checks if a file record is in the "normal" pad of the clipboard
274 *
275 * @param string $mode "copy", "cut" or '' for any mode
276 * @return bool
277 */
278 protected function isRecordInClipboard(string $mode = ''): bool
279 {
280 $isSelected = '';
281 // Pseudo table name for use in the clipboard.
282 $table = '_FILE';
283 $uid = GeneralUtility::shortMD5($this->record->getCombinedIdentifier());
284 if ($this->clipboard->current === 'normal') {
285 $isSelected = $this->clipboard->isSelected($table, $uid);
286 }
287 return $mode === '' ? !empty($isSelected) : $isSelected === $mode;
288 }
289
290 /**
291 * @return bool
292 */
293 protected function isStorageRoot(): bool
294 {
295 return $this->record->getIdentifier() === $this->record->getStorage()->getRootLevelFolder()->getIdentifier();
296 }
297
298 /**
299 * @return bool
300 */
301 protected function isFile(): bool
302 {
303 return $this->record instanceof File;
304 }
305
306 /**
307 * @return bool
308 */
309 protected function isFolder(): bool
310 {
311 return $this->record instanceof Folder;
312 }
313
314 /**
315 * @param string $itemName
316 * @return array
317 */
318 protected function getAdditionalAttributes(string $itemName): array
319 {
320 $attributes = [
321 'data-callback-module' => 'TYPO3/CMS/Filelist/ContextMenuActions'
322 ];
323 if ($itemName === 'delete' && $this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
324 $recordTitle = GeneralUtility::fixed_lgd_cs($this->record->getName(), $this->backendUser->uc['titleLen']);
325
326 $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:delete');
327 $confirmMessage = sprintf(
328 $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.delete'),
329 $recordTitle
330 );
331 if ($this->isFolder()) {
332 $confirmMessage .= BackendUtility::referenceCount(
333 '_FILE',
334 $this->record->getIdentifier(),
335 ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder')
336 );
337 } else {
338 $confirmMessage .= BackendUtility::referenceCount(
339 'sys_file',
340 $this->record->getUid(),
341 ' ' . $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile')
342 );
343 }
344 $attributes += [
345 'data-title' => htmlspecialchars($title),
346 'data-message' => htmlspecialchars($confirmMessage)
347 ];
348 }
349 if ($itemName === 'pasteInto' && $this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
350 $elArr = $this->clipboard->elFromTable('_FILE');
351 $selItem = reset($elArr);
352 $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem);
353
354 $title = $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste');
355
356 $confirmMessage = sprintf(
357 $this->languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.'
358 . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_into'),
359 $fileOrFolderInClipBoard->getName(),
360 $this->record->getName()
361 );
362 $attributes += [
363 'data-title' => htmlspecialchars($title),
364 'data-message' => htmlspecialchars($confirmMessage)
365 ];
366 }
367 return $attributes;
368 }
369
370 /**
371 * @return string
372 */
373 protected function getIdentifier(): string
374 {
375 return $this->record->getCombinedIdentifier();
376 }
377 }