From 08639768592ac2f691fcbd9f0aedefaa4957434b Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 29 Nov 2022 10:09:48 +0100 Subject: [PATCH] Resolve #717 --- dev/View/User/MailBox/MessageList.js | 36 +++- .../RainLoop/Actions/Attachments.php | 179 ++++++++++++++++++ .../app/libraries/RainLoop/Actions/User.php | 173 +---------------- .../templates/Views/User/MailMessageList.html | 5 +- 4 files changed, 217 insertions(+), 176 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Attachments.php diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index 69fcbecce..d50494eba 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -11,6 +11,7 @@ import { leftPanelDisabled, toggleLeftPanel, addShortcut, registerShortcut, formFieldFocused } from 'Common/Globals'; +import { arrayLength } from 'Common/Utils'; import { computedPaginatorHelper, showMessageComposer, populateMessageBody, download, moveAction } from 'Common/UtilsUser'; import { FileInfo } from 'Common/File'; import { isFullscreen, toggleFullscreen } from 'Common/Fullscreen'; @@ -102,6 +103,9 @@ export class MailMessageList extends AbstractViewRight { this.dragOver = ko.observable(false).extend({ throttle: 1 }); this.dragOverEnter = ko.observable(false).extend({ throttle: 1 }); + const attachmentsActions = Settings.app('attachmentsActions'); + this.attachmentsActions = ko.observableArray(arrayLength(attachmentsActions) ? attachmentsActions : []); + addComputablesTo(this, { sortSupported: () => @@ -148,7 +152,9 @@ export class MailMessageList extends AbstractViewRight { return '𝐒' + (desc ? '⬆' : '⬇'); } return (mode.includes('SIZE') ? '✉' : '📅') + (desc ? '⬇' : '⬆'); - } + }, + + downloadAsZipAllowed: () => this.attachmentsActions.includes('zip') }); this.selector = new Selector( @@ -259,7 +265,8 @@ export class MailMessageList extends AbstractViewRight { ).throttle(50)); decorateKoCommands(this, { - downloadCommand: canBeMovedHelper, + downloadAttachCommand: canBeMovedHelper, + downloadZipCommand: canBeMovedHelper, forwardCommand: canBeMovedHelper, deleteWithoutMoveCommand: canBeMovedHelper, deleteCommand: canBeMovedHelper, @@ -292,7 +299,30 @@ export class MailMessageList extends AbstractViewRight { ]); } - downloadCommand() { + downloadZipCommand() { + let hashes = []/*, uids = []*/; +// MessagelistUserStore.forEach(message => message.checked() && uids.push(message.uid)); + MessagelistUserStore.forEach(message => message.checked() && hashes.push(message.requestHash)); + if (hashes.length) { + Remote.post('AttachmentsActions', null, { + Do: 'Zip', + Folder: MessagelistUserStore().Folder, +// Uids: uids, + Hashes: hashes + }) + .then(result => { + let hash = result?.Result?.FileHash; + if (hash) { + download(attachmentDownload(hash), hash+'.zip'); + } else { + alert('Download failed'); + } + }) + .catch(() => alert('Download failed')); + } + } + + downloadAttachCommand() { let hashes = []; MessagelistUserStore.forEach(message => { if (message.checked()) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Attachments.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Attachments.php new file mode 100644 index 000000000..adcde6fc8 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Attachments.php @@ -0,0 +1,179 @@ +GetActionParam('Do', ''); + $sFolder = $this->GetActionParam('Folder', ''); + $aHashes = $this->GetActionParam('Hashes', null); + $oFilesProvider = $this->FilesProvider(); + if (empty($sAction) || !$this->GetCapa(Capa::ATTACHMENTS_ACTIONS) || !$oFilesProvider || !$oFilesProvider->IsActive()) { + return $this->FalseResponse(__FUNCTION__); + } + + $oAccount = $this->initMailClientConnection(); + + $bError = false; + $aData = []; + $mUIDs = []; + + if (\is_array($aHashes) && \count($aHashes)) { + foreach ($aHashes as $sZipHash) { + $aResult = $this->getMimeFileByHash($oAccount, $sZipHash); + if (empty($aResult['FileHash'])) { + $bError = true; + break; + } + $aData[] = $aResult; + if (!empty($aResult['MimeIndex'])) { + $mUIDs[$aResult['Uid']] = $aResult['Uid']; + } + } + } + $mUIDs = 1 < \count($mUIDs); + + if ($bError || !\count($aData)) { + return $this->FalseResponse(__FUNCTION__); + } + + $mResult = false; + switch (\strtolower($sAction)) + { + case 'zip': + + $sZipHash = \MailSo\Base\Utils::Sha1Rand(); + $sZipFileName = $oFilesProvider->GenerateLocalFullFileName($oAccount, $sZipHash); + + if (!empty($sZipFileName)) { + if (\class_exists('ZipArchive')) { + $oZip = new \ZipArchive(); + $oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE); + $oZip->setArchiveComment('SnappyMail/'.APP_VERSION); + foreach ($aData as $aItem) { + $sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $aItem['FileHash']); + $sFileName = ($mUIDs ? "{$aItem['Uid']}/" : ($sFolder ? "{$aItem['Uid']}-" : '')) . $aItem['FileName']; + if (!$oZip->addFile($sFullFileNameHash, $sFileName)) { + $bError = true; + } + } + + if ($bError) { + $oZip->close(); + } else { + $bError = !$oZip->close(); + } +/* + } else { + @\unlink($sZipFileName); + $oZip = new \SnappyMail\Stream\ZIP($sZipFileName); +// $oZip->setArchiveComment('SnappyMail/'.APP_VERSION); + foreach ($aData as $aItem) { + if ($aItem['FileHash']) { + $sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $aItem['FileHash']); + if (!$oZip->addFile($sFullFileNameHash, $aItem['FileName'])) { + $bError = true; + } + } + } + $oZip->close(); +*/ + } else { + @\unlink($sZipFileName); + $oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP); + $oZip->compressFiles(\Phar::GZ); + foreach ($aData as $aItem) { + $oZip->addFile( + $oFilesProvider->GetFileName($oAccount, $aItem['FileHash']), + ($mUIDs ? "{$aItem['Uid']}/" : ($sFolder ? "{$aItem['Uid']}-" : '')) . $aItem['FileName'] + ); + } + $oZip->compressFiles(\Phar::GZ); + unset($oZip); + \rename($sZipFileName . '.zip', $sZipFileName); + } + + foreach ($aData as $aItem) { + $oFilesProvider->Clear($oAccount, $aItem['FileHash']); + } + + if (!$bError) { + $mResult = array( + 'FileHash' => Utils::EncodeKeyValuesQ(array( + 'Account' => $oAccount ? $oAccount->Hash() : '', + 'FileName' => ($sFolder ? 'messages' : 'attachments') . \date('-YmdHis') . '.zip', + 'MimeType' => 'application/zip', + 'FileHash' => $sZipHash + )) + ); + } + } + break; + + default: + $data = new \SnappyMail\AttachmentsAction; + $data->action = $sAction; + $data->items = $aData; + $data->filesProvider = $oFilesProvider; + $data->account = $oAccount; + $this->Plugins()->RunHook('json.attachments', array($data)); + $mResult = $data->result; + break; + } + +// $this->requestSleep(); + return $this->DefaultResponse(__FUNCTION__, $bError ? false : $mResult); + } + + private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array + { + $aValues = $this->getDecodedRawKeyValue($sHash); + + $sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : ''; + $iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0; + $sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : ''; + + $sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : ''; + $sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : 'file.dat'; + + $oFileProvider = $this->FilesProvider(); + + $sResultHash = ''; + + $mResult = $this->MailClient()->MessageMimeStream(function ($rResource, $sContentType, $sFileName, $sMimeIndex = '') + use ($oAccount, $oFileProvider, $sFileNameIn, $sContentTypeIn, &$sResultHash) { + + unset($sContentType, $sFileName, $sMimeIndex); + + if (\is_resource($rResource)) + { + $sHash = \MailSo\Base\Utils::Sha1Rand($sFileNameIn.'~'.$sContentTypeIn); + $rTempResource = $oFileProvider->GetFile($oAccount, $sHash, 'wb+'); + + if (\is_resource($rTempResource)) + { + if (false !== \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource))) + { + $sResultHash = $sHash; + } + + \fclose($rTempResource); + } + } + + }, $sFolder, $iUid, $sMimeIndex); + + $aValues['FileName'] = $sFileNameIn; + $aValues['FileHash'] = $mResult ? $sResultHash : ''; + + return $aValues; + } +} diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php index 39d1234f0..ba7f0c6ea 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php @@ -15,6 +15,7 @@ trait User use Filters; use Folders; use Messages; + use Attachments; use Pgp; /** @@ -68,131 +69,6 @@ trait User return $this->DefaultResponse(__FUNCTION__, $this->AppData(false)); } - /** - * @throws \MailSo\RuntimeException - */ - public function DoAttachmentsActions() : array - { - $sAction = $this->GetActionParam('Do', ''); - $aHashes = $this->GetActionParam('Hashes', null); - $oFilesProvider = $this->FilesProvider(); - if (empty($sAction) || !$this->GetCapa(Capa::ATTACHMENTS_ACTIONS) || !$oFilesProvider || !$oFilesProvider->IsActive()) { - return $this->FalseResponse(__FUNCTION__); - } - - $oAccount = $this->initMailClientConnection(); - - $bError = false; - $aData = []; - $mUIDs = []; - - if (\is_array($aHashes) && \count($aHashes)) { - foreach ($aHashes as $sZipHash) { - $aResult = $this->getMimeFileByHash($oAccount, $sZipHash); - if (empty($aResult['FileHash'])) { - $bError = true; - break; - } - $aData[] = $aResult; - $mUIDs[$aResult['Uid']] = $aResult['Uid']; - } - } - $mUIDs = 1 < \count($mUIDs); - - if ($bError || !\count($aData)) { - return $this->FalseResponse(__FUNCTION__); - } - - $mResult = false; - switch (\strtolower($sAction)) - { - case 'zip': - - $sZipHash = \MailSo\Base\Utils::Sha1Rand(); - $sZipFileName = $oFilesProvider->GenerateLocalFullFileName($oAccount, $sZipHash); - - if (!empty($sZipFileName)) { - if (\class_exists('ZipArchive')) { - $oZip = new \ZipArchive(); - $oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE); - $oZip->setArchiveComment('SnappyMail/'.APP_VERSION); - foreach ($aData as $aItem) { - $sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $aItem['FileHash']); - $sFileName = ($mUIDs ? "{$aItem['Uid']}/" : '') . ($aItem['FileName'] ?: 'file.dat'); - if (!$oZip->addFile($sFullFileNameHash, $sFileName)) { - $bError = true; - } - } - - if ($bError) { - $oZip->close(); - } else { - $bError = !$oZip->close(); - } -/* - } else { - @\unlink($sZipFileName); - $oZip = new \SnappyMail\Stream\ZIP($sZipFileName); -// $oZip->setArchiveComment('SnappyMail/'.APP_VERSION); - foreach ($aData as $aItem) { - $sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat'); - $sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : ''); - if (!empty($sFileHash)) { - $sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash); - if (!$oZip->addFile($sFullFileNameHash, $sFileName)) { - $bError = true; - } - } - } - $oZip->close(); -*/ - } else { - @\unlink($sZipFileName); - $oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP); - $oZip->compressFiles(\Phar::GZ); - foreach ($aData as $aItem) { - $oZip->addFile( - $oFilesProvider->GetFileName($oAccount, $aItem['FileHash']), - ($mUIDs ? "{$aItem['Uid']}/" : '') . ($aItem['FileName'] ?: 'file.dat') - ); - } - $oZip->compressFiles(\Phar::GZ); - unset($oZip); - \rename($sZipFileName . '.zip', $sZipFileName); - } - - foreach ($aData as $aItem) { - $oFilesProvider->Clear($oAccount, $aItem['FileHash']); - } - - if (!$bError) { - $mResult = array( - 'FileHash' => Utils::EncodeKeyValuesQ(array( - 'Account' => $oAccount ? $oAccount->Hash() : '', - 'FileName' => 'attachments.zip', - 'MimeType' => 'application/zip', - 'FileHash' => $sZipHash - )) - ); - } - } - break; - - default: - $data = new \SnappyMail\AttachmentsAction; - $data->action = $sAction; - $data->items = $aData; - $data->filesProvider = $oFilesProvider; - $data->account = $oAccount; - $this->Plugins()->RunHook('json.attachments', array($data)); - $mResult = $data->result; - break; - } - -// $this->requestSleep(); - return $this->DefaultResponse(__FUNCTION__, $bError ? false : $mResult); - } - public function DoLogout() : array { $bMain = true; // empty($_COOKIE[self::AUTH_ADDITIONAL_TOKEN_KEY]); @@ -468,53 +344,6 @@ trait User $this->SettingsProvider()->Save($oAccount, $oSettings) : false); } - private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array - { - $aValues = $this->getDecodedRawKeyValue($sHash); - - $sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : ''; - $iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0; - $sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : ''; - - $sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : ''; - $sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : ''; - - $oFileProvider = $this->FilesProvider(); - - $sResultHash = ''; - - $mResult = $this->MailClient()->MessageMimeStream(function ($rResource, $sContentType, $sFileName, $sMimeIndex = '') - use ($oAccount, $oFileProvider, $sFileNameIn, $sContentTypeIn, &$sResultHash) { - - unset($sContentType, $sFileName, $sMimeIndex); - - if ($oAccount && \is_resource($rResource)) - { - $sHash = \MailSo\Base\Utils::Sha1Rand($sFileNameIn.'~'.$sContentTypeIn); - $rTempResource = $oFileProvider->GetFile($oAccount, $sHash, 'wb+'); - - if (\is_resource($rTempResource)) - { - if (false !== \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource))) - { - $sResultHash = $sHash; - } - - \fclose($rTempResource); - } - } - - }, $sFolder, $iUid, $sMimeIndex); - - $aValues['FileHash'] = ''; - if ($mResult) - { - $aValues['FileHash'] = $sResultHash; - } - - return $aValues; - } - private function setSettingsFromParams(\RainLoop\Settings $oSettings, string $sConfigName, string $sType = 'string', ?callable $cCallback = null) : void { if ($this->HasActionParam($sConfigName)) diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageList.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageList.html index a306a89ea..ee773eed0 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageList.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageList.html @@ -43,9 +43,12 @@ -
  • +
  • +
  • + +