]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailEditor.m
Install libs to /usr/lib
[scalable-opengroupware.org] / 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 #import <Foundation/NSFileManager.h>
23 #import <Foundation/NSKeyValueCoding.h>
24 #import <Foundation/NSString.h>
25 #import <Foundation/NSUserDefaults.h>
26
27 #import <NGObjWeb/NSException+HTTP.h>
28 #import <NGObjWeb/SoSubContext.h>
29 #import <NGObjWeb/WORequest.h>
30 #import <NGObjWeb/WOResponse.h>
31 #import <NGExtensions/NSNull+misc.h>
32 #import <NGExtensions/NSObject+Logs.h>
33 #import <NGExtensions/NSString+misc.h>
34 #import <NGExtensions/NSException+misc.h>
35 #import <NGMail/NGMimeMessage.h>
36 #import <NGMail/NGMimeMessageGenerator.h>
37 #import <NGMime/NGMimeBodyPart.h>
38 #import <NGMime/NGMimeHeaderFields.h>
39 #import <NGMime/NGMimeMultipartBody.h>
40
41 #import <SoObjects/Mailer/SOGoDraftObject.h>
42 #import <SoObjects/Mailer/SOGoMailFolder.h>
43 #import <SoObjects/Mailer/SOGoMailAccount.h>
44 #import <SoObjects/Mailer/SOGoMailAccounts.h>
45 #import <SoObjects/SOGo/SOGoUser.h>
46 #import <SoObjects/SOGo/NSArray+Utilities.h>
47 #import <SoObjects/SOGo/NSDictionary+Utilities.h>
48 #import <SOGoUI/UIxComponent.h>
49
50 /*
51   UIxMailEditor
52   
53   An mail editor component which works on SOGoDraftObject's.
54 */
55
56 @interface UIxMailEditor : UIxComponent
57 {
58   NSArray  *to;
59   NSArray  *cc;
60   NSArray  *bcc;
61   NSString *subject;
62   NSString *text;
63   NSArray *fromEMails;
64   NSString *from;
65   SOGoMailFolder *sentFolder;
66
67   /* these are for the inline attachment list */
68   NSString *attachmentName;
69   NSArray  *attachmentNames;
70   NSMutableArray *attachedFiles;
71 }
72
73 @end
74
75 @implementation UIxMailEditor
76
77 static BOOL showInternetMarker = NO;
78 static BOOL useLocationBasedSentFolder = NO;
79 static NSDictionary *internetMailHeaders = nil;
80 static NSArray *infoKeys = nil;
81
82 + (void) initialize
83 {
84   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
85   
86   infoKeys = [[NSArray alloc] initWithObjects:
87                                 @"subject", @"to", @"cc", @"bcc", 
88                               @"from", @"replyTo", @"inReplyTo",
89                               nil];
90   
91   useLocationBasedSentFolder =
92     [ud boolForKey:@"SOGoUseLocationBasedSentFolder"];
93   
94   /* Internet mail settings */
95   
96   showInternetMarker = [ud boolForKey:@"SOGoShowInternetMarker"];
97   if (!showInternetMarker)
98     NSLog(@"Note: visual Internet marker on mail editor disabled "
99           @"(SOGoShowInternetMarker)");
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 {
109   [sentFolder release];
110   [fromEMails release];
111   [from release];
112   [text release];
113   [subject release];
114   [to release];
115   [cc release];
116   [bcc release];
117   [attachmentName release];
118   [attachmentNames release];
119   [attachedFiles release];
120   [super dealloc];
121 }
122
123 /* accessors */
124
125 - (void) setFrom: (NSString *) newFrom
126 {
127   ASSIGN (from, newFrom);
128 }
129
130 - (NSString *) from
131 {
132   NSDictionary *identity;
133
134   if (!from)
135     {
136       identity = [[context activeUser] primaryIdentity];
137       from = [identity keysWithFormat: @"%{fullName} <%{email}>"];
138     }
139
140   return from;
141 }
142
143 // - (void) setReplyTo: (NSString *) ignore
144 // {
145 // }
146
147 // - (NSString *) replyTo
148 // {
149 //   /* we are here for future extensibility */
150 //   return @"";
151 // }
152
153 - (void) setSubject: (NSString *) newSubject
154 {
155   ASSIGN (subject, newSubject);
156 }
157
158 - (NSString *) subject
159 {
160   return subject;
161 }
162
163 - (void) setText: (NSString *) newText
164 {
165   ASSIGN (text, newText);
166 }
167
168 - (NSString *) text
169 {
170   return text;
171 }
172
173 - (void) setTo: (NSArray *) newTo
174 {
175   if ([newTo isKindOfClass: [NSNull class]])
176     newTo = nil;
177
178   ASSIGN (to, newTo);
179 }
180
181 - (NSArray *) to
182 {
183   return to;
184 }
185
186 - (void) setCc: (NSArray *) newCc
187 {
188   if ([newCc isKindOfClass: [NSNull class]])
189     newCc = nil;
190
191   ASSIGN (cc, newCc);
192 }
193
194 - (NSArray *) cc
195 {
196   return cc;
197 }
198
199 - (void) setBcc: (NSArray *) newBcc
200 {
201   if ([newBcc isKindOfClass: [NSNull class]])
202     newBcc = nil;
203
204   ASSIGN (bcc, newBcc);
205 }
206
207 - (NSArray *) bcc
208 {
209   return bcc;
210 }
211
212 - (BOOL) hasOneOrMoreRecipients
213 {
214   return (([to count] + [cc count] + [bcc count]) > 0);
215 }
216
217 - (void) setAttachmentName: (NSString *) newAttachmentName
218 {
219   ASSIGN (attachmentName, newAttachmentName);
220 }
221
222 - (NSString *) attachmentName
223 {
224   return attachmentName;
225 }
226
227 /* from addresses */
228
229 - (NSArray *) fromEMails
230 {
231   NSArray *allIdentities;
232
233   if (!fromEMails)
234     { 
235       allIdentities = [[context activeUser] allIdentities];
236       fromEMails = [allIdentities keysWithFormat: @"%{fullName} <%{email}>"];
237       [fromEMails retain];
238     }
239
240   return fromEMails;
241 }
242
243 /* info loading */
244
245 - (void) loadInfo: (NSDictionary *) _info
246 {
247   if (![_info isNotNull]) return;
248   [self debugWithFormat:@"loading info ..."];
249   [self takeValuesFromDictionary:_info];
250 }
251
252 - (NSDictionary *) storeInfo
253 {
254   [self debugWithFormat:@"storing info ..."];
255   return [self valuesForKeys:infoKeys];
256 }
257
258 /* requests */
259
260 - (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
261                            inContext: (WOContext*) localContext
262 {
263   return YES;
264 }
265
266 /* actions */
267 - (NSString *) _fixedFilename: (NSString *) filename
268 {
269   NSString *newFilename, *baseFilename, *extension;
270   unsigned int variation;
271
272   if (!attachedFiles)
273     attachedFiles = [NSMutableArray new];
274
275   newFilename = filename;
276
277   baseFilename = [filename stringByDeletingPathExtension];
278   extension = [filename pathExtension];
279   variation = 0;
280   while ([attachedFiles containsObject: newFilename])
281     {
282       variation++;
283       newFilename = [NSString stringWithFormat: @"%@-%d.%@", baseFilename,
284                               variation, extension];
285     }
286   [attachedFiles addObject: newFilename];
287
288   return newFilename;
289 }
290
291 - (NSDictionary *) _scanAttachmentFilenamesInRequest: (id) httpBody
292 {
293   NSMutableDictionary *filenames;
294   NSDictionary *attachment;
295   NSArray *parts;
296   unsigned int count, max;
297   NGMimeBodyPart *part;
298   NGMimeContentDispositionHeaderField *header;
299   NSString *mimeType, *filename;
300
301   parts = [httpBody parts];
302   max = [parts count];
303   filenames = [NSMutableDictionary dictionaryWithCapacity: max];
304
305   for (count = 0; count < max; count++)
306     {
307       part = [parts objectAtIndex: count];
308       header = (NGMimeContentDispositionHeaderField *)
309         [part headerForKey: @"content-disposition"];
310       mimeType = [(NGMimeType *)
311                    [part headerForKey: @"content-type"] stringValue];
312       filename = [self _fixedFilename: [header filename]];
313       attachment = [NSDictionary dictionaryWithObjectsAndKeys:
314                                    filename, @"filename",
315                                  mimeType, @"mimetype", nil];
316       [filenames setObject: attachment forKey: [header name]];
317     }
318
319   return filenames;
320 }
321
322 - (BOOL) _saveAttachments
323 {
324   WORequest *request;
325   NSEnumerator *allKeys;
326   NSString *key;
327   BOOL success;
328   NSDictionary *filenames;
329   id httpBody;
330   SOGoDraftObject *co;
331
332   success = YES;
333   request = [context request];
334
335   httpBody = [[request httpRequest] body];
336   filenames = [self _scanAttachmentFilenamesInRequest: httpBody];
337
338   co = [self clientObject];
339   allKeys = [[request formValueKeys] objectEnumerator];
340   key = [allKeys nextObject];
341   while (key && success)
342     {
343       if ([key hasPrefix: @"attachment"])
344         success
345           = (![co saveAttachment: (NSData *) [request formValueForKey: key]
346                   withMetadata: [filenames objectForKey: key]]);
347       key = [allKeys nextObject];
348     }
349
350   return success;
351 }
352
353 - (BOOL) _saveFormInfo
354 {
355   NSDictionary *info;
356   NSException *error;
357   BOOL success;
358   SOGoDraftObject *co;
359
360   co = [self clientObject];
361   [co fetchInfo];
362
363   success = YES;
364
365   if ([self _saveAttachments])
366     {
367       info = [self storeInfo];
368       [co setHeaders: info];
369       [co setText: text];
370       error = [co storeInfo];
371       if (error)
372         {
373           [self errorWithFormat: @"failed to store draft: %@", error];
374           // TODO: improve error handling
375           success = NO;
376         }
377     }
378   else
379     success = NO;
380
381   // TODO: wrap content
382   
383   return success;
384 }
385
386 - (id) failedToSaveFormResponse
387 {
388   // TODO: improve error handling
389   return [NSException exceptionWithHTTPStatus:500 /* server error */
390                       reason:@"failed to store draft object on server!"];
391 }
392
393 /* attachment helper */
394
395 - (NSArray *) attachmentNames
396 {
397   NSArray *a;
398
399   if (!attachmentNames)
400     {
401       a = [[self clientObject] fetchAttachmentNames];
402       ASSIGN (attachmentNames,
403               [a sortedArrayUsingSelector: @selector (compare:)]);
404     }
405
406   return attachmentNames;
407 }
408
409 - (BOOL) hasAttachments
410 {
411   return [[self attachmentNames] count] > 0 ? YES : NO;
412 }
413
414 - (id) defaultAction
415 {
416   SOGoDraftObject *co;
417
418   co = [self clientObject];
419   [co fetchInfo];
420   [self loadInfo: [co headers]];
421   [self setText: [co text]];
422
423   return self;
424 }
425
426 - (id <WOActionResults>) saveAction
427 {
428   id result;
429
430   if ([self _saveFormInfo])
431     {
432       result = [[self clientObject] save];
433       if (!result)
434         result = [self responseWith204];
435     }
436   else
437     result = [self failedToSaveFormResponse];
438
439   return result;
440 }
441
442 - (NSException *) validateForSend
443 {
444   NSException *error;
445
446   if (![self hasOneOrMoreRecipients])
447     error = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
448                          reason: @"Please select a recipient!"];
449   else if ([[self subject] length] == 0)
450     error = [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
451                          reason: @"Please set a subject!"];
452   else
453     error = nil;
454   
455   return error;
456 }
457
458 - (id <WOActionResults>) sendAction
459 {
460   id <WOActionResults> result;
461
462   // TODO: need to validate whether we have a To etc
463   
464   /* first, save form data */
465   result = [self validateForSend];
466   if (!result)
467     {
468       if ([self _saveFormInfo])
469         {
470           result = [[self clientObject] sendMail];
471           if (!result)
472             result = [self jsCloseWithRefreshMethod: @"refreshCurrentFolder()"];
473         }
474       else
475         result = [self failedToSaveFormResponse];
476     }
477
478   return result;
479 }
480
481 @end /* UIxMailEditor */