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/NSTimeZone.h>
32 #import <Foundation/NSValue.h>
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>
41 #import <EOControl/EOQualifier.h>
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>
51 #import "UIxMailListView.h"
53 #define messagesPerPage 50
55 @implementation UIxMailListView
61 if ((self = [super init]))
64 user = [context activeUser];
65 ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
66 ASSIGN (userTimeZone, [user timeZone]);
79 [dateFormatter release];
80 [userTimeZone release];
86 - (void) setMessage: (id) _msg
88 ASSIGN (message, _msg);
96 - (NSString *) messageDate
98 NSCalendarDate *messageDate;
100 messageDate = [[message valueForKey: @"envelope"] date];
101 [messageDate setTimeZone: userTimeZone];
103 return [dateFormatter formattedDateAndTime: messageDate];
106 - (NSString *) messageSubject
111 baseSubject = [[message valueForKey: @"envelope"] subject];
112 subject = [baseSubject decodedSubject];
113 if (![subject length])
114 subject = [self labelForKey: @"Untitled"];
119 - (BOOL) showToAddress
125 co = [self clientObject];
126 if ([co isKindOfClass: [SOGoSentFolder class]]
127 || [co isKindOfClass: [SOGoDraftsFolder class]])
133 return (folderType == 1);
138 - (NSString *) objectTitle
140 return [[self clientObject] nameInContainer];
143 - (NSString *) panelTitle
147 s = [self labelForKey:@"View Mail Folder"];
148 s = [s stringByAppendingString:@": "];
149 s = [s stringByAppendingString:[self objectTitle]];
153 /* derived accessors */
155 - (BOOL) isMessageDeleted
159 flags = [[self message] valueForKey:@"flags"];
160 return [flags containsObject:@"deleted"];
163 - (BOOL) isMessageRead
167 flags = [[self message] valueForKey:@"flags"];
168 return [flags containsObject:@"seen"];
171 - (NSString *) messageUidString
173 return [[[self message] valueForKey:@"uid"] stringValue];
176 - (NSString *) messageRowStyleClass
180 rowClass = [self isMessageDeleted]? @"mailer_listcell_deleted" : @"mailer_listcell_regular";
182 if (![self isMessageRead])
183 rowClass = [rowClass stringByAppendingString: @" mailer_unreadmail"];
188 - (NSString *) messageSubjectCellStyleClass
191 NSString *cellClass = @"messageSubjectColumn ";
193 flags = [[self message] valueForKey:@"flags"];
195 if ([flags containsObject: @"answered"])
197 if ([flags containsObject: @"$forwarded"])
198 cellClass = [cellClass stringByAppendingString: @"mailer_forwardedrepliedmailsubject"];
200 cellClass = [cellClass stringByAppendingString: @"mailer_repliedmailsubject"];
202 else if ([flags containsObject: @"$forwarded"])
203 cellClass = [cellClass stringByAppendingString: @"mailer_forwardedmailsubject"];
205 cellClass = [cellClass stringByAppendingString: @"mailer_readmailsubject"];
210 - (BOOL) hasMessageAttachment
213 NSEnumerator *dispositions;
214 NSDictionary *currentDisp;
219 parts = [[message objectForKey: @"body"] objectForKey: @"parts"];
220 if ([parts count] > 1)
223 = [[parts objectsForKey: @"disposition"] objectEnumerator];
224 while (!hasAttachment
225 && (currentDisp = [dispositions nextObject]))
226 hasAttachment = ([[currentDisp objectForKey: @"type"] length]);
229 return hasAttachment;
232 /* fetching messages */
234 - (NSArray *) fetchKeys
236 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
237 static NSArray *keys = nil;
240 keys = [[NSArray alloc] initWithObjects: @"UID",
241 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE",
242 @"BODYSTRUCTURE", nil];
247 - (NSString *) defaultSortKey
252 - (NSString *) imap4SortKey
256 sort = [[context request] formValueForKey: @"sort"];
259 sort = [self defaultSortKey];
261 return [sort uppercaseString];
264 - (NSString *) imap4SortOrdering
266 NSString *sort, *ascending;
268 sort = [self imap4SortKey];
270 ascending = [[context request] formValueForKey: @"asc"];
271 if (![ascending boolValue])
272 sort = [@"REVERSE " stringByAppendingString: sort];
277 - (NSRange) fetchRange
279 if (firstMessageNumber == 0)
280 return NSMakeRange(0, messagesPerPage);
281 return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
284 - (NSArray *) sortedUIDs
286 EOQualifier *fetchQualifier, *notDeleted;
290 notDeleted = [EOQualifier qualifierWithQualifierFormat:
291 @"(not (flags = %@))",
295 fetchQualifier = [[EOAndQualifier alloc] initWithQualifiers:
296 notDeleted, qualifier,
298 [fetchQualifier autorelease];
301 fetchQualifier = notDeleted;
304 = [[self clientObject] fetchUIDsMatchingQualifier: fetchQualifier
305 sortOrdering: [self imap4SortOrdering]];
312 - (unsigned int) totalMessageCount
314 return [sortedUIDs count];
317 - (BOOL) showsAllMessages
319 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
322 - (NSRange) fetchBlock
328 r = [self fetchRange];
329 uids = [self sortedUIDs];
331 /* only need to restrict if we have a lot */
332 if ((len = [uids count]) <= r.length) {
338 if (len < r.location) {
339 // TODO: CHECK CONDITION (< vs <=)
340 /* out of range, recover at first block */
345 if (r.location + r.length > len)
346 r.length = len - r.location;
350 - (unsigned int) firstMessageNumber
352 return [self fetchBlock].location + 1;
355 - (unsigned int) lastMessageNumber
359 r = [self fetchBlock];
360 return r.location + r.length;
365 return [self fetchBlock].location == 0 ? NO : YES;
370 NSRange r = [self fetchBlock];
371 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
374 - (unsigned int) nextFirstMessageNumber
376 return [self firstMessageNumber] + [self fetchRange].length;
379 - (unsigned int) lastFirstMessageNumber
381 unsigned int max, modulo;
386 max = [sortedUIDs count];
387 modulo = (max % messagesPerPage);
389 modulo = messagesPerPage;
391 return (max + 1 - modulo);
394 - (unsigned int) prevFirstMessageNumber
399 idx = [self firstMessageNumber];
400 r = [self fetchRange];
402 return (idx - r.length);
406 - (NSArray *) messages
415 r = [self fetchBlock];
416 uids = [self sortedUIDs];
419 /* only need to restrict if we have a lot */
420 uids = [uids subarrayWithRange: r];
422 msgs = (NSDictionary *) [[self clientObject] fetchUIDs: uids
423 parts: [self fetchKeys]];
424 messages = [[msgs objectForKey: @"fetch"] retain];
432 - (NSString *) messageViewTarget
434 return [NSString stringWithFormat: @"SOGo_msg_%@",
435 [self messageUidString]];
438 - (NSString *) messageViewURL
440 // TODO: noframe only when view-target is empty
441 // TODO: markread only if the message is unread
444 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
445 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
448 - (NSString *) markReadURL
450 return [@"markMessageRead?uid=" stringByAppendingString:
451 [self messageUidString]];
453 - (NSString *) markUnreadURL
455 return [@"markMessageUnread?uid=" stringByAppendingString:
456 [self messageUidString]];
461 - (NSString *)msgRowID
463 return [@"row_" stringByAppendingString:[self messageUidString]];
466 - (NSString *)msgDivID
468 return [@"div_" stringByAppendingString:[self messageUidString]];
471 - (NSString *)msgIconReadImgID
473 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
476 - (NSString *)msgIconUnreadImgID
478 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
481 /* error redirects */
483 - (id) redirectToViewWithError: (id) _error
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)
491 if (![_error isNotNull])
492 return [self redirectToLocation:@"view"];
494 if ([_error isKindOfClass:[NSException class]])
495 _error = [_error reason];
496 else if ([_error isKindOfClass:[NSString class]])
497 _error = [_error stringValue];
499 url = [_error stringByEscapingURL];
500 url = [@"view?error=" stringByAppendingString:url];
501 return [self redirectToLocation:url];
506 - (int) firstMessageOfPageFor: (int) messageNbr
508 NSArray *messageNbrs;
512 messageNbrs = [self sortedUIDs];
514 = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
516 firstMessage = ((int) (nbrInArray / messagesPerPage)
517 * messagesPerPage) + 1;
524 - (void) _setQualifierForCriteria: (NSString *) criteria
525 andValue: (NSString *) value
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: %@))",
540 else if ([criteria isEqualToString: @"to_or_cc"])
541 qualifier = [EOQualifier qualifierWithQualifierFormat:
542 @"((to doesContain: %@)"
543 @" OR (cc doesContain: %@))",
545 else if ([criteria isEqualToString: @"entire_message"])
546 qualifier = [EOQualifier qualifierWithQualifierFormat:
547 @"(body doesContain: %@)", value];
557 NSString *specificMessage, *searchCriteria, *searchValue;
560 request = [context request];
562 co = [self clientObject];
563 [co flushMailCaches];
564 [co expungeLastMarkedFolder];
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];
575 ? [self firstMessageOfPageFor: [specificMessage intValue]]
576 : [[request formValueForKey:@"idx"] intValue]);
583 // TODO: we might want to flush the caches?
586 if ((client = [self clientObject]) == nil) {
587 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
588 reason:@"did not find mail folder"];
591 if (![client respondsToSelector:@selector(flushMailCaches) ])
593 return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
595 @"invalid client object (does not support flush)"];
598 [client flushMailCaches];
600 return [self redirectToLocation:@"view"];
603 - (NSString *) msgLabels
605 NSMutableArray *labels;
607 NSString *currentFlag;
609 labels = [NSMutableArray new];
610 [labels autorelease];
612 flags = [[message objectForKey: @"flags"] objectEnumerator];
613 while ((currentFlag = [flags nextObject]))
614 if ([currentFlag hasPrefix: @"$label"])
615 [labels addObject: [currentFlag substringFromIndex: 1]];
617 return [labels componentsJoinedByString: @" "];
622 /* UIxMailListView */