diff --git a/typo3/sysext/backend/Classes/Clipboard/Clipboard.php b/typo3/sysext/backend/Classes/Clipboard/Clipboard.php index b6da2541b78a9b021c97f4061e19913aa2085153..1fba6e2775609d399c698710cabca65e01905bec 100644 --- a/typo3/sysext/backend/Classes/Clipboard/Clipboard.php +++ b/typo3/sysext/backend/Classes/Clipboard/Clipboard.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /* * This file is part of the TYPO3 CMS project. * @@ -43,11 +45,6 @@ use TYPO3\CMS\Fluid\View\StandaloneView; */ class Clipboard { - /** - * @var int - */ - public $numberTabs = 3; - /** * Clipboard data kept here * @@ -69,21 +66,15 @@ class Clipboard * * @var array */ - public $clipData = []; + public array $clipData = []; public bool $changed = false; - /** - * @var string - */ - public $current = ''; + public string $current = ''; public bool $lockToNormal = false; - /** - * If set, clipboard is displaying files. - */ - public bool $fileMode = false; + public int $numberOfPads = 3; protected IconFactory $iconFactory; protected UriBuilder $uriBuilder; @@ -104,21 +95,21 @@ class Clipboard /** * Initialize the clipboard from the be_user session */ - public function initializeClipboard() + public function initializeClipboard(): void { $userTsConfig = $this->getBackendUser()->getTSConfig(); // Get data $clipData = $this->getBackendUser()->getModuleData('clipboard', !empty($userTsConfig['options.']['saveClipboard']) ? '' : 'ses') ?: []; $clipData += ['normal' => []]; - $this->numberTabs = MathUtility::forceIntegerInRange((int)($userTsConfig['options.']['clipboardNumberPads'] ?? 3), 0, 20); + $this->numberOfPads = MathUtility::forceIntegerInRange((int)($userTsConfig['options.']['clipboardNumberPads'] ?? 3), 0, 20); // Resets/reinstates the clipboard pads $this->clipData['normal'] = is_array($clipData['normal']) ? $clipData['normal']: []; - for ($a = 1; $a <= $this->numberTabs; $a++) { + for ($a = 1; $a <= $this->numberOfPads; $a++) { $index = 'tab_' . $a; $this->clipData[$index] = is_iterable($clipData[$index] ?? null) ? $clipData[$index] : []; } // Setting the current pad pointer ($this->current)) - $current = $clipData['current'] ?? ''; + $current = (string)($clipData['current'] ?? ''); $this->current = isset($this->clipData[$current]) ? $current : 'normal'; $this->clipData['current'] = $this->current; } @@ -128,7 +119,7 @@ class Clipboard * Trying to switch pad through ->setCmd will not work. * This is used by the clickmenu since it only allows operation on single elements at a time (that is the "normal" pad) */ - public function lockToNormal() + public function lockToNormal(): void { $this->lockToNormal = true; $this->current = 'normal'; @@ -145,28 +136,28 @@ class Clipboard * * @param array $cmd Array of actions, see function description */ - public function setCmd($cmd) + public function setCmd(array $cmd): void { $cmd['el'] ??= []; $cmd['el'] = is_iterable($cmd['el']) ? $cmd['el'] : []; - foreach ($cmd['el'] as $k => $v) { + foreach ($cmd['el'] as $key => $value) { if ($this->current === 'normal') { unset($this->clipData['normal']); } - if ($v) { - $this->clipData[$this->current]['el'][$k] = $v; + if ($value) { + $this->clipData[$this->current]['el'][$key] = $value; } else { - $this->removeElement($k); + $this->removeElement((string)$key); } $this->changed = true; } // Change clipboard pad (if not locked to normal) if ($cmd['setP'] ?? false) { - $this->setCurrentPad($cmd['setP']); + $this->setCurrentPad((string)$cmd['setP']); } // Remove element (value = item ident: DB; '[tablename]|[uid]' FILE: '_FILE|[shortmd5 hash of path]' if ($cmd['remove'] ?? false) { - $this->removeElement($cmd['remove']); + $this->removeElement((string)$cmd['remove']); $this->changed = true; } // Remove all on current pad (value = pad-ident) @@ -186,10 +177,10 @@ class Clipboard * * @param string $padIdentifier Key in the array $this->clipData */ - public function setCurrentPad($padIdentifier) + public function setCurrentPad(string $padIdentifier): void { // Change clipboard pad (if not locked to normal) - if (!$this->lockToNormal && $this->current != $padIdentifier) { + if (!$this->lockToNormal && $this->current !== $padIdentifier) { if (isset($this->clipData[$padIdentifier])) { $this->clipData['current'] = ($this->current = $padIdentifier); } @@ -205,7 +196,7 @@ class Clipboard * Call this after initialization and setCmd in order to save the clipboard to the user session. * The function will check if the internal flag ->changed has been set and if so, save the clipboard. Else not. */ - public function endClipboard() + public function endClipboard(): void { if ($this->changed) { $this->saveClipboard(); @@ -218,17 +209,15 @@ class Clipboard * * @param array $CBarr Element array from outside ("key" => "selected/deselected") * @param string $table The 'table which is allowed'. Must be set. - * @param bool|int $removeDeselected Can be set in order to remove entries which are marked for deselection. + * @param bool $removeDeselected Can be set in order to remove entries which are marked for deselection. * @return array Processed input $CBarr */ - public function cleanUpCBC($CBarr, $table, $removeDeselected = 0) + public function cleanUpCBC(array $CBarr, string $table, bool $removeDeselected = false): array { - if (is_array($CBarr)) { - foreach ($CBarr as $k => $v) { - $p = explode('|', $k); - if ((string)$p[0] != (string)$table || $removeDeselected && !$v) { - unset($CBarr[$k]); - } + foreach ($CBarr as $reference => $value) { + $referenceTable = (string)(explode('|', $reference)[0] ?? ''); + if ($referenceTable !== $table || ($removeDeselected && !$value)) { + unset($CBarr[$reference]); } } return $CBarr; @@ -244,10 +233,11 @@ class Clipboard * * @return string HTML output * @throws \BadFunctionCallException + * @todo This should be a web component */ - public function printClipboard() + public function printClipboard(string $table = ''): string { - $elementCount = count($this->elFromTable($this->fileMode ? '_FILE' : '')); + $elementCount = count($this->elFromTable($table)); $view = $this->getStandaloneView(); // CopyMode Selector menu @@ -268,26 +258,26 @@ class Clipboard 'url' => GeneralUtility::linkThisScript(['CB' => ['setP' => 'normal']]), 'description' => 'labels.normal-description', 'label' => 'labels.normal', - 'padding' => $this->padTitle('normal') + 'padding' => $this->padTitle('normal', $table) ]; if ($this->current === 'normal') { $tabArray['normal']['content'] = $this->getContentFromTab('normal'); } // Print header and content for the NUMERIC tabs: - for ($a = 1; $a <= $this->numberTabs; $a++) { + for ($a = 1; $a <= $this->numberOfPads; $a++) { $tabArray['tab_' . $a] = [ 'id' => 'tab_' . $a, 'number' => $a, 'url' => GeneralUtility::linkThisScript(['CB' => ['setP' => 'tab_' . $a]]), 'description' => 'labels.cliptabs-description', 'label' => 'labels.cliptabs-name', - 'padding' => $this->padTitle('tab_' . $a) + 'padding' => $this->padTitle('tab_' . $a, $table) ]; if ($this->current === 'tab_' . $a) { $tabArray['tab_' . $a]['content'] = $this->getContentFromTab('tab_' . $a); } } - $view->assign('clipboardHeader', BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_clipboard', $this->clLabel('buttons.clipboard'))); + $view->assign('clipboardHeader', BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_clipboard', $this->clipboardLabel('buttons.clipboard'))); $view->assign('tabArray', $tabArray); return $view->render(); } @@ -295,95 +285,96 @@ class Clipboard /** * Print the content on a pad. Called from ->printClipboard() * - * @internal - * @param string $pad Pad reference + * @param string $padIdentifier Pad reference * @return array Array with table rows for the clipboard. */ - public function getContentFromTab($pad) + protected function getContentFromTab(string $padIdentifier): array { - $lines = []; - if (is_array($this->clipData[$pad]['el'] ?? false)) { - foreach ($this->clipData[$pad]['el'] as $k => $v) { - if ($v) { - [$table, $uid] = explode('|', $k); - // Rendering files/directories on the clipboard - if ($table === '_FILE') { - $fileObject = $this->resourceFactory->retrieveFileOrFolderObject($v); - if ($fileObject) { - $thumb = []; - $folder = $fileObject instanceof Folder; - $size = $folder ? '' : '(' . GeneralUtility::formatSize((int)$fileObject->getSize()) . 'bytes)'; - /** @var File $fileObject */ - if (!$folder && $fileObject->isImage()) { - $processedFile = $fileObject->process( - ProcessedFile::CONTEXT_IMAGEPREVIEW, - [ - 'width' => 64, - 'height' => 64, - ] - ); - - $thumb = '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl() ?? '')) . '" ' . - 'width="' . htmlspecialchars($processedFile->getProperty('width')) . '" ' . - 'height="' . htmlspecialchars($processedFile->getProperty('height')) . '" ' . - 'title="' . htmlspecialchars($processedFile->getName()) . '" alt="" />'; - } - $lines[] = [ - 'icon' => '<span title="' . htmlspecialchars($fileObject->getName() . ' ' . $size) . '">' . $this->iconFactory->getIconForResource( - $fileObject, - Icon::SIZE_SMALL - )->render() . '</span>', - 'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs( - $fileObject->getName(), - $this->getBackendUser()->uc['titleLen'] - )), $fileObject->getName()), - 'thumb' => $thumb, - 'infoDataDispatch' => [ - 'action' => 'TYPO3.InfoWindow.showItem', - 'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, $v], false), - ], - 'removeLink' => $this->removeUrl('_FILE', GeneralUtility::shortMD5($v)) - ]; - } else { - // If the file did not exist (or is illegal) then it is removed from the clipboard immediately: - unset($this->clipData[$pad]['el'][$k]); - $this->changed = true; + if (!is_array($this->clipData[$padIdentifier]['el'] ?? false)) { + return []; + } + + $records = []; + foreach ($this->clipData[$padIdentifier]['el'] as $reference => $value) { + if ($value) { + [$table, $uid] = explode('|', $reference); + // Rendering files/directories on the clipboard + if ($table === '_FILE') { + $fileObject = $this->resourceFactory->retrieveFileOrFolderObject($value); + if ($fileObject) { + $thumb = []; + $folder = $fileObject instanceof Folder; + $size = $folder ? '' : '(' . GeneralUtility::formatSize((int)$fileObject->getSize()) . 'bytes)'; + /** @var File $fileObject */ + if (!$folder && $fileObject->isImage()) { + $processedFile = $fileObject->process( + ProcessedFile::CONTEXT_IMAGEPREVIEW, + [ + 'width' => 64, + 'height' => 64, + ] + ); + + $thumb = '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl() ?? '')) . '" ' . + 'width="' . htmlspecialchars((string)$processedFile->getProperty('width')) . '" ' . + 'height="' . htmlspecialchars((string)$processedFile->getProperty('height')) . '" ' . + 'title="' . htmlspecialchars($processedFile->getName()) . '" alt="" />'; } + $records[] = [ + 'icon' => '<span title="' . htmlspecialchars($fileObject->getName() . ' ' . $size) . '">' . $this->iconFactory->getIconForResource( + $fileObject, + Icon::SIZE_SMALL + )->render() . '</span>', + 'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs( + $fileObject->getName(), + $this->getBackendUser()->uc['titleLen'] + )), $fileObject->getName()), + 'thumb' => $thumb, + 'infoDataDispatch' => [ + 'action' => 'TYPO3.InfoWindow.showItem', + 'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, $value], false), + ], + 'removeLink' => $this->removeUrl('_FILE', GeneralUtility::shortMD5($value)) + ]; } else { - // Rendering records: - $rec = BackendUtility::getRecordWSOL($table, (int)$uid); - if (is_array($rec)) { - $lines[] = [ - 'icon' => $this->linkItemText($this->iconFactory->getIconForRecord( - $table, - $rec, - Icon::SIZE_SMALL - )->render(), $rec, $table), - 'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle( - $table, - $rec - ), $this->getBackendUser()->uc['titleLen'])), $rec, $table), - 'infoDataDispatch' => [ - 'action' => 'TYPO3.InfoWindow.showItem', - 'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, (int)$uid], false), - ], - 'removeLink' => $this->removeUrl($table, $uid) - ]; - - $localizationData = $this->getLocalizations($table, $rec); - if (!empty($localizationData)) { - $lines = array_merge($lines, $localizationData); - } - } else { - unset($this->clipData[$pad]['el'][$k]); - $this->changed = true; + // If the file did not exist (or is illegal) then it is removed from the clipboard immediately: + unset($this->clipData[$padIdentifier]['el'][$reference]); + $this->changed = true; + } + } else { + // Rendering records: + $record = BackendUtility::getRecordWSOL($table, (int)$uid); + if (is_array($record)) { + $records[] = [ + 'icon' => $this->linkItemText($this->iconFactory->getIconForRecord( + $table, + $record, + Icon::SIZE_SMALL + )->render(), $record, $table), + 'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle( + $table, + $record + ) ?? '', $this->getBackendUser()->uc['titleLen'])), $record, $table), + 'infoDataDispatch' => [ + 'action' => 'TYPO3.InfoWindow.showItem', + 'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, (int)$uid], false), + ], + 'removeLink' => $this->removeUrl($table, $uid) + ]; + + $localizationData = $this->getLocalizations($table, $record); + if (!empty($localizationData)) { + $records = array_merge($records, $localizationData); } + } else { + unset($this->clipData[$padIdentifier]['el'][$reference]); + $this->changed = true; } } } } $this->endClipboard(); - return $lines; + return $records; } /** @@ -391,7 +382,7 @@ class Clipboard * * @return bool */ - public function hasElements() + public function hasElements(): bool { foreach ($this->clipData as $data) { if (isset($data['el']) && is_array($data['el']) && !empty($data['el'])) { @@ -405,97 +396,99 @@ class Clipboard * Gets all localizations of the current record. * * @param string $table The table - * @param array $parentRec The current record + * @param array $parentRecord The parent record * @return array HTML table rows + * @todo This should be protected (However, still called in ClipboardTest) */ - public function getLocalizations($table, $parentRec) + public function getLocalizations(string $table, array $parentRecord): array { - $lines = []; + if (!BackendUtility::isTableLocalizable($table)) { + return []; + } + + $records = []; $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl']; - $workspaceId = (int)$this->getBackendUser()->workspace; - - if (BackendUtility::isTableLocalizable($table)) { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); - $queryBuilder->getRestrictions() - ->removeAll() - ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); - - $queryBuilder - ->select('*') - ->from($table) - ->where( - $queryBuilder->expr()->eq( - $tcaCtrl['transOrigPointerField'], - $queryBuilder->createNamedParameter($parentRec['uid'], \PDO::PARAM_INT) - ), - $queryBuilder->expr()->neq( - $tcaCtrl['languageField'], - $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) - ), - $queryBuilder->expr()->gt( - 'pid', - $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) - ) + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $queryBuilder + ->select('*') + ->from($table) + ->where( + $queryBuilder->expr()->eq( + $tcaCtrl['transOrigPointerField'], + $queryBuilder->createNamedParameter((int)$parentRecord['uid'], \PDO::PARAM_INT) + ), + $queryBuilder->expr()->neq( + $tcaCtrl['languageField'], + $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) + ), + $queryBuilder->expr()->gt( + 'pid', + $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) ) - ->orderBy($tcaCtrl['languageField']); + ) + ->orderBy($tcaCtrl['languageField']); - if (BackendUtility::isTableWorkspaceEnabled($table)) { - $queryBuilder->getRestrictions()->add( - GeneralUtility::makeInstance(WorkspaceRestriction::class, $workspaceId) - ); - } - $rows = $queryBuilder->execute()->fetchAllAssociative(); - if (is_array($rows)) { - foreach ($rows as $rec) { - $lines[] = [ - 'icon' => $this->iconFactory->getIconForRecord($table, $rec, Icon::SIZE_SMALL)->render(), - 'title' => htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $rec), $this->getBackendUser()->uc['titleLen'])) - ]; - } - } + if (BackendUtility::isTableWorkspaceEnabled($table)) { + $queryBuilder->getRestrictions()->add( + GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->getBackendUser()->workspace) + ); } - return $lines; + + foreach ($queryBuilder->execute()->fetchAllAssociative() as $record) { + $records[] = [ + 'icon' => $this->iconFactory->getIconForRecord($table, $record, Icon::SIZE_SMALL)->render(), + 'title' => htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $record), $this->getBackendUser()->uc['titleLen'])) + ]; + } + + return $records; } /** * Warps title with number of elements if any. * - * @param string $pad Pad reference + * @param string $padIdentifier Identifier for the clipboard pad + * @param string $table The table name to count for elements * @return string padding */ - public function padTitle($pad) + protected function padTitle(string $padIdentifier, string $table = ''): string { - $el = count($this->elFromTable($this->fileMode ? '_FILE' : '', $pad)); - if ($el) { - return ' (' . ($pad === 'normal' ? (($this->clipData['normal']['mode'] ?? '') === 'copy' ? $this->clLabel('cm.copy') : $this->clLabel('cm.cut')) : htmlspecialchars((string)$el)) . ')'; + $el = count($this->elFromTable($table, $padIdentifier)); + if (!$el) { + return ''; } - return ''; + $modeLabel = ($this->clipData['normal']['mode'] ?? '') === 'copy' ? $this->clipboardLabel('cm.copy') : $this->clipboardLabel('cm.cut'); + return ' (' . ($padIdentifier === 'normal' ? $modeLabel : htmlspecialchars((string)$el)) . ')'; } /** * Wraps the title of the items listed in link-tags. The items will link to the page/folder where they originate from * - * @param string $str Title of element - must be htmlspecialchar'ed on beforehand. - * @param mixed $rec If array, a record is expected. If string, its a path + * @param string $itemText Title of element - must be htmlspecialchar'ed on beforehand. + * @param array|string $reference If array, a record is expected. If string, its a path * @param string $table Table name * @return string */ - public function linkItemText($str, $rec, $table = '') + protected function linkItemText(string $itemText, $reference, string $table = ''): string { - if (is_array($rec) && $table) { - if ($this->fileMode) { - $str = '<span class="text-muted">' . $str . '</span>'; + if (is_array($reference) && $table !== '') { + if ($table === '_FILE') { + $itemText = '<span class="text-muted">' . $itemText . '</span>'; } else { - $str = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('web_list', ['id' => $rec['pid']])) . '">' . $str . '</a>'; + $itemText = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('web_list', ['id' => $reference['pid']])) . '">' . $itemText . '</a>'; } - } elseif (file_exists($rec)) { - if (!$this->fileMode) { - $str = '<span class="text-muted">' . $str . '</span>'; + } elseif (is_string($reference) && file_exists($reference)) { + if ($table !== '_FILE') { + $itemText = '<span class="text-muted">' . $itemText . '</span>'; } elseif (ExtensionManagementUtility::isLoaded('filelist')) { - $str = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('file_list', ['id' => PathUtility::dirname($rec)])) . '">' . $str . '</a>'; + $itemText = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('file_list', ['id' => PathUtility::dirname($reference)])) . '">' . $itemText . '</a>'; } } - return $str; + return $itemText; } /** @@ -503,19 +496,25 @@ class Clipboard * * @param string $table Table name * @param int $uid Uid of record - * @param bool|int $copy If set, copymode will be enabled - * @param bool|int $deselect If set, the link will deselect, otherwise select. - * @param array $baseArray The base array of GET vars to be sent in addition. Notice that current GET vars WILL automatically be included. + * @param bool $copy If set, copymode will be enabled + * @param bool $deselect If set, the link will deselect, otherwise select. + * @param array $getParameters The base array of GET parameters to be sent in addition. + * Notice that current GET vars WILL automatically be included. * @return string URL linking to the current script but with the CB array set to select the element with table/uid */ - public function selUrlDB($table, $uid, $copy = 0, $deselect = 0, $baseArray = []) - { + public function selUrlDB( + string $table, + int $uid, + bool $copy = false, + bool $deselect = false, + array $getParameters = [] + ): string { $CB = ['el' => [rawurlencode($table . '|' . $uid) => $deselect ? 0 : 1]]; if ($copy) { $CB['setCopyMode'] = 1; } - $baseArray['CB'] = $CB; - return GeneralUtility::linkThisScript($baseArray); + $getParameters['CB'] = $CB; + return GeneralUtility::linkThisScript($getParameters); } /** @@ -526,7 +525,7 @@ class Clipboard * @param bool $deselect If set, the link will deselect, otherwise select. * @return string URL linking to the current script but with the CB array set to select the path */ - public function selUrlFile($path, $copy = false, $deselect = false) + public function selUrlFile(string $path, bool $copy = false, bool $deselect = false): string { $CB = [ 'el' => [ @@ -545,22 +544,25 @@ class Clipboard * The URL will point to tce_file or tce_db depending in $table * * @param string $table Tablename (_FILE for files) - * @param mixed $uid "destination": can be positive or negative indicating how the paste is done (paste into / paste after) + * @param string|int $identifier "destination": can be positive or negative indicating how the paste is done + * (paste into / paste after). For files, this is the combined identifier. * @param bool $setRedirect If set, then the redirect URL will point back to the current script, but with CB reset. * @param array|null $update Additional key/value pairs which should get set in the moved/copied record (via DataHandler) * @return string */ - public function pasteUrl($table, $uid, $setRedirect = true, array $update = null) + public function pasteUrl(string $table, $identifier, bool $setRedirect = true, array $update = null): string { $urlParameters = [ - 'CB[paste]' => $table . '|' . $uid, - 'CB[pad]' => $this->current + 'CB' => [ + 'paste' => $table . '|' . $identifier, + 'pad' => $this->current + ] ]; if ($setRedirect) { $urlParameters['redirect'] = GeneralUtility::linkThisScript(['CB' => '']); } if (is_array($update)) { - $urlParameters['CB[update]'] = $update; + $urlParameters['CB']['update'] = $update; } return (string)$this->uriBuilder->buildUriFromRoute($table === '_FILE' ? 'tce_file' : 'tce_db', $urlParameters); } @@ -570,74 +572,82 @@ class Clipboard * for file $table='_FILE' and $uid = shortmd5 hash of path * * @param string $table Tablename - * @param string $uid Uid integer/shortmd5 hash + * @param string $identifier Either the records uid or the shortmd5 hash for files * @return string URL */ - public function removeUrl($table, $uid) + protected function removeUrl(string $table, string $identifier): string { - return GeneralUtility::linkThisScript(['CB' => ['remove' => $table . '|' . $uid]]); + return GeneralUtility::linkThisScript(['CB' => ['remove' => $table . '|' . $identifier]]); } /** * Returns confirm JavaScript message * * @param string $table Table name - * @param mixed $rec For records its an array, for files its a string (path) + * @param array|string $reference For records its an array, for files its a string (path) * @param string $type Type-code - * @param array $clElements Array of selected elements + * @param array $selectedElements Array of selected elements * @param string $columnLabel Name of the content column * @return string the text for a confirm message */ - public function confirmMsgText($table, $rec, $type, $clElements, $columnLabel = '') - { - if ($this->getBackendUser()->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) { - $labelKey = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.' . ($this->currentMode() === 'copy' ? 'copy' : 'move') . ($this->current === 'normal' ? '' : 'cb') . '_' . $type; - $msg = $this->getLanguageService()->sL($labelKey . ($columnLabel ? '_colPos' : '')); - if ($table === '_FILE') { - $thisRecTitle = PathUtility::basename($rec); - if ($this->current === 'normal') { - $selItem = reset($clElements); - $selRecTitle = PathUtility::basename($selItem); - } else { - $selRecTitle = count($clElements); - } + public function confirmMsgText( + string $table, + $reference, + string $type, + array $selectedElements, + string $columnLabel = '' + ): string { + if (!$this->getBackendUser()->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) { + return ''; + } + + $labelKey = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.' + . ($this->currentMode() === 'copy' ? 'copy' : 'move') + . ($this->current === 'normal' ? '' : 'cb') . '_' . $type; + $confirmationMessage = $this->getLanguageService()->sL($labelKey . ($columnLabel ? '_colPos' : '')); + + if ($table === '_FILE' && is_string($reference)) { + $recordTitle = PathUtility::basename($reference); + if ($this->current === 'normal') { + $selectedItem = reset($selectedElements); + $selectedRecordTitle = PathUtility::basename($selectedItem); } else { - $thisRecTitle = $table === 'pages' && !is_array($rec) ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : BackendUtility::getRecordTitle($table, $rec); - if ($this->current === 'normal') { - $selItem = $this->getSelectedRecord(); - $selRecTitle = $selItem['_RECORD_TITLE']; - } else { - $selRecTitle = count($clElements); - } - } - // @TODO - // This can get removed as soon as the "_colPos" label is translated - // into all available locallang languages. - if (!$msg && $columnLabel) { - $thisRecTitle .= ' | ' . $columnLabel; - $msg = $this->getLanguageService()->sL($labelKey); + $selectedRecordTitle = count($selectedElements); } - - // Message - $conf = sprintf( - $msg, - GeneralUtility::fixed_lgd_cs($selRecTitle, 30), - GeneralUtility::fixed_lgd_cs($thisRecTitle, 30), - GeneralUtility::fixed_lgd_cs($columnLabel, 30) - ); } else { - $conf = ''; + $recordTitle = $table !== 'pages' && is_array($reference) + ? BackendUtility::getRecordTitle($table, $reference) + : $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']; + if ($this->current === 'normal') { + $selectedItem = $this->getSelectedRecord(); + $selectedRecordTitle = $selectedItem['_RECORD_TITLE']; + } else { + $selectedRecordTitle = count($selectedElements); + } } - return $conf; + // @TODO + // This can get removed as soon as the "_colPos" label is translated + // into all available locallang languages. + if (!$confirmationMessage && $columnLabel) { + $recordTitle .= ' | ' . $columnLabel; + $confirmationMessage = $this->getLanguageService()->sL($labelKey); + } + + return sprintf( + $confirmationMessage, + GeneralUtility::fixed_lgd_cs($selectedRecordTitle, 30), + GeneralUtility::fixed_lgd_cs($recordTitle, 30), + GeneralUtility::fixed_lgd_cs($columnLabel, 30) + ); } /** * Clipboard label - getting from "EXT:core/Resources/Private/Language/locallang_core.xlf:" * * @param string $key Label Key - * @return string + * @return string htmspecialchared' label */ - public function clLabel($key) + protected function clipboardLabel(string $key): string { return htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:' . $key)); } @@ -650,21 +660,19 @@ class Clipboard /** * Removes element on clipboard * - * @param string $el Key of element in ->clipData array + * @param string $elementKey Key of element in ->clipData array */ - public function removeElement($el) + public function removeElement(string $elementKey): void { - unset($this->clipData[$this->current]['el'][$el]); + unset($this->clipData[$this->current]['el'][$elementKey]); $this->changed = true; } /** * Saves the clipboard, no questions asked. * Use ->endClipboard normally (as it checks if changes has been done so saving is necessary) - * - * @internal */ - public function saveClipboard() + protected function saveClipboard(): void { $this->getBackendUser()->pushModuleData('clipboard', $this->clipData); } @@ -674,7 +682,7 @@ class Clipboard * * @return string "copy" or "cut */ - public function currentMode() + public function currentMode(): string { return ($this->clipData[$this->current]['mode'] ?? '') === 'copy' ? 'copy' : 'cut'; } @@ -683,28 +691,28 @@ class Clipboard * This traverses the elements on the current clipboard pane * and unsets elements which does not exist anymore or are disabled. */ - public function cleanCurrent() + public function cleanCurrent(): void { - if (is_array($this->clipData[$this->current]['el'] ?? false)) { - foreach ($this->clipData[$this->current]['el'] as $k => $v) { - [$table, $uid] = explode('|', $k); - if ($table !== '_FILE') { - if (!$v || !is_array(BackendUtility::getRecord($table, (int)$uid, 'uid'))) { - unset($this->clipData[$this->current]['el'][$k]); - $this->changed = true; - } - } else { - if (!$v) { - unset($this->clipData[$this->current]['el'][$k]); - $this->changed = true; - } else { - try { - $this->resourceFactory->retrieveFileOrFolderObject($v); - } catch (ResourceDoesNotExistException $e) { - // The file has been deleted in the meantime, so just remove it silently - unset($this->clipData[$this->current]['el'][$k]); - } - } + if (!is_array($this->clipData[$this->current]['el'] ?? false)) { + return; + } + + foreach ($this->clipData[$this->current]['el'] as $reference => $value) { + [$table, $uid] = explode('|', $reference); + if ($table !== '_FILE') { + if (!$value || !is_array(BackendUtility::getRecord($table, (int)$uid, 'uid'))) { + unset($this->clipData[$this->current]['el'][$reference]); + $this->changed = true; + } + } elseif (!$value) { + unset($this->clipData[$this->current]['el'][$reference]); + $this->changed = true; + } else { + try { + $this->resourceFactory->retrieveFileOrFolderObject($value); + } catch (ResourceDoesNotExistException $e) { + // The file has been deleted in the meantime, so just remove it silently + unset($this->clipData[$this->current]['el'][$reference]); } } } @@ -714,44 +722,48 @@ class Clipboard * Counts the number of elements from the table $matchTable. If $matchTable is blank, all tables (except '_FILE' of course) is counted. * * @param string $matchTable Table to match/count for. - * @param string $pad Can optionally be used to set another pad than the current. + * @param string $padIdentifier Can optionally be used to set another pad than the current. * @return array Array with keys from the CB. */ - public function elFromTable($matchTable = '', $pad = '') + public function elFromTable(string $matchTable = '', string $padIdentifier = ''): array { - $pad = $pad ?: $this->current; - $list = []; - if (is_array($this->clipData[$pad]['el'] ?? false)) { - foreach ($this->clipData[$pad]['el'] as $k => $v) { - if ($v) { - [$table, $uid] = explode('|', $k); - if ($table !== '_FILE') { - if ((!$matchTable || (string)$table == (string)$matchTable) && $GLOBALS['TCA'][$table]) { - $list[$k] = $pad === 'normal' ? $v : $uid; - } - } else { - if ((string)$table == (string)$matchTable) { - $list[$k] = $v; - } - } + $padIdentifier = $padIdentifier ?: $this->current; + + if (!is_array($this->clipData[$padIdentifier]['el'] ?? false)) { + return []; + } + + $elements = []; + foreach ($this->clipData[$padIdentifier]['el'] as $reference => $value) { + if (!$value) { + continue; + } + [$table, $uid] = explode('|', $reference); + if ($table !== '_FILE') { + if ((!$matchTable || $table === $matchTable) && ($GLOBALS['TCA'][$table] ?? false)) { + $elements[$reference] = $padIdentifier === 'normal' ? $value : $uid; } + } elseif ($table === $matchTable) { + $elements[$reference] = $value; } } - return $list; + return $elements; } /** * Verifies if the item $table/$uid is on the current pad. - * If the pad is "normal" and the element exists, the mode value is returned. Thus you'll know if the item was copied or cut. + * If the pad is "normal" and the element exists, the mode value is returned. + * Thus you'll know if the item was copied or cut. * * @param string $table Table name, (_FILE for files...) - * @param int $uid Element uid (path for files) - * @return string + * @param string|int $identifier Either the records' uid or a filepath + * @return string If selected the current mode is returned, otherwise an empty string */ - public function isSelected($table, $uid) + public function isSelected(string $table, $identifier): string { - $k = $table . '|' . $uid; - return !empty($this->clipData[$this->current]['el'][$k]) ? ($this->current === 'normal' ? $this->currentMode() : 1) : ''; + $key = $table . '|' . $identifier; + $mode = $this->current === 'normal' ? $this->currentMode() : 'any'; + return !empty($this->clipData[$this->current]['el'][$key]) ? $mode : ''; } /** @@ -760,17 +772,17 @@ class Clipboard * * @return array Element record with extra field _RECORD_TITLE set to the title of the record */ - public function getSelectedRecord() + public function getSelectedRecord(): array { - $elArr = $this->elFromTable(''); - reset($elArr); - [$table, $uid] = explode('|', (string)key($elArr)); - if ($this->isSelected($table, (int)$uid)) { - $selRec = BackendUtility::getRecordWSOL($table, (int)$uid); - $selRec['_RECORD_TITLE'] = BackendUtility::getRecordTitle($table, $selRec); - return $selRec; - } - return []; + $elements = $this->elFromTable(); + reset($elements); + [$table, $uid] = explode('|', (string)key($elements)); + if (!$this->isSelected($table, (int)$uid)) { + return []; + } + $selectedRecord = BackendUtility::getRecordWSOL($table, (int)$uid); + $selectedRecord['_RECORD_TITLE'] = BackendUtility::getRecordTitle($table, $selectedRecord); + return $selectedRecord; } /** @@ -778,139 +790,17 @@ class Clipboard * * @return bool TRUE if elements exist. */ - public function isElements() + protected function isElements(): bool { return is_array($this->clipData[$this->current]['el'] ?? null) && !empty($this->clipData[$this->current]['el']); } - /** - * Applies the proper paste configuration in the $cmd array send to SimpleDataHandlerController (tce_db route) - * $ref is the target, see description below. - * The current pad is pasted - * - * $ref: [tablename]:[paste-uid]. - * Tablename is the name of the table from which elements *on the current clipboard* is pasted with the 'pid' paste-uid. - * No tablename means that all items on the clipboard (non-files) are pasted. This requires paste-uid to be positive though. - * so 'tt_content:-3' means 'paste tt_content elements on the clipboard to AFTER tt_content:3 record - * 'tt_content:30' means 'paste tt_content elements on the clipboard into page with id 30 - * ':30' means 'paste ALL database elements on the clipboard into page with id 30 - * ':-30' not valid. - * - * @param string $ref [tablename]:[paste-uid], see description - * @param array $CMD Command-array - * @param array|null $update If additional values should get set in the copied/moved record this will be an array containing key=>value pairs - * @return array Modified Command-array - */ - public function makePasteCmdArray($ref, $CMD, array $update = null) - { - [$pTable, $pUid] = explode('|', $ref); - $pUid = (int)$pUid; - // pUid must be set and if pTable is not set (that means paste ALL elements) - // the uid MUST be positive/zero (pointing to page id) - if ($pTable || $pUid >= 0) { - $elements = $this->elFromTable($pTable); - // So the order is preserved. - $elements = array_reverse($elements); - $mode = $this->currentMode() === 'copy' ? 'copy' : 'move'; - // Traverse elements and make CMD array - foreach ($elements as $tP => $value) { - [$table, $uid] = explode('|', $tP); - if (!is_array($CMD[$table])) { - $CMD[$table] = []; - } - if (is_array($update)) { - $CMD[$table][$uid][$mode] = [ - 'action' => 'paste', - 'target' => $pUid, - 'update' => $update, - ]; - } else { - $CMD[$table][$uid][$mode] = $pUid; - } - if ($mode === 'move') { - $this->removeElement($tP); - } - } - $this->endClipboard(); - } - return $CMD; - } - - /** - * Delete record entries in CMD array - * - * @param array $CMD Command-array - * @return array Modified Command-array - */ - public function makeDeleteCmdArray($CMD) - { - // all records - $elements = $this->elFromTable(''); - foreach ($elements as $tP => $value) { - [$table, $uid] = explode('|', $tP); - if (!is_array($CMD[$table])) { - $CMD[$table] = []; - } - $CMD[$table][$uid]['delete'] = 1; - $this->removeElement($tP); - } - $this->endClipboard(); - return $CMD; - } - - /***************************************** - * - * FOR USE IN tce_file.php: - * - ****************************************/ - /** - * Applies the proper paste configuration in the $file array send to tce_file.php. - * The current pad is pasted - * - * @param string $ref Reference to element (splitted by "|") - * @param array $FILE Command-array - * @return array Modified Command-array - */ - public function makePasteCmdArray_file($ref, $FILE) - { - $pUid = explode('|', $ref)[1]; - $elements = $this->elFromTable('_FILE'); - $mode = $this->currentMode() === 'copy' ? 'copy' : 'move'; - // Traverse elements and make CMD array - foreach ($elements as $tP => $path) { - $FILE[$mode][] = ['data' => $path, 'target' => $pUid]; - if ($mode === 'move') { - $this->removeElement($tP); - } - } - $this->endClipboard(); - return $FILE; - } - - /** - * Delete files in CMD array - * - * @param array $FILE Command-array - * @return array Modified Command-array - */ - public function makeDeleteCmdArray_file($FILE) - { - $elements = $this->elFromTable('_FILE'); - // Traverse elements and make CMD array - foreach ($elements as $tP => $path) { - $FILE['delete'][] = ['data' => $path]; - $this->removeElement($tP); - } - $this->endClipboard(); - return $FILE; - } - /** * Returns LanguageService * * @return LanguageService */ - protected function getLanguageService() + protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } @@ -920,7 +810,7 @@ class Clipboard * * @return BackendUserAuthentication */ - protected function getBackendUser() + protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } diff --git a/typo3/sysext/backend/Classes/Controller/File/FileController.php b/typo3/sysext/backend/Classes/Controller/File/FileController.php index 3703dc19e5377d34676901002f2f1e5ea3dc10b3..20214cc67d8d7eae25a6b6f7263de620a25e54ec 100644 --- a/typo3/sysext/backend/Classes/Controller/File/FileController.php +++ b/typo3/sysext/backend/Classes/Controller/File/FileController.php @@ -215,9 +215,9 @@ class FileController // Set the GPvars from outside $parsedBody = $request->getParsedBody(); $queryParams = $request->getQueryParams(); - $this->file = $parsedBody['data'] ?? $queryParams['data'] ?? null; - $redirectUrl = $parsedBody['redirect'] ?? $queryParams['redirect'] ?? ''; - if ($this->file === null || !empty($redirectUrl)) { + $this->file = (array)($parsedBody['data'] ?? $queryParams['data'] ?? []); + $redirectUrl = (string)($parsedBody['redirect'] ?? $queryParams['redirect'] ?? ''); + if ($this->file === [] || $redirectUrl !== '') { // This in clipboard mode or when a new folder is created $this->redirect = GeneralUtility::sanitizeLocalUrl($redirectUrl); } else { @@ -225,7 +225,7 @@ class FileController $elementKey = key($this->file[$mode]); $this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect'] ?? ''); } - $this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null; + $this->CB = (array)($parsedBody['CB'] ?? $queryParams['CB'] ?? []); if (isset($this->file['rename'][0]['conflictMode'])) { $conflictMode = $this->file['rename'][0]['conflictMode']; @@ -242,16 +242,16 @@ class FileController */ protected function initClipboard(): void { - if (is_array($this->CB)) { + if ($this->CB !== []) { $clipObj = GeneralUtility::makeInstance(Clipboard::class); $clipObj->initializeClipboard(); - if ($this->CB['paste']) { - $clipObj->setCurrentPad($this->CB['pad']); - $this->file = $clipObj->makePasteCmdArray_file($this->CB['paste'], $this->file); + if ($this->CB['paste'] ?? false) { + $clipObj->setCurrentPad((string)($this->CB['pad'] ?? '')); + $this->setPasteCmd($clipObj); } if ($this->CB['delete'] ?? false) { - $clipObj->setCurrentPad($this->CB['pad']); - $this->file = $clipObj->makeDeleteCmdArray_file($this->file); + $clipObj->setCurrentPad((string)($this->CB['pad'] ?? '')); + $this->setDeleteCmd($clipObj); } } } @@ -328,4 +328,34 @@ class FileController return $result; } + + /** + * Applies the proper paste configuration to $this->file + */ + protected function setPasteCmd(Clipboard $clipboard): void + { + $target = explode('|', (string)$this->CB['paste'])[1] ?? ''; + $mode = $clipboard->currentMode() === 'copy' ? 'copy' : 'move'; + // Traverse elements and make CMD array + foreach ($clipboard->elFromTable('_FILE') as $key => $path) { + $this->file[$mode][] = ['data' => $path, 'target' => $target]; + if ($mode === 'move') { + $clipboard->removeElement($key); + } + } + $clipboard->endClipboard(); + } + + /** + * Applies the proper delete configuration to $this->file + */ + protected function setDeleteCmd(Clipboard $clipObj): void + { + // Traverse elements and make CMD array + foreach ($clipObj->elFromTable('_FILE') as $key => $path) { + $this->file['delete'][] = ['data' => $path]; + $clipObj->removeElement($key); + } + $clipObj->endClipboard(); + } } diff --git a/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php b/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php index 7276bfc32ae1f081afd1beb48f7f8df3d484175c..8749de3a5b7687a8ac60c7f263b5615916dfdb7e 100644 --- a/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php +++ b/typo3/sysext/backend/Classes/Controller/SimpleDataHandlerController.php @@ -175,14 +175,13 @@ class SimpleDataHandlerController $queryParams = $request->getQueryParams(); // GPvars: - $this->flags = $parsedBody['flags'] ?? $queryParams['flags'] ?? null; - $this->data = $parsedBody['data'] ?? $queryParams['data'] ?? null; - $this->cmd = $parsedBody['cmd'] ?? $queryParams['cmd'] ?? null; - $this->mirror = $parsedBody['mirror'] ?? $queryParams['mirror'] ?? null; - $this->cacheCmd = $parsedBody['cacheCmd'] ?? $queryParams['cacheCmd'] ?? null; - $redirect = $parsedBody['redirect'] ?? $queryParams['redirect'] ?? ''; - $this->redirect = GeneralUtility::sanitizeLocalUrl($redirect); - $this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null; + $this->flags = (array)($parsedBody['flags'] ?? $queryParams['flags'] ?? []); + $this->data = (array)($parsedBody['data'] ?? $queryParams['data'] ?? []); + $this->cmd = (array)($parsedBody['cmd'] ?? $queryParams['cmd'] ?? []); + $this->mirror = (array)($parsedBody['mirror'] ?? $queryParams['mirror'] ?? []); + $this->cacheCmd = (string)($parsedBody['cacheCmd'] ?? $queryParams['cacheCmd'] ?? ''); + $this->CB = (array)($parsedBody['CB'] ?? $queryParams['CB'] ?? []); + $this->redirect = GeneralUtility::sanitizeLocalUrl((string)($parsedBody['redirect'] ?? $queryParams['redirect'] ?? '')); // Creating DataHandler object $this->tce = GeneralUtility::makeInstance(DataHandler::class); // Configuring based on user prefs. @@ -204,20 +203,16 @@ class SimpleDataHandlerController */ protected function initializeClipboard(): void { - if (is_array($this->CB)) { + if ($this->CB !== []) { $clipObj = GeneralUtility::makeInstance(Clipboard::class); $clipObj->initializeClipboard(); - if ($this->CB['paste']) { - $clipObj->setCurrentPad($this->CB['pad']); - $this->cmd = $clipObj->makePasteCmdArray( - $this->CB['paste'], - $this->cmd, - $this->CB['update'] ?? null - ); + if ($this->CB['paste'] ?? false) { + $clipObj->setCurrentPad((string)($this->CB['pad'] ?? '')); + $this->setPasteCmd($clipObj); } - if ($this->CB['delete']) { - $clipObj->setCurrentPad($this->CB['pad']); - $this->cmd = $clipObj->makeDeleteCmdArray($this->cmd); + if ($this->CB['delete'] ?? false) { + $clipObj->setCurrentPad((string)($this->CB['pad'] ?? '')); + $this->setDeleteCmd($clipObj); } } } @@ -229,7 +224,7 @@ class SimpleDataHandlerController { // LOAD DataHandler with data and cmd arrays: $this->tce->start($this->data, $this->cmd); - if (is_array($this->mirror)) { + if ($this->mirror !== []) { $this->tce->setMirror($this->mirror); } // Execute actions: @@ -245,6 +240,68 @@ class SimpleDataHandlerController } } + /** + * Applies the proper paste configuration to $this->cmd + * + * The reference ($this->CB['paste']) has following format: [tablename]:[paste-uid]. + * Tablename is the name of the table from which elements *on the current clipboard* is pasted with the 'pid' paste-uid. + * No tablename means that all items on the clipboard (non-files) are pasted. This requires paste-uid to be positive though. + * so 'tt_content:-3' means 'paste tt_content elements on the clipboard to AFTER tt_content:3 record + * 'tt_content:30' means 'paste tt_content elements on the clipboard into page with id 30 + * ':30' means 'paste ALL database elements on the clipboard into page with id 30 + * ':-30' not valid. + */ + protected function setPasteCmd(Clipboard $clipboard): void + { + [$pasteTable, $pasteUid] = explode('|', (string)$this->CB['paste']); + $pasteUid = (int)$pasteUid; + // pUid must be set and if pTable is not set (that means paste ALL elements) + // the uid MUST be positive/zero (pointing to page id) + if (!$pasteTable && $pasteUid < 0) { + return; + } + $elements = $clipboard->elFromTable($pasteTable); + // So the order is preserved. + $elements = array_reverse($elements); + $mode = $clipboard->currentMode() === 'copy' ? 'copy' : 'move'; + // Traverse elements and make CMD array + foreach ($elements as $key => $value) { + [$table, $uid] = explode('|', $key); + if (!is_array($this->cmd[$table])) { + $this->cmd[$table] = []; + } + if (is_array($this->CB['update'] ?? false)) { + $this->cmd[$table][$uid][$mode] = [ + 'action' => 'paste', + 'target' => $pasteUid, + 'update' => $this->CB['update'], + ]; + } else { + $this->cmd[$table][$uid][$mode] = $pasteUid; + } + if ($mode === 'move') { + $clipboard->removeElement($key); + } + } + $clipboard->endClipboard(); + } + + /** + * Applies the proper delete configuration to $this->cmd + */ + protected function setDeleteCmd(Clipboard $clipboard): void + { + foreach ($clipboard->elFromTable() as $key => $value) { + [$table, $uid] = explode('|', $key); + if (!is_array($this->cmd[$table])) { + $this->cmd[$table] = []; + } + $this->cmd[$table][$uid]['delete'] = 1; + $clipboard->removeElement($key); + } + $clipboard->endClipboard(); + } + /** * Returns the current BE user. * diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php index 7bedb3fb9ec3d10c3ef282b8c7bb8e04201176b4..8c9af3748dbc87fc42717bee27dde8e08dfca7a1 100644 --- a/typo3/sysext/filelist/Classes/Controller/FileListController.php +++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php @@ -216,7 +216,7 @@ class FileListController implements LoggerAwareInterface // Generate the clipboard, if enabled if ($this->MOD_SETTINGS['clipBoard'] ?? false) { - $this->view->assign('clipBoardHtml', $this->filelist->clipObj->printClipboard()); + $this->view->assign('clipBoardHtml', $this->filelist->clipObj->printClipboard('_FILE')); } // Register drag-uploader @@ -341,7 +341,7 @@ class FileListController implements LoggerAwareInterface $items = $this->filelist->clipObj->cleanUpCBC( (array)($request->getParsedBody()['CBC'] ?? []), '_FILE', - 1 + true ); if (!empty($items)) { // Make command array: diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php index 36c07e04697e8bf70b7a7260b7481299caa10488..e9000cabb2bc95ab3c01f374573d383c48e5f5e9 100644 --- a/typo3/sysext/filelist/Classes/FileList.php +++ b/typo3/sysext/filelist/Classes/FileList.php @@ -188,7 +188,6 @@ class FileList ); // Create clipboard object and initialize that $this->clipObj = GeneralUtility::makeInstance(Clipboard::class); - $this->clipObj->fileMode = true; $this->clipObj->initializeClipboard(); $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $this->getLanguageService()->includeLLFile('EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf'); diff --git a/typo3/sysext/recordlist/Classes/Controller/RecordListController.php b/typo3/sysext/recordlist/Classes/Controller/RecordListController.php index 3058e6ea0e086957185084dc646e4998483dd35d..c6fcde2013fa22ade0ae5f8eec043cc3ab2df41c 100644 --- a/typo3/sysext/recordlist/Classes/Controller/RecordListController.php +++ b/typo3/sysext/recordlist/Classes/Controller/RecordListController.php @@ -200,7 +200,7 @@ class RecordListController // Deleting records...: // Has not to do with the clipboard but is simply the delete action. The clipboard object is used to clean up the submitted entries to only the selected table. if ($cmd === 'delete' && $request->getMethod() === 'POST') { - $items = $clipboard->cleanUpCBC($parsedBody['CBC'] ?? [], $parsedBody['cmd_table'] ?? '', 1); + $items = $clipboard->cleanUpCBC((array)($parsedBody['CBC'] ?? []), (string)($parsedBody['cmd_table'] ?? ''), true); if (!empty($items)) { // Create data handler command array $dataHandlerCmd = []; diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php index ae9ecdd72549766da9f99bf96922ce3ea78a470a..ed38ff855bd7b6a98eaf913c4b748072eb4f6f3f 100644 --- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php +++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php @@ -1853,8 +1853,8 @@ class DatabaseRecordList $cells['copy'] = '<a class="btn btn-default"' . ' href="' . htmlspecialchars($this->clipObj->selUrlDB( $table, - $row['uid'], - 1, + (int)$row['uid'], + true, $isSel === 'copy', ['returnUrl' => $this->listURL()] )) . '"' @@ -1875,8 +1875,8 @@ class DatabaseRecordList $cells['cut'] = '<a class="btn btn-default"' . ' href="' . htmlspecialchars($this->clipObj->selUrlDB( $table, - $row['uid'], - 0, + (int)$row['uid'], + false, $isSel === 'cut', ['returnUrl' => $this->listURL()] )) . '"'