]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailListView.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1216 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/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/NSArray+Utilities.h>
44 #import <SoObjects/SOGo/SOGoDateFormatter.h>
45 #import <SoObjects/SOGo/SOGoUser.h>
46
47 #import "UIxMailListView.h"
48
49 #define messagesPerPage 50
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   NSArray *parts;
207   NSEnumerator *dispositions;
208   NSDictionary *currentDisp;
209   BOOL hasAttachment;
210
211   hasAttachment = NO;
212
213   parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
214   if ([parts count] > 1)
215     {
216       dispositions
217         = [[parts objectsForKey: @"disposition"] objectEnumerator];
218       while (!hasAttachment
219              && (currentDisp = [dispositions nextObject]))
220         hasAttachment = ([[currentDisp objectForKey: @"type"]
221                            isEqualToString: @"ATTACHMENT"]);
222     }
223
224   return hasAttachment;
225 }
226
227 /* fetching messages */
228
229 - (NSArray *) fetchKeys 
230 {
231   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
232   static NSArray *keys = nil;
233
234   if (!keys)
235     keys = [[NSArray alloc] initWithObjects: @"UID",
236                             @"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
237                             @"BODYSTRUCTURE", nil];
238
239   return keys;
240 }
241
242 - (NSString *) defaultSortKey 
243 {
244   return @"ARRIVAL";
245 }
246
247 - (NSString *) imap4SortKey 
248 {
249   NSString *sort;
250   
251   sort = [[context request] formValueForKey: @"sort"];
252
253   if (![sort length])
254     sort = [self defaultSortKey];
255
256   return [sort uppercaseString];
257 }
258
259 - (NSString *) imap4SortOrdering 
260 {
261   NSString *sort, *ascending;
262
263   sort = [self imap4SortKey];
264
265   ascending = [[context request] formValueForKey: @"asc"];
266   if (![ascending boolValue])
267     sort = [@"REVERSE " stringByAppendingString: sort];
268
269   return sort;
270 }
271
272 - (NSRange) fetchRange 
273 {
274   if (firstMessageNumber == 0)
275     return NSMakeRange(0, messagesPerPage);
276   return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
277 }
278
279 - (NSArray *) sortedUIDs 
280 {
281   EOQualifier *fetchQualifier, *notDeleted;
282
283   if (!sortedUIDs)
284     {
285       notDeleted = [EOQualifier qualifierWithQualifierFormat:
286                                   @"(not (flags = %@))",
287                                 @"deleted"];
288       if (qualifier)
289         {
290           fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
291                                                      notDeleted, qualifier,
292                                                    nil];
293           [fetchQualifier autorelease];
294         }
295       else
296         fetchQualifier = notDeleted;
297
298       sortedUIDs
299         = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
300                                sortOrdering: [self imap4SortOrdering]];
301       [sortedUIDs retain];
302     }
303
304   return sortedUIDs;
305 }
306
307 - (unsigned int) totalMessageCount 
308 {
309   return [sortedUIDs count];
310 }
311
312 - (BOOL) showsAllMessages 
313 {
314   return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
315 }
316
317 - (NSRange) fetchBlock 
318 {
319   NSRange  r;
320   unsigned len;
321   NSArray  *uids;
322   
323   r    = [self fetchRange];
324   uids = [self sortedUIDs];
325   
326   /* only need to restrict if we have a lot */
327   if ((len = [uids count]) <= r.length) {
328     r.location = 0;
329     r.length   = len;
330     return r;
331   }
332   
333   if (len < r.location) {
334     // TODO: CHECK CONDITION (< vs <=)
335     /* out of range, recover at first block */
336     r.location = 0;
337     return r;
338   }
339   
340   if (r.location + r.length > len)
341     r.length = len - r.location;
342   return r;
343 }
344
345 - (unsigned int) firstMessageNumber 
346 {
347   return [self fetchBlock].location + 1;
348 }
349
350 - (unsigned int) lastMessageNumber 
351 {
352   NSRange r;
353   
354   r = [self fetchBlock];
355   return r.location + r.length;
356 }
357
358 - (BOOL) hasPrevious 
359 {
360   return [self fetchBlock].location == 0 ? NO : YES;
361 }
362
363 - (BOOL) hasNext 
364 {
365   NSRange r = [self fetchBlock];
366   return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
367 }
368
369 - (unsigned int) nextFirstMessageNumber 
370 {
371   return [self firstMessageNumber] + [self fetchRange].length;
372 }
373
374 - (unsigned int) lastFirstMessageNumber 
375 {
376   unsigned int max, modulo;
377
378   if (!sortedUIDs)
379     [self sortedUIDs];
380
381   max = [sortedUIDs count];
382   modulo = (max % messagesPerPage);
383   if (modulo == 0)
384     modulo = messagesPerPage;
385
386   return (max + 1 - modulo);
387 }
388
389 - (unsigned int) prevFirstMessageNumber 
390 {
391   NSRange  r;
392   unsigned idx;
393   
394   idx = [self firstMessageNumber];
395   r   = [self fetchRange];
396   if (idx > r.length)
397     return (idx - r.length);
398   return 1;
399 }
400
401 - (NSArray *) messages 
402 {
403   NSArray *uids;
404   NSDictionary *msgs;
405   NSRange r;
406   unsigned len;
407   
408   if (!messages)
409     {
410       r    = [self fetchBlock];
411       uids = [self sortedUIDs];
412       len = [uids count];
413       if (len > r.length)
414         /* only need to restrict if we have a lot */
415         uids = [uids subarrayWithRange: r];
416   
417       msgs = [[self clientObject] fetchUIDs: uids parts: [self fetchKeys]];
418       messages = [[msgs objectForKey: @"fetch"] retain];
419     }
420
421   return messages;
422 }
423
424 /* URL processing */
425
426 - (NSString *) messageViewTarget
427 {
428   return [NSString stringWithFormat: @"SOGo_msg_%@",
429                    [self messageUidString]];
430 }
431
432 - (NSString *) messageViewURL 
433 {
434   // TODO: noframe only when view-target is empty
435   // TODO: markread only if the message is unread
436   NSString *s;
437   
438   s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
439   if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
440   return s;
441 }
442 - (NSString *) markReadURL 
443 {
444   return [@"markMessageRead?uid=" stringByAppendingString:
445              [self messageUidString]];
446 }
447 - (NSString *) markUnreadURL 
448 {
449   return [@"markMessageUnread?uid=" stringByAppendingString:
450              [self messageUidString]];
451 }
452
453 /* JavaScript */
454
455 - (NSString *)msgRowID
456 {
457   return [@"row_" stringByAppendingString:[self messageUidString]];
458 }
459
460 - (NSString *)msgDivID
461 {
462   return [@"div_" stringByAppendingString:[self messageUidString]];
463 }
464
465 - (NSString *)msgIconReadImgID
466 {
467   return [@"readdiv_" stringByAppendingString:[self messageUidString]];
468 }
469
470 - (NSString *)msgIconUnreadImgID
471 {
472   return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
473 }
474
475 /* error redirects */
476
477 - (id) redirectToViewWithError: (id) _error 
478 {
479   // TODO: DUP in UIxMailAccountView
480   // TODO: improve, localize
481   // TODO: there is a bug in the treeview which preserves the current URL for
482   //       the active object (displaying the error again)
483   id url;
484   
485   if (![_error isNotNull])
486     return [self redirectToLocation:@"view"];
487   
488   if ([_error isKindOfClass:[NSException class]])
489     _error = [_error reason];
490   else if ([_error isKindOfClass:[NSString class]])
491     _error = [_error stringValue];
492   
493   url = [_error stringByEscapingURL];
494   url = [@"view?error=" stringByAppendingString:url];
495   return [self redirectToLocation:url];
496 }
497
498 /* actions */
499
500 - (int) firstMessageOfPageFor: (int) messageNbr
501 {
502   NSArray *messageNbrs;
503   int nbrInArray;
504   int firstMessage;
505
506   messageNbrs = [self sortedUIDs];
507   nbrInArray
508     = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
509   if (nbrInArray > -1)
510     firstMessage = ((int) (nbrInArray / messagesPerPage)
511                     * messagesPerPage) + 1;
512   else
513     firstMessage = 1;
514
515   return firstMessage;
516 }
517
518 - (void) _setQualifierForCriteria: (NSString *) criteria
519                          andValue: (NSString *) value
520 {
521   [qualifier release];
522
523   if ([criteria isEqualToString: @"subject"])
524     qualifier = [EOQualifier qualifierWithQualifierFormat:
525                                @"(subject doesContain: %@)", value];
526   else if ([criteria isEqualToString: @"sender"])
527     qualifier = [EOQualifier qualifierWithQualifierFormat:
528                                @"(from doesContain: %@)", value];
529   else if ([criteria isEqualToString: @"subject_or_sender"])
530     qualifier = [EOQualifier qualifierWithQualifierFormat:
531                                @"((subject doesContain: %@)"
532                              @" OR (from doesContain: %@))",
533                              value, value];
534   else if ([criteria isEqualToString: @"to_or_cc"])
535     qualifier = [EOQualifier qualifierWithQualifierFormat:
536                                @"((to doesContain: %@)"
537                              @" OR (cc doesContain: %@))",
538                              value, value];
539   else if ([criteria isEqualToString: @"entire_message"])
540     qualifier = [EOQualifier qualifierWithQualifierFormat:
541                                @"(body doesContain: %@)", value];
542   else
543     qualifier = nil;
544
545   [qualifier retain];
546 }
547
548 - (id) defaultAction 
549 {
550   WORequest *request;
551   NSString *specificMessage, *searchCriteria, *searchValue;
552   SOGoUserFolder *co;
553
554   request = [context request];
555
556   co = [self clientObject];
557   [co flushMailCaches];
558   [co expungeLastMarkedFolder];
559
560   specificMessage = [request formValueForKey: @"pageforuid"];
561   searchCriteria = [request formValueForKey: @"search"];
562   searchValue = [request formValueForKey: @"value"];
563   if ([searchValue length])
564     [self _setQualifierForCriteria: searchCriteria
565           andValue: searchValue];
566
567   firstMessageNumber
568     = ((specificMessage)
569        ? [self firstMessageOfPageFor: [specificMessage intValue]]
570        : [[request formValueForKey:@"idx"] intValue]);
571
572   return self;
573 }
574
575 - (id) getMailAction 
576 {
577   // TODO: we might want to flush the caches?
578   id client;
579
580   if ((client = [self clientObject]) == nil) {
581     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
582                         reason:@"did not find mail folder"];
583   }
584
585   if (![client respondsToSelector:@selector(flushMailCaches) ]) 
586     {
587       return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
588                           reason:
589                             @"invalid client object (does not support flush)"];
590     }
591
592   [client flushMailCaches];
593
594   return [self redirectToLocation:@"view"];
595 }
596
597 - (NSString *) msgLabels
598 {
599   NSMutableArray *labels;
600   NSEnumerator *flags;
601   NSString *currentFlag;
602
603   labels = [NSMutableArray new];
604   [labels autorelease];
605
606   flags = [[message objectForKey: @"flags"] objectEnumerator];
607   while ((currentFlag = [flags nextObject]))
608     if ([currentFlag hasPrefix: @"$label"])
609       [labels addObject: [currentFlag substringFromIndex: 1]];
610
611   return [labels componentsJoinedByString: @" "];
612 }
613
614 @end
615
616 /* UIxMailListView */