1<?php 2declare(strict_types = 1); 3namespace 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 18use TYPO3\CMS\Backend\Utility\BackendUtility; 19use TYPO3\CMS\Core\Resource\File; 20use TYPO3\CMS\Core\Resource\Folder; 21use TYPO3\CMS\Core\Resource\ResourceFactory; 22use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation; 23use TYPO3\CMS\Core\Utility\GeneralUtility; 24 25/** 26 * Provides click menu items for files and folders 27 */ 28class 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:core/Resources/Private/Language/locallang_core.xlf:cm.edit', 41 'iconIdentifier' => 'actions-page-open', 42 'callbackAction' => 'editFile' 43 ], 44 'rename' => [ 45 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.rename', 46 'iconIdentifier' => 'actions-edit-rename', 47 'callbackAction' => 'renameFile' 48 ], 49 'upload' => [ 50 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.upload', 51 'iconIdentifier' => 'actions-edit-upload', 52 'callbackAction' => 'uploadFile' 53 ], 54 'new' => [ 55 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.new', 56 'iconIdentifier' => 'actions-document-new', 57 'callbackAction' => 'createFile' 58 ], 59 'info' => [ 60 'label' => 'LLL:EXT:core/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:core/Resources/Private/Language/locallang_core.xlf:cm.copy', 69 'iconIdentifier' => 'actions-edit-copy', 70 'callbackAction' => 'copyFile' 71 ], 72 'copyRelease' => [ 73 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.copy', 74 'iconIdentifier' => 'actions-edit-copy-release', 75 'callbackAction' => 'copyReleaseFile' 76 ], 77 'cut' => [ 78 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cut', 79 'iconIdentifier' => 'actions-edit-cut', 80 'callbackAction' => 'cutFile' 81 ], 82 'cutRelease' => [ 83 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.cutrelease', 84 'iconIdentifier' => 'actions-edit-cut-release', 85 'callbackAction' => 'cutReleaseFile' 86 ], 87 'pasteInto' => [ 88 'label' => 'LLL:EXT:core/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:core/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->record->checkActionPermission('copy') && !$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 { 266 return (!$fileOrFolderInClipBoard instanceof Folder) 267 || ( 268 $this->record->getStorage()->getRootLevelFolder()->getCombinedIdentifier() 269 == $fileOrFolderInClipBoard->getStorage()->getRootLevelFolder()->getCombinedIdentifier() 270 ); 271 } 272 273 /** 274 * Checks if a file record is in the "normal" pad of the clipboard 275 * 276 * @param string $mode "copy", "cut" or '' for any mode 277 * @return bool 278 */ 279 protected function isRecordInClipboard(string $mode = ''): bool 280 { 281 if ($mode !== '' && !$this->record->checkActionPermission($mode)) { 282 return false; 283 } 284 $isSelected = ''; 285 // Pseudo table name for use in the clipboard. 286 $table = '_FILE'; 287 $uid = GeneralUtility::shortMD5($this->record->getCombinedIdentifier()); 288 if ($this->clipboard->current === 'normal') { 289 $isSelected = $this->clipboard->isSelected($table, $uid); 290 } 291 return $mode === '' ? !empty($isSelected) : $isSelected === $mode; 292 } 293 294 /** 295 * @return bool 296 */ 297 protected function isStorageRoot(): bool 298 { 299 return $this->record->getIdentifier() === $this->record->getStorage()->getRootLevelFolder()->getIdentifier(); 300 } 301 302 /** 303 * @return bool 304 */ 305 protected function isFile(): bool 306 { 307 return $this->record instanceof File; 308 } 309 310 /** 311 * @return bool 312 */ 313 protected function isFolder(): bool 314 { 315 return $this->record instanceof Folder; 316 } 317 318 /** 319 * @param string $itemName 320 * @return array 321 */ 322 protected function getAdditionalAttributes(string $itemName): array 323 { 324 $attributes = [ 325 'data-callback-module' => 'TYPO3/CMS/Filelist/ContextMenuActions' 326 ]; 327 if ($itemName === 'delete' && $this->backendUser->jsConfirmation(JsConfirmation::DELETE)) { 328 $recordTitle = GeneralUtility::fixed_lgd_cs($this->record->getName(), $this->backendUser->uc['titleLen']); 329 330 $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:delete'); 331 $confirmMessage = sprintf( 332 $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.delete'), 333 $recordTitle 334 ); 335 if ($this->isFolder()) { 336 $confirmMessage .= BackendUtility::referenceCount( 337 '_FILE', 338 $this->record->getIdentifier(), 339 ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFolder') 340 ); 341 } else { 342 $confirmMessage .= BackendUtility::referenceCount( 343 'sys_file', 344 $this->record->getUid(), 345 ' ' . $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToFile') 346 ); 347 } 348 $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel'); 349 $deleteText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.delete'); 350 $attributes += [ 351 'data-title' => htmlspecialchars($title), 352 'data-message' => htmlspecialchars($confirmMessage), 353 'data-button-close-text' => htmlspecialchars($closeText), 354 'data-button-ok-text' => htmlspecialchars($deleteText), 355 ]; 356 } 357 if ($itemName === 'pasteInto' && $this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) { 358 $elArr = $this->clipboard->elFromTable('_FILE'); 359 $selItem = reset($elArr); 360 $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem); 361 362 $title = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:clip_paste'); 363 364 $confirmMessage = sprintf( 365 $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.' 366 . ($this->clipboard->currentMode() === 'copy' ? 'copy' : 'move') . '_into'), 367 $fileOrFolderInClipBoard->getName(), 368 $this->record->getName() 369 ); 370 $closeText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:button.cancel'); 371 $okLabel = $this->clipboard->currentMode() === 'copy' ? 'copy' : 'pasteinto'; 372 $okText = $this->languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.' . $okLabel); 373 $attributes += [ 374 'data-title' => htmlspecialchars($title), 375 'data-message' => htmlspecialchars($confirmMessage), 376 'data-button-close-text' => htmlspecialchars($closeText), 377 'data-button-ok-text' => htmlspecialchars($okText), 378 ]; 379 } 380 381 return $attributes; 382 } 383 384 /** 385 * @return string 386 */ 387 protected function getIdentifier(): string 388 { 389 return $this->record->getCombinedIdentifier(); 390 } 391} 392