diff --git a/dev/Model/Account.js b/dev/Model/Account.js index 7b7eae8e3..0c88bb8c1 100644 --- a/dev/Model/Account.js +++ b/dev/Model/Account.js @@ -22,4 +22,22 @@ export class AccountModel extends AbstractModel { }); } + /** + * Imports all mail to main account + *//* + importAll(account) { + rl.app.Remote.streamPerLine(line => { + try { + line = JSON.parse(line); + console.dir(line); + } catch (e) { + // OOPS + } + }, 'AccountImport', { + Action: 'AccountImport', + email: account.email + }); + } + */ + } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Messages.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Messages.php index ab3af2be8..2c5ded329 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Commands/Messages.php @@ -287,6 +287,7 @@ trait Messages * @throws \MailSo\RuntimeException * @throws \MailSo\Net\Exceptions\* * @throws \MailSo\Imap\Exceptions\* + * $sStoreAction = \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT */ public function MessageStoreFlag(SequenceSet $oRange, array $aInputStoreItems, string $sStoreAction) : ?ResponseCollection { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php index 72a759745..30cc4aabc 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Folder.php @@ -20,20 +20,14 @@ class Folder // RFC5258 Response data STATUS items when using LIST-EXTENDED use Traits\Status; - /** - * @var string - */ - private $sDelimiter; + private ?string $sDelimiter; - /** - * @var array - */ - private $aFlagsLowerCase; + private array $aFlagsLowerCase; /** * RFC 5464 */ - private $aMetadata = array(); + private array $aMetadata = array(); /** * @throws \InvalidArgumentException @@ -57,7 +51,7 @@ class Folder public function setFlags(array $aFlags) : void { - $this->aFlagsLowerCase = \array_map('strtolower', $aFlags); + $this->aFlagsLowerCase = \array_map('mb_strtolower', $aFlags); } public function setDelimiter(?string $sDelimiter) : void diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index d6881b19c..4dbde1ae5 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -26,8 +26,8 @@ class ImapClient extends \MailSo\Net\NetClient use Commands\Metadata; use Commands\Quota; - const - TAG_PREFIX = 'TAG'; + public + $TAG_PREFIX = 'TAG'; /** * @var int @@ -606,7 +606,7 @@ class ImapClient extends \MailSo\Net\NetClient protected function getCurrentTag() : string { - return self::TAG_PREFIX.$this->iTagCount; + return $this->TAG_PREFIX.$this->iTagCount; } public function EscapeString(?string $sStringForEscape) : string diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Traits/ResponseParser.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Traits/ResponseParser.php index 44636cffe..b5dfcea4f 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Traits/ResponseParser.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/Traits/ResponseParser.php @@ -401,8 +401,7 @@ trait ResponseParser return false; } - $rImapLiteralStream = - \MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen); + $rImapLiteralStream = \MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen); $this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. ' - try to read '.$iLiteralLen.' bytes.', \LOG_INFO); @@ -411,7 +410,7 @@ trait ResponseParser try { - $this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream); + $this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream, $iLiteralLen); } catch (\Throwable $oException) { @@ -445,12 +444,10 @@ trait ResponseParser \fclose($rImapLiteralStream); if (0 < $iNotReadLiteralLen) { - $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', - \LOG_WARNING); + $this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', \LOG_WARNING); } } else { - $this->writeLog('Literal stream is not resource after callback.', - \LOG_WARNING); + $this->writeLog('Literal stream is not resource after callback.', \LOG_WARNING); } $this->bRunningCallback = false; diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Folder.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Folder.php index 125f6fc38..fa6279307 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Folder.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Folder.php @@ -20,20 +20,11 @@ use MailSo\Imap\Enumerations\MetadataKeys; */ class Folder implements \JsonSerializable { - /** - * @var bool - */ - private $bExists; + private bool $bExists; - /** - * @var bool - */ - private $bSubscribed; + private bool $bSubscribed; - /** - * @var \MailSo\Imap\Folder - */ - private $oImapFolder; + private \MailSo\Imap\Folder $oImapFolder; /** * @throws \InvalidArgumentException @@ -70,7 +61,7 @@ class Folder implements \JsonSerializable return $this->oImapFolder->NameRaw(); } - public function Delimiter() : string + public function Delimiter() : ?string { return $this->oImapFolder->Delimiter(); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index ee7952367..435c036ff 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1096,7 +1096,7 @@ class Actions if (!$this->MailClient()->IsLoggined()) { try { - $oAccount->ImapConnectAndLoginHelper($this->oPlugins, $this->MailClient(), $this->oConfig); + $oAccount->ImapConnectAndLoginHelper($this->oPlugins, $this->MailClient()->ImapClient(), $this->oConfig); } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { throw new Exceptions\ClientException(Notifications::ConnectionError, $oException); } catch (\Throwable $oException) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php index 49dc6a07f..a57d7dcf2 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php @@ -110,6 +110,47 @@ trait Accounts return $this->TrueResponse(__FUNCTION__); } + /** + * Imports all mail from AdditionalAccount into MainAccount + */ + public function DoAccountImport(): array + { + $sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($this->GetActionParam('email', '')), true); + if (!\strlen($sEmail)) { + throw new ClientException(Notifications::AccountDoesNotExist); + } + + $oMainAccount = $this->getMainAccountFromToken(); + $oLogger = $this->Logger(); + + $aAccounts = $this->GetAccounts($oMainAccount); + if (!isset($aAccounts[$sEmail])) { + throw new ClientException(Notifications::AccountDoesNotExist); + } + $oAccount = AdditionalAccount::NewInstanceFromTokenArray( + $this, $aAccounts[$sEmail] + ); + if (!$oAccount) { + throw new ClientException(Notifications::AccountDoesNotExist); + } + + $oImapTarget = new \MailSo\Imap\ImapClient; + $oImapTarget->SetLogger($oLogger); + $this->imapConnect($oMainAccount, false, $oImapTarget); + + $oImapSource = new \MailSo\Imap\ImapClient; + $oImapSource->SetLogger($oLogger); + $this->imapConnect($oAccount, false, $oImapSource); + + $oSync = new \SnappyMail\Imap\Sync; + $oSync->oImapSource = $oImapSource; + $oSync->oImapTarget = $oImapTarget; + + $rootfolder = $this->GetActionParam('rootfolder', '') ?: $sEmail; + $oSync->import($rootfolder); + exit; + } + /** * @throws \MailSo\RuntimeException */ diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index fa7bb17f7..d5a48e6e6 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -139,7 +139,7 @@ trait UserAuth } try { - $this->CheckMailConnection($oAccount, true); + $this->imapConnect($oAccount, true); if ($bMainAccount) { $bSignMe && $this->SetSignMeToken($oAccount); $this->StorageProvider()->Put($oAccount, StorageType::SESSION, Utils::GetSessionToken(), 'true'); @@ -188,7 +188,8 @@ trait UserAuth } // Test the login - $this->CheckMailConnection($oAccount); + $oImapClient = new \MailSo\Imap\ImapClient; + $this->imapConnect($oAccount, false, $oImapClient); $this->SetAdditionalAuthToken($oAccount); return true; @@ -380,7 +381,7 @@ trait UserAuth if (!$oAccount) { throw new \RuntimeException('token has no account'); } - $this->CheckMailConnection($oAccount); + $this->imapConnect($oAccount); // Update lifetime $this->SetSignMeToken($oAccount); return $oAccount; @@ -440,10 +441,13 @@ trait UserAuth /** * @throws \RainLoop\Exceptions\ClientException */ - protected function CheckMailConnection(Account $oAccount, bool $bAuthLog = false): void + protected function imapConnect(Account $oAccount, bool $bAuthLog = false, \MailSo\Imap\ImapClient $oImapClient = null): void { try { - $oAccount->ImapConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); + if (!$oImapClient) { + $oImapClient = $this->MailClient()->ImapClient(); + } + $oAccount->ImapConnectAndLoginHelper($this->Plugins(), $oImapClient, $this->Config()); } catch (ClientException $oException) { throw $oException; } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index cac637b11..649a74c7d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -216,9 +216,8 @@ abstract class Account implements \JsonSerializable return $oAccount; } - public function ImapConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool + public function ImapConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Imap\ImapClient $oImapClient, \RainLoop\Config\Application $oConfig) : bool { - $oImapClient = $oMailClient->ImapClient(); $oImapClient->__FORCE_SELECT_ON_EXAMINE__ = !!$oConfig->Get('imap', 'use_force_selection'); $oImapClient->__DISABLE_METADATA = !!$oConfig->Get('imap', 'disable_metadata'); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/imap/sync.php b/snappymail/v/0.0.0/app/libraries/snappymail/imap/sync.php new file mode 100644 index 000000000..edd239501 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/imap/sync.php @@ -0,0 +1,187 @@ +oImapSource->TAG_PREFIX = 'S'; + $this->oImapTarget->TAG_PREFIX = 'T'; + +// $this->oImapTarget->Logger()->Write('Get oImapTarget->FolderList'); + \SnappyMail\Log::notice('SYNC', 'Get oImapTarget->FolderList'); + $aTargetFolders = $this->oImapTarget->FolderList($sParent, $sListPattern); + if (!$aTargetFolders) { + return null; + } + $sTargetINBOX = 'INBOX'; + $sTargetDelimiter = ''; + foreach ($aTargetFolders as $sFullName => $oImapFolder) { + if ($oImapFolder->IsInbox()) { + $sTargetINBOX = $sFullName; + } + if (!$sTargetDelimiter) { + $sTargetDelimiter = $oImapFolder->Delimiter(); + } + } + + \SnappyMail\Log::notice('SYNC', 'Get oImapSource->FolderList'); + $bUseListStatus = $this->oImapSource->IsSupported('LIST-EXTENDED'); + $aSourceFolders = $this->oImapSource->FolderList($sParent, $sListPattern, false, $bUseListStatus); + if (!$aSourceFolders) { + return null; + } + $sSourceINBOX = 'INBOX'; + + \SnappyMail\HTTP\Stream::start(); + \SnappyMail\HTTP\Stream::JSON([ + 'folders' => \count($aSourceFolders) + ]); + + $fi = 0; + foreach ($aSourceFolders as $sSourceFolderName => $oImapFolder) { + ++$fi; + \SnappyMail\HTTP\Stream::JSON([ + 'index' => $fi, + 'folder' => $sSourceFolderName + ]); + if ($oImapFolder->IsSelectable()) { + if ($oImapFolder->IsInbox()) { + $sSourceINBOX = $sSourceFolderName; + } + // Set mailbox delimiter + $sTargetFolderName = $sSourceFolderName; + if ($sTargetDelimiter) { + $sTargetFolderName = \str_replace($oImapFolder->Delimiter(), $sTargetDelimiter, $sTargetFolderName); + $sTargetRootFolderName = \str_replace($sTargetDelimiter, '-', $sTargetRootFolderName); + } + if ($sTargetRootFolderName) { + $sTargetFolderName = $sTargetRootFolderName . ($sTargetDelimiter?:'-') . $sTargetFolderName; + } + // Create mailbox if not exists + if (!isset($aTargetFolders[$sTargetFolderName])) { + $this->oImapTarget->FolderCreate($sTargetFolderName); + if (!$bUseListStatus || \in_array('\\subscribed', $oImapFolder->FlagsLowerCase())) { + $this->oImapTarget->FolderSubscribe($sTargetFolderName); + } + } else if (!$aTargetFolders[$sTargetFolderName]->IsSelectable()) { + // Can't copy messages + continue; + } + + // Set Source metadata on target + if ($aMetadata = $oImapFolder->Metadata()) { + $this->oImapTarget->FolderSetMetadata($sTargetFolderName, $aMetadata); + } + + $oSourceInfo = $this->oImapSource->FolderSelect($sSourceFolderName); + if ($oSourceInfo->MESSAGES) { + \SnappyMail\HTTP\Stream::JSON([ + 'index' => $fi, + 'messages' => $oSourceInfo->MESSAGES + ]); + // All id's to skip from source + $oTargetInfo = $this->oImapTarget->FolderSelect($sTargetFolderName); + if ($oTargetInfo->MESSAGES) { + // Get all existing message id's from target to skip + $aTargetMessageIDs = []; + $this->oImapTarget->SendRequest('FETCH', [ + '1:*', [FetchType::BuildBodyCustomHeaderRequest([Header::MESSAGE_ID], true)] + ]); + foreach ($this->oImapTarget->yieldUntaggedResponses() as $oResponse) { + if ('FETCH' === $oResponse->ResponseList[2]) { + // $oResponse->ResponseList[3][0] == 'BODY[HEADER.FIELDS (MESSAGE-ID)]' + // 'Message-ID: ...' + $aTargetMessageIDs[] = $oResponse->ResponseList[3][1]; + } + } + } + // Set all existing id's from source to skip and get all flags + $aSourceSkipIDs = []; + $aSourceFlags = []; + $this->oImapSource->SendRequest('FETCH', [ + '1:*', [FetchType::FLAGS, FetchType::BuildBodyCustomHeaderRequest([Header::MESSAGE_ID], true)] + ]); + foreach ($this->oImapSource->yieldUntaggedResponses() as $oResponse) { + if ('FETCH' === $oResponse->ResponseList[2] + && isset($oResponse->ResponseList[3]) + && \is_array($oResponse->ResponseList[3]) + ) { + $id = $oResponse->ResponseList[1]; + foreach ($oResponse->ResponseList[3] as $i => $mItem) { + if ('FLAGS' === $mItem) { + $aSourceFlags[$id] = $oResponse->ResponseList[3][$i+1]; + } else if ('MESSAGE-ID' === $mItem && \in_array($oResponse->ResponseList[3][$i+1], $aTargetMessageIDs)) { + $aSourceSkipIDs[] = $id; + } + } + } + } + + $aTargetMessageIDs = []; + // Now copy each message from source to target + for ($i = 1; $i <= $oSourceInfo->MESSAGES; ++$i) { + if (!\in_array($i, $aSourceSkipIDs)) { + $sPeek = $this->oImapSource->IsSupported('BINARY') + ? FetchType::BINARY_PEEK + : FetchType::BODY_PEEK; + $iAppendUid = 0; + $aFetchResponse = $this->oImapSource->Fetch(array( + array( + $sPeek.'[]', + function ($sParent, $sLiteralAtomUpperCase, $rLiteralStream, $iLiteralLen) + use ($sTargetFolderName, &$iAppendUid, $aSourceFlags, $i) { + if (\strlen($sLiteralAtomUpperCase) && \is_resource($rLiteralStream) && 'FETCH' === $sParent) { +// $sMessage = \stream_get_contents($rLiteralStream); + $iAppendUid = $this->oImapTarget->MessageAppendStream( + $sTargetFolderName, + $rLiteralStream, + $iLiteralLen, + isset($aSourceFlags[$i]) ? $aSourceFlags[$i] : [] + ); + } + } + )), $i, false); + +/* + $aFlags = $aFetchResponse[0]->GetFetchValue('FLAGS'); + $iAppendUid = $this->oImapTarget->MessageAppendStream( + $sTargetFolderName, + $rLiteralStream, + $iLiteralLen, + $aFlags + ); + if ($iAppendUid && $aFlags) { + $this->MessageStoreFlag( + new SequenceSet([$iAppendUid]), + $aFlags, + \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT + ); + } +*/ + } + + \SnappyMail\HTTP\Stream::JSON([ + 'index' => $fi, + 'message' => $i + ]); + } + } + } + } + } + +} diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsAccounts.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsAccounts.html index db4d826a7..5f3bb24b8 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsAccounts.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsAccounts.html @@ -22,6 +22,9 @@ +