From 42127c51abce3e94e7fead691c8fc6c5133734bf Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Wed, 14 Sep 2016 15:57:49 -0400 Subject: [PATCH] (feat) added IMAP folders subscriptions management (fixes #255) --- ActiveSync/SOGoActiveSyncDispatcher.m | 2 +- NEWS | 2 +- SoObjects/Mailer/SOGoMailAccount.h | 14 ++- SoObjects/Mailer/SOGoMailAccount.m | 97 ++++++++++--------- SoObjects/Mailer/SOGoMailFolder.m | 2 +- UI/MailerUI/English.lproj/Localizable.strings | 5 +- UI/MailerUI/GNUmakefile | 3 +- UI/MailerUI/UIxMailAccountActions.m | 23 ++++- UI/MailerUI/UIxMailFolderActions.m | 66 ++++++++----- UI/MailerUI/UIxMailFolderSubscriptions.h | 28 ++++++ UI/MailerUI/UIxMailFolderSubscriptions.m | 42 ++++++++ UI/MailerUI/product.plist | 9 ++ .../MailerUI/UIxMailFolderSubscriptions.wox | 50 ++++++++++ UI/Templates/MailerUI/UIxMailMainFrame.wox | 44 ++++++--- .../js/Mailer/Account.service.js | 10 +- .../js/Mailer/Mailbox.service.js | 16 ++- .../js/Mailer/MailboxesController.js | 53 ++++++++-- 17 files changed, 362 insertions(+), 104 deletions(-) create mode 100644 UI/MailerUI/UIxMailFolderSubscriptions.h create mode 100644 UI/MailerUI/UIxMailFolderSubscriptions.m create mode 100644 UI/Templates/MailerUI/UIxMailFolderSubscriptions.wox diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index b9612c229..d7cfd2094 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -813,7 +813,7 @@ void handle_eas_terminate(int signum) } allFoldersMetadata = [NSMutableArray array]; - [self _flattenFolders: [accountFolder allFoldersMetadata] into: allFoldersMetadata parent: nil parentType: nil]; + [self _flattenFolders: [accountFolder allFoldersMetadata: SOGoMailStandardListing] into: allFoldersMetadata parent: nil parentType: nil]; // Get GUIDs of folder (IMAP) // e.g. {folderINBOX = folder6b93c528176f1151c7260000aef6df92} diff --git a/NEWS b/NEWS index c33253eef..98381a154 100644 --- a/NEWS +++ b/NEWS @@ -2,7 +2,7 @@ ------------------ New features - - + - [web] added IMAP folder subscriptions management (#255) Enhancements - [web] don't allow a recurrence rule to end before the first occurrence diff --git a/SoObjects/Mailer/SOGoMailAccount.h b/SoObjects/Mailer/SOGoMailAccount.h index 3daee9e2a..fd1f76056 100644 --- a/SoObjects/Mailer/SOGoMailAccount.h +++ b/SoObjects/Mailer/SOGoMailAccount.h @@ -1,6 +1,5 @@ /* - Copyright (C) 2009-2014 Inverse inc. - Copyright (C) 2004-2005 SKYRIX Software AG + Copyright (C) 2009-2016 Inverse inc. This file is part of SOGo. @@ -35,6 +34,7 @@ */ @class NSArray; +@class NSMutableDictionary; @class NSMutableArray; @class NSString; @@ -50,6 +50,11 @@ typedef enum { rfc4314 } SOGoIMAPAclStyle; +typedef enum { + SOGoMailStandardListing = 0, + SOGoMailSubscriptionsManagementListing = 1 +} SOGoMailListingMode; + @interface SOGoMailAccount : SOGoMailBaseObject { SOGoMailFolder *inboxFolder; @@ -61,6 +66,7 @@ typedef enum { NSMutableArray *identities; NSString *otherUsersFolderName; NSString *sharedFoldersName; + NSMutableDictionary *subscribedFolders; } - (SOGoIMAPAclStyle) imapAclStyle; @@ -83,8 +89,8 @@ typedef enum { /* folder pathes */ - (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs; -- (NSArray *) allFolderPaths; -- (NSArray *) allFoldersMetadata; +- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode; +- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode; - (NSDictionary *) imapFolderGUIDs; diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index a73a9f072..1b2711139 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -1,5 +1,4 @@ /* - Copyright (C) 2004-2005 SKYRIX Software AG Copyright (C) 2007-2016 Inverse inc. This file is part of SOGo. @@ -25,7 +24,6 @@ #import #import - #import #import #import @@ -73,6 +71,7 @@ static NSString *inboxFolderName = @"INBOX"; identities = nil; otherUsersFolderName = nil; sharedFoldersName = nil; + subscribedFolders = nil; } return self; @@ -88,11 +87,10 @@ static NSString *inboxFolderName = @"INBOX"; [identities release]; [otherUsersFolderName release]; [sharedFoldersName release]; + [subscribedFolders release]; [super dealloc]; } -/* listing the available folders */ - - (BOOL) isInDraftsFolder { return NO; @@ -116,8 +114,6 @@ static NSString *inboxFolderName = @"INBOX"; } } -/* namespaces */ - - (void) _appendNamespaces: (NSMutableArray *) folders { NSDictionary *namespaceDict; @@ -342,30 +338,45 @@ static NSString *inboxFolderName = @"INBOX"; // // // -- (NSArray *) allFolderPaths +- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode { NSMutableArray *folderPaths, *namespaces; NSArray *folders, *mainFolders; - SOGoUserDefaults *ud; NSString *namespace; BOOL subscribedOnly; int count, max; - ud = [[context activeUser] userDefaults]; - subscribedOnly = [ud mailShowSubscribedFoldersOnly]; + if (theListingMode == SOGoMailStandardListing) + subscribedOnly = [[[context activeUser] userDefaults] mailShowSubscribedFoldersOnly]; + else + { + subscribedOnly = NO; + DESTROY(subscribedFolders); + subscribedFolders = [[NSMutableDictionary alloc] init]; + folders = [[self imap4Connection] allFoldersForURL: [self imap4URL] + onlySubscribedFolders: YES]; + max = [folders count]; + for (count = 0; count < max; count++) + { + [subscribedFolders setObject: [NSNull null] + forKey: [folders objectAtIndex: count]]; + } + [[self imap4Connection] flushFolderHierarchyCache]; + } mainFolders = [[NSArray arrayWithObjects: [self inboxFolderNameInContext: context], [self draftsFolderNameInContext: context], [self sentFolderNameInContext: context], [self trashFolderNameInContext: context], - [self junkFolderNameInContext: context], - nil] stringsWithFormat: @"/%@"]; + [self junkFolderNameInContext: context], + nil] stringsWithFormat: @"/%@"]; folders = [[self imap4Connection] allFoldersForURL: [self imap4URL] - onlySubscribedFolders: subscribedOnly]; + onlySubscribedFolders: subscribedOnly]; folderPaths = [folders mutableCopy]; [folderPaths autorelease]; + [folderPaths removeObjectsInArray: mainFolders]; namespaces = [NSMutableArray arrayWithCapacity: 10]; [self _appendNamespaces: namespaces]; @@ -441,6 +452,9 @@ static NSString *inboxFolderName = @"INBOX"; return folderType; } +// +// +// - (NSMutableDictionary *) _insertFolder: (NSString *) folderPath foldersList: (NSMutableArray *) theFolders { @@ -449,8 +463,9 @@ static NSString *inboxFolderName = @"INBOX"; NSMutableDictionary *currentFolder, *parentFolder, *folder; NSString *currentFolderName, *currentPath, *fullName, *folderType; SOGoUserManager *userManager; + + BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed; int i, j, count; - BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder; parentFolder = nil; parentIsOtherUsersFolder = NO; @@ -469,13 +484,9 @@ static NSString *inboxFolderName = @"INBOX"; // Search for the current path in the children of the parent folder. // For the first iteration, take the parent folder passed as argument. if (parentFolder) - { - folders = [parentFolder objectForKey: @"children"]; - } + folders = [parentFolder objectForKey: @"children"]; else - { - folders = theFolders; - } + folders = theFolders; for (j = 0; j < [folders count]; j++) { @@ -485,10 +496,9 @@ static NSString *inboxFolderName = @"INBOX"; folder = currentFolder; // Make sure all branches are ready to receive children if (!last && ![folder objectForKey: @"children"]) - { [folder setObject: [NSMutableArray array] forKey: @"children"]; - } - break; + + break; } } @@ -496,18 +506,13 @@ static NSString *inboxFolderName = @"INBOX"; currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName]; if (otherUsersFolderName && [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame) - { - isOtherUsersFolder = YES; - } + isOtherUsersFolder = YES; else - { - isOtherUsersFolder = NO; - } + isOtherUsersFolder = NO; if (folder == nil) { // Folder was not found; create it and add it to the folders list - if (parentIsOtherUsersFolder) { // Parent folder is the "Other users" folder; translate the user's mailbox name @@ -519,14 +524,10 @@ static NSString *inboxFolderName = @"INBOX"; currentFolderName = fullName; } else if (isOtherUsersFolder) - { - currentFolderName = [self labelForKey: @"OtherUsersFolderName"]; - } + currentFolderName = [self labelForKey: @"OtherUsersFolderName"]; else if (sharedFoldersName && [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame) - { - currentFolderName = [self labelForKey: @"SharedFoldersName"]; - } + currentFolderName = [self labelForKey: @"SharedFoldersName"]; flags = [NSMutableArray array];; @@ -536,13 +537,19 @@ static NSString *inboxFolderName = @"INBOX"; else folderType = @"additional"; + if ([subscribedFolders objectForKey: folderPath]) + isSubscribed = YES; + else + isSubscribed = NO; + folder = [NSMutableDictionary dictionaryWithObjectsAndKeys: currentPath, @"path", - folderType, @"type", - currentFolderName, @"name", - [NSMutableArray array], @"children", - flags, @"flags", - nil]; + folderType, @"type", + currentFolderName, @"name", + [NSMutableArray array], @"children", + flags, @"flags", + [NSNumber numberWithBool: isSubscribed], @"subscribed", + nil]; // Either add this new folder to its parent or the list of root folders [folders addObject: folder]; } @@ -557,7 +564,7 @@ static NSString *inboxFolderName = @"INBOX"; // // Return a tree representation of the mailboxes // -- (NSArray *) allFoldersMetadata +- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode { NSString *currentFolder; NSMutableArray *folders; @@ -565,7 +572,7 @@ static NSString *inboxFolderName = @"INBOX"; NSAutoreleasePool *pool; NSArray *allFolderPaths; - allFolderPaths = [self allFolderPaths]; + allFolderPaths = [self allFolderPaths: theListingMode]; rawFolders = [allFolderPaths objectEnumerator]; folders = [NSMutableArray array]; @@ -587,8 +594,6 @@ static NSString *inboxFolderName = @"INBOX"; return folders; } - -/* IMAP4 */ - (NSDictionary *) _mailAccount { NSDictionary *mailAccount; @@ -761,7 +766,7 @@ static NSString *inboxFolderName = @"INBOX"; [self trashFolderNameInContext: context], nil] stringsWithFormat: @"/%@"]; else - folderList = [self allFolderPaths]; + folderList = [self allFolderPaths: SOGoMailStandardListing]; folders = [NSMutableDictionary dictionary]; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 868e4d456..1e8ea9ce2 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -227,7 +227,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) prefix = [self absoluteImap4Name]; - result = [[self mailAccountFolder] allFolderPaths]; + result = [[self mailAccountFolder] allFolderPaths: SOGoMailStandardListing]; folderNames = [result objectEnumerator]; while ((currentFolderName = [folderNames nextObject])) if ([currentFolderName hasPrefix: prefix]) diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index a6b05e2d2..2cff5d291 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -361,4 +361,7 @@ "Folder compacted" = "Folder compacted"; /* Aria label for scope of search on messages */ -"Search scope" = "Search scope"; \ No newline at end of file +"Search scope" = "Search scope"; + +/* Subscriptions Dialog */ +"Manage Subscriptions" = "Manage Subscriptions"; \ No newline at end of file diff --git a/UI/MailerUI/GNUmakefile b/UI/MailerUI/GNUmakefile index 4a465aae0..9148cecb8 100644 --- a/UI/MailerUI/GNUmakefile +++ b/UI/MailerUI/GNUmakefile @@ -23,10 +23,11 @@ MailerUI_OBJC_FILES += \ UIxMailPopupView.m \ UIxMailMoveToPopUp.m \ UIxMailFilterPanel.m \ - UIxMailSearch.m \ + UIxMailSearch.m \ \ UIxMailAccountActions.m \ UIxMailFolderActions.m \ + UIxMailFolderSubscriptions.m \ UIxMailActions.m \ UIxMailEditor.m \ UIxMailToSelection.m \ diff --git a/UI/MailerUI/UIxMailAccountActions.m b/UI/MailerUI/UIxMailAccountActions.m index af7e94ce7..d66b0f2b5 100644 --- a/UI/MailerUI/UIxMailAccountActions.m +++ b/UI/MailerUI/UIxMailAccountActions.m @@ -46,14 +46,33 @@ co = [self clientObject]; - folders = [co allFoldersMetadata]; + folders = [co allFoldersMetadata: SOGoMailStandardListing]; data = [NSDictionary dictionaryWithObjectsAndKeys: folders, @"mailboxes", [co getInboxQuota], @"quotas", nil]; - return [self responseWithStatus: 200 andJSONRepresentation: data]; + return [self responseWithStatus: 200 + andJSONRepresentation: data]; +} + +- (WOResponse *) listAllMailboxesAction +{ + SOGoMailAccount *co; + NSArray *folders; + NSDictionary *data; + + co = [self clientObject]; + + folders = [co allFoldersMetadata: SOGoMailSubscriptionsManagementListing]; + + data = [NSDictionary dictionaryWithObjectsAndKeys: + folders, @"mailboxes", + nil]; + + return [self responseWithStatus: 200 + andJSONRepresentation: data]; } /* compose */ diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index 1ddb51700..7435b5483 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -743,42 +743,62 @@ return response; } -#warning here should be done what should be done: IMAP subscription -- (WOResponse *) _subscriptionStubAction +// - (WOResponse *) _subscriptionStubAction +// { +// NSString *mailInvitationParam, *mailInvitationURL; +// WOResponse *response; +// SOGoMailFolder *clientObject; + +// mailInvitationParam +// = [[context request] formValueForKey: @"mail-invitation"]; +// if ([mailInvitationParam boolValue]) +// { +// clientObject = [self clientObject]; +// mailInvitationURL +// = [[clientObject soURLToBaseContainerForCurrentUser] +// absoluteString]; +// response = [self responseWithStatus: 302]; +// [response setHeader: mailInvitationURL +// forKey: @"location"]; +// } +// else +// { +// response = [self responseWithStatus: 500]; +// [response appendContentString: @"How did you end up here?"]; +// } + +// return response; +// } + +- (WOResponse *) _subscribeOrUnsubscribeAction: (BOOL) subscribing { - NSString *mailInvitationParam, *mailInvitationURL; + NGImap4Client *client; WOResponse *response; - SOGoMailFolder *clientObject; + SOGoMailFolder *co; + NSDictionary *d; - mailInvitationParam - = [[context request] formValueForKey: @"mail-invitation"]; - if ([mailInvitationParam boolValue]) - { - clientObject = [self clientObject]; - mailInvitationURL - = [[clientObject soURLToBaseContainerForCurrentUser] - absoluteString]; - response = [self responseWithStatus: 302]; - [response setHeader: mailInvitationURL - forKey: @"location"]; - } + co = [self clientObject]; + client = [[co imap4Connection] client]; + + if (subscribing) + d = [client subscribe: [[co imap4URL] path]]; else - { - response = [self responseWithStatus: 500]; - [response appendContentString: @"How did you end up here?"]; - } + d = [client unsubscribe: [[co imap4URL] path]]; - return response; + if ([[[[d objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"result"] isEqualToString: @"ok"]) + return [self responseWith204]; + + return [self responseWithStatus: 200]; } - (WOResponse *) subscribeAction { - return [self _subscriptionStubAction]; + return [self _subscribeOrUnsubscribeAction: YES]; } - (WOResponse *) unsubscribeAction { - return [self _subscriptionStubAction]; + return [self _subscribeOrUnsubscribeAction: NO]; } - (WOResponse *) addOrRemoveLabelAction diff --git a/UI/MailerUI/UIxMailFolderSubscriptions.h b/UI/MailerUI/UIxMailFolderSubscriptions.h new file mode 100644 index 000000000..4f413cef1 --- /dev/null +++ b/UI/MailerUI/UIxMailFolderSubscriptions.h @@ -0,0 +1,28 @@ +/* UIxMailFolderSubscriptions.h - this file is part of SOGo + * + * Copyright (C) 2016 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +@interface UIxMailFolderSubscriptions : UIxComponent +{ + +} + +@end diff --git a/UI/MailerUI/UIxMailFolderSubscriptions.m b/UI/MailerUI/UIxMailFolderSubscriptions.m new file mode 100644 index 000000000..a2cdced94 --- /dev/null +++ b/UI/MailerUI/UIxMailFolderSubscriptions.m @@ -0,0 +1,42 @@ +/* UIxMailFolderSubscriptions.m - this file is part of SOGo + * + * Copyright (C) 2016 Inverse inc. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import "UIxMailFolderSubscriptions.h" + +@implementation UIxMailFolderSubscriptions + +- (id) init +{ + if ((self = [super init])) + { + + } + + return self; +} + +- (void) dealloc +{ + [super dealloc]; +} + +@end diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index ee9a004be..930ea1eca 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -400,6 +400,15 @@ actionClass = "UIxMailAccountActions"; actionName = "listMailboxes"; }; + viewAll = { + protectedBy = "View"; + actionClass = "UIxMailAccountActions"; + actionName = "listAllMailboxes"; + }; + subscribe = { + protectedBy = "Access Contents Information"; + pageName = "UIxMailFolderSubscriptions"; + }; createFolder = { protectedBy = "View"; actionClass = "UIxMailFolderActions"; diff --git a/UI/Templates/MailerUI/UIxMailFolderSubscriptions.wox b/UI/Templates/MailerUI/UIxMailFolderSubscriptions.wox new file mode 100644 index 000000000..955a62378 --- /dev/null +++ b/UI/Templates/MailerUI/UIxMailFolderSubscriptions.wox @@ -0,0 +1,50 @@ + + + + + + +
+ folder +
+ +
{{subscriptions.account.name}}
+
+ + close + +
+
+ + + + +
+ {{subscriptions.app.metadataForFolder(folder).icon}} +
+

+ {{subscriptions.app.metadataForFolder(folder).name}} +

+ + +
+
+
+ + + + + +
+
diff --git a/UI/Templates/MailerUI/UIxMailMainFrame.wox b/UI/Templates/MailerUI/UIxMailMainFrame.wox index 2e00677e2..fb4471cce 100644 --- a/UI/Templates/MailerUI/UIxMailMainFrame.wox +++ b/UI/Templates/MailerUI/UIxMailMainFrame.wox @@ -40,21 +40,35 @@
{{account.name}}
-
- - - people - - - - add_circle_outline - -
+ + more_vert + + + + + + + + + + + + + + + + + + +