/* Copyright (C) 2007-2014 Inverse inc. This file is part of SOGo. SOGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "UIxMailMainFrame.h" // Avoid compilation warnings @interface SOGoUserFolder (private) - (SOGoAppointmentFolders *) privateContacts: (NSString *) key inContext: (WOContext *) localContext; @end @implementation UIxMailMainFrame - (id) init { if ((self = [super init])) { folderType = 0; } return self; } - (void) dealloc { RELEASE(_currentLabel); [super dealloc]; } - (NSString *) modulePath { return @"Mail"; } - (void) _setupContext { SOGoUser *activeUser; NSString *module; SOGoMailAccounts *clientObject; activeUser = [context activeUser]; clientObject = [self clientObject]; module = [clientObject nameInContainer]; us = [activeUser userSettings]; moduleSettings = [us objectForKey: module]; if (!moduleSettings) { moduleSettings = [NSMutableDictionary dictionary]; [us setObject: moduleSettings forKey: module]; } } /* accessors */ - (NSString *) mailAccounts { NSArray *accounts; accounts = [[self clientObject] mailAccounts]; return [accounts jsonRepresentation]; } - (WOResponse *) mailAccountsAction { WOResponse *response; NSString *s; s = [self mailAccounts]; response = [self responseWithStatus: 200 andString: s]; return response; } - (NSString *) userNames { NSArray *accounts, *userNames; accounts = [[self clientObject] mailAccounts]; userNames = [accounts objectsForKey: @"userName" notFoundMarker: nil]; return [userNames jsonRepresentation]; } - (NSString *) pageFormURL { NSString *u; NSRange r; u = [[[self context] request] uri]; if ((r = [u rangeOfString:@"?"]).length > 0) { /* has query parameters */ // TODO: this is ugly, create reusable link facility in SOPE // TODO: remove 'search' and 'filterpopup', preserve sorting NSMutableString *ms; NSArray *qp; NSUInteger i, count; qp = [[u substringFromIndex:(r.location + r.length)] componentsSeparatedByString:@"&"]; count = [qp count]; ms = [NSMutableString stringWithCapacity:count * 12]; for (i = 0; i < count; i++) { NSString *s; s = [qp objectAtIndex:i]; /* filter out */ if ([s hasPrefix:@"search="]) continue; if ([s hasPrefix:@"filterpopup="]) continue; if ([ms length] > 0) [ms appendString:@"&"]; [ms appendString:s]; } if ([ms length] == 0) { /* no other query params */ u = [u substringToIndex:r.location]; } else { u = [u substringToIndex:r.location + r.length]; u = [u stringByAppendingString:ms]; } return u; } return [u hasSuffix:@"/"] ? @"view" : @"#"; } - (id ) composeAction { id contact; NSArray *contactsId, *cards; NSString *newLocation, *parameters, *folderId, *uid, *formattedMail; NSEnumerator *uids; NSMutableArray *addresses; NGVCard *card; NGVList *list; SOGoMailAccounts *co; SOGoContactFolders *folders; SOGoParentFolder *folder; WORequest *request; NSUInteger i, count; parameters = nil; co = [self clientObject]; request = [context request]; if ((folderId = [request formValueForKey: @"folder"]) && (contactsId = [request formValuesForKey: @"uid"])) { // Retrieve the email addresses from the specified address book // and contact IDs folders = (SOGoContactFolders *)[[[self clientObject] container] privateContacts: @"Contacts" inContext: nil]; folder = [folders lookupName: folderId inContext: nil acquire: NO]; if (folder) { uids = [contactsId objectEnumerator]; uid = [uids nextObject]; addresses = [NSMutableArray array]; while (uid) { contact = [folder lookupName: uid inContext: [self context] acquire: NO]; if ([contact isKindOfClass: [SOGoContactGCSList class]]) { list = [(SOGoContactGCSList *)contact vList]; cards = [list cardReferences]; count = [cards count]; for (i = 0; i < count; i++) { formattedMail = [self formattedMailtoString: [cards objectAtIndex: i]]; if (formattedMail) [addresses addObject: formattedMail]; } } else if ([contact isKindOfClass: [SOGoContactGCSEntry class]] || [contact isKindOfClass: [SOGoContactLDIFEntry class]]) { // We fetch the preferred email address of the contact or // the first defined email address card = [contact vCard]; formattedMail = [self formattedMailtoString: card]; if (formattedMail) [addresses addObject: formattedMail]; } uid = [uids nextObject]; } if ([addresses count] > 0) parameters = [NSString stringWithFormat: @"?mailto=%@", [addresses jsonRepresentation]]; } } else if ([[request formValues] objectForKey: @"mailto"]) // We use the email addresses defined in the request parameters = [[request formValues] asURLParameters]; if (!parameters) // No parameter passed; simply open the compose window parameters = @"?mailto="; newLocation = [NSString stringWithFormat: @"%@/0/compose%@", [co baseURLInContext: context], parameters]; return [self redirectToLocation: newLocation]; } - (NSString *) formattedMailtoString: (NGVCard *) card { NSString *firstName, *lastName, *email; NSMutableString *fn, *rc = nil; CardElement *n; if ([card isKindOfClass: [NGVCard class]]) email = [card preferredEMail]; else email = [(NGVCardReference *)card email]; if (email == nil) email = (NSString*)[card firstChildWithTag: @"EMAIL"]; if (email) { email = [NSString stringWithFormat: @"<%@>", email]; // We append the contact's name fn = [NSMutableString stringWithString: [card fn]]; if ([fn length] == 0 && [card isKindOfClass: [NGVCard class]]) { n = [card n]; lastName = [n flattenedValueAtIndex: 0 forKey: @""]; firstName = [n flattenedValueAtIndex: 1 forKey: @""]; if ([firstName length] > 0) { if ([lastName length] > 0) fn = [NSMutableString stringWithFormat: @"%@ %@", firstName, lastName]; else fn = [NSMutableString stringWithString: firstName]; } else if ([lastName length] > 0) fn = [NSMutableString stringWithString: lastName]; } if ([fn length] > 0) { [fn appendFormat: @" %@", email]; rc = fn; } else rc = [NSMutableString stringWithString: email]; } return rc; } - (WOResponse *) getFoldersStateAction { id o; NSArray *expandedFolders; [self _setupContext]; o = [moduleSettings objectForKey: @"ExpandedFolders"]; if ([o isKindOfClass: [NSString class]]) expandedFolders = [o componentsSeparatedByString: @","]; else expandedFolders = o; return [self responseWithStatus: 200 andJSONRepresentation: expandedFolders]; } - (NSString *) verticalDragHandleStyle { NSString *vertical; [self _setupContext]; vertical = [moduleSettings objectForKey: @"DragHandleVertical"]; return ((vertical && [vertical intValue] > 0) ? (id)[vertical stringByAppendingString: @"px"] : nil); } - (NSString *) horizontalDragHandleStyle { NSString *horizontal; [self _setupContext]; horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"]; return ((horizontal && [horizontal intValue] > 0) ? (id)[horizontal stringByAppendingString: @"px"] : nil); } - (NSString *) mailboxContentStyle { NSString *height; [self _setupContext]; height = [moduleSettings objectForKey: @"DragHandleVertical"]; return ((height && [height intValue] > 0) ? (id)[NSString stringWithFormat: @"%ipx", ([height intValue] - 27)] : nil); } - (WOResponse *) saveDragHandleStateAction { WORequest *request; NSString *dragHandle; [self _setupContext]; request = [context request]; if ((dragHandle = [request formValueForKey: @"vertical"]) != nil) [moduleSettings setObject: dragHandle forKey: @"DragHandleVertical"]; else if ((dragHandle = [request formValueForKey: @"horizontal"]) != nil) [moduleSettings setObject: dragHandle forKey: @"DragHandleHorizontal"]; else return [self responseWithStatus: 400]; [us synchronize]; return [self responseWithStatus: 204]; } - (WOResponse *) saveFoldersStateAction { WORequest *request; NSArray *expandedFolders; [self _setupContext]; request = [context request]; expandedFolders = [[request contentAsString] objectFromJSONString]; [moduleSettings setObject: expandedFolders forKey: @"ExpandedFolders"]; [us synchronize]; return [self responseWithStatus: 204]; } - (NSString *) columnsState { NSDictionary *columns; [self _setupContext]; columns = [moduleSettings objectForKey: @"ColumnsState"]; return [columns jsonRepresentation]; } - (WOResponse *) saveColumnsStateAction { WORequest *request; NSDictionary *columns; NSArray *columnsIds, *widths; [self _setupContext]; request = [context request]; columnsIds = [[request formValueForKey: @"columns"] componentsSeparatedByString: @","]; widths = [[request formValueForKey: @"widths"] componentsSeparatedByString: @","]; if (columnsIds != nil && widths != nil && [columnsIds count] == [widths count]) { columns = [NSDictionary dictionaryWithObjects: widths forKeys: columnsIds]; [moduleSettings setObject: columns forKey: @"ColumnsState"]; } else return [self responseWithStatus: 400]; [us synchronize]; return [self responseWithStatus: 204]; } - (id) defaultAction { SOGoUserDefaults *ud; ud = [[context activeUser] userDefaults]; if ([ud rememberLastModule]) { [ud setLoginModule: @"Mail"]; [ud synchronize]; } return [super defaultAction]; } - (BOOL) showToAddress { SOGoMailFolder *co; if (!folderType) { co = [self clientObject]; if ([co isKindOfClass: [SOGoSentFolder class]] || [co isKindOfClass: [SOGoDraftsFolder class]]) folderType = 1; else folderType = -1; } return (folderType == 1); } - (NSDictionary *) columnsMetaData { NSMutableDictionary *columnsMetaData; NSArray *tmpColumns, *tmpKeys; columnsMetaData = [NSMutableDictionary dictionaryWithCapacity: 8]; tmpKeys = [NSArray arrayWithObjects: @"headerClass", @"headerId", @"value", nil]; tmpColumns = [NSArray arrayWithObjects: @"messageThreadColumn tbtv_headercell", @"invisibleHeader", @"Thread", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Thread"]; tmpColumns = [NSArray arrayWithObjects: @"messageSubjectColumn tbtv_headercell sortableTableHeader resizable", @"subjectHeader", @"Subject", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Subject"]; tmpColumns = [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", @"invisibleHeader", @"Flagged", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Flagged"]; tmpColumns = [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", @"attachmentHeader", @"Attachment", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Attachment"]; tmpColumns = [NSArray arrayWithObjects: @"messageFlagColumn tbtv_headercell", @"messageFlagHeader", @"Unread", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Unread"]; tmpColumns = [NSArray arrayWithObjects: @"messageAddressHeader tbtv_headercell sortableTableHeader resizable", @"toHeader", @"To", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"To"]; tmpColumns = [NSArray arrayWithObjects: @"messageAddressColumn tbtv_headercell sortableTableHeader resizable", @"fromHeader", @"From", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"From"]; tmpColumns = [NSArray arrayWithObjects: @"messageDateColumn tbtv_headercell sortableTableHeader resizable", @"dateHeader", @"Date", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Date"]; tmpColumns = [NSArray arrayWithObjects: @"messagePriorityColumn tbtv_headercell resizable", @"priorityHeader", @"Priority", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Priority"]; tmpColumns = [NSArray arrayWithObjects: @"messageSizeColumn tbtv_headercell sortableTableHeader", @"sizeHeader", @"Size", nil]; [columnsMetaData setObject: [NSDictionary dictionaryWithObjects: tmpColumns forKeys: tmpKeys] forKey: @"Size"]; return columnsMetaData; } - (NSArray *) columnsDisplayOrder { NSMutableArray *finalOrder, *invalid; NSArray *available; NSDictionary *metaData; SOGoUserDefaults *ud; NSUInteger i; if (!columnsOrder) { ud = [[context activeUser] userDefaults]; columnsOrder = [ud mailListViewColumnsOrder]; metaData = [self columnsMetaData]; invalid = [columnsOrder mutableCopy]; [invalid autorelease]; available = [metaData allKeys]; [invalid removeObjectsInArray: available]; if ([invalid count] > 0) { [self errorWithFormat: @"those column names specified in" @" SOGoMailListViewColumnsOrder are invalid: '%@'", [invalid componentsJoinedByString: @"', '"]]; [self errorWithFormat: @" falling back on hardcoded column order"]; columnsOrder = available; } finalOrder = [columnsOrder mutableCopy]; [finalOrder autorelease]; if (![ud mailSortByThreads]) [finalOrder removeObject: @"Thread"]; else { i = [finalOrder indexOfObject: @"Thread"]; if (i == NSNotFound) [finalOrder insertObject: @"Thread" atIndex: 0]; } if ([self showToAddress]) { i = [finalOrder indexOfObject: @"From"]; if (i != NSNotFound) { [finalOrder removeObject: @"To"]; [finalOrder replaceObjectAtIndex: i withObject: @"To"]; } } else { i = [finalOrder indexOfObject: @"To"]; if (i != NSNotFound) { [finalOrder removeObject: @"From"]; [finalOrder replaceObjectAtIndex: i withObject: @"From"]; } } columnsOrder = [[self columnsMetaData] objectsForKeys: finalOrder notFoundMarker: @""]; [columnsOrder retain]; } return columnsOrder; } - (NSString *) columnsDisplayCount { return [NSString stringWithFormat: @"%d", (int)[[self columnsDisplayOrder] count]]; } - (void) setCurrentColumn: (NSDictionary *) newCurrentColumn { ASSIGN (currentColumn, newCurrentColumn); } - (NSDictionary *) currentColumn { return currentColumn; } - (NSString *) columnTitle { return [self labelForKey: [currentColumn objectForKey: @"value"]]; } - (unsigned int) _unseenCountForFolder: (NSString *) theFolder { NSArray *pathComponents; SOGoMailAccount *account; SOGoMailFolder *folder; pathComponents = [theFolder pathComponents]; account = [[self clientObject] lookupName: [pathComponents objectAtIndex: 0] inContext: context acquire: YES]; folder = [account lookupNameByPaths: [pathComponents subarrayWithRange: NSMakeRange(1, [pathComponents count]-1)] inContext: context acquire: YES]; return [folder unseenCount]; } - (WOResponse *) unseenCountAction { NSMutableDictionary *data; WOResponse *response; NSArray *folders; NSString *folder; int i; folders = [[[[context request] contentAsString] objectFromJSONString] objectForKey: @"mailboxes"]; data = [NSMutableDictionary dictionary]; for (i = 0; i < [folders count]; i++) { folder = [folders objectAtIndex: i]; [data setObject: [NSNumber numberWithUnsignedInt: [self _unseenCountForFolder: folder]] forKey: folder]; } response = [self responseWithStatus: 200]; [response setHeader: @"text/plain; charset=utf-8" forKey: @"content-type"]; [response appendContentString: [data jsonRepresentation]]; return response; } - (NSString *) unseenCountFolders { NSArray *pathComponents, *filters, *actions; NSDictionary *d, *action; NSMutableArray *folders; NSMutableString *path; NSString *component; SOGoUserDefaults *ud; NSString *s; NSUInteger i, j, k; // If Sieve scripts aren't enabled, there's no need in considering // what's potentially in the database regarding "fileinto" scripts. if (![[[context activeUser] domainDefaults] sieveScriptsEnabled]) return [[NSArray array] jsonRepresentation]; ud = [[context activeUser] userDefaults]; folders = [NSMutableArray array]; filters = [ud sieveFilters]; for (i = 0; i < [filters count]; i++) { d = [filters objectAtIndex: i]; actions = [d objectForKey: @"actions"]; for (j = 0; j < [actions count]; j++) { action = [actions objectAtIndex: j]; if ([[action objectForKey: @"method"] caseInsensitiveCompare: @"fileinto"] == NSOrderedSame) { s = [action objectForKey: @"argument"]; // We format the result string so that MailerUI.js can simply consume that information // without doing anything special on its side if (s) { pathComponents = [s componentsSeparatedByString: @"/"]; path = [NSMutableString string]; for (k = 0; k < [pathComponents count]; k++) { component = [[pathComponents objectAtIndex: k] asCSSIdentifier]; [path appendString: [NSString stringWithFormat: @"folder%@", component]]; if (k < [pathComponents count] - 1) [path appendString: @"/"]; } [folders addObject: [NSString stringWithFormat: @"0/%@", path]]; } } } } return [folders jsonRepresentation]; } // // Standard mapping done by Thunderbird: // // label1 => Important // label2 => Work // label3 => Personal // label4 => To Do // label5 => Later // - (NSArray *) availableLabels { NSDictionary *v; v = [[[context activeUser] userDefaults] mailLabelsColors]; return [SOGoMailLabel labelsFromDefaults: v component: self]; } - (void) setCurrentLabel: (SOGoMailLabel *) theCurrentLabel { ASSIGN(_currentLabel, theCurrentLabel); } - (SOGoMailLabel *) currentLabel { return _currentLabel; } @end /* UIxMailMainFrame */ @interface UIxMailFolderTemplate : UIxComponent @end @implementation UIxMailFolderTemplate @end @interface UIxMailViewTemplate : UIxComponent @end @implementation UIxMailViewTemplate @end