]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailListView.m
7c6a855bbb48cdc6921c159c14fb01204b266915
[scalable-opengroupware.org] / UI / MailerUI / UIxMailListView.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 /*
23   UIxMailListView
24   
25   This component represent a list of mails and is attached to an SOGoMailFolder
26   object.
27 */
28
29 #import <Foundation/NSCalendarDate.h>
30 #import <Foundation/NSDictionary.h>
31 #import <Foundation/NSValue.h>
32 #import <NGObjWeb/WOResponse.h>
33 #import <NGObjWeb/WORequest.h>
34 #import <NGObjWeb/SoObject+SoDAV.h>
35 #import <NGObjWeb/NSException+HTTP.h>
36 #import <NGExtensions/NSNull+misc.h>
37 #import <NGExtensions/NSString+misc.h>
38
39 #import <EOControl/EOQualifier.h>
40
41 #import <SoObjects/Mailer/SOGoMailFolder.h>
42 #import <SoObjects/Mailer/SOGoMailObject.h>
43 #import <SoObjects/SOGo/SOGoDateFormatter.h>
44 #import <SoObjects/SOGo/SOGoUser.h>
45
46 #import "UIxMailListView.h"
47
48 #define messagesPerPage 50
49 static int attachmentFlagSize = 8096;
50
51 @implementation UIxMailListView
52
53 - (id) init
54 {
55   SOGoUser *user;
56
57   if ((self = [super init]))
58     {
59       qualifier = nil;
60       user = [context activeUser];
61       ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
62       ASSIGN (userTimeZone, [user timeZone]);
63     }
64
65   return self;
66 }
67
68 - (void) dealloc 
69 {
70   [qualifier release];
71   [sortedUIDs release];
72   [messages release];
73   [message release];
74   [dateFormatter release];
75   [userTimeZone release];
76   [super dealloc];
77 }
78
79 /* accessors */
80
81 - (void) setMessage: (id) _msg
82 {
83   ASSIGN(message, _msg);
84 }
85
86 - (id) message 
87 {
88   return message;
89 }
90
91 - (NSString *) messageDate
92 {
93   NSCalendarDate *messageDate;
94
95   messageDate = [[message valueForKey: @"envelope"] date];
96   [messageDate setTimeZone: userTimeZone];
97
98   return [dateFormatter formattedDateAndTime: messageDate];
99 }
100
101 - (NSString *) messageSubject
102 {
103   NSString *subject;
104   id envSubject;
105
106   envSubject = [[message valueForKey: @"envelope"] subject];
107   if ([envSubject isKindOfClass: [NSData class]])
108     {
109       subject = [[NSString alloc] initWithData: envSubject
110                                   encoding: NSUTF8StringEncoding];
111       [subject autorelease];
112     }
113   else
114     subject = envSubject;
115
116   return subject;
117 }
118
119 - (BOOL) showToAddress 
120 {
121   NSString *ftype;
122   
123   ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
124   return [ftype isEqual:@"IPF.Sent"];
125 }
126
127 /* title */
128
129 - (NSString *) objectTitle 
130 {
131   return [[self clientObject] nameInContainer];
132 }
133
134 - (NSString *) panelTitle 
135 {
136   NSString *s;
137   
138   s = [self labelForKey:@"View Mail Folder"];
139   s = [s stringByAppendingString:@": "];
140   s = [s stringByAppendingString:[self objectTitle]];
141   return s;
142 }
143
144 /* derived accessors */
145
146 - (BOOL) isMessageDeleted 
147 {
148   NSArray *flags;
149   
150   flags = [[self message] valueForKey:@"flags"];
151   return [flags containsObject:@"deleted"];
152 }
153
154 - (BOOL) isMessageRead 
155 {
156   NSArray *flags;
157   
158   flags = [[self message] valueForKey:@"flags"];
159   return [flags containsObject:@"seen"];
160 }
161
162 - (NSString *) messageUidString 
163 {
164   return [[[self message] valueForKey:@"uid"] stringValue];
165 }
166
167 - (NSString *) messageRowStyleClass 
168 {
169   return [self isMessageDeleted]
170     ? @"mailer_listcell_deleted"
171     : @"mailer_listcell_regular";
172 }
173
174 - (NSString *) messageSubjectCellStyleClass 
175 {
176   NSArray *flags;
177   NSString *cellClass;
178
179   flags = [[self message] valueForKey:@"flags"];
180
181   if ([flags containsObject: @"seen"])
182     {
183       if ([flags containsObject: @"answered"])
184         {
185           if ([flags containsObject: @"$forwarded"])
186             cellClass = @"mailer_forwardedrepliedmailsubject";
187           else
188             cellClass = @"mailer_repliedmailsubject";
189         }
190       else if ([flags containsObject: @"$forwarded"])
191         cellClass = @"mailer_forwardedmailsubject";
192       else
193         cellClass = @"mailer_readmailsubject";
194     }
195   else
196     cellClass = @"mailer_unreadmailsubject";
197
198   return cellClass;
199 //   return ([self isMessageRead]
200 //        ? @"mailer_readmailsubject"
201 //        : @"mailer_unreadmailsubject");
202 }
203
204 - (BOOL) hasMessageAttachment 
205 {
206   /* we detect attachments by size ... */
207   unsigned size;
208   
209   size = [[[self message] valueForKey:@"size"] intValue];
210   return size > attachmentFlagSize;
211 }
212
213 /* fetching messages */
214
215 - (NSArray *) fetchKeys 
216 {
217   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
218   static NSArray *keys = nil;
219   if (keys == nil) {
220     keys = [[NSArray alloc] initWithObjects: @"UID",
221                               @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
222   }
223   return keys;
224 }
225
226 - (NSString *) defaultSortKey 
227 {
228   return @"ARRIVAL";
229 }
230
231 - (NSString *) imap4SortKey 
232 {
233   NSString *sort;
234   
235   sort = [[context request] formValueForKey: @"sort"];
236
237   if (![sort length])
238     sort = [self defaultSortKey];
239
240   return [sort uppercaseString];
241 }
242
243 - (NSString *) imap4SortOrdering 
244 {
245   NSString *sort, *ascending;
246
247   sort = [self imap4SortKey];
248
249   ascending = [[context request] formValueForKey: @"asc"];
250   if (![ascending boolValue])
251     sort = [@"REVERSE " stringByAppendingString: sort];
252
253   return sort;
254 }
255
256 - (NSRange) fetchRange 
257 {
258   if (firstMessageNumber == 0)
259     return NSMakeRange(0, messagesPerPage);
260   return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
261 }
262
263 - (NSArray *) sortedUIDs 
264 {
265   EOQualifier *fetchQualifier, *notDeleted;
266   if (!sortedUIDs)
267     {
268       notDeleted = [EOQualifier qualifierWithQualifierFormat:
269                                   @"(not (flags = %@))",
270                                 @"deleted"];
271       if (qualifier)
272         {
273           fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
274                                                      notDeleted, qualifier,
275                                                    nil];
276           [fetchQualifier autorelease];
277         }
278       else
279         fetchQualifier = notDeleted;
280
281       sortedUIDs
282         = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
283                                sortOrdering: [self imap4SortOrdering]];
284       [sortedUIDs retain];
285     }
286
287   return sortedUIDs;
288 }
289
290 - (unsigned int) totalMessageCount 
291 {
292   return [sortedUIDs count];
293 }
294
295 - (BOOL) showsAllMessages 
296 {
297   return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
298 }
299
300 - (NSRange) fetchBlock 
301 {
302   NSRange  r;
303   unsigned len;
304   NSArray  *uids;
305   
306   r    = [self fetchRange];
307   uids = [self sortedUIDs];
308   
309   /* only need to restrict if we have a lot */
310   if ((len = [uids count]) <= r.length) {
311     r.location = 0;
312     r.length   = len;
313     return r;
314   }
315   
316   if (len < r.location) {
317     // TODO: CHECK CONDITION (< vs <=)
318     /* out of range, recover at first block */
319     r.location = 0;
320     return r;
321   }
322   
323   if (r.location + r.length > len)
324     r.length = len - r.location;
325   return r;
326 }
327
328 - (unsigned int) firstMessageNumber 
329 {
330   return [self fetchBlock].location + 1;
331 }
332
333 - (unsigned int) lastMessageNumber 
334 {
335   NSRange r;
336   
337   r = [self fetchBlock];
338   return r.location + r.length;
339 }
340
341 - (BOOL) hasPrevious 
342 {
343   return [self fetchBlock].location == 0 ? NO : YES;
344 }
345
346 - (BOOL) hasNext 
347 {
348   NSRange r = [self fetchBlock];
349   return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
350 }
351
352 - (unsigned int) nextFirstMessageNumber 
353 {
354   return [self firstMessageNumber] + [self fetchRange].length;
355 }
356
357 - (unsigned int) prevFirstMessageNumber 
358 {
359   NSRange  r;
360   unsigned idx;
361   
362   idx = [self firstMessageNumber];
363   r   = [self fetchRange];
364   if (idx > r.length)
365     return (idx - r.length);
366   return 1;
367 }
368
369 - (NSArray *) messages 
370 {
371   NSArray  *uids;
372   NSArray  *msgs;
373   NSRange  r;
374   unsigned len;
375   
376   if (messages != nil)
377     return messages;
378
379   r    = [self fetchBlock];
380   uids = [self sortedUIDs];
381   if ((len = [uids count]) > r.length)
382     /* only need to restrict if we have a lot */
383     uids = [uids subarrayWithRange:r];
384   
385   msgs = [[self clientObject] fetchUIDs: uids parts: [self fetchKeys]];
386   messages = [[msgs valueForKey: @"fetch"] retain];
387
388   return messages;
389 }
390
391 /* URL processing */
392
393 - (NSString *) messageViewTarget
394 {
395   return [NSString stringWithFormat: @"SOGo_msg_%@",
396                    [self messageUidString]];
397 }
398
399 - (NSString *) messageViewURL 
400 {
401   // TODO: noframe only when view-target is empty
402   // TODO: markread only if the message is unread
403   NSString *s;
404   
405   s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
406   if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
407   return s;
408 }
409 - (NSString *) markReadURL 
410 {
411   return [@"markMessageRead?uid=" stringByAppendingString:
412              [self messageUidString]];
413 }
414 - (NSString *) markUnreadURL 
415 {
416   return [@"markMessageUnread?uid=" stringByAppendingString:
417              [self messageUidString]];
418 }
419
420 /* JavaScript */
421
422 - (NSString *)msgRowID
423 {
424   return [@"row_" stringByAppendingString:[self messageUidString]];
425 }
426
427 - (NSString *)msgDivID
428 {
429   return [@"div_" stringByAppendingString:[self messageUidString]];
430 }
431
432 - (NSString *)msgIconReadImgID
433 {
434   return [@"readdiv_" stringByAppendingString:[self messageUidString]];
435 }
436
437 - (NSString *)msgIconUnreadImgID
438 {
439   return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
440 }
441
442 /* error redirects */
443
444 - (id) redirectToViewWithError: (id) _error 
445 {
446   // TODO: DUP in UIxMailAccountView
447   // TODO: improve, localize
448   // TODO: there is a bug in the treeview which preserves the current URL for
449   //       the active object (displaying the error again)
450   id url;
451   
452   if (![_error isNotNull])
453     return [self redirectToLocation:@"view"];
454   
455   if ([_error isKindOfClass:[NSException class]])
456     _error = [_error reason];
457   else if ([_error isKindOfClass:[NSString class]])
458     _error = [_error stringValue];
459   
460   url = [_error stringByEscapingURL];
461   url = [@"view?error=" stringByAppendingString:url];
462   return [self redirectToLocation:url];
463 }
464
465 /* actions */
466
467 - (int) firstMessageOfPageFor: (int) messageNbr
468 {
469   NSArray *messageNbrs;
470   int nbrInArray;
471   int firstMessage;
472
473   messageNbrs = [self sortedUIDs];
474   nbrInArray
475     = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
476   if (nbrInArray > -1)
477     firstMessage = ((int) (nbrInArray / messagesPerPage)
478                     * messagesPerPage) + 1;
479   else
480     firstMessage = 1;
481
482   return firstMessage;
483 }
484
485 - (void) _setQualifierForCriteria: (NSString *) criteria
486                          andValue: (NSString *) value
487 {
488   [qualifier release];
489
490   if ([criteria isEqualToString: @"subject"])
491     qualifier = [EOQualifier qualifierWithQualifierFormat:
492                                @"(subject doesContain: %@)", value];
493   else if ([criteria isEqualToString: @"sender"])
494     qualifier = [EOQualifier qualifierWithQualifierFormat:
495                                @"(from doesContain: %@)", value];
496   else if ([criteria isEqualToString: @"subject_or_sender"])
497     qualifier = [EOQualifier qualifierWithQualifierFormat:
498                                @"((subject doesContain: %@)"
499                              @" OR (from doesContain: %@))",
500                              value, value];
501   else if ([criteria isEqualToString: @"to_or_cc"])
502     qualifier = [EOQualifier qualifierWithQualifierFormat:
503                                @"((to doesContain: %@)"
504                              @" OR (cc doesContain: %@))",
505                              value, value];
506   else if ([criteria isEqualToString: @"entire_message"])
507     qualifier = [EOQualifier qualifierWithQualifierFormat:
508                                @"(body doesContain: %@)", value];
509   else
510     qualifier = nil;
511
512   [qualifier retain];
513 }
514
515 - (id) defaultAction 
516 {
517   WORequest *request;
518   NSString *specificMessage, *searchCriteria, *searchValue;
519
520   request = [context request];
521
522   [[self clientObject] flushMailCaches];
523
524   specificMessage = [request formValueForKey: @"pageforuid"];
525   searchCriteria = [request formValueForKey: @"search"];
526   searchValue = [request formValueForKey: @"value"];
527   if ([searchValue length])
528     [self _setQualifierForCriteria: searchCriteria
529           andValue: searchValue];
530
531   firstMessageNumber
532     = ((specificMessage)
533        ? [self firstMessageOfPageFor: [specificMessage intValue]]
534        : [[request formValueForKey:@"idx"] intValue]);
535   return self;
536 }
537
538 - (id) getMailAction 
539 {
540   // TODO: we might want to flush the caches?
541   id client;
542
543   if ((client = [self clientObject]) == nil) {
544     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
545                         reason:@"did not find mail folder"];
546   }
547
548   if (![client respondsToSelector:@selector(flushMailCaches) ]) 
549     {
550       return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
551                           reason:
552                             @"invalid client object (does not support flush)"];
553     }
554
555   [client flushMailCaches];
556
557   return [self redirectToLocation:@"view"];
558 }
559
560 - (NSString *) msgLabels
561 {
562   NSMutableArray *labels;
563   NSEnumerator *flags;
564   NSString *currentFlag;
565
566   labels = [NSMutableArray new];
567   [labels autorelease];
568
569   flags = [[message objectForKey: @"flags"] objectEnumerator];
570   while ((currentFlag = [flags nextObject]))
571     if ([currentFlag hasPrefix: @"$label"])
572       [labels addObject: [currentFlag substringFromIndex: 1]];
573
574   return [labels componentsJoinedByString: @" "];
575 }
576
577 @end
578
579 /* UIxMailListView */