diff --git a/ActiveSync/NGVCard+ActiveSync.m b/ActiveSync/NGVCard+ActiveSync.m index 9ac476969..bb4f29248 100644 --- a/ActiveSync/NGVCard+ActiveSync.m +++ b/ActiveSync/NGVCard+ActiveSync.m @@ -40,6 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import +#import + #include "NSDate+ActiveSync.h" #include "NSString+ActiveSync.h" @@ -199,6 +201,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendFormat: @"%@", o]; [s appendString: @""]; } + + if ((o = [self photo])) + [s appendFormat: @"%@", o]; return s; } @@ -285,13 +290,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ((o = [theValues objectForKey: @"Email1Address"])) { element = [self elementWithTag: @"email" ofType: @"work"]; - [element setSingleValue: o forKey: @""]; + [element setSingleValue: [o pureEMailAddress] forKey: @""]; } if ((o = [theValues objectForKey: @"Email2Address"])) { element = [self elementWithTag: @"email" ofType: @"home"]; - [element setSingleValue: o forKey: @""]; + [element setSingleValue: [o pureEMailAddress] forKey: @""]; } // SOGo currently only supports 2 email addresses ... but AS clients might send 3 @@ -299,7 +304,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ((o = [theValues objectForKey: @"Email3Address"])) { element = [self elementWithTag: @"email" ofType: @"three"]; - [element setSingleValue: o forKey: @""]; + [element setSingleValue: [o pureEMailAddress] forKey: @""]; } // Formatted name @@ -346,6 +351,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ((o = [theValues objectForKey: @"NickName"])) [self setNickname: o]; + + if ((o = [theValues objectForKey: @"Picture"])) + [self setPhoto: o]; + } @end diff --git a/ActiveSync/NSString+ActiveSync.h b/ActiveSync/NSString+ActiveSync.h index f3f95b07b..172d71eb1 100644 --- a/ActiveSync/NSString+ActiveSync.h +++ b/ActiveSync/NSString+ActiveSync.h @@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (NSCalendarDate *) calendarDate; - (NSString *) deviceId; - (NSString *) deviceType; +- (NSString *) attachmentName; - (NSString *) command; - (NSData *) convertHexStringToBytes; diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m index b1fba0f4c..3d1a2b86b 100644 --- a/ActiveSync/NSString+ActiveSync.m +++ b/ActiveSync/NSString+ActiveSync.m @@ -186,6 +186,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return s; } +// This method extracts the "AttachmentName" from a URI: +// +// /SOGo/Microsoft-Server-ActiveSync?Cmd=GetAttachment&User=sogo&DeviceId=HTCa04b4932597acd3f2dc1a918b9728&DeviceType=htcvision&AttachmentName=mail/TestFldr/8/2 +// +- (NSString *) attachmentName +{ + NSString *s; + + s = [self _valueForParameter: @"ATTACHMENTNAME="]; + + if (!s) + s = @"Unknown"; + + return s; +} + + // // // diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 3102e92e8..024b2a2a7 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -123,8 +123,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [o setTableUrl: [self folderTableURL]]; [o reloadIfNeeded]; + [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] addEntriesFromDictionary: theFolderMetadata]; [o save]; @@ -476,20 +478,70 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. lastServerKey: (NSString **) theLastServerKey { + NSMutableDictionary *folderMetadata, *dateCache, *syncCache; NSMutableString *s; BOOL more_available; int i, max; + s = [NSMutableString string]; + + more_available = NO; + + if (theFolderType == ActiveSyncMailFolder && !([theSyncKey isEqualToString: @"-1"]) && theFilterType) + { + NSArray *allKeys; + NSString *key; + int softdelete_count; + + softdelete_count = 0; + + folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + + allKeys = [dateCache allKeys]; + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + + if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending) + { + [s appendString: @""]; + [s appendFormat: @"%@", key]; + [s appendString: @""]; + + [syncCache removeObjectForKey: key]; + [dateCache removeObjectForKey: key]; + + softdelete_count++; + } + + if (softdelete_count >= theWindowSize) + { + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; + + more_available = YES; + *theLastServerKey = theSyncKey; + + // Since WindowSize is reached don't even try to add more to the response, let's just + // jump to the end and return the response immediately + goto return_response; + } + } + + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; + } + // // No changes in the collection - 2.2.2.19.1.1 Empty Sync Request. // We check this and we don't generate any commands if we don't have to. // - if ([theSyncKey isEqualToString: [theCollection davCollectionTag]]) + if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length])) return; - s = [NSMutableString string]; - more_available = NO; switch (theFolderType) @@ -602,12 +654,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; } } // for ... + + folderMetadata = [NSDictionary dictionaryWithObject: [theCollection davCollectionTag] + forKey: @"SyncKey"]; + [self _setFolderMetadata: folderMetadata + forKey: [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]]]; } break; case ActiveSyncMailFolder: default: { - NSMutableDictionary *syncCache, *dateCache, *folderMetadata; SOGoSyncCacheObject *lastCacheObject, *aCacheObject; NSMutableArray *allCacheObjects, *sortedBySequence; @@ -637,6 +693,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; } + // Check whether GUID in cache is equal to the GUID from imap - this is to avoid cache corruptions if a folder has been renamed and a new folder // with the same name has been created but folderSync has not yet updated the cache if (!([[theCollection nameInContainer] isEqualToString: @@ -782,17 +839,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } if (more_available) - [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + { + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + [folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"]; + } else - [folderMetadata removeObjectForKey: @"MoreAvailable"]; + { + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + [folderMetadata setObject: [theCollection davCollectionTag] forKey: @"SyncKey"]; + } - [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; } // default: break; } // switch (folderType) ... + return_response: + if ([s length]) { [theBuffer appendString: @""]; @@ -996,7 +1060,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. davCollectionTag = [collection davCollectionTag]; } - // Generate the response buffer [theBuffer appendString: @""]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 63e4b54ee..82c1868fc 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -64,6 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import #import @@ -122,6 +123,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +@interface SOGoActiveSyncDispatcher (Sync) + +- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey; + +@end + @implementation SOGoActiveSyncDispatcher - (id) init @@ -164,6 +171,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return [o properties]; } +- (unsigned int) _softDeleteCountWithFilter: (NSCalendarDate *) theFilter + collectionId: (NSString *) theCollectionId +{ + NSMutableDictionary *dateCache; + NSMutableArray *sdUids; + SOGoCacheGCSObject *o; + NSArray *allKeys; + NSString *key; + + int i; + + sdUids = [NSMutableArray array]; + + if (theFilter) + { + o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + dateCache = [[o properties] objectForKey: @"DateCache"]; + allKeys = [dateCache allKeys]; + + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + + if ([[dateCache objectForKey:key] compare: theFilter ] == NSOrderedAscending) + [sdUids addObject: [dateCache objectForKey:key]]; + } + } + + return [sdUids count]; +} + - (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate type: (SOGoMicrosoftActiveSyncFolderType) theFolderType { @@ -409,10 +451,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (!error) { + NSString *syncKey, *key; + SOGoCacheGCSObject *o; NSMutableString *s; - NSString *syncKey; NSData *d; - + + // + // We mark the cache object as deleted + // + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [folderToDelete nameInContainer]]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + [o delete]; + // // We update the FolderSync's synckey // @@ -484,10 +536,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Handle new name exist if (!error) { + NSString *syncKey, *key; + SOGoCacheGCSObject *o; NSMutableString *s; - NSString *syncKey; NSData *d; + // + // We update our cache + // + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], serverId]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + key = [NSString stringWithFormat: @"/%@+%@", [context objectForKey: @"DeviceId"], [folderToUpdate nameInContainer]]; + [o changePathTo: key]; + // // We update the FolderSync's synckey // @@ -499,8 +563,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [self _setFolderSyncKey: syncKey]; - // FIXME - TODO: We *MUST* update the path in our cache! See -changePathTo in SOGoCacheGCSObject - s = [NSMutableString string]; [s appendString: @""]; [s appendString: @""]; @@ -781,7 +843,51 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processGetAttachment: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { + NSString *fileReference, *realCollectionId; + SOGoMicrosoftActiveSyncFolderType folderType; + + fileReference = [context objectForKey: @"AttachmentName"]; + + realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; + + if (folderType == ActiveSyncMailFolder) + { + id currentFolder, currentCollection, currentBodyPart; + NSString *folderName, *messageName, *pathToPart; + SOGoMailAccounts *accountsFolder; + SOGoUserFolder *userFolder; + SOGoMailObject *mailObject; + + NSRange r1, r2; + + r1 = [realCollectionId rangeOfString: @"/"]; + r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)]; + + folderName = [realCollectionId substringToIndex: r1.location]; + messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)]; + pathToPart = [realCollectionId substringFromIndex: r2.location+1]; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] + inContext: context + acquire: NO]; + + mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; + currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; + + [theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]] + forKey: @"Content-Type"]; + + [theResponse setContent: [currentBodyPart fetchBLOB] ]; + } + else + { + [theResponse setStatus: 500]; + } } // @@ -849,6 +955,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sortOrdering: @"REVERSE ARRIVAL" threaded: NO]; count = [uids count]; + + // Add the number of UIDs expected to "soft delete" + count += [self _softDeleteCountWithFilter: filter collectionId: realCollectionId]; + } else { @@ -891,7 +1001,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fileReference = [[[(id)[theDocumentElement getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; - realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; if (folderType == ActiveSyncMailFolder) { @@ -1218,30 +1327,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } // -// Ping requests make a little sense because the request -// doesn't contain the SyncKey on the client. So we can't -// really know if something has changed on the server. What we -// do for now is simply return Status=5 with the HeartbeatInterval -// set at 60 seconds or we wait 60 seconds before responding with -// Status=1 +// +// +// +// 3540 +// +// +// mail%2Fsogo_680f_193506d5_0 +// Email +// +// +// vevent/personal +// Calendar +// +// +// vcard/personal +// Contacts +// +// +// mail%2Fsogo_680f_193506d5_1 +// Email +// +// +// mail%2Fsogo_680f_193506d5_2 +// Email +// +// +// vtodo/personal +// Tasks +// +// +// mail%2Fsogo_753e_193511a1_0 +// Email +// +// +// mail%2Fsogo_753e_193511a1_1 +// Email +// +// +// // - (void) processPing: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { + NSString *collectionId, *realCollectionId, *syncKey; + NSMutableArray *foldersWithChanges, *allFoldersID; + SOGoMicrosoftActiveSyncFolderType folderType; + NSMutableDictionary *folderMetadata; SOGoSystemDefaults *defaults; + id aCollection; + NSArray *allCollections; + NSMutableString *s; + id collection; NSData *d; - int heartbeatInterval, defaultInterval, status; + + int i, j, heartbeatInterval, defaultInterval, internalInterval, status; defaults = [SOGoSystemDefaults sharedSystemDefaults]; defaultInterval = [defaults maximumPingInterval]; + internalInterval = [defaults internalSyncInterval]; if (theDocumentElement) heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue]; else heartbeatInterval = defaultInterval; - + if (heartbeatInterval > defaultInterval || heartbeatInterval == 0) { heartbeatInterval = defaultInterval; @@ -1252,15 +1404,116 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. status = 1; } - NSLog(@"Got Ping request with valid interval - sleeping for %d seconds.", heartbeatInterval); - sleep(heartbeatInterval); + // We build the list of folders to "ping". When the payload is empty, we use the list + // of "cached" folders. + allCollections = (id)[theDocumentElement getElementsByTagName: @"Folders"]; + allFoldersID = [NSMutableArray array]; + if (![allCollections count]) + { + SOGoMailAccounts *accountsFolder; + SOGoMailAccount *accountFolder; + SOGoUserFolder *userFolder; + NSArray *allValues; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + allValues = [[accountFolder imapFolderGUIDs] allValues]; + + for (i = 0; i < [allValues count]; i++) + [allFoldersID addObject: [NSString stringWithFormat: @"mail/%@", [allValues objectAtIndex: i]]]; + + + // FIXME: handle multiple GCS collecitons + [allFoldersID addObject: @"vcard/personal"]; + [allFoldersID addObject: @"vevent/personal"]; + [allFoldersID addObject: @"vtodo/personal"]; + } + else + { + for (i = 0; i < [allCollections count]; i++) + { + aCollection = [allCollections objectAtIndex: i]; + collectionId = [[(id) [aCollection getElementsByTagName: @"Id"] lastObject] textValue]; + [allFoldersID addObject: collectionId]; + } + } + + foldersWithChanges = [NSMutableArray array]; + + // We enter our loop detection change + for (i = 0; i < (heartbeatInterval/internalInterval); i++) + { + for (j = 0; j < [allFoldersID count]; j++) + { + collectionId = [allFoldersID objectAtIndex: j]; + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; + collection = [self collectionFromId: realCollectionId type: folderType]; + + switch (folderType) + { + case ActiveSyncContactFolder: + folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vcard/%@", [collection nameInContainer]]]; + break; + case ActiveSyncEventFolder: + folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vevent/%@", [collection nameInContainer]]]; + break; + case ActiveSyncTaskFolder: + folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vtodo/%@", [collection nameInContainer]]]; + break; + default: + folderMetadata = [self _folderMetadataForKey: [collection nameInContainer]]; + } + + syncKey = [folderMetadata objectForKey: @"SyncKey"]; + + if (![syncKey isEqualToString: [collection davCollectionTag]]) + { + [foldersWithChanges addObject: collectionId]; + } + } + + if ([foldersWithChanges count]) + { + NSLog(@"Change detected, we push the content."); + status = 2; + break; + } + else + { + NSLog(@"Sleeping %d seconds while detecting changes...", internalInterval); + sleep(internalInterval); + } + } + // We generate our response s = [NSMutableString string]; [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; [s appendFormat: @"%d", status]; + + if ([foldersWithChanges count]) + { + [s appendString: @""]; + + for (i = 0; i < [foldersWithChanges count]; i++) + { + // A bit tricky here because we must call stringByEscapingURL on mail folders, but not on GCS ones. + // We do the same thing in -processFolderSync + collectionId = [foldersWithChanges objectAtIndex: i]; + + if ([collectionId hasPrefix: @"mail/"]) + collectionId = [collectionId stringByEscapingURL]; + + [s appendFormat: @"%@", collectionId]; + } + + [s appendString: @""]; + } if (status == 5) { @@ -1686,9 +1939,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. value = [theDocumentElement getElementsByTagName: @"ReplaceMime"]; - // ReplaceMime isn't specified so we must NOT use the server copy + // ReplaceMime IS specified so we must NOT use the server copy // but rather take the data as-is from the client. - if (![value count]) + if ([value count]) { [self processSendMail: theDocumentElement inResponse: theResponse]; @@ -1708,11 +1961,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSData *data; NGMimeMessageGenerator *generator; - NGMimeBodyPart *bodyPart; + NGMimeBodyPart *bodyPart; NGMutableHashMap *map; NGMimeFileData *fdata; NSException *error; - id body; + + id body, bodyFromSmartForward; userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; @@ -1728,10 +1982,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. parser = [[NGMimeMessageParser alloc] init]; data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]; messageFromSmartForward = [parser parsePartFromData: data]; - RELEASE(parser); - // We create a new MIME multipart/mixed message. The first part will be the text part // of our "smart forward" and the second part will be the message/rfc822 part of the // "smart forwarded" message. @@ -1741,11 +1993,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease]; - // First part + // First part - either a text/* or a multipart/*. If it's a multipart, + // we take the first part text/* part we see. map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; - [map setObject: @"text/plain" forKey: @"content-type"]; + bodyFromSmartForward = nil; + + if ([[messageFromSmartForward body] isKindOfClass: [NGMimeMultipartBody class]]) + { + NGMimeBodyPart *part; + NSArray *parts; + int i; + + parts = [[messageFromSmartForward body] parts]; + + for (i = 0; i < [parts count]; i++) + { + part = [parts objectAtIndex: i]; + + if ([[[part contentType] type] isEqualToString: @"text"]) + { + [map setObject: [[part contentType] stringValue] forKey: @"content-type"]; + bodyFromSmartForward = [part body]; + break; + } + } + } + else + { + [map setObject: [[messageFromSmartForward contentType] stringValue] forKey: @"content-type"]; + bodyFromSmartForward = [messageFromSmartForward body]; + } + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; - [bodyPart setBody: [messageFromSmartForward body]]; + [bodyPart setBody: bodyFromSmartForward]; [body addBodyPart: bodyPart]; // Second part @@ -1826,6 +2106,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. deviceId = [[theRequest uri] deviceId]; [context setObject: deviceId forKey: @"DeviceId"]; [context setObject: [[theRequest uri] deviceType] forKey: @"DeviceType"]; + [context setObject: [[theRequest uri] attachmentName] forKey: @"AttachmentName"]; + cmdName = [[theRequest uri] command]; @@ -1869,7 +2151,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { // We check if it's a Ping command with no body. // See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details - if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame) + if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame) return [NSException exceptionWithHTTPStatus: 500]; } @@ -1923,9 +2205,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([parts count] == 5) { /* If "OCSFolderInfoURL" is properly configured, we must have 5 - parts in this url. */ + parts in this url. We strip the '-' character in case we have + this in the domain part - like foo@bar-zot.com */ ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@", - [[user loginInDomain] asCSSIdentifier]]; + [[[user loginInDomain] asCSSIdentifier] + stringByReplacingOccurrencesOfString: @"-" + withString: @"_"]]; [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; folderTableURL = [NSURL URLWithString: [parts componentsJoinedByString: @"/"]]; diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 7b0a008a4..e5f349092 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -238,6 +238,8 @@ struct GlobalObjectId { if (key) { + NSString *s, *charset; + d = [[self fetchPlainTextParts] objectForKey: key]; encoding = [[self lookupInfoForBodyPart: key] objectForKey: @"encoding"]; @@ -246,6 +248,14 @@ struct GlobalObjectId { d = [d dataByDecodingBase64]; else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame) d = [d dataByDecodingQuotedPrintableTransferEncoding]; + + charset = [[[self lookupInfoForBodyPart: key] objectForKey: @"parameterList"] objectForKey: @"charset"]; + + if (![charset length]) + charset = @"us-ascii"; + + s = [NSString stringWithData: d usingEncodingNamed: charset]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } return d; @@ -296,13 +306,13 @@ struct GlobalObjectId { if ([body isKindOfClass: [NSData class]]) { NSString *charset; - int encoding; - + charset = [[thePart contentType] valueOfParameter: @"charset"]; - encoding = [NGMimeType stringEncodingForCharset: charset]; + + if (![charset length]) + charset = @"us-ascii"; - s = [[NSString alloc] initWithData: body encoding: encoding]; - AUTORELEASE(s); + s = [NSString stringWithData: body usingEncodingNamed: charset]; } else { @@ -396,6 +406,13 @@ struct GlobalObjectId { { if ([type isEqualToString: @"text"]) { + NSString *s, *charset; + + charset = [[[self lookupInfoForBodyPart: @""] objectForKey: @"parameterList"] objectForKey: @"charset"]; + + if (![charset length]) + charset = @"us-ascii"; + d = [[self fetchPlainTextParts] objectForKey: @""]; // We check if we have base64 encoded parts. If so, we just @@ -407,17 +424,15 @@ struct GlobalObjectId { else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame) d = [d dataByDecodingQuotedPrintableTransferEncoding]; + s = [NSString stringWithData: d usingEncodingNamed: charset]; + // Check if we must convert html->plain if (theType == 1 && [subtype isEqualToString: @"html"]) { - NSString *s; - - s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; - AUTORELEASE(s); - s = [s htmlToText]; - d = [s dataUsingEncoding: NSUTF8StringEncoding]; } + + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } else if ([type isEqualToString: @"multipart"]) { @@ -761,7 +776,7 @@ struct GlobalObjectId { // Flags [s appendString: @""]; - [s appendFormat: @"%d", 0]; + [s appendFormat: @"%d", ([self flagged] ? 2 : 0)]; [s appendString: @""]; // FIXME - support these in the future @@ -799,10 +814,21 @@ struct GlobalObjectId { if ((o = [theValues objectForKey: @"Flag"])) { - o = [o objectForKey: @"FlagStatus"]; - - if ([o intValue]) - [self addFlags: @"\\Flagged"]; + // We must handle empty flags -> {Flag = ""; } - some ActiveSync clients, like the HTC Desire + // will send an empty Flag message when "unflagging" a mail. + if (([o isKindOfClass: [NSMutableDictionary class]])) + { + if ((o = [o objectForKey: @"FlagStatus"])) + { + // 0 = The flag is cleared. + // 1 = The status is set to complete. + // 2 = The status is set to active. + if (([o isEqualToString: @"2"])) + [self addFlags: @"\\Flagged"]; + else + [self removeFlags: @"\\Flagged"]; + } + } else [self removeFlags: @"\\Flagged"]; } diff --git a/ChangeLog b/ChangeLog index 40ae66810..7c75ed5a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,454 @@ +commit 5dd2088d42388b397e8cc3fde35455b2455f7f3e +Author: Ludovic Marcotte +Date: Wed Jul 2 11:32:06 2014 -0400 + + Updated for the release + +M NEWS +M Version + +commit 9c3c2cb92657809945eeaf25310039173efb58b0 +Author: Ludovic Marcotte +Date: Wed Jul 2 11:23:57 2014 -0400 + + Updated the doc for the release + +M Documentation/SOGo Installation Guide.odt +M Documentation/SOGo Mozilla Thunderbird Configuration.odt +M Documentation/SOGo Native Microsoft Outlook Configuration.odt + +commit 55422b5f037b1c410c25a685afa6d3077c3fce21 +Author: Ludovic Marcotte +Date: Wed Jul 2 10:11:13 2014 -0400 + + Fixed typo + +M NEWS + +commit 899a1eb026503165436a333110fa6eb73522c5a6 +Author: Ludovic Marcotte +Date: Wed Jul 2 08:39:01 2014 -0400 + + Updated NEWS file + +M NEWS + +commit 660f39a980513bf9bcff14bdc8d74dd092c50717 +Author: Alexandre Cloutier +Date: Mon Jun 30 14:13:52 2014 -0400 + + delete notice and put grey background for multi-columns view + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/WebServerResources/SchedulerUI.js + +commit 8335c388308d381afc8b4b544f0b9b88e2140c25 +Author: Alexandre Cloutier +Date: Mon Jun 30 13:57:10 2014 -0400 + + fix infinite loop bug + +M UI/WebServerResources/SchedulerUI.js + +commit 0fb7135335810a31c8936a3e58680f93c550b0c2 +Author: Alexandre Cloutier +Date: Mon Jun 30 13:36:12 2014 -0400 + + bugfixes + +M UI/Scheduler/UIxCalListingActions.m +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxCalViewPrint.js +A UI/WebServerResources/error.png +A UI/WebServerResources/notice.png +A UI/WebServerResources/success.png +A UI/WebServerResources/warning.png + +commit 6b58e034f3db20f3433eccfd425f0e57349aeced +Author: Alexandre Cloutier +Date: Mon Jun 30 10:42:07 2014 -0400 + + printView display the parentView by default + +M UI/Scheduler/UIxCalViewPrint.h +M UI/Scheduler/UIxCalViewPrint.m +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/UIxCalViewPrint.js + +commit ff015afde8700b0a4163c89ce4b9cdca5f6261e5 +Author: Ludovic Marcotte +Date: Mon Jun 30 10:35:55 2014 -0400 + + Fixed indent + +M UI/PreferencesUI/UIxPreferences.m + +commit 6da03bc75ca64a79785001897eb629b6250b2cb8 +Author: Ludovic Marcotte +Date: Sat Jun 28 08:31:07 2014 -0400 + + Cleaned up code for older gcc + +M UI/Scheduler/UIxCalListingActions.m + +commit 2b119c63e0bd0fa96361369152a5aface05ffe32 +Author: Alexandre Cloutier +Date: Fri Jun 27 17:21:18 2014 -0400 + + Applied comments + +M UI/Scheduler/UIxCalDayTable.m +M UI/Scheduler/UIxCalListingActions.m + +commit 5b61df8ca4035d3bd66bb024a1f5b62cd6054a84 +Author: Alexandre Cloutier +Date: Fri Jun 27 17:00:32 2014 -0400 + + activeTasks updates dynamically + +M UI/Scheduler/UIxCalListingActions.m +M UI/Scheduler/product.plist +M UI/WebServerResources/SchedulerUI.js + +commit 5b9c7e07d1d6b6eba1901d3fa40ee22cbbf73ead +Author: Alexandre Cloutier +Date: Thu Jun 26 21:22:12 2014 -0400 + + text wraping and text alignment in list view + +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/generic.css + +commit 7a69bc46eb5c2300e93d4b85c6954638a4271132 +Author: Alexandre Cloutier +Date: Thu Jun 26 16:42:05 2014 -0400 + + applied comments + +M SoObjects/Appointments/SOGoAppointmentFolder.m +M UI/Scheduler/UIxCalendarSelector.m +M UI/WebServerResources/SchedulerUI.js + +commit 302e40a1c3c1f13578499d82e40c75e50beff79e +Author: Alexandre Cloutier +Date: Thu Jun 26 16:08:47 2014 -0400 + + bugFix#2823 + +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/SOGo/SOGoParentFolder.m +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/WebServerResources/UIxPreferences.js + +commit b6be4e6e2fb8e00c8f0b29e4f493936d0fcfbb2e +Author: Ludovic Marcotte +Date: Thu Jun 26 11:01:16 2014 -0400 + + Applied patch from bug #2810 + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS + +commit dabd8d3282c1e1f4a4d3622637c7952e1e295cb5 +Author: Alexandre Cloutier +Date: Thu Jun 26 10:05:48 2014 -0400 + + Fix list view printing with multiColumns view + +M UI/WebServerResources/UIxCalViewPrint.js + +commit d7d458a934b407bea6aa7e1818e71b5d544cf705 +Author: Alexandre Cloutier +Date: Wed Jun 25 17:39:48 2014 -0400 + + multiColumns print view + bugFixes + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/UIxCalViewPrint.m +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 5419f411e5bae58cc17ab22b3b95a88efab74252 +Author: Ludovic Marcotte +Date: Wed Jun 25 15:05:25 2014 -0400 + + Fix for bug #2734 + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 22f5ed62fef337f9fa2f84318e3c24962ee3c207 +Author: Ludovic Marcotte +Date: Wed Jun 25 10:14:28 2014 -0400 + + Fixed tests regarding default permissions on CalDAV collections + +M Tests/Integration/test-davacl.py + +commit 3523dc489f00c147279217faab9ad12504ca7340 +Author: Ludovic Marcotte +Date: Mon Jun 23 12:19:02 2014 -0400 + + Fix for bug #2825 + +M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m + +commit 5b3de04fc19daef85749fe34b969724f78a6b7ab +Author: Alexandre Cloutier +Date: Fri Jun 20 13:27:04 2014 -0400 + + Fix error from the last commit + +M UI/Scheduler/UIxCalDayTable.m + +commit eb3cc8916df223dae773243dcbcf65269e8b0451 +Author: Alexandre Cloutier +Date: Fri Jun 20 13:16:55 2014 -0400 + + The multiColumns view + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +M UI/Scheduler/UIxCalDayTable.h +M UI/Scheduler/UIxCalDayTable.m +M UI/Scheduler/UIxCalListingActions.h +M UI/Scheduler/UIxCalListingActions.m +M UI/Scheduler/UIxCalMulticolumnDayView.m +M UI/Scheduler/UIxCalendarSelector.m +M UI/Templates/SchedulerUI/UIxCalDayTable.wox +M UI/Templates/SchedulerUI/UIxCalDayView.wox +M UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox +M UI/Templates/SchedulerUI/UIxCalWeekView.wox +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/SchedulerUI.js + +commit 9fa0b42e19c76184d4195a1530039dcad5cb5a80 +Author: Ludovic Marcotte +Date: Fri Jun 20 11:25:02 2014 -0400 + + Ignore SCHEDULE-AGENT for Thunderbird/Lightning + +M NEWS +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit e048346cb715d03fe6bd32d837b96107fdb6d44b +Author: Ludovic Marcotte +Date: Thu Jun 19 16:08:15 2014 -0400 + + Include, in our private NS, all ACLs defined on a calendar collectiob + +M SoObjects/Appointments/SOGoAppointmentFolder.m + +commit 333b561a5932acbe66d5b62c8963dd166b9de7f2 +Author: Ludovic Marcotte +Date: Mon Jun 16 15:30:26 2014 -0400 + + Applied patch from bug #2736 + +M SOPE/NGCards/NGCardsSaxHandler.m +M SOPE/NGCards/samples/vcf2xml.m +M SoObjects/Mailer/NSString+Mail.m +M UI/MailPartViewers/UIxMailPartHTMLViewer.m + +commit 50c0b49a6fe210b438d2e5a7abac7219bdb15b8f +Author: Ludovic Marcotte +Date: Mon Jun 16 11:00:39 2014 -0400 + + Fix for bug #2813 + +M NEWS +M SoObjects/SOGo/SOGoGCSFolder.m + +commit 8202d0588fb8e1774e502941fc01223fb9005334 +Author: Francis Lachapelle +Date: Mon Jun 16 10:20:02 2014 -0400 + + Add documentation for SOGoSubscriptionFolderFormat + + Fixes #2799 + +M Documentation/SOGo Installation Guide.odt + +commit d9b578b869da27b876f019bc10acf72839220fd3 +Author: Francis Lachapelle +Date: Mon Jun 16 09:52:29 2014 -0400 + + Wait 20 seconds before deleting temporary forms + + Those temporary forms are created to download attachments. + Fixes #2811 + +M NEWS +M UI/WebServerResources/MailerUI.js + +commit 5bb64916c6fc96bf20bc258a075c33413784ca01 +Author: Francis Lachapelle +Date: Thu Jun 12 09:09:08 2014 -0400 + + Fix regression when attaching files to a reply + +M NEWS +M UI/MailerUI/UIxMailEditor.m + +commit e7ddfb0546fcb70f1ac3401375656936ea00c380 +Author: Ludovic Marcotte +Date: Wed Jun 11 12:55:51 2014 -0400 + + Fix for bug #2805 + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 0e182f84360aa35c87d39320cab08d7eaeb55d90 +Author: Alexandre Cloutier +Date: Wed Jun 11 11:25:17 2014 -0400 + + Update SOGoAppointmentFolder.m + + Fix the status filtering from 2 to 1 + +M SoObjects/Appointments/SOGoAppointmentFolder.m + +commit de2ee2322af740251ac82ca06deb1f7585637024 +Author: Alexandre Cloutier +Date: Wed Jun 11 10:24:04 2014 -0400 + + Change the NSString with NSNumber and add some JavaScript + +M SoObjects/Appointments/SOGoAppointmentFolder.h +M SoObjects/Appointments/SOGoAppointmentFolder.m +M UI/Scheduler/UIxCalendarSelector.m +M UI/Templates/SchedulerUI/UIxCalendarSelector.wox +M UI/WebServerResources/SchedulerUI.js + +commit ce07695a5597964354404557c9246eee5325abb2 +Author: Ludovic Marcotte +Date: Tue Jun 10 16:35:36 2014 -0400 + + Make sure to keep the / path prefix + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 43ac57b671ecf4f0d81daf3e40fab856a5d3b52a +Author: Alexandre Cloutier +Date: Tue Jun 10 16:18:12 2014 -0400 + + display the number of active tasks in calendar view + +M SoObjects/Appointments/SOGoAppointmentFolder.h +M SoObjects/Appointments/SOGoAppointmentFolder.m +M UI/Scheduler/UIxCalendarSelector.m +M UI/Templates/SchedulerUI/UIxCalendarSelector.wox + +commit 7f7e3cb06a6b8789a1cd1667fb62418fad02aac1 +Author: Francis Lachapelle +Date: Tue Jun 10 14:48:25 2014 -0400 + + Fix folder name of new subscriptions + + Fixes #2801 + +M NEWS +M UI/MainUI/SOGoUserHomePage.m +M UI/WebServerResources/UIxContactsUserFolders.js + +commit 6b3a3f858baf2b73a7910a2039103f2976ebdc7c +Author: Francis Lachapelle +Date: Mon Jun 9 22:07:44 2014 -0400 + + Update prototype.js to version 1.7.2 + +M UI/WebServerResources/prototype.js + +commit 0133bb3b6bf13c81350b791be57c01d83356cba7 +Author: Ludovic Marcotte +Date: Tue Jun 10 13:07:14 2014 -0400 + + Correctly update the cache on folder update/delete ops. + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 91e3e19e40b843063cda21b84e6fcc072da2ad49 +Author: Ludovic Marcotte +Date: Tue Jun 10 11:04:27 2014 -0400 + + Added the Ping ActiveSync command + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 19a95a1818e34755bffd7c5f9dbd35e214123cac +Author: Ludovic Marcotte +Date: Tue Jun 10 08:58:44 2014 -0400 + + Applied fix from #2809 + +M NEWS +M UI/MailPartViewers/UIxMailPartHTMLViewer.m + +commit 1292c191eca61027b9856b04840105fde588d42c +Author: Ludovic Marcotte +Date: Tue Jun 10 08:46:35 2014 -0400 + + Make sure we don't have '-' in table names. + +M OpenChange/MAPIStoreUserContext.m + +commit 77dad178d11b36da07660a9b5f0dd8c7a74cad50 +Author: Ludovic Marcotte +Date: Tue Jun 10 08:45:35 2014 -0400 + + Make sure we don't have '-' in table names. + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit f48839bf55917cb3a9756134930206b7b15d0279 +Author: Ludovic Marcotte +Date: Mon Jun 9 09:34:00 2014 -0400 + + Added (slightly modified) patch from #2806 + +M ActiveSync/SOGoMailObject+ActiveSync.m +M NEWS +M SoObjects/Mailer/SOGoMailObject.h +M SoObjects/Mailer/SOGoMailObject.m + +commit 0f04be2bcb003ff0b0c1099c524dffa3b40d0181 +Author: Ludovic Marcotte +Date: Mon Jun 9 09:25:06 2014 -0400 + + Added patch from #2808 + +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 3f8bbe8c6012e60334002affded0c94151db20e3 +Author: Ludovic Marcotte +Date: Mon Jun 9 09:14:30 2014 -0400 + + Applied patch from #2807 + +M ActiveSync/NGVCard+ActiveSync.m +M NEWS +M SOPE/NGCards/NGVCard.h +M SOPE/NGCards/NGVCard.m + +commit 6b6ec8a2ae0f453dd0a127d8cf37082c80449c94 +Author: Francis Lachapelle +Date: Thu Jun 5 13:35:17 2014 -0400 + + Update ChangeLog + +M ChangeLog + commit ad30df26af2c95f91ad42fd02a3b45ada115502b Author: Francis Lachapelle Date: Thu Jun 5 13:34:40 2014 -0400 diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index aaaf94024..81fbe0883 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/Documentation/SOGo Mozilla Thunderbird Configuration.odt b/Documentation/SOGo Mozilla Thunderbird Configuration.odt index a25587c33..97588c202 100644 Binary files a/Documentation/SOGo Mozilla Thunderbird Configuration.odt and b/Documentation/SOGo Mozilla Thunderbird Configuration.odt differ diff --git a/Documentation/SOGo Native Microsoft Outlook Configuration.odt b/Documentation/SOGo Native Microsoft Outlook Configuration.odt index e143bfd41..7651f52ed 100644 Binary files a/Documentation/SOGo Native Microsoft Outlook Configuration.odt and b/Documentation/SOGo Native Microsoft Outlook Configuration.odt differ diff --git a/NEWS b/NEWS index e9dd332da..4b233c364 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,28 @@ +2.2.6 (2014-07-02) +------------------ + +New features + - add new 'multi-columns' calendar view (#1948) + +Enhancements + - contacts photos are now synchronized using ActiveSync (#2807) + - implemented the GetAttachment ActiveSync command (#2808) + - implemented the Ping ActiveSync command + - added "soft deletes" support for ActiveSync (#2734) + - now display the active tasks next to calendar names (#2760) + +Bug fixes + - better handling of empty "Flag" messages over ActiveSync (#2806) + - fixed Chinese charset handling (#2809) + - fixed folder name (calendars and contacts) of new subscriptions (#2801) + - fixed the reply/forward operation over ActiveSync (#2805) + - fixed regression when attaching files to a reply + - wait 20 seconds (instead of 2) before deleting temporary download forms (#2811) + - avoid raising exceptions when the db is down and we try to access the preferences module (#2813) + - we now ignore the SCHEDULE-AGENT property when Thunderbird/Lightning sends it to avoid + not-generating invitation responses for externally received IMIP messages + - improved charset handling over ActiveSync (#2810) + 2.2.5 (2014-06-05) ------------------ diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m index d0284eb6f..bad00b260 100644 --- a/OpenChange/MAPIStoreUserContext.m +++ b/OpenChange/MAPIStoreUserContext.m @@ -294,9 +294,12 @@ static NSMapTable *contextsTable = nil; if ([parts count] == 5) { /* If "OCSFolderInfoURL" is properly configured, we must have 5 - parts in this url. */ + parts in this url. We strip the '-' character in case we have + this in the domain part - like foo@bar-zot.com */ ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@", - [username asCSSIdentifier]]; + [[[user loginInDomain] asCSSIdentifier] + stringByReplacingOccurrencesOfString: @"-" + withString: @"_"]]; [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; folderTableURL = [NSURL URLWithString: [parts componentsJoinedByString: @"/"]]; diff --git a/SOPE/NGCards/NGCardsSaxHandler.m b/SOPE/NGCards/NGCardsSaxHandler.m index 5314dee08..fca1a64e6 100644 --- a/SOPE/NGCards/NGCardsSaxHandler.m +++ b/SOPE/NGCards/NGCardsSaxHandler.m @@ -232,7 +232,7 @@ static NSArray *privilegedTagNames = nil; } - (void) characters: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { if (_len && _chars) { diff --git a/SOPE/NGCards/NGVCard.h b/SOPE/NGCards/NGVCard.h index 1d3696c63..9553da380 100644 --- a/SOPE/NGCards/NGVCard.h +++ b/SOPE/NGCards/NGVCard.h @@ -126,6 +126,9 @@ typedef enum - (void) setCategories: (NSArray *) newCategories; - (NSArray *) categories; +- (NSString *) photo; +- (void) setPhoto: (NSString *) _value; + // - (void) setN: (NGVCardName *) _v; // - (NGVCardName *) n; diff --git a/SOPE/NGCards/NGVCard.m b/SOPE/NGCards/NGVCard.m index db3833c6e..5f8028aa5 100644 --- a/SOPE/NGCards/NGVCard.m +++ b/SOPE/NGCards/NGVCard.m @@ -288,6 +288,25 @@ return [self uniqueChildWithTag: @"n"]; } +- (NSString *) photo +{ + NGVCardPhoto *photo; + + photo = (NGVCardPhoto *)[self uniqueChildWithTag: @"photo"]; + + return [[photo valuesAtIndex: 0 forKey: @""] objectAtIndex: 0]; +} + +- (void) setPhoto: (NSString *) _value +{ + CardElement *photo; + photo = [self uniqueChildWithTag: @"photo"]; + [photo setValue: 0 ofAttribute: @"encoding" to: @"BASE64"]; + + [photo setSingleValue: _value forKey: @""]; +} + + - (void) setOrg: (NSString *) anOrg units: (NSArray *) someUnits { diff --git a/SOPE/NGCards/samples/vcf2xml.m b/SOPE/NGCards/samples/vcf2xml.m index 3f56c2c88..31d61f026 100644 --- a/SOPE/NGCards/samples/vcf2xml.m +++ b/SOPE/NGCards/samples/vcf2xml.m @@ -217,10 +217,10 @@ printf("\n", [_localName cString]); } -- (void)characters:(unichar *)_chars length:(int)_len { +- (void)characters:(unichar *)_chars length:(NSUInteger)_len { NSString *str; id tmp; - unsigned i, len; + NSUInteger i, len; if (_len == 0) { [self indent]; @@ -228,7 +228,7 @@ return; } - for (i = 0; i < (unsigned)_len; i++) { + for (i = 0; i < _len; i++) { if (_chars[i] > 255) { NSLog(@"detected large char: o%04o d%03i h%04X", _chars[i], _chars[i], _chars[i]); @@ -246,7 +246,7 @@ [self indent]; printf("\"%s\"\n", [str cString]); } -- (void)ignorableWhitespace:(unichar *)_chars length:(int)_len { +- (void)ignorableWhitespace:(unichar *)_chars length:(NSUInteger)_len { NSString *data; id tmp; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index ee703b1d5..05e760909 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -80,6 +80,8 @@ typedef enum { - (NSArray *) calendarUIDs; +- (NSNumber *) activeTasks; + /* vevent UID handling */ - (NSString *) resourceNameForEventUID: (NSString *) _uid; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 61cef70dc..96a14d536 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -230,6 +230,8 @@ static iCalEvent *iCalEventK = nil; abstract: NO withEquivalent: SoPerm_AddDocumentsImagesAndFiles asChildOf: davElement (@"write", XMLNS_WEBDAV)]; + + /* read-acl and write-acl are defined in RFC3744 */ [aclManager registerDAVPermission: davElement (@"admin", nsI) abstract: YES withEquivalent: nil @@ -242,6 +244,72 @@ static iCalEvent *iCalEventK = nil; abstract: YES withEquivalent: SoPerm_ChangePermissions asChildOf: davElement (@"admin", nsI)]; + + /* Default permissions for calendars. These are very important so that DAV client can + detect permission changes on calendars and reload all items, if necessary */ + + /* Public ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholePublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + /* Private ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholePrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + /* Condifential ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholeConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; } return aclManager; @@ -522,7 +590,6 @@ static iCalEvent *iCalEventK = nil; { /* this is used for group calendars (this folder just returns itself) */ NSString *s; - s = [[self container] nameInContainer]; // [self logWithFormat:@"CAL UID: %@", s]; return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil; @@ -3270,4 +3337,25 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return users; } +- (NSNumber *) activeTasks +{ + NSArray *tasksList; + NSMutableArray *fields; + NSNumber *activeTasks; + + fields = [NSMutableArray arrayWithObjects: @"c_component", @"c_status", nil]; + + tasksList = [self bareFetchFields: fields + from: nil + to: nil + title: nil + component: @"vtodo" + additionalFilters: @"c_status != 1 AND c_status != 3"]; + + activeTasks = [NSNumber numberWithInt:[tasksList count]]; + + return activeTasks; +} + + @end /* SOGoAppointmentFolder */ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 1712ec7ea..0e397928f 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1428,9 +1428,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // - (BOOL) _shouldScheduleEvent: (iCalPerson *) theOrganizer { + NSArray *userAgents; NSString *v; BOOL b; - + int i; + b = YES; if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"])) @@ -1440,6 +1442,27 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent b = NO; } + // + // If we have to deal with Thunderbird/Lightning, we always send invitation + // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT + // to NONE/CLIENT when responding to an external invitation received by + // SOGo - so no invitation responses are ever sent by Lightning. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 + // + userAgents = [[context request] headersForKey: @"User-Agent"]; + + for (i = 0; i < [userAgents count]; i++) + { + if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound && + [[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound) + { + b = YES; + break; + } + } + + return b; } diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 48c850b9e..1efdcb62f 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -430,7 +430,7 @@ } - (void) ignorableWhitespace: (unichar *) whitespaces - length: (int) length + length: (NSUInteger) length { showWhoWeAre(); } diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 7fd1a4c35..4d0f9aa38 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1761,26 +1761,26 @@ static NSString *userAgent = nil; { SOGoUserDefaults *ud; ud = [[context activeUser] userDefaults]; - + if ([ud mailAddOutgoingAddresses]) { + Class contactGCSEntry; SOGoContactFolders *contactFolders; - NGMailAddressParser *parser; - id parsedRecipient; SOGoContactFolder *folder; SOGoContactGCSEntry *newContact; NGVCard *card; - Class contactGCSEntry; + NGMailAddressParser *parser; + NSArray *matchingContacts; NSMutableArray *recipients; NSString *recipient, *emailAddress, *addressBook, *uid; - NSArray *matchingContacts; + id parsedRecipient; int i; - + // Get all the addressbooks contactFolders = [[[context activeUser] homeFolderInContext: context] - lookupName: @"Contacts" - inContext: context - acquire: NO]; + lookupName: @"Contacts" + inContext: context + acquire: NO]; // Get all the recipients from the current email recipients = [self allRecipients]; for (i = 0; i < [recipients count]; i++) @@ -1790,7 +1790,7 @@ static NSString *userAgent = nil; parser = [NGMailAddressParser mailAddressParserWithString: recipient]; parsedRecipient = [parser parse]; emailAddress = [parsedRecipient address]; - + matchingContacts = [contactFolders allContactsFromFilter: emailAddress excludeGroups: YES excludeLists: YES]; diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index 46c1d7c14..af52334ee 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -114,6 +114,7 @@ NSArray *SOGoMailCoreInfoKeys; - (NSException *) removeFlags:(id)_f; - (BOOL) isNewMail; /* \Recent */ +- (BOOL) flagged; /* \Flagged */ - (BOOL) read; /* \Unseen */ - (BOOL) replied; /* \Answered */ - (BOOL) forwarded; /* $forwarded */ diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index f2fa35eac..f99bcf49d 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -1464,6 +1464,11 @@ static BOOL debugSoParts = NO; return [self _hasFlag: @"seen"]; } +- (BOOL) flagged +{ + return [self _hasFlag: @"flagged"]; +} + - (BOOL) replied { return [self _hasFlag: @"answered"]; diff --git a/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m index b0e9eea52..cef17775b 100644 --- a/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m +++ b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m @@ -72,7 +72,7 @@ = (@"CREATE TABLE %@ (" @" c_path VARCHAR(255) PRIMARY KEY," @" c_parent_path VARCHAR(255)," - @" c_type TINYINT NOT NULL," + @" c_type TINYINT UNSIGNED NOT NULL," @" c_creationdate INT NOT NULL," @" c_lastmodified INT NOT NULL," @" c_version INT NOT NULL DEFAULT 0," diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 3f626bc61..b0110f58d 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -329,17 +329,24 @@ static NSArray *childRecordFields = nil; fc = [cm acquireOpenChannelForURL: folderLocation]; if (fc) { - sql - = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" - @" WHERE c_path = '%@'"), - [folderLocation gcsTableName], ocsPath]; - [fc evaluateExpressionX: sql]; - attrs = [fc describeResults: NO]; - row = [fc fetchAttributes: attrs withZone: NULL]; - if (row) - [self _setDisplayNameFromRow: row]; - [fc cancelFetch]; - [cm releaseChannel: fc]; + // We use an exception handler here in case the database is down when + // performing the query. This could have unexpected results. + NS_DURING + { + sql + = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" + @" WHERE c_path = '%@'"), + [folderLocation gcsTableName], ocsPath]; + [fc evaluateExpressionX: sql]; + attrs = [fc describeResults: NO]; + row = [fc fetchAttributes: attrs withZone: NULL]; + if (row) + [self _setDisplayNameFromRow: row]; + [fc cancelFetch]; + [cm releaseChannel: fc]; + } + NS_HANDLER; + NS_ENDHANDLER; } } diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 4c7c7c31a..164296b83 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -164,34 +164,45 @@ static SoSecurityManager *sm = nil; NSString *folderName; SOGoGCSFolder *folder; SOGoUser *folderOwner; - + SOGoUserDefaults *ud; + roles = [[context activeUser] rolesForObject: self inContext: context]; folderOwner = [SOGoUser userWithLogin: [self ownerInContext: context]]; - + + // We autocreate the calendars if the user is the owner, a superuser or // if it's a resource as we won't necessarily want to login as a resource // in order to create its database tables. // FolderType is an enum where 0 = Personal and 1 = collected if ([roles containsObject: SoRole_Owner] || (folderOwner && [folderOwner isResource])) + { + if (folderType == SOGoPersonalFolder) { - if (folderType == SOGoPersonalFolder) - { - folderName = @"personal"; - folder = [subFolderClass objectWithName: folderName inContainer: self]; - [folder setDisplayName: [self defaultFolderName]]; - } - else if (folderType == SOGoCollectedFolder) - { - folderName = @"collected"; - folder = [subFolderClass objectWithName: folderName inContainer: self]; - [folder setDisplayName: [self collectedFolderName]]; - } + folderName = @"personal"; + folder = [subFolderClass objectWithName: folderName inContainer: self]; + [folder setDisplayName: [self defaultFolderName]]; [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]]; if ([folder create]) - [subFolders setObject: folder forKey: folderName]; + [subFolders setObject: folder forKey: folderName]; } + else if (folderType == SOGoCollectedFolder) + { + ud = [[context activeUser] userDefaults]; + if ([ud mailAddOutgoingAddresses]) { + folderName = @"collected"; + folder = [subFolderClass objectWithName: folderName inContainer: self]; + [folder setDisplayName: [self collectedFolderName]]; + [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]]; + + if ([folder create]) + [subFolders setObject: folder forKey: folderName]; + + [ud setSelectedAddressBook:folderName]; + } + } + } } - (NSException *) fetchSpecialFolders: (NSString *) sql diff --git a/Tests/Integration/test-davacl.py b/Tests/Integration/test-davacl.py index a254f25f9..a8a1a5bac 100755 --- a/Tests/Integration/test-davacl.py +++ b/Tests/Integration/test-davacl.py @@ -401,7 +401,17 @@ class DAVCalendarAclTest(DAVAclTest): else: expStatus = 207 privileges = self._currentUserPrivilegeSet(self.resource, expStatus) - self._comparePrivilegeSets(expectedPrivileges, privileges) + + # When comparing privileges on DAV collection, we remove all 'default' + # privileges on the collection. + privileges_set = set(privileges); + for x in ("public", "private", "confidential"): + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}viewwhole-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}viewdant-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}modify-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}respondto-%s-records" %x) + + self._comparePrivilegeSets(expectedPrivileges, list(privileges_set)) def _testEventDAVAcl(self, event_class, right, error_code): icsClass = self.classToICSClass[event_class].lower() diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index df981a470..7160f6ba2 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -86,6 +86,9 @@ _xmlCharsetForCharset (NSString *charset) { @"windows-1251", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1251 -> utf8 conversion { @"windows-1257", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1257 -> utf8 conversion { @"gb2312", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion + { @"gbk", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion + { @"gb18030", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion + { @"big5", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion { @"euc-jp", XML_CHAR_ENCODING_EUC_JP}}; unsigned count; xmlCharEncoding encoding; @@ -416,9 +419,9 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) _appendStyle: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { - unsigned int count, length; + NSUInteger count, length; unichar *start, *currentChar; start = _chars; @@ -685,7 +688,7 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) characters: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); if (!ignoredContent) @@ -709,7 +712,7 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) ignorableWhitespace: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); } @@ -732,7 +735,7 @@ static NSData* _sanitizeContent(NSData *theData) /* SaxLexicalHandler */ - (void) comment: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); if (inStyle) diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 28de05922..f78843b53 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -644,8 +644,9 @@ static NSArray *infoKeys = nil; if (!attachmentAttrs || ![co imap4URL]) { [co fetchInfo]; - if (![co inReplyTo] && [co IMAP4ID] > -1) + if ((![co inReplyTo] || currentAttachment) && [co IMAP4ID] > -1) { + // When currentAttachment is defined, it means we just attached a new file to the mail mail = [[[SOGoMailObject alloc] initWithImap4URL: [co imap4URL] inContainer: [co container]] autorelease]; a = [mail fetchFileAttachmentKeys]; ASSIGN (attachmentAttrs, a); diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 1e2e361ae..95d7a72de 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -364,8 +364,7 @@ // We sort our array - this is pretty useful for the Web // interface of SOGo. - allUsers = [users - sortedArrayUsingSelector: @selector (caseInsensitiveDisplayNameCompare:)]; + allUsers = [users sortedArrayUsingSelector: @selector (caseInsensitiveDisplayNameCompare:)]; max = [allUsers count]; jsonResponse = [NSMutableArray arrayWithCapacity: max]; @@ -462,9 +461,8 @@ folderType = [self queryParameterForKey: @"type"]; userFolder = [self clientObject]; - folders - = [userFolder foldersOfType: folderType - forUID: [userFolder ownerInContext: context]]; + folders = [userFolder foldersOfType: folderType + forUID: [userFolder ownerInContext: context]]; result = [self _foldersResponseForResults: folders]; return result; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 54c0df6b8..0900c4d3a 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -74,7 +74,7 @@ static NSArray *reminderValues = nil; if (!reminderItems && !reminderValues) { reminderItems = [NSArray arrayWithObjects: - @"5_MINUTES_BEFORE", + @"5_MINUTES_BEFORE", @"10_MINUTES_BEFORE", @"15_MINUTES_BEFORE", @"30_MINUTES_BEFORE", @@ -90,7 +90,7 @@ static NSArray *reminderValues = nil; @"1_WEEK_BEFORE", nil]; reminderValues = [NSArray arrayWithObjects: - @"-PT5M", + @"-PT5M", @"-PT10M", @"-PT15M", @"-PT30M", @@ -699,49 +699,53 @@ static NSArray *reminderValues = nil; /* We want all the SourceIDS */ NSMutableArray *folders, *availableAddressBooksID, *availableAddressBooksName; SOGoParentFolder *contactFolders; - + int i, count; BOOL collectedAlreadyExist; - + contactFolders = [[[context activeUser] homeFolderInContext: context] - lookupName: @"Contacts" - inContext: context - acquire: NO]; + lookupName: @"Contacts" + inContext: context + acquire: NO]; folders = [NSMutableArray arrayWithArray: [contactFolders subFolders]]; count = [folders count]-1; - + // Inside this loop we remove all the public or shared addressbooks for (; count >= 0; count--) - { - if (![[folders objectAtIndex: count] isKindOfClass: [SOGoContactGCSFolder class]]) - [folders removeObjectAtIndex: count]; - } - + { + if (![[folders objectAtIndex: count] isKindOfClass: [SOGoContactGCSFolder class]]) + [folders removeObjectAtIndex: count]; + } + // Parse the objects in order to have only the displayName of the addressbooks to be displayed on the preferences interface availableAddressBooksID = [NSMutableArray arrayWithCapacity: [folders count]]; availableAddressBooksName = [NSMutableArray arrayWithCapacity: [folders count]]; count = [folders count]-1; collectedAlreadyExist = NO; - - for (i = 0; i <= count ; i++) { - [availableAddressBooksID addObject:[[folders objectAtIndex:i] realNameInContainer]]; - [availableAddressBooksName addObject:[[folders objectAtIndex:i] displayName]]; - - if ([[availableAddressBooksID objectAtIndex:i] isEqualToString: @"collected"]) - collectedAlreadyExist = YES; - } + + for (i = 0; i <= count ; i++) + { + [availableAddressBooksID addObject:[[folders objectAtIndex:i] realNameInContainer]]; + [availableAddressBooksName addObject:[[folders objectAtIndex:i] displayName]]; + + if ([[availableAddressBooksID objectAtIndex:i] isEqualToString: @"collected"]) + collectedAlreadyExist = YES; + } // Create the dictionary for the next function : itemAddressBookText. if (!addressBooksIDWithDisplayName) - addressBooksIDWithDisplayName = [[NSMutableDictionary alloc] initWithObjects:availableAddressBooksName - forKeys:availableAddressBooksID]; - if (!collectedAlreadyExist) + { + addressBooksIDWithDisplayName = [[NSMutableDictionary alloc] initWithObjects:availableAddressBooksName + forKeys:availableAddressBooksID]; + } + if (!collectedAlreadyExist) { [availableAddressBooksID addObject: @"collected"]; - [addressBooksIDWithDisplayName setObject: [self labelForKey: @"Collected Address Book"] forKey: @"collected"]; + [addressBooksIDWithDisplayName setObject: [self labelForKey: @"Collected Address Book"] forKey: @"collected"]; } - + return availableAddressBooksID; } + - (NSString *) itemAddressBookText { return [addressBooksIDWithDisplayName objectForKey: item]; @@ -929,7 +933,7 @@ static NSArray *reminderValues = nil; capabilities = [[self sieveClient] capabilities]; else capabilities = [NSArray array]; - [capabilities retain]; + [capabilities retain]; } return [capabilities jsonRepresentation]; @@ -1220,12 +1224,12 @@ static NSArray *reminderValues = nil; SOGoSieveManager *manager; if (!client) - { - folder = [[self clientObject] mailAccountsFolder: @"Mail" inContext: context]; - account = [folder lookupName: @"0" inContext: context acquire: NO]; - manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; - client = [[manager clientForAccount: account] retain]; - } + { + folder = [[self clientObject] mailAccountsFolder: @"Mail" inContext: context]; + account = [folder lookupName: @"0" inContext: context acquire: NO]; + manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; + client = [[manager clientForAccount: account] retain]; + } return client; } @@ -1273,9 +1277,9 @@ static NSArray *reminderValues = nil; results = [self responseWithStatus: 502 andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Connection error", @"textStatus", nil]]; } - else - results = [self responseWithStatus: 503 - andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Service temporarily unavailable", @"textStatus", nil]]; + else + results = [self responseWithStatus: 503 + andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Service temporarily unavailable", @"textStatus", nil]]; } else results = self; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 32dc2db3d..ecb4a9135 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -155,6 +155,7 @@ "What to Print" = "What to Print"; "Options" = "Options"; "Tasks with no due date" = "Tasks with no due date"; +"Display working hours only" = "Display working hours only"; "Completed tasks" = "Completed Tasks"; /* Appointments */ @@ -313,6 +314,7 @@ "Repeat" = "Repeat"; "Daily" = "Daily"; +"Multi-Columns" = "Multi-Columns"; "Weekly" = "Weekly"; "Monthly" = "Monthly"; "Yearly" = "Yearly"; @@ -542,6 +544,8 @@ vtodo_class2 = "(Confidential task)"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; "DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; "EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; +"Please select at least one calendar" = "Please select at least one calendar"; + "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar index 54a5eae10..14e5fcd5e 100644 --- a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +++ b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar @@ -21,12 +21,11 @@ onclick = "return onDayOverview();"; image = "day-view.png"; tooltip = "Switch to day view"; }, - /* disabled until we fix the view */ - /* { link = "#"; - label="Multicolumn Day View"; - onclick = "return onMulticolumnDayOverview();"; - image = "day-view-multicolumn.png"; - tooltip = ""; }, */ + { link = "#"; + label="Multi-Columns View"; + onclick = "return onMulticolumnDayOverview();"; + image = "day-view-multicolumn.png"; + tooltip = "Switch to multi-columns day view"; }, { link = "#"; label="Week View"; onclick = "return onWeekOverview();"; diff --git a/UI/Scheduler/UIxCalDayTable.h b/UI/Scheduler/UIxCalDayTable.h index 88ab136e8..57d91e846 100644 --- a/UI/Scheduler/UIxCalDayTable.h +++ b/UI/Scheduler/UIxCalDayTable.h @@ -35,27 +35,27 @@ @interface UIxCalDayTable : UIxCalView { - unsigned int numberOfDays; - NSCalendarDate *startDate; - NSCalendarDate *currentTableDay; - NSString *currentTableHour; - NSMutableArray *daysToDisplay; - NSMutableArray *hoursToDisplay; - NSArray *weekDays; SOGoDateFormatter *dateFormatter; - NSString *timeFormat; + NSArray *weekDays; + NSString *currentView, *timeFormat, *currentTableHour; + NSCalendarDate *startDate, *currentTableDay; + NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay; + unsigned int numberOfDays; } - (void) setNumberOfDays: (NSNumber *) aNumber; - (NSNumber *) numberOfDays; +- (NSString *) currentView; - (void) setStartDate: (NSCalendarDate *) aStartDate; - (NSCalendarDate *) startDate; - (NSCalendarDate *) endDate; - (NSArray *) daysToDisplay; +- (NSArray *) calendarsToDisplay; - (void) setCurrentTableDay: (NSCalendarDate *) aTableDay; - (NSCalendarDate *) currentTableDay; +- (NSMutableArray *) currentCalendar; @end diff --git a/UI/Scheduler/UIxCalDayTable.m b/UI/Scheduler/UIxCalDayTable.m index 174e1d1ea..799d55f57 100644 --- a/UI/Scheduler/UIxCalDayTable.m +++ b/UI/Scheduler/UIxCalDayTable.m @@ -55,9 +55,12 @@ ASSIGN (timeFormat, [ud timeFormat]); daysToDisplay = nil; + calendarsToDisplay = nil; hoursToDisplay = nil; numberOfDays = 1; startDate = nil; + currentView = nil; + currentCalendar = nil; currentTableDay = nil; currentTableHour = nil; weekDays = [locale objectForKey: NSShortWeekDayNameArray]; @@ -75,6 +78,8 @@ // [allAppointments release]; [weekDays release]; [daysToDisplay release]; + [calendarsToDisplay release]; + [currentView release]; [hoursToDisplay release]; [dateFormatter release]; [timeFormat release]; @@ -119,6 +124,16 @@ return [endDate endOfDay]; } +- (void) setCurrentView: (NSString *) aView +{ + ASSIGN(currentView, aView); +} + +- (NSString *) currentView +{ + return currentView; +} + - (NSArray *) hoursToDisplay { unsigned int currentHour, lastHour; @@ -148,22 +163,68 @@ { NSCalendarDate *currentDate; int count; - + if (!daysToDisplay) + { + daysToDisplay = [NSMutableArray new]; + currentDate = [[self startDate] hour: [self dayStartHour] + minute: 0]; + + for (count = 0; count < numberOfDays; count++) { - daysToDisplay = [NSMutableArray new]; - currentDate = [[self startDate] hour: [self dayStartHour] - minute: 0]; - for (count = 0; count < numberOfDays; count++) - { - [daysToDisplay addObject: currentDate]; - currentDate = [currentDate tomorrow]; - } + [daysToDisplay addObject: currentDate]; + currentDate = [currentDate tomorrow]; } - + } + return daysToDisplay; } +- (NSArray *) calendarsToDisplay +{ + if (!calendarsToDisplay) + { + int max=0, i; + NSArray *folders; + SOGoAppointmentFolders *co; + SOGoAppointmentFolder *folder; + NSMutableDictionary *calendar; + unsigned int count, foldersCount; + NSString *folderName, *fDisplayName; + NSNumber *isActive; + + co = [self clientObject]; + folders = [co subFolders]; + foldersCount = [folders count]; + calendarsToDisplay = [[NSMutableArray alloc] initWithCapacity: foldersCount]; + for (count = 0; count < foldersCount; count++) + { + folder = [folders objectAtIndex: count]; + isActive = [NSNumber numberWithBool: [folder isActive]]; + if ([isActive intValue] != 0) { + calendar = [NSMutableDictionary dictionary]; + folderName = [folder nameInContainer]; + fDisplayName = [folder displayName]; + if (fDisplayName == nil) + fDisplayName = @""; + if ([fDisplayName isEqualToString: [co defaultFolderName]]) + fDisplayName = [self labelForKey: fDisplayName]; + [calendar setObject: [NSString stringWithFormat: @"/%@", folderName] + forKey: @"id"]; + [calendar setObject: fDisplayName forKey: @"displayName"]; + [calendar setObject: folderName forKey: @"folder"]; + [calendar setObject: [folder calendarColor] forKey: @"color"]; + [calendar setObject: isActive forKey: @"active"]; + [calendar setObject: [folder ownerInContext: context] + forKey: @"owner"]; + [calendarsToDisplay addObject: calendar]; + } + } + } + + return calendarsToDisplay; +} + - (void) setCurrentTableDay: (NSCalendarDate *) aTableDay { currentTableDay = aTableDay; @@ -174,6 +235,16 @@ return currentTableDay; } +- (void) setCurrentCalendar: (NSMutableArray *) aCalendar +{ + ASSIGN(currentCalendar, aCalendar); +} + +- (NSMutableArray *) currentCalendar +{ + return currentCalendar; +} + - (void) setCurrentTableHour: (NSString *) aTableHour { currentTableHour = aTableHour; @@ -226,6 +297,16 @@ return [dateFormatter shortFormattedDate: currentTableDay]; } +- (NSString *) labelForCalendar +{ + return [currentCalendar objectForKey: @"displayName"]; +} + +- (NSString *) colorForCalendar +{ + return [currentCalendar objectForKey:@"color"]; +} + // - (NSDictionary *) _adjustedAppointment: (NSDictionary *) anAppointment // forStart: (NSCalendarDate *) start // andEnd: (NSCalendarDate *) end @@ -318,7 +399,15 @@ - (NSString *) daysViewClasses { - return [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays]; + NSString *daysView; + + if ([currentView isEqualToString:@"multicolumndayview"]) + daysView = @"daysView daysViewForMultipleDays"; + + else + daysView = [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays]; + + return daysView; } - (NSString *) dayClasses @@ -326,19 +415,24 @@ NSMutableString *classes; unsigned int currentDayNbr, realDayOfWeek; - currentDayNbr = [daysToDisplay indexOfObject: currentTableDay]; - realDayOfWeek = [currentTableDay dayOfWeek]; - classes = [NSMutableString string]; - [classes appendFormat: @"day day%d", currentDayNbr]; - if (numberOfDays > 1) + if ([currentView isEqualToString:@"multicolumndayview"]) + [classes appendFormat:@"day dayColumn"]; + else { + currentDayNbr = [daysToDisplay indexOfObject: currentTableDay]; + realDayOfWeek = [currentTableDay dayOfWeek]; + + [classes appendFormat: @"day day%d", currentDayNbr]; + + if (numberOfDays > 1) { if (realDayOfWeek == 0 || realDayOfWeek == 6) [classes appendString: @" weekEndDay"]; if ([currentTableDay isToday]) [classes appendString: @" dayOfToday"]; } - + } + return classes; } @@ -358,4 +452,20 @@ return cellClass; } +- (BOOL) isMultiColumnView +{ + if ([currentView isEqualToString:@"multicolumndayview"]) + return YES; + + return NO; +} + +- (BOOL) isNotMultiColumnView +{ + if ([currentView isEqualToString:@"dayview"] || [currentView isEqualToString:@"weekview"]) + return YES; + + return NO; +} + @end diff --git a/UI/Scheduler/UIxCalListingActions.h b/UI/Scheduler/UIxCalListingActions.h index 4f12d8cfe..41c27cecd 100644 --- a/UI/Scheduler/UIxCalListingActions.h +++ b/UI/Scheduler/UIxCalListingActions.h @@ -43,6 +43,7 @@ NSString *value; NSString *criteria; NSString *userLogin; + NSString *currentView; BOOL dayBasedView; WORequest *request; SOGoDateFormatter *dateFormatter; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 8670dc383..607dc9062 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -1,9 +1,6 @@ /* UIxCalListingActions.m - this file is part of SOGo * - * Copyright (C) 2006-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2006-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,6 +75,8 @@ static NSArray *tasksFields = nil; #define maxBlocks (offsetBlocks * 2) // maximum number of blocks to search // for a free slot (10 days) +@class SOGoAppointment; + @implementation UIxCalListingActions + (void) initialize @@ -121,6 +120,7 @@ static NSArray *tasksFields = nil; ASSIGN (dateFormatter, [user dateFormatterInContext: context]); ASSIGN (userTimeZone, [[user userDefaults] timeZone]); dayBasedView = NO; + currentView = nil; } return self; @@ -239,6 +239,7 @@ static NSArray *tasksFields = nil; endDate = nil; param = [request formValueForKey: @"view"]; + currentView = param; dayBasedView = ![param isEqualToString: @"monthview"]; } } @@ -480,7 +481,7 @@ static NSArray *tasksFields = nil; return infos; } -- (WOResponse *) _responseWithData: (NSArray *) data +- (WOResponse *) _responseWithData: (id) data { WOResponse *response; @@ -1044,47 +1045,113 @@ _computeBlocksPosition (NSArray *blocks) /* ... _computeBlocksMultiplier() ... */ } +- (NSArray *) _selectedCalendars +{ + SOGoAppointmentFolders *co; + SOGoAppointmentFolder *folder; + NSMutableArray *selectedCalendars; + NSArray *folders; + NSString *fUID; + NSNumber *isActive; + unsigned int count, foldersCount; + int max=0, i; + + co = [self clientObject]; + folders = [co subFolders]; + foldersCount = [folders count]; + selectedCalendars = [[NSMutableArray alloc] initWithCapacity: foldersCount]; + for (count = 0; count < foldersCount; count++) + { + folder = [folders objectAtIndex: count]; + isActive = [NSNumber numberWithBool: [folder isActive]]; + if ([isActive intValue] != 0) { + fUID = [folder nameInContainer]; + [selectedCalendars addObject: fUID]; + } + } + return selectedCalendars; +} + - (WOResponse *) eventsBlocksAction { int count, max; NSArray *events, *event, *eventsBlocks; - NSMutableArray *allDayBlocks, *blocks, *currentDay; + NSMutableArray *allDayBlocks, *blocks, *currentDay, *calendars, *eventsByCalendars, *eventsForCalendar; NSNumber *eventNbr; BOOL isAllDay; + int i, j; [self _setupContext]; - [self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks]; - events = [self _fetchFields: eventsFields - forComponentOfType: @"vevent"]; - eventsBlocks = [NSArray arrayWithObjects: events, allDayBlocks, blocks, nil]; - max = [events count]; - for (count = 0; count < max; count++) - { - event = [events objectAtIndex: count]; - // NSLog(@"***[UIxCalListingActions eventsBlocksAction] %i = %@ : %@ / %@ / %@", count, - // [event objectAtIndex: eventTitleIndex], - // [event objectAtIndex: eventStartDateIndex], - // [event objectAtIndex: eventEndDateIndex], - // [event objectAtIndex: eventRecurrenceIdIndex]); - eventNbr = [NSNumber numberWithUnsignedInt: count]; - isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue]; - if (dayBasedView && isAllDay) - [self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr]; - else - [self _fillBlocks: blocks withEvent: event withNumber: eventNbr]; - } + events = [self _fetchFields: eventsFields forComponentOfType: @"vevent"]; - max = [blocks count]; - for (count = 0; count < max; count++) + if ([currentView isEqualToString: @"multicolumndayview"]) { - currentDay = [blocks objectAtIndex: count]; - [currentDay sortUsingSelector: @selector (compareEventByStart:)]; - [self _addBlocksWidth: currentDay]; + calendars = [self _selectedCalendars]; + eventsByCalendars = [NSMutableArray arrayWithCapacity:[calendars count]]; + for (i = 0; i < [calendars count]; i++) // For each calendar + { + eventsForCalendar = [NSMutableArray array]; + [self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks]; + for (j = 0; j < [events count]; j++) { + if ([[[events objectAtIndex:j] objectAtIndex:1] isEqualToString:[calendars objectAtIndex:i]]) { + [eventsForCalendar addObject: [events objectAtIndex:j]]; + } + } + eventsBlocks = [NSArray arrayWithObjects:eventsForCalendar, allDayBlocks, blocks, nil]; + max = [eventsForCalendar count]; + for (count = 0; count < max; count++) + { + event = [eventsForCalendar objectAtIndex: count]; + eventNbr = [NSNumber numberWithUnsignedInt: count]; + isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue]; + if (dayBasedView && isAllDay) + [self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr]; + else + [self _fillBlocks: blocks withEvent: event withNumber: eventNbr]; + } + max = [blocks count]; + for (count = 0; count < max; count++) + { + currentDay = [blocks objectAtIndex: count]; + [currentDay sortUsingSelector: @selector (compareEventByStart:)]; + [self _addBlocksWidth: currentDay]; + } + + [eventsByCalendars insertObject:eventsBlocks atIndex:i]; + } + return [self _responseWithData: eventsByCalendars]; + } + else + { + [self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks]; + eventsBlocks = [NSArray arrayWithObjects: events, allDayBlocks, blocks, nil]; + max = [events count]; + for (count = 0; count < max; count++) + { + event = [events objectAtIndex: count]; + // NSLog(@"***[UIxCalListingActions eventsBlocksAction] %i = %@ : %@ / %@ / %@", count, + // [event objectAtIndex: eventTitleIndex], + // [event objectAtIndex: eventStartDateIndex], + // [event objectAtIndex: eventEndDateIndex], + // [event objectAtIndex: eventRecurrenceIdIndex]); + eventNbr = [NSNumber numberWithUnsignedInt: count]; + isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue]; + if (dayBasedView && isAllDay) + [self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr]; + else + [self _fillBlocks: blocks withEvent: event withNumber: eventNbr]; + } + + max = [blocks count]; + for (count = 0; count < max; count++) + { + currentDay = [blocks objectAtIndex: count]; + [currentDay sortUsingSelector: @selector (compareEventByStart:)]; + [self _addBlocksWidth: currentDay]; + } + return [self _responseWithData: eventsBlocks]; } - - return [self _responseWithData: eventsBlocks]; - // timeIntervalSinceDate: } - (NSString *) _getStatusClassForStatusCode: (int) statusCode @@ -1208,4 +1275,28 @@ _computeBlocksPosition (NSArray *blocks) return [self _responseWithData: filteredTasks]; } +- (WOResponse *) activeTasksAction +{ + NSMutableDictionary *activeTasksByCalendars; + SOGoAppointmentFolder *folder; + SOGoAppointmentFolders *co; + NSArray *folders; + + int i; + + co = [self clientObject]; + folders = [co subFolders]; + activeTasksByCalendars = [NSMutableDictionary dictionaryWithCapacity: [folders count]]; + + for (i = 0; i < [folders count]; i++) + { + folder = [folders objectAtIndex: i]; + + [activeTasksByCalendars setObject: [folder activeTasks] + forKey: [folder nameInContainer]]; + } + + return [self _responseWithData: activeTasksByCalendars]; +} + @end diff --git a/UI/Scheduler/UIxCalMulticolumnDayView.m b/UI/Scheduler/UIxCalMulticolumnDayView.m index 6c1683d1a..b96241686 100644 --- a/UI/Scheduler/UIxCalMulticolumnDayView.m +++ b/UI/Scheduler/UIxCalMulticolumnDayView.m @@ -39,25 +39,25 @@ - (id) init { if ((self = [super init])) - { -// allAppointments = nil; - subscriptionUsers = nil; - hoursToDisplay = nil; - currentTableUser = nil; - currentTableHour = nil; -// dateFormatter = [[SOGoDateFormatter alloc] -// initWithLocale: [self locale]]; - } - + { + // allAppointments = nil; + subscriptionUsers = nil; + hoursToDisplay = nil; + currentTableUser = nil; + currentTableHour = nil; + // dateFormatter = [[SOGoDateFormatter alloc] + // initWithLocale: [self locale]]; + } + return self; } - (void) dealloc { -// [allAppointments release]; + // [allAppointments release]; [subscriptionUsers release]; [hoursToDisplay release]; -// [dateFormatter release]; + // [dateFormatter release]; [super dealloc]; } @@ -188,16 +188,6 @@ /* fetching */ -// - (NSCalendarDate *) startDate -// { -// return [[self selectedDate] beginOfDay]; -// } - -// - (NSCalendarDate *) endDate -// { -// return [[self selectedDate] endOfDay]; -// } - // - (NSArray *) appointmentsForCurrentUser // { // NSMutableArray *filteredAppointments; @@ -292,4 +282,11 @@ * (100.0 / [users count]))]; } +- (id ) defaultAction +{ + [super setCurrentView: @"multicolumndayview"]; + + return self; +} + @end diff --git a/UI/Scheduler/UIxCalViewPrint.h b/UI/Scheduler/UIxCalViewPrint.h index ac3de23a8..a0977d594 100644 --- a/UI/Scheduler/UIxCalViewPrint.h +++ b/UI/Scheduler/UIxCalViewPrint.h @@ -24,6 +24,7 @@ @interface UIxCalViewPrint : UIxComponent { id item; + SOGoUserSettings *us; } @end diff --git a/UI/Scheduler/UIxCalViewPrint.m b/UI/Scheduler/UIxCalViewPrint.m index af94b9792..d186ef4fa 100644 --- a/UI/Scheduler/UIxCalViewPrint.m +++ b/UI/Scheduler/UIxCalViewPrint.m @@ -22,6 +22,9 @@ #import #import +#import +#import + #import static NSArray *layoutItems = nil; @@ -32,21 +35,22 @@ static NSArray *layoutItems = nil; { if (!layoutItems) { - layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Weekly", nil]; + layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Multi-Columns", @"Weekly", nil]; [layoutItems retain]; } } + - (id) init { item = nil; - return [super init]; + + return self; } - (void) dealloc { [item release]; - [super dealloc]; } /****************************************************************/ @@ -72,10 +76,29 @@ static NSArray *layoutItems = nil; return [self labelForKey: [NSString stringWithFormat: item]]; } -- (NSString *) layoutSelectedByUser +- (NSString *) parentPrintLayout { - return nil; + // The objective here is to return the parent view layout and select the print layout corresponding. Default print view: list view + SOGoUser *activeUser; + NSString *parentView; + + activeUser = [context activeUser]; + us = [activeUser userSettings]; + parentView = [[us objectForKey:@"Calendar"] objectForKey:@"View" ]; + + if ([parentView isEqualToString:@"dayview"]) + return @"Daily"; + + else if ([parentView isEqualToString:@"weekview"]) + return @"Weekly"; + + else if ([parentView isEqualToString:@"multicolumndayview"]) + return @"Multi-Columns"; + + else + return @"LIST"; } + /******************************************************************/ /* */ diff --git a/UI/Scheduler/UIxCalendarSelector.m b/UI/Scheduler/UIxCalendarSelector.m index 7da4392b3..2c8a6910d 100644 --- a/UI/Scheduler/UIxCalendarSelector.m +++ b/UI/Scheduler/UIxCalendarSelector.m @@ -93,37 +93,39 @@ _intValueFromHex (NSString *hexString) NSMutableDictionary *calendar; unsigned int count, max; NSString *folderName, *fDisplayName; - NSNumber *isActive; - + NSNumber *isActive, *fActiveTasks; + if (!calendars) + { + co = [self clientObject]; + folders = [co subFolders]; + max = [folders count]; + calendars = [[NSMutableArray alloc] initWithCapacity: max]; + for (count = 0; count < max; count++) { - co = [self clientObject]; - folders = [co subFolders]; - max = [folders count]; - calendars = [[NSMutableArray alloc] initWithCapacity: max]; - for (count = 0; count < max; count++) - { - folder = [folders objectAtIndex: count]; - calendar = [NSMutableDictionary dictionary]; - folderName = [folder nameInContainer]; - fDisplayName = [folder displayName]; - if (fDisplayName == nil) - fDisplayName = @""; - if ([fDisplayName isEqualToString: [co defaultFolderName]]) - fDisplayName = [self labelForKey: fDisplayName]; - [calendar setObject: [NSString stringWithFormat: @"/%@", folderName] - forKey: @"id"]; - [calendar setObject: fDisplayName forKey: @"displayName"]; - [calendar setObject: folderName forKey: @"folder"]; - [calendar setObject: [folder calendarColor] forKey: @"color"]; - isActive = [NSNumber numberWithBool: [folder isActive]]; - [calendar setObject: isActive forKey: @"active"]; - [calendar setObject: [folder ownerInContext: context] - forKey: @"owner"]; - [calendars addObject: calendar]; - } + folder = [folders objectAtIndex: count]; + calendar = [NSMutableDictionary dictionary]; + folderName = [folder nameInContainer]; + fDisplayName = [folder displayName]; + if (fDisplayName == nil) + fDisplayName = @""; + if ([fDisplayName isEqualToString: [co defaultFolderName]]) + fDisplayName = [self labelForKey: fDisplayName]; + [calendar setObject: [NSString stringWithFormat: @"/%@", folderName] + forKey: @"id"]; + [calendar setObject: fDisplayName forKey: @"displayName"]; + [calendar setObject: folderName forKey: @"folder"]; + [calendar setObject: [folder calendarColor] forKey: @"color"]; + isActive = [NSNumber numberWithBool: [folder isActive]]; + [calendar setObject: isActive forKey: @"active"]; + [calendar setObject: [folder ownerInContext: context] + forKey: @"owner"]; + fActiveTasks = [folder activeTasks]; + [calendar setObject:fActiveTasks forKey:@"activeTasks" ]; + [calendars addObject: calendar]; } - + } + return calendars; } diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 417b9e2d6..aab66ebfc 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -94,6 +94,11 @@ actionClass = "UIxCalListingActions"; actionName = "tasksList"; }; + activeTasks = { + protectedBy = "View"; + actionClass = "UIxCalListingActions"; + actionName = "activeTasks"; + }; dayview = { protectedBy = "View"; pageName = "UIxCalDayView"; diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index fbfc0b30a..9cc6675a2 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -299,7 +299,8 @@
+ var:checked="addOutgoingAddresses" + onChange = "onAddOutgoingAddressesCheck(this);"/>
-

-
-
+ + + + +
+ +
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ + +
+
+
+
+
+ + + + +
+ +
+
+ +
+
+
+ + +
+ +
+
+
+
- + + +
-
-
- -
-
-
-
- - - -
-
+
+ +
+
+
+ + + + +
+
+ +
+ + + +
+
+
+
+
+
+
+
+ + +
+
+ +
+ + + +
+
+
+
+
+
+
+
diff --git a/UI/Templates/SchedulerUI/UIxCalDayView.wox b/UI/Templates/SchedulerUI/UIxCalDayView.wox index 676d65d54..83cf7c21c 100644 --- a/UI/Templates/SchedulerUI/UIxCalDayView.wox +++ b/UI/Templates/SchedulerUI/UIxCalDayView.wox @@ -40,6 +40,7 @@ className="UIxCalDayTable" startDate="startDate" const:CSSClass="dayOverview" - const:numberOfDays="1" /> + const:numberOfDays="1" + const:currentView="dayview"/>
diff --git a/UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox b/UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox index 62ee604e3..862a0026b 100644 --- a/UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox @@ -5,40 +5,33 @@ xmlns:const="http://www.skyrix.com/od/constant" xmlns:rsrc="OGo:url" xmlns:label="OGo:label"> - + + + - - + + + + + + + + + + - + +
-
-
-
:00
-
-
-
-
- -
-
-
-
-
- -
-
-
-
-
+
- + \ No newline at end of file diff --git a/UI/Templates/SchedulerUI/UIxCalViewPrint.wox b/UI/Templates/SchedulerUI/UIxCalViewPrint.wox index cbee832c1..f8370f0f5 100644 --- a/UI/Templates/SchedulerUI/UIxCalViewPrint.wox +++ b/UI/Templates/SchedulerUI/UIxCalViewPrint.wox @@ -25,10 +25,10 @@ + string="itemPrintLayoutText" selection="parentPrintLayout" /> - + @@ -81,6 +81,16 @@
+ + + + +
+
+