From 54896bf044332f6d3f364944a1cd009e25934f0a Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 15 Dec 2022 13:49:39 +0100 Subject: [PATCH] Remove most DoFolders loops as they are not used. And move system folder detection/autocreate to MailboxDetectPlugin because it is hardly used. And by using mailbox.role the default system folder detection is only using (IMAP and JMAP) RFC standards. --- dev/App/User.js | 13 +- dev/Common/EnumsUser.js | 33 +-- dev/Model/FolderCollection.js | 135 +++++++--- dev/Stores/User/Folder.js | 4 +- dev/View/Popup/Compose.js | 6 +- dev/View/Popup/FolderSystem.js | 16 +- dev/View/User/MailBox/MessageList.js | 4 +- dev/View/User/MailBox/MessageView.js | 4 +- .../snappymail/lib/Search/Provider.php | 2 +- plugins/README.md | 22 -- plugins/mailbox-detect/index.php | 219 ++++++++++++++++ .../app/libraries/MailSo/Imap/Folder.php | 145 ++--------- .../MailSo/Mail/FolderCollection.php | 12 +- .../app/libraries/MailSo/Mail/MailClient.php | 26 +- .../0.0.0/app/libraries/RainLoop/Actions.php | 2 +- .../libraries/RainLoop/Actions/Accounts.php | 2 +- .../libraries/RainLoop/Actions/Folders.php | 241 ++---------------- .../libraries/RainLoop/Config/Application.php | 1 - 18 files changed, 419 insertions(+), 468 deletions(-) create mode 100644 plugins/mailbox-detect/index.php diff --git a/dev/App/User.js b/dev/App/User.js index 37491cc8f..bc40fe6b0 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -6,7 +6,6 @@ import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility } from 'Commo import { FolderType, - SetSystemFoldersNotification, ClientSideKeyNameFolderListSize } from 'Common/EnumsUser'; @@ -101,27 +100,27 @@ export class AppUser extends AbstractApp { */ moveMessagesToFolderType(iFolderType, sFromFolderFullName, oUids, bDelete) { let oMoveFolder = null, - nSetSystemFoldersNotification = null; + nSetSystemFoldersNotification = 0; switch (iFolderType) { - case FolderType.Spam: + case FolderType.Junk: oMoveFolder = getFolderFromCacheList(FolderUserStore.spamFolder()); - nSetSystemFoldersNotification = SetSystemFoldersNotification.Spam; + nSetSystemFoldersNotification = iFolderType; bDelete = bDelete || UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(); break; - case FolderType.NotSpam: + case FolderType.Inbox: oMoveFolder = getFolderFromCacheList(getFolderInboxName()); break; case FolderType.Trash: oMoveFolder = getFolderFromCacheList(FolderUserStore.trashFolder()); - nSetSystemFoldersNotification = SetSystemFoldersNotification.Trash; + nSetSystemFoldersNotification = iFolderType; bDelete = bDelete || UNUSED_OPTION_VALUE === FolderUserStore.trashFolder() || sFromFolderFullName === FolderUserStore.spamFolder() || sFromFolderFullName === FolderUserStore.trashFolder(); break; case FolderType.Archive: oMoveFolder = getFolderFromCacheList(FolderUserStore.archiveFolder()); - nSetSystemFoldersNotification = SetSystemFoldersNotification.Archive; + nSetSystemFoldersNotification = iFolderType; bDelete = bDelete || UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(); break; // no default diff --git a/dev/Common/EnumsUser.js b/dev/Common/EnumsUser.js index 6f3067740..326964d6d 100644 --- a/dev/Common/EnumsUser.js +++ b/dev/Common/EnumsUser.js @@ -4,14 +4,27 @@ * @enum {number} */ export const FolderType = { - User: 0, Inbox: 1, Sent: 2, Drafts: 3, - Spam: 4, // JUNK + Junk: 4, // Spam Trash: 5, - Archive: 6, - NotSpam: 80 + Archive: 6 +/* + IMPORTANT : 10; + FLAGGED : 11; + ALL : 13; + // TODO: SnappyMail + TEMPLATES : 19; + // Kolab + CONFIGURATION : 20; + CALENDAR : 21; + CONTACTS : 22; + TASKS : 23; + NOTES : 24; + FILES : 25; + JOURNAL : 26; +*/ }; /** @@ -41,18 +54,6 @@ export const ComposeType = { EditAsNew: 6 }; -/** - * @enum {number} - */ -export const SetSystemFoldersNotification = { - None: 0, - Sent: 1, - Draft: 2, - Spam: 3, - Trash: 4, - Archive: 5 -}; - /** * @enum {number} */ diff --git a/dev/Model/FolderCollection.js b/dev/Model/FolderCollection.js index fee11b3bd..c94d2b807 100644 --- a/dev/Model/FolderCollection.js +++ b/dev/Model/FolderCollection.js @@ -37,7 +37,7 @@ const Inbox: 0, Sent: 0, Drafts: 0, - Spam: 0, + Junk: 0, // Spam Trash: 0, Archive: 0 }, @@ -62,7 +62,7 @@ const case FolderType.Trash: case FolderType.Archive: return i18n('FOLDER_LIST/' + getKeyByValue(FolderType, type).toUpperCase() + '_NAME'); - case FolderType.Spam: + case FolderType.Junk: return i18n('GLOBAL/SPAM'); // no default } @@ -104,11 +104,11 @@ export class FolderCollectionModel extends AbstractCollectionModel /* constructor() { super(); - this.CountRec - this.Namespace; - this.Optimized - this.SystemFolders - this.Capabilities + this.quotaUsage; + this.quotaLimit; + this.namespace; + this.optimized + this.capabilities } */ @@ -118,16 +118,13 @@ export class FolderCollectionModel extends AbstractCollectionModel */ static reviveFromJson(object) { const expandedFolders = Local.get(ClientSideKeyNameExpandedFolders); - if (object?.SystemFolders) { - forEachObjectEntry(SystemFolders, key => - SystemFolders[key] = SettingsGet(key+'Folder') || object.SystemFolders[FolderType[key]] - ); - } + + forEachObjectEntry(SystemFolders, (key, value) => + value || (SystemFolders[key] = SettingsGet(key+'Folder')) + ); const result = super.reviveFromJson(object, oFolder => { - let oCacheFolder = getFolderFromCacheList(oFolder.FullName), - type = FolderType[getKeyByValue(SystemFolders, oFolder.FullName)]; - + let oCacheFolder = getFolderFromCacheList(oFolder.FullName); if (oCacheFolder) { // oCacheFolder.revivePropertiesFromJson(oFolder); if (oFolder.Hash) { @@ -143,17 +140,59 @@ export class FolderCollectionModel extends AbstractCollectionModel oCacheFolder = FolderModel.reviveFromJson(oFolder); if (!oCacheFolder) return null; - - if (1 == type) { - oCacheFolder.type(type); - setFolderInboxName(oFolder.FullName); - } setFolder(oCacheFolder); } - if (1 < type) { - oCacheFolder.type(type); + // JMAP RFC 8621 + let role = oFolder.role; +/* + if (!role) { + // Kolab + let type = oFolder.metadata[FolderMetadataKeys.KolabFolderType] + || oFolder.metadata[FolderMetadataKeys.KolabFolderTypeShared]; + switch (type) { + case 'mail.inbox': + case 'mail.drafts': + role = type.replace('mail.', ''); + break; +// case 'mail.outbox': + case 'mail.sentitems': + role = 'sent'; + break; + case 'mail.junkemail': + role = 'spam'; + break; + case 'mail.wastebasket': + role = 'trash'; + break; + } + // Flags + if (oFolder.Flags.includes('\\sentmail')) { + role = 'sent'; + } + if (oFolder.Flags.includes('\\spam')) { + role = 'junk'; + } + if (oFolder.Flags.includes('\\bin')) { + role = 'trash'; + } + if (oFolder.Flags.includes('\\important')) { + role = 'important'; + } + if (oFolder.Flags.includes('\\starred')) { + role = 'flagged'; + } + if (oFolder.Flags.includes('\\all') || oFolder.Flags.includes('\\allmail')) { + role = 'all'; + } } +*/ + if (role) { + role = role[0].toUpperCase() + role.slice(1); + SystemFolders[role] || (SystemFolders[role] = oFolder.FullName); + } + + oCacheFolder.type(FolderType[getKeyByValue(SystemFolders, oFolder.FullName)] || 0); oCacheFolder.collapsed(!expandedFolders || !isArray(expandedFolders) @@ -162,12 +201,43 @@ export class FolderCollectionModel extends AbstractCollectionModel return oCacheFolder; }); + result.CountRec = result.length; + setFolderInboxName(SystemFolders.Inbox); + let i = result.length; if (i) { sortFolders(result); try { while (i--) { let folder = result[i], parent = getFolderFromCacheList(folder.parentName); + if (!parent) { + // Create NonExistent parent folders + let delimiter = folder.delimiter; + if (delimiter) { + let parents = folder.fullName.split(delimiter); + parents.pop(); + while (parents.length) { + let parentName = parents.join(delimiter), + name = parents.pop(), + pfolder = getFolderFromCacheList(parentName); + if (!pfolder) { + pfolder = FolderModel.reviveFromJson({ + '@Object': 'Object/Folder', + Name: name, + FullName: parentName, + Delimiter: delimiter, + Exists: false, + isSubscribed: false, + Flags: ['\\nonexistent'] + }); + setFolder(pfolder); + result.splice(i, 0, pfolder); + ++i; + } + } + parent = getFolderFromCacheList(folder.parentName); + } + } if (parent) { parent.subFolders.unshift(folder); result.splice(i,1); @@ -187,7 +257,7 @@ export class FolderCollectionModel extends AbstractCollectionModel if (!( SettingsGet('SentFolder') + SettingsGet('DraftsFolder') + - SettingsGet('SpamFolder') + + SettingsGet('JunkFolder') + SettingsGet('TrashFolder') + SettingsGet('ArchiveFolder') ) @@ -197,21 +267,21 @@ export class FolderCollectionModel extends AbstractCollectionModel FolderUserStore.folderList(this); - FolderUserStore.namespace = this.Namespace; + FolderUserStore.namespace = this.namespace; // 'THREAD=REFS', 'THREAD=REFERENCES', 'THREAD=ORDEREDSUBJECT' AppUserStore.threadsAllowed(!!( - Settings.app('useImapThread') && this.Capabilities.some(capa => capa.startsWith('THREAD=')) + Settings.app('useImapThread') && this.capabilities.some(capa => capa.startsWith('THREAD=')) )); - FolderUserStore.folderListOptimized(!!this.Optimized); +// FolderUserStore.folderListOptimized(!!this.optimized); FolderUserStore.quotaUsage(this.quotaUsage); FolderUserStore.quotaLimit(this.quotaLimit); - FolderUserStore.capabilities(this.Capabilities); + FolderUserStore.capabilities(this.capabilities); FolderUserStore.sentFolder(normalizeFolder(SystemFolders.Sent)); FolderUserStore.draftsFolder(normalizeFolder(SystemFolders.Drafts)); - FolderUserStore.spamFolder(normalizeFolder(SystemFolders.Spam)); + FolderUserStore.spamFolder(normalizeFolder(SystemFolders.Junk)); FolderUserStore.trashFolder(normalizeFolder(SystemFolders.Trash)); FolderUserStore.archiveFolder(normalizeFolder(SystemFolders.Archive)); @@ -238,7 +308,8 @@ export class FolderModel extends AbstractModel { addObservablesTo(this, { name: '', - type: FolderType.User, + type: 0, + role: null, selectable: false, focused: false, @@ -345,9 +416,9 @@ export class FolderModel extends AbstractModel { } ), - canBeEdited: () => FolderType.User === folder.type() && folder.exists/* && folder.selectable()*/, + canBeEdited: () => !folder.type() && folder.exists/* && folder.selectable()*/, - isSystemFolder: () => FolderType.User !== folder.type() + isSystemFolder: () => folder.type() | (FolderUserStore.allowKolab() && !!folder.kolabType() & !SettingsUserStore.unhideKolabFolders()), canBeSelected: () => folder.selectable() && !folder.isSystemFolder(), @@ -363,7 +434,7 @@ export class FolderModel extends AbstractModel { * Or when all below conditions are true: * - selectable() * - isSubscribed() OR hideUnsubscribed = false - * - FolderType.User + * - 0 == type() * - not kolabType() */ visible: () => { diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 0723e8266..123c221e2 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -117,7 +117,7 @@ FolderUserStore = new class { const subscribeRemoveSystemFolder = observable => { - observable.subscribe(() => getFolderFromCacheList(observable())?.type(FolderType.User), self, 'beforeChange'); + observable.subscribe(() => getFolderFromCacheList(observable())?.type(0), self, 'beforeChange'); }, fSetSystemFolderType = type => value => getFolderFromCacheList(value)?.type(type); @@ -130,7 +130,7 @@ FolderUserStore = new class { addSubscribablesTo(self, { sentFolder: fSetSystemFolderType(FolderType.Sent), draftsFolder: fSetSystemFolderType(FolderType.Drafts), - spamFolder: fSetSystemFolderType(FolderType.Spam), + spamFolder: fSetSystemFolderType(FolderType.Junk), trashFolder: fSetSystemFolderType(FolderType.Trash), archiveFolder: fSetSystemFolderType(FolderType.Archive) }); diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 8862785fe..0526d3330 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -8,7 +8,7 @@ import { import { ComposeType, EditorDefaultType, - SetSystemFoldersNotification + FolderType } from 'Common/EnumsUser'; import { pInt, isArray, arrayLength } from 'Common/Utils'; @@ -444,7 +444,7 @@ export class ComposePopupView extends AbstractViewPopup { } if (!sSentFolder) { - showScreenPopup(FolderSystemPopupView, [SetSystemFoldersNotification.Sent]); + showScreenPopup(FolderSystemPopupView, [FolderType.Sent]); } else try { this.sendError(false); this.sending(true); @@ -501,7 +501,7 @@ export class ComposePopupView extends AbstractViewPopup { saveCommand() { if (FolderUserStore.draftsFolderNotEnabled()) { - showScreenPopup(FolderSystemPopupView, [SetSystemFoldersNotification.Draft]); + showScreenPopup(FolderSystemPopupView, [FolderType.Drafts]); } else { this.savedError(false); this.saving(true); diff --git a/dev/View/Popup/FolderSystem.js b/dev/View/Popup/FolderSystem.js index 3b6994cbe..8c6d898d6 100644 --- a/dev/View/Popup/FolderSystem.js +++ b/dev/View/Popup/FolderSystem.js @@ -1,7 +1,7 @@ import ko from 'ko'; import { koComputable, addSubscribablesTo } from 'External/ko'; -import { SetSystemFoldersNotification } from 'Common/EnumsUser'; +import { FolderType } from 'Common/EnumsUser'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { defaultOptionsAfterRender } from 'Common/Utils'; import { folderListOptionsBuilder } from 'Common/Folders'; @@ -47,24 +47,24 @@ export class FolderSystemPopupView extends AbstractViewPopup { } /** - * @param {number=} notificationType = SetSystemFoldersNotification.None + * @param {number=} notificationType = 0 */ - onShow(notificationType = SetSystemFoldersNotification.None) { + onShow(notificationType = 0) { let notification = '', prefix = 'POPUPS_SYSTEM_FOLDERS/NOTIFICATION_'; switch (notificationType) { - case SetSystemFoldersNotification.Sent: + case FolderType.Sent: notification = i18n(prefix + 'SENT'); break; - case SetSystemFoldersNotification.Draft: + case FolderType.Drafts: notification = i18n(prefix + 'DRAFTS'); break; - case SetSystemFoldersNotification.Spam: + case FolderType.Junk: notification = i18n(prefix + 'SPAM'); break; - case SetSystemFoldersNotification.Trash: + case FolderType.Trash: notification = i18n(prefix + 'TRASH'); break; - case SetSystemFoldersNotification.Archive: + case FolderType.Archive: notification = i18n(prefix + 'ARCHIVE'); break; // no default diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index d50494eba..b591406a1 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -364,11 +364,11 @@ export class MailMessageList extends AbstractViewRight { } spamCommand() { - moveMessagesToFolderType(FolderType.Spam); + moveMessagesToFolderType(FolderType.Junk); } notSpamCommand() { - moveMessagesToFolderType(FolderType.NotSpam); + moveMessagesToFolderType(FolderType.Inbox); } moveCommand(vm, event) { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 2b13bbf51..3bd14da25 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -220,8 +220,8 @@ export class MailMessageView extends AbstractViewRight { this.deleteCommand = createCommandActionHelper(FolderType.Trash); this.deleteWithoutMoveCommand = createCommandActionHelper(FolderType.Trash, true); this.archiveCommand = createCommandActionHelper(FolderType.Archive); - this.spamCommand = createCommandActionHelper(FolderType.Spam); - this.notSpamCommand = createCommandActionHelper(FolderType.NotSpam); + this.spamCommand = createCommandActionHelper(FolderType.Junk); + this.notSpamCommand = createCommandActionHelper(FolderType.Inbox); decorateKoCommands(this, { editCommand: self => self.messageVisibility(), diff --git a/integrations/nextcloud/snappymail/lib/Search/Provider.php b/integrations/nextcloud/snappymail/lib/Search/Provider.php index c52a8912e..7b9b63157 100644 --- a/integrations/nextcloud/snappymail/lib/Search/Provider.php +++ b/integrations/nextcloud/snappymail/lib/Search/Provider.php @@ -99,7 +99,7 @@ class Provider implements IProvider } $search = \rawurlencode($oParams->sSearch); -// $MessageCollection->MessageResultCount; +// $MessageCollection->totalEmails; foreach ($MessageCollection as $Message) { // $Message instanceof \MailSo\Mail\Message $result[] = new SearchResultEntry( diff --git a/plugins/README.md b/plugins/README.md index 9e6219fbc..9561dd5cf 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -218,28 +218,6 @@ $Plugin->addHook('hook.name', 'functionName'); bool $bSuccess \MailSo\Smtp\Settings $oSettings -## Folders - -### filter.folders-post - params: - \RainLoop\Model\Account $oAccount - \MailSo\Mail\FolderCollection $oFolderCollection - -### filter.folders-complete - params: - \RainLoop\Model\Account $oAccount - \MailSo\Mail\FolderCollection $oFolderCollection - -### filter.folders-system-types - params: - \RainLoop\Model\Account $oAccount - array &$aList - -### filter.system-folders-names - params: - \RainLoop\Model\Account $oAccount - array &$aCache - ## Json service actions Called by RainLoop\ServiceActions::ServiceJson() {actionname} is one of the RainLoop\Actions::Do{ActionName}(), diff --git a/plugins/mailbox-detect/index.php b/plugins/mailbox-detect/index.php new file mode 100644 index 000000000..861627888 --- /dev/null +++ b/plugins/mailbox-detect/index.php @@ -0,0 +1,219 @@ +addHook('json.after-folders', 'AfterFolders'); + } + + protected function configMapping() : array + { + return array( + \RainLoop\Plugins\Property::NewInstance('autocreate_system_folders')->SetLabel('Autocreate system folders') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDefaultValue(false), + ); + } + + public function AfterFolders(array &$aResponse) + { + if (!empty($aResponse['Result']['@Collection'])) { + $oActions = \RainLoop\Api::Actions(); + $oAccount = $oActions->getAccountFromToken(); + if (!$oAccount) { + \error_log('No Account'); + return; + } + $oSettingsLocal = $oActions->SettingsProvider(true)->Load($oAccount); + $roles = [ + 'inbox' => false, + 'sent' => !!$oSettingsLocal->GetConf('SentFolder', ''), + 'drafts' => !!$oSettingsLocal->GetConf('DraftFolder', ''), + 'junk' => !!$oSettingsLocal->GetConf('SpamFolder', ''), + 'trash' => !!$oSettingsLocal->GetConf('TrashFolder', ''), + 'archive' => !!$oSettingsLocal->GetConf('ArchiveFolder', '') + ]; + $types = [ + FolderType::SENT => 'sent', + FolderType::DRAFTS => 'drafts', + FolderType::JUNK => 'junk', + FolderType::TRASH => 'trash', + FolderType::ARCHIVE => 'archive' + ]; + $found = [ + 'inbox' => [], + 'sent' => [], + 'drafts' => [], + 'junk' => [], + 'trash' => [], + 'archive' => [] + ]; + $aMap = $this->systemFoldersNames($oAccount); + $sDelimiter = ''; + foreach ($aResponse['Result']['@Collection'] as $i => $folder) { + $sDelimiter || ($sDelimiter = $folder['Delimiter']); + if ($folder['role']) { + $roles[$folder['role']] = true; + } else if (\in_array('\\sentmail', $folder['Flags'])) { + $found['sent'][] = $i; + } else if (\in_array('\\spam', $folder['Flags'])) { + $found['junk'][] = $i; + } else if (\in_array('\\bin', $folder['Flags'])) { + $found['trash'][] = $i; + } else if (\in_array('\\starred', $folder['Flags'])) { + $found['flagged'][] = $i; + } else { + // Kolab + $kolab = $folder['Metadata'][MetadataKeys::KOLAB_CTYPE] + ?? $folder['Metadata'][MetadataKeys::KOLAB_CTYPE_SHARED] + ?? ''; + if ('mail.inbox' === $kolab) { + $found['inbox'][] = $i; + } else if ('mail.sentitems' === $kolab /*|| 'mail.outbox' === $kolab*/) { + $found['sent'][] = $i; + } else if ('mail.drafts' === $kolab) { + $found['drafts'][] = $i; + } else if ('mail.junkemail' === $kolab) { + $found['junk'][] = $i; + } else if ('mail.wastebasket' === $kolab) { + $found['trash'][] = $i; + } else { + $iFolderType = 0; + if (isset($aMap[$folder['FullName']])) { + $iFolderType = $aMap[$folder['FullName']]; + } else if (isset($aMap[$folder['name']]) || isset($aMap["INBOX{$folder['Delimiter']}{$folder['name']}"])) { + $iFolderType = $aMap[$folder['name']]; + } + if ($iFolderType && isset($types[$iFolderType])) { + $found[$types[$iFolderType]][] = $i; + } + } + } + } + foreach ($roles as $role => $hasRole) { + if ($hasRole) { + unset($found[$role]); + } + } + if ($found) { + foreach ($found as $role => $folders) { + if (isset($folders[0])) { + // Set the first as default +// \error_log("Set role {$role}"); + $aResponse['Result']['@Collection'][$folders[0]]['role'] = $role; + } else if ($this->Config()->Get('plugin', 'autocreate_system_folders', false)) { + try + { + $sParent = \substr($aResponse['Result']['namespace'], 0, -1); + $sFolderNameToCreate = \ucfirst($role); +/* + $this->Manager()->RunHook('filter.folders-system-types', array($oAccount, &$aList)); + $iPos = \strrpos($sFolderNameToCreate, $sDelimiter); + if (false !== $iPos) { + $mNewParent = \substr($sFolderNameToCreate, 0, $iPos); + $mNewFolderNameToCreate = \substr($sFolderNameToCreate, $iPos + 1); + if (\strlen($mNewFolderNameToCreate)) { + $sFolderNameToCreate = $mNewFolderNameToCreate; + } + + if (\strlen($mNewParent)) { + $sParent = \strlen($sParent) ? $sParent.$sDelimiter.$mNewParent : $mNewParent; + } + } +*/ +// \error_log("Create mailbox {$sFolderNameToCreate}"); + $oFolder = $oActions->MailClient()->FolderCreate( + $sFolderNameToCreate, + $sParent, + true, + $sDelimiter + ); + $aResponse['Result']['@Collection'][] = \json_encode($oFolder); + } + catch (\Throwable $oException) + { + $this->Logger()->WriteException($oException); + } + } + } + } + } + } + + /** + * @staticvar array $aCache + */ + private function systemFoldersNames(\RainLoop\Model\Account $oAccount) : array + { + static $aCache = null; + if (null === $aCache) { + $aCache = array( + 'Sent' => FolderType::SENT, + 'Send' => FolderType::SENT, + 'Outbox' => FolderType::SENT, + 'Out box' => FolderType::SENT, + 'Sent Item' => FolderType::SENT, + 'Sent Items' => FolderType::SENT, + 'Send Item' => FolderType::SENT, + 'Send Items' => FolderType::SENT, + 'Sent Mail' => FolderType::SENT, + 'Sent Mails' => FolderType::SENT, + 'Send Mail' => FolderType::SENT, + 'Send Mails' => FolderType::SENT, + + 'Drafts' => FolderType::DRAFTS, + 'Draft' => FolderType::DRAFTS, + 'Draft Mail' => FolderType::DRAFTS, + 'Draft Mails' => FolderType::DRAFTS, + 'Drafts Mail' => FolderType::DRAFTS, + 'Drafts Mails' => FolderType::DRAFTS, + + 'Junk' => FolderType::JUNK, + 'Junk E-mail' => FolderType::JUNK, + 'Spam' => FolderType::JUNK, + 'Spams' => FolderType::JUNK, + 'Bulk Mail' => FolderType::JUNK, + 'Bulk Mails' => FolderType::JUNK, + + 'Trash' => FolderType::TRASH, + 'Deleted' => FolderType::TRASH, + 'Deleted Items' => FolderType::TRASH, + 'Bin' => FolderType::TRASH, + + 'Archive' => FolderType::ARCHIVE, + 'Archives' => FolderType::ARCHIVE, + + 'All' => FolderType::ALL, + 'All Mail' => FolderType::ALL, + 'All Mails' => FolderType::ALL, + ); + + $aNewCache = array(); + foreach ($aCache as $sKey => $iType) { + $aNewCache[$sKey] = $iType; + $aNewCache[\str_replace(' ', '', $sKey)] = $iType; + } + + $aCache = $aNewCache; + + $this->Manager()->RunHook('filter.system-folders-names', array($oAccount, &$aCache)); + } + + return $aCache; + } +} 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 e59571956..67ba399ef 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 @@ -11,7 +11,6 @@ namespace MailSo\Imap; -use MailSo\Imap\Enumerations\FolderType; use MailSo\Imap\Enumerations\MetadataKeys; /** @@ -135,45 +134,27 @@ class Folder implements \JsonSerializable // JMAP RFC 8621 public function Role() : ?string { - $aFlags = $this->aFlagsLowerCase; - $aFlags[] = \strtolower($this->GetMetadata(MetadataKeys::SPECIALUSE)); - - $match = \array_intersect([ - '\\inbox', - '\\all', // '\\allmail' - '\\archive', - '\\drafts', - '\\flagged', // '\\starred' - '\\important', - '\\junk', // '\\spam' - '\\sent', // '\\sentmail' - '\\trash', // '\\bin' - ], $aFlags); - if ($match) { - return \substr(\array_shift($match), 1); - } - - if ('INBOX' === \strtoupper($this->FolderName)) { - return 'inbox'; - } -/* - // Kolab - $type = $this->GetMetadata(MetadataKeys::KOLAB_CTYPE) ?: $this->GetMetadata(MetadataKeys::KOLAB_CTYPE_SHARED); - switch ($type) { - case 'mail.inbox': + $role = \strtolower($this->GetMetadata(MetadataKeys::SPECIALUSE) ?: ''); + if (!$role) { + $match = \array_intersect([ + '\\inbox', + '\\all', // '\\allmail' + '\\archive', + '\\drafts', + '\\flagged', // '\\starred' + '\\important', + '\\junk', // '\\spam' + '\\sent', // '\\sentmail' + '\\trash', // '\\bin' + ], $this->aFlagsLowerCase); + if ($match) { + $role = \array_shift($match); + } + if (!$role && 'INBOX' === \strtoupper($this->FolderName)) { return 'inbox'; -// case 'mail.outbox': - case 'mail.sentitems': - return 'sent'; - case 'mail.drafts': - return 'drafts'; - case 'mail.junkemail': - return 'junk'; - case 'mail.wastebasket': - return 'trash'; + } } -*/ - return null; + return $role ? \ltrim($role, '\\') : null; } public function Hash(string $sClientHash) : ?string @@ -181,94 +162,6 @@ class Folder implements \JsonSerializable return $this->getHash($sClientHash); } - public function GetType() : int - { - $aFlags = $this->aFlagsLowerCase; - // RFC 6154 -// $aFlags[] = \strtolower($this->GetMetadata(MetadataKeys::SPECIALUSE)); - - switch (true) - { - case $this->IsInbox(): - return FolderType::INBOX; - - case \in_array('\\sent', $this->aFlagsLowerCase): - case \in_array('\\sentmail', $this->aFlagsLowerCase): - return FolderType::SENT; - - case \in_array('\\drafts', $this->aFlagsLowerCase): - return FolderType::DRAFTS; - - case \in_array('\\junk', $this->aFlagsLowerCase): - case \in_array('\\spam', $this->aFlagsLowerCase): - return FolderType::JUNK; - - case \in_array('\\trash', $this->aFlagsLowerCase): - case \in_array('\\bin', $this->aFlagsLowerCase): - return FolderType::TRASH; - - case \in_array('\\important', $this->aFlagsLowerCase): - return FolderType::IMPORTANT; - - case \in_array('\\flagged', $this->aFlagsLowerCase): - case \in_array('\\starred', $this->aFlagsLowerCase): - return FolderType::FLAGGED; - - case \in_array('\\archive', $this->aFlagsLowerCase): - return FolderType::ARCHIVE; - - case \in_array('\\all', $this->aFlagsLowerCase): - case \in_array('\\allmail', $this->aFlagsLowerCase): - return FolderType::ALL; - - // TODO -// case 'Templates' === $this->FullName(): -// return FolderType::TEMPLATES; - } - - // Kolab - $type = $this->GetMetadata(MetadataKeys::KOLAB_CTYPE) ?: $this->GetMetadata(MetadataKeys::KOLAB_CTYPE_SHARED); - switch ($type) - { -/* - // TODO: Kolab - case 'event': - case 'event.default': - return FolderType::CALENDAR; - case 'contact': - case 'contact.default': - return FolderType::CONTACTS; - case 'task': - case 'task.default': - return FolderType::TASKS; - case 'note': - case 'note.default': - return FolderType::NOTES; - case 'file': - case 'file.default': - return FolderType::FILES; - case 'configuration': - return FolderType::CONFIGURATION; - case 'journal': - case 'journal.default': - return FolderType::JOURNAL; -*/ - case 'mail.inbox': - return FolderType::INBOX; -// case 'mail.outbox': - case 'mail.sentitems': - return FolderType::SENT; - case 'mail.drafts': - return FolderType::DRAFTS; - case 'mail.junkemail': - return FolderType::JUNK; - case 'mail.wastebasket': - return FolderType::TRASH; - } - - return FolderType::USER; - } - #[\ReturnTypeWillChange] public function jsonSerialize() { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php index c36039593..9c6265997 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/FolderCollection.php @@ -17,7 +17,7 @@ namespace MailSo\Mail; */ class FolderCollection extends \MailSo\Base\Collection { - public bool $Optimized = false; +// public bool $Optimized = false; public function append($oFolder, bool $bToTop = false) : void { @@ -30,4 +30,14 @@ class FolderCollection extends \MailSo\Base\Collection $oFolder = $this['INBOX'] ?? $this[0] ?? null; return $oFolder ? $oFolder->Delimiter() : '/'; } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array( + '@Object' => 'Collection/FolderCollection', + '@Collection' => $this->getArrayCopy(), +// 'optimized' => $this->Optimized + ); + } } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index f26456c2a..822fc01f0 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -933,29 +933,11 @@ class MailClient return null; } - foreach ($aFolders as $sFullName => /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder) { - if (($bUseListSubscribeStatus && (null === $aImapSubscribedFoldersHelper || \in_array($sFullName, $aImapSubscribedFoldersHelper))) || $oImapFolder->IsInbox()) { - $oImapFolder->setSubscribed(); - } - - // Add NonExistent folders - $sDelimiter = $oImapFolder->Delimiter(); - $aFolderExplode = \explode($sDelimiter, $sFullName); - \array_pop($aFolderExplode); - while ($aFolderExplode) { - $sFullName = \implode($sDelimiter, $aFolderExplode); - if (!isset($aFolders[$sFullName])) { - try - { - $aFolders[$sFullName] = - new \MailSo\Imap\Folder($sFullName, $sDelimiter, ['\\Noselect', '\\NonExistent']); - } - catch (\Throwable $oExc) - { - unset($oExc); - } + if ($bUseListSubscribeStatus) { + foreach ($aFolders as $sFullName => /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder) { + if (null === $aImapSubscribedFoldersHelper || \in_array($sFullName, $aImapSubscribedFoldersHelper)) { + $oImapFolder->setSubscribed(); } - \array_pop($aFolderExplode); } } 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 6ba5e7b10..9188c194d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -759,7 +759,7 @@ class Actions if ($oSettingsLocal instanceof Settings) { $aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', ''); $aResult['DraftsFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', ''); - $aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', ''); + $aResult['JunkFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', ''); $aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', ''); $aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', ''); $aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']); 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 411f0572b..65831c1d9 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 @@ -216,7 +216,7 @@ trait Accounts $oConfig = $this->Config(); $aResult['SentFolder'] = (string) $oSettingsLocal->GetConf('SentFolder', ''); $aResult['DraftsFolder'] = (string) $oSettingsLocal->GetConf('DraftFolder', ''); - $aResult['SpamFolder'] = (string) $oSettingsLocal->GetConf('SpamFolder', ''); + $aResult['JunkFolder'] = (string) $oSettingsLocal->GetConf('SpamFolder', ''); $aResult['TrashFolder'] = (string) $oSettingsLocal->GetConf('TrashFolder', ''); $aResult['ArchiveFolder'] = (string) $oSettingsLocal->GetConf('ArchiveFolder', ''); $aResult['HideUnsubscribed'] = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', false); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php index d59836205..d68769976 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php @@ -10,11 +10,6 @@ use MailSo\Imap\Enumerations\FolderType; trait Folders { - private function getFolderCollection(bool $HideUnsubscribed) : ?\MailSo\Mail\FolderCollection - { - return $this->MailClient()->Folders('', '*', $HideUnsubscribed); - } - /** * Appends uploaded rfc822 message to mailbox * @throws \MailSo\RuntimeException @@ -56,125 +51,32 @@ trait Folders $HideUnsubscribed = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', $HideUnsubscribed); } - $oFolderCollection = $this->getFolderCollection($HideUnsubscribed); + $oFolderCollection = $this->MailClient()->Folders('', '*', $HideUnsubscribed); if ($oFolderCollection) { - $sNamespace = $this->MailClient()->GetPersonalNamespace(); - - $this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection)); - - $aSystemFolders = array(); - $this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders); - - if ($this->Config()->Get('labs', 'autocreate_system_folders', false)) { - $bDoItAgain = false; - - $sParent = \substr($sNamespace, 0, -1); - - $sDelimiter = $oFolderCollection->FindDelimiter(); - - $aList = array(); - $aMap = $this->systemFoldersNames($oAccount); - - if ('' === $oSettingsLocal->GetConf('SentFolder', '')) { - $aList[] = FolderType::SENT; - } - - if ('' === $oSettingsLocal->GetConf('DraftFolder', '')) { - $aList[] = FolderType::DRAFTS; - } - - if ('' === $oSettingsLocal->GetConf('SpamFolder', '')) { - $aList[] = FolderType::JUNK; - } - - if ('' === $oSettingsLocal->GetConf('TrashFolder', '')) { - $aList[] = FolderType::TRASH; - } - - if ('' === $oSettingsLocal->GetConf('ArchiveFolder', '')) { - $aList[] = FolderType::ARCHIVE; - } - - $this->Plugins()->RunHook('filter.folders-system-types', array($oAccount, &$aList)); - - foreach ($aList as $iType) { - if (!isset($aSystemFolders[$iType])) { - $mFolderNameToCreate = \array_search($iType, $aMap); - if (!empty($mFolderNameToCreate)) { - $iPos = \strrpos($mFolderNameToCreate, $sDelimiter); - if (false !== $iPos) { - $mNewParent = \substr($mFolderNameToCreate, 0, $iPos); - $mNewFolderNameToCreate = \substr($mFolderNameToCreate, $iPos + 1); - if (\strlen($mNewFolderNameToCreate)) { - $mFolderNameToCreate = $mNewFolderNameToCreate; - } - - if (\strlen($mNewParent)) { - $sParent = \strlen($sParent) ? $sParent.$sDelimiter.$mNewParent : $mNewParent; - } - } - - $sFullNameToCheck = $mFolderNameToCreate; - if (\strlen($sParent)) { - $sFullNameToCheck = $sParent.$sDelimiter.$sFullNameToCheck; - } - - if (!isset($oFolderCollection[$sFullNameToCheck])) { - try - { - $this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter); - $bDoItAgain = true; - } - catch (\Throwable $oException) - { - $this->Logger()->WriteException($oException); - } - } - } - } - } - - if ($bDoItAgain) { - $oFolderCollection = $this->getFolderCollection($HideUnsubscribed); - - if ($oFolderCollection) { - $aSystemFolders = array(); - $this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders); - } + $aQuota = null; + if ($this->GetCapa(Capa::QUOTA)) { + try { +// $aQuota = $this->MailClient()->Quota(); + $aQuota = $this->MailClient()->QuotaRoot(); + } catch (\Throwable $oException) { + // ignore } } - if ($oFolderCollection) { - $this->Plugins()->RunHook('filter.folders-complete', array($oAccount, $oFolderCollection)); + $aCapabilities = \array_values(\array_filter($this->MailClient()->Capability(), function ($item) { + return !\preg_match('/^(IMAP|AUTH|LOGIN|SASL)/', $item); + })); - $aQuota = null; - if ($this->GetCapa(Capa::QUOTA)) { - try { -// $aQuota = $this->MailClient()->Quota(); - $aQuota = $this->MailClient()->QuotaRoot(); - } catch (\Throwable $oException) { - // ignore - } - } - - $aCapabilities = \array_filter($this->MailClient()->Capability(), function ($item) { - return !\preg_match('/^(IMAP|AUTH|LOGIN|SASL)/', $item); - }); - - $oFolderCollection = \array_merge( - $oFolderCollection->jsonSerialize(), - array( - 'quotaUsage' => $aQuota ? $aQuota[0] * 1024 : null, - 'quotaLimit' => $aQuota ? $aQuota[1] * 1024 : null, - 'Namespace' => $sNamespace, - 'Optimized' => $oFolderCollection->Optimized, - 'CountRec' => $oFolderCollection->count(), - 'SystemFolders' => empty($aSystemFolders) ? null : $aSystemFolders, - 'Capabilities' => \array_values($aCapabilities) - ) - ); - } + $oFolderCollection = \array_merge( + $oFolderCollection->jsonSerialize(), + array( + 'quotaUsage' => $aQuota ? $aQuota[0] * 1024 : null, + 'quotaLimit' => $aQuota ? $aQuota[1] * 1024 : null, + 'namespace' => $this->MailClient()->GetPersonalNamespace(), + 'capabilities' => $aCapabilities + ) + ); } return $this->DefaultResponse(__FUNCTION__, $oFolderCollection); @@ -439,107 +341,4 @@ trait Folders return $this->DefaultResponse(__FUNCTION__, $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } - - private function recFoldersTypes(\RainLoop\Model\Account $oAccount, \MailSo\Mail\FolderCollection $oFolders, array &$aResult, bool $bListFolderTypes = true) : void - { - if ($oFolders->count()) { - if ($bListFolderTypes) { - $types = array( - FolderType::INBOX, - FolderType::SENT, - FolderType::DRAFTS, - FolderType::JUNK, - FolderType::TRASH, - FolderType::ARCHIVE - ); - foreach ($oFolders as $oFolder) { - $iFolderType = $oFolder->GetType(); - if (!isset($aResult[$iFolderType]) && \in_array($iFolderType, $types)) { - $aResult[$iFolderType] = $oFolder->FullName(); - } - } - } - - $aMap = $this->systemFoldersNames($oAccount); - foreach ($oFolders as $oFolder) { - $sName = $oFolder->Name(); - $sFullName = $oFolder->FullName(); - if (isset($aMap[$sName]) || isset($aMap[$sFullName])) { - $iFolderType = isset($aMap[$sName]) ? $aMap[$sName] : $aMap[$sFullName]; - if ((!isset($aResult[$iFolderType]) || $sName === $sFullName || "INBOX{$oFolder->Delimiter()}{$sName}" === $sFullName) && \in_array($iFolderType, $types)) { - $aResult[$iFolderType] = $oFolder->FullName(); - } - } - } - } - } - - /** - * @staticvar array $aCache - */ - private function systemFoldersNames(\RainLoop\Model\Account $oAccount) : array - { - static $aCache = null; - if (null === $aCache) { - $aCache = array( - - 'Sent' => FolderType::SENT, - 'Send' => FolderType::SENT, - - 'Outbox' => FolderType::SENT, - 'Out box' => FolderType::SENT, - - 'Sent Item' => FolderType::SENT, - 'Sent Items' => FolderType::SENT, - 'Send Item' => FolderType::SENT, - 'Send Items' => FolderType::SENT, - 'Sent Mail' => FolderType::SENT, - 'Sent Mails' => FolderType::SENT, - 'Send Mail' => FolderType::SENT, - 'Send Mails' => FolderType::SENT, - - 'Drafts' => FolderType::DRAFTS, - - 'Draft' => FolderType::DRAFTS, - 'Draft Mail' => FolderType::DRAFTS, - 'Draft Mails' => FolderType::DRAFTS, - 'Drafts Mail' => FolderType::DRAFTS, - 'Drafts Mails' => FolderType::DRAFTS, - - 'Junk E-mail' => FolderType::JUNK, - - 'Spam' => FolderType::JUNK, - 'Spams' => FolderType::JUNK, - - 'Junk' => FolderType::JUNK, - 'Bulk Mail' => FolderType::JUNK, - 'Bulk Mails' => FolderType::JUNK, - - 'Deleted Items' => FolderType::TRASH, - - 'Trash' => FolderType::TRASH, - 'Deleted' => FolderType::TRASH, - 'Bin' => FolderType::TRASH, - - 'Archive' => FolderType::ARCHIVE, - 'Archives' => FolderType::ARCHIVE, - - 'All' => FolderType::ALL, - 'All Mail' => FolderType::ALL, - 'All Mails' => FolderType::ALL, - ); - - $aNewCache = array(); - foreach ($aCache as $sKey => $iType) { - $aNewCache[$sKey] = $iType; - $aNewCache[\str_replace(' ', '', $sKey)] = $iType; - } - - $aCache = $aNewCache; - - $this->Plugins()->RunHook('filter.system-folders-names', array($oAccount, &$aCache)); - } - - return $aCache; - } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 6cda116c3..e10b355cf 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -370,7 +370,6 @@ Enables caching in the system'), 'labs' => array( 'cache_system_data' => array(true), 'date_from_headers' => array(true), - 'autocreate_system_folders' => array(false), 'allow_message_append' => array(false), 'login_fault_delay' => array(1), 'log_ajax_response_write_limit' => array(300),