]> err.no Git - scalable-opengroupware.org/blobdiff - UI/MailerUI/UIxMailEditor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1193 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / MailerUI / UIxMailEditor.m
index 64ea6cfd8a64b94f87113f4f44843a93271fd97f..a2eb9ee00cf94c8b412ee6e4cc60d5ef6d7c33ec 100644 (file)
   02111-1307, USA.
 */
 
+#import <Foundation/NSFileManager.h>
+#import <Foundation/NSKeyValueCoding.h>
+#import <Foundation/NSString.h>
+#import <Foundation/NSUserDefaults.h>
+
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGObjWeb/SoSubContext.h>
+#import <NGObjWeb/WORequest.h>
+#import <NGObjWeb/WOResponse.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
+#import <NGExtensions/NSString+misc.h>
+#import <NGExtensions/NSException+misc.h>
+#import <NGMail/NGMimeMessage.h>
+#import <NGMail/NGMimeMessageGenerator.h>
+#import <NGMime/NGMimeBodyPart.h>
+#import <NGMime/NGMimeHeaderFields.h>
+#import <NGMime/NGMimeMultipartBody.h>
+
+#import <SoObjects/Mailer/SOGoDraftObject.h>
+#import <SoObjects/Mailer/SOGoMailFolder.h>
+#import <SoObjects/Mailer/SOGoMailAccount.h>
+#import <SoObjects/Mailer/SOGoMailAccounts.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+#import <SoObjects/SOGo/NSArray+Utilities.h>
+#import <SoObjects/SOGo/NSDictionary+Utilities.h>
 #import <SOGoUI/UIxComponent.h>
 
 /*
@@ -27,9 +53,6 @@
   An mail editor component which works on SOGoDraftObject's.
 */
 
-@class NSArray, NSString;
-@class SOGoMailFolder;
-
 @interface UIxMailEditor : UIxComponent
 {
   NSArray  *to;
@@ -37,7 +60,7 @@
   NSArray  *bcc;
   NSString *subject;
   NSString *text;
-  NSArray  *fromEMails;
+  NSArray *fromEMails;
   NSString *from;
   SOGoMailFolder *sentFolder;
 
 
 @end
 
-#import <SoObjects/Mailer/SOGoDraftObject.h>
-#import <SoObjects/Mailer/SOGoMailFolder.h>
-#import <SoObjects/Mailer/SOGoMailAccount.h>
-#import <SoObjects/Mailer/SOGoMailAccounts.h>
-#import <SoObjects/Mailer/SOGoMailIdentity.h>
-#import <SoObjects/SOGo/WOContext+Agenor.h>
-#import <NGMail/NGMimeMessage.h>
-#import <NGMail/NGMimeMessageGenerator.h>
-#import <NGObjWeb/SoSubContext.h>
-#import "common.h"
-
 @implementation UIxMailEditor
 
-static BOOL         keepMailTmpFile      = NO;
-static BOOL         showInternetMarker   = NO;
-static BOOL         useLocationBasedSentFolder = NO;
+static BOOL showInternetMarker = NO;
+static BOOL useLocationBasedSentFolder = NO;
 static NSDictionary *internetMailHeaders = nil;
-static NSArray      *infoKeys            = nil;
+static NSArray *infoKeys = nil;
 
-+ (void)initialize {
++ (void) initialize
+{
   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
   
   infoKeys = [[NSArray alloc] initWithObjects:
-                               @"subject", @"text", @"to", @"cc", @"bcc", 
-                               @"from", @"replyTo",
+                               @"subject", @"to", @"cc", @"bcc", 
+                             @"from", @"replyTo", @"inReplyTo",
                              nil];
   
-  keepMailTmpFile = [ud boolForKey:@"SOGoMailEditorKeepTmpFile"];
-  if (keepMailTmpFile)
-    NSLog(@"WARNING: keeping mail files.");
-  
   useLocationBasedSentFolder =
     [ud boolForKey:@"SOGoUseLocationBasedSentFolder"];
   
   /* Internet mail settings */
   
   showInternetMarker = [ud boolForKey:@"SOGoShowInternetMarker"];
-  if (!showInternetMarker) {
+  if (!showInternetMarker)
     NSLog(@"Note: visual Internet marker on mail editor disabled "
          @"(SOGoShowInternetMarker)");
-  }
   
   internetMailHeaders = 
     [[ud dictionaryForKey:@"SOGoInternetMailHeaders"] copy];
-  NSLog(@"Note: specified %d headers for mails send via the Internet.", 
+  NSLog (@"Note: specified %d headers for mails send via the Internet.", 
        [internetMailHeaders count]);
 }
 
-- (void)dealloc {
-  [self->sentFolder release];
-  [self->fromEMails release];
-  [self->from    release];
-  [self->text    release];
-  [self->subject release];
-  [self->to      release];
-  [self->cc      release];
-  [self->bcc     release];
-  
-  [self->attachmentName  release];
-  [self->attachmentNames release];
+- (void) dealloc
+{
+  [sentFolder release];
+  [fromEMails release];
+  [from release];
+  [text release];
+  [subject release];
+  [to release];
+  [cc release];
+  [bcc release];
+  [attachmentName release];
+  [attachmentNames release];
   [super dealloc];
 }
 
 /* accessors */
 
-- (void)setFrom:(NSString *)_value {
-  ASSIGNCOPY(self->from, _value);
-}
-- (NSString *)from {
-  if (![self->from isNotEmpty])
-    return [[[self context] activeUser] email];
-  return self->from;
+- (void) setFrom: (NSString *) newFrom
+{
+  ASSIGN (from, newFrom);
 }
 
-- (void)setReplyTo:(NSString *)_ignore {
-}
-- (NSString *)replyTo {
-  /* we are here for future extensibility */
-  return @"";
-}
+- (NSString *) from
+{
+  NSDictionary *identity;
 
-- (void)setSubject:(NSString *)_value {
-  ASSIGNCOPY(self->subject, _value);
-}
-- (NSString *)subject {
-  return self->subject ? self->subject : @"";
-}
+  if (!from)
+    {
+      identity = [[context activeUser] primaryIdentity];
+      from = [identity keysWithFormat: @"%{fullName} <%{email}>"];
+    }
 
-- (void)setText:(NSString *)_value {
-  ASSIGNCOPY(self->text, _value);
-}
-- (NSString *)text {
-  return [self->text isNotNull] ? self->text : @"";
+  return from;
 }
 
-- (void)setTo:(NSArray *)_value {
-  ASSIGNCOPY(self->to, _value);
-}
-- (NSArray *)to {
-  return [self->to isNotNull] ? self->to : [NSArray array];
-}
+// - (void) setReplyTo: (NSString *) ignore
+// {
+// }
+
+// - (NSString *) replyTo
+// {
+//   /* we are here for future extensibility */
+//   return @"";
+// }
 
-- (void)setCc:(NSArray *)_value {
-  ASSIGNCOPY(self->cc, _value);
+- (void) setSubject: (NSString *) newSubject
+{
+  ASSIGN (subject, newSubject);
 }
-- (NSArray *)cc {
-  return [self->cc isNotNull] ? self->cc : [NSArray array];
+
+- (NSString *) subject
+{
+  return subject;
 }
 
-- (void)setBcc:(NSArray *)_value {
-  ASSIGNCOPY(self->bcc, _value);
+- (void) setText: (NSString *) newText
+{
+  ASSIGN (text, newText);
 }
-- (NSArray *)bcc {
-  return [self->bcc isNotNull] ? self->bcc : [NSArray array];
+
+- (NSString *) text
+{
+  return text;
 }
 
-- (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;
+- (void) setTo: (NSArray *) newTo
+{
+  if ([newTo isKindOfClass: [NSNull class]])
+    newTo = nil;
+
+  ASSIGN (to, newTo);
 }
 
-- (void)setAttachmentName:(NSString *)_attachmentName {
-  ASSIGN(self->attachmentName, _attachmentName);
+- (NSArray *) to
+{
+  return to;
 }
-- (NSString *)attachmentName {
-  return self->attachmentName;
+
+- (void) setCc: (NSArray *) newCc
+{
+  if ([newCc isKindOfClass: [NSNull class]])
+    newCc = nil;
+
+  ASSIGN (cc, newCc);
 }
 
-/* from addresses */
+- (NSArray *) cc
+{
+  return cc;
+}
 
-- (NSArray *)fromEMails {
-  NSString *primary, *uid;
-  NSArray  *shares;
-  
-  if (self->fromEMails != nil) 
-    return self->fromEMails;
-  
-  uid     = [[self user] login];
-  primary = [[[self context] activeUser] email];
-  if (![[self context] isAccessFromIntranet]) {
-    self->fromEMails = [[NSArray alloc] initWithObjects:&primary count:1];
-    return self->fromEMails;
-  }
-  
-  shares = 
-    [[[self context] activeUser] valueForKey:@"additionalEMailAddresses"];
-  if ([shares count] == 0)
-    self->fromEMails = [[NSArray alloc] initWithObjects:&primary count:1];
-  else {
-    id tmp;
+- (void) setBcc: (NSArray *) newBcc
+{
+  if ([newBcc isKindOfClass: [NSNull class]])
+    newBcc = nil;
 
-    tmp = [[NSArray alloc] initWithObjects:&primary count:1];
-    self->fromEMails = [[tmp arrayByAddingObjectsFromArray:shares] copy];
-    [tmp release]; tmp = nil;
-  }
-  return self->fromEMails;
+  ASSIGN (bcc, newBcc);
 }
 
-/* title */
+- (NSArray *) bcc
+{
+  return bcc;
+}
 
-- (NSString *)panelTitle {
-  return [self labelForKey:@"Compose Mail"];
+- (BOOL) hasOneOrMoreRecipients
+{
+  return (([to count] + [cc count] + [bcc count]) > 0);
 }
 
-/* detect webmail being accessed from the outside */
+- (void) setAttachmentName: (NSString *) newAttachmentName
+{
+  ASSIGN (attachmentName, newAttachmentName);
+}
 
-- (BOOL)isInternetRequest {
-  // DEPRECATED
-  return [[self context] isAccessFromIntranet] ? NO : YES;
+- (NSString *) attachmentName
+{
+  return attachmentName;
 }
 
-- (BOOL)showInternetMarker {
-  if (!showInternetMarker)
-    return NO;
-  return [[self context] isAccessFromIntranet] ? NO : YES;
+/* from addresses */
+
+- (NSArray *) fromEMails
+{
+  NSArray *allIdentities;
+
+  if (!fromEMails)
+    { 
+      allIdentities = [[context activeUser] allIdentities];
+      fromEMails = [allIdentities keysWithFormat: @"%{fullName} <%{email}>"];
+      [fromEMails retain];
+    }
+
+  return fromEMails;
 }
 
 /* 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 (self->sentFolder != nil)
-    return [self->sentFolder isNotNull] ? self->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 ((self->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 ([self->from isNotEmpty]) /* a from is already set */
-    return;
-
-  accountID = [[[self context] request] formValueForKey:@"account"];
-  if (![accountID isNotEmpty])
-    return;
+/* actions */
 
-  accounts = [[self clientObject] mailAccountsFolder];
-  if ([accounts isExceptionOrNull])
-    return; /* we don't treat this as an error but are tolerant */
+- (NSDictionary *) _scanAttachmentFilenamesInRequest: (id) httpBody
+{
+  NSMutableDictionary *filenames;
+  NSDictionary *attachment;
+  NSArray *parts;
+  unsigned int count, max;
+  NGMimeBodyPart *part;
+  NGMimeContentDispositionHeaderField *header;
+  NSString *mimeType;
+
+  parts = [httpBody parts];
+  max = [parts count];
+  filenames = [NSMutableDictionary dictionaryWithCapacity: max];
+
+  for (count = 0; count < max; count++)
+    {
+      part = [parts objectAtIndex: count];
+      header = (NGMimeContentDispositionHeaderField *) [part headerForKey: @"content-disposition"];
+      mimeType = [(NGMimeType *) [part headerForKey: @"content-type"] stringValue];
+      attachment = [NSDictionary dictionaryWithObjectsAndKeys:
+                                  [header filename], @"filename",
+                                mimeType, @"mime-type", nil];
+      [filenames setObject: attachment
+                forKey: [header name]];
+    }
 
-  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]];
+  return filenames;
 }
 
-- (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 (self->sentFolder != nil)
-    return [self->sentFolder isNotNull] ? self->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]];
-  
-  self->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:    %@", self->sentFolder];
-#endif
-  return self->sentFolder;
-}
+- (BOOL) _saveAttachments
+{
+  WORequest *request;
+  NSEnumerator *allKeys;
+  NSString *key;
+  BOOL success;
+  NSDictionary *filenames;
+  id httpBody;
+  SOGoDraftObject *co;
+
+  success = YES;
+  request = [context request];
+
+  httpBody = [[request httpRequest] body];
+  filenames = [self _scanAttachmentFilenamesInRequest: httpBody];
+
+  co = [self clientObject];
+  allKeys = [[request formValueKeys] objectEnumerator];
+  key = [allKeys nextObject];
+  while (key && success)
+    {
+      if ([key hasPrefix: @"attachment"])
+       success
+         = (![co saveAttachment: (NSData *) [request formValueForKey: key]
+                 withMetadata: [filenames objectForKey: key]]);
+      key = [allKeys nextObject];
+    }
 
-- (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;
+  return success;
 }
 
-/* actions */
-
-- (BOOL)_saveFormInfo {
+- (BOOL) _saveFormInfo
+{
   NSDictionary *info;
-  
-  if ((info = [self storeInfo]) != nil) {
-    NSException *error;
-    
-    if ((error = [[self clientObject] storeInfo:info]) != nil) {
-      [self errorWithFormat:@"failed to store draft: %@", error];
-      // TODO: improve error handling
-      return NO;
+  NSException *error;
+  BOOL success;
+  SOGoDraftObject *co;
+
+  co = [self clientObject];
+  [co fetchInfo];
+
+  success = YES;
+
+  if ([self _saveAttachments])
+    {
+      info = [self storeInfo];
+      [co setHeaders: info];
+      [co setText: text];
+      error = [co storeInfo];
+      if (error)
+       {
+         [self errorWithFormat: @"failed to store draft: %@", error];
+         // TODO: improve error handling
+         success = NO;
+       }
     }
-  }
-  
+  else
+    success = NO;
+
   // TODO: wrap content
   
-  return YES;
+  return success;
 }
-- (id)failedToSaveFormResponse {
+
+- (id) failedToSaveFormResponse
+{
   // TODO: improve error handling
   return [NSException exceptionWithHTTPStatus:500 /* server error */
                      reason:@"failed to store draft object on server!"];
@@ -430,158 +365,90 @@ static NSArray      *infoKeys            = nil;
 
 /* attachment helper */
 
-- (NSArray *)attachmentNames {
+- (NSArray *) attachmentNames
+{
   NSArray *a;
-  
-  if (self->attachmentNames != nil)
-    return self->attachmentNames;
-  
+
+  if (attachmentNames != nil)
+    return attachmentNames;
+
   a = [[self clientObject] fetchAttachmentNames];
-  a = [a sortedArrayUsingSelector:@selector(compare:)];
-  self->attachmentNames = [a copy];
-  return self->attachmentNames;
-}
-- (BOOL)hasAttachments {
-  return [[self attachmentNames] count] > 0 ? YES : NO;
-}
+  a = [a sortedArrayUsingSelector: @selector (compare:)];
+  attachmentNames = [a copy];
 
-- (NSString *)initialLeftsideStyle {
-  if ([self hasAttachments])
-    return @"width: 67%";
-  return @"width: 100%";
+  return attachmentNames;
 }
 
-- (NSString *)initialRightsideStyle {
-  if ([self hasAttachments])
-    return @"display: block";
-  return @"display: none";
+- (BOOL) hasAttachments
+{
+  return [[self attachmentNames] count] > 0 ? YES : NO;
 }
 
-- (id)defaultAction {
-  return [self redirectToLocation:@"edit"];
-}
+- (id) defaultAction
+{
+  SOGoDraftObject *co;
+
+  co = [self clientObject];
+  [co fetchInfo];
+  [self loadInfo: [co headers]];
+  [self setText: [co text]];
 
-- (id)editAction {
-#if 0
-  [self logWithFormat:@"edit action, load content from: %@",
-         [self clientObject]];
-#endif
-  
-  [self loadInfo:[[self clientObject] fetchInfo]];
-  [self _presetFromBasedOnAccountsQueryParameter];
   return self;
 }
 
-- (id)saveAction {
-  return [self _saveFormInfo] ? self : [self failedToSaveFormResponse];
+- (id <WOActionResults>) saveAction
+{
+  id result;
+
+  if ([self _saveFormInfo])
+    {
+      result = [[self clientObject] save];
+      if (!result)
+       result = [self responseWith204];
+    }
+  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 *) validateForSend
+{
+  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 <WOActionResults>) sendAction
 {
-  NSException  *error;
-  NSString     *mailPath;
-  NSDictionary *h;
   id <WOActionResults> 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 */
-  
-  h = [[self context] isAccessFromIntranet] ? nil : internetMailHeaders;
-  
-  /* 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:h];
-
-  /* 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: @"refreshFolderByType(\"sent\")"];
+       }
+      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 */