2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
25 This component represent a list of mails and is attached to an SOGoMailFolder
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>
39 #import <EOControl/EOQualifier.h>
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>
47 #import "UIxMailListView.h"
49 #define messagesPerPage 50
51 @implementation UIxMailListView
57 if ((self = [super init]))
60 user = [context activeUser];
61 ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
62 ASSIGN (userTimeZone, [user timeZone]);
74 [dateFormatter release];
75 [userTimeZone release];
81 - (void) setMessage: (id) _msg
83 ASSIGN (message, _msg);
91 - (NSString *) messageDate
93 NSCalendarDate *messageDate;
95 messageDate = [[message valueForKey: @"envelope"] date];
96 [messageDate setTimeZone: userTimeZone];
98 return [dateFormatter formattedDateAndTime: messageDate];
101 - (NSString *) messageSubject
106 envSubject = [[message valueForKey: @"envelope"] subject];
107 if ([envSubject isKindOfClass: [NSData class]])
109 subject = [[NSString alloc] initWithData: envSubject
110 encoding: NSUTF8StringEncoding];
111 [subject autorelease];
114 subject = envSubject;
119 - (BOOL) showToAddress
123 ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
124 return [ftype isEqual:@"IPF.Sent"];
129 - (NSString *) objectTitle
131 return [[self clientObject] nameInContainer];
134 - (NSString *) panelTitle
138 s = [self labelForKey:@"View Mail Folder"];
139 s = [s stringByAppendingString:@": "];
140 s = [s stringByAppendingString:[self objectTitle]];
144 /* derived accessors */
146 - (BOOL) isMessageDeleted
150 flags = [[self message] valueForKey:@"flags"];
151 return [flags containsObject:@"deleted"];
154 - (BOOL) isMessageRead
158 flags = [[self message] valueForKey:@"flags"];
159 return [flags containsObject:@"seen"];
162 - (NSString *) messageUidString
164 return [[[self message] valueForKey:@"uid"] stringValue];
167 - (NSString *) messageRowStyleClass
169 return [self isMessageDeleted]
170 ? @"mailer_listcell_deleted"
171 : @"mailer_listcell_regular";
174 - (NSString *) messageSubjectCellStyleClass
179 flags = [[self message] valueForKey:@"flags"];
181 if ([flags containsObject: @"seen"])
183 if ([flags containsObject: @"answered"])
185 if ([flags containsObject: @"$forwarded"])
186 cellClass = @"mailer_forwardedrepliedmailsubject";
188 cellClass = @"mailer_repliedmailsubject";
190 else if ([flags containsObject: @"$forwarded"])
191 cellClass = @"mailer_forwardedmailsubject";
193 cellClass = @"mailer_readmailsubject";
196 cellClass = @"mailer_unreadmailsubject";
199 // return ([self isMessageRead]
200 // ? @"mailer_readmailsubject"
201 // : @"mailer_unreadmailsubject");
204 - (BOOL) hasMessageAttachment
207 NSEnumerator *dispositions;
208 NSDictionary *currentDisp;
213 parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
214 if ([parts count] > 1)
217 = [[parts objectsForKey: @"disposition"] objectEnumerator];
218 while (!hasAttachment
219 && (currentDisp = [dispositions nextObject]))
220 hasAttachment = ([[currentDisp objectForKey: @"type"]
221 isEqualToString: @"ATTACHMENT"]);
224 return hasAttachment;
227 /* fetching messages */
229 - (NSArray *) fetchKeys
231 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
232 static NSArray *keys = nil;
235 keys = [[NSArray alloc] initWithObjects: @"UID",
236 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
237 @"BODYSTRUCTURE", nil];
242 - (NSString *) defaultSortKey
247 - (NSString *) imap4SortKey
251 sort = [[context request] formValueForKey: @"sort"];
254 sort = [self defaultSortKey];
256 return [sort uppercaseString];
259 - (NSString *) imap4SortOrdering
261 NSString *sort, *ascending;
263 sort = [self imap4SortKey];
265 ascending = [[context request] formValueForKey: @"asc"];
266 if (![ascending boolValue])
267 sort = [@"REVERSE " stringByAppendingString: sort];
272 - (NSRange) fetchRange
274 if (firstMessageNumber == 0)
275 return NSMakeRange(0, messagesPerPage);
276 return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
279 - (NSArray *) sortedUIDs
281 EOQualifier *fetchQualifier, *notDeleted;
285 notDeleted = [EOQualifier qualifierWithQualifierFormat:
286 @"(not (flags = %@))",
290 fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
291 notDeleted, qualifier,
293 [fetchQualifier autorelease];
296 fetchQualifier = notDeleted;
299 = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
300 sortOrdering: [self imap4SortOrdering]];
307 - (unsigned int) totalMessageCount
309 return [sortedUIDs count];
312 - (BOOL) showsAllMessages
314 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
317 - (NSRange) fetchBlock
323 r = [self fetchRange];
324 uids = [self sortedUIDs];
326 /* only need to restrict if we have a lot */
327 if ((len = [uids count]) <= r.length) {
333 if (len < r.location) {
334 // TODO: CHECK CONDITION (< vs <=)
335 /* out of range, recover at first block */
340 if (r.location + r.length > len)
341 r.length = len - r.location;
345 - (unsigned int) firstMessageNumber
347 return [self fetchBlock].location + 1;
350 - (unsigned int) lastMessageNumber
354 r = [self fetchBlock];
355 return r.location + r.length;
360 return [self fetchBlock].location == 0 ? NO : YES;
365 NSRange r = [self fetchBlock];
366 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
369 - (unsigned int) nextFirstMessageNumber
371 return [self firstMessageNumber] + [self fetchRange].length;
374 - (unsigned int) lastFirstMessageNumber
376 unsigned int max, modulo;
381 max = [sortedUIDs count];
382 modulo = (max % messagesPerPage);
384 modulo = messagesPerPage;
386 return (max + 1 - modulo);
389 - (unsigned int) prevFirstMessageNumber
394 idx = [self firstMessageNumber];
395 r = [self fetchRange];
397 return (idx - r.length);
401 - (NSArray *) messages
410 r = [self fetchBlock];
411 uids = [self sortedUIDs];
414 /* only need to restrict if we have a lot */
415 uids = [uids subarrayWithRange: r];
417 msgs = [[self clientObject] fetchUIDs: uids parts: [self fetchKeys]];
418 messages = [[msgs objectForKey: @"fetch"] retain];
426 - (NSString *) messageViewTarget
428 return [NSString stringWithFormat: @"SOGo_msg_%@",
429 [self messageUidString]];
432 - (NSString *) messageViewURL
434 // TODO: noframe only when view-target is empty
435 // TODO: markread only if the message is unread
438 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
439 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
442 - (NSString *) markReadURL
444 return [@"markMessageRead?uid=" stringByAppendingString:
445 [self messageUidString]];
447 - (NSString *) markUnreadURL
449 return [@"markMessageUnread?uid=" stringByAppendingString:
450 [self messageUidString]];
455 - (NSString *)msgRowID
457 return [@"row_" stringByAppendingString:[self messageUidString]];
460 - (NSString *)msgDivID
462 return [@"div_" stringByAppendingString:[self messageUidString]];
465 - (NSString *)msgIconReadImgID
467 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
470 - (NSString *)msgIconUnreadImgID
472 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
475 /* error redirects */
477 - (id) redirectToViewWithError: (id) _error
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)
485 if (![_error isNotNull])
486 return [self redirectToLocation:@"view"];
488 if ([_error isKindOfClass:[NSException class]])
489 _error = [_error reason];
490 else if ([_error isKindOfClass:[NSString class]])
491 _error = [_error stringValue];
493 url = [_error stringByEscapingURL];
494 url = [@"view?error=" stringByAppendingString:url];
495 return [self redirectToLocation:url];
500 - (int) firstMessageOfPageFor: (int) messageNbr
502 NSArray *messageNbrs;
506 messageNbrs = [self sortedUIDs];
508 = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
510 firstMessage = ((int) (nbrInArray / messagesPerPage)
511 * messagesPerPage) + 1;
518 - (void) _setQualifierForCriteria: (NSString *) criteria
519 andValue: (NSString *) value
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: %@))",
534 else if ([criteria isEqualToString: @"to_or_cc"])
535 qualifier = [EOQualifier qualifierWithQualifierFormat:
536 @"((to doesContain: %@)"
537 @" OR (cc doesContain: %@))",
539 else if ([criteria isEqualToString: @"entire_message"])
540 qualifier = [EOQualifier qualifierWithQualifierFormat:
541 @"(body doesContain: %@)", value];
551 NSString *specificMessage, *searchCriteria, *searchValue;
554 request = [context request];
556 co = [self clientObject];
557 [co flushMailCaches];
558 [co expungeLastMarkedFolder];
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];
569 ? [self firstMessageOfPageFor: [specificMessage intValue]]
570 : [[request formValueForKey:@"idx"] intValue]);
577 // TODO: we might want to flush the caches?
580 if ((client = [self clientObject]) == nil) {
581 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
582 reason:@"did not find mail folder"];
585 if (![client respondsToSelector:@selector(flushMailCaches) ])
587 return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
589 @"invalid client object (does not support flush)"];
592 [client flushMailCaches];
594 return [self redirectToLocation:@"view"];
597 - (NSString *) msgLabels
599 NSMutableArray *labels;
601 NSString *currentFlag;
603 labels = [NSMutableArray new];
604 [labels autorelease];
606 flags = [[message objectForKey: @"flags"] objectEnumerator];
607 while ((currentFlag = [flags nextObject]))
608 if ([currentFlag hasPrefix: @"$label"])
609 [labels addObject: [currentFlag substringFromIndex: 1]];
611 return [labels componentsJoinedByString: @" "];
616 /* UIxMailListView */