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/SOGoDateFormatter.h>
44 #import <SoObjects/SOGo/SOGoUser.h>
46 #import "UIxMailListView.h"
48 #define messagesPerPage 50
49 static int attachmentFlagSize = 8096;
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 objectForKey: @"envelope"] date];
96 [messageDate setTimeZone: userTimeZone];
98 return [dateFormatter formattedDateAndTime: messageDate];
101 - (BOOL) showToAddress
105 ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
106 return [ftype isEqual:@"IPF.Sent"];
111 - (NSString *) objectTitle
113 return [[self clientObject] nameInContainer];
116 - (NSString *) panelTitle
120 s = [self labelForKey:@"View Mail Folder"];
121 s = [s stringByAppendingString:@": "];
122 s = [s stringByAppendingString:[self objectTitle]];
126 /* derived accessors */
128 - (BOOL) isMessageDeleted
132 flags = [[self message] valueForKey:@"flags"];
133 return [flags containsObject:@"deleted"];
136 - (BOOL) isMessageRead
140 flags = [[self message] valueForKey:@"flags"];
141 return [flags containsObject:@"seen"];
143 - (NSString *) messageUidString
145 return [[[self message] valueForKey:@"uid"] stringValue];
148 - (NSString *) messageRowStyleClass
150 return [self isMessageDeleted]
151 ? @"mailer_listcell_deleted"
152 : @"mailer_listcell_regular";
155 - (NSString *) messageSubjectCellStyleClass
157 return ([self isMessageRead]
158 ? @"mailer_readmailsubject"
159 : @"mailer_unreadmailsubject");
162 - (BOOL) hasMessageAttachment
164 /* we detect attachments by size ... */
167 size = [[[self message] valueForKey:@"size"] intValue];
168 return size > attachmentFlagSize;
171 /* fetching messages */
173 - (NSArray *) fetchKeys
175 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
176 static NSArray *keys = nil;
178 keys = [[NSArray alloc] initWithObjects: @"UID",
179 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
184 - (NSString *) defaultSortKey
189 - (NSString *) imap4SortKey
193 sort = [[context request] formValueForKey: @"sort"];
196 sort = [self defaultSortKey];
198 return [sort uppercaseString];
201 - (NSString *) imap4SortOrdering
203 NSString *sort, *ascending;
205 sort = [self imap4SortKey];
207 ascending = [[context request] formValueForKey: @"asc"];
208 if (![ascending boolValue])
209 sort = [@"REVERSE " stringByAppendingString: sort];
214 - (NSRange) fetchRange
216 if (firstMessageNumber == 0)
217 return NSMakeRange(0, messagesPerPage);
218 return NSMakeRange(firstMessageNumber - 1, messagesPerPage);
221 - (NSArray *) sortedUIDs
226 = [[self clientObject] fetchUIDsMatchingQualifier: qualifier
227 sortOrdering: [self imap4SortOrdering]];
234 - (unsigned int) totalMessageCount
236 return [sortedUIDs count];
239 - (BOOL) showsAllMessages
241 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
244 - (NSRange) fetchBlock
250 r = [self fetchRange];
251 uids = [self sortedUIDs];
253 /* only need to restrict if we have a lot */
254 if ((len = [uids count]) <= r.length) {
260 if (len < r.location) {
261 // TODO: CHECK CONDITION (< vs <=)
262 /* out of range, recover at first block */
267 if (r.location + r.length > len)
268 r.length = len - r.location;
272 - (unsigned int) firstMessageNumber
274 return [self fetchBlock].location + 1;
277 - (unsigned int) lastMessageNumber
281 r = [self fetchBlock];
282 return r.location + r.length;
287 return [self fetchBlock].location == 0 ? NO : YES;
292 NSRange r = [self fetchBlock];
293 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
296 - (unsigned int) nextFirstMessageNumber
298 return [self firstMessageNumber] + [self fetchRange].length;
301 - (unsigned int) prevFirstMessageNumber
306 idx = [self firstMessageNumber];
307 r = [self fetchRange];
309 return (idx - r.length);
313 - (NSArray *) messages
323 r = [self fetchBlock];
324 uids = [self sortedUIDs];
325 if ((len = [uids count]) > r.length)
326 /* only need to restrict if we have a lot */
327 uids = [uids subarrayWithRange:r];
329 msgs = [[self clientObject] fetchUIDs: uids parts: [self fetchKeys]];
330 messages = [[msgs valueForKey: @"fetch"] retain];
337 - (NSString *) messageViewTarget
339 return [NSString stringWithFormat: @"SOGo_msg_%@",
340 [self messageUidString]];
343 - (NSString *) messageViewURL
345 // TODO: noframe only when view-target is empty
346 // TODO: markread only if the message is unread
349 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
350 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
353 - (NSString *) markReadURL
355 return [@"markMessageRead?uid=" stringByAppendingString:
356 [self messageUidString]];
358 - (NSString *) markUnreadURL
360 return [@"markMessageUnread?uid=" stringByAppendingString:
361 [self messageUidString]];
366 - (NSString *)msgRowID
368 return [@"row_" stringByAppendingString:[self messageUidString]];
371 - (NSString *)msgDivID
373 return [@"div_" stringByAppendingString:[self messageUidString]];
376 - (NSString *)msgIconReadImgID
378 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
381 - (NSString *)msgIconUnreadImgID
383 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
386 /* error redirects */
388 - (id) redirectToViewWithError: (id) _error
390 // TODO: DUP in UIxMailAccountView
391 // TODO: improve, localize
392 // TODO: there is a bug in the treeview which preserves the current URL for
393 // the active object (displaying the error again)
396 if (![_error isNotNull])
397 return [self redirectToLocation:@"view"];
399 if ([_error isKindOfClass:[NSException class]])
400 _error = [_error reason];
401 else if ([_error isKindOfClass:[NSString class]])
402 _error = [_error stringValue];
404 url = [_error stringByEscapingURL];
405 url = [@"view?error=" stringByAppendingString:url];
406 return [self redirectToLocation:url];
411 - (SOGoMailObject *) lookupActiveMessage
415 if ((uid = [[context request] formValueForKey: @"uid"]) == nil)
418 return [[self clientObject] lookupName: uid
425 - (BOOL) isJavaScriptRequest
427 return [[[context request] formValueForKey:@"jsonly"] boolValue];
434 r = [context response];
435 [r setStatus:200 /* OK */];
439 - (int) firstMessageOfPageFor: (int) messageNbr
441 NSArray *messageNbrs;
445 messageNbrs = [self sortedUIDs];
447 = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
449 firstMessage = ((int) (nbrInArray / messagesPerPage)
450 * messagesPerPage) + 1;
457 - (void) _setQualifierForCriteria: (NSString *) criteria
458 andValue: (NSString *) value
462 if ([criteria isEqualToString: @"subject"])
463 qualifier = [EOQualifier qualifierWithQualifierFormat:
464 @"(subject doesContain: %@)",
466 else if ([criteria isEqualToString: @"sender"])
467 qualifier = [EOQualifier qualifierWithQualifierFormat:
468 @"(from doesContain: %@)",
470 else if ([criteria isEqualToString: @"subject_or_sender"])
471 qualifier = [EOQualifier qualifierWithQualifierFormat:
472 @"(subject doesContain: %@) OR "
473 @"(from doesContain: %@)",
475 else if ([criteria isEqualToString: @"to_or_cc"])
476 qualifier = [EOQualifier qualifierWithQualifierFormat:
477 @"(to doesContain: %@) OR "
478 @"(cc doesContain: %@)",
480 else if ([criteria isEqualToString: @"entire_message"])
481 qualifier = [EOQualifier qualifierWithQualifierFormat:
482 @"(message doesContain: %@)",
493 NSString *specificMessage, *searchCriteria, *searchValue;
495 request = [context request];
497 [[self clientObject] flushMailCaches];
499 specificMessage = [request formValueForKey: @"pageforuid"];
500 searchCriteria = [request formValueForKey: @"search"];
501 searchValue = [request formValueForKey: @"value"];
502 if ([searchCriteria length] > 0
503 && [searchValue length] > 0)
504 [self _setQualifierForCriteria: searchCriteria
505 andValue: searchValue];
509 ? [self firstMessageOfPageFor: [specificMessage intValue]]
510 : [[request formValueForKey:@"idx"] intValue]);
517 return [self defaultAction];
520 - (id) markMessageUnreadAction
524 if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
525 // TODO: improve error handling
528 if ([self isJavaScriptRequest])
529 return [self javaScriptOK];
531 return [self redirectToLocation:@"view"];
534 - (id) markMessageReadAction
538 if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
539 // TODO: improve error handling
542 if ([self isJavaScriptRequest])
543 return [self javaScriptOK];
545 return [self redirectToLocation:@"view"];
550 // TODO: we might want to flush the caches?
553 if ((client = [self clientObject]) == nil) {
554 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
555 reason:@"did not find mail folder"];
558 if (![client respondsToSelector:@selector(flushMailCaches) ])
560 return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
562 @"invalid client object (does not support flush)"];
565 [client flushMailCaches];
567 return [self redirectToLocation:@"view"];
572 // TODO: we might want to flush the caches?
576 clientObject = [self clientObject];
579 error = [clientObject expunge];
582 if ([clientObject respondsToSelector: @selector(flushMailCaches)])
583 [clientObject flushMailCaches];
584 return [self redirectToLocation:@"view"];
588 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
589 reason: @"did not find mail folder"];
596 /* UIxMailListView */