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