From 50dad7263f6adbb526d7e4a32183b39b817d32e0 Mon Sep 17 00:00:00 2001 From: wolfgang Date: Fri, 17 Aug 2007 02:47:37 +0000 Subject: [PATCH] git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1147 d1b88da0-ebda-0310-925b-ed51d893ca5b --- ChangeLog | 206 +++ Main/SOGo.m | 16 +- NEWS | 3 +- .../Appointments/SOGoAppointmentFolder.m | 3 +- .../Appointments/SOGoCalendarComponent.m | 2 - .../Appointments/iCalEntityObject+SOGo.m | 9 +- SoObjects/Mailer/GNUmakefile | 4 +- SoObjects/Mailer/SOGoDraftObject.h | 55 +- SoObjects/Mailer/SOGoDraftObject.m | 1149 +++++++++-------- SoObjects/Mailer/SOGoDraftsFolder.h | 77 +- SoObjects/Mailer/SOGoDraftsFolder.m | 246 +--- SoObjects/Mailer/SOGoMailAccount.h | 39 +- SoObjects/Mailer/SOGoMailAccount.m | 138 +- SoObjects/Mailer/SOGoMailAccounts.h | 7 +- SoObjects/Mailer/SOGoMailAccounts.m | 93 +- SoObjects/Mailer/SOGoMailBaseObject.h | 22 +- SoObjects/Mailer/SOGoMailBaseObject.m | 7 +- SoObjects/Mailer/SOGoMailFolder.h | 1 + SoObjects/Mailer/SOGoMailFolder.m | 72 +- SoObjects/Mailer/SOGoMailIdentity.h | 111 -- SoObjects/Mailer/SOGoMailIdentity.m | 143 -- SoObjects/Mailer/SOGoMailObject+Draft.h | 38 + SoObjects/Mailer/SOGoMailObject+Draft.m | 205 +++ SoObjects/Mailer/SOGoMailObject.h | 2 +- SoObjects/Mailer/SOGoMailObject.m | 158 +-- SoObjects/Mailer/SOGoUser+Mail.h | 48 - SoObjects/Mailer/SOGoUser+Mail.m | 146 --- SoObjects/SOGo/NSArray+Utilities.h | 3 + SoObjects/SOGo/NSArray+Utilities.m | 56 + SoObjects/SOGo/NSDictionary+Utilities.h | 1 + SoObjects/SOGo/NSDictionary+Utilities.m | 24 + SoObjects/SOGo/SOGoFolder.m | 7 +- SoObjects/SOGo/SOGoMailer.h | 4 +- SoObjects/SOGo/SOGoUser.h | 15 +- SoObjects/SOGo/SOGoUser.m | 143 +- UI/Common/GNUmakefile | 4 +- UI/Common/WODirectAction+SOGo.h | 37 + UI/Common/WODirectAction+SOGo.m | 42 + UI/MailPartViewers/UIxMailPartICalViewer.m | 9 +- UI/MailerUI/English.lproj/Localizable.strings | 2 + UI/MailerUI/French.lproj/Localizable.strings | 2 + UI/MailerUI/GNUmakefile | 9 +- UI/MailerUI/Toolbars/SOGoMailObject.toolbar | 4 +- UI/MailerUI/UIxMailAccountActions.m | 34 + UI/MailerUI/UIxMailActions.h | 31 + UI/MailerUI/UIxMailActions.m | 107 ++ UI/MailerUI/UIxMailEditor.m | 497 ++----- UI/MailerUI/UIxMailEditorAction.h | 61 - UI/MailerUI/UIxMailEditorAction.m | 203 --- UI/MailerUI/UIxMailFolderActions.h | 7 +- UI/MailerUI/UIxMailFolderActions.m | 38 +- UI/MailerUI/UIxMailForwardAction.m | 117 -- UI/MailerUI/UIxMailListView.m | 24 - UI/MailerUI/UIxMailMainFrame.h | 18 +- UI/MailerUI/UIxMailMainFrame.m | 175 +-- UI/MailerUI/UIxMailReplyAction.m | 252 ---- UI/MailerUI/UIxMailView.m | 5 + UI/MailerUI/product.plist | 62 +- UI/MainUI/GNUmakefile | 4 +- UI/SOGoUI/SOGoACLAdvisory.m | 14 +- UI/Scheduler/UIxCalView.m | 17 +- UI/Scheduler/UIxComponentEditor.m | 4 +- .../ContactsUI/UIxContactsListView.wox | 2 +- UI/Templates/MailerUI/UIxMailEditor.wox | 107 +- UI/Templates/MailerUI/UIxMailMainFrame.wox | 312 ++--- UI/Templates/MailerUI/UIxMailView.wox | 4 + UI/Templates/UIxPageFrame.wox | 2 +- UI/WebServerResources/ContactsUI.js | 25 +- UI/WebServerResources/MailerUI.css | 8 +- UI/WebServerResources/MailerUI.js | 108 +- UI/WebServerResources/SOGoDragHandles.js | 83 +- UI/WebServerResources/SchedulerUI.js | 15 - UI/WebServerResources/UIxMailEditor.css | 5 +- UI/WebServerResources/UIxMailEditor.js | 32 +- UI/WebServerResources/UIxMailPopupView.js | 9 + UI/WebServerResources/generic.js | 60 +- 76 files changed, 2694 insertions(+), 3110 deletions(-) delete mode 100644 SoObjects/Mailer/SOGoMailIdentity.h delete mode 100644 SoObjects/Mailer/SOGoMailIdentity.m create mode 100644 SoObjects/Mailer/SOGoMailObject+Draft.h create mode 100644 SoObjects/Mailer/SOGoMailObject+Draft.m delete mode 100644 SoObjects/Mailer/SOGoUser+Mail.h delete mode 100644 SoObjects/Mailer/SOGoUser+Mail.m create mode 100644 UI/Common/WODirectAction+SOGo.h create mode 100644 UI/Common/WODirectAction+SOGo.m create mode 100644 UI/MailerUI/UIxMailActions.h create mode 100644 UI/MailerUI/UIxMailActions.m delete mode 100644 UI/MailerUI/UIxMailEditorAction.h delete mode 100644 UI/MailerUI/UIxMailEditorAction.m delete mode 100644 UI/MailerUI/UIxMailForwardAction.m delete mode 100644 UI/MailerUI/UIxMailReplyAction.m diff --git a/ChangeLog b/ChangeLog index c9653a6b..cf536a10 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,209 @@ +2007-08-16 Wolfgang Sourdeau + + * UI/MailerUI/UIxMailEditor.m ([-patchFlagsInStore]): removed + useless stub method, of which the intention was implemented in + SOGoDraftObject. + ([-lookupSentFolderUsingAccount]): removed obsolete method. + ([-selectedMailIdentity]): removed obsolete method. + ([-lookupSentFolderUsingFrom]): removed obsolete method. + ([-storeMailInSentFolder:_path]): removed obsolete method, of + which the mechanism has been put in -[SOGoDraftObject sendMail] + method. + ([UIxMailEditor -_saveFormInfo], [UIxMailEditor -defaultAction]) + ([UIxMailEditor -saveAction], [UIxMailEditor -sendAction]): + adapted algorithms to the new SOGoDraftObject methods. + ([-deleteAction]): removed method since local draft objects cannot + be removed by the user. + + * UI/MailerUI/UIxMailFolderActions.m ([UIxMailFolderActions + -expungeAction]): new method replacing the one previously found in + UIxMailListView. + ([UIxMailFolderActions -createFolderAction]) + ([UIxMailFolderActions -renameFolderAction]) + ([UIxMailFolderActions -deleteFolderAction]) + ([UIxMailFolderActions -emptyTrashAction]) + ([UIxMailFolderActions -subscribeAction]) + ([UIxMailFolderActions -unsubscribeAction]): error situations + should have http return code 500 instead of 403. + + * UI/MailerUI/UIxMailAccountActions.m ([UIxMailAccountActions + -composeAction]): new method replacing the one previously in + UIxMailMainFrame. + + * SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject + -imap4URLString]): removed overriden method (see below). + + * SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder -aclUsers]): + cache the mailbox acl. + ([SOGoMailFolder -aclsForUser:uid]): cache the mailbox acl. + ([SOGoMailFolder -setRoles:rolesforUser:uid]): reset the mailbox + acl cache. + ([SOGoMailFolder -httpURLForAdvisoryToUser:uid]): modified to use + the new method of determining the users mail accounts. + + * SoObjects/Mailer/SOGoMailBaseObject.m ([-imap4URLString]): no + longer adds a "/" at the end of the string (the default for + folders), therefore this will be overriden in SOGoMailFolder + rather than in SOGoMailObject. + + * UI/MailerUI/UIxMailListView.m ([-expungeAction]): removed + method, moved into the new UIxMailActions module class. + + * SoObjects/Mailer/SOGoDraftsFolder.m ([SOGoDraftsFolder + -newDraft]): new method that returns a new SOGoDraftObject + instance with a unique filename based on the current timestamp and + the "newDraft" prefix. + ([SOGoDraftsFolder + -lookupName:nameinContext:localContextacquire:acquire]): overriden + method by detecting local drafts with their "newDraft" prefix. + ([SOGoDraftsFolder -isInDraftsFolder]): returns YES. + + * SoObjects/Mailer/SOGoDraftsFolder.[hm]: rewrote class module + from scratch by making it a subclass of SOGoMailFolder. + + * UI/MailerUI/UIxMailReplyAction.m: removed obsolete class + module. + + * UI/MailerUI/UIxMailForwardAction.m: removed obsolete class + module. + + * UI/MailerUI/UIxMailEditorAction.[hm]: removed obsolete class + module. + + * SoObjects/Mailer/SOGoDraftObject.m ([SOGoDraftObject -init]): + new method, initializing the new ivars: IMAP4ID, headers, text, + sourceURL and sourceFlag. + ([-spoolFileManager], [SOGoDraftObject -userSpoolFolderPath]) + ([-_ensureUserSpoolFolderPath]) + ([-saveMimeMessageToTemporaryFileWithHeaders:]) + ([SOGoDraftObject -mimeMessageWithHeaders:_headers]) + ([-deleteTemporaryMessageFile:], [-delete], [-content]) + ([-GETAction:_ctx], [-DELETEAction:_ctx], [-fetchParts:]) + ([-uid], [-flags], [-size], [-envelope]): removed methods. + ([SOGoDraftObject -storeInfo]): rewrote method to take the new + ivars into account. + ([SOGoDraftObject -setSourceURL:newSourceURL]) + ([SOGoDraftObject -setSourceFlag:newSourceFlag]): new accessor + methods to store the url of the original message between accesses + so that, depending on the action taken (forward or reply), the + correct flag can be given. + ([SOGoDraftObject -setIMAP4ID:]): new accessor method that sets + the imap4 id that is returned for the message whenever it is saved + into the drafts folder. + ([SOGoDraftObject -save]): new method that takes in charge the + saving of the message in the IMAP drafts folder. + ([SOGoDraftObject -fetchMailForReplying:sourceMailtoAll:toAll]): + new method that fills the original data of the new message with + the reply content of the original message. + ([SOGoDraftObject -fetchMailForForwarding:sourceMail]): same as + above for message forwarding. + ([-spoolFileManager]): removed useless method (only returned the + default filemanager...) + ([SOGoDraftObject -mimeMessageAsData]): new method that returns + the resulting message as an NSData chunk. + + * SoObjects/Mailer/SOGoMailObject+Draft.m: new extension module + containing a rewrite of the mail action methods found in + UI/Mailer/, that needed to be put in the SOGoMailObject class. + ([SOGoMailObject -subjectForReply]): new method that returns a + subject suitable for replies. + ([SOGoMailObject -contentForReply]): new method that returns the + textual content of an email, quoted for replying. + ([SOGoMailObject -filenameForForward]): new method that returns + the name of the filename that should contain the forwarded + message, based on its subject. + ([SOGoMailObject -subjectForForward]): explicit. + +2007-08-15 Wolfgang Sourdeau + + * UI/MailerUI/UIxMailMainFrame.m ([UIxMailMainFrame + -mailAccounts]): rewrote method to return the name of the mail + accounts now available with the -[SOGoUser mailAccounts] method. + ([UIxMailMainFrame -showLinkBanner]): removed method. + ([UIxMailMainFrame -bannerToolbarStyle]): removed method. + ([UIxMailMainFrame -bannerConsumeStyle]): removed method. + ([UIxMailMainFrame -rootURL]): removed method. + ([UIxMailMainFrame -userRootURL]): removed method. + ([UIxMailMainFrame -calendarRootURL]): removed method. + ([UIxMailMainFrame -contactsRootURL]): removed method. + ([UIxMailMainFrame -hasErrorText]) + ([UIxMailMainFrame -errorText]) + ([UIxMailMainFrame -errorAlertJavaScript]): removed methods. + ([-composeAction]): removed method. Now provided by + UIxMailAccountActions. + ([UIxMailMainFrame -setHideFolderTree:_flag]): removed method. + ([UIxMailMainFrame -hideFolderTree]): removed method. + ([UIxMailMainFrame -treeRootClassName]): removed method. + ([UIxMailMainFrame +initialize]): removed method. + SOGoMailTreeRootClass userdefaults will no longer have any effect. + + * UI/Common/WODirectAction+SOGo.m ([WODirectAction + -redirectToLocation:newLocation]): new method that implements the + same functionality as WOComponent. + + * UI/Common/WODirectAction+SOGo.[hm]: new class extension module. + + * UI/MailerUI/UIxMailView.m ([UIxMailView -mailIsDraft]): new + method that returns whether the current mail is store in the + drafts folder hierarchy. + + * SoObjects/SOGo/SOGoUser.m ([-fullEmail]): removed method. + ([-primaryEmail]): removed method. + ([SOGoUser -primaryIMAP4AccountString]): removed method. + ([SOGoUser -mailAccounts]): new method that returns an array + containing description dictionaries for all the user mail + accounts. Each account also contain the user's identities for that + account. + ([SOGoUser -allIdentities]): new utility method that returns all + the user identities on all accounts. + ([SOGoUser -primaryIdentity]): new method return the first + identity of the first account. + + * SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder -httpURLForAdvisoryToUser:uid]): + + * SoObjects/Mailer/SOGoMailAccount.m ([SOGoMailAccount + -isInDraftsFolder]): returns NO. + ([-preferredIdentity]): removed method, replaced with -[SOGoUser + primaryIdentity]. + ([SOGoMailAccount -draftsFolderInContext:_ctx]): new method. + + * SoObjects/Mailer/SOGoMailBaseObject.m ([SOGoMailBaseObject + -isInDraftsFolder]): new method that detects if self is contained + by the drafts folder by calling itself on the container object. + + * SoObjects/Mailer/SOGoMailAccounts.m ([-fetchAllIdentities]): + removed method. + ([-fetchIdentitiesWithEmitterPermissions]): removed method. + ([SOGoMailAccounts -toManyRelationshipKeys]): rewrote method to + return the name of the mail accounts now available with the + -[SOGoUser mailAccounts] method. + + * SoObjects/Mailer/SOGoUser+Mail.[hm]: removed useless class + extension module. + + * SoObjects/Mailer/SOGoMailIdentity.[hm]: removed useless class + module. + + * SoObjects/SOGo/NSArray+Utilities.m ([NSArray + -keysWithFormat:format]): method that forward the method of the + same name to each member of the array, considering they all are + instances of NSDictionary. + ([NSArray -objectsForKey:key]): same principle as above. + ([NSArray -flattenedArray]): new method that transforms an array + of arrays into a single array containing all the elements of the + subarrays. + + * SoObjects/SOGo/NSDictionary+Utilities.m ([NSDictionary + -keysWithFormat:keyFormat]): new method inspired by the python + string formatting system and which replaces occurences of "%{key}" + by the corresponding keys. + +2007-08-13 Wolfgang Sourdeau + + * Main/SOGo.m ([SOGo -run]): check for channel-type specific + sql script before the generic one when initializing mandatory + system tables. + 2007-08-09 Wolfgang Sourdeau * SoObjects/SOGo/NSString+Utilities.m ([NSString diff --git a/Main/SOGo.m b/Main/SOGo.m index b4d247f8..70229749 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -151,15 +151,18 @@ static BOOL debugObjectAllocation = NO; tableURL: (NSString *) url andType: (NSString *) tableType { - NSString *tableName, *descFile; + NSString *tableName, *descFile, *tableFile, *fileSuffix; EOAdaptorChannel *tc; NGBundleManager *bm; NSBundle *bundle; unsigned int length; + NSURL *channelURL; bm = [NGBundleManager defaultBundleManager]; - - tc = [cm acquireOpenChannelForURL: [NSURL URLWithString: url]]; + + channelURL = [NSURL URLWithString: url]; + fileSuffix = [channelURL scheme]; + tc = [cm acquireOpenChannelForURL: channelURL]; tableName = [url lastPathComponent]; if ([tc evaluateExpressionX: @@ -167,8 +170,13 @@ static BOOL debugObjectAllocation = NO; { bundle = [bm bundleWithName: @"MainUI" type: @"SOGo"]; length = [tableType length] - 3; - descFile = [bundle pathForResource: [tableType substringToIndex: length] + tableFile = [tableType substringToIndex: length]; + descFile + = [bundle pathForResource: [NSString stringWithFormat: @"%@-%@", + tableFile, fileSuffix] ofType: @"sql"]; + if (!descFile) + descFile = [bundle pathForResource: tableFile ofType: @"sql"]; if (![tc evaluateExpressionX: [NSString stringWithContentsOfFile: descFile]]) [self logWithFormat: @"table '%@' successfully created!", tableName]; diff --git a/NEWS b/NEWS index 3c5dfca9..8aed0efb 100644 --- a/NEWS +++ b/NEWS @@ -9,7 +9,8 @@ - added support for limiting LDAP queries with the SOGoLDAPQueryLimit and the SOGoLDAPSizeLimit settings; - fixed a bug where folders starting with digits would not be displayed; -- improved IE7 and Safari support: priority menus, attendees selector; +- improved IE7 and Safari support: priority menus, attendees selector, + search fields, textarea sizes; - added the ability to print messages from the mailer toolbar; - added the ability to use and configure SMTP as the email transport instead of sendmail; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 7e0c0e6d..5e7955a7 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -746,7 +746,8 @@ static NSNumber *sharedYes = nil; privacySqlString = @"and (c_isopaque = 1)"; else { - email = [activeUser primaryEmail]; +#warning we do not manage all the user's possible emails + email = [[activeUser primaryIdentity] objectForKey: @"email"]; privacySqlString = [NSString stringWithFormat: diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 808275d3..be754cf6 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -274,14 +274,12 @@ static BOOL sendEMailNotifications = NO; iCalPerson *person; NSString *newContent; NSException *ex; - NSString *myEMail; ex = nil; component = [self component: NO]; if (component) { - myEMail = [[context activeUser] primaryEmail]; person = [self findParticipantWithUID: owner]; if (person) { diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index 5b100f9a..6b67f2da 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -23,6 +23,7 @@ #import #import +#import #import #import "iCalEntityObject+SOGo.h" @@ -32,12 +33,14 @@ - (BOOL) userIsParticipant: (SOGoUser *) user { NSEnumerator *emails; + NSArray *identities; NSString *currentEmail; BOOL response; response = NO; - emails = [[user allEmails] objectEnumerator]; + identities = [user allIdentities]; + emails = [[identities objectsForKey: @"email"] objectEnumerator]; currentEmail = [emails nextObject]; while (!response && currentEmail) if ([self isParticipant: currentEmail]) @@ -51,12 +54,14 @@ - (BOOL) userIsOrganizer: (SOGoUser *) user { NSEnumerator *emails; + NSArray *identities; NSString *currentEmail; BOOL response; response = NO; - emails = [[user allEmails] objectEnumerator]; + identities = [user allIdentities]; + emails = [[identities objectsForKey: @"email"] objectEnumerator]; currentEmail = [emails nextObject]; while (!response && currentEmail) if ([self isOrganizer: currentEmail]) diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index fbe4346e..a0fc74d4 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -18,6 +18,7 @@ Mailer_OBJC_FILES += \ SOGoMailFolder.m \ SOGoSharedInboxFolder.m \ SOGoMailObject.m \ + SOGoMailObject+Draft.m \ SOGoTrashFolder.m \ \ SOGoMailBodyPart.m \ @@ -28,9 +29,6 @@ Mailer_OBJC_FILES += \ \ SOGoDraftsFolder.m \ SOGoDraftObject.m \ - \ - SOGoMailIdentity.m \ - SOGoUser+Mail.m \ Mailer_RESOURCE_FILES += \ Version \ diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 2995f722..95b7c5fc 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -22,7 +22,7 @@ #ifndef __Mailer_SOGoDraftObject_H__ #define __Mailer_SOGoDraftObject_H__ -#include +#import "SOGoMailBaseObject.h" /* SOGoDraftsFolder @@ -36,20 +36,48 @@ TODO: store-info should be an own object, not NSDictionary. */ -@class NSString, NSArray, NSDictionary, NSData, NSException; -@class NGMimeMessage, NGImap4Envelope; +@class NSArray; +@class NSData; +@class NSDictionary; +@class NSException; +@class NGImap4Envelope; +@class NGMimeMessage; +@class NSMutableDictionary; +@class NSString; + +@class SOGoMailObject; @interface SOGoDraftObject : SOGoMailBaseObject { - NSString *path; - NSDictionary *info; /* stores the envelope information */ + NSString *path; NGImap4Envelope *envelope; + int IMAP4ID; + NSMutableDictionary *headers; + NSString *text; + NSString *sourceURL; + NSString *sourceFlag; } /* contents */ +- (void) fetchInfo; +- (NSException *) storeInfo; + +- (void) fetchMailForEditing: (SOGoMailObject *) sourceMail; +- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail + toAll: (BOOL) toAll; +- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail; + +- (void) setHeaders: (NSDictionary *) newHeaders; +- (NSDictionary *) headers; +- (void) setText: (NSString *) newText; +- (NSString *) text; + +/* for replies and forwards */ +- (void) setSourceURL: (NSString *) newSurceURL; +- (void) setSourceFlag: (NSString *) newSourceFlag; -- (NSDictionary *) fetchInfo; -- (NSException *) storeInfo: (NSDictionary *) _info; +- (void) setIMAP4ID: (int) newIMAPID; +- (int) IMAP4ID; /* attachments */ @@ -62,19 +90,16 @@ /* NGMime representations */ - (NGMimeMessage *) mimeMessage; - -- (NSString *) saveMimeMessageToTemporaryFile; -- (NSString *) saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_addh; - -- (NSException *) sendMail; +- (NSData *) mimeMessageAsData; /* operations */ -- (NSException *) delete; +- (NSException *) sendMail; +- (NSException *) save; -/* fake being a SOGoMailObject */ +// /* fake being a SOGoMailObject */ -- (id) fetchParts: (NSArray *) _parts; +// - (id) fetchParts: (NSArray *) _parts; @end diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 1d627aba..e42ec5e4 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -37,6 +38,9 @@ #import #import #import +#import +#import +#import #import #import #import @@ -49,10 +53,16 @@ #import #import +#import "SOGoMailAccount.h" +#import "SOGoMailFolder.h" +#import "SOGoMailObject.h" +#import "SOGoMailObject+Draft.h" #import "SOGoDraftObject.h" static NSString *contentTypeValue = @"text/plain; charset=utf-8"; +static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc", + @"from", @"replyTo", nil}; @interface NSString (NGMimeHelpers) @@ -88,155 +98,425 @@ static BOOL draftDeleteDisabled = NO; // for debugging static BOOL debugOn = NO; static BOOL showTextAttachmentsInline = NO; -+ (void)initialize { ++ (void) initialize +{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; /* Note: be aware of the charset issues before enabling this! */ - showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"]; + showTextAttachmentsInline = [ud boolForKey: @"SOGoShowTextAttachmentsInline"]; - if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"])) + if ((draftDeleteDisabled = [ud boolForKey: @"SOGoNoDraftDeleteAfterSend"])) NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)"); - TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy]; - MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy]; + TextPlainType = [[NGMimeType mimeType: @"text" subType: @"plain"] copy]; + MultiMixedType = [[NGMimeType mimeType: @"multipart" subType: @"mixed"] copy]; } -- (void)dealloc { +- (id) init +{ + if ((self = [super init])) + { + IMAP4ID = -1; + headers = [NSMutableDictionary new]; + text = @""; + sourceURL = nil; + sourceFlag = nil; + } + + return self; +} + +- (void) dealloc +{ + [headers release]; + [text release]; [envelope release]; - [info release]; [path release]; + [sourceURL release]; + [sourceFlag release]; [super dealloc]; } /* draft folder functionality */ -- (NSFileManager *)spoolFileManager { - return [[self container] spoolFileManager]; -} -- (NSString *)userSpoolFolderPath { +- (NSString *) userSpoolFolderPath +{ return [[self container] userSpoolFolderPath]; } -- (BOOL)_ensureUserSpoolFolderPath { - return [[self container] _ensureUserSpoolFolderPath]; -} /* draft object functionality */ -- (NSString *)draftFolderPath { - if (path != nil) - return path; - - path = [[[self userSpoolFolderPath] stringByAppendingPathComponent: - [self nameInContainer]] copy]; +- (NSString *) draftFolderPath +{ + if (!path) + { + path = [[self userSpoolFolderPath] stringByAppendingPathComponent: + nameInContainer]; + [path retain]; + } + return path; } -- (BOOL)_ensureDraftFolderPath { + +- (BOOL) _ensureDraftFolderPath +{ NSFileManager *fm; + + fm = [NSFileManager defaultManager]; - if (![self _ensureUserSpoolFolderPath]) - return NO; - - if ((fm = [self spoolFileManager]) == nil) { - [self errorWithFormat:@"missing spool file manager!"]; - return NO; - } - return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil]; + return ([fm createDirectoriesAtPath: [container userSpoolFolderPath] + attributes: nil] + && [fm createDirectoriesAtPath: [self draftFolderPath] + attributes:nil]); } -- (NSString *)infoPath { - return [[self draftFolderPath] - stringByAppendingPathComponent:@".info.plist"]; +- (NSString *) infoPath +{ + return [[self draftFolderPath] + stringByAppendingPathComponent: @".info.plist"]; } /* contents */ -- (NSException *)storeInfo:(NSDictionary *)_info { - if (_info == nil) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"got no info to write for draft!"]; - } - if (![self _ensureDraftFolderPath]) { - [self errorWithFormat:@"could not create folder for draft: '%@'", +- (void) setHeaders: (NSDictionary *) newHeaders +{ + id headerValue; + unsigned int count; + + for (count = 0; count < 6; count++) + { + headerValue = [newHeaders objectForKey: headerKeys[count]]; + if (headerValue) + [headers setObject: headerValue + forKey: headerKeys[count]]; + else + [headers removeObjectForKey: headerKeys[count]]; + } +} + +- (NSDictionary *) headers +{ + return headers; +} + +- (void) setText: (NSString *) newText +{ + ASSIGN (text, newText); +} + +- (NSString *) text +{ + return text; +} + +- (void) setSourceURL: (NSString *) newSourceURL +{ + ASSIGN (sourceURL, newSourceURL); +} + +- (void) setSourceFlag: (NSString *) newSourceFlag +{ + ASSIGN (sourceFlag, newSourceFlag); +} + +- (NSException *) storeInfo +{ + NSMutableDictionary *infos; + NSException *error; + + if ([self _ensureDraftFolderPath]) + { + infos = [NSMutableDictionary new]; + [infos setObject: headers forKey: @"headers"]; + if (text) + [infos setObject: text forKey: @"text"]; + if (IMAP4ID > -1) + [infos setObject: [NSNumber numberWithInt: IMAP4ID] + forKey: @"IMAP4ID"]; + if (sourceURL && sourceFlag) + { + [infos setObject: sourceURL forKey: @"sourceURL"]; + [infos setObject: sourceFlag forKey: @"sourceFlag"]; + } + + if ([infos writeToFile: [self infoPath] atomically:YES]) + error = nil; + else + { + [self errorWithFormat: @"could not write info: '%@'", + [self infoPath]]; + error = [NSException exceptionWithHTTPStatus:500 /* server error */ + reason: @"could not write draft info!"]; + } + + [infos release]; + } + else + { + [self errorWithFormat: @"could not create folder for draft: '%@'", [self draftFolderPath]]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not create folder for draft!"]; - } - if (![_info writeToFile:[self infoPath] atomically:YES]) { - [self errorWithFormat:@"could not write info: '%@'", [self infoPath]]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not write draft info!"]; - } - - /* reset info cache */ - [info release]; info = nil; - - return nil /* everything is excellent */; + error = [NSException exceptionWithHTTPStatus:500 /* server error */ + reason: @"could not create folder for draft!"]; + } + + return error; } -- (NSDictionary *)fetchInfo { + +- (void) _loadInfosFromDictionary: (NSDictionary *) infoDict +{ + id value; + + value = [infoDict objectForKey: @"headers"]; + if (value) + [self setHeaders: value]; + + value = [infoDict objectForKey: @"text"]; + if ([value length] > 0) + [self setText: value]; + + value = [infoDict objectForKey: @"IMAP4ID"]; + if (value) + [self setIMAP4ID: [value intValue]]; + + value = [infoDict objectForKey: @"sourceURL"]; + if (value) + [self setSourceURL: value]; + value = [infoDict objectForKey: @"sourceFlag"]; + if (value) + [self setSourceFlag: value]; +} + +- (NSString *) relativeImap4Name +{ + return [NSString stringWithFormat: @"%d", IMAP4ID]; +} + +- (void) fetchInfo +{ NSString *p; + NSDictionary *infos; + NSFileManager *fm; - if (info != nil) - return info; - p = [self infoPath]; - if (![[self spoolFileManager] fileExistsAtPath:p]) { - [self debugWithFormat:@"Note: info object does not yet exist: %@", p]; - return nil; - } + + fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath: p]) + { + infos = [NSDictionary dictionaryWithContentsOfFile: p]; + if (infos) + [self _loadInfosFromDictionary: infos]; +// else +// [self errorWithFormat: @"draft info dictionary broken at path: %@", p]; + } + else + [self debugWithFormat: @"Note: info object does not yet exist: %@", p]; +} + +- (void) setIMAP4ID: (int) newIMAP4ID +{ + IMAP4ID = newIMAP4ID; +} + +- (int) IMAP4ID +{ + return IMAP4ID; +} + +- (int) _IMAP4IDFromAppendResult: (NSDictionary *) result +{ + NSDictionary *results; + NSString *flag, *newIdString; + + results = [[result objectForKey: @"RawResponse"] + objectForKey: @"ResponseResult"]; + flag = [results objectForKey: @"flag"]; + newIdString = [[flag componentsSeparatedByString: @" "] objectAtIndex: 2]; + + return [newIdString intValue]; +} + +- (NSException *) save +{ + NGImap4Client *client; + NSException *error; + NSData *message; + NSString *folder; + id result; + + error = nil; + message = [self mimeMessageAsData]; + + client = [[self imap4Connection] client]; + folder = [imap4 imap4FolderNameForURL: [container imap4URL]]; + result + = [client append: message toFolder: folder + withFlags: [NSArray arrayWithObjects: @"seen", @"draft", nil]]; + if ([[result objectForKey: @"result"] boolValue]) + { + if (IMAP4ID > -1) + error = [imap4 markURLDeleted: [self imap4URL]]; + IMAP4ID = [self _IMAP4IDFromAppendResult: result]; + [self storeInfo]; + } + else + error = [NSException exceptionWithHTTPStatus:500 /* Server Error */ + reason: @"Failed to store message"]; + + return error; +} + +- (void) fetchMailForEditing: (SOGoMailObject *) sourceMail +{ +#warning unimplemented +} + +- (void) _addEMailsOfAddresses: (NSArray *) _addrs + toArray: (NSMutableArray *) _ma +{ + unsigned i, count; + + for (i = 0, count = [_addrs count]; i < count; i++) + [_ma addObject: + [(NGImap4EnvelopeAddress *) [_addrs objectAtIndex: i] email]]; +} + +- (void) _fillInReplyAddresses: (NSMutableDictionary *) _info + replyToAll: (BOOL) _replyToAll + envelope: (NGImap4Envelope *) _envelope +{ + /* + The rules as implemented by Thunderbird: + - if there is a 'reply-to' header, only include that (as TO) + - if we reply to all, all non-from addresses are added as CC + - the from is always the lone TO (except for reply-to) + + Note: we cannot check reply-to, because Cyrus even sets a reply-to in the + envelope if none is contained in the message itself! (bug or + feature?) + + TODO: what about sender (RFC 822 3.6.2) + */ + NSMutableArray *to; + NSArray *addrs; + + to = [NSMutableArray arrayWithCapacity:2]; + + /* first check for "reply-to" */ - info = [[NSDictionary alloc] initWithContentsOfFile:p]; - if (info == nil) - [self errorWithFormat:@"draft info dictionary broken at path: %@", p]; + addrs = [_envelope replyTo]; + if ([addrs count] == 0) + /* no "reply-to", try "from" */ + addrs = [_envelope from]; + + [self _addEMailsOfAddresses: addrs toArray: to]; + [_info setObject: to forKey: @"to"]; + + /* CC processing if we reply-to-all: add all 'to' and 'cc' */ - return info; + if (_replyToAll) + { + to = [NSMutableArray arrayWithCapacity:8]; + + [self _addEMailsOfAddresses: [_envelope to] toArray: to]; + [self _addEMailsOfAddresses: [_envelope cc] toArray: to]; + + [_info setObject: to forKey: @"cc"]; + } +} + +- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail + toAll: (BOOL) toAll +{ + NSString *contentForReply; + NSMutableDictionary *info; + + [sourceMail fetchCoreInfos]; + + info = [NSMutableDictionary dictionaryWithCapacity: 16]; + [info setObject: [sourceMail subjectForReply] forKey: @"subject"]; + [self _fillInReplyAddresses: info replyToAll: toAll + envelope: [sourceMail envelope]]; + contentForReply = [sourceMail contentForReply]; + [self setText: contentForReply]; + [self setHeaders: info]; + [self setSourceURL: [sourceMail imap4URLString]]; + [self setSourceFlag: @"Answered"]; + [self storeInfo]; +} + +- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail +{ + NSDictionary *info, *attachment; + + [sourceMail fetchCoreInfos]; + + info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward] + forKey: @"subject"]; + [self setHeaders: info]; + [self setSourceURL: [sourceMail imap4URLString]]; + [self setSourceFlag: @"$Forwarded"]; + + /* attach message */ + + // TODO: use subject for filename? +// error = [newDraft saveAttachment:content withName:@"forward.mail"]; + attachment = [NSDictionary dictionaryWithObjectsAndKeys: + [sourceMail filenameForForward], @"filename", + @"message/rfc822", @"mime-type", + nil]; + [self saveAttachment: [sourceMail content] + withMetadata: attachment]; + [self storeInfo]; } /* accessors */ -- (NSString *)sender { +- (NSString *) sender +{ id tmp; - if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil) + if ((tmp = [headers objectForKey: @"from"]) == nil) return nil; if ([tmp isKindOfClass:[NSArray class]]) - return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil; + return [tmp count] > 0 ? [tmp objectAtIndex: 0] : nil; + return tmp; } /* attachments */ -- (NSArray *)fetchAttachmentNames { +- (NSArray *) fetchAttachmentNames +{ NSMutableArray *ma; - NSFileManager *fm; - NSArray *files; - unsigned i, count; - - fm = [self spoolFileManager]; - if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil) - return nil; - - count = [files count]; - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NSString *filename; - - filename = [files objectAtIndex:i]; - if ([filename hasPrefix:@"."]) - continue; - - [ma addObject:filename]; - } + NSFileManager *fm; + NSArray *files; + unsigned count, max; + NSString *filename; + + fm = [NSFileManager defaultManager]; + files = [fm directoryContentsAtPath: [self draftFolderPath]]; + + max = [files count]; + ma = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + filename = [files objectAtIndex: count]; + if (![filename hasPrefix: @"."]) + [ma addObject: filename]; + } + return ma; } -- (BOOL)isValidAttachmentName:(NSString *)_name { +- (BOOL) isValidAttachmentName: (NSString *) _name +{ static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil }; unsigned i; NSRange r; if (![_name isNotNull]) return NO; if ([_name length] == 0) return NO; - if ([_name hasPrefix:@"."]) return NO; + if ([_name hasPrefix: @"."]) return NO; for (i = 0; sescape[i] != nil; i++) { r = [_name rangeOfString:sescape[i]]; @@ -245,16 +525,18 @@ static BOOL showTextAttachmentsInline = NO; return YES; } -- (NSString *)pathToAttachmentWithName:(NSString *)_name { +- (NSString *) pathToAttachmentWithName: (NSString *) _name +{ if ([_name length] == 0) return nil; return [[self draftFolderPath] stringByAppendingPathComponent:_name]; } -- (NSException *)invalidAttachmentNameError:(NSString *)_name { +- (NSException *) invalidAttachmentNameError: (NSString *) _name +{ return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Invalid attachment name!"]; + reason: @"Invalid attachment name!"]; } - (NSException *) saveAttachment: (NSData *) _attach @@ -264,12 +546,12 @@ static BOOL showTextAttachmentsInline = NO; if (![_attach isNotNull]) { return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Missing attachment content!"]; + reason: @"Missing attachment content!"]; } if (![self _ensureDraftFolderPath]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not create folder for draft!"]; + reason: @"Could not create folder for draft!"]; } name = [metadata objectForKey: @"filename"]; if (![self isValidAttachmentName: name]) @@ -279,7 +561,7 @@ static BOOL showTextAttachmentsInline = NO; if (![_attach writeToFile: p atomically: YES]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not write attachment to draft!"]; + reason: @"Could not write attachment to draft!"]; } mimeType = [metadata objectForKey: @"mime-type"]; @@ -291,115 +573,121 @@ static BOOL showTextAttachmentsInline = NO; writeToFile: p atomically: YES]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not write attachment to draft!"]; + reason: @"Could not write attachment to draft!"]; } } return nil; /* everything OK */ } -- (NSException *)deleteAttachmentWithName:(NSString *)_name { +- (NSException *) deleteAttachmentWithName: (NSString *) _name +{ NSFileManager *fm; NSString *p; - - if (![self isValidAttachmentName:_name]) - return [self invalidAttachmentNameError:_name]; - - fm = [self spoolFileManager]; - p = [self pathToAttachmentWithName:_name]; - if (![fm fileExistsAtPath:p]) - return nil; /* well, doesn't exist, so its deleted ;-) */ - - if (![fm removeFileAtPath:p handler:nil]) { - [self logWithFormat:@"ERROR: failed to delete file: %@", p]; - return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not delete attachment from draft!"]; - } - return nil; /* everything OK */ + NSException *error; + + error = nil; + + if ([self isValidAttachmentName:_name]) + { + fm = [NSFileManager defaultManager]; + p = [self pathToAttachmentWithName:_name]; + if ([fm fileExistsAtPath: p]) + if (![fm removeFileAtPath: p handler: nil]) + error + = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"Could not delete attachment from draft!"]; + } + else + error = [self invalidAttachmentNameError:_name]; + + return error; } /* NGMime representations */ -- (NGMimeBodyPart *)bodyPartForText +- (NGMimeBodyPart *) bodyPartForText { /* This add the text typed by the user (the primary plain/text part). */ NGMutableHashMap *map; NGMimeBodyPart *bodyPart; - NSDictionary *lInfo; - id body; - - if ((lInfo = [self fetchInfo]) == nil) - return nil; /* prepare header of body part */ - map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease]; + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; // TODO: set charset in header! - [map setObject:@"text/plain" forKey:@"content-type"]; - if ((body = [lInfo objectForKey:@"text"]) != nil) { - if ([body isKindOfClass: [NSString class]]) { - [map setObject: contentTypeValue - forKey: @"content-type"]; -// body = [body dataUsingEncoding:NSUTF8StringEncoding]; - } - } + [map setObject: @"text/plain" forKey: @"content-type"]; + if (text) + [map setObject: contentTypeValue forKey: @"content-type"]; + +// if ((body = text) != nil) { +// if ([body isKindOfClass: [NSString class]]) { +// [map setObject: contentTypeValue +// forKey: @"content-type"]; +// // body = [body dataUsingEncoding:NSUTF8StringEncoding]; +// } +// } /* prepare body content */ bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; - [bodyPart setBody:body]; + [bodyPart setBody: text]; + return bodyPart; } -- (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map +- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map { - NSDictionary *lInfo; NGMimeMessage *message; - BOOL addSuffix; +// BOOL addSuffix; id body; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - [map setObject: @"text/plain" forKey: @"content-type"]; - if ((body = [lInfo objectForKey:@"text"]) != nil) { - if ([body isKindOfClass:[NSString class]]) - /* Note: just 'utf8' is displayed wrong in Mail.app */ - [map setObject: contentTypeValue - forKey: @"content-type"]; + body = text; + if (body) + { +// if ([body isKindOfClass:[NSString class]]) + /* Note: just 'utf8' is displayed wrong in Mail.app */ + [map setObject: contentTypeValue + forKey: @"content-type"]; // body = [body dataUsingEncoding:NSUTF8StringEncoding]; - else if ([body isKindOfClass:[NSData class]] && addSuffix) { - body = [[body mutableCopy] autorelease]; +// else if ([body isKindOfClass:[NSData class]] && addSuffix) { +// body = [[body mutableCopy] autorelease]; +// } +// else if (addSuffix) { +// [self warnWithFormat: @"Note: cannot add Internet marker to body: %@", +// NSStringFromClass([body class])]; +// } + + message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; + [message setBody: body]; } - else if (addSuffix) { - [self warnWithFormat:@"Note: cannot add Internet marker to body: %@", - NSStringFromClass([body class])]; - } - } - - message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; - [message setBody:body]; + else + message = nil; + return message; } -- (NSString *)mimeTypeForExtension:(NSString *)_ext { +- (NSString *) mimeTypeForExtension: (NSString *) _ext +{ // TODO: make configurable // TODO: use /etc/mime-types - if ([_ext isEqualToString:@"txt"]) return @"text/plain"; - if ([_ext isEqualToString:@"html"]) return @"text/html"; - if ([_ext isEqualToString:@"htm"]) return @"text/html"; - if ([_ext isEqualToString:@"gif"]) return @"image/gif"; - if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg"; - if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg"; - if ([_ext isEqualToString:@"mail"]) return @"message/rfc822"; + if ([_ext isEqualToString: @"txt"]) return @"text/plain"; + if ([_ext isEqualToString: @"html"]) return @"text/html"; + if ([_ext isEqualToString: @"htm"]) return @"text/html"; + if ([_ext isEqualToString: @"gif"]) return @"image/gif"; + if ([_ext isEqualToString: @"jpg"]) return @"image/jpeg"; + if ([_ext isEqualToString: @"jpeg"]) return @"image/jpeg"; + if ([_ext isEqualToString: @"mail"]) return @"message/rfc822"; return @"application/octet-stream"; } -- (NSString *)contentTypeForAttachmentWithName:(NSString *)_name { +- (NSString *) contentTypeForAttachmentWithName: (NSString *) _name +{ NSString *s, *p; NSData *mimeData; @@ -416,35 +704,37 @@ static BOOL showTextAttachmentsInline = NO; { s = [self mimeTypeForExtension:[_name pathExtension]]; if ([_name length] > 0) - s = [s stringByAppendingFormat:@"; name=\"%@\"", _name]; + s = [s stringByAppendingFormat: @"; name=\"%@\"", _name]; } return s; } -- (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name { +- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name +{ NSString *type; NSString *cdtype; NSString *cd; type = [self contentTypeForAttachmentWithName:_name]; - if ([type hasPrefix:@"text/"]) + if ([type hasPrefix: @"text/"]) cdtype = showTextAttachmentsInline ? @"inline" : @"attachment"; - else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"]) + else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"]) cdtype = @"inline"; else cdtype = @"attachment"; - cd = [cdtype stringByAppendingString:@"; filename=\""]; + cd = [cdtype stringByAppendingString: @"; filename=\""]; cd = [cd stringByAppendingString:_name]; - cd = [cd stringByAppendingString:@"\""]; + cd = [cd stringByAppendingString: @"\""]; // TODO: add size parameter (useful addition, RFC 2183) return cd; } -- (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name { +- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name +{ NSFileManager *fm; NGMutableHashMap *map; NGMimeBodyPart *bodyPart; @@ -458,10 +748,10 @@ static BOOL showTextAttachmentsInline = NO; /* check attachment */ - fm = [self spoolFileManager]; + fm = [NSFileManager defaultManager]; p = [self pathToAttachmentWithName:_name]; if (![fm isReadableFileAtPath:p]) { - [self errorWithFormat:@"did not find attachment: '%@'", _name]; + [self errorWithFormat: @"did not find attachment: '%@'", _name]; return nil; } attachAsString = NO; @@ -472,14 +762,14 @@ static BOOL showTextAttachmentsInline = NO; map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease]; if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) { - [map setObject:s forKey:@"content-type"]; - if ([s hasPrefix:@"text/"]) + [map setObject:s forKey: @"content-type"]; + if ([s hasPrefix: @"text/"]) attachAsString = YES; - else if ([s hasPrefix:@"message/rfc822"]) + else if ([s hasPrefix: @"message/rfc822"]) is7bit = YES; } if ((s = [self contentDispositionForAttachmentWithName:_name])) - [map setObject:s forKey:@"content-disposition"]; + [map setObject:s forKey: @"content-disposition"]; /* prepare body content */ @@ -507,9 +797,9 @@ static BOOL showTextAttachmentsInline = NO; generator! */ body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO]; - [map setObject:@"7bit" forKey:@"content-transfer-encoding"]; + [map setObject: @"7bit" forKey: @"content-transfer-encoding"]; [map setObject:[NSNumber numberWithInt:[body length]] - forKey:@"content-length"]; + forKey: @"content-length"]; } else { /* @@ -522,9 +812,9 @@ static BOOL showTextAttachmentsInline = NO; encoded = [content dataByEncodingBase64]; [content release]; content = nil; - [map setObject:@"base64" forKey:@"content-transfer-encoding"]; + [map setObject: @"base64" forKey: @"content-transfer-encoding"]; [map setObject:[NSNumber numberWithInt:[encoded length]] - forKey:@"content-length"]; + forKey: @"content-length"]; /* Note: the -init method will create a temporary file! */ body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes] @@ -538,55 +828,61 @@ static BOOL showTextAttachmentsInline = NO; return bodyPart; } -- (NSArray *)bodyPartsForAllAttachments { +- (NSArray *) bodyPartsForAllAttachments +{ /* returns nil on error */ - NSMutableArray *bodyParts; NSArray *names; unsigned i, count; - + NGMimeBodyPart *bodyPart; + NSMutableArray *bodyParts; + names = [self fetchAttachmentNames]; - if ((count = [names count]) == 0) - return [NSArray array]; - - bodyParts = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NGMimeBodyPart *bodyPart; - - bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]]; - if (bodyPart == nil) - return nil; - - [bodyParts addObject:bodyPart]; - } + count = [names count]; + bodyParts = [NSMutableArray arrayWithCapacity: count]; + + for (i = 0; i < count; i++) + { + bodyPart = [self bodyPartForAttachmentWithName: [names objectAtIndex: i]]; + [bodyParts addObject: bodyPart]; + } + return bodyParts; } -- (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map - andBodyParts:(NSArray *)_bodyParts +- (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map + andBodyParts: (NSArray *) _bodyParts { NGMimeMessage *message; NGMimeMultipartBody *mBody; NGMimeBodyPart *part; NSEnumerator *e; - [map addObject:MultiMixedType forKey:@"content-type"]; - - message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; - mBody = [[NGMimeMultipartBody alloc] initWithPart:message]; - + [map addObject: MultiMixedType forKey: @"content-type"]; + + message = [[NGMimeMessage alloc] initWithHeader: map]; + [message autorelease]; + mBody = [[NGMimeMultipartBody alloc] initWithPart: message]; + part = [self bodyPartForText]; - [mBody addBodyPart:part]; - + [mBody addBodyPart: part]; + e = [_bodyParts objectEnumerator]; - while ((part = [e nextObject]) != nil) - [mBody addBodyPart:part]; - - [message setBody:mBody]; - [mBody release]; mBody = nil; + part = [e nextObject]; + while (part) + { + [mBody addBodyPart: part]; + part = [e nextObject]; + } + + [message setBody: mBody]; + [mBody release]; + return message; } -- (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map { +- (void) _addHeaders: (NSDictionary *) _h + toHeaderMap: (NGMutableHashMap *) _map +{ NSEnumerator *names; NSString *name; @@ -602,398 +898,225 @@ static BOOL showTextAttachmentsInline = NO; } } -- (BOOL)isEmptyValue:(id)_value { +- (BOOL) isEmptyValue: (id) _value +{ if (![_value isNotNull]) return YES; - if ([_value isKindOfClass:[NSArray class]]) + if ([_value isKindOfClass: [NSArray class]]) return [_value count] == 0 ? YES : NO; - if ([_value isKindOfClass:[NSString class]]) + if ([_value isKindOfClass: [NSString class]]) return [_value length] == 0 ? YES : NO; - + return NO; } -- (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers { +- (NGMutableHashMap *) mimeHeaderMapWithHeaders: (NSDictionary *) _headers +{ NGMutableHashMap *map; - NSDictionary *lInfo; // TODO: this should be some kind of object? NSArray *emails; NSString *s, *dateString; id from, replyTo; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease]; /* add recipients */ - if ((emails = [lInfo objectForKey:@"to"]) != nil) { + if ((emails = [headers objectForKey: @"to"]) != nil) { if ([emails count] == 0) { - [self errorWithFormat:@"missing 'to' recipient in email!"]; + [self errorWithFormat: @"missing 'to' recipient in email!"]; return nil; } - [map setObjects:emails forKey:@"to"]; + [map setObjects: emails forKey: @"to"]; } - if ((emails = [lInfo objectForKey:@"cc"]) != nil) - [map setObjects:emails forKey:@"cc"]; - if ((emails = [lInfo objectForKey:@"bcc"]) != nil) - [map setObjects:emails forKey:@"bcc"]; + if ((emails = [headers objectForKey: @"cc"]) != nil) + [map setObjects:emails forKey: @"cc"]; + if ((emails = [headers objectForKey: @"bcc"]) != nil) + [map setObjects:emails forKey: @"bcc"]; /* add senders */ - from = [lInfo objectForKey:@"from"]; - replyTo = [lInfo objectForKey:@"replyTo"]; + from = [headers objectForKey: @"from"]; + replyTo = [headers objectForKey: @"replyTo"]; if (![self isEmptyValue:from]) { if ([from isKindOfClass:[NSArray class]]) - [map setObjects:from forKey:@"from"]; + [map setObjects: from forKey: @"from"]; else - [map setObject:from forKey:@"from"]; + [map setObject: from forKey: @"from"]; } - if (![self isEmptyValue:replyTo]) { + if (![self isEmptyValue: replyTo]) { if ([from isKindOfClass:[NSArray class]]) - [map setObjects:from forKey:@"reply-to"]; + [map setObjects:from forKey: @"reply-to"]; else - [map setObject:from forKey:@"reply-to"]; + [map setObject:from forKey: @"reply-to"]; } else if (![self isEmptyValue:from]) - [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"]; + [map setObjects:[map objectsForKey: @"from"] forKey: @"reply-to"]; /* add subject */ - if ([(s = [lInfo objectForKey:@"subject"]) length] > 0) + if ([(s = [headers objectForKey: @"subject"]) length] > 0) [map setObject: [s asQPSubjectString: @"utf-8"] - forKey:@"subject"]; -// [map setObject: [s asQPSubjectString: @"utf-8"] forKey:@"subject"]; + forKey: @"subject"]; +// [map setObject: [s asQPSubjectString: @"utf-8"] forKey: @"subject"]; /* add standard headers */ dateString = [[NSCalendarDate date] rfc822DateString]; - [map addObject: dateString forKey:@"date"]; - [map addObject: @"1.0" forKey:@"MIME-Version"]; - [map addObject: userAgent forKey:@"X-Mailer"]; + [map addObject: dateString forKey: @"date"]; + [map addObject: @"1.0" forKey: @"MIME-Version"]; + [map addObject: userAgent forKey: @"X-Mailer"]; /* add custom headers */ - [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map]; - [self _addHeaders:_headers toHeaderMap:map]; +// [self _addHeaders: [lInfo objectForKey: @"headers"] toHeaderMap:map]; + [self _addHeaders: _headers toHeaderMap: map]; return map; } -- (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers { - NSAutoreleasePool *pool; +- (NGMimeMessage *) mimeMessageWithHeaders: (NSDictionary *) _headers +{ NGMutableHashMap *map; NSArray *bodyParts; NGMimeMessage *message; - - pool = [[NSAutoreleasePool alloc] init]; - - if ([self fetchInfo] == nil) { - [self errorWithFormat:@"could not locate draft fetch info!"]; - return nil; - } - - if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil) - return nil; - [self debugWithFormat:@"MIME Envelope: %@", map]; - - if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) { - [self errorWithFormat: - @"could not create body parts for attachments!"]; - return nil; // TODO: improve error handling, return exception - } - [self debugWithFormat:@"attachments: %@", bodyParts]; - - if ([bodyParts count] == 0) { - /* no attachments */ - message = [self mimeMessageForContentWithHeaderMap:map]; - } - else { - /* attachments, create multipart/mixed */ - message = [self mimeMultiPartMessageWithHeaderMap:map - andBodyParts:bodyParts]; - } - [self debugWithFormat:@"message: %@", message]; - message = [message retain]; - [pool release]; - return [message autorelease]; -} -- (NGMimeMessage *)mimeMessage { - return [self mimeMessageWithHeaders:nil]; -} - -- (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h { - NGMimeMessageGenerator *gen; - NSAutoreleasePool *pool; - NGMimeMessage *message; - NSString *tmpPath; + message = nil; - pool = [[NSAutoreleasePool alloc] init]; - - message = [self mimeMessageWithHeaders:_h]; - if (![message isNotNull]) - return nil; - if ([message isKindOfClass:[NSException class]]) { - [self errorWithFormat:@"error: %@", message]; - return nil; - } - - gen = [[NGMimeMessageGenerator alloc] init]; - tmpPath = [[gen generateMimeFromPartToFile:message] copy]; - [gen release]; gen = nil; - - [pool release]; - return [tmpPath autorelease]; -} -- (NSString *)saveMimeMessageToTemporaryFile { - return [self saveMimeMessageToTemporaryFileWithHeaders:nil]; -} - -- (void)deleteTemporaryMessageFile:(NSString *)_path { - NSFileManager *fm; + map = [self mimeHeaderMapWithHeaders: _headers]; + if (map) + { + [self debugWithFormat: @"MIME Envelope: %@", map]; - if (![_path isNotNull]) - return; + bodyParts = [self bodyPartsForAllAttachments]; + if (bodyParts) + { + [self debugWithFormat: @"attachments: %@", bodyParts]; + + if ([bodyParts count] == 0) + /* no attachments */ + message = [self mimeMessageForContentWithHeaderMap: map]; + else + /* attachments, create multipart/mixed */ + message = [self mimeMultiPartMessageWithHeaderMap: map + andBodyParts: bodyParts]; + [self debugWithFormat: @"message: %@", message]; + } + else + [self errorWithFormat: + @"could not create body parts for attachments!"]; + } - fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_path]) - return; - - [fm removeFileAtPath:_path handler:nil]; + return message; } -- (NSArray *)allRecipients { - NSDictionary *lInfo; - NSMutableArray *ma; - NSArray *tmp; - - if ((lInfo = [self fetchInfo]) == nil) - return nil; - - ma = [NSMutableArray arrayWithCapacity:16]; - if ((tmp = [lInfo objectForKey:@"to"]) != nil) - [ma addObjectsFromArray:tmp]; - if ((tmp = [lInfo objectForKey:@"cc"]) != nil) - [ma addObjectsFromArray:tmp]; - if ((tmp = [lInfo objectForKey:@"bcc"]) != nil) - [ma addObjectsFromArray:tmp]; - return ma; +- (NGMimeMessage *) mimeMessage +{ + return [self mimeMessageWithHeaders: nil]; } -- (NSException *) sendMail +- (NSData *) mimeMessageAsData { - NSException *error; - NSString *tmpPath; - - /* save MIME mail to file */ - - tmpPath = [self saveMimeMessageToTemporaryFile]; - if (![tmpPath isNotNull]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not save MIME message for draft!"]; - } - - /* send mail */ - error = [[SOGoMailer sharedMailer] sendMailAtPath: tmpPath - toRecipients: [self allRecipients] - sender: [self sender]]; + NGMimeMessageGenerator *generator; + NSData *message; - /* delete temporary file */ - [self deleteTemporaryMessageFile:tmpPath]; + generator = [NGMimeMessageGenerator new]; + message = [generator generateMimeFromPart: [self mimeMessage]]; + [generator release]; - return error; + return message; } -/* operations */ +- (NSArray *) allRecipients +{ + NSMutableArray *allRecipients; + NSArray *recipients; + NSString *fieldNames[] = {@"to", @"cc", @"bcc"}; + unsigned int count; -- (NSException *)delete { - NSFileManager *fm; - NSString *p, *sp; - NSEnumerator *e; - - if ((fm = [self spoolFileManager]) == nil) { - [self errorWithFormat:@"missing spool file manager!"]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"missing spool file manager!"]; - } - - p = [self draftFolderPath]; - if (![fm fileExistsAtPath:p]) { - return [NSException exceptionWithHTTPStatus:404 /* not found */ - reason:@"did not find draft!"]; - } - - e = [[fm directoryContentsAtPath:p] objectEnumerator]; - while ((sp = [e nextObject])) { - sp = [p stringByAppendingPathComponent:sp]; - if (draftDeleteDisabled) { - [self logWithFormat:@"should delete draft file %@ ...", sp]; - continue; - } - - if (![fm removeFileAtPath:sp handler:nil]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to delete draft!"]; - } - } + allRecipients = [NSMutableArray arrayWithCapacity: 16]; - if (draftDeleteDisabled) { - [self logWithFormat:@"should delete draft directory: %@", p]; - } - else { - if (![fm removeFileAtPath:p handler:nil]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to delete draft directory!"]; + for (count = 0; count < 3; count++) + { + recipients = [headers objectForKey: fieldNames[count]]; + if ([recipients count] > 0) + [allRecipients addObjectsFromArray: recipients]; } - } - return nil; -} -- (NSData *)content { - /* Note: does not cache, expensive operation */ - NSData *data; - NSString *p; - - if ((p = [self saveMimeMessageToTemporaryFile]) == nil) - return nil; - - data = [NSData dataWithContentsOfMappedFile:p]; - - /* delete temporary file */ - [self deleteTemporaryMessageFile:p]; - - return data; -} -- (NSString *)contentAsString { - NSString *str; - NSData *data; - - if ((data = [self content]) == nil) - return nil; - - str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; - if (str == nil) { - [self errorWithFormat:@"could not load draft as ASCII (data size=%d)", - [data length]]; - return nil; - } - - return [str autorelease]; + return allRecipients; } -/* actions */ - -- (id)DELETEAction:(id)_ctx { +- (NSException *) sendMail +{ NSException *error; - - if ((error = [self delete]) != nil) - return error; + SOGoMailFolder *sentFolder; + NSData *message; + NSURL *sourceIMAP4URL; - return [NSNumber numberWithBool:YES]; /* delete worked out ... */ -} - -- (id) GETAction: (id) _ctx -{ - /* - Override, because SOGoObject's GETAction uses the less efficient - -contentAsString method. - */ - WORequest *rq; - - rq = [_ctx request]; - if ([rq isSoWebDAVRequest]) { - WOResponse *r; - NSData *content; - - if ((content = [self content]) == nil) { - return [NSException exceptionWithHTTPStatus:500 - reason:@"Could not generate MIME content!"]; + /* send mail */ + sentFolder = [[self mailAccountFolder] sentFolderInContext: context]; + if ([sentFolder isKindOfClass: [NSException class]]) + error = (NSException *) sentFolder; + else + { + message = [self mimeMessageAsData]; + error = [[SOGoMailer sharedMailer] sendMailData: message + toRecipients: [self allRecipients] + sender: [self sender]]; + if (!error) + { + error = [sentFolder postData: message flags: @"seen"]; + if (!error) + { + [self imap4Connection]; + if (IMAP4ID > -1) + [imap4 markURLDeleted: [self imap4URL]]; + if (sourceURL && sourceFlag) + { + sourceIMAP4URL = [NSURL URLWithString: sourceURL]; + [imap4 addFlags: sourceFlag toURL: sourceIMAP4URL]; + } + } + } } - r = [_ctx response]; - [r setHeader:@"message/rfc822" forKey:@"content-type"]; - [r setContent:content]; - return r; - } - - return [super GETAction:_ctx]; -} -/* fake being a SOGoMailObject */ - -- (id)fetchParts:(NSArray *)_parts { - return [NSDictionary dictionaryWithObject:self forKey:@"fetch"]; + return error; } -- (NSString *)uid { - return [self nameInContainer]; -} -- (NSArray *)flags { - static NSArray *seenFlags = nil; - seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil]; - return seenFlags; -} -- (unsigned)size { - // TODO: size, hard to support, we would need to generate MIME? - return 0; -} +/* operations */ -- (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails { - NSMutableArray *ma; - unsigned i, count; - - if (_emails == nil) - return nil; - if ((count = [_emails count]) == 0) - return [NSArray array]; - - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NGImap4EnvelopeAddress *envaddr; - - envaddr = [[NGImap4EnvelopeAddress alloc] - initWithString:[_emails objectAtIndex:i]]; - if ([envaddr isNotNull]) - [ma addObject:envaddr]; - [envaddr release]; - } - return ma; -} +- (NSString *) contentAsString +{ + NSString *str; + NSData *message; -- (NGImap4Envelope *)envelope { - NSDictionary *lInfo; - id from, replyTo; - - if (envelope != nil) - return envelope; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - - if ((from = [self sender]) != nil) - from = [NSArray arrayWithObjects:&from count:1]; + message = [self mimeMessageAsData]; + if (message) + { + str = [[NSString alloc] initWithData: message + encoding: NSUTF8StringEncoding]; + if (!str) + [self errorWithFormat: @"could not load draft as UTF-8 (data size=%d)", + [message length]]; + else + [str autorelease]; + } + else + { + [self errorWithFormat: @"message data is empty"]; + str = nil; + } - if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) { - if (![replyTo isKindOfClass:[NSArray class]]) - replyTo = [NSArray arrayWithObjects:&replyTo count:1]; - } - - envelope = - [[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer] - subject:[lInfo objectForKey:@"subject"] - from:from replyTo:replyTo - to:[lInfo objectForKey:@"to"] - cc:[lInfo objectForKey:@"cc"] - bcc:[lInfo objectForKey:@"bcc"]]; - return envelope; + return str; } /* debugging */ -- (BOOL)isDebuggingEnabled { +- (BOOL) isDebuggingEnabled +{ return debugOn; } diff --git a/SoObjects/Mailer/SOGoDraftsFolder.h b/SoObjects/Mailer/SOGoDraftsFolder.h index 982f28ef..ea97a6f6 100644 --- a/SoObjects/Mailer/SOGoDraftsFolder.h +++ b/SoObjects/Mailer/SOGoDraftsFolder.h @@ -1,49 +1,32 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#ifndef __Mailer_SOGoDraftsFolder_H__ -#define __Mailer_SOGoDraftsFolder_H__ - -#include - -/* - SOGoDraftsFolder - Parent object: SOGoMailAccount - Child objects: SOGoDraftObject's - - The SOGoDraftsFolder is used for composing new messages. It is necessary - because we can't cache objects in a session. So the contents of the drafts - folder are some kind of "mail creation transaction". -*/ - -@interface SOGoDraftsFolder : SOGoMailBaseObject -{ -} - -/* new objects */ - -- (NSString *)makeNewObjectNameInContext:(id)_ctx; -- (NSString *)newObjectBaseURLInContext:(id)_ctx; -- (id)newObjectInContext:(id)_ctx; +/* SOGoDraftsFolder.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 "SOGoMailFolder.h" + +@class SOGoDraftObject; + +@interface SOGoDraftsFolder : SOGoMailFolder + +- (SOGoDraftObject *) newDraft; +- (BOOL) isInDraftsFolder; @end - -#endif /* __Mailer_SOGoDraftsFolder_H__ */ diff --git a/SoObjects/Mailer/SOGoDraftsFolder.m b/SoObjects/Mailer/SOGoDraftsFolder.m index 2509ff05..8cd7b2af 100644 --- a/SoObjects/Mailer/SOGoDraftsFolder.m +++ b/SoObjects/Mailer/SOGoDraftsFolder.m @@ -1,219 +1,115 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import - -#import +/* SOGoDraftsFolder.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 #import -#import -#import -#import -#import +#import +#import -#import #import "SOGoDraftObject.h" #import "SOGoDraftsFolder.h" -@implementation SOGoDraftsFolder - static NSString *spoolFolder = nil; +static NSTimeInterval lastNew = 0; +static unsigned int newCount; + +@implementation SOGoDraftsFolder + + (void) initialize { - NSUserDefaults *ud;; + NSUserDefaults *ud; if (!spoolFolder) { ud = [NSUserDefaults standardUserDefaults]; - spoolFolder = [[ud stringForKey:@"SOGoMailSpoolPath"] copy]; - if ([spoolFolder length] < 3) + spoolFolder = [ud stringForKey:@"SOGoMailSpoolPath"]; + if (![spoolFolder length]) spoolFolder = @"/tmp/"; + [spoolFolder retain]; NSLog(@"Note: using SOGo mail spool folder: %@", spoolFolder); } } -/* new objects */ - -- (NSString *) makeNewObjectNameInContext: (id) _ctx -{ - static int counter = 1; // THREAD - - return [NSString stringWithFormat:@"draft_%08d_%08x", getpid(), counter++]; -} - -- (NSString *) newObjectBaseURLInContext: (id) _ctx -{ - NSString *s, *n; - - n = [self makeNewObjectNameInContext:_ctx]; - if (![n isNotNull]) return nil; - - s = [self baseURLInContext:_ctx]; - if (![s isNotNull]) return nil; - if (![s hasSuffix:@"/"]) s = [s stringByAppendingString:@"/"]; - return [s stringByAppendingString:n]; -} - -- (id) newObjectInContext: (id) _ctx -{ - return [self lookupName:[self makeNewObjectNameInContext:_ctx] - inContext:_ctx acquire:NO]; -} - -/* draft folder functionality */ - -- (NSFileManager *) spoolFileManager -{ - return [NSFileManager defaultManager]; -} - -- (NSString *) spoolFolderPath +- (NSString *) generateNameForNewDraft { - return spoolFolder; -} + NSString *newName, *login; + unsigned int currentTime; -- (NSString *) userSpoolFolderPath -{ - NSString *p, *n; - - p = [self spoolFolderPath]; - n = [[self lookupUserFolder] nameInContainer]; + currentTime = [[NSDate date] timeIntervalSince1970]; + if (currentTime == lastNew) + newCount++; + else + { + lastNew = currentTime; + newCount = 1; + } - return [p stringByAppendingPathComponent:n]; -} + login = [[context activeUser] login]; + newName = [NSString stringWithFormat: @"newDraft%u-%u", + currentTime, newCount]; -- (BOOL) _ensureUserSpoolFolderPath -{ - NSFileManager *fm; - - if ((fm = [self spoolFileManager]) == nil) { - [self errorWithFormat:@"missing spool file manager!"]; - return NO; - } - return [fm createDirectoriesAtPath:[self userSpoolFolderPath] - attributes:nil]; + return newName; } -- (NSArray *) fetchMailNames +- (SOGoDraftObject *) newDraft { - NSString *p; - - if ((p = [self userSpoolFolderPath]) == nil) - return nil; - - return [[self spoolFileManager] directoryContentsAtPath:p]; + return [SOGoDraftObject objectWithName: [self generateNameForNewDraft] + inContainer: self]; } -/* folder methods (used by template) */ - -- (NSArray *) fetchUIDsMatchingQualifier: (id) _q - sortOrdering: (id) _so +- (id) lookupName: (NSString *) name + inContext: (WOContext *) localContext + acquire: (BOOL) acquire { - // TODO: retrieve contained objects - NSArray *allUids; - - allUids = [self fetchMailNames]; - if (![allUids isNotNull]) { - [self logWithFormat:@"Note: no uids in drafts folder: %@", - [self userSpoolFolderPath]]; - return [NSArray array]; - } - - // TODO: should sort uids (q=%@,so=%@): %@", _q, _so, allUids]; - return allUids; -} -- (NSArray *)fetchUIDs: (NSArray *)_uids parts: (NSArray *)_parts { - /* FLAGS, ENVELOPE, RFC822.SIZE */ - NSMutableArray *drafts; - unsigned i, count; - - if (_uids == nil) - return nil; - if ((count = [_uids count]) == 0) - return [NSArray array]; - - drafts = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - SOGoDraftObject *draft; - id parts; - - draft = [self lookupName:[_uids objectAtIndex:i] inContext:nil acquire:NO]; - if (![draft isNotNull] || [draft isKindOfClass:[NSException class]]) - continue; - - parts = [draft fetchParts:_parts]; - if ([parts isNotNull]) - [drafts addObject:parts]; - } - - return drafts; -} + id object; -/* name lookup */ + if ([name hasPrefix: @"newDraft"]) + object = [SOGoDraftObject objectWithName: name inContainer: self]; + else + object = [super lookupName: name + inContext: localContext + acquire: acquire]; -- (id) lookupDraftMessage: (NSString *) _key - inContext: (id) _ctx -{ - // TODO: we might want to check for existence prior controller creation - return [[[SOGoDraftObject alloc] initWithName:_key - inContainer:self] autorelease]; + return object; } -- (id) lookupName: (NSString *) _key - inContext: (id) _ctx - acquire: (BOOL) _flag -{ - id obj; - - /* first check attributes directly bound to the application */ - if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil) - return obj; - - if ((obj = [self lookupDraftMessage:_key inContext:_ctx]) != nil) - return obj; - - /* return 404 to stop acquisition */ - return [NSException exceptionWithHTTPStatus:404 /* Not Found */]; -} - -/* WebDAV */ - -- (BOOL) davIsCollection +- (BOOL) isInDraftsFolder { return YES; } -- (NSArray *) toOneRelationshipKeys +- (NSString *) userSpoolFolderPath { - return [self fetchMailNames]; -} + NSString *login; -/* folder type */ + login = [[context activeUser] login]; -- (NSString *) outlookFolderClass -{ - return @"IPF.Drafts"; + return [NSString stringWithFormat: @"%@/%@", + spoolFolder, login]; } -@end /* SOGoDraftsFolder */ +@end diff --git a/SoObjects/Mailer/SOGoMailAccount.h b/SoObjects/Mailer/SOGoMailAccount.h index d9bd0751..dff40537 100644 --- a/SoObjects/Mailer/SOGoMailAccount.h +++ b/SoObjects/Mailer/SOGoMailAccount.h @@ -22,7 +22,7 @@ #ifndef __Mailer_SOGoMailAccount_H__ #define __Mailer_SOGoMailAccount_H__ -#include +#import /* SOGoMailAccount @@ -33,41 +33,42 @@ password, etc) */ -@class NSString, NSArray; -@class SOGoMailFolder, SOGoMailIdentity; +@class NSArray; +@class NSString; +@class SOGoDraftsFolder; +@class SOGoMailFolder; @interface SOGoMailAccount : SOGoMailBaseObject { SOGoMailFolder *inboxFolder; + SOGoDraftsFolder *draftsFolder; SOGoMailFolder *sentFolder; SOGoMailFolder *trashFolder; } /* folder pathes */ -- (NSArray *)allFolderPaths; -- (NSArray *)additionalRootFolderNames; /* stuff like filters and drafts */ +- (NSArray *) allFolderPaths; +- (NSArray *) additionalRootFolderNames; /* stuff like filters and drafts */ +- (BOOL) isInDraftsFolder; /* shared accounts */ -- (BOOL)isSharedAccount; -- (NSString *)sharedAccountName; - -/* identity */ - -- (SOGoMailIdentity *)preferredIdentity; +- (BOOL) isSharedAccount; +- (NSString *) sharedAccountName; /* special folders */ -- (NSString *)inboxFolderNameInContext:(id)_ctx; -- (NSString *)draftsFolderNameInContext:(id)_ctx; -- (NSString *)sieveFolderNameInContext:(id)_ctx; -- (NSString *)sentFolderNameInContext:(id)_ctx; -- (NSString *)trashFolderNameInContext:(id)_ctx; +- (NSString *) inboxFolderNameInContext: (id)_ctx; +- (NSString *) draftsFolderNameInContext: (id)_ctx; +- (NSString *) sieveFolderNameInContext: (id)_ctx; +- (NSString *) sentFolderNameInContext: (id)_ctx; +- (NSString *) trashFolderNameInContext: (id)_ctx; -- (SOGoMailFolder *)inboxFolderInContext:(id)_ctx; -- (SOGoMailFolder *)sentFolderInContext:(id)_ctx; -- (SOGoMailFolder *)trashFolderInContext:(id)_ctx; +- (SOGoMailFolder *) inboxFolderInContext: (id)_ctx; +- (SOGoDraftsFolder *) draftsFolderInContext: (id)_ctx; +- (SOGoMailFolder *) sentFolderInContext: (id)_ctx; +- (SOGoMailFolder *) trashFolderInContext: (id)_ctx; /* user defaults */ - (NSString *) sharedFolderName; diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 93927275..bf2a7a58 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -35,23 +35,23 @@ #import "SOGoMailFolder.h" #import "SOGoMailManager.h" #import "SOGoDraftsFolder.h" -#import "SOGoUser+Mail.h" #import "SOGoMailAccount.h" @implementation SOGoMailAccount -static NSArray *rootFolderNames = nil; -static NSString *inboxFolderName = @"INBOX"; -static NSString *draftsFolderName = @"Drafts"; -static NSString *sieveFolderName = @"Filters"; +static NSArray *rootFolderNames = nil; +static NSString *inboxFolderName = @"INBOX"; +static NSString *draftsFolderName = @"Drafts"; +static NSString *sieveFolderName = @"Filters"; static NSString *sentFolderName = nil; static NSString *trashFolderName = nil; -static NSString *sharedFolderName = @""; // TODO: add English default +static NSString *sharedFolderName = @""; // TODO: add English default static NSString *otherUsersFolderName = @""; // TODO: add English default -static BOOL useAltNamespace = NO; +static BOOL useAltNamespace = NO; -+ (void)initialize { ++ (void) initialize +{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSString *cfgDraftsFolderName; @@ -82,22 +82,43 @@ static BOOL useAltNamespace = NO; NSLog(@"Note: using shared-folders name: '%@'", sharedFolderName); NSLog(@"Note: using other-users-folders name: '%@'", otherUsersFolderName); - if ([ud boolForKey:@"SOGoEnableSieveFolder"]) { + if ([ud boolForKey: @"SOGoEnableSieveFolder"]) rootFolderNames = [[NSArray alloc] initWithObjects: draftsFolderName, sieveFolderName, nil]; - } - else { + else rootFolderNames = [[NSArray alloc] initWithObjects: draftsFolderName, nil]; - } +} + +- (id) init +{ + if ((self = [super init])) + { + inboxFolder = nil; + draftsFolder = nil; + sentFolder = nil; + trashFolder = nil; + } + + return self; +} + +- (void) dealloc +{ + [inboxFolder release]; + [draftsFolder release]; + [sentFolder release]; + [trashFolder release]; + [super dealloc]; } /* shared accounts */ -- (BOOL)isSharedAccount { +- (BOOL) isSharedAccount +{ NSString *s; NSRange r; @@ -110,23 +131,29 @@ static BOOL useAltNamespace = NO; return [s rangeOfString:@".-."].length > 0 ? YES : NO; } -- (NSString *)sharedAccountName { +- (NSString *) sharedAccountName +{ return nil; } /* listing the available folders */ -- (NSArray *)additionalRootFolderNames { +- (NSArray *) additionalRootFolderNames +{ return rootFolderNames; } +- (BOOL) isInDraftsFolder +{ + return NO; +} + - (NSArray *) toManyRelationshipKeys { NSMutableArray *folders; NSArray *imapFolders, *additionalFolders; - folders = [NSMutableArray new]; - [folders autorelease]; + folders = [NSMutableArray array]; imapFolders = [[self imap4Connection] subfoldersForURL: [self imap4URL]]; additionalFolders = [self additionalRootFolderNames]; @@ -141,16 +168,10 @@ static BOOL useAltNamespace = NO; return folders; } -/* identity */ - -- (SOGoMailIdentity *)preferredIdentity { - return [[context activeUser] primaryMailIdentityForAccount: - [self nameInContainer]]; -} - /* hierarchy */ -- (SOGoMailAccount *)mailAccountFolder { +- (SOGoMailAccount *) mailAccountFolder +{ return self; } @@ -175,11 +196,13 @@ static BOOL useAltNamespace = NO; /* IMAP4 */ -- (BOOL)useSSL { +- (BOOL) useSSL +{ return NO; } -- (NSString *)imap4LoginFromHTTP { +- (NSString *) imap4LoginFromHTTP +{ WORequest *rq; NSString *s; NSArray *creds; @@ -231,21 +254,29 @@ static BOOL useAltNamespace = NO; /* name lookup */ -- (id)lookupFolder:(NSString *)_key ofClassNamed:(NSString *)_cn - inContext:(id)_cx +- (id) lookupFolder: (NSString *) _key + ofClassNamed: (NSString *) _cn + inContext: (id) _cx { Class clazz; + SOGoMailFolder *folder; - if ((clazz = NSClassFromString(_cn)) == Nil) { - [self logWithFormat:@"ERROR: did not find class '%@' for key: '%@'", + if ((clazz = NSClassFromString(_cn)) == Nil) + { + [self logWithFormat:@"ERROR: did not find class '%@' for key: '%@'", _cn, _key]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"did not find mail folder class!"]; - } - return [[[clazz alloc] initWithName:_key inContainer:self] autorelease]; + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:@"did not find mail folder class!"]; + } + + folder = [clazz objectWithName: _key inContainer: self]; + + return folder; } -- (id)lookupImap4Folder:(NSString *)_key inContext:(id)_cx { +- (id) lookupImap4Folder: (NSString *) _key + inContext: (id) _cx +{ NSString *s; s = [_key isEqualToString: [self trashFolderNameInContext:_cx]] @@ -254,11 +285,15 @@ static BOOL useAltNamespace = NO; return [self lookupFolder:_key ofClassNamed:s inContext:_cx]; } -- (id)lookupDraftsFolder:(NSString *)_key inContext:(id)_ctx { - return [self lookupFolder:_key ofClassNamed:@"SOGoDraftsFolder" - inContext:_ctx]; +- (id) lookupDraftsFolder: (NSString *) _key + inContext: (id) _ctx +{ + return [self lookupFolder: _key ofClassNamed: @"SOGoDraftsFolder" + inContext: _ctx]; } -- (id)lookupFiltersFolder:(NSString *)_key inContext:(id)_ctx { + +- (id) lookupFiltersFolder: (NSString *) _key inContext: (id) _ctx +{ return [self lookupFolder:_key ofClassNamed:@"SOGoSieveScriptsFolder" inContext:_ctx]; } @@ -332,6 +367,29 @@ static BOOL useAltNamespace = NO; return inboxFolder; } +- (SOGoDraftsFolder *) draftsFolderInContext: (id) _ctx +{ + SOGoMailFolder *lookupFolder; + // TODO: use some profile to determine real location, use a -traverse lookup + + if (!draftsFolder) + { + lookupFolder = (useAltNamespace + ? (id) self + : [self inboxFolderInContext:_ctx]); + if (![lookupFolder isKindOfClass: [NSException class]]) + draftsFolder + = [lookupFolder lookupName: [self draftsFolderNameInContext:_ctx] + inContext: _ctx acquire: NO]; + if (![draftsFolder isNotNull]) + draftsFolder = [NSException exceptionWithHTTPStatus: 404 /* not found */ + reason: @"did not find Drafts folder!"]; + [draftsFolder retain]; + } + + return draftsFolder; +} + - (SOGoMailFolder *) sentFolderInContext: (id) _ctx { SOGoMailFolder *lookupFolder; diff --git a/SoObjects/Mailer/SOGoMailAccounts.h b/SoObjects/Mailer/SOGoMailAccounts.h index 3bccf908..96475f7a 100644 --- a/SoObjects/Mailer/SOGoMailAccounts.h +++ b/SoObjects/Mailer/SOGoMailAccounts.h @@ -22,7 +22,7 @@ #ifndef __Mailer_SOGoMailAccounts_H__ #define __Mailer_SOGoMailAccounts_H__ -#include +#import /* SOGoMailAccounts @@ -40,11 +40,6 @@ @class NSArray; @interface SOGoMailAccounts : SOGoObject -{ -} - -- (NSArray *)fetchAllIdentities; -- (NSArray *)fetchIdentitiesWithEmitterPermissions; @end diff --git a/SoObjects/Mailer/SOGoMailAccounts.m b/SoObjects/Mailer/SOGoMailAccounts.m index 71d4f0b8..09a8cfd6 100644 --- a/SoObjects/Mailer/SOGoMailAccounts.m +++ b/SoObjects/Mailer/SOGoMailAccounts.m @@ -26,7 +26,8 @@ #import #import -#import "SOGoUser+Mail.h" +#import "../SOGo/NSArray+Utilities.h" +#import "../SOGo/SOGoUser.h" #import "SOGoMailAccounts.h" @@ -38,72 +39,28 @@ static NSString *AgenorShareLoginMarker = @".-."; - (BOOL) isInHomeFolderBranchOfLoggedInAccount: (NSString *) userLogin { - return [[[self container] nameInContainer] isEqualToString: userLogin]; + return [[container nameInContainer] isEqualToString: userLogin]; } -- (NSArray *)toManyRelationshipKeys { - SOGoUser *user; - id account; -// NSArray *shares; - NSString *userLogin; - - /* - Note: this is not strictly correct. The accounts being retrieved should be - the accounts based on the container object of this folder. Given - sufficient rights (eg delegation rights!), this would allow you to - browse the hierarchies of other users. - - But then, the home-folder would need to know about mail - functionality which isn't perfect either. - => TODO - */ - user = [context activeUser]; - userLogin = [user login]; - - /* for now: return nothing if the home-folder does not belong to the login */ - if (![self isInHomeFolderBranchOfLoggedInAccount: userLogin]) { - [self warnWithFormat:@"User %@ tried to access mail hierarchy of %@", - [user login], [[self container] nameInContainer]]; - return nil; - } - - account = [user primaryIMAP4AccountString]; - if ([account isNotNull]) account = [NSArray arrayWithObject:account]; - - return account; -// shares = [user valueForKey:@"additionalIMAP4AccountStrings"] -// return ([shares count] == 0) -// ? account -// : [account arrayByAddingObjectsFromArray:shares]; -} - -- (NSArray *) fetchIdentitiesWithOnlyEmitterAccess: (BOOL) _flag +- (NSArray *) toManyRelationshipKeys { - NSString *accountString; + NSArray *accounts; - accountString = [[context activeUser] primaryIMAP4AccountString]; - - return [NSArray arrayWithObject: accountString]; -} + accounts = [[context activeUser] mailAccounts]; -- (NSArray *)fetchAllIdentities { - return [self fetchIdentitiesWithOnlyEmitterAccess:NO]; -} - -- (NSArray *)fetchIdentitiesWithEmitterPermissions { - return [self fetchIdentitiesWithOnlyEmitterAccess:YES]; + return [accounts objectsForKey: @"name"]; } /* name lookup */ -- (BOOL)isValidMailAccountName:(NSString *)_key { - if ([_key length] == 0) - return NO; - - return YES; +- (BOOL) isValidMailAccountName: (NSString *) _key +{ + return ([_key length] > 0); } -- (id)mailAccountWithName:(NSString *)_key inContext:(id)_ctx { +- (id) mailAccountWithName: (NSString *) _key + inContext: (id) _ctx +{ static Class ctClass = Nil; id ct; @@ -118,22 +75,22 @@ static NSString *AgenorShareLoginMarker = @".-."; return [ct autorelease]; } -- (id)sharedMailAccountWithName:(NSString *)_key inContext:(id)_ctx { +- (id) sharedMailAccountWithName: (NSString *) _key + inContext: (id) _ctx +{ static Class ctClass = Nil; - id ct; - + + if (ctClass == Nil) + ctClass = NSClassFromString (@"SOGoSharedMailAccount"); if (ctClass == Nil) - ctClass = NSClassFromString(@"SOGoSharedMailAccount"); - if (ctClass == Nil) { [self errorWithFormat:@"missing SOGoSharedMailAccount class!"]; - return nil; - } - ct = [[ctClass alloc] initWithName:_key inContainer:self]; - return [ct autorelease]; + return [ctClass objectWithName: _key inContainer: self]; } -- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag +- (id) lookupName: (NSString *) _key + inContext: (id) _ctx + acquire: (BOOL) _flag { id obj; NSString *userLogin; @@ -145,8 +102,8 @@ static NSString *AgenorShareLoginMarker = @".-."; return obj; if (![self isInHomeFolderBranchOfLoggedInAccount: userLogin]) { - [self warnWithFormat:@"User %@ tried to access mail hierarchy of %@", - userLogin, [[self container] nameInContainer]]; + [self warnWithFormat:@ "User %@ tried to access mail hierarchy of %@", + userLogin, [container nameInContainer]]; return [NSException exceptionWithHTTPStatus:403 /* Forbidden */ reason:@"Tried to access the mail of another user"]; diff --git a/SoObjects/Mailer/SOGoMailBaseObject.h b/SoObjects/Mailer/SOGoMailBaseObject.h index d2231786..ec2d8baa 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.h +++ b/SoObjects/Mailer/SOGoMailBaseObject.h @@ -48,30 +48,32 @@ NGImap4Connection *imap4; } -- (id)initWithImap4URL:(NSURL *)_url inContainer:(id)_container; +- (id) initWithImap4URL: (NSURL *) _url + inContainer: (id) _container; /* hierarchy */ -- (SOGoMailAccount *)mailAccountFolder; -- (SOGoMailAccounts *)mailAccountsFolder; +- (SOGoMailAccount *) mailAccountFolder; +- (SOGoMailAccounts *) mailAccountsFolder; +- (BOOL) isInDraftsFolder; /* IMAP4 */ -- (NGImap4Connection *)imap4Connection; -- (NGImap4ConnectionManager *)mailManager; +- (NGImap4Connection *) imap4Connection; +- (NGImap4ConnectionManager *) mailManager; - (NSString *) relativeImap4Name; - (NSMutableString *) imap4URLString; -- (NSURL *)imap4URL; -- (NSString *)imap4Login; -- (NSString *)imap4Password; +- (NSURL *) imap4URL; +- (NSString *) imap4Login; +- (NSString *) imap4Password; -- (void)flushMailCaches; +- (void) flushMailCaches; /* IMAP4 names */ -- (BOOL)isBodyPartKey:(NSString *)_key inContext:(id)_ctx; +- (BOOL) isBodyPartKey: (NSString *) _key inContext: (id) _ctx; @end diff --git a/SoObjects/Mailer/SOGoMailBaseObject.m b/SoObjects/Mailer/SOGoMailBaseObject.m index 91d3c8d8..11b74259 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SoObjects/Mailer/SOGoMailBaseObject.m @@ -95,6 +95,11 @@ static BOOL debugOn = YES; return o; } +- (BOOL) isInDraftsFolder +{ + return [container isInDraftsFolder]; +} + /* IMAP4 */ - (NGImap4ConnectionManager *) mailManager @@ -146,7 +151,7 @@ static BOOL debugOn = YES; urlString = [container imap4URLString]; imap4Name = [[self relativeImap4Name] stringByEscapingURL]; - [urlString appendFormat: @"%@/", imap4Name]; + [urlString appendFormat: @"%@", imap4Name]; return urlString; } diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index 491734c7..89d8c68d 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -40,6 +40,7 @@ { NSMutableArray *filenames; NSString *folderType; + NSDictionary *mailboxACL; } /* messages */ diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 3232947f..7087a171 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -85,6 +85,7 @@ static BOOL useAltNamespace = NO; inContainer: newContainer])) { [self _adjustOwner]; + mailboxACL = nil; } return self; @@ -94,6 +95,7 @@ static BOOL useAltNamespace = NO; { [filenames release]; [folderType release]; + [mailboxACL release]; [super dealloc]; } @@ -104,6 +106,17 @@ static BOOL useAltNamespace = NO; return [nameInContainer substringFromIndex: 6]; } + +- (NSMutableString *) imap4URLString +{ + NSMutableString *urlString; + + urlString = [super imap4URLString]; + [urlString appendString: @"/"]; + + return urlString; +} + /* listing the available folders */ - (NSArray *) toManyRelationshipKeys @@ -179,22 +192,22 @@ static BOOL useAltNamespace = NO; sortOrdering: (id) _so { /* seems to return an NSArray of NSNumber's */ - return [[self imap4Connection] fetchUIDsInURL:[self imap4URL] - qualifier:_q sortOrdering:_so]; + return [[self imap4Connection] fetchUIDsInURL: [self imap4URL] + qualifier: _q sortOrdering: _so]; } - (NSArray *) fetchUIDs: (NSArray *) _uids parts: (NSArray *) _parts { - return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL] - parts:_parts]; + return [[self imap4Connection] fetchUIDs: _uids inURL: [self imap4URL] + parts: _parts]; } - (NSException *) postData: (NSData *) _data flags: (id) _flags { - return [[self imap4Connection] postData:_data flags:_flags - toFolderURL:[self imap4URL]]; + return [[self imap4Connection] postData: _data flags: _flags + toFolderURL: [self imap4URL]]; } - (NSException *) expunge @@ -460,14 +473,22 @@ static BOOL useAltNamespace = NO; return imapAcls; } +- (void) _readMailboxACL +{ + mailboxACL + = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]]; + [mailboxACL retain]; +} + - (NSArray *) aclUsers { NSArray *users; - NSDictionary *imapAcls; - imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]]; - if ([imapAcls isKindOfClass: [NSDictionary class]]) - users = [imapAcls allKeys]; + if (!mailboxACL) + [self _readMailboxACL]; + + if ([mailboxACL isKindOfClass: [NSDictionary class]]) + users = [mailboxACL allKeys]; else users = nil; @@ -506,17 +527,19 @@ static BOOL useAltNamespace = NO; - (NSArray *) aclsForUser: (NSString *) uid { - NSDictionary *imapAcls; NSMutableArray *acls; NSString *userAcls; acls = [self _sharesACLs]; - imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]]; - if ([imapAcls isKindOfClass: [NSDictionary class]]) + + if (!mailboxACL) + [self _readMailboxACL]; + + if ([mailboxACL isKindOfClass: [NSDictionary class]]) { - userAcls = [imapAcls objectForKey: uid]; + userAcls = [mailboxACL objectForKey: uid]; if (!([userAcls length] || [uid isEqualToString: defaultUserID])) - userAcls = [imapAcls objectForKey: defaultUserID]; + userAcls = [mailboxACL objectForKey: defaultUserID]; if ([userAcls length]) [acls addObjectsFromArray: [self _imapAclsToSOGoAcls: userAcls]]; } @@ -541,6 +564,8 @@ static BOOL useAltNamespace = NO; [client deleteACL: folderName uid: currentUID]; currentUID = [uids nextObject]; } + [mailboxACL release]; + mailboxACL = nil; } - (void) setRoles: (NSArray *) roles @@ -551,6 +576,9 @@ static BOOL useAltNamespace = NO; acls = [self _sogoAclsToImapAcls: roles]; folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]]; [[imap4 client] setACL: folderName rights: acls uid: uid]; + + [mailboxACL release]; + mailboxACL = nil; } - (NSString *) defaultUserID @@ -592,14 +620,20 @@ static BOOL useAltNamespace = NO; { SOGoUser *user; NSString *otherUsersPath, *url; + SOGoMailAccount *thisAccount; + NSDictionary *mailAccount; user = [SOGoUser userWithLogin: uid roles: nil]; otherUsersPath = [self otherUsersPathToFolder]; if (otherUsersPath) - url = [NSString stringWithFormat: @"%@/%@%@", - [self soURLToBaseContainerForUser: uid], - [user primaryIMAP4AccountString], - otherUsersPath]; + { + thisAccount = [self mailAccountFolder]; + mailAccount = [[user mailAccounts] objectAtIndex: 0]; + url = [NSString stringWithFormat: @"%@/%@%@", + [self soURLToBaseContainerForUser: uid], + [mailAccount objectForKey: @"name"], + otherUsersPath]; + } else url = nil; diff --git a/SoObjects/Mailer/SOGoMailIdentity.h b/SoObjects/Mailer/SOGoMailIdentity.h deleted file mode 100644 index c340fdee..00000000 --- a/SoObjects/Mailer/SOGoMailIdentity.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - Copyright (C) 2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#ifndef __Mailer_SOGoMailIdentity_H__ -#define __Mailer_SOGoMailIdentity_H__ - -#import - -/* - SOGoMailIdentity - - A user identity bound to an account. - - Note: currently this is not a SoObject. This might change later on. - - - In Thunderbird you have a set of accounts which in turn have a set of - identities. There is one default identity. - - The identities then have: - - settings - - a name - - a from-email - - a reply-to - - an organization - - a signature - - a vcard (to be attached) - - folder settings - - Sent-Folder and bcc - - Drafts + Templates - - composition - - whether to use HTML - - whether to quote the source message (reply below, above the quote or - select the quote) -*/ - -@class NSString; - -@interface SOGoMailIdentity : NSObject -{ - NSString *name; - NSString *email; - NSString *replyTo; - NSString *organization; - NSString *signature; - NSString *vCard; - NSString *sentFolderName; - NSString *sentBCC; - NSString *draftsFolderName; - NSString *templatesFolderName; - struct { - int composeHTML:1; - int reserved:31; - } idFlags; -} - -/* accessors */ - -- (void)setName:(NSString *)_value; -- (NSString *)name; - -- (void)setEmail:(NSString *)_value; -- (NSString *)email; - -- (void)setReplyTo:(NSString *)_value; -- (NSString *)replyTo; - -- (void)setOrganization:(NSString *)_value; -- (NSString *)organization; - -- (void)setSignature:(NSString *)_value; -- (NSString *)signature; -- (BOOL)hasSignature; - -- (void)setVCard:(NSString *)_value; -- (NSString *)vCard; -- (BOOL)hasVCard; - -- (void)setSentFolderName:(NSString *)_value; -- (NSString *)sentFolderName; - -- (void)setSentBCC:(NSString *)_value; -- (NSString *)sentBCC; - -- (void)setDraftsFolderName:(NSString *)_value; -- (NSString *)draftsFolderName; - -- (void)setTemplatesFolderName:(NSString *)_value; -- (NSString *)templatesFolderName; - -@end - -#endif /* __Mailer_SOGoMailIdentity_H__ */ diff --git a/SoObjects/Mailer/SOGoMailIdentity.m b/SoObjects/Mailer/SOGoMailIdentity.m deleted file mode 100644 index 424d0891..00000000 --- a/SoObjects/Mailer/SOGoMailIdentity.m +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright (C) 2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import - -#import - -#import "SOGoMailIdentity.h" - -@implementation SOGoMailIdentity - -- (void)dealloc { - [name release]; - [email release]; - [replyTo release]; - [organization release]; - [signature release]; - [vCard release]; - [sentFolderName release]; - [sentBCC release]; - [draftsFolderName release]; - [templatesFolderName release]; - [super dealloc]; -} - -/* accessors */ - -- (void)setName:(NSString *)_value { - ASSIGNCOPY(name, _value); -} -- (NSString *)name { - return name; -} - -- (void)setEmail:(NSString *)_value { - ASSIGNCOPY(email, _value); -} -- (NSString *)email { - return email; -} - -- (void)setReplyTo:(NSString *)_value { - ASSIGNCOPY(replyTo, _value); -} -- (NSString *)replyTo { - return replyTo; -} - -- (void)setOrganization:(NSString *)_value { - ASSIGNCOPY(organization, _value); -} -- (NSString *)organization { - return organization; -} - -- (void)setSignature:(NSString *)_value { - ASSIGNCOPY(signature, _value); -} -- (NSString *)signature { - return signature; -} -- (BOOL)hasSignature { - return [[self signature] isNotEmpty]; -} - -- (void)setVCard:(NSString *)_value { - ASSIGNCOPY(vCard, _value); -} -- (NSString *)vCard { - return vCard; -} -- (BOOL)hasVCard { - return [[self vCard] isNotEmpty]; -} - -- (void)setSentFolderName:(NSString *)_value { - ASSIGNCOPY(sentFolderName, _value); -} -- (NSString *)sentFolderName { - return sentFolderName; -} - -- (void)setSentBCC:(NSString *)_value { - ASSIGNCOPY(sentBCC, _value); -} -- (NSString *)sentBCC { - return sentBCC; -} - -- (void)setDraftsFolderName:(NSString *)_value { - ASSIGNCOPY(draftsFolderName, _value); -} -- (NSString *)draftsFolderName { - return draftsFolderName; -} - -- (void)setTemplatesFolderName:(NSString *)_value { - ASSIGNCOPY(templatesFolderName, _value); -} -- (NSString *)templatesFolderName { - return templatesFolderName; -} - -/* description */ - -- (NSString *)description { - NSMutableString *ms; - - ms = [NSMutableString stringWithCapacity:128]; - [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; - - if (name != nil) [ms appendFormat:@" name='%@'", name]; - if (email != nil) [ms appendFormat:@" email='%@'", email]; - - if (sentFolderName != nil) - [ms appendFormat:@" sent='%@'", sentFolderName]; - - if ([sentBCC length] > 0) [ms appendString:@" sent-bcc"]; - if ([vCard length] > 0) [ms appendString:@" vcard"]; - - [ms appendString:@">"]; - return ms; -} - -@end /* SOGoMailIdentity */ diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.h b/SoObjects/Mailer/SOGoMailObject+Draft.h new file mode 100644 index 00000000..5da1cdae --- /dev/null +++ b/SoObjects/Mailer/SOGoMailObject+Draft.h @@ -0,0 +1,38 @@ +/* SOGoMailObject+Draft.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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. + */ + +#ifndef SOGOMAILOBJECT_DRAFT_H +#define SOGOMAILOBJECT_DRAFT_H + +#import "SOGoMailObject.h" + +@interface SOGoMailObject (SOGoDraftObjectExtensions) + +- (NSString *) subjectForReply; +- (NSString *) contentForReply; + +- (NSString *) subjectForForward; +- (NSString *) filenameForForward; + +@end + +#endif /* SOGOMAILOBJECT_DRAFT_H */ diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m new file mode 100644 index 00000000..a5d8da06 --- /dev/null +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -0,0 +1,205 @@ +/* SOGoMailObject+Draft.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 +#import + +#import "SOGoMailObject+Draft.h" + +#define maxFilenameLength 64 + +@implementation SOGoMailObject (SOGoDraftObjectExtensions) + +- (NSString *) subjectForReply +{ + static NSString *replyPrefixes[] = { + @"Re:", // regular + @"RE:", // Outlook v11 (English?) + @"AW:", // German Outlook v11 + @"Re[", // numbered Re, eg "Re[2]:" + nil + }; + BOOL hasPrefix; + unsigned int i; + NSString *subject, *newSubject; + + hasPrefix = NO; + + subject = [[self envelope] subject]; + i = 0; + while (!hasPrefix && replyPrefixes[i]) + if ([subject hasPrefix: replyPrefixes[i]]) + hasPrefix = YES; + else + i++; + + if (hasPrefix) + newSubject = subject; + else + newSubject = [NSString stringWithFormat: @"Re: %@", subject]; + + return newSubject; +} + +- (NSString *) contentForReplyOnParts: (NSDictionary *) _prts + keys: (NSArray *) _k +{ + static NSString *textPartSeparator = @"\n---\n"; + NSMutableString *ms; + unsigned int count, max; + NSString *k, *v; + + ms = [NSMutableString stringWithCapacity: 16000]; + + max = [_k count]; + for (count = 0; count < max; count++) + { + k = [_k objectAtIndex: count]; + + // TODO: this is DUP code to SOGoMailObject + if ([k isEqualToString: @"body[text]"]) + k = @""; + else if ([k hasPrefix: @"body["]) { + k = [k substringFromIndex: 5]; + if ([k length] > 0) + k = [k substringToIndex: ([k length] - 1)]; + } + + v = [_prts objectForKey: k]; + if ([v isKindOfClass: [NSString class]] + && [v length] > 0) + { + if (count > 0) + [ms appendString: textPartSeparator]; + [ms appendString: [v stringByApplyingMailQuoting]]; + } + else + [self logWithFormat:@"Note: cannot show part %@", k]; + } + + return ms; +} + +#warning this method should be fixed to return the first available text/plain \ + part, and otherwise the first text/html part converted to text +- (NSString *) contentForReply +{ + NSArray *keys; + NSDictionary *parts; + NSMutableArray *topLevelKeys = nil; + unsigned int count, max; + NSRange r; + NSString *contentForReply; + +// SOGoMailObject *co; + +// co = self; +// keys = [co plainTextContentFetchKeys]; +// infos = [co fetchCoreInfos]; +// partInfos = [infos objectForKey: keys]; +// NSLog (@"infos: '%@'", infos); + + keys = [self plainTextContentFetchKeys]; + max = [keys count]; + if (max > 0) + { + if (max > 1) + { + /* filter keys, only include top-level, or if none, the first */ + for (count = 0; count < max; count++) + { + r = [[keys objectAtIndex: count] rangeOfString: @"."]; + if (!r.length) + { + if (!topLevelKeys) + topLevelKeys = [NSMutableArray arrayWithCapacity: 4]; + [topLevelKeys addObject: [keys objectAtIndex: count]]; + } + } + + if ([topLevelKeys count] > 0) + /* use top-level keys if we have some */ + keys = topLevelKeys; + else + /* just take the first part */ + keys = [NSArray arrayWithObject: [keys objectAtIndex: 0]]; + } + + parts = [self fetchPlainTextStrings: keys]; + contentForReply = [self contentForReplyOnParts: parts + keys: keys]; + } + else + contentForReply = nil; + + return contentForReply; +} + +- (NSString *) filenameForForward +{ + NSString *subject; + NSMutableString *newSubject; + static NSString *sescape[] = { + @"/", @"..", @"~", @"\"", @"'", @" ", @".", nil + }; + unsigned int count, length; + + subject = [[self envelope] subject]; + length = [subject length]; + if (!length) + { + subject = @"forward"; + length = [subject length]; + } + + if (length > maxFilenameLength) + length = maxFilenameLength; + newSubject = [NSMutableString + stringWithString: [subject substringToIndex: length]]; + count = 0; + while (sescape[count]) + { + [newSubject replaceString: sescape[count] + withString: @"_"]; + count++; + } + [newSubject appendString: @".eml"]; + + return newSubject; +} + +- (NSString *) subjectForForward +{ + NSString *subject, *newSubject; + + subject = [[self envelope] subject]; + if ([subject length] > 0) + newSubject = [NSString stringWithFormat: @"[Fwd: %@]", subject]; + else + newSubject = subject; + + return newSubject; +} + +@end diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index 72dae388..2dd21d42 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -86,7 +86,7 @@ /* deletion */ - (BOOL)isDeletionAllowed; -- (NSException *)trashInContext:(id)_ctx; +- (NSException *) trashInContext:(id)_ctx; - (NSException *) moveToFolderNamed: (NSString *) folderName inContext: (id)_ctx; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 5eb14069..9465de04 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -100,7 +100,8 @@ static BOOL debugSoParts = NO; NSLog(@"Note(SOGoMailObject): etag caching disabled!"); } -- (void)dealloc { +- (void) dealloc +{ [headers release]; [headerPart release]; [coreInfos release]; @@ -114,18 +115,6 @@ static BOOL debugSoParts = NO; return [nameInContainer stringByDeletingPathExtension]; } -- (NSMutableString *) imap4URLString -{ - NSMutableString *urlString; - NSString *imap4Name; - - urlString = [container imap4URLString]; - imap4Name = [[self relativeImap4Name] stringByEscapingURL]; - [urlString appendFormat: @"%@", imap4Name]; - - return urlString; -} - /* hierarchy */ - (SOGoMailObject *)mailObject { @@ -134,7 +123,8 @@ static BOOL debugSoParts = NO; /* part hierarchy */ -- (NSString *)keyExtensionForPart:(id)_partInfo { +- (NSString *) keyExtensionForPart: (id) _partInfo +{ NSString *mt, *st; if (_partInfo == nil) @@ -200,7 +190,8 @@ static BOOL debugSoParts = NO; /* message */ -- (id)fetchParts:(NSArray *)_parts { +- (id) fetchParts: (NSArray *) _parts +{ // TODO: explain what it does /* Called by -fetchPlainTextParts: @@ -227,7 +218,8 @@ static BOOL debugSoParts = NO; return [msgs count] > 0 ? YES : NO; } -- (id)fetchCoreInfos { +- (id) fetchCoreInfos +{ id msgs; if (coreInfos != nil) @@ -244,19 +236,23 @@ static BOOL debugSoParts = NO; return nil; coreInfos = [[msgs objectAtIndex:0] retain]; + return coreInfos; } -- (id)bodyStructure { +- (id) bodyStructure +{ id body; body = [[self fetchCoreInfos] valueForKey: @"body"]; if (debugBodyStructure) [self logWithFormat: @"BODY: %@", body]; + return body; } -- (NGImap4Envelope *)envelope { +- (NGImap4Envelope *) envelope +{ return [[self fetchCoreInfos] valueForKey: @"envelope"]; } @@ -458,7 +454,9 @@ static BOOL debugSoParts = NO; /* bulk fetching of plain/text content */ -- (BOOL)shouldFetchPartOfType:(NSString *)_type subtype:(NSString *)_subtype { +- (BOOL) shouldFetchPartOfType: (NSString *) _type + subtype: (NSString *) _subtype +{ /* This method decides which parts are 'prefetched' for display. Those are usually text parts (the set is currently hardcoded in this method ...). @@ -475,9 +473,10 @@ static BOOL debugSoParts = NO; || [_subtype hasPrefix: @"x-vnd.kolab."]))); } -- (void)addRequiredKeysOfStructure:(id)_info path:(NSString *)_p - toArray:(NSMutableArray *)_keys - recurse:(BOOL)_recurse +- (void) addRequiredKeysOfStructure: (id) _info + path: (NSString *) _p + toArray: (NSMutableArray *) _keys + recurse: (BOOL) _recurse { /* This is used to collect the set of IMAP4 fetch-keys required to fetch @@ -486,67 +485,70 @@ static BOOL debugSoParts = NO; The method calls itself recursively to walk the body structure. */ - NSArray *parts; + NSArray *parts; unsigned i, count; BOOL fetchPart; + NSString *k; id body; - - /* Note: if the part itself doesn't qualify, we still check subparts */ - fetchPart = [self shouldFetchPartOfType:[_info valueForKey: @"type"] - subtype:[_info valueForKey: @"subtype"]]; - if (fetchPart) { - NSString *k; + NSString *sp; + id childInfo; - if ([_p length] > 0) { - k = [[@"body[" stringByAppendingString:_p] stringByAppendingString: @"]"]; - } - else { - /* - for some reason we need to add ".TEXT" for plain text stuff on root - entities? - TODO: check with HTML - */ - k = @"body[text]"; + + /* Note: if the part itself doesn't qualify, we still check subparts */ + fetchPart = [self shouldFetchPartOfType: [_info valueForKey: @"type"] + subtype: [_info valueForKey: @"subtype"]]; + if (fetchPart) + { + if ([_p length] > 0) + k = [NSString stringWithFormat: @"body[%@]", _p]; + else + { + /* + for some reason we need to add ".TEXT" for plain text stuff on root + entities? + TODO: check with HTML + */ + k = @"body[text]"; + } + [_keys addObject: k]; } - [_keys addObject:k]; - } - - if (!_recurse) - return; - - /* recurse */ - - parts = [(NSDictionary *)_info objectForKey: @"parts"]; - for (i = 0, count = [parts count]; i < count; i++) { - NSString *sp; - id childInfo; - - sp = ([_p length] > 0) - ? [_p stringByAppendingFormat: @".%d", i + 1] - : [NSString stringWithFormat: @"%d", i + 1]; - - childInfo = [parts objectAtIndex:i]; - - [self addRequiredKeysOfStructure:childInfo path:sp toArray:_keys - recurse:YES]; - } - - /* check body */ - - if ((body = [(NSDictionary *)_info objectForKey: @"body"]) != nil) { - NSString *sp; - sp = [[body valueForKey: @"type"] lowercaseString]; - if ([sp isEqualToString: @"multipart"]) - sp = _p; - else - sp = [_p length] > 0 ? [_p stringByAppendingString: @".1"] : @"1"; - [self addRequiredKeysOfStructure:body path:sp toArray:_keys - recurse:YES]; - } + if (_recurse) + { + /* recurse */ + parts = [(NSDictionary *)_info objectForKey: @"parts"]; + count = [parts count]; + for (i = 0; i < count; i++) + { + sp = (([_p length] > 0) + ? [_p stringByAppendingFormat: @".%d", i + 1] + : [NSString stringWithFormat: @"%d", i + 1]); + + childInfo = [parts objectAtIndex: i]; + + [self addRequiredKeysOfStructure: childInfo + path: sp toArray: _keys + recurse: YES]; + } + + /* check body */ + body = [(NSDictionary *)_info objectForKey: @"body"]; + if (body) + { + sp = [[body valueForKey: @"type"] lowercaseString]; + if ([sp isEqualToString: @"multipart"]) + sp = _p; + else + sp = [_p length] > 0 ? [_p stringByAppendingString: @".1"] : @"1"; + [self addRequiredKeysOfStructure: body + path: sp toArray: _keys + recurse: YES]; + } + } } -- (NSArray *)plainTextContentFetchKeys { +- (NSArray *) plainTextContentFetchKeys +{ /* The name is not 100% correct. The method returns all body structure fetch keys which are marked by the -shouldFetchPartOfType:subtype: method. @@ -554,8 +556,8 @@ static BOOL debugSoParts = NO; NSMutableArray *ma; ma = [NSMutableArray arrayWithCapacity:4]; - [self addRequiredKeysOfStructure:[[self clientObject] bodyStructure] - path: @"" toArray:ma recurse:YES]; + [self addRequiredKeysOfStructure: [[self clientObject] bodyStructure] + path: @"" toArray: ma recurse: YES]; return ma; } @@ -853,7 +855,7 @@ static BOOL debugSoParts = NO; /* b) mark deleted */ - error = [[self imap4Connection] markURLDeleted:[self imap4URL]]; + error = [[self imap4Connection] markURLDeleted: [self imap4URL]]; if (error != nil) return error; /* c) expunge */ diff --git a/SoObjects/Mailer/SOGoUser+Mail.h b/SoObjects/Mailer/SOGoUser+Mail.h deleted file mode 100644 index 432de0f1..00000000 --- a/SoObjects/Mailer/SOGoUser+Mail.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (C) 2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#ifndef __Mailer_SOGoUser_Mail_H__ -#define __Mailer_SOGoUser_Mail_H__ - -#include - -/* - SOGoUser(Mail) - - TODO: document - - This category adds mail related stuff to the SOGo user class. -*/ - -#include - -@class NSArray; -@class SOGoMailIdentity; - -@interface SOGoUser(Mail) - -- (SOGoMailIdentity *)primaryMailIdentity; -- (NSArray *)fetchAllMailIdentitiesWithOnlyEmitterAccess:(BOOL)_onlyGC; -- (SOGoMailIdentity *)primaryMailIdentityForAccount:(NSString *)_account; - -@end - -#endif /* __Mailer_SOGoUser_Mail_H__ */ diff --git a/SoObjects/Mailer/SOGoUser+Mail.m b/SoObjects/Mailer/SOGoUser+Mail.m deleted file mode 100644 index 2cde8398..00000000 --- a/SoObjects/Mailer/SOGoUser+Mail.m +++ /dev/null @@ -1,146 +0,0 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import -#import -#import - -#import -#import - -#import "SOGoMailIdentity.h" - -#import "SOGoUser+Mail.h" - -@implementation SOGoUser(Mail) - -- (NSString *)agenorSentFolderName { - /* Note: specialty: the Sent folder is called the same in all accounts */ - static NSString *s = nil; - if (s == nil) { - NSUserDefaults *ud; - - ud = [NSUserDefaults standardUserDefaults]; - s = [[ud stringForKey:@"SOGoSentFolderName"] copy]; - if (![s isNotEmpty]) s = @"Sent"; - [self logWithFormat:@"Note: using SOGoSentFolderName: '%@'", s]; - } - return s; -} - -- (NSString *)agenorSentFolderForAccount:(NSString *)_account { - // TODO: support different locations for shares! - NSString *p; - - if (![_account isNotEmpty]) - return nil; - - // if ([_account rangeOfString:@".-."].length == 0) - // TODO: check whether we need special handling for shares! - p = [_account stringByAppendingString:@"/"]; - p = [p stringByAppendingString:[self agenorSentFolderName]]; - return p; -} - -- (SOGoMailIdentity *)primaryMailIdentity { - SOGoMailIdentity *identity; - NSString *account; - - account = [self valueForKey:@"primaryIMAP4AccountString"]; - - identity = [[[SOGoMailIdentity alloc] init] autorelease]; - [identity setName: [self cn]]; - [identity setEmail: [self primaryEmail]]; - [identity setSentFolderName:[self agenorSentFolderForAccount:account]]; - return identity; -} - -- (SOGoMailIdentity *)mailIdentityForAccount:(NSString *)_account - emitter:(NSString *)_em -{ - SOGoMailIdentity *identity; - - identity = [[[SOGoMailIdentity alloc] init] autorelease]; - [identity setName:[self cn]]; // TODO: should we use something else? - if ([_em isNotEmpty]) [identity setEmail:_em]; - [identity setSentFolderName:[self agenorSentFolderForAccount:_account]]; - return identity; -} - -- (NSArray *)fetchAllMailIdentitiesWithOnlyEmitterAccess:(BOOL)_onlyGC { - NSMutableArray *identities; - NSEnumerator *accounts; - NSDictionary *shares; - NSString *account; - id identity; - - identity = [self primaryMailIdentity]; - shares = [self valueForKey:@"additionalIMAP4AccountsAndEMails"]; - if ([shares count] == 0) - return [NSArray arrayWithObject: identity]; - - identities = [NSMutableArray arrayWithCapacity:[shares count] + 1]; - if (identity != nil) [identities addObject:identity]; - - accounts = [shares keyEnumerator]; - while ((account = [accounts nextObject]) != nil) { - NSString *emitter; - - emitter = [shares objectForKey:account]; - if (_onlyGC && ![emitter isNotNull]) continue; - - identity = [self mailIdentityForAccount:account emitter:emitter]; - if (identity != nil) - [identities addObject:identity]; - } - - return identities; -} - -- (SOGoMailIdentity *)primaryMailIdentityForAccount:(NSString *)_account { - NSEnumerator *accounts; - NSDictionary *shares; - NSString *account; - id identity; - - identity = [self primaryMailIdentity]; - shares = [self valueForKey:@"additionalIMAP4AccountsAndEMails"]; - if ([shares count] == 0) - return identity; - - /* scan shares for ID */ - accounts = [shares keyEnumerator]; - while ((account = [accounts nextObject]) != nil) { - NSString *emitter; - - if (![account isEqualToString:_account]) - continue; - - emitter = [shares objectForKey:account]; - identity = [self mailIdentityForAccount:_account emitter:emitter]; - if ([identity isNotNull]) - return identity; - } - - return identity; -} - -@end /* SOGoUser(Mail) */ diff --git a/SoObjects/SOGo/NSArray+Utilities.h b/SoObjects/SOGo/NSArray+Utilities.h index b330cf85..181f636c 100644 --- a/SoObjects/SOGo/NSArray+Utilities.h +++ b/SoObjects/SOGo/NSArray+Utilities.h @@ -32,6 +32,9 @@ - (NSString *) jsonRepresentation; - (NSArray *) stringsWithFormat: (NSString *) format; +- (NSArray *) keysWithFormat: (NSString *) format; +- (NSArray *) objectsForKey: (NSString *) key; +- (NSArray *) flattenedArray; - (BOOL) containsCaseInsensitiveString: (NSString *) match; diff --git a/SoObjects/SOGo/NSArray+Utilities.m b/SoObjects/SOGo/NSArray+Utilities.m index 77dabdcb..4845b33d 100644 --- a/SoObjects/SOGo/NSArray+Utilities.m +++ b/SoObjects/SOGo/NSArray+Utilities.m @@ -33,6 +33,7 @@ id currentObject; formattedStrings = [NSMutableArray arrayWithCapacity: [self count]]; + objects = [self objectEnumerator]; currentObject = [objects nextObject]; while (currentObject) @@ -45,6 +46,61 @@ return formattedStrings; } +- (NSArray *) keysWithFormat: (NSString *) format +{ + NSMutableArray *formattedStrings; + NSEnumerator *objects; + id currentObject; + + formattedStrings = [NSMutableArray arrayWithCapacity: [self count]]; + + objects = [self objectEnumerator]; + currentObject = [objects nextObject]; + while (currentObject) + { + [formattedStrings addObject: [currentObject keysWithFormat: format]]; + currentObject = [objects nextObject]; + } + + return formattedStrings; +} + +- (NSArray *) objectsForKey: (NSString *) key +{ + NSMutableArray *objectsForKey; + unsigned int count, max; + id value; + + max = [self count]; + objectsForKey = [NSMutableArray arrayWithCapacity: max]; + + for (count = 0; count < max; count++) + { + value = [[self objectAtIndex: count] objectForKey: key]; + [objectsForKey addObject: value]; + } + + return objectsForKey; +} + +- (NSArray *) flattenedArray +{ + NSMutableArray *flattenedArray; + NSEnumerator *objects; + id currentObject; + + flattenedArray = [NSMutableArray array]; + objects = [self objectEnumerator]; + currentObject = [objects nextObject]; + while (currentObject) + { + [flattenedArray addObjectsFromArray: currentObject]; + currentObject = [objects nextObject]; + } + + return flattenedArray; +} + - (void) makeObjectsPerform: (SEL) selector withObject: (id) object1 withObject: (id) object2 diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index df40a342..bc054c81 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -30,6 +30,7 @@ @interface NSDictionary (SOGoDictionaryUtilities) - (NSString *) jsonRepresentation; +- (NSString *) keysWithFormat: (NSString *) keyFormat; @end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index 5b21ce2c..6caaeaa6 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -23,6 +23,7 @@ #import #import +#import "NSArray+Utilities.h" #import "NSObject+Utilities.h" #import "NSDictionary+Utilities.h" @@ -52,4 +53,27 @@ return representation; } +- (NSString *) keysWithFormat: (NSString *) keyFormat +{ + NSArray *keys, *allKeys; + unsigned int count, max; + NSMutableString *keysWithFormat; + id value; + + keysWithFormat = [NSMutableString stringWithString: keyFormat]; + + allKeys = [self allKeys]; + keys = [allKeys stringsWithFormat: @"%{%@}"]; + + max = [allKeys count]; + for (count = 0; count < max; count++) + { + value = [self objectForKey: [allKeys objectAtIndex: count]]; + [keysWithFormat replaceString: [keys objectAtIndex: count] + withString: [value description]]; + } + + return keysWithFormat; +} + @end diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index 5b6f5d2c..d6676c0e 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -23,8 +23,10 @@ #import #import +#import #import #import +#import #import #import @@ -134,10 +136,7 @@ static NSString *defaultUserID = @""; static GCSFolderManager *folderManager = nil; if (!folderManager) - { - folderManager = [GCSFolderManager defaultFolderManager]; - [folderManager setFolderNamePrefix: @"SOGo"]; - } + folderManager = [GCSFolderManager defaultFolderManager]; return folderManager; } diff --git a/SoObjects/SOGo/SOGoMailer.h b/SoObjects/SOGo/SOGoMailer.h index 29a4c6fc..3108e687 100644 --- a/SoObjects/SOGo/SOGoMailer.h +++ b/SoObjects/SOGo/SOGoMailer.h @@ -37,10 +37,12 @@ + (id) sharedMailer; +- (NSException *) sendMailData: (NSData *) data + toRecipients: (NSArray *) recipients + sender: (NSString *) sender; - (NSException *) sendMailAtPath: (NSString *) filename toRecipients: (NSArray *) recipients sender: (NSString *) sender; - - (NSException *) sendMimePart: (id ) part toRecipients: (NSArray *) recipients sender: (NSString *) sender; diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 7d681642..5d4bd9ad 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -58,6 +58,7 @@ extern NSString *SOGoWeekStartFirstFullWeek; NSUserDefaults *userSettings; NSTimeZone *userTimeZone; SOGoDateFormatter *dateFormatter; + NSMutableArray *mailAccounts; } + (SOGoUser *) userWithLogin: (NSString *) login @@ -67,11 +68,11 @@ extern NSString *SOGoWeekStartFirstFullWeek; /* properties */ -- (NSString *) fullEmail; +// - (NSString *) fullEmail; -- (NSString *) primaryEmail; -- (NSString *) systemEmail; -- (NSArray *) allEmails; +// - (NSString *) primaryEmail; +// - (NSString *) systemEmail; +// - (NSArray *) allEmails; - (BOOL) hasEmail: (NSString *) email; @@ -82,7 +83,7 @@ extern NSString *SOGoWeekStartFirstFullWeek; /* shares and identities */ -- (NSString *) primaryIMAP4AccountString; +// - (NSString *) primaryIMAP4AccountString; // - (NSString *) primaryIMAP4AccountString; // - (NSString *) primaryMailServer; @@ -99,6 +100,10 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (NSTimeZone *) timeZone; - (NSTimeZone *) serverTimeZone; +- (NSArray *) mailAccounts; +- (NSArray *) allIdentities; +- (NSDictionary *) primaryIdentity; + /* folders */ - (id) homeFolderInContext: (id) _ctx; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index d212d2c1..3dc5273a 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import #import @@ -189,33 +190,33 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; /* properties */ -- (NSString *) fullEmail -{ - return [[LDAPUserManager sharedUserManager] getFullEmailForUID: login]; -} +// - (NSString *) fullEmail +// { +// return [[LDAPUserManager sharedUserManager] getFullEmailForUID: login]; +// } -- (NSString *) primaryEmail -{ - if (!allEmails) - [self _fetchAllEmails]; +// - (NSString *) primaryEmail +// { +// if (!allEmails) +// [self _fetchAllEmails]; - return [allEmails objectAtIndex: 0]; -} +// return [allEmails objectAtIndex: 0]; +// } -- (NSString *) systemEmail +- (NSArray *) allEmails { if (!allEmails) [self _fetchAllEmails]; - return [allEmails lastObject]; + return allEmails; } -- (NSArray *) allEmails +- (NSString *) systemEmail { if (!allEmails) [self _fetchAllEmails]; - return allEmails; + return [allEmails lastObject]; } - (BOOL) hasEmail: (NSString *) email @@ -234,11 +235,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return cn; } -- (NSString *) primaryIMAP4AccountString -{ - return [NSString stringWithFormat: @"%@@%@", login, fallbackIMAP4Server]; -} - // - (NSString *) primaryMailServer // { // return [[self userManager] getServerForUID: [self login]]; @@ -347,6 +343,115 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return serverTimeZone; } +/* mail */ +- (NSArray *) mailAccounts +{ +#warning should be implemented with the user defaults interfaces + NSMutableDictionary *mailAccount, *identity; + NSMutableArray *identities; + NSString *name, *fullName; + + if (!mailAccounts) + { + mailAccount = [NSMutableDictionary dictionary]; + name = [NSString stringWithFormat: @"%@@%@", login, fallbackIMAP4Server]; + [mailAccount setObject: login forKey: @"userName"]; + [mailAccount setObject: fallbackIMAP4Server forKey: @"serverName"]; + [mailAccount setObject: name forKey: @"name"]; + + identity = [NSMutableDictionary dictionary]; + fullName = [self cn]; + if (![fullName length]) + fullName = login; + [identity setObject: fullName forKey: @"fullName"]; + [identity setObject: [self systemEmail] forKey: @"email"]; + [identity setObject: [NSNumber numberWithBool: YES] forKey: @"isDefault"]; + + identities = [NSMutableArray array]; + [identities addObject: identity]; + [mailAccount setObject: identities forKey: @"identities"]; + + mailAccounts = [NSMutableArray new]; + [mailAccounts addObject: mailAccount]; + } + + return mailAccounts; +} + +/* +@interface SOGoMailIdentity : NSObject +{ + NSString *name; + NSString *email; + NSString *replyTo; + NSString *organization; + NSString *signature; + NSString *vCard; + NSString *sentFolderName; + NSString *sentBCC; + NSString *draftsFolderName; + NSString *templatesFolderName; + struct + { + int composeHTML:1; + int reserved:31; + } idFlags; +} + +- (void) setName: (NSString *) _value; +- (NSString *) name; + +- (void) setEmail: (NSString *) _value; +- (NSString *) email; + +- (void) setReplyTo: (NSString *) _value; +- (NSString *) replyTo; + +- (void) setOrganization: (NSString *) _value; +- (NSString *) organization; + +- (void) setSignature: (NSString *) _value; +- (NSString *) signature; +- (BOOL) hasSignature; + +- (void) setVCard: (NSString *) _value; +- (NSString *) vCard; +- (BOOL) hasVCard; + +- (void) setSentFolderName: (NSString *) _value; +- (NSString *) sentFolderName; + +- (void) setSentBCC: (NSString *) _value; +- (NSString *) sentBCC; + +- (void) setDraftsFolderName: (NSString *) _value; +- (NSString *) draftsFolderName; + +- (void) setTemplatesFolderName: (NSString *) _value; +- (NSString *) templatesFolderName; + +@end */ + +- (NSArray *) allIdentities +{ + NSArray *identities; + + [self mailAccounts]; + identities = [mailAccounts objectsForKey: @"identities"]; + + return [identities flattenedArray]; +} + +- (NSDictionary *) primaryIdentity +{ + NSDictionary *defaultAccount; + + [self mailAccounts]; + defaultAccount = [mailAccounts objectAtIndex: 0]; + + return [[defaultAccount objectForKey: @"identities"] objectAtIndex: 0]; +} + /* folders */ // TODO: those methods should check whether the traversal stack in the context diff --git a/UI/Common/GNUmakefile b/UI/Common/GNUmakefile index cd4320a2..455202c2 100644 --- a/UI/Common/GNUmakefile +++ b/UI/Common/GNUmakefile @@ -22,7 +22,9 @@ CommonUI_OBJC_FILES += \ UIxTabItem.m \ UIxUserRightsEditor.m \ \ - UIxToolbar.m + UIxToolbar.m \ + \ + WODirectAction+SOGo.m \ CommonUI_RESOURCE_FILES += \ Version \ diff --git a/UI/Common/WODirectAction+SOGo.h b/UI/Common/WODirectAction+SOGo.h new file mode 100644 index 00000000..92ec0320 --- /dev/null +++ b/UI/Common/WODirectAction+SOGo.h @@ -0,0 +1,37 @@ +/* WODirectAction+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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. + */ + +#ifndef WODIRECTACTION_SOGO_H +#define WODIRECTACTION_SOGO_H + +#import + +@class NSString; +@class WOResponse; + +@interface WODirectAction (SOGoExtension) + +- (WOResponse *) redirectToLocation: (NSString *) newLocation; + +@end + +#endif /* WODIRECTACTION_SOGO_H */ diff --git a/UI/Common/WODirectAction+SOGo.m b/UI/Common/WODirectAction+SOGo.m new file mode 100644 index 00000000..ddeea2aa --- /dev/null +++ b/UI/Common/WODirectAction+SOGo.m @@ -0,0 +1,42 @@ +/* WODirectAction+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 + +#import "WODirectAction+SOGo.h" + +@implementation WODirectAction (SOGoExtension) + +- (WOResponse *) redirectToLocation: (NSString *) newLocation +{ + WOResponse *response; + + response = [context response]; + [response setStatus: 302 /* moved */]; + [response setHeader: newLocation forKey: @"location"]; + + return response; +} + +@end diff --git a/UI/MailPartViewers/UIxMailPartICalViewer.m b/UI/MailPartViewers/UIxMailPartICalViewer.m index 600a473a..6e7a690c 100644 --- a/UI/MailPartViewers/UIxMailPartICalViewer.m +++ b/UI/MailPartViewers/UIxMailPartICalViewer.m @@ -252,8 +252,13 @@ /* organizer tracking */ -- (NSString *)loggedInUserEMail { - return [[[self context] activeUser] primaryEmail]; +- (NSString *) loggedInUserEMail +{ + NSDictionary *identity; + + identity = [[context activeUser] primaryIdentity]; + + return [identity objectForKey: @"email"]; } - (iCalEvent *)authorativeEvent { diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index f253e9c3..01ee0002 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -69,6 +69,8 @@ "Addressbook" = "Addressbook"; "Anais" = "Anais"; +"Edit Draft..." = "Edit Draft..."; + "This mail is being sent from an unsecure network!" = "This mail is being sent from an unsecure network!"; /* Popup "show" */ diff --git a/UI/MailerUI/French.lproj/Localizable.strings b/UI/MailerUI/French.lproj/Localizable.strings index 1c604684..1a48c901 100644 --- a/UI/MailerUI/French.lproj/Localizable.strings +++ b/UI/MailerUI/French.lproj/Localizable.strings @@ -69,6 +69,8 @@ "cc" = "Cc"; "bcc" = "Cci"; +"Edit Draft..." = "Modifier le brouillon..."; + "This mail is being sent from an unsecure network!" = "Ce mail est envoyé depuis un réseau non sécurisé !"; /* Popup "show" */ diff --git a/UI/MailerUI/GNUmakefile b/UI/MailerUI/GNUmakefile index 0fb3d52a..cc8411a8 100644 --- a/UI/MailerUI/GNUmakefile +++ b/UI/MailerUI/GNUmakefile @@ -27,16 +27,17 @@ MailerUI_OBJC_FILES += \ \ UIxMailAccountActions.m \ UIxMailFolderActions.m \ + UIxMailActions.m \ UIxMailEditor.m \ - UIxMailEditorAction.m \ - UIxMailReplyAction.m \ - UIxMailForwardAction.m \ UIxMailToSelection.m \ UIxMailWindowCloser.m \ \ + UIxMailUserRightsEditor.m \ +# UIxMailEditorAction.m \ +# UIxMailReplyAction.m \ +# UIxMailForwardAction.m \ # UIxFilterList.m \ # \ - UIxMailUserRightsEditor.m # UIxSieveEditor.m MailerUI_RESOURCE_FILES += \ diff --git a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar index 8df3661d..ac4ad5a8 100644 --- a/UI/MailerUI/Toolbars/SOGoMailObject.toolbar +++ b/UI/MailerUI/Toolbars/SOGoMailObject.toolbar @@ -10,8 +10,8 @@ { link = "#"; isSafe = NO; image = "tb-mail-write-flat-24x24.png"; - onclick = "return openMessageWindow(null, 'compose');"; - cssClass = "tbicon_compose"; label = "Write"; }, + onclick = "return onComposeMessage();"; + cssClass = "tbicon_compose"; label = "Write"; }, { link = "#"; target = "addressbook"; onclick = "openAddressbook(this);return false;"; image = "tb-mail-addressbook-flat-24x24.png"; diff --git a/UI/MailerUI/UIxMailAccountActions.m b/UI/MailerUI/UIxMailAccountActions.m index 6342e92f..f7cb0be5 100644 --- a/UI/MailerUI/UIxMailAccountActions.m +++ b/UI/MailerUI/UIxMailAccountActions.m @@ -25,10 +25,16 @@ #import #import +#import #import #import #import +#import +#import #import +#import + +#import "../Common/WODirectAction+SOGo.h" #import "UIxMailAccountActions.h" @@ -111,4 +117,32 @@ return response; } +/* compose */ + +- (WOResponse *) composeAction +{ + SOGoDraftsFolder *drafts; + SOGoDraftObject *newDraftMessage; + NSString *urlBase, *url; + NSString *mailTo; + + drafts = [[self clientObject] draftsFolderInContext: context]; + newDraftMessage = [drafts newDraft]; + + mailTo = [[self request] formValueForKey: @"mailto"]; + if ([mailTo length] > 0) + { + [newDraftMessage setHeaders: [NSDictionary dictionaryWithObject: mailTo + forKey: @"to"]]; + [newDraftMessage storeInfo]; + } + + urlBase = [newDraftMessage baseURLInContext: context]; + url = [urlBase composeURLWithAction: @"edit" + parameters: nil + andHash: NO]; + + return [self redirectToLocation: url]; +} + @end diff --git a/UI/MailerUI/UIxMailActions.h b/UI/MailerUI/UIxMailActions.h new file mode 100644 index 00000000..950639e9 --- /dev/null +++ b/UI/MailerUI/UIxMailActions.h @@ -0,0 +1,31 @@ +/* UIxMailActions.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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. + */ + +#ifndef UIXMAILACTIONS_H +#define UIXMAILACTIONS_H + +#import + +@interface UIxMailActions : WODirectAction +@end + +#endif /* UIXMAILACTIONS_H */ diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m new file mode 100644 index 00000000..7e177cda --- /dev/null +++ b/UI/MailerUI/UIxMailActions.m @@ -0,0 +1,107 @@ +/* UIxMailActions.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * 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 +#import +#import +#import + +#import "../Common/WODirectAction+SOGo.h" + +#import "UIxMailActions.h" + +@implementation UIxMailActions + +- (WOResponse *) editAction +{ + SOGoMailAccount *account; + SOGoMailObject *co; + SOGoDraftsFolder *folder; + SOGoDraftObject *newMail; + NSString *newLocation; + + co = [self clientObject]; + account = [co mailAccountFolder]; + folder = [account draftsFolderInContext: context]; + newMail = [folder newDraft]; + [newMail fetchMailForEditing: co]; + [newMail storeInfo]; + + newLocation = [NSString stringWithFormat: @"%@/edit", + [newMail baseURLInContext: context]]; + + return [self redirectToLocation: newLocation]; +} + +- (WOResponse *) replyToAll: (BOOL) toAll +{ + SOGoMailAccount *account; + SOGoMailObject *co; + SOGoDraftsFolder *folder; + SOGoDraftObject *newMail; + NSString *newLocation; + + co = [self clientObject]; + account = [co mailAccountFolder]; + folder = [account draftsFolderInContext: context]; + newMail = [folder newDraft]; + [newMail fetchMailForReplying: co toAll: toAll]; + + newLocation = [NSString stringWithFormat: @"%@/edit", + [newMail baseURLInContext: context]]; + + return [self redirectToLocation: newLocation]; +} + +- (WOResponse *) replyAction +{ + return [self replyToAll: NO]; +} + +- (WOResponse *) replyToAllAction +{ + return [self replyToAll: NO]; +} + +- (WOResponse *) forwardAction +{ + SOGoMailAccount *account; + SOGoMailObject *co; + SOGoDraftsFolder *folder; + SOGoDraftObject *newMail; + NSString *newLocation; + + co = [self clientObject]; + account = [co mailAccountFolder]; + folder = [account draftsFolderInContext: context]; + newMail = [folder newDraft]; + [newMail fetchMailForForwarding: co]; + + newLocation = [NSString stringWithFormat: @"%@/edit", + [newMail baseURLInContext: context]]; + + return [self redirectToLocation: newLocation]; +} + +@end diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 89d8cd2a..a221609e 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -24,9 +24,10 @@ #import #import -#import -#import #import +#import +#import +#import #import #import #import @@ -41,8 +42,9 @@ #import #import #import -#import #import +#import +#import #import /* @@ -51,9 +53,6 @@ An mail editor component which works on SOGoDraftObject's. */ -@class NSArray, NSString; -@class SOGoMailFolder; - @interface UIxMailEditor : UIxComponent { NSArray *to; @@ -61,7 +60,7 @@ NSArray *bcc; NSString *subject; NSString *text; - NSMutableArray *fromEMails; + NSArray *fromEMails; NSString *from; SOGoMailFolder *sentFolder; @@ -74,18 +73,18 @@ @implementation UIxMailEditor -static BOOL keepMailTmpFile = NO; -static BOOL showInternetMarker = NO; -static BOOL useLocationBasedSentFolder = NO; +static BOOL keepMailTmpFile = NO; +static BOOL showInternetMarker = NO; +static BOOL useLocationBasedSentFolder = NO; static NSDictionary *internetMailHeaders = nil; -static NSArray *infoKeys = nil; +static NSArray *infoKeys = nil; + (void) initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; infoKeys = [[NSArray alloc] initWithObjects: - @"subject", @"text", @"to", @"cc", @"bcc", + @"subject", @"to", @"cc", @"bcc", @"from", @"replyTo", nil]; @@ -113,104 +112,114 @@ static NSArray *infoKeys = nil; { [sentFolder release]; [fromEMails release]; - [from release]; - [text release]; + [from release]; + [text release]; [subject release]; - [to release]; - [cc release]; - [bcc release]; - - [attachmentName release]; + [to release]; + [cc release]; + [bcc release]; + [attachmentName release]; [attachmentNames release]; [super dealloc]; } /* accessors */ -- (void) setFrom: (NSString *) _value +- (void) setFrom: (NSString *) newFrom { - ASSIGNCOPY(from, _value); + ASSIGN (from, newFrom); } - (NSString *) from { - if (![from isNotEmpty]) - return [[[self context] activeUser] primaryEmail]; + NSDictionary *identity; + + if (!from) + { + identity = [[context activeUser] primaryIdentity]; + from = [identity keysWithFormat: @"%{fullName} <%{email}>"]; + } return from; } -- (void) setReplyTo: (NSString *) _ignore -{ -} +// - (void) setReplyTo: (NSString *) ignore +// { +// } -- (NSString *) replyTo -{ - /* we are here for future extensibility */ - return @""; -} +// - (NSString *) replyTo +// { +// /* we are here for future extensibility */ +// return @""; +// } -- (void) setSubject: (NSString *) _value +- (void) setSubject: (NSString *) newSubject { - ASSIGNCOPY(subject, _value); + ASSIGN (subject, newSubject); } - (NSString *) subject { - return subject ? subject : @""; + return subject; } -- (void) setText: (NSString *) _value +- (void) setText: (NSString *) newText { - ASSIGNCOPY(text, _value); + ASSIGN (text, newText); } - (NSString *) text { - return [text isNotNull] ? text : @""; + return text; } -- (void) setTo: (NSArray *)_value +- (void) setTo: (NSArray *) newTo { - ASSIGNCOPY(to, _value); + if ([newTo isKindOfClass: [NSNull class]]) + newTo = nil; + + ASSIGN (to, newTo); } - (NSArray *) to { - return [to isNotNull] ? to : [NSArray array]; + return to; } -- (void) setCc: (NSArray *) _value +- (void) setCc: (NSArray *) newCc { - ASSIGNCOPY(cc, _value); + if ([newCc isKindOfClass: [NSNull class]]) + newCc = nil; + + ASSIGN (cc, newCc); } - (NSArray *) cc { - return [cc isNotNull] ? cc : [NSArray array]; + return cc; } -- (void) setBcc: (NSArray *) _value +- (void) setBcc: (NSArray *) newBcc { - ASSIGNCOPY(bcc, _value); + if ([newBcc isKindOfClass: [NSNull class]]) + newBcc = nil; + + ASSIGN (bcc, newBcc); } - (NSArray *) bcc { - return [bcc isNotNull] ? bcc : [NSArray array]; + return bcc; } - (BOOL) hasOneOrMoreRecipients { - if ([[self to] count] > 0) return YES; - if ([[self cc] count] > 0) return YES; - if ([[self bcc] count] > 0) return YES; - return NO; + return (([to count] + [cc count] + [bcc count]) > 0); } -- (void) setAttachmentName: (NSString *) _attachmentName +- (void) setAttachmentName: (NSString *) newAttachmentName { - ASSIGN(attachmentName, _attachmentName); + ASSIGN (attachmentName, newAttachmentName); } - (NSString *) attachmentName @@ -222,223 +231,41 @@ static NSArray *infoKeys = nil; - (NSArray *) fromEMails { - NSEnumerator *emails; - SOGoUser *activeUser; - NSString *cn, *fullMail, *email; - + NSArray *allIdentities; + if (!fromEMails) { - fromEMails = [NSMutableArray new]; - activeUser = [context activeUser]; - cn = [activeUser cn]; - if ([cn length] == 0) - cn = nil; - emails = [[activeUser allEmails] objectEnumerator]; - email = [emails nextObject]; - while (email) - { - if (cn) - fullMail = [NSString stringWithFormat: @"%@ <%@>", cn, email]; - else - fullMail = email; - [fromEMails addObject: fullMail]; - email = [emails nextObject]; - } + allIdentities = [[context activeUser] allIdentities]; + fromEMails = [allIdentities keysWithFormat: @"%{fullName} <%{email}>"]; + [fromEMails retain]; } return fromEMails; } -/* title */ - -- (NSString *)panelTitle { - return [self labelForKey:@"Compose Mail"]; -} - /* info loading */ -- (void)loadInfo:(NSDictionary *)_info { +- (void) loadInfo: (NSDictionary *) _info +{ if (![_info isNotNull]) return; [self debugWithFormat:@"loading info ..."]; [self takeValuesFromDictionary:_info]; } -- (NSDictionary *)storeInfo { + +- (NSDictionary *) storeInfo +{ [self debugWithFormat:@"storing info ..."]; return [self valuesForKeys:infoKeys]; } /* requests */ -- (BOOL) shouldTakeValuesFromRequest: (WORequest *) _rq - inContext: (WOContext*) _c +- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request + inContext: (WOContext*) localContext { return YES; } -/* IMAP4 store */ - -- (NSException *) patchFlagsInStore -{ - /* - Flags we should set: - if the draft is a reply => [message markAnswered] - if the draft is a forward => [message addFlag:@"forwarded"] - - This is hard, we would need to find the original message in Cyrus. - */ - return nil; -} - -- (id) lookupSentFolderUsingAccount -{ - SOGoMailAccount *account; - SOGoMailFolder *folder; - - if (sentFolder != nil) - return [sentFolder isNotNull] ? sentFolder : nil;; - - account = [[self clientObject] mailAccountFolder]; - if ([account isKindOfClass:[NSException class]]) return account; - - folder = [account sentFolderInContext:[self context]]; - if ([folder isKindOfClass:[NSException class]]) return folder; - return ((sentFolder = [folder retain])); -} - -- (void) _presetFromBasedOnAccountsQueryParameter -{ - /* preset the from field to the primary identity of the given account */ - /* Note: The compose action sets the 'accounts' query parameter */ - NSString *accountID; - SOGoMailAccounts *accounts; - SOGoMailAccount *account; - SOGoMailIdentity *identity; - - if (useLocationBasedSentFolder) /* from will be based on location */ - return; - - if ([from isNotEmpty]) /* a from is already set */ - return; - - accountID = [[[self context] request] formValueForKey:@"account"]; - if (![accountID isNotEmpty]) - return; - - accounts = [[self clientObject] mailAccountsFolder]; - if ([accounts isExceptionOrNull]) - return; /* we don't treat this as an error but are tolerant */ - - account = [accounts lookupName:accountID inContext:[self context] - acquire:NO]; - if ([account isExceptionOrNull]) - return; /* we don't treat this as an error but are tolerant */ - - identity = [account valueForKey:@"preferredIdentity"]; - if (![identity isNotNull]) { - [self warnWithFormat:@"Account has no preferred identity: %@", account]; - return; - } - - [self setFrom: [identity email]]; -} - -- (SOGoMailIdentity *) selectedMailIdentity -{ - SOGoMailAccounts *accounts; - NSEnumerator *e; - SOGoMailIdentity *identity; - - accounts = [[self clientObject] mailAccountsFolder]; - if ([accounts isExceptionOrNull]) return (id)accounts; - - // TODO: This is still a hack because we detect the identity based on the - // from. In Agenor all of the identities have unique emails, but this - // is not required for SOGo. - - if ([[self from] length] == 0) - return nil; - - e = [[accounts fetchIdentitiesWithEmitterPermissions] objectEnumerator]; - while ((identity = [e nextObject]) != nil) { - if ([[identity email] isEqualToString:[self from]]) - return identity; - } - return nil; -} - -- (id) lookupSentFolderUsingFrom -{ - // TODO: if we have the identity we could also support BCC - SOGoMailAccounts *accounts; - SOGoMailIdentity *identity; - SoSubContext *ctx; - NSString *sentFolderName; - NSArray *sentFolderPath; - NSException *error = nil; - - if (sentFolder != nil) - return [sentFolder isNotNull] ? sentFolder : nil;; - - identity = [self selectedMailIdentity]; - if ([identity isKindOfClass:[NSException class]]) return identity; - - if (![(sentFolderName = [identity sentFolderName]) isNotEmpty]) { - [self warnWithFormat:@"Identity has no sent folder name: %@", identity]; - return nil; - } - - // TODO: fixme, we treat the foldername as a hardcoded path from SOGoAccounts - // TODO: escaping of foldernames with slashes - // TODO: maybe the SOGoMailIdentity should have an 'account-identifier' - // which is used to lookup the account and _then_ perform an account - // local folder lookup? => would not be possible to have identities - // saving to different accounts. - sentFolderPath = [sentFolderName componentsSeparatedByString:@"/"]; - - accounts = [[self clientObject] mailAccountsFolder]; - if ([accounts isKindOfClass:[NSException class]]) return (id)accounts; - - ctx = [[SoSubContext alloc] initWithParentContext:[self context]]; - - sentFolder = [[accounts traversePathArray:sentFolderPath - inContext:ctx error:&error - acquire:NO] retain]; - [ctx release]; ctx = nil; - if (error != nil) { - [self errorWithFormat:@"Sent-Folder lookup for identity %@ failed: %@", - identity, sentFolderPath]; - return error; - } - -#if 0 - [self logWithFormat:@"Sent-Folder: %@", sentFolderName]; - [self logWithFormat:@" object: %@", sentFolder]; -#endif - return sentFolder; -} - -- (NSException *) storeMailInSentFolder: (NSString *) _path -{ - SOGoMailFolder *folder; - NSData *data; - id result; - - folder = useLocationBasedSentFolder - ? [self lookupSentFolderUsingAccount] - : [self lookupSentFolderUsingFrom]; - if ([folder isKindOfClass:[NSException class]]) return (id)folder; - if (folder == nil) return nil; - - if ((data = [[NSData alloc] initWithContentsOfMappedFile:_path]) == nil) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not find temporary draft file!"]; - } - - result = [folder postData:data flags:@"seen"]; - [data release]; data = nil; - return result; -} - /* actions */ - (NSDictionary *) _scanAttachmentFilenamesInRequest: (id) httpBody @@ -458,8 +285,8 @@ static NSArray *infoKeys = nil; for (count = 0; count < max; count++) { part = [parts objectAtIndex: count]; - header = [part headerForKey: @"content-disposition"]; - mimeType = [[part headerForKey: @"content-type"] stringValue]; + header = (NGMimeContentDispositionHeaderField *) [part headerForKey: @"content-disposition"]; + mimeType = [(NGMimeType *) [part headerForKey: @"content-type"] stringValue]; attachment = [NSDictionary dictionaryWithObjectsAndKeys: [header filename], @"filename", mimeType, @"mime-type", nil]; @@ -506,26 +333,29 @@ static NSArray *infoKeys = nil; NSDictionary *info; NSException *error; BOOL success; + SOGoDraftObject *co; + + co = [self clientObject]; + [co fetchInfo]; success = YES; if ([self _saveAttachments]) { info = [self storeInfo]; - if (info) + [co setHeaders: info]; + [co setText: text]; + error = [co storeInfo]; + if (error) { - error = [[self clientObject] storeInfo:info]; - if (error) - { - [self errorWithFormat:@"failed to store draft: %@", error]; - // TODO: improve error handling - success = NO; - } + [self errorWithFormat: @"failed to store draft: %@", error]; + // TODO: improve error handling + success = NO; } } else success = NO; - + // TODO: wrap content return success; @@ -543,13 +373,14 @@ static NSArray *infoKeys = nil; - (NSArray *) attachmentNames { NSArray *a; - + if (attachmentNames != nil) return attachmentNames; - + a = [[self clientObject] fetchAttachmentNames]; - a = [a sortedArrayUsingSelector:@selector(compare:)]; + a = [a sortedArrayUsingSelector: @selector (compare:)]; attachmentNames = [a copy]; + return attachmentNames; } @@ -560,126 +391,72 @@ static NSArray *infoKeys = nil; - (id) defaultAction { -#if 0 - [self logWithFormat:@"edit action, load content from: %@", - [self clientObject]]; -#endif - - [self loadInfo:[[self clientObject] fetchInfo]]; - [self _presetFromBasedOnAccountsQueryParameter]; + SOGoDraftObject *co; + + co = [self clientObject]; + [co fetchInfo]; + [self loadInfo: [co headers]]; + [self setText: [co text]]; + return self; } -- (id) saveAction +- (id ) saveAction { - return [self _saveFormInfo] ? self : [self failedToSaveFormResponse]; + id result; + + if ([self _saveFormInfo]) + { + result = [[self clientObject] save]; + if (!result) + { + result = [context response]; + [result setStatus: 204]; + } + } + else + result = [self failedToSaveFormResponse]; + + return result; } - (NSException *) validateForSend { - // TODO: localize errors - - if (![self hasOneOrMoreRecipients]) { - return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Please select a recipient!"]; - } - if ([[self subject] length] == 0) { - return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Please set a subject!"]; - } + NSException *error; + + if (![self hasOneOrMoreRecipients]) + error = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ + reason: @"Please select a recipient!"]; + else if ([[self subject] length] == 0) + error = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */ + reason: @"Please set a subject!"]; + else + error = nil; - return nil; + return error; } - (id ) sendAction { - NSException *error; - NSString *mailPath; id result; // TODO: need to validate whether we have a To etc /* first, save form data */ - - if (![self _saveFormInfo]) - return [self failedToSaveFormResponse]; - - /* validate for send */ - - if ((error = [self validateForSend]) != nil) { - id url; - - url = [[error reason] stringByEscapingURL]; - url = [@"edit?error=" stringByAppendingString:url]; - return [self redirectToLocation:url]; - } - - /* setup some extra headers if required */ - - /* save mail to file (so that we can upload the mail to Cyrus) */ - // TODO: all this could be handled by the SOGoDraftObject? - - mailPath = [[self clientObject] saveMimeMessageToTemporaryFileWithHeaders: internetMailHeaders]; - - /* then, send mail */ - - if ((error = [[self clientObject] sendMimeMessageAtPath:mailPath]) != nil) { - // TODO: improve error handling - [[NSFileManager defaultManager] removeFileAtPath:mailPath handler:nil]; - return error; - } - - /* patch flags in store for replies etc */ - - if ((error = [self patchFlagsInStore]) != nil) - return error; - - /* finally store in Sent */ - - if ((error = [self storeMailInSentFolder:mailPath]) != nil) - return error; - - /* delete temporary mail file */ - - if (keepMailTmpFile) - [self warnWithFormat:@"keeping mail file: '%@'", mailPath]; - else - [[NSFileManager defaultManager] removeFileAtPath:mailPath handler:nil]; - mailPath = nil; - - /* delete draft */ - - if ((error = [[self clientObject] delete]) != nil) - return error; - - if ([[[[self context] request] formValueForKey: @"nojs"] intValue]) - result = [self redirectToLocation: [self applicationPath]]; - else - result = [self jsCloseWithRefreshMethod: nil]; + result = [self validateForSend]; + if (!result) + { + if ([self _saveFormInfo]) + { + result = [[self clientObject] sendMail]; + if (!result) + result = [self jsCloseWithRefreshMethod: nil]; + } + else + result = [self failedToSaveFormResponse]; + } return result; } -- (id) deleteAction -{ - NSException *error; - id page; - - if ((error = [[self clientObject] delete]) != nil) { - /* Note: we ignore 404: those are drafts which were not yet saved */ - if (![error httpStatus] == 404) - return error; - } - -#if 1 - page = [self pageWithName:@"UIxMailWindowCloser"]; - [page takeValue:@"YES" forKey:@"refreshOpener"]; - return page; -#else - // TODO: if we just return nil, we produce a 500 - return [NSException exceptionWithHTTPStatus:204 /* No Content */ - reason:@"object was deleted."]; -#endif -} - @end /* UIxMailEditor */ diff --git a/UI/MailerUI/UIxMailEditorAction.h b/UI/MailerUI/UIxMailEditorAction.h deleted file mode 100644 index 9a9fa4f8..00000000 --- a/UI/MailerUI/UIxMailEditorAction.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#ifndef __UIxMailEditorAction_H__ -#define __UIxMailEditorAction_H__ - -#include - -/* - UIxMailEditorAction - - This action implements the backend for the various buttons which invoke the - mail editor. The mail editor itself only works on a SOGoDraftObject which - needs to be created in advance. -*/ - -@class NSException; -@class WOResponse; -@class SOGoDraftObject, SOGoDraftsFolder; - -@interface UIxMailEditorAction : WODirectAction -{ - SOGoDraftObject *newDraft; -} - -/* errors */ - -- (id)didNotFindDraftsError; -- (id)couldNotCreateDraftError:(SOGoDraftsFolder *)_draftsFolder; -- (id)didNotFindMailError; - -/* creating new draft object */ - -- (NSException *)_setupNewDraft; -- (WOResponse *)redirectToEditNewDraft; - -/* state */ - -- (void)reset; - -@end - -#endif /* __UIxMailEditorAction_H__ */ diff --git a/UI/MailerUI/UIxMailEditorAction.m b/UI/MailerUI/UIxMailEditorAction.m deleted file mode 100644 index 8fd8ed27..00000000 --- a/UI/MailerUI/UIxMailEditorAction.m +++ /dev/null @@ -1,203 +0,0 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import - -#import -#import -#import -#import -#import -#import - -#import -#import -#import -#import -#import - -#import "UIxMailEditorAction.h" - -@implementation UIxMailEditorAction - -- (void)dealloc -{ - [self->newDraft release]; - [super dealloc]; -} - -/* caches */ - -- (void)reset { - [self->newDraft release]; self->newDraft = nil; -} - -/* lookups */ - -- (SOGoDraftsFolder *)draftsFolder { - /* - Note: we cannot use acquisition to find the nearest drafts folder, because - the IMAP4 server might contains an own Drafts folder. - */ -// SOGoDraftsFolder *drafts; - SOGoMailAccount *accountFolder; - - accountFolder = [[self clientObject] mailAccountFolder]; - - return [accountFolder - lookupName: [accountFolder draftsFolderNameInContext: context] - inContext: context acquire: NO]; -} - -/* errors */ - -- (id)didNotFindDraftsError { - // TODO: make a nice error page - return [@"did not find drafts folder in object: " - stringByAppendingString:[[self clientObject] description]]; -} -- (id)couldNotCreateDraftError:(SOGoDraftsFolder *)_draftsFolder { - return [@"could not create a new draft in folder: " - stringByAppendingString:[_draftsFolder description]]; -} -- (id)didNotFindMailError { - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"Did not find mail for operation!"]; -} - -/* compose */ - -- (id) composeAction -{ - SOGoDraftsFolder *drafts; - WOResponse *r; - NSString *urlBase, *url; - NSMutableDictionary *urlParams; - id parameter; - id returnValue; - - drafts = [self draftsFolder]; - if ([drafts isNotNull]) - { - if ([drafts isKindOfClass: [NSException class]]) - returnValue = drafts; - else - { - urlBase = [drafts newObjectBaseURLInContext: context]; - if ([urlBase isNotNull]) - { - urlParams = [NSMutableDictionary new]; - [urlParams autorelease]; - - /* attach mail-account info */ - parameter - = [[self clientObject] valueForKey: @"mailAccountFolder"]; - if (parameter && ![parameter isExceptionOrNull]) - [urlParams setObject: [parameter nameInContainer] - forKey: @"account"]; - - parameter = [[self request] formValueForKey: @"mailto"]; - if (parameter) - [urlParams setObject: parameter - forKey: @"mailto"]; - - url = [urlBase composeURLWithAction: @"edit" - parameters: urlParams - andHash: NO]; - - /* perform redirect */ - - [self debugWithFormat:@"compose on %@: %@", drafts, url]; - - r = [context response]; - [r setStatus: 302 /* move d */]; - [r setHeader: url forKey: @"location"]; - [self reset]; - - returnValue = r; - } - else - returnValue = [self couldNotCreateDraftError: drafts]; - } - } - else - returnValue = [self didNotFindDraftsError]; - - return returnValue; -} - -/* creating new draft object */ - -- (id)newDraftObject { - SOGoDraftsFolder *drafts; - - drafts = [self draftsFolder]; - if (![drafts isNotNull]) - return [self didNotFindDraftsError]; - if ([drafts isKindOfClass:[NSException class]]) - return drafts; - - return [drafts newObjectInContext:context]; -} - -- (NSException *)_setupNewDraft { - SOGoDraftObject *tmp; - - /* create draft object */ - - if ([(tmp = [self newDraftObject]) isKindOfClass:[NSException class]]) - return (NSException *)tmp; - if (![tmp isNotNull]) { /* Note: should never happen? */ - [self logWithFormat:@"WARNING: got no new draft object and no error!"]; - return [self didNotFindDraftsError]; // TODO: not exact - } - - ASSIGN(self->newDraft, tmp); - //[self debugWithFormat:@"NEW DRAFT: %@", self->newDraft]; - - return nil; -} - -- (WOResponse *)redirectToEditNewDraft { - WOResponse *r; - NSString *url; - - if (![self->newDraft isNotNull]) { - [self logWithFormat:@"ERROR(%s): missing new draft (already -reset?)", - __PRETTY_FUNCTION__]; - return nil; - } - - url = [self->newDraft baseURLInContext:context]; - if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"]; - url = [url stringByAppendingString:@"edit"]; - - // TODO: debug log - [self logWithFormat:@"compose on %@", url]; - - r = [context response]; - [r setStatus:302 /* moved */]; - [r setHeader:url forKey:@"location"]; - [self reset]; - return r; -} - -@end /* UIxMailEditorAction */ diff --git a/UI/MailerUI/UIxMailFolderActions.h b/UI/MailerUI/UIxMailFolderActions.h index df473df6..d3641d4a 100644 --- a/UI/MailerUI/UIxMailFolderActions.h +++ b/UI/MailerUI/UIxMailFolderActions.h @@ -28,12 +28,15 @@ @class WOResponse; @interface UIxMailFolderActions : WODirectAction -{ -} - (WOResponse *) createFolderAction; - (WOResponse *) renameFolderAction; - (WOResponse *) deleteFolderAction; +- (WOResponse *) expungeAction; +- (WOResponse *) emptyTrashAction; +- (WOResponse *) subscribeAction; +- (WOResponse *) unsubscribeAction; +- (WOResponse *) quotasAction; @end diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index 3111e172..b4a184b8 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -58,7 +58,7 @@ error = [connection createMailbox: folderName atURL: [co imap4URL]]; if (error) { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Unable to create folder."]; } else @@ -66,7 +66,7 @@ } else { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Missing 'name' parameter."]; } @@ -112,7 +112,7 @@ toURL: destURL]; if (error) { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Unable to rename folder."]; } else @@ -120,7 +120,7 @@ } else { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Missing 'name' parameter."]; } @@ -164,7 +164,7 @@ toURL: destURL]; if (error) { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Unable to move folder."]; } else @@ -173,6 +173,30 @@ return response; } +- (WOResponse *) expungeAction +{ + NSException *error; + SOGoTrashFolder *co; + WOResponse *response; + + co = [self clientObject]; + response = [context response]; + + error = [co expunge]; + if (error) + { + [response setStatus: 500]; + [response appendContentString: @"Unable to expunge folder."]; + } + else + { + [co flushMailCaches]; + [response setStatus: 204]; + } + + return response; +} + - (WOResponse *) emptyTrashAction { NSException *error; @@ -202,7 +226,7 @@ } if (error) { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"Unable to empty the trash folder."]; } else @@ -233,7 +257,7 @@ } else { - [response setStatus: 403]; + [response setStatus: 500]; [response appendContentString: @"How did you end up here?"]; } diff --git a/UI/MailerUI/UIxMailForwardAction.m b/UI/MailerUI/UIxMailForwardAction.m deleted file mode 100644 index ba59391e..00000000 --- a/UI/MailerUI/UIxMailForwardAction.m +++ /dev/null @@ -1,117 +0,0 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import -#import - -#import - -#import -#import - -#import "UIxMailEditorAction.h" - -@interface UIxMailForwardAction : UIxMailEditorAction -@end - - -@implementation UIxMailForwardAction - -- (NSString *)getAttachmentNameForSubject:(NSString *)_subject { - /* SOGoDraftObject disallows some strings - anything else required? */ - static NSString *sescape[] = { - @"/", @"..", @"~", @"\"", @"'", @" ", @".", nil - }; - static int maxFilenameLength = 64; - NSString *s; - unsigned i; - - if (![_subject isNotNull] || [_subject length] == 0) - return _subject; - s = _subject; - - if ([s length] > maxFilenameLength) - s = [s substringToIndex:maxFilenameLength]; - - for (i = 0; sescape[i] != nil; i++) - s = [s stringByReplacingString:sescape[i] withString:@"_"]; - - return [s stringByAppendingString:@".mail"]; -} - -- (NSString *)forwardSubject:(NSString *)_subject { - if (![_subject isNotNull] || [_subject length] == 0) - return _subject; - - /* Note: this is how Thunderbird 1.0 creates the subject */ - _subject = [@"[Fwd: " stringByAppendingString:_subject]; - _subject = [_subject stringByAppendingString:@"]"]; - return _subject; -} - -- (id)forwardAction { - NSException *error; - NSData *content; - NSDictionary *info, *attachment; - id result; - - /* fetch message */ - - if ((content = [[self clientObject] content]) == nil) - return [self didNotFindMailError]; - if ([content isKindOfClass:[NSException class]]) - return content; - - /* setup draft */ - - if ((error = [self _setupNewDraft]) != nil) - return error; - - /* set subject (do we need to set anything else?) */ - - info = [NSDictionary dictionaryWithObjectsAndKeys: - [self forwardSubject:[[self clientObject] subject]], - @"subject", - nil]; - if ((error = [newDraft storeInfo:info]) != nil) - return error; - - /* attach message */ - - // TODO: use subject for filename? -// error = [newDraft saveAttachment:content withName:@"forward.mail"]; - attachment = [NSDictionary dictionaryWithObjectsAndKeys: - @"forward.mail", @"filename", - @"message/rfc822", @"mime-type", - nil]; - error = [newDraft saveAttachment: content - withMetadata: attachment]; - if (error != nil) - return error; - - // TODO: we might want to pass the original URL to the editor for a final - // redirect back to the message? - result = [self redirectToEditNewDraft]; - [self reset]; - return result; -} - -@end /* UIxMailForwardAction */ diff --git a/UI/MailerUI/UIxMailListView.m b/UI/MailerUI/UIxMailListView.m index 41ba101b..ae275867 100644 --- a/UI/MailerUI/UIxMailListView.m +++ b/UI/MailerUI/UIxMailListView.m @@ -567,30 +567,6 @@ static int attachmentFlagSize = 8096; return [self redirectToLocation:@"view"]; } -- (id) expungeAction -{ - // TODO: we might want to flush the caches? - NSException *error; - id clientObject; - - clientObject = [self clientObject]; - if (clientObject) - { - error = [clientObject expunge]; - if (!error) - { - if ([clientObject respondsToSelector: @selector(flushMailCaches)]) - [clientObject flushMailCaches]; - return [self redirectToLocation:@"view"]; - } - } - else - error = [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason: @"did not find mail folder"]; - - return error; -} - @end /* UIxMailListView */ diff --git a/UI/MailerUI/UIxMailMainFrame.h b/UI/MailerUI/UIxMailMainFrame.h index 2de75609..70bf0198 100644 --- a/UI/MailerUI/UIxMailMainFrame.h +++ b/UI/MailerUI/UIxMailMainFrame.h @@ -1,4 +1,4 @@ -/* UIxMailMainFrame.h - this file is part of $PROJECT_NAME_HERE$ +/* UIxMailMainFrame.h - this file is part of SOGo * * Copyright (C) 2006 Inverse groupe conseil * @@ -23,21 +23,9 @@ #ifndef UIXMAILMAINFRAME_H #define UIXMAILMAINFRAME_H -#import "../Common/UIxPageFrame.h" +#import "../SOGoUI/UIxComponent.h" -@interface UIxMailMainFrame : UIxPageFrame -{ - NSString *rootURL; - NSString *userRootURL; - struct { - int hideFolderTree:1; - int reserved:31; - } mmfFlags; -} - -- (NSString *)rootURL; -- (NSString *)userRootURL; -- (NSString *)calendarRootURL; +@interface UIxMailMainFrame : UIxComponent @end diff --git a/UI/MailerUI/UIxMailMainFrame.m b/UI/MailerUI/UIxMailMainFrame.m index 95c64ca8..9ad78fb1 100644 --- a/UI/MailerUI/UIxMailMainFrame.m +++ b/UI/MailerUI/UIxMailMainFrame.m @@ -26,54 +26,27 @@ #import #import -#import -#import +#import +#import #import #import "UIxMailMainFrame.h" @implementation UIxMailMainFrame -static NSString *treeRootClassName = nil; - -+ (void)initialize { - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - - treeRootClassName = [[ud stringForKey:@"SOGoMailTreeRootClass"] copy]; - if (treeRootClassName) - NSLog(@"Note: use class '%@' as root for mail tree.", treeRootClassName); - else - treeRootClassName = @"SOGoMailAccounts"; -} - -- (void)dealloc { - [self->rootURL release]; - [self->userRootURL release]; - [super dealloc]; -} - /* accessors */ - (NSString *) mailAccounts { - SOGoMailAccounts *co; + NSArray *accounts, *accountNames; - co = [self clientObject]; + accounts = [[context activeUser] mailAccounts]; + accountNames = [accounts objectsForKey: @"name"]; - return [[co fetchAllIdentities] jsonRepresentation]; + return [accountNames jsonRepresentation]; } -- (NSString *)treeRootClassName { - return treeRootClassName; -} - -- (void)setHideFolderTree:(BOOL)_flag { - self->mmfFlags.hideFolderTree = _flag ? 1 : 0; -} -- (BOOL)hideFolderTree { - return self->mmfFlags.hideFolderTree ? YES : NO; -} - -- (NSString *) pageFormURL { +- (NSString *) pageFormURL +{ NSString *u; NSRange r; @@ -117,136 +90,4 @@ static NSString *treeRootClassName = nil; return [u hasSuffix:@"/"] ? @"view" : @"#"; } -- (BOOL)showLinkBanner { - return YES; -} - -- (NSString *)bannerToolbarStyle { - return nil; -} - -- (NSString *)bannerConsumeStyle { - return nil; -} - -/* URL generation */ -// TODO: I think all this should be done by the clientObject?! -// TODO: is the stuff below necessary at all in the mailer frame? - -- (NSString *)rootURL { - WOContext *ctx; - NSArray *traversalObjects; - - if (self->rootURL != nil) - return self->rootURL; - - ctx = [self context]; - traversalObjects = [ctx objectTraversalStack]; - self->rootURL = [[[traversalObjects objectAtIndex:0] - rootURLInContext:ctx] - copy]; - return self->rootURL; -} - -- (NSString *)userRootURL { - WOContext *ctx; - NSArray *traversalObjects; - - if (self->userRootURL) - return self->userRootURL; - - ctx = [self context]; - traversalObjects = [ctx objectTraversalStack]; - self->userRootURL = [[[[traversalObjects objectAtIndex:1] - baseURLInContext:ctx] - stringByAppendingString:@"/"] - retain]; - return self->userRootURL; -} - -- (NSString *)calendarRootURL { - return [[self userRootURL] stringByAppendingString:@"Calendar/"]; -} - -- (NSString *)contactsRootURL { - return [[self userRootURL] stringByAppendingString:@"Contacts/"]; -} - -/* error handling */ - -- (BOOL)hasErrorText { - return [[[[self context] request] formValueForKey:@"error"] length] > 0 - ? YES : NO; -} -- (NSString *)errorText { - return [[[self context] request] formValueForKey:@"error"]; -} - -- (NSString *)errorAlertJavaScript { - NSString *errorText; - - if ([(errorText = [self errorText]) length] == 0) - return nil; - - // TODO: proper JavaScript escaping - errorText = [errorText stringByEscapingHTMLString]; - errorText = [errorText stringByReplacingString:@"\"" withString:@"'"]; - - return [NSString stringWithFormat: - @"", errorText]; -} - -/* FIXME: migrated methods which might not work yet... */ -// #warning check this -// - (NSString *) mailFolderName -// { -// NSMutableArray *mailboxes; -// SOGoMailObject *currentObject; - -// mailboxes = [NSMutableArray new]; -// [mailboxes autorelease]; - -// currentObject = [self clientObject]; -// while (![currentObject isKindOfClass: [SOGoMailAccounts class]]) -// { -// [mailboxes insertObject: [currentObject nameInContainer] atIndex: 0]; -// currentObject = [currentObject container]; -// } - -// return [NSString stringWithFormat: @"/%@", -// [mailboxes componentsJoinedByString: @"/"]]; -// } - -- (id) composeAction -{ - NSArray *c; - NSString *inbox, *url, *parameter; - NSMutableDictionary *urlParams; - id actionResult; - - c = [[self clientObject] toManyRelationshipKeys]; - if ([c count] > 0) - { - urlParams = [NSMutableDictionary new]; - [urlParams autorelease]; - - parameter = [self queryParameterForKey: @"mailto"]; - if (parameter) - [urlParams setObject: parameter - forKey: @"mailto"]; - inbox = [NSString stringWithFormat: @"%@/folderINBOX", - [c objectAtIndex: 0]]; - url = [inbox composeURLWithAction: @"compose" - parameters: urlParams - andHash: NO]; - actionResult = [self redirectToLocation: url]; - } - else - actionResult = self; - - return actionResult; -} - @end /* UIxMailMainFrame */ diff --git a/UI/MailerUI/UIxMailReplyAction.m b/UI/MailerUI/UIxMailReplyAction.m deleted file mode 100644 index ac3e13b8..00000000 --- a/UI/MailerUI/UIxMailReplyAction.m +++ /dev/null @@ -1,252 +0,0 @@ -/* - Copyright (C) 2004-2005 SKYRIX Software AG - - This file is part of OpenGroupware.org. - - OGo is free software; you can redistribute it and/or modify it under - the terms of the GNU Lesser General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - OGo 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 Lesser General Public - License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. -*/ - -#import -#import -#import -#import -#import -#import -#import -#import - -#import "UIxMailEditorAction.h" - -@interface UIxMailReplyAction : UIxMailEditorAction -@end - -@implementation UIxMailReplyAction - -- (BOOL)hasReplyPrefix:(NSString *)_subject { - static NSString *replyPrefixes[] = { - @"Re:", // regular - @"RE:", // Outlook v11 (English?) - @"AW:", // German Outlook v11 - @"Re[", // numbered Re, eg "Re[2]:" - nil - }; - unsigned i; - for (i = 0; replyPrefixes[i] != nil; i++) { - if ([_subject hasPrefix:replyPrefixes[i]]) - return YES; - } - return NO; -} - -- (NSString *)replySubject:(NSString *)_subject { - if (![_subject isNotNull] || [_subject length] == 0) - return _subject; - - if ([self hasReplyPrefix:_subject]) { - /* do not do: "Re: Re: Re: My Mail" - a single Re is sufficient ;-) */ - return _subject; - } - - return [@"Re: " stringByAppendingString:_subject]; -} - -- (void)addEMailsOfAddresses:(NSArray *)_addrs toArray:(NSMutableArray *)_ma { - unsigned i, count; - - for (i = 0, count = [_addrs count]; i < count; i++) - [_ma addObject:[(NGImap4EnvelopeAddress *)[_addrs objectAtIndex:i] email]]; -} - -- (void)fillInReplyAddresses:(NSMutableDictionary *)_info - replyToAll:(BOOL)_replyToAll - envelope:(NGImap4Envelope *)_envelope -{ - /* - The rules as implemented by Thunderbird: - - if there is a 'reply-to' header, only include that (as TO) - - if we reply to all, all non-from addresses are added as CC - - the from is always the lone TO (except for reply-to) - - Note: we cannot check reply-to, because Cyrus even sets a reply-to in the - envelope if none is contained in the message itself! (bug or - feature?) - - TODO: what about sender (RFC 822 3.6.2) - */ - NSMutableArray *to; - NSArray *addrs; - - to = [NSMutableArray arrayWithCapacity:2]; - - /* first check for "reply-to" */ - - addrs = [_envelope replyTo]; - if ([addrs count] == 0) { - /* no "reply-to", try "from" */ - addrs = [_envelope from]; - } - [self addEMailsOfAddresses:addrs toArray:to]; - [_info setObject:to forKey:@"to"]; - - /* CC processing if we reply-to-all: add all 'to' and 'cc' */ - - if (_replyToAll) { - to = [NSMutableArray arrayWithCapacity:8]; - - [self addEMailsOfAddresses:[_envelope to] toArray:to]; - [self addEMailsOfAddresses:[_envelope cc] toArray:to]; - - [_info setObject:to forKey:@"cc"]; - } -} - -- (NSString *) contentForReplyOnParts: (NSDictionary *) _prts - keys: (NSArray *) _k -{ - static NSString *textPartSeparator = @"\n---\n"; - NSMutableString *ms; - unsigned i, count; - - ms = [NSMutableString stringWithCapacity:16000]; - for (i = 0, count = [_k count]; i < count; i++) { - NSString *k, *v; - - k = [_k objectAtIndex:i]; - - // TODO: this is DUP code to SOGoMailObject - if ([k isEqualToString:@"body[text]"]) - k = @""; - else if ([k hasPrefix:@"body["]) { - k = [k substringFromIndex:5]; - if ([k length] > 0) k = [k substringToIndex:([k length] - 1)]; - } - - v = [_prts objectForKey:k]; - if (![v isKindOfClass:[NSString class]]) { - [self logWithFormat:@"Note: cannot show part %@", k]; - continue; - } - if ([v length] == 0) - continue; - - if (i != 0) [ms appendString:textPartSeparator]; - [ms appendString:[v stringByApplyingMailQuoting]]; - } - return ms; -} - -- (NSString *)contentForReply { - NSArray *keys, *partInfos; - NSDictionary *parts, *infos; - - keys = [[self clientObject] plainTextContentFetchKeys]; -// SOGoMailObject *co; - -// co = [self clientObject]; -// keys = [co plainTextContentFetchKeys]; -// infos = [co fetchCoreInfos]; -// partInfos = [infos objectForKey: keys]; -// NSLog (@"infos: '%@'", infos); - - if ([keys count] == 0) - return nil; - - if ([keys count] > 1) { - /* filter keys, only include top-level, or if none, the first */ - NSMutableArray *topLevelKeys = nil; - unsigned i; - - for (i = 0; i < [keys count]; i++) { - NSRange r; - - r = [[keys objectAtIndex:i] rangeOfString:@"."]; - if (r.length > 0) - continue; - - if (topLevelKeys == nil) - topLevelKeys = [NSMutableArray arrayWithCapacity:4]; - [topLevelKeys addObject:[keys objectAtIndex:i]]; - } - - if ([topLevelKeys count] > 0) { - /* use top-level keys if we have some */ - keys = topLevelKeys; - } - else { - /* just take the first part */ - keys = [NSArray arrayWithObject:[keys objectAtIndex:0]]; - } - } - - parts = [[self clientObject] fetchPlainTextStrings:keys]; - return [self contentForReplyOnParts:parts keys:keys]; -} - -- (id)replyToAll:(BOOL)_replyToAll { - NSMutableDictionary *info; - NSException *error; - id result; - id tmp; - - /* ensure mail exists and is filled */ - - // TODO: we could transport the body structure in a hidden field of the mail - // viewer to avoid refetching the core-info? - tmp = [[self clientObject] fetchCoreInfos]; - if ([tmp isKindOfClass:[NSException class]]) - return tmp; - if (![tmp isNotNull]) - return [self didNotFindMailError]; - - /* setup draft */ - - if ((error = [self _setupNewDraft]) != nil) - return error; - - /* fill draft info */ - - info = [NSMutableDictionary dictionaryWithCapacity:16]; - - [info setObject:[self replySubject:[[self clientObject] subject]] - forKey:@"subject"]; - [self fillInReplyAddresses:info replyToAll:_replyToAll - envelope:[[self clientObject] envelope]]; - - /* fill in text content */ - - if ((tmp = [self contentForReply]) != nil) - [info setObject:tmp forKey:@"text"]; - - /* save draft info */ - - if ((error = [self->newDraft storeInfo:info]) != nil) - return error; - - // TODO: we might want to pass the original URL to the editor for a final - // redirect back to the message? - result = [self redirectToEditNewDraft]; - [self reset]; - return result; -} - -- (id)replyAction { - return [self replyToAll:NO]; -} -- (id)replyallAction { - return [self replyToAll:YES]; -} - -@end /* UIxMailReplyAction */ diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index a8c01a0b..1eb40bc7 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -187,6 +187,11 @@ static NSString *mailETag = nil; return NO; } +- (BOOL) mailIsDraft +{ + return [[self clientObject] isInDraftsFolder]; +} + - (id) redirectToParentFolder { id url; diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index 8a3b1331..f974b072 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -133,8 +133,8 @@ categories = { }; expunge = { protectedBy = "View"; - pageName = "UIxMailListView"; - actionName = "expunge"; + actionClass = "UIxMailFolderActions"; + actionName = "emptyTrash"; }; createFolder = { protectedBy = "View"; @@ -151,11 +151,6 @@ categories = { actionClass = "UIxMailFolderActions"; actionName = "deleteFolder"; }; - compose = { - protectedBy = "View"; - actionClass = "UIxMailEditorAction"; - actionName = "compose"; - }; userRights = { protectedBy = "ReadAcls"; pageName = "UIxMailUserRightsEditor"; @@ -228,26 +223,22 @@ categories = { }; edit = { protectedBy = "View"; - pageName = "UIxMailEditor"; - }; - compose = { - protectedBy = "View"; - actionClass = "UIxMailEditorAction"; - actionName = "compose"; + actionClass = "UIxMailActions"; + actionName = "edit"; }; reply = { protectedBy = "View"; - actionClass = "UIxMailReplyAction"; + actionClass = "UIxMailActions"; actionName = "reply"; }; replyall = { protectedBy = "View"; - actionClass = "UIxMailReplyAction"; - actionName = "replyall"; + actionClass = "UIxMailActions"; + actionName = "replyToAll"; }; forward = { protectedBy = "View"; - actionClass = "UIxMailForwardAction"; + actionClass = "UIxMailActions"; actionName = "forward"; }; }; @@ -265,11 +256,6 @@ categories = { protectedBy = "View"; pageName = "UIxMailMainFrame"; }; - compose = { - protectedBy = "View"; - pageName = "UIxMailMainFrame"; - actionName = "compose"; - }; getMail = { protectedBy = "View"; pageName = "UIxMailAccountsView"; @@ -287,7 +273,7 @@ categories = { methods = { compose = { protectedBy = "View"; - actionClass = "UIxMailEditorAction"; + actionClass = "UIxMailAccountActions"; actionName = "compose"; }; mailboxes = { @@ -331,11 +317,6 @@ categories = { protectedBy = "View"; pageName = "UIxMailListView"; }; - compose = { - protectedBy = "View"; - actionClass = "UIxMailEditorAction"; - actionName = "compose"; - }; }; }; @@ -347,10 +328,6 @@ categories = { }; }; methods = { - view = { /* somewhat hackish */ - protectedBy = "View"; - pageName = "UIxMailView"; - }; edit = { protectedBy = "View"; pageName = "UIxMailEditor"; @@ -360,27 +337,6 @@ categories = { pageName = "UIxMailEditor"; actionName = "save"; }; - delete = { - protectedBy = "View"; - pageName = "UIxMailEditor"; - actionName = "delete"; - }; - - viewAttachments = { - protectedBy = "View"; - pageName = "UIxMailEditorAttach"; - actionName = "viewAttachments"; - }; - attach = { - protectedBy = "View"; - pageName = "UIxMailEditorAttach"; - actionName = "attach"; - }; - deleteAttachment = { - protectedBy = "View"; - pageName = "UIxMailEditorAttach"; - actionName = "deleteAttachment"; - }; send = { protectedBy = "View"; pageName = "UIxMailEditor"; diff --git a/UI/MainUI/GNUmakefile b/UI/MainUI/GNUmakefile index 6ea51ebe..7d053e5b 100644 --- a/UI/MainUI/GNUmakefile +++ b/UI/MainUI/GNUmakefile @@ -18,8 +18,10 @@ MainUI_OBJC_FILES += \ MainUI_RESOURCE_FILES += \ Version \ product.plist \ + AgenorProfile.sql \ + AgenorProfile-oracle.sql \ OCSFolderInfo.sql \ - AgenorProfile.sql + OCSFolderInfo-oracle.sql \ MainUI_LOCALIZED_RESOURCE_FILES += \ Locale Localizable.strings diff --git a/UI/SOGoUI/SOGoACLAdvisory.m b/UI/SOGoUI/SOGoACLAdvisory.m index aefddbdd..31b42ad6 100644 --- a/UI/SOGoUI/SOGoACLAdvisory.m +++ b/UI/SOGoUI/SOGoACLAdvisory.m @@ -176,13 +176,21 @@ NGMimeMessage *message; NGMimeMultipartBody *body; SOGoUser *activeUser; + NSDictionary *identity; + NSString *from, *fullMail; activeUser = [context activeUser]; - recipient = [[LDAPUserManager sharedUserManager] getFullEmailForUID: recipientUID]; + identity = [activeUser primaryIdentity]; + from = [identity objectForKey: @"email"]; + fullMail = [NSString stringWithFormat: @"%@ <%@>", + [identity objectForKey: @"fullName"], from]; + + recipient = [[LDAPUserManager sharedUserManager] + getFullEmailForUID: recipientUID]; headerMap = [NGMutableHashMap hashMapWithCapacity: 5]; [headerMap setObject: @"multipart/alternative" forKey: @"content-type"]; - [headerMap setObject: [activeUser fullEmail] forKey: @"From"]; + [headerMap setObject: fullMail forKey: @"From"]; [headerMap setObject: recipient forKey: @"To"]; date = [[NSCalendarDate date] rfc822DateString]; [headerMap setObject: date forKey: @"Date"]; @@ -197,7 +205,7 @@ [[SOGoMailer sharedMailer] sendMimePart: message toRecipients: [NSArray arrayWithObject: recipient] - sender: [activeUser primaryEmail]]; + sender: from]; } @end diff --git a/UI/Scheduler/UIxCalView.m b/UI/Scheduler/UIxCalView.m index fd5b0f24..62fb66fe 100644 --- a/UI/Scheduler/UIxCalView.m +++ b/UI/Scheduler/UIxCalView.m @@ -100,23 +100,20 @@ static BOOL shouldDisplayWeekend = NO; - (NSArray *) filterAppointments:(NSArray *) _apts { NSMutableArray *filtered; - unsigned i, count; - NSString *email; - NSDictionary *info; - NSArray *partmails; - unsigned p, pCount; + unsigned i, count, p, pCount; + NSString *email, *partmailsString, *state, *pEmail; + NSDictionary *info, *primaryIdentity; + NSArray *partmails, *partstates; BOOL shouldAdd; - NSString *partmailsString; - NSArray *partstates; - NSString *state; - NSString *pEmail; if ([self shouldDisplayRejectedAppointments]) return _apts; { count = [_apts count]; filtered = [[[NSMutableArray alloc] initWithCapacity: count] autorelease]; - email = [[context activeUser] primaryEmail]; + + primaryIdentity = [[context activeUser] primaryIdentity]; + email = [primaryIdentity objectForKey: @"email"]; for (i = 0; i < count; i++) { diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 08ef2092..cec6bbd1 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -783,6 +783,7 @@ { NSString *organizerEmail; SOGoUser *activeUser; + NSDictionary *primaryIdentity; organizerEmail = [[component organizer] email]; if ([organizerEmail length] == 0) @@ -791,8 +792,9 @@ { ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]); activeUser = [context activeUser]; + primaryIdentity = [activeUser primaryIdentity]; [organizer setCn: [activeUser cn]]; - [organizer setEmail: [activeUser primaryEmail]]; + [organizer setEmail: [primaryIdentity objectForKey: @"email"]]; [component setOrganizer: organizer]; } } diff --git a/UI/Templates/ContactsUI/UIxContactsListView.wox b/UI/Templates/ContactsUI/UIxContactsListView.wox index 79df8ddf..bbaa1661 100644 --- a/UI/Templates/ContactsUI/UIxContactsListView.wox +++ b/UI/Templates/ContactsUI/UIxContactsListView.wox @@ -29,7 +29,7 @@ - - + + + -
-
-
- -
    - -
-
- : -
-
- -
-
:
+ +
+
+ +
    + +
+
+ : +
+
+
+
:
+
-