]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/MailerUI/UIxMailEditor.m
perform some validation prior trying to send mail (#1451)
[scalable-opengroupware.org] / SOGo / UI / MailerUI / UIxMailEditor.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <SOGoUI/UIxComponent.h>
23
24 /*
25   UIxMailEditor
26   
27   An mail editor component which works on SOGoDraftObject's.
28 */
29
30 @class NSArray, NSString;
31 @class SOGoMailFolder;
32
33 @interface UIxMailEditor : UIxComponent
34 {
35   NSArray  *to;
36   NSArray  *cc;
37   NSArray  *bcc;
38   NSString *subject;
39   NSString *text;
40   NSArray  *fromEMails;
41   NSString *from;
42   SOGoMailFolder *sentFolder;
43 }
44
45 @end
46
47 #include <SoObjects/Mailer/SOGoDraftObject.h>
48 #include <SoObjects/Mailer/SOGoMailFolder.h>
49 #include <SoObjects/Mailer/SOGoMailAccount.h>
50 #include <SoObjects/SOGo/WOContext+Agenor.h>
51 #include <NGMail/NGMimeMessage.h>
52 #include <NGMail/NGMimeMessageGenerator.h>
53 #include "common.h"
54
55 @interface UIxComponent (Scheduler_Privates)
56 - (NSString *)emailForUser;
57 @end
58
59 @implementation UIxMailEditor
60
61 static BOOL         keepMailTmpFile    = NO;
62 static BOOL         showInternetMarker = NO;
63 static EOQualifier  *internetDetectQualifier = nil;
64 static NSDictionary *internetMailHeaders     = nil;
65 static NSArray      *infoKeys = nil;
66
67 + (void)initialize {
68   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
69   NSString *s;
70   
71   infoKeys = [[NSArray alloc] initWithObjects:
72                                 @"subject", @"text", @"to", @"cc", @"bcc", 
73                                 @"from", @"replyTo",
74                               nil];
75   
76   keepMailTmpFile = [ud boolForKey:@"SOGoMailEditorKeepTmpFile"];
77   if (keepMailTmpFile)
78     NSLog(@"WARNING: keeping mail files.");
79
80   /* Internet mail settings */
81   
82   showInternetMarker = [ud boolForKey:@"SOGoShowInternetMarker"];
83   if (!showInternetMarker) {
84     NSLog(@"Note: visual Internet marker on mail editor disabled "
85           @"(SOGoShowInternetMarker)");
86   }
87   
88   if ((s = [ud stringForKey:@"SOGoInternetDetectQualifier"]) != nil) {
89     internetDetectQualifier = 
90       [[EOQualifier qualifierWithQualifierFormat:s] retain];
91     if (internetDetectQualifier == nil)
92       NSLog(@"UIxMailEditor: could not parse qualifier: '%@'", s);
93   }
94   if (internetDetectQualifier == nil)
95     NSLog(@"UIxMailEditor: no 'SOGoInternetDetectQualifier' configured.");
96   else {
97     NSLog(@"UIxMailEditor: detect Internet access using: %@", 
98           internetDetectQualifier);
99   }
100   
101   internetMailHeaders = 
102     [[ud dictionaryForKey:@"SOGoInternetMailHeaders"] copy];
103   NSLog(@"Note: specified %d headers for mails send via the Internet.", 
104         [internetMailHeaders count]);
105 }
106
107 - (void)dealloc {
108   [self->sentFolder release];
109   [self->fromEMails release];
110   [self->from    release];
111   [self->text    release];
112   [self->subject release];
113   [self->to      release];
114   [self->cc      release];
115   [self->bcc     release];
116   [super dealloc];
117 }
118
119 /* accessors */
120
121 - (void)setFrom:(NSString *)_value {
122   ASSIGNCOPY(self->from, _value);
123 }
124 - (NSString *)from {
125   if (![self->from isNotNull])
126     return [self emailForUser];
127   return self->from;
128 }
129
130 - (void)setReplyTo:(NSString *)_ignore {
131 }
132 - (NSString *)replyTo {
133   /* we are here for future extensibility */
134   return @"";
135 }
136
137 - (void)setSubject:(NSString *)_value {
138   ASSIGNCOPY(self->subject, _value);
139 }
140 - (NSString *)subject {
141   return self->subject ? self->subject : @"";
142 }
143
144 - (void)setText:(NSString *)_value {
145   ASSIGNCOPY(self->text, _value);
146 }
147 - (NSString *)text {
148   return [self->text isNotNull] ? self->text : @"";
149 }
150
151 - (void)setTo:(NSArray *)_value {
152   ASSIGNCOPY(self->to, _value);
153 }
154 - (NSArray *)to {
155   return [self->to isNotNull] ? self->to : [NSArray array];
156 }
157
158 - (void)setCc:(NSArray *)_value {
159   ASSIGNCOPY(self->cc, _value);
160 }
161 - (NSArray *)cc {
162   return [self->cc isNotNull] ? self->cc : [NSArray array];
163 }
164
165 - (void)setBcc:(NSArray *)_value {
166   ASSIGNCOPY(self->bcc, _value);
167 }
168 - (NSArray *)bcc {
169   return [self->bcc isNotNull] ? self->bcc : [NSArray array];
170 }
171
172 - (BOOL)hasOneOrMoreRecipients {
173   if ([[self to]  count] > 0) return YES;
174   if ([[self cc]  count] > 0) return YES;
175   if ([[self bcc] count] > 0) return YES;
176   return NO;
177 }
178
179 /* from addresses */
180
181 - (NSArray *)fromEMails {
182   NSString *primary, *uid;
183   NSArray  *shares;
184   
185   if (self->fromEMails != nil) 
186     return self->fromEMails;
187   
188   uid     = [[self user] login];
189   primary = [self emailForUser];
190   if (![[self context] isAccessFromIntranet]) {
191     self->fromEMails = [[NSArray alloc] initWithObjects:&primary count:1];
192     return self->fromEMails;
193   }
194   
195   shares = 
196     [[[self context] activeUser] valueForKey:@"additionalEMailAddresses"];
197   if ([shares count] == 0)
198     self->fromEMails = [[NSArray alloc] initWithObjects:&primary count:1];
199   else {
200     id tmp;
201
202     tmp = [[NSArray alloc] initWithObjects:&primary count:1];
203     self->fromEMails = [[tmp arrayByAddingObjectsFromArray:shares] copy];
204     [tmp release]; tmp = nil;
205   }
206   return self->fromEMails;
207 }
208
209 /* title */
210
211 - (NSString *)panelTitle {
212   return [self labelForKey:@"Compose Mail"];
213 }
214
215 /* detect webmail being accessed from the outside */
216
217 - (BOOL)isInternetRequest {
218   return [[self context] isAccessFromIntranet] ? NO : YES;
219 }
220
221 - (BOOL)showInternetMarker {
222   if (!showInternetMarker)
223     return NO;
224   return [self isInternetRequest];
225 }
226
227 /* info loading */
228
229 - (void)loadInfo:(NSDictionary *)_info {
230   if (![_info isNotNull]) return;
231   [self debugWithFormat:@"loading info ..."];
232   [self takeValuesFromDictionary:_info];
233 }
234 - (NSDictionary *)storeInfo {
235   [self debugWithFormat:@"storing info ..."];
236   return [self valuesForKeys:infoKeys];
237 }
238
239 /* requests */
240
241 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
242   return YES;
243 }
244
245 /* IMAP4 store */
246
247 - (NSException *)patchFlagsInStore {
248   /*
249     Flags we should set:
250       if the draft is a reply   => [message markAnswered]
251       if the draft is a forward => [message addFlag:@"forwarded"]
252       
253     This is hard, we would need to find the original message in Cyrus.
254   */
255   return nil;
256 }
257
258 - (id)lookupSentFolder {
259   SOGoMailAccount *account;
260   SOGoMailFolder  *folder;
261   
262   if (self->sentFolder != nil)
263     return self;
264   
265   account = [[self clientObject] mailAccountFolder];
266   if ([account isKindOfClass:[NSException class]]) return account;
267   
268   folder = [account sentFolderInContext:[self context]];
269   if ([folder isKindOfClass:[NSException class]]) return folder;
270   return ((self->sentFolder = [folder retain]));
271 }
272
273 - (NSException *)storeMailInSentFolder:(NSString *)_path {
274   SOGoMailFolder *folder;
275   NSData *data;
276   id result;
277   
278   folder = [self lookupSentFolder];
279   if ([folder isKindOfClass:[NSException class]]) return (id)folder;
280   
281   if ((data = [[NSData alloc] initWithContentsOfMappedFile:_path]) == nil) {
282     return [NSException exceptionWithHTTPStatus:500 /* server error */
283                         reason:@"could not temporary draft file!"];
284   }
285   
286   result = [folder postData:data flags:@"seen"];
287   [data release]; data = nil;
288   return result;
289 }
290
291 /* actions */
292
293 - (BOOL)_saveFormInfo {
294   NSDictionary *info;
295   
296   if ((info = [self storeInfo]) != nil) {
297     NSException *error;
298     
299     if ((error = [[self clientObject] storeInfo:info]) != nil) {
300       [self errorWithFormat:@"failed to store draft: %@", error];
301       // TODO: improve error handling
302       return NO;
303     }
304   }
305   
306   // TODO: wrap content
307   
308   return YES;
309 }
310 - (id)failedToSaveFormResponse {
311   // TODO: improve error handling
312   return [NSException exceptionWithHTTPStatus:500 /* server error */
313                       reason:@"failed to store draft object on server!"];
314 }
315
316 - (id)defaultAction {
317   return [self redirectToLocation:@"edit"];
318 }
319
320 - (id)editAction {
321 #if 0
322   [self logWithFormat:@"edit action, load content from: %@",
323           [self clientObject]];
324 #endif
325   
326   [self loadInfo:[[self clientObject] fetchInfo]];
327   return self;
328 }
329
330 - (id)saveAction {
331   return [self _saveFormInfo] ? self : [self failedToSaveFormResponse];
332 }
333
334 - (NSException *)validateForSend {
335   // TODO: localize errors
336   
337   if (![self hasOneOrMoreRecipients]) {
338     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
339                         reason:@"Please select a recipient!"];
340   }
341   if ([[self subject] length] == 0) {
342     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
343                         reason:@"Please set a subject!"];
344   }
345   
346   return nil;
347 }
348
349 - (id)sendAction {
350   NSException  *error;
351   NSString     *mailPath;
352   NSDictionary *h;
353   
354   // TODO: need to validate whether we have a To etc
355   
356   /* first, save form data */
357   
358   if (![self _saveFormInfo])
359     return [self failedToSaveFormResponse];
360   
361   /* validate for send */
362   
363   if ((error = [self validateForSend]) != nil) {
364     id url;
365     
366     url = [[error reason] stringByEscapingURL];
367     url = [@"edit?error=" stringByAppendingString:url];
368     return [self redirectToLocation:url];
369   }
370   
371   /* setup some extra headers if required */
372   
373   h = [self isInternetRequest] ? internetMailHeaders : nil;
374   
375   /* save mail to file (so that we can upload the mail to Cyrus) */
376   // TODO: all this could be handled by the SOGoDraftObject?
377   
378   mailPath = [[self clientObject] saveMimeMessageToTemporaryFileWithHeaders:h];
379   
380   /* then, send mail */
381   
382   if ((error = [[self clientObject] sendMimeMessageAtPath:mailPath]) != nil) {
383     // TODO: improve error handling
384     [[NSFileManager defaultManager] removeFileAtPath:mailPath handler:nil];
385     return error;
386   }
387   
388   /* patch flags in store for replies etc */
389   
390   if ((error = [self patchFlagsInStore]) != nil)
391      return error;
392   
393   /* finally store in Sent */
394
395   if ((error = [self storeMailInSentFolder:mailPath]) != nil)
396     return error;
397   
398   /* delete temporary mail file */
399   
400   if (keepMailTmpFile)
401     [self warnWithFormat:@"keeping mail file: '%@'", mailPath];
402   else
403     [[NSFileManager defaultManager] removeFileAtPath:mailPath handler:nil];
404   mailPath = nil;
405   
406   /* delete draft */
407   
408   if ((error = [[self clientObject] delete]) != nil)
409     return error;
410
411   // if everything is ok, close the window (send a JS closing the Window)
412   return [self pageWithName:@"UIxMailWindowCloser"];
413 }
414
415 - (id)deleteAction {
416   NSException *error;
417   id page;
418   
419   if ((error = [[self clientObject] delete]) != nil)
420     return error;
421   
422 #if 1
423   page = [self pageWithName:@"UIxMailWindowCloser"];
424   [page takeValue:@"YES" forKey:@"refreshOpener"];
425   return page;
426 #else
427   // TODO: if we just return nil, we produce a 500
428   return [NSException exceptionWithHTTPStatus:204 /* No Content */
429                       reason:@"object was deleted."];
430 #endif
431 }
432
433 @end /* UIxMailEditor */