diff --git a/.gitignore b/.gitignore index 88becdaa6..5123e76a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ config.make tags +*._* */obj/ +*._* */*/obj/ */*/*/obj/ */*/*.SOGo/ @@ -11,3 +13,4 @@ SoObjects/SOGo/SOGo.framework/ SoObjects/SOGo/derived_src/ Tests/*/config.py *.pyc +._* diff --git a/ActiveSync/GNUmakefile b/ActiveSync/GNUmakefile index 969580670..926e02725 100644 --- a/ActiveSync/GNUmakefile +++ b/ActiveSync/GNUmakefile @@ -14,16 +14,18 @@ ActiveSync_OBJC_FILES = \ iCalRecurrenceRule+ActiveSync.m \ iCalTimeZone+ActiveSync.m \ iCalToDo+ActiveSync.m \ - NSCalendarDate+ActiveSync.m \ + NSCalendarDate+ActiveSync.m \ NSData+ActiveSync.m \ NSDate+ActiveSync.m \ NGDOMElement+ActiveSync.m \ NGMimeMessage+ActiveSync.m \ NGVCard+ActiveSync.m \ + NSArray+SyncCache.m \ NSString+ActiveSync.m \ SOGoActiveSyncDispatcher.m \ SOGoActiveSyncDispatcher+Sync.m \ - SOGoMailObject+ActiveSync.m \ + SOGoMailObject+ActiveSync.m \ + SOGoSyncCacheObject.m \ SoObjectWebDAVDispatcher+ActiveSync.m ActiveSync_RESOURCE_FILES += \ diff --git a/ActiveSync/NGDOMElement+ActiveSync.m b/ActiveSync/NGDOMElement+ActiveSync.m index 527486605..e9294e573 100644 --- a/ActiveSync/NGDOMElement+ActiveSync.m +++ b/ActiveSync/NGDOMElement+ActiveSync.m @@ -37,6 +37,28 @@ static NSArray *asElementArray = nil; @implementation NGDOMElement (ActiveSync) +- (BOOL) isTextNode +{ + id children; + id element; + int i; + + if ([self nodeType] == DOM_TEXT_NODE) + return YES; + + children = [self childNodes]; + + for (i = 0; i < [children length]; i++) + { + element = [children objectAtIndex: i]; + + if ([element nodeType] != DOM_TEXT_NODE) + return NO; + } + + return YES; +} + // // We must handle "inner data" like this: // @@ -79,7 +101,7 @@ static NSArray *asElementArray = nil; int i, count; if (!asElementArray) - asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", nil]; + asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", nil]; data = [NSMutableDictionary dictionary]; @@ -96,9 +118,17 @@ static NSArray *asElementArray = nil; tag = [element tagName]; count = [(NSArray *)[element childNodes] count]; - + + // We check if the node is a text one or if all its + // children are text nodes. This is important to avoid side-effects + // in SOPE where "foo & bar" would result into 3 childnodes instead + // of just one. + if ([(id)element isTextNode]) + { + value = [(id)element textValue]; + } // Handle inner data - see above for samples - if (count > 2) + else { NSMutableArray *innerElements; id innerElement; @@ -123,7 +153,10 @@ static NSArray *asElementArray = nil; if ([innerTag isEqualToString: [innerElement tagName]]) { - [innerElements addObject: [(NGDOMElement *)innerElement applicationData]]; + if ([(id)innerElement isTextNode]) + [innerElements addObject: [(NGDOMElement *)innerElement textValue]]; + else + [innerElements addObject: [(NGDOMElement *)innerElement applicationData]]; } else { @@ -144,12 +177,11 @@ static NSArray *asElementArray = nil; value = nil; } } - else - value = [[element firstChild] textValue]; if (value && tag) [data setObject: value forKey: tag]; - } + + } // if ([element nodeType] == DOM_ELEMENT_NODE) } return data; diff --git a/ActiveSync/NGVCard+ActiveSync.m b/ActiveSync/NGVCard+ActiveSync.m index 8c9a421a0..9ac476969 100644 --- a/ActiveSync/NGVCard+ActiveSync.m +++ b/ActiveSync/NGVCard+ActiveSync.m @@ -47,9 +47,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { + NSArray *emails, *addresses, *categories, *elements; CardElement *n, *homeAdr, *workAdr; - NSArray *emails, *addresses; NSMutableString *s; + NSString *url; id o; int i; @@ -65,10 +66,42 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ((o = [self workCompany])) [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [[self org] flattenedValueAtIndex: 1 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + categories = [self categories]; + + if ([categories count]) + { + [s appendFormat: @""]; + for (i = 0; i < [categories count]; i++) + { + [s appendFormat: @"%@", [[categories objectAtIndex: i] activeSyncRepresentationInContext: context]]; + } + [s appendFormat: @""]; + } + + elements = [self childrenWithTag: @"url" + andAttribute: @"type" + havingValue: @"work"]; + if ([elements count] > 0) + { + url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; + [s appendFormat: @"%@", [url activeSyncRepresentationInContext: context]]; + } + + + if ((o = [[self uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self nickname])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + if ((o = [self title])) [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; - + if ((o = [self preferredEMail])) [s appendFormat: @"%@", o]; @@ -154,7 +187,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Other, less important fields if ((o = [self birthday])) - [s appendFormat: @"%@", [o activeSyncRepresentationWithoutSeparatorsInContext: context]]; + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; if ((o = [self note])) { @@ -183,6 +216,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) [self setNote: o]; + // Categories + if ((o = [theValues objectForKey: @"Categories"])) + [self setCategories: o]; + // Birthday if ((o = [theValues objectForKey: @"Birthday"])) { @@ -238,24 +275,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Company's name if ((o = [theValues objectForKey: @"CompanyName"])) - { - [self setOrg: o units: nil]; - } + [self setOrg: o units: nil]; + + // Department + if ((o = [theValues objectForKey: @"Department"])) + [self setOrg: nil units: [NSArray arrayWithObjects:o,nil]]; // Email addresses if ((o = [theValues objectForKey: @"Email1Address"])) { - [self addEmail: o types: [NSArray arrayWithObject: @"pref"]]; + element = [self elementWithTag: @"email" ofType: @"work"]; + [element setSingleValue: o forKey: @""]; } if ((o = [theValues objectForKey: @"Email2Address"])) { - [self addEmail: o types: nil]; + element = [self elementWithTag: @"email" ofType: @"home"]; + [element setSingleValue: o forKey: @""]; } + // SOGo currently only supports 2 email addresses ... but AS clients might send 3 + // FIXME: revise this when the GUI revamp is done in SOGo if ((o = [theValues objectForKey: @"Email3Address"])) { - [self addEmail: o types: nil]; + element = [self elementWithTag: @"email" ofType: @"three"]; + [element setSingleValue: o forKey: @""]; } // Formatted name @@ -293,16 +337,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Job's title if ((o = [theValues objectForKey: @"JobTitle"])) - { - [self setTitle: o]; - } + [self setTitle: o]; // WebPage (work) if ((o = [theValues objectForKey: @"WebPage"])) - { - [[self elementWithTag: @"url" ofType: @"work"] + [[self elementWithTag: @"url" ofType: @"work"] setSingleValue: o forKey: @""]; - } + + if ((o = [theValues objectForKey: @"NickName"])) + [self setNickname: o]; } @end diff --git a/ActiveSync/NSArray+SyncCache.h b/ActiveSync/NSArray+SyncCache.h new file mode 100644 index 000000000..8eebd2475 --- /dev/null +++ b/ActiveSync/NSArray+SyncCache.h @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSARRAYSYNCCACHE_H__ +#define __NSARRAYSYNCCACHE_H__ + +#import + +@class NSDictionary; + +@interface NSMutableArray (SyncCache) + +- (id) initWithDictionary: (NSDictionary *) theDictionary; + +@end + +@interface NSArray (SyncCache) + +- (NSDictionary *) dictionaryValue; + +@end + +#endif diff --git a/ActiveSync/NSArray+SyncCache.m b/ActiveSync/NSArray+SyncCache.m new file mode 100644 index 000000000..c499f2e23 --- /dev/null +++ b/ActiveSync/NSArray+SyncCache.m @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NSArray+SyncCache.h" + +#import + +#include "SOGoSyncCacheObject.h" + +@implementation NSMutableArray (SyncCache) + +- (id) initWithDictionary: (NSDictionary *) theDictionary +{ + SOGoSyncCacheObject *o; + NSArray *allKeys; + id key; + int i; + + self = [self initWithCapacity: [theDictionary count]]; + + allKeys = [theDictionary allKeys]; + + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + o = [SOGoSyncCacheObject syncCacheObjectWithUID: key + sequence: [theDictionary objectForKey: key]]; + [self addObject: o]; + } + + return self; +} + +@end + +// +// +// +@implementation NSArray (SyncCache) + +- (NSDictionary *) dictionaryValue +{ + NSMutableDictionary *d; + SOGoSyncCacheObject *o; + int i; + + d = [NSMutableDictionary dictionary]; + + for (i = 0; i < [self count]; i++) + { + o = [self objectAtIndex: i]; + [d setObject: [o sequence] forKey: [o uid]]; + } + + return d; +} + +@end diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 2a2382ffa..3102e92e8 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -32,9 +32,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import #import #import #import +#import #import #import @@ -72,6 +74,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -101,11 +104,48 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "NSString+ActiveSync.h" #include "SOGoActiveSyncConstants.h" #include "SOGoMailObject+ActiveSync.h" +#include "SOGoSyncCacheObject.h" #include @implementation SOGoActiveSyncDispatcher (Sync) +- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata + forKey: (NSString *) theFolderKey +{ + SOGoCacheGCSObject *o; + NSString *key; + + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties] removeObjectForKey: @"SyncCache"]; + [[o properties] removeObjectForKey: @"DateCache"]; + + [[o properties] addEntriesFromDictionary: theFolderMetadata]; + [o save]; +} + +- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey +{ + SOGoCacheGCSObject *o; + NSString *key; + + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + return [o properties]; +} + + // // // @@ -423,7 +463,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendString: @""]; } - // // The method handles // @@ -450,6 +489,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return; s = [NSMutableString string]; + more_available = NO; switch (theFolderType) @@ -567,106 +607,189 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncMailFolder: default: { - NSMutableArray *addedOrChangedMessages; - NSString *uid, *command, *key; + NSMutableDictionary *syncCache, *dateCache, *folderMetadata; + SOGoSyncCacheObject *lastCacheObject, *aCacheObject; + NSMutableArray *allCacheObjects, *sortedBySequence; + SOGoMailObject *mailObject; - NSDictionary *aMessage; NSArray *allMessages; - int deleted_count; + + int j, k, return_count; + BOOL found_in_cache; + allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; - addedOrChangedMessages = [NSMutableArray array]; - deleted_count = 0; - - // Check for the WindowSize. - // FIXME: we should eventually check for modseq and slice the maximum - // amount of messages returned to ensure we don't have the same - // modseq accross contiguous boundaries max = [allMessages count]; - // We first check the number of deleted messages we have - // We do NOT honor the window size here as it seems to be - // impossible to get the modseq of an expunged message so - // we can't iterate in the list of deleted messages. - for (i = 0; i < max; i++) - { - aMessage = [allMessages objectAtIndex: i]; - - uid = [[[aMessage allKeys] lastObject] stringValue]; - command = [[aMessage allValues] lastObject]; - - if ([command isEqualToString: @"deleted"]) - { - [s appendString: @""]; - [s appendFormat: @"%@", uid]; - [s appendString: @""]; - deleted_count++; - } - else - { - [addedOrChangedMessages addObject: aMessage]; - } - } - - // We then "pad" with our added/changed messages. We ALWAYS - // at least return one if available - max = [addedOrChangedMessages count]; + allCacheObjects = [NSMutableArray array]; for (i = 0; i < max; i++) { - aMessage = [addedOrChangedMessages objectAtIndex: i]; - - uid = [[[aMessage allKeys] lastObject] stringValue]; - command = [[aMessage allValues] lastObject]; - - // We check for Outlook stupidity to avoid creating duplicates - see the comment - // in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details. - key = [NSString stringWithFormat: @"%@+%@+%@+%@", - [[context activeUser] login], - [context objectForKey: @"DeviceType"], - [theCollection displayName], - uid]; - - if ([[SOGoCache sharedCache] valueForKey: key]) + [allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject] + sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]]; + } + + // If it's a new Sync operation, DateCache and SyncCache need to be deleted + // but GUID stored by folderSync shouldn't be touched + folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; + if ([theSyncKey isEqualToString: @"-1"]) + { + [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: + [NSString stringWithFormat: @"folder%@", [self globallyUniqueIDToIMAPFolderName: [folderMetadata objectForKey: @"GUID"] type: theFolderType]]])) + { + NSLog(@"GUID mismatch don't sync now!"); + return; + } + + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; + + sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache]; + [sortedBySequence sortUsingSelector: @selector(compareSequence:)]; + [sortedBySequence autorelease]; + + [allCacheObjects sortUsingSelector: @selector(compareSequence:)]; + + //NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]); + //NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]); + + lastCacheObject = [sortedBySequence lastObject]; + + if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject) + { + for (j = 0; j < [allCacheObjects count]; j++) { - [[SOGoCache sharedCache] removeValueForKey: key]; - command = @"changed"; + if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]]) + { + // Found out where we're at, let's continue from there... + found_in_cache = YES; + break; + } } - - if ([command isEqualToString: @"added"]) - [s appendString: @""]; - else - [s appendString: @""]; - - mailObject = [theCollection lookupName: uid - inContext: context - acquire: 0]; - - [s appendFormat: @"%@", uid]; - [s appendString: @""]; - [s appendString: [mailObject activeSyncRepresentationInContext: context]]; - [s appendString: @""]; - - if ([command isEqualToString: @"added"]) - [s appendString: @""]; - else - [s appendString: @""]; - - - // We check if we must stop padding - if (i+1+deleted_count > theWindowSize) + } + else + found_in_cache = NO; + + + if (found_in_cache) + k = j+1; + else + { + k = 0; + j = 0; + } + + //NSLog(@"found in cache: %d k = %d", found_in_cache, k); + + return_count = 0; + + for (; k < [allCacheObjects count]; k++) + { + // Check for the WindowSize and slice accordingly + if (return_count >= theWindowSize) { + NSString *lastSequence; more_available = YES; + + lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]); + *theLastServerKey = [NSString stringWithFormat: @"%@-%@", [aCacheObject uid], lastSequence]; + //NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey); break; } + + aCacheObject = [allCacheObjects objectAtIndex: k]; + + // If found in cache, it's either a Change or a Delete + if ([syncCache objectForKey: [aCacheObject uid]]) + { + if ([[aCacheObject sequence] isEqual: [NSNull null]]) + { + // Deleted + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + + [syncCache removeObjectForKey: [aCacheObject uid]]; + [dateCache removeObjectForKey: [aCacheObject uid]]; + } + else + { + // Changed + outlook_hack: + mailObject = [theCollection lookupName: [aCacheObject uid] + inContext: context + acquire: 0]; + + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendString: @""]; + [s appendString: @""]; + + [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]]; + } + + return_count++; + } + else + { + // Added + if (![[aCacheObject sequence] isEqual: [NSNull null]]) + { + NSString *key; + + // We check for Outlook stupidity to avoid creating duplicates - see the comment + // in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details. + key = [NSString stringWithFormat: @"%@+%@+%@+%@", + [[context activeUser] login], + [context objectForKey: @"DeviceType"], + [theCollection displayName], + [aCacheObject uid]]; + + if ([[SOGoCache sharedCache] valueForKey: key]) + { + [[SOGoCache sharedCache] removeValueForKey: key]; + goto outlook_hack; + } + + mailObject = [theCollection lookupName: [aCacheObject uid] + inContext: context + acquire: 0]; + + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendString: @""]; + [s appendString: @""]; + + [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]]; + [dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]]; + return_count++; + } + else + { + //NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]); + } + } + } - - // + if (more_available) - { - *theLastServerKey = uid; - } - } + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + else + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + + + [self _setFolderMetadata: folderMetadata + forKey: [theCollection nameInContainer]]; + } // default: break; } // switch (folderType) ... @@ -767,13 +890,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSMutableString *changeBuffer, *commandsBuffer; BOOL getChanges, first_sync; - unsigned int windowSize; + unsigned int windowSize, v; changeBuffer = [NSMutableString string]; commandsBuffer = [NSMutableString string]; collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; collection = [self collectionFromId: realCollectionId type: folderType]; syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; @@ -783,6 +907,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (windowSize == 0 || windowSize > 512) windowSize = 100; + + // We check if we must overwrite the windowSize with a system preference. This can be useful + // if the user population has large mailboxes and slow connectivity + if ((v = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncWindowSize])) + windowSize = v; lastServerKey = nil; @@ -855,12 +984,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([changeBuffer length] || [commandsBuffer length]) { if (lastServerKey) - davCollectionTag = [collection davCollectionTagFromId: lastServerKey]; - else + davCollectionTag = lastServerKey; + else if (![[self _folderMetadataForKey: [collection nameInContainer]] objectForKey: @"MoreAvailable"]) davCollectionTag = [collection davCollectionTag]; *changeDetected = YES; } + else + { + if (folderType == ActiveSyncMailFolder && [syncKey isEqualToString: @"-1"]) + davCollectionTag = [collection davCollectionTag]; + } + // Generate the response buffer [theBuffer appendString: @""]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.h b/ActiveSync/SOGoActiveSyncDispatcher.h index 0c28ebced..d72073ad1 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.h +++ b/ActiveSync/SOGoActiveSyncDispatcher.h @@ -32,17 +32,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoActiveSyncConstants.h" @class NSException; +@class NSURL; @interface SOGoActiveSyncDispatcher : NSObject { + NSURL *folderTableURL; id context; } - (id) collectionFromId: (NSString *) theCollectionId type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; +- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; + - (NSException *) dispatchRequest: (id) theRequest inResponse: (id) theResponse context: (id) theContext; +- (NSURL *) folderTableURL; +- (void) ensureFolderTableExists; + @end diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 7d99cebc5..63e4b54ee 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -77,6 +77,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import #import @@ -85,6 +86,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import +#import #import #import @@ -115,24 +118,76 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoActiveSyncConstants.h" #include "SOGoMailObject+ActiveSync.h" +#import + #include @implementation SOGoActiveSyncDispatcher +- (id) init +{ + [super init]; + + folderTableURL = nil; + return self; +} + +- (void) dealloc +{ + RELEASE(folderTableURL); + [super dealloc]; +} + - (void) _setFolderSyncKey: (NSString *) theSyncKey { - NSMutableDictionary *metadata; - - metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; - - [metadata setObject: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"SyncKey"] forKey: @"FolderSync"]; + SOGoCacheGCSObject *o; - [[[context activeUser] userSettings] setMicrosoftActiveSyncMetadata: metadata - forDevice: [context objectForKey: @"DeviceId"]]; - - [[[context activeUser] userSettings] synchronize]; + o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties] removeAllObjects]; + [[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]]; + [o save]; } +- (NSMutableDictionary *) _globalMetadataForDevice +{ + SOGoCacheGCSObject *o; + + o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + return [o properties]; +} + +- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType +{ + if (theFolderType == ActiveSyncMailFolder) + { + SOGoMailAccounts *accountsFolder; + SOGoMailAccount *accountFolder; + SOGoUserFolder *userFolder; + NSDictionary *imapGUIDs; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + // Get the GUID of the IMAP folder + imapGUIDs = [accountFolder imapFolderGUIDs]; + + return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0]; + } + + return theIdToTranslate; +} + + // // // @@ -166,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; - + collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId] inContext: context acquire: NO]; @@ -212,20 +267,52 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; - - newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]] - inContext: context - acquire: NO]; + + // If the parrent is 0 -> ok ; otherwise need to build the foldername based on parentId + displayName + if ([parentId isEqualToString: @"0"]) + newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]] + inContext: context + acquire: NO]; + else + { + parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: ActiveSyncMailFolder]; + newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@/%@", [parentId stringByEncodingImap4FolderName], + [displayName stringByEncodingImap4FolderName]] + inContext: context + acquire: NO]; + } // FIXME // handle exists (status == 2) // handle right synckey if ([newFolder create]) { + SOGoMailAccount *accountFolder; + NSDictionary *imapGUIDs; + SOGoCacheGCSObject *o; + NSString *key; + nameInContainer = [newFolder nameInContainer]; // We strip the "folder" prefix nameInContainer = [nameInContainer substringFromIndex: 6]; + + // save new guid into cache + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + // update GUID in cache + imapGUIDs = [accountFolder imapFolderGUIDs]; + + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], nameInContainer ]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + nameInContainer =[imapGUIDs objectForKey: nameInContainer]; + + [[o properties ] setObject: nameInContainer forKey: @"GUID"]; + [o save]; + nameInContainer = [[NSString stringWithFormat: @"mail/%@", nameInContainer] stringByEscapingURL]; } else @@ -306,9 +393,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSString *serverId; SOGoMicrosoftActiveSyncFolderType folderType; - serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType]; userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; @@ -369,6 +456,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int status; serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType]; parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue]; displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue]; @@ -380,7 +468,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inContext: context acquire: NO]; - error = [folderToUpdate renameTo: displayName]; + // If parent is 0 or displayname is not changed it is either a rename of a folder in 0 or a move to 0 + if ([parentId isEqualToString: @"0"] || + ([serverId hasSuffix: [NSString stringWithFormat: @"/%@", displayName]] && [parentId isEqualToString: @"0"])) + { + error = [folderToUpdate renameTo: [NSString stringWithFormat: @"/%@", [displayName stringByEncodingImap4FolderName]]]; + } + else + { + parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: folderType]; + error = [folderToUpdate renameTo: [NSString stringWithFormat: @"%@/%@", [parentId stringByEncodingImap4FolderName], + [displayName stringByEncodingImap4FolderName]]]; + } // Handle new name exist if (!error) @@ -396,10 +495,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // See http://msdn.microsoft.com/en-us/library/gg675615(v=exchg.80).aspx // we return '9' - we force a FolderSync - status = 9; + status = 1; [self _setFolderSyncKey: syncKey]; + // FIXME - TODO: We *MUST* update the path in our cache! See -changePathTo in SOGoCacheGCSObject + s = [NSMutableString string]; [s appendString: @""]; [s appendString: @""]; @@ -430,15 +531,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processFolderSync: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { + NSMutableDictionary *metadata; NSMutableString *s; NSString *syncKey; NSData *d; - + BOOL first_sync; int status; - metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; + metadata = [self _globalMetadataForDevice]; syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; s = [NSMutableString string]; @@ -450,7 +552,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. first_sync = YES; syncKey = @"1"; } - else if (![syncKey isEqualToString: [[metadata objectForKey: @"FolderSync"] objectForKey: @"SyncKey"]]) + else if (![syncKey isEqualToString: [metadata objectForKey: @"FolderSyncKey"]]) { // Synchronization key mismatch or invalid synchronization key status = 9; @@ -461,84 +563,213 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [s appendString: @""]; [s appendFormat: @"%d%@", status, syncKey]; - - // Initial sync, let's return the complete folder list - if (first_sync) + + if (status == 1) { SOGoMailAccounts *accountsFolder; SOGoMailAccount *accountFolder; SOGoUserFolder *userFolder; id currentFolder; - NSDictionary *folderMetadata; - NSArray *allFoldersMetadata; - NSString *name, *serverId, *parentId; + NSString *key, *cKey, *nkey, *name, *serverId, *parentId; + NSDictionary *folderMetadata, *imapGUIDs; + NSArray *allFoldersMetadata, *allKeys; + NSMutableDictionary *cachedGUIDs; + NSMutableString *commands; + SOGoCacheGCSObject *o; + + int i, type, command_count; - int i, type; - userFolder = [[context activeUser] homeFolderInContext: context]; - accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; - accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; allFoldersMetadata = [accountFolder allFoldersMetadata]; + + // Get GUIDs of folder (IMAP) + // e.g. {INBOX = "sogo_73c_192bd57b_d8" + imapGUIDs = [accountFolder imapFolderGUIDs]; - // See 2.2.3.170.3 Type (FolderSync) - http://msdn.microsoft.com/en-us/library/gg650877(v=exchg.80).aspx - [s appendFormat: @"%d", [allFoldersMetadata count]+3]; + cachedGUIDs = [NSMutableDictionary dictionary]; + + // No need to read cached folder infos during first sync. Otherwise, pull it from the database. + // e.g. {"sogo_73c_192bd57b_d8" = INBOX} - guid = foldername for easy reverse lookup with imapGUIDs + if (!first_sync) + { + NSArray *foldersInCache; + NSString *folderName; + + // get the list of folder stored in cache + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], @"0"]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + foldersInCache = [o folderList: [context objectForKey: @"DeviceId"] newerThanVersion: -1]; + + // get guids of folders stored in cache + for (i = 0; i < [foldersInCache count]; i++) + { + folderName = [foldersInCache objectAtIndex: i]; + key = [folderName substringFromIndex: 1]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + if ([[o properties ] objectForKey: @"GUID"]) + [cachedGUIDs setObject: [key substringFromIndex: [key rangeOfString: @"+"].location+7] + forKey: [[o properties] objectForKey: @"GUID"]]; + } + } + + // Handle folders that have been deleted over IMAP + command_count = 0; + commands = [NSMutableString string]; + allKeys = [cachedGUIDs allKeys]; + for (i = 0; i < [allKeys count]; i++) + { + cKey = [allKeys objectAtIndex: i]; + + if (![imapGUIDs allKeysForObject: cKey]) + { + // Delete folders cache content to avoid stale data if a new folder gets created with the same name + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: cKey]]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + // Only send a delete command if GUID is found + if ([[o properties] objectForKey: @"GUID"]) + { + [commands appendFormat: @"%@", [[NSString stringWithFormat: @"mail/%@", [[o properties ] objectForKey: @"GUID"]] stringByEscapingURL] ]; + command_count++; + } + + [[o properties] removeAllObjects]; + [o save]; + } + } + + // Handle addition and changes for (i = 0; i < [allFoldersMetadata count]; i++) { folderMetadata = [allFoldersMetadata objectAtIndex: i]; - serverId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"path"]]; + + // No GUID -> no sync + if (!([imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]])) + continue; + + serverId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]; name = [folderMetadata objectForKey: @"displayName"]; if ([name hasPrefix: @"/"]) name = [name substringFromIndex: 1]; if ([name hasSuffix: @"/"]) - name = [name substringToIndex: [name length]-2]; - + name = [name substringToIndex: [name length]-1]; + type = [[folderMetadata objectForKey: @"type"] activeSyncFolderType]; - parentId = @"0"; - + if ([folderMetadata objectForKey: @"parent"]) { - parentId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"parent"]]; + parentId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]]; name = [[name pathComponents] lastObject]; } - - [s appendFormat: @"%@%@%d%@", - [serverId stringByEscapingURL], - [parentId stringByEscapingURL], - type, - [name activeSyncRepresentationInContext: context]]; + + // Decide between add and change + if ([cachedGUIDs objectForKey: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]) + { + // Search GUID to check name change in cache (diff between IMAP and cache) + if ((![[[folderMetadata objectForKey: @"path"] substringFromIndex: 1] isEqualToString: [imapGUIDs objectForKey: [cachedGUIDs objectForKey: + [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]])) + { + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: + [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]]; + nkey = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] ]; + + if (![key isEqualToString: nkey]) + { + [commands appendFormat: @"%@%@%d%@", + [serverId stringByEscapingURL], + [parentId stringByEscapingURL], + type, + [name activeSyncRepresentationInContext: context]]; + + + // Change path in cache + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + [o changePathTo: [NSString stringWithFormat: @"/%@", nkey]]; // ?? why is '/' prefix needed - problem in changePathTo? + + command_count++; + } + } + } + else + { + [commands appendFormat: @"%@%@%d%@", + [serverId stringByEscapingURL], + [parentId stringByEscapingURL], + type, + [name activeSyncRepresentationInContext: context]]; + + // Store folder's GUID in cache + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties ] setObject: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]] forKey: @"GUID"]; + [o save]; + + command_count++; + } } - - // We add the personal calendar - events - // FIXME: add all calendars - currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; - name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; - - // We add the personal calendar - tasks - // FIXME: add all calendars - currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; - name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; - // We add the personal address book - // FIXME: add all address books - currentFolder = [[context activeUser] personalContactsFolderInContext: context]; - name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + if (first_sync) + [s appendFormat: @"%d", command_count+3]; + else + [s appendFormat: @"%d", command_count]; + + if (command_count > 0) + [s appendFormat: @"%@", commands]; + + if (first_sync) + { + // We add the personal calendar - events + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal calendar - tasks + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal address book + // FIXME: add all address books + currentFolder = [[context activeUser] personalContactsFolderInContext: context]; + name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + } } - + [s appendString: @""]; - + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; - + [theResponse setContent: d]; -} +} // // From: http://msdn.microsoft.com/en-us/library/ee157980(v=exchg.80).aspx : @@ -585,6 +816,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; currentCollection = [self collectionFromId: realCollectionId type: folderType]; // @@ -659,6 +891,7 @@ 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) { @@ -752,6 +985,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. status = 1; realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue]; requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; appointmentObject = nil; @@ -902,6 +1136,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSDictionary *response; NSString *v; + srcFolderId = [self globallyUniqueIDToIMAPFolderName: srcFolderId type: srcFolderType]; + dstFolderId = [self globallyUniqueIDToIMAPFolderName: dstFolderId type: dstFolderType]; + currentCollection = [self collectionFromId: srcFolderId type: srcFolderType]; client = [[currentCollection imap4Connection] client]; @@ -1037,6 +1274,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theResponse setContent: d]; } +// +// We ignore everything for now. +// +- (void) processProvision: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSMutableString *s; + NSData *d; + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + // // // @@ -1420,11 +1677,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { NSString *folderId, *itemId, *realCollectionId; SOGoMicrosoftActiveSyncFolderType folderType; + id value; folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue]; itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue]; realCollectionId = [folderId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; + value = [theDocumentElement getElementsByTagName: @"ReplaceMime"]; + + // ReplaceMime isn't specified so we must NOT use the server copy + // but rather take the data as-is from the client. + if (![value count]) + { + [self processSendMail: theDocumentElement + inResponse: theResponse]; + return; + } + if (folderType == ActiveSyncMailFolder) { SOGoMailAccounts *accountsFolder; @@ -1559,6 +1829,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmdName = [[theRequest uri] command]; + // We make sure our cache table exists + [self ensureFolderTableExists]; + // // If the MS-ASProtocolVersion header is set to "12.1", the body of the SendMail request is // is a "message/rfc822" payload - otherwise, it's a WBXML blob. @@ -1630,4 +1903,72 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return nil; } +- (NSURL *) folderTableURL +{ + NSString *urlString, *ocFSTableName; + NSMutableArray *parts; + SOGoUser *user; + + if (!folderTableURL) + { + user = [context activeUser]; + + if (![user loginInDomain]) + return nil; + + urlString = [[user domainDefaults] folderInfoURL]; + parts = [[urlString componentsSeparatedByString: @"/"] + mutableCopy]; + [parts autorelease]; + if ([parts count] == 5) + { + /* If "OCSFolderInfoURL" is properly configured, we must have 5 + parts in this url. */ + ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@", + [[user loginInDomain] asCSSIdentifier]]; + [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; + folderTableURL + = [NSURL URLWithString: [parts componentsJoinedByString: @"/"]]; + [folderTableURL retain]; + } + else + [NSException raise: @"MAPIStoreIOException" + format: @"'OCSFolderInfoURL' is not set"]; + } + + return folderTableURL; +} + +- (void) ensureFolderTableExists +{ + GCSChannelManager *cm; + EOAdaptorChannel *channel; + NSString *tableName, *query; + GCSSpecialQueries *queries; + + [self folderTableURL]; + + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: folderTableURL]; + + /* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */ + tableName = [[folderTableURL path] lastPathComponent]; + if (tableName && + [channel evaluateExpressionX: + [NSString stringWithFormat: @"SELECT count(*) FROM %@", + tableName]]) + { + queries = [channel specialQueries]; + query = [queries createSOGoCacheGCSFolderTableWithName: tableName]; + if ([channel evaluateExpressionX: query]) + [NSException raise: @"MAPIStoreIOException" + format: @"could not create special table '%@'", tableName]; + } + else + [channel cancelFetch]; + + + [cm releaseChannel: channel]; +} + @end diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 05f448df3..eb4fcff3e 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoMailObject+ActiveSync.h" #import +#import #import #import #import @@ -479,7 +480,9 @@ struct GlobalObjectId { // - (NSString *) activeSyncRepresentationInContext: (WOContext *) _context { + NSAutoreleasePool *pool; NSData *d, *globalObjId; + NSArray *attachmentKeys; NSMutableString *s; id value; @@ -670,6 +673,10 @@ struct GlobalObjectId { // Body - namespace 17 preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue]; + // Make use of a local pool here as _preferredBodyDataUsingType:nativeType: will consume + // a significant amout of RAM and file descriptors + pool = [[NSAutoreleasePool alloc] init]; + nativeBodyType = 1; d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType]; @@ -710,8 +717,10 @@ struct GlobalObjectId { [s appendString: @""]; } + DESTROY(pool); + // Attachments -namespace 16 - NSArray *attachmentKeys = [self fetchFileAttachmentKeys]; + attachmentKeys = [self fetchFileAttachmentKeys]; if ([attachmentKeys count]) { int i; diff --git a/ActiveSync/SOGoSyncCacheObject.h b/ActiveSync/SOGoSyncCacheObject.h new file mode 100644 index 000000000..c6ba61068 --- /dev/null +++ b/ActiveSync/SOGoSyncCacheObject.h @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef __SOGOSYNCCACHEOBJECT_H__ +#define __SOGOSYNCCACHEOBJECT_H__ + +#import + +@class NSNull; + +@interface SOGoSyncCacheObject : NSObject +{ + id _uid; + id _sequence; +} + ++ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; +- (id) uid; +- (void) setUID: (id) theUID; +- (id) sequence; +- (void) setSequence: (id) theSequence; + +- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject; +- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject; + +@end + +#endif diff --git a/ActiveSync/SOGoSyncCacheObject.m b/ActiveSync/SOGoSyncCacheObject.m new file mode 100644 index 000000000..ab973348d --- /dev/null +++ b/ActiveSync/SOGoSyncCacheObject.m @@ -0,0 +1,122 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "SOGoSyncCacheObject.h" + +#import +#import + +@implementation SOGoSyncCacheObject + +- (id) init +{ + if ((self = [super init])) + { + _uid = nil; + _sequence = nil; + } + + return self; +} + ++ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; +{ + id o; + + o = [[self alloc] init]; + + [o setUID: theUID]; + [o setSequence: theSequence]; + + return o; +} + +- (void) dealloc +{ + RELEASE(_uid); + RELEASE(_sequence); + [super dealloc]; +} + +- (id) uid +{ + return _uid; +} + +- (void) setUID: (id) theUID +{ + ASSIGN(_uid, theUID); +} + +- (id) sequence +{ + return _sequence; +} + +- (void) setSequence: (id) theSequence +{ + ASSIGN(_sequence, theSequence); +} + + +- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject +{ + return [[self uid] compare: [theSyncCacheObject uid]]; +} + +// +// We might get NSNull values here, so if both are NSNull instances, +// we sort by UID. If both sequences are equal, we also sort by UID. +// +- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject +{ + if ([[self sequence] isEqual: [NSNull null]] && + [[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return [self compareUID: theSyncCacheObject]; + + if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return NSOrderedDescending; + + if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return NSOrderedAscending; + + // Must check this here, to avoid comparing NSNull objects + if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame) + return [self compareUID: theSyncCacheObject]; + + return [[self sequence] compare: [theSyncCacheObject sequence]]; +} + +- (NSString *) description +{ + return [NSString stringWithFormat: @"%@-%@", _uid, _sequence]; +} + +@end diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m index 8b15a6d8d..0a8ea7a56 100644 --- a/ActiveSync/iCalEvent+ActiveSync.m +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -160,7 +160,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. attendee = [attendees objectAtIndex: i]; [s appendFormat: @"%@", [attendee rfc822Email]]; - [s appendFormat: @"%@", [attendee cn]]; + [s appendFormat: @"%@", [[attendee cn] activeSyncRepresentationInContext: context]]; attendee_status = [self _attendeeStatus: attendee]; diff --git a/ActiveSync/iCalRecurrenceRule+ActiveSync.m b/ActiveSync/iCalRecurrenceRule+ActiveSync.m index 18bdf595c..8e7cf57cb 100644 --- a/ActiveSync/iCalRecurrenceRule+ActiveSync.m +++ b/ActiveSync/iCalRecurrenceRule+ActiveSync.m @@ -79,7 +79,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. for (i = 0; i < 7; i++) { - if (occurrences[i]) + if (occurrences[0][i]) v += (1 << i); } diff --git a/ChangeLog b/ChangeLog index d984d6bab..a8448861f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,1381 @@ +commit d91b37413fa0fe7f416311697ad7b487f88d967c +Author: Francis Lachapelle +Date: Thu May 29 11:16:40 2014 -0400 + + Preparation for release 2.2.4 + +M Documentation/SOGo Installation Guide.odt +M Documentation/SOGo Mozilla Thunderbird Configuration.odt +M Documentation/SOGo Native Microsoft Outlook Configuration.odt +M Version + +commit bd916f06eb9433db71aef0c0885bfc6cde5e4fea +Author: Francis Lachapelle +Date: Thu May 29 11:15:13 2014 -0400 + + Update ChangeLog + +M ChangeLog + +commit ecbe0858f72742872ac45a17bc393ac5bedefc70 +Author: Francis Lachapelle +Date: Thu May 29 11:14:23 2014 -0400 + + Update translations + +M NEWS +M SoObjects/Contacts/Czech.lproj/Localizable.strings +M SoObjects/Contacts/Hungarian.lproj/Localizable.strings +M SoObjects/Contacts/Polish.lproj/Localizable.strings +M SoObjects/Contacts/Russian.lproj/Localizable.strings +M SoObjects/Contacts/Slovak.lproj/Localizable.strings +M SoObjects/Contacts/SpanishArgentina.lproj/Localizable.strings +M SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings +M SoObjects/Mailer/Czech.lproj/Localizable.strings +M SoObjects/Mailer/Polish.lproj/Localizable.strings +M SoObjects/Mailer/Slovak.lproj/Localizable.strings +M SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings +M UI/Common/Hungarian.lproj/Localizable.strings +M UI/Common/SpanishArgentina.lproj/Localizable.strings +M UI/Contacts/Czech.lproj/Localizable.strings +M UI/Contacts/Hungarian.lproj/Localizable.strings +M UI/Contacts/Polish.lproj/Localizable.strings +M UI/Contacts/Russian.lproj/Localizable.strings +M UI/Contacts/Slovak.lproj/Localizable.strings +M UI/Contacts/SpanishArgentina.lproj/Localizable.strings +M UI/Contacts/SpanishSpain.lproj/Localizable.strings +M UI/MailPartViewers/Slovak.lproj/Localizable.strings +M UI/MailPartViewers/SpanishArgentina.lproj/Localizable.strings +M UI/MailerUI/Czech.lproj/Localizable.strings +M UI/MailerUI/French.lproj/Localizable.strings +M UI/MailerUI/Hungarian.lproj/Localizable.strings +M UI/MailerUI/Polish.lproj/Localizable.strings +M UI/MailerUI/Russian.lproj/Localizable.strings +M UI/MailerUI/Slovak.lproj/Localizable.strings +M UI/MailerUI/SpanishArgentina.lproj/Localizable.strings +M UI/MailerUI/SpanishSpain.lproj/Localizable.strings +M UI/PreferencesUI/Czech.lproj/Localizable.strings +M UI/PreferencesUI/Hungarian.lproj/Localizable.strings +M UI/PreferencesUI/Polish.lproj/Localizable.strings +M UI/PreferencesUI/Russian.lproj/Localizable.strings +M UI/PreferencesUI/Slovak.lproj/Localizable.strings +M UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings +M UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +M UI/Scheduler/Czech.lproj/Localizable.strings +M UI/Scheduler/Hungarian.lproj/Localizable.strings +M UI/Scheduler/Polish.lproj/Localizable.strings +M UI/Scheduler/Russian.lproj/Localizable.strings +M UI/Scheduler/Slovak.lproj/Localizable.strings +M UI/Scheduler/SpanishArgentina.lproj/Localizable.strings +M UI/Scheduler/SpanishSpain.lproj/Localizable.strings + +commit 24095a464c60b7a966daa0eecb8d720f25aa1366 +Author: Francis Lachapelle +Date: Thu May 29 10:45:49 2014 -0400 + + Update CKEditor to version 4.4.1 + +M NEWS +M UI/WebServerResources/ckeditor/ckeditor.js +M UI/WebServerResources/ckeditor/contents.css +M UI/WebServerResources/ckeditor/lang/ar.js +M UI/WebServerResources/ckeditor/lang/ca.js +M UI/WebServerResources/ckeditor/lang/cs.js +M UI/WebServerResources/ckeditor/lang/cy.js +M UI/WebServerResources/ckeditor/lang/da.js +M UI/WebServerResources/ckeditor/lang/de.js +M UI/WebServerResources/ckeditor/lang/en.js +M UI/WebServerResources/ckeditor/lang/es.js +M UI/WebServerResources/ckeditor/lang/fi.js +M UI/WebServerResources/ckeditor/lang/fr.js +M UI/WebServerResources/ckeditor/lang/hu.js +M UI/WebServerResources/ckeditor/lang/is.js +M UI/WebServerResources/ckeditor/lang/it.js +M UI/WebServerResources/ckeditor/lang/nb.js +M UI/WebServerResources/ckeditor/lang/nl.js +M UI/WebServerResources/ckeditor/lang/no.js +M UI/WebServerResources/ckeditor/lang/pl.js +M UI/WebServerResources/ckeditor/lang/pt-br.js +M UI/WebServerResources/ckeditor/lang/ru.js +M UI/WebServerResources/ckeditor/lang/sk.js +M UI/WebServerResources/ckeditor/lang/sv.js +M UI/WebServerResources/ckeditor/lang/uk.js +M UI/WebServerResources/ckeditor/plugins/div/dialogs/div.js +D UI/WebServerResources/ckeditor/plugins/fakeobjects/images/spacer.gif +M UI/WebServerResources/ckeditor/plugins/icons.png +M UI/WebServerResources/ckeditor/plugins/link/dialogs/anchor.js +M UI/WebServerResources/ckeditor/plugins/link/dialogs/link.js +M UI/WebServerResources/ckeditor/plugins/scayt/dialogs/options.js +M UI/WebServerResources/ckeditor/plugins/wsc/dialogs/ciframe.html +D UI/WebServerResources/ckeditor/plugins/wsc/dialogs/tmp.html +M UI/WebServerResources/ckeditor/plugins/wsc/dialogs/tmpFrameset.html +M UI/WebServerResources/ckeditor/plugins/wsc/dialogs/wsc.css +M UI/WebServerResources/ckeditor/plugins/wsc/dialogs/wsc.js +M UI/WebServerResources/ckeditor/plugins/wsc/dialogs/wsc_ie.js +M UI/WebServerResources/ckeditor/skins/moono/dialog.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_iequirks.css +D UI/WebServerResources/ckeditor/skins/moono/dialog_opera.css +M UI/WebServerResources/ckeditor/skins/moono/icons.png + +commit efc33d817458e38309f4175a5ba20745b57769fd +Author: Jeroen Dekkers +Date: Thu May 29 15:53:13 2014 +0200 + + Fix unit test by also checking for the different gnustep 1.24 ordering of elements + +M Tests/Unit/TestVersit.m + +commit 93971dc4348f52f46bde415d3dc8f92449418860 +Author: Jeroen Dekkers +Date: Thu May 29 15:35:46 2014 +0200 + + Add $(TEST_TOOL) as dependency of make check + +M Tests/Unit/GNUmakefile + +commit fac9520f6437473ee00fd93fc63e08cd67254e05 +Author: Francis Lachapelle +Date: Wed May 28 11:39:25 2014 -0400 + + Fix update of participation status via CalDAV + + Fixes #2786 + +M NEWS +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 9eb075cc3f0bd2ba95d6c36377601c4410e9de41 +Author: Francis Lachapelle +Date: Wed May 28 11:24:10 2014 -0400 + + Rename "Archive" to "Export" in Webmail + + Fixes #2758 + +M UI/MailerUI/English.lproj/Localizable.strings +M UI/Templates/MailerUI/UIxMailMainFrame.wox + +commit c5922b07aa5412e675aa5ac6a4c0698780f2667a +Author: Francis Lachapelle +Date: Wed May 28 11:19:31 2014 -0400 + + Restore options when editing a draft + + Fixes #193 + +M NEWS +M SoObjects/Mailer/SOGoDraftObject.m +M UI/WebServerResources/UIxMailEditor.js + +commit 51e07fc3065498ce00b3fe2e626d039fb11bbf4b +Author: Ludovic Marcotte +Date: Tue May 27 15:33:39 2014 -0400 + + Fix over previous commit + +M SoObjects/Mailer/SOGoMailAccount.m + +commit d35c52bb38e6bf0aa2f22e9466b66f1bf894b8e6 +Author: Ludovic Marcotte +Date: Tue May 27 14:44:57 2014 -0400 + + Fix for bug #2688 + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.h +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS +M SoObjects/Mailer/SOGoMailAccount.h +M SoObjects/Mailer/SOGoMailAccount.m +M SoObjects/SOGo/SOGoCacheGCSObject.h +M SoObjects/SOGo/SOGoCacheGCSObject.m + +commit 6bab4c94f2ccf324794b7e0b01d46d990b2bcee9 +Author: Ludovic Marcotte +Date: Tue May 27 09:42:02 2014 -0400 + + Added an empty stub for the Provision command + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit c31429a5d5f0f6f5ca167d294d9c3593a21e90a8 +Author: Ludovic Marcotte +Date: Tue May 27 08:45:00 2014 -0400 + + Also include the BS character in non-safe ones. + +M SoObjects/SOGo/NSString+Utilities.m + +commit 04bb88a4701927f8b6eea7610581372ace79ea81 +Author: Ludovic Marcotte +Date: Mon May 26 13:25:39 2014 -0400 + + Updated NEWS file regarding pull request #39 + +M NEWS + +commit 6fc81e61ecd98034b00fa36761f8ef81cb60ffe7 +Author: Ludovic Marcotte +Date: Fri May 23 11:30:28 2014 -0400 + + Fixed documentation: NGImap4ConnectionGroupPrefix -> NGImap4ConnectionGroupIdPrefix + +M Documentation/SOGo Installation Guide.odt + +commit d9ad28665095676f486f290936fced2e55e5d2e0 +Author: Francis Lachapelle +Date: Fri May 23 11:23:02 2014 -0400 + + Fix newline in signature of attachment + + Fixes #2787 + +M NEWS +M UI/MailerUI/UIxMailActions.m + +commit dd57d56cf91fdf23a588528bb32b1dcd0087d653 +Author: Alexandre Cloutier +Date: Fri May 23 10:15:21 2014 -0400 + + remove crufts and applies comments + +M SoObjects/SOGo/SOGoGCSFolder.m + +commit ab7780d617c28656d5c1afadcbe42aace27362b1 +Author: Ludovic Marcotte +Date: Thu May 22 14:39:58 2014 -0400 + + Prevent a crash when someone tries to access /SOGo/Microsoft-Server-ActiveSync directly + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit cb9332f2406fc1d7b1b33b11d2322b58d805064c +Author: Ludovic Marcotte +Date: Thu May 22 10:26:58 2014 -0400 + + Updated NEWS file regarding #2775 + +M NEWS + +commit 7b60ad4797167202dfba52c3956a0f4f48c723e2 +Author: Ludovic Marcotte +Date: Thu May 22 09:20:05 2014 -0400 + + Added patch from #2775 + +M ActiveSync/NGDOMElement+ActiveSync.m +M ActiveSync/NGVCard+ActiveSync.m + +commit 3261c215499f657950345df0164ff1f964ac7f06 +Author: Alexandre Cloutier +Date: Wed May 21 14:26:08 2014 -0400 + + fix case where the displayed name for the subscribed folders is duplicated + +M UI/WebServerResources/UIxContactsUserFolders.js + +commit 364b4095017dd574ae088c291ea064dd68b0fb46 +Author: Francis Lachapelle +Date: Wed May 21 08:44:35 2014 -0400 + + FIx mail templates for Brazilian Portuguese + + Fixes #2738 + +M NEWS +M SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod +M SoObjects/Mailer/SOGoMailBrazilianPortugueseReply.wo/SOGoMailBrazilianPortugueseReply.wod + +commit 064565e1a9651daf538ca0ce9786a9db0a874014 +Author: Alexandre Cloutier +Date: Tue May 20 17:05:13 2014 -0400 + + Added new SOGo configurable variable for which you can change the default display when adding a shared calendar or a shared addressBook + +M SoObjects/SOGo/SOGoDefaults.plist +M SoObjects/SOGo/SOGoDomainDefaults.h +M SoObjects/SOGo/SOGoDomainDefaults.m +M SoObjects/SOGo/SOGoGCSFolder.h +M SoObjects/SOGo/SOGoGCSFolder.m + +commit f92d280fbb00b138591f7fc31f30705aecbed25e +Author: Alexandre Cloutier +Date: Tue May 20 11:24:27 2014 -0400 + + remove unneeded condition to fix navigation in print preview + +M UI/WebServerResources/UIxCalViewPrint.js + +commit ea2bce706cd7fe7a307c5a93cab62160a6f3ecc5 +Author: Francis Lachapelle +Date: Fri May 16 21:15:38 2014 -0400 + + Update NEWS file + +M NEWS + +commit d74afaf2954c6043358701f590d543ff15e51583 +Author: Francis Lachapelle +Date: Fri May 16 21:15:28 2014 -0400 + + Update French translation + +M UI/Scheduler/French.lproj/Localizable.strings + +commit 5e7beb334526027367d45d9648d0632f40d5543b +Author: Francis Lachapelle +Date: Fri May 16 21:14:59 2014 -0400 + + Remove unused localizable strings + +M UI/Scheduler/English.lproj/Localizable.strings + +commit 3d1bdf1a09718f2a6e4e2978ed1a48959345575d +Author: Francis Lachapelle +Date: Fri May 16 16:52:44 2014 -0400 + + Update French translation + +M NEWS +M SoObjects/Contacts/French.lproj/Localizable.strings +M UI/Contacts/French.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/Scheduler/French.lproj/Localizable.strings + +commit 5de1343506df07d2c97fef18d28473b61a1e4a0d +Author: Francis Lachapelle +Date: Fri May 16 16:44:25 2014 -0400 + + Remove unused localizable string + +M UI/Scheduler/English.lproj/Localizable.strings + +commit 1eaa3f3891b0a6ca0251273c41126e024e6dbd87 +Author: Francis Lachapelle +Date: Fri May 16 16:33:52 2014 -0400 + + Update NEWS file + +M NEWS + +commit 819d0641b75e0359eba4661e368a190d06b91d33 +Author: Francis Lachapelle +Date: Fri May 16 16:19:40 2014 -0400 + + Remove common localizable strings from UI/Contacts + +M UI/Contacts/English.lproj/Localizable.strings + +commit 5d7f4ba6c659d5f0144f9be93cb995a06e36b3c2 +Author: Francis Lachapelle +Date: Fri May 16 15:59:52 2014 -0400 + + Restore localizable string + + To avoid new translation .. + +M UI/Scheduler/English.lproj/Localizable.strings + +commit 34a7e28858346a95225b34a5acef58c01d8afbd6 +Author: Francis Lachapelle +Date: Fri May 16 15:58:34 2014 -0400 + + Rename 'Properties' button of contacts to 'Edit' + +M UI/Contacts/English.lproj/Localizable.strings +M UI/Contacts/Toolbars/SOGoContactFolder.toolbar +M UI/Templates/ContactsUI/UIxContactFoldersView.wox + +commit 87b010c70030befd873e2135822328ce9c45f622 +Author: Francis Lachapelle +Date: Fri May 16 15:57:33 2014 -0400 + + Indentation + +M UI/Contacts/GNUmakefile +M UI/Contacts/UIxContactFolderProperties.h +M UI/Contacts/UIxContactFolderProperties.m +M UI/Templates/ContactsUI/UIxContactFolderProperties.wox +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/UIxContactFolderProperties.js + +commit b488721f83fb7d2838b2b5e2ffe40d9df303039f +Author: Alexandre Cloutier +Date: Fri May 16 15:00:47 2014 -0400 + + Reverse loop to make sure no objects is skipped + +M UI/Scheduler/UIxCalListingActions.m + +commit 8a03f3bd631ae53e5d5c111886cd27275cf69644 +Author: Alexandre Cloutier +Date: Fri May 16 14:36:51 2014 -0400 + + remove start date in tasks + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/WebServerResources/UIxCalViewPrint.js + +commit f8c8abc989b12988aca19d44152f11afbc0b57e6 +Author: Alexandre Cloutier +Date: Fri May 16 13:50:52 2014 -0400 + + Function isPublicAccessEnabled was missing + +M UI/Contacts/UIxContactFolderProperties.m + +commit db8d9bb16cc94b3bbc5fb5f20daf9953202791fb +Author: Alexandre Cloutier +Date: Fri May 16 11:29:37 2014 -0400 + + Repair broken logic + +M UI/Scheduler/UIxCalListingActions.m + +commit 2794425a3fd7d81c1f4be4c2b62df599dfed5ca9 +Author: Alexandre Cloutier +Date: Fri May 16 10:04:37 2014 -0400 + + added last changes from cgx + +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 2f7979c796ced02fb169023e152fefd87328eb2b +Author: Ludovic Marcotte +Date: Fri May 16 08:33:06 2014 -0400 + + Fix for bug #2709 + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit c426afd7f23a39382e3a3449164190d526ce28cc +Author: Ludovic Marcotte +Date: Thu May 15 15:03:24 2014 -0400 + + New cache subsystem for ActiveSync. + +M ActiveSync/GNUmakefile +A ActiveSync/NSArray+SyncCache.h +A ActiveSync/NSArray+SyncCache.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.h +M ActiveSync/SOGoActiveSyncDispatcher.m +A ActiveSync/SOGoSyncCacheObject.h +A ActiveSync/SOGoSyncCacheObject.m +M SoObjects/SOGo/SOGoCacheGCSObject.h +M SoObjects/SOGo/SOGoCacheObject.m + +commit 07445eb06965aee2279db2de61eba0a9fa4aa197 +Author: Alexandre Cloutier +Date: Thu May 15 14:44:31 2014 -0400 + + Created a window for address book properties and add the cardDav URL + +M UI/Contacts/English.lproj/Localizable.strings +M UI/Contacts/GNUmakefile +A UI/Contacts/UIxContactFolderProperties.h +A UI/Contacts/UIxContactFolderProperties.m +M UI/Contacts/product.plist +M UI/Scheduler/English.lproj/Localizable.strings +A UI/Templates/ContactsUI/UIxContactFolderProperties.wox +M UI/Templates/SchedulerUI/UIxCalendarProperties.wox +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/UIxCalendarProperties.js +A UI/WebServerResources/UIxContactFolderProperties.css +A UI/WebServerResources/UIxContactFolderProperties.js + +commit 93bf98c3265904c80a307015b75d68f1f3d061a7 +Author: Ludovic Marcotte +Date: Thu May 15 14:45:32 2014 -0400 + + Fixed copyright + +M SoObjects/SOGo/SOGoPermissions.m + +commit c08c3e27d5153c52237f56f1c65d3b8e2dfd4eb7 +Author: Ludovic Marcotte +Date: Thu May 15 14:44:53 2014 -0400 + + Improved modseq code + +M SoObjects/Mailer/SOGoMailFolder.h +M SoObjects/Mailer/SOGoMailFolder.m + +commit c8860dc92bde227ba083b35cb85871a69af3ff33 +Author: Ludovic Marcotte +Date: Thu May 15 14:32:46 2014 -0400 + + Dropped old user-settings for ActiveSync + +M SoObjects/SOGo/SOGoUserSettings.h +M SoObjects/SOGo/SOGoUserSettings.m + +commit 1a1543a3806e98ccb09d119c7352911c358d495c +Author: Ludovic Marcotte +Date: Thu May 15 14:29:39 2014 -0400 + + Updated script to reflect new table names + +M Scripts/openchange_user_cleanup + +commit d2d5467317d3507f0b8dd03a45db555ef987d8f7 +Author: Ludovic Marcotte +Date: Wed May 14 10:51:16 2014 -0400 + + Removed unused fluff in the file. + +M SoObjects/SOGo/SOGoCacheGCSObject.m + +commit 18443de9bfff1b60682c9ba44e77942d53133462 +Author: Ludovic Marcotte +Date: Wed May 14 09:58:05 2014 -0400 + + Updated copyright info + +M SoObjects/Appointments/SOGoAppointmentFolder.m +M SoObjects/Appointments/SOGoAppointmentInboxFolder.m +M SoObjects/SOGo/WOResponse+SOGo.m +M UI/Scheduler/UIxComponentEditor.h + +commit be72456a9aa7d06c8a7151b1bc7a0fa4ea43fa57 +Author: Ludovic Marcotte +Date: Wed May 14 09:56:10 2014 -0400 + + Renamed folder cache creation method + +M OpenChange/MAPIStoreUserContext.m +M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h +M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m + +commit a54b3e42896c3b446342da2bb49e2fafb2346d81 +Author: Ludovic Marcotte +Date: Wed May 14 08:56:35 2014 -0400 + + Renamed the default table name from socfs_ to sogo_cache_folder_ + +M OpenChange/MAPIStoreUserContext.m + +commit 7a4feae8b8c51980427ef187317c90586018d63f +Author: Ludovic Marcotte +Date: Wed May 14 06:56:05 2014 -0400 + + Updated code to reflect cache regorg. + +M OpenChange/MAPIStoreDBBaseContext.m +M OpenChange/MAPIStoreDBFolder.m +M OpenChange/MAPIStoreFolder.h +M OpenChange/MAPIStoreFolder.m + +commit 99d49b9bd95dff92f9167d99037266d12490ca87 +Author: Ludovic Marcotte +Date: Tue May 13 21:14:57 2014 -0400 + + Updated code to use the category + +M OpenChange/GNUmakefile +M OpenChange/MAPIStoreFolder.m +M OpenChange/SOGoCacheGCSObject+MAPIStore.h +M OpenChange/SOGoCacheGCSObject+MAPIStore.m + +commit 5f9fb4e1c46ab8e90539b6ebe0f078fa4ba3e4a5 +Author: Ludovic Marcotte +Date: Tue May 13 21:06:59 2014 -0400 + + Big refactor for new caching mechanism. + +D OpenChange/BSONCodec.h +D OpenChange/BSONCodec.m +D OpenChange/EOBitmaskQualifier.h +D OpenChange/EOBitmaskQualifier.m +D OpenChange/EOQualifier+MAPI.h +D OpenChange/EOQualifier+MAPI.m +M OpenChange/GNUmakefile +M OpenChange/MAPIStoreDBBaseContext.m +M OpenChange/MAPIStoreDBFolder.m +M OpenChange/MAPIStoreFallbackContext.m +M OpenChange/MAPIStoreFolder.m +M OpenChange/MAPIStoreMailFolder.m +M OpenChange/MAPIStoreMailVolatileMessage.m +M OpenChange/MAPIStoreTable.m +M OpenChange/MAPIStoreUserContext.m +M OpenChange/NSObject+PropertyList.m +A OpenChange/SOGoCacheGCSObject+MAPIStore.h +A OpenChange/SOGoCacheGCSObject+MAPIStore.m +M OpenChange/SOGoMAPIDBMessage.h +M OpenChange/SOGoMAPIDBMessage.m +M OpenChange/dbmsgreader.m +A SoObjects/SOGo/BSONCodec.h +A SoObjects/SOGo/BSONCodec.m +A SoObjects/SOGo/EOBitmaskQualifier.h +A SoObjects/SOGo/EOBitmaskQualifier.m +A SoObjects/SOGo/EOQualifier+SOGoCacheObject.h +A SoObjects/SOGo/EOQualifier+SOGoCacheObject.m +M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h +M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m +M SoObjects/SOGo/GNUmakefile +M SoObjects/SOGo/SOGoCacheGCSFolder.h +M SoObjects/SOGo/SOGoCacheGCSFolder.m +M SoObjects/SOGo/SOGoCacheGCSObject.h +M SoObjects/SOGo/SOGoCacheGCSObject.m +M SoObjects/SOGo/SOGoCacheObject.h +M SoObjects/SOGo/SOGoCacheObject.m + +commit ef2188962a232de164b1da7cbf21a3193aba50e4 +Author: Ludovic Marcotte +Date: Tue May 13 19:58:19 2014 -0400 + + Renamed cache files for generalization. + + The upcoming commit will rename the content. + +D OpenChange/GCSSpecialQueries+OpenChange.h +D OpenChange/GCSSpecialQueries+OpenChange.m +D OpenChange/SOGoMAPIDBFolder.h +D OpenChange/SOGoMAPIDBFolder.m +D OpenChange/SOGoMAPIDBObject.h +D OpenChange/SOGoMAPIDBObject.m +D OpenChange/SOGoMAPIObject.h +D OpenChange/SOGoMAPIObject.m +A SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h +A SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m +A SoObjects/SOGo/SOGoCacheGCSFolder.h +A SoObjects/SOGo/SOGoCacheGCSFolder.m +A SoObjects/SOGo/SOGoCacheGCSObject.h +A SoObjects/SOGo/SOGoCacheGCSObject.m +A SoObjects/SOGo/SOGoCacheObject.h +A SoObjects/SOGo/SOGoCacheObject.m + +commit 2922f15ca7fbf2f9319b96efafd7e2c809ebc46f +Author: Ludovic Marcotte +Date: Tue May 13 18:03:37 2014 -0400 + + More rename + +M OpenChange/SOGoMAPIDBFolder.h +M OpenChange/SOGoMAPIDBFolder.m +M OpenChange/SOGoMAPIDBObject.h +M OpenChange/SOGoMAPIDBObject.m + +commit 41e6fb89d304edf9619641b766bc37ad402a954c +Author: Ludovic Marcotte +Date: Tue May 13 17:54:02 2014 -0400 + + Renamed constants in preparation for the big cache cleanup + +M OpenChange/MAPIStoreDBFolder.m +M OpenChange/MAPIStoreFolder.m +M OpenChange/MAPIStoreGCSFolder.m +M OpenChange/MAPIStoreMailFolder.m +M OpenChange/SOGoMAPIDBFolder.m +M OpenChange/SOGoMAPIDBObject.h +M OpenChange/SOGoMAPIDBObject.m + +commit c4a38436955e8ff77021a6cd124cbf596a7750a0 +Author: Ludovic Marcotte +Date: Tue May 13 13:52:20 2014 -0400 + + Removed unused configuration parameters + +M OpenChange/SOGoMAPIDBObject.m + +commit bf67b6435fd1376132ad8bef2eefa34736f858ff +Author: Alexandre Cloutier +Date: Tue May 13 13:21:18 2014 -0400 + + applies comments + +M UI/Scheduler/UIxCalListingActions.m + +commit b5319446dbb8a60c2fc8fa06d21e2c9298c07252 +Author: Alexandre Cloutier +Date: Tue May 13 10:36:26 2014 -0400 + + display and printing modification + +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit d58184af1533cffa34b00e903dea79f709e0d693 +Author: Francis Lachapelle +Date: Tue May 13 10:15:02 2014 -0400 + + Fix synchronization of seen/unseen status of msgs + +M NEWS +M UI/WebServerResources/MailerUI.js + +commit fbc6856fd57dca21f7dcdc748b452352875e064e +Author: Francis Lachapelle +Date: Mon May 12 16:06:49 2014 -0400 + + Improve contextual menu in events list + +M UI/Templates/SchedulerUI/UIxCalMainView.wox + +commit 5c41915080eadc24ac524454b56480febcd5a907 +Author: Francis Lachapelle +Date: Mon May 12 15:25:50 2014 -0400 + + Fix focus of popup windows with FF on Windows + +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/generic.js + +commit 698524b39f7893d4ac40af42a699cbfd885439c5 +Author: Francis Lachapelle +Date: Fri May 9 17:28:02 2014 -0400 + + Fix synchronization of seen/unseen status of msgs + + Fixes #2715 + +M UI/WebServerResources/MailerUI.js + +commit 1f5a2a4c6221e0d06444eeed624a59373b627dba +Author: Ludovic Marcotte +Date: Fri May 9 15:40:57 2014 -0400 + + Fixed IMAP copy/move op between subfolder in different accounts + +M NEWS +M SoObjects/Mailer/SOGoMailFolder.m + +commit fc3cfa548453f3620dfbd7bac3a28a6d878116d1 +Author: Alexandre Cloutier +Date: Thu May 8 19:14:39 2014 -0400 + + applied comments + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +M UI/Scheduler/UIxCalViewPrint.m +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/SchedulerUI.css +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 593b44ed01212d5ffb12dc513b9c1f5be8f4aca9 +Author: Alexandre Cloutier +Date: Thu May 8 17:53:20 2014 -0400 + + forgotten code + +M SoObjects/Appointments/SOGoAppointmentFolder.m + +commit b2ed7e3f6baf79e1aaa580375b050fa2870697bd +Author: Alexandre Cloutier +Date: Thu May 8 17:17:05 2014 -0400 + + search capabilites + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/UIxCalFilterPanel.m +M UI/Scheduler/UIxCalListingActions.h +M UI/Scheduler/UIxCalListingActions.m +M UI/Scheduler/UIxCalMainView.m +M UI/Templates/SchedulerUI/UIxCalFilterPanel.wox +M UI/Templates/SchedulerUI/UIxCalMainView.wox +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/generic.css + +commit bc2b41f38336462fdffc1970e0bc7d6004711f3a +Author: Ludovic Marcotte +Date: Thu May 8 09:13:37 2014 -0400 + + Fixed unit test. + +M Tests/Integration/test-webdavsync.py + +commit 7d7961ffcfdf9750dfdadd4fa81efc8df2992e01 +Author: Ludovic Marcotte +Date: Fri May 2 13:35:21 2014 -0400 + + Fix for bug #2492 + +M NEWS +M SoObjects/SOGo/SOGoGCSFolder.m + +commit 0b9afec635c6d1c86f5c92fa174f65cb5ec29ef0 +Author: Francis Lachapelle +Date: Thu May 1 14:07:25 2014 -0400 + + Fix saving preferences with no Sieve requirements + + Fixes #2746 + +M NEWS +M UI/PreferencesUI/UIxPreferences.m + +commit 4e42a6a689f3386710eb4917092b84d2873f2b2a +Author: Francis Lachapelle +Date: Wed Apr 30 12:35:25 2014 -0400 + + Don't limit Sieve script to 8KB + + Fixes #2745 + +M NEWS +M SoObjects/SOGo/SOGoSieveManager.m + +commit da8febf138c20632285219cd615f153a33d7cb9e +Author: Alexandre Cloutier +Date: Tue Apr 29 15:42:50 2014 -0400 + + Printing display appear on one page only, fix issues with browsers + +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/UIxCalViewPrint.css + +commit 8c563c2922a1f6acc996c4260ba0509282c50a20 +Author: Francis Lachapelle +Date: Tue Apr 29 14:25:53 2014 -0400 + + Fix possible exception when retrieving reminder + + Fixes #2744 + +M UI/Scheduler/UIxComponentEditor.m + +commit fa5cf55367c8bdb83355625c7d579c3e846704bd +Author: Alexandre Cloutier +Date: Mon Apr 28 15:42:17 2014 -0400 + + Update GNUmakefile + +M UI/Scheduler/GNUmakefile + +commit e43b704858484f03c4b82d7db6e1253e944d1bae +Author: Alexandre Cloutier +Date: Mon Apr 28 15:32:47 2014 -0400 + + Update UIxCalViewPrint.js + +M UI/WebServerResources/UIxCalViewPrint.js + +commit 44a4baca7f4e05a6e2f1a2035b62200cbcdb4315 +Author: Alexandre Cloutier +Date: Mon Apr 28 15:30:32 2014 -0400 + + fix the comments in the headers + +M UI/Scheduler/UIxCalViewPrint.h +M UI/Scheduler/UIxCalViewPrint.m +M UI/WebServerResources/UIxCalViewPrint.js + +commit 78447d81ae1cb0fb0b2c39245754a93805c37faa +Author: Alexandre Cloutier +Date: Mon Apr 28 13:54:29 2014 -0400 + + Code refactoring and retouch + +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 9dc07fccc0732107621297cec2eadb9d2673e049 +Author: Alexandre Cloutier +Date: Fri Apr 25 18:04:24 2014 -0400 + + It is now possible to print the calendar as a list, dayView or weekView + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/UIxCalViewPrint.m +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit ba7b65cf7bd15425ee9eab1e3e94e52ba54d4df9 +Author: Ludovic Marcotte +Date: Thu Apr 24 07:59:07 2014 -0400 + + Fix for bug #2733 + +M ActiveSync/NGVCard+ActiveSync.m + +commit 66bfbeaac1cbed4e1e06fa727273fcfd0d5c36d0 +Author: Francis Lachapelle +Date: Wed Apr 23 10:55:04 2014 -0400 + + MailEditor: fix text conversion with HTML entities + +M SoObjects/Mailer/NSString+Mail.m + +commit 619cb2c0ec310514c80e976dcb2cf89c25822f46 +Author: Ludovic Marcotte +Date: Wed Apr 23 09:22:20 2014 -0400 + + Fix for bug #2721 + +M NEWS +M UI/MailPartViewers/UIxMailPartHTMLViewer.m +M UI/WebServerResources/MailerUI.js + +commit f9869d19b478c82d86a423dd3b3948adcdc19d5e +Author: Alexandre Cloutier +Date: Tue Apr 22 17:47:29 2014 -0400 + + Finished the view, need to display the eventsblock + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/UIxCalViewPrint.m +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 57b747c24ada3ceb1f2b4f1ac8ec551014f8ffc1 +Author: Alexandre Cloutier +Date: Thu Apr 17 17:33:30 2014 -0400 + + Events list is done. + +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/UIxCalViewPrint.h +M UI/Scheduler/UIxCalViewPrint.m +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/UIxCalViewPrint.css +M UI/WebServerResources/UIxCalViewPrint.js + +commit 8f6b0f80a794743d83c0cc129266dc6b2492528f +Author: Alexandre Cloutier +Date: Mon Apr 14 10:29:23 2014 -0400 + + Creation of the first version of the printing interface + +M UI/Scheduler/GNUmakefile +M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +A UI/Scheduler/UIxCalViewPrint.h +A UI/Scheduler/UIxCalViewPrint.m +M UI/Scheduler/product.plist +M UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/WebServerResources/SchedulerUI.js +A UI/WebServerResources/UIxCalViewPrint.css +A UI/WebServerResources/UIxCalViewPrint.js + +commit cb5e6e7ef26f3b400968a3f1b9738b1e5fd285ed +Author: Alexandre Cloutier +Date: Thu Apr 3 16:47:02 2014 -0400 + + adding button print calendar toolbar, adding template for the print window, ajusting indentation + +M UI/Common/UIxToolbar.m +M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +A UI/Templates/SchedulerUI/UIxCalViewPrint.wox +M UI/Templates/UIxPageFrame.wox +M UI/Templates/UIxToolbar.wox + +commit 49363cfe364ccf5add432f6375895ee283149dab +Author: Francis Lachapelle +Date: Fri Apr 11 21:37:55 2014 -0400 + + Update datepicker to latest version + + From https://github.com/eternicode/bootstrap-datepicker/ + +M NEWS +M UI/WebServerResources/UIxAppointmentEditor.js +M UI/WebServerResources/UIxContactEditor.js +M UI/WebServerResources/UIxTaskEditor.js +M UI/WebServerResources/datepicker.css +M UI/WebServerResources/datepicker.js + +commit c345fc2b8d2effc7cb73e605b054041fe93adeea +Author: Francis Lachapelle +Date: Thu Apr 10 12:25:42 2014 -0400 + + Fix display of category of events + + Fixes #2703 + +M NEWS +M UI/WebServerResources/SchedulerUI.js + +commit d200629b0175b3b710bf15aad35b4f9826ab757f +Author: Francis Lachapelle +Date: Thu Apr 10 10:05:28 2014 -0400 + + Fix CSS in contact editor and preferences module + +M UI/Templates/ContactsUI/UIxContactEditor.wox +M UI/WebServerResources/UIxContactEditor.css +M UI/WebServerResources/UIxPreferences.css + +commit cfc1ac6b3baa1d1c0753104ec7a05184209a65a6 +Author: Francis Lachapelle +Date: Thu Apr 10 10:01:58 2014 -0400 + + Add missing localizable string + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit e66ceaad0770c8487fafef96566cb263df970f55 +Author: Ludovic Marcotte +Date: Thu Apr 10 09:40:25 2014 -0400 + + Fixed gcc warnings and added some rationale around yesterday's fix + +M ActiveSync/NGDOMElement+ActiveSync.m + +commit f370ac114a69281ab4a61927fc2781122e22998e +Author: Ludovic Marcotte +Date: Thu Apr 10 09:24:43 2014 -0400 + + Now include message/rfc822 parts as attachments when forwarding mails + +M SoObjects/Mailer/SOGoMailObject.m + +commit 75ee6b48062864c9d907bf41996a7b8be3668387 +Author: Ludovic Marcotte +Date: Thu Apr 10 08:44:44 2014 -0400 + + Fixed once more true/false errors + +M UI/PreferencesUI/UIxPreferences.m + +commit 0961fbbab36a6506a736929586d381ef6827adc6 +Author: Ludovic Marcotte +Date: Thu Apr 10 06:59:18 2014 -0400 + + Properly escape attendee names in case they contain HTML entities + +M ActiveSync/iCalEvent+ActiveSync.m + +commit 0d1c39ca71a237d0fe065ede471842b9ea5132e5 +Author: Ludovic Marcotte +Date: Wed Apr 9 20:12:19 2014 -0400 + + Added new configuration parameter to limit the window size + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M Documentation/SOGo Installation Guide.odt +M NEWS +M SoObjects/SOGo/SOGoSystemDefaults.h +M SoObjects/SOGo/SOGoSystemDefaults.m + +commit 5739356f8884c005c5152d730807ecd1275d2426 +Author: Ludovic Marcotte +Date: Wed Apr 9 19:27:38 2014 -0400 + + Fix tests to handle list-component + +M Tests/Unit/TestVersit.m + +commit 33467093c14074d1399dd4af0d1c633a24cfd609 +Author: Ludovic Marcotte +Date: Wed Apr 9 18:03:33 2014 -0400 + + Fix for #2695 + +M ActiveSync/NGDOMElement+ActiveSync.m +M NEWS + +commit 96ece873caad0b735147637435545d0721b06368 +Author: Ludovic Marcotte +Date: Wed Apr 9 14:49:31 2014 -0400 + + Fix for bug #2654 + +M ActiveSync/iCalRecurrenceRule+ActiveSync.m +M NEWS + +commit 8909775cf653ec3edef69ff02102947acf866e30 +Author: Ludovic Marcotte +Date: Wed Apr 9 12:19:23 2014 -0400 + + Fix for #2680 + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS + +commit 0ba59de88e48824f5ae8160e134429246d6a8049 +Author: Ludovic Marcotte +Date: Wed Apr 9 11:09:13 2014 -0400 + + Fixed formatting/warnings in previous commit. + +M UI/PreferencesUI/UIxPreferences.m + +commit ab80e87a3fcd86a7b211bbae5e8033d5a6c39d4a +Author: Ludovic Marcotte +Date: Wed Apr 9 10:57:56 2014 -0400 + + Applied patches from #2700 + +M ActiveSync/SOGoActiveSyncDispatcher.m +M NEWS +M SoObjects/Mailer/SOGoMailFolder.m + +commit 068eb921aa473239b48115ef0abaac523ad28854 +Author: Alexandre Cloutier +Date: Wed Apr 9 10:48:47 2014 -0400 + + rebase and fix comments made by extrafu + +M SoObjects/SOGo/SOGoSieveManager.m +M UI/PreferencesUI/UIxPreferences.m + +commit 371e5ead0821d409dd65a3c47e15963568394dc6 +Author: Ludovic Marcotte +Date: Wed Apr 9 10:34:48 2014 -0400 + + Updated the doc in prepration for the release + +M Documentation/SOGo Installation Guide.odt +M NEWS + +commit c5e6ccf3323cc6b3945e7f5da130f5bc35994065 +Author: Alexandre Cloutier +Date: Fri Apr 4 15:04:37 2014 -0400 + + Fix function sieveClient and the way the iVar Client is handled. Change behaviour of saveMailAccounts()so sogo wont crash if the user tries to resave his preferences after a connection error. + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit ec9d48c3a9160fbb4729c032d1f64fa83d5d0124 +Author: Alexandre Cloutier +Date: Mon Mar 10 10:53:44 2014 -0400 + + Change the function name getClient to sieveClient + +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m + +commit 877082f0428eed910fd95637e97d273bbbc83d86 +Author: Alexandre Cloutier +Date: Mon Mar 10 10:33:27 2014 -0400 + + Added missing functions getClient and IsSieveServerConnected and the changes that comes with it + +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m + +commit 342b68fd4405be1894af594f5f0b4dd4d5b8ae48 +Author: Alexandre Cloutier +Date: Thu Mar 6 15:20:41 2014 -0500 + + Erase the merging conflicts writings + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit e24c22e3dc61b7550343b5fb17657d39d7a7bc83 +Author: Alexandre Cloutier +Date: Fri Feb 28 16:39:27 2014 -0500 + + BugFix #1046; Whenever the sieve server is unavailable an error message will appear. + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit 7997448d416c12284e11a0038ae1ceed00a7cea2 +Author: Ludovic Marcotte +Date: Wed Apr 9 06:33:02 2014 -0400 + + Fixed issus in previous commits + +M SoObjects/Mailer/SOGoMailLabel.h +M UI/PreferencesUI/UIxPreferences.m + +commit 3629670fcaa53fa71203631214397008188301ba +Author: Ludovic Marcotte +Date: Tue Apr 8 15:35:36 2014 -0400 + + Fix for #2489 + +M NEWS +M SoObjects/Appointments/product.plist + +commit c250b9a6d44112c2441b9b7573ea40b885caba64 +Author: Alexandre Cloutier +Date: Tue Apr 8 15:32:13 2014 -0400 + + Fix deleting calendar + + Added a "/" inside the function: deletePersonalCalendarConfirm(). + +M UI/WebServerResources/SchedulerUI.js + +commit d9b99f536c9d85807df1682463a9d03d7a0356a3 +Author: Alexandre Cloutier +Date: Tue Apr 8 12:48:59 2014 -0400 + + Erased strings no longer used + +M UI/Contacts/English.lproj/Localizable.strings + +commit 5699e5e1b405fc6cfa8e8f9e580bdc80973dd8e7 +Author: Alexandre Cloutier +Date: Tue Apr 8 12:12:30 2014 -0400 + + Add datepicker for contact birthday + +M .gitignore +M UI/Templates/ContactsUI/UIxContactEditor.wox +M UI/WebServerResources/UIxContactEditor.css +M UI/WebServerResources/UIxContactEditor.js +M UI/WebServerResources/datepicker.js +M UI/WebServerResources/generic.js + +commit e869406081da91336344f25e2f7beb6bf02e7d25 +Author: Francis Lachapelle +Date: Tue Apr 8 11:51:47 2014 -0400 + + Minor CSS improvement in mail headers + +M UI/WebServerResources/MailerUI.css + +commit 3e221253adbd793447a12c23f04be33fe9d17315 +Author: Ludovic Marcotte +Date: Tue Apr 8 11:19:53 2014 -0400 + + Fix for feature #1496 + +M SoObjects/Appointments/SOGoAppointmentFolders.m +M SoObjects/SOGo/SOGoParentFolder.h + +commit 446ef3467a579b53f9b7469f9eb2c465293c00d7 +Author: Alexandre Cloutier +Date: Tue Apr 8 09:14:00 2014 -0400 + + Fix typo + +M UI/PreferencesUI/French.lproj/Localizable.strings + +commit c9cbcf256691491fd78dad77b9601df36a3ecb25 +Author: Alexandre Cloutier +Date: Mon Apr 7 16:40:44 2014 -0400 + + applied Morgan's law + +M SoObjects/Mailer/SOGoDraftObject.m + +commit 534e4e3d4dff7476598590a98ef2ca5e3e232385 +Author: Francis Lachapelle +Date: Mon Apr 7 11:20:19 2014 -0400 + + Fix text nodes of message with HTML entitites + +M NEWS +M SoObjects/Mailer/NSString+Mail.m + +commit 09bcd2aee27b3ab2fa80c5b21b50b2fa3c9b655e +Author: Alexandre Cloutier +Date: Mon Apr 7 10:59:38 2014 -0400 + + fix code + +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/SOGo/SOGoParentFolder.m +M UI/PreferencesUI/UIxPreferences.m + +commit c80e2222100fa8859a898079fe8da248cca4016e +Author: Jeroen Dekkers +Date: Sun Apr 6 22:44:50 2014 +0200 + + Delete generated source files when running "make clean". + +M OpenChange/GNUmakefile.preamble +M SoObjects/SOGo/GNUmakefile + +commit 6e60bbb553ca0a109408d35a5bec760189f67a67 +Author: Francis Lachapelle +Date: Fri Apr 4 16:47:46 2014 -0400 + + Fix saved HTML content of draft uploading a file + +M NEWS +M UI/WebServerResources/UIxMailEditor.js + +commit 846212336aead0959d1e9b34d9b2f157f9e0ac94 +Author: Ludovic Marcotte +Date: Fri Apr 4 16:53:58 2014 -0400 + + Make use of a local pool to avoid over-memory usage + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 349f4d824a297f41f2d04087bcff49b49f70cd60 +Author: Alexandre Cloutier +Date: Fri Apr 4 15:04:37 2014 -0400 + + Fix function sieveClient and the way the iVar Client is handled. Change behaviour of saveMailAccounts()so sogo wont crash if the user tries to resave his preferences after a connection error. + +M .gitignore +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit 2f565d2ec99286316bbe988fc2ad48a8fa1ceef6 +Author: Alexandre Cloutier +Date: Fri Apr 4 14:56:14 2014 -0400 + + fix variable name for UIxFilterEditor.js + + FolderEncoding to sieveFolderEncoding + +M UI/WebServerResources/UIxFilterEditor.js + +commit 2ff4498983b99ef11371eb76491b276c7074067a +Author: Ludovic Marcotte +Date: Fri Apr 4 08:44:50 2014 -0400 + + Fixed previous commit + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit 3eec753b7394d222ff0dcf782695ce10d354b978 +Author: Ludovic Marcotte +Date: Fri Apr 4 08:39:13 2014 -0400 + + Slightly improved previous pull request merge. + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit f999c87d4d737ed69512bc4150daa85c361f3f47 +Author: Alexandre Cloutier +Date: Thu Apr 3 17:09:00 2014 -0400 + + added traduction in localstring french, added some specification in function sogoVersion + +M .gitignore +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m + +commit 378f2b58767aefcf0456d288246e673f850e9327 +Author: Francis Lachapelle +Date: Thu Apr 3 10:05:43 2014 -0400 + + Preparation for release 2.2.3 + +M Documentation/SOGo Installation Guide.odt +M Documentation/SOGo Mobile Devices Configuration.odt +M Documentation/SOGo Mozilla Thunderbird Configuration.odt +M Documentation/SOGo Native Microsoft Outlook Configuration.odt +M Version + +commit d091b3d7cd46134719a76d65be9eef3d7bfe596a +Author: Francis Lachapelle +Date: Thu Apr 3 10:01:40 2014 -0400 + + Update ChangeLog + +M ChangeLog + commit 370e1e6d266a84eb089ccf33b14e0902ecee7fff Author: Francis Lachapelle Date: Thu Apr 3 10:01:01 2014 -0400 @@ -147,6 +1525,33 @@ M SoObjects/Mailer/SOGoMailBaseObject.m M SoObjects/Mailer/SOGoMailObject.m M UI/WebServerResources/MailerUI.js +commit 03d5975be357b6cce4d74e90d4fb1069b9ff92ae +Author: Alexandre Cloutier +Date: Fri Mar 28 10:28:33 2014 -0400 + + Changed strings in french and change the instanciation of dictionary(addressBooksIDWithDisplayName) to release it manually + +M .gitignore +M SoObjects/Contacts/French.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m + +commit 1da1764aaec713a44a1e633db7e156cdb5ae5db1 +Author: Alexandre Cloutier +Date: Thu Mar 27 15:27:48 2014 -0400 + + The collected address book will be created only if the user select it in the preferences. Fix labels. + +M SoObjects/Contacts/French.lproj/Localizable.strings +M SoObjects/Contacts/SOGoContactFolders.h +M SoObjects/Contacts/SOGoContactFolders.m +M SoObjects/SOGo/SOGoParentFolder.h +M SoObjects/SOGo/SOGoParentFolder.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m + commit 6e4f776d4b7ede6acc9c54f4f1799858085b6b4c Author: Francis Lachapelle Date: Thu Mar 27 12:06:24 2014 -0400 @@ -158,6 +1563,58 @@ Date: Thu Mar 27 12:06:24 2014 -0400 M NEWS M UI/WebServerResources/UIxFilterEditor.js +commit 365e271a8458f26bf5101dddbfdfc3d28baf37fd +Author: Alexandre Cloutier +Date: Tue Mar 25 15:44:28 2014 -0400 + + Correct typo + +M UI/PreferencesUI/English.lproj/Localizable.strings + +commit bce701208a2347fdf6c660b9ade8998927f3df48 +Author: Alexandre Cloutier +Date: Tue Mar 25 14:53:42 2014 -0400 + + Added the modifications specified by extrafu + +M SoObjects/Contacts/SOGoContactFolders.h +M SoObjects/Contacts/SOGoContactFolders.m +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/SOGo/SOGoConstants.h +M SoObjects/SOGo/SOGoParentFolder.h +M SoObjects/SOGo/SOGoParentFolder.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m + +commit b16911edc1f36956c94800cfbfa6900af7812657 +Author: Alexandre Cloutier +Date: Mon Mar 24 13:55:52 2014 -0400 + + Modified the displayed name of SOGo version and added the label in the .lproj + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit 76307cfaafd886f053b4f5f6436f1412f7148c83 +Author: Alexandre Cloutier +Date: Mon Mar 24 13:41:57 2014 -0400 + + New feature : 1496; Unknown outgoing email addresses can now be automatically be added to your address books. + +M SoObjects/Contacts/English.lproj/Localizable.strings +M SoObjects/Contacts/SOGoContactFolders.h +M SoObjects/Contacts/SOGoContactFolders.m +M SoObjects/Mailer/SOGoDraftObject.h +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/SOGo/SOGoDefaults.plist +M SoObjects/SOGo/SOGoParentFolder.m +M SoObjects/SOGo/SOGoUserDefaults.h +M SoObjects/SOGo/SOGoUserDefaults.m +M UI/Contacts/UIxContactFoldersView.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox + commit e5fb7dc7b8685102203a4071a5d381aa56382dc8 Author: Francis Lachapelle Date: Mon Mar 24 12:43:34 2014 -0400 @@ -570,6 +2027,14 @@ Date: Tue Mar 11 13:21:05 2014 -0400 M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +commit d108777e89593ac4b6e62b12a78c6f6df92e18d7 +Author: Alexandre Cloutier +Date: Tue Mar 11 10:25:01 2014 -0400 + + Display the version at the top of the general tap + +M UI/Templates/PreferencesUI/UIxPreferences.wox + commit af68f101152928295156cbfe497271b914f45952 Author: Daniel B. Date: Tue Mar 11 09:23:13 2014 +0100 @@ -592,6 +2057,61 @@ Date: Mon Mar 10 14:47:01 2014 -0400 M NEWS M UI/PreferencesUI/UIxPreferences.m +commit 8cc352fb28013801c56cc5138dba97f12de0ff6c +Author: Alexandre Cloutier +Date: Mon Mar 10 14:45:10 2014 -0400 + + It is now possible to see the version on the general page of the preferences window + +M UI/PreferencesUI/UIxPreferences.m +M UI/Templates/PreferencesUI/UIxPreferences.wox + +commit 0d69a476bcf9b4f95d88a5ae0ecf30c8602c2a66 +Author: Alexandre Cloutier +Date: Mon Mar 10 12:16:12 2014 -0400 + + add specific date for limiting the user to today's for his contact birthday + +M UI/WebServerResources/UIxContactEditor.js + +commit cee1e529f82e35c8f715657ccf6cf168294f8b72 +Author: Alexandre Cloutier +Date: Mon Mar 10 12:05:01 2014 -0400 + + BugFix#1636; fix the issue where the user could add anything for the birthday of his contacts + +M UI/Contacts/English.lproj/Localizable.strings +M UI/WebServerResources/UIxContactEditor.js + +commit 3a681bd5a5a856a2230e730945c3ed2df076ff0a +Author: Alexandre Cloutier +Date: Mon Mar 10 10:53:44 2014 -0400 + + Change the function name getClient to sieveClient + +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m + +commit f5faa608145ccb70ed89b0d29e9b8adad17d2f0b +Author: Alexandre Cloutier +Date: Mon Mar 10 10:33:27 2014 -0400 + + Added missing functions getClient and IsSieveServerConnected and the changes that comes with it + +M UI/PreferencesUI/UIxPreferences.h +M UI/PreferencesUI/UIxPreferences.m + +commit f8939bfb3efbb92957f237f998ff9f5fd5e72541 +Author: Alexandre Cloutier +Date: Mon Mar 10 10:07:47 2014 -0400 + + Change the variable name for sieveFolderEncoding instead of folderEncoding + +M SoObjects/SOGo/SOGoDefaults.plist +M SoObjects/SOGo/SOGoSystemDefaults.h +M SoObjects/SOGo/SOGoSystemDefaults.m +M UI/Templates/PreferencesUI/UIxFilterEditor.wox + commit 78636f039664e75932bed65210bfd87939fd6e4c Author: Ludovic Marcotte Date: Sun Mar 9 15:58:11 2014 -0400 @@ -674,6 +2194,58 @@ M NEWS M UI/WebServerResources/MailerUI.js M UI/WebServerResources/generic.js +commit f4d189ffc893de10d397249dd15427a1de961852 +Author: Alexandre Cloutier +Date: Thu Mar 6 15:20:41 2014 -0500 + + Erase the merging conflicts writings + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit 1b9ac03e947e5c85cbcfdb1d252dffd43545f536 +Author: Alexandre Cloutier +Date: Fri Feb 28 16:39:27 2014 -0500 + + BugFix #1046; Whenever the sieve server is unavailable an error message will appear. + +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxPreferences.js + +commit 6ad87052baffea79fb80af09ed25540b8bd9cb53 +Author: Alexandre Cloutier +Date: Tue Mar 4 13:23:55 2014 -0500 + + bugFix #0002616 : change the behavior of ApplicationBaseURL. Now the object return a standard path(without a slash at the end of the path) That means every string added the URL ApplicationBaseURL must start with a Slash. + +M .gitignore +M UI/SOGoUI/UIxComponent.m +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/SOGoDragHandles.js +M UI/WebServerResources/SOGoResizableTable.js +M UI/WebServerResources/SOGoRootPage.js +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/UIxComponentEditor.js +M UI/WebServerResources/UIxFilterEditor.js +M UI/WebServerResources/UIxPreferences.js +M UI/WebServerResources/generic.js + +commit e7a16cba0d00a35a14de334385dfb7f174f5438a +Author: Alexandre Cloutier +Date: Wed Mar 5 12:31:27 2014 -0500 + + BugFix #0002622: The server Dovecot use by default an UTF-8 and by doing so it cause a wrong re-encoding in UTF-7 when reaching the sieve server (knowing that the foldername in sieve are encoded in UTF-7). Since it is not the case with a Cyrus server, I created a new variable available in the sogo.conf that let you specified the type of encoding you need. By default the encoding is set to UTF-7. ex : SOGoFolderEncoding = 'UTF-7' + +M SoObjects/SOGo/SOGoDefaults.plist +M SoObjects/SOGo/SOGoSystemDefaults.h +M SoObjects/SOGo/SOGoSystemDefaults.m +M UI/PreferencesUI/UIxFilterEditor.m +M UI/Templates/PreferencesUI/UIxFilterEditor.wox +M UI/WebServerResources/UIxFilterEditor.js + commit 31ace947cbf1fbbfa93a92ed0f028e5a56b8b0d1 Author: Ludovic Marcotte Date: Thu Mar 6 14:16:08 2014 -0500 diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index fff970fae..868371ac1 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 80006f295..efcfeacb7 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 dd347b2d0..8d8cec8f3 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 2ff80d636..5f2502ed7 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,48 @@ +2.2.4 (2014-05-29) +------------------ + +New features + - new print option in Calendar module + - now able to save unknown recipient emails to address book on send (#1496) + +Enhancements + - Sieve folder encoding is now configurable (#2622) + - SOGo version is now displayed in preferences window (#2612) + - report Sieve error when saving preferences (#1046) + - added the SOGoMaximumSyncWindowSize system default to overwrite the + maximum number of items returned during an ActiveSync sync operation + - updated datepicker + - addressbooks properties are now accessible from a popup window + - extended events and tasks searches + - updated Czech, French, Hungarian, Polish, Russian, Slovak, Spanish (Argentina), and Spanish (Spain) translations + - added more sycned contact properties when using ActiveSync (#2775) + - now possible to configure the default subscribed resource name using SOGoSubscriptionFolderFormat + - now handle server-side folder updates using ActiveSync (#2688) + - updated CKEditor to version 4.4.1 + +Bug fixes + - fixed saved HTML content of draft when attaching a file + - fixed text nodes of HTML content handler by encoding HTML entities + - fixed iCal7 delegation issue with the "inbox" folder (#2489) + - fixed birth date validity checks (#1636) + - fixed URL handling (#2616) + - improved folder rename operations using ActiveSync (#2700) + - fixed SmartReply/Forward when ReplaceMime was omitted (#2680) + - fixed wrong generation of weekly repetitive events with ActiveSync (#2654) + - fixed incorrect XML data conversion with ActiveSync (#2695) + - fixed display of events having a category with HTML entities (#2703) + - fixed display of images in CSS background (#2437) + - fixed limitation of Sieve script size (#2745) + - fixed sync-token generation when no change was returned (#2492) + - fixed the IMAP copy/move operation between subfolders in different accounts + - fixed synchronization of seen/unseen status of msgs in Webmail (#2715) + - fixed focus of popup windows open through a contextual menu with Firefox on Windows 7 + - fixed missing characters in shared folder names over ActiveSync (#2709) + - fixed reply and forward mail templates for Brazilian Portuguese (#2738) + - fixed newline in signature when forwarding a message as attachment in HTML mode (#2787) + - fixed restoration of options (priority & return receipt) when editing a draft (#193) + - fixed update of participation status via CalDAV (#2786) + 2.2.3 (2014-04-03) ------------------ diff --git a/OpenChange/GNUmakefile b/OpenChange/GNUmakefile index 3d9fa5b9f..2a7bac557 100644 --- a/OpenChange/GNUmakefile +++ b/OpenChange/GNUmakefile @@ -46,11 +46,8 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreSamDBUtils.m \ MAPIStoreUserContext.m \ \ - SOGoMAPIObject.m \ - \ - SOGoMAPIDBObject.m \ SOGoMAPIDBMessage.m \ - SOGoMAPIDBFolder.m \ + SOGoCacheGCSObject+MAPIStore.m \ \ MAPIStoreAppointmentWrapper.m \ MAPIStoreAttachment.m \ @@ -123,13 +120,6 @@ $(SOGOBACKEND)_OBJC_FILES += \ iCalEvent+MAPIStore.m \ iCalTimeZone+MAPIStore.m \ \ - GCSSpecialQueries+OpenChange.m\ - \ - EOQualifier+MAPI.m \ - \ - EOBitmaskQualifier.m \ - \ - BSONCodec.m \ RTFHandler.m diff --git a/OpenChange/GNUmakefile.preamble b/OpenChange/GNUmakefile.preamble index 27b4b5692..7dba896b6 100644 --- a/OpenChange/GNUmakefile.preamble +++ b/OpenChange/GNUmakefile.preamble @@ -3,3 +3,6 @@ all:: MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h: gen-property-selectors.py code-MAPIStorePropertySelectors.m code-MAPIStorePropertySelectors.h @echo " Auto-generating MAPIStorePropertySelectors.[hm]..." @$(PYTHON) ./gen-property-selectors.py -o MAPIStorePropertySelectors $(LIBMAPISTORE_CFLAGS) + +distclean clean:: + -rm -f MAPIStorePropertySelectors.m MAPIStorePropertySelectors.h diff --git a/OpenChange/MAPIStoreDBBaseContext.m b/OpenChange/MAPIStoreDBBaseContext.m index d82538083..22a15bc85 100644 --- a/OpenChange/MAPIStoreDBBaseContext.m +++ b/OpenChange/MAPIStoreDBBaseContext.m @@ -1,8 +1,6 @@ /* MAPIStoreDBBaseContext.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -32,7 +30,7 @@ #import "MAPIStoreDBFolder.h" #import "MAPIStoreMapping.h" #import "MAPIStoreUserContext.h" -#import "SOGoMAPIDBFolder.h" +#import #import "MAPIStoreDBBaseContext.h" @@ -60,7 +58,7 @@ static Class MAPIStoreDBFolderK; - (void) ensureContextFolder { - SOGoMAPIDBFolder *currentFolder; + SOGoCacheGCSFolder *currentFolder; NSArray *parts; NSMutableArray *folders; NSString *folderName; @@ -78,7 +76,7 @@ static Class MAPIStoreDBFolderK; folderName = [parts objectAtIndex: count]; if ([folderName length] > 0) { - currentFolder = [SOGoMAPIDBFolder objectWithName: folderName + currentFolder = [SOGoCacheGCSFolder objectWithName: folderName inContainer: currentFolder]; [folders addObject: currentFolder]; } @@ -98,11 +96,11 @@ static Class MAPIStoreDBFolderK; - (id) rootSOGoFolder { - SOGoMAPIDBFolder *folder; + SOGoCacheGCSFolder *folder; [userContext ensureFolderTableExists]; - folder = [SOGoMAPIDBFolder objectWithName: [isa MAPIModuleName] + folder = [SOGoCacheGCSFolder objectWithName: [isa MAPIModuleName] inContainer: nil]; [folder setTableUrl: [userContext folderTableURL]]; // [folder reloadIfNeeded]; diff --git a/OpenChange/MAPIStoreDBFolder.m b/OpenChange/MAPIStoreDBFolder.m index 74bf12e2f..f9b2e128d 100644 --- a/OpenChange/MAPIStoreDBFolder.m +++ b/OpenChange/MAPIStoreDBFolder.m @@ -32,7 +32,7 @@ #import #import #import -#import "EOQualifier+MAPI.h" +#import #import "MAPIStoreContext.h" #import "MAPIStoreDBFolderTable.h" #import "MAPIStoreDBMessage.h" @@ -40,7 +40,7 @@ #import "MAPIStoreMapping.h" #import "MAPIStoreTypes.h" #import "MAPIStoreUserContext.h" -#import "SOGoMAPIDBFolder.h" +#import #import "SOGoMAPIDBMessage.h" #import "MAPIStoreDBFolder.h" @@ -49,7 +49,7 @@ #include #include -static Class EOKeyValueQualifierK, SOGoMAPIDBFolderK, MAPIStoreDBFolderK; +static Class EOKeyValueQualifierK, SOGoCacheGCSFolderK, MAPIStoreDBFolderK; static NSString *MAPIStoreRightReadItems = @"RightsReadItems"; static NSString *MAPIStoreRightCreateItems = @"RightsCreateItems"; @@ -66,7 +66,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; + (void) initialize { EOKeyValueQualifierK = [EOKeyValueQualifier class]; - SOGoMAPIDBFolderK = [SOGoMAPIDBFolder class]; + SOGoCacheGCSFolderK = [SOGoCacheGCSFolder class]; MAPIStoreDBFolderK = [MAPIStoreDBFolder class]; } @@ -92,7 +92,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; { enum mapistore_error rc; NSString *folderName, *nameInContainer; - SOGoMAPIDBFolder *newFolder; + SOGoCacheGCSFolder *newFolder; struct SPropValue *value; value = get_SPropValue_SRow (aRow, PidTagDisplayName); @@ -111,7 +111,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; { nameInContainer = [NSString stringWithFormat: @"0x%.16"PRIx64, (unsigned long long) newFID]; - newFolder = [SOGoMAPIDBFolderK objectWithName: nameInContainer + newFolder = [SOGoCacheGCSFolderK objectWithName: nameInContainer inContainer: sogoObject]; [newFolder reloadIfNeeded]; [[newFolder properties] setObject: folderName @@ -181,7 +181,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; [SOGoObject globallyUniqueObjectId]]; fsObject = [SOGoMAPIDBMessage objectWithName: newKey inContainer: sogoObject]; - [fsObject setObjectType: MAPIDBObjectTypeMessage]; + [fsObject setObjectType: MAPIMessageCacheObject]; [fsObject reloadIfNeeded]; newMessage = [MAPIStoreDBMessage mapiStoreObjectWithSOGoObject: fsObject inContainer: self]; @@ -198,7 +198,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; ownerUser = [[self userContext] sogoUser]; if ([[context activeUser] isEqual: ownerUser] || [self subscriberCanReadMessages]) - keys = [(SOGoMAPIDBFolder *) sogoObject childKeysOfType: MAPIDBObjectTypeMessage + keys = [(SOGoCacheGCSFolder *) sogoObject childKeysOfType: MAPIMessageCacheObject includeDeleted: NO matchingQualifier: qualifier andSortOrderings: sortOrderings]; @@ -211,7 +211,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; - (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { - return [dbFolder childKeysOfType: MAPIDBObjectTypeFolder + return [dbFolder childKeysOfType: MAPIFolderCacheObject includeDeleted: NO matchingQualifier: qualifier andSortOrderings: sortOrderings]; diff --git a/OpenChange/MAPIStoreFallbackContext.m b/OpenChange/MAPIStoreFallbackContext.m index ea6fa7ff3..1c9a0106b 100644 --- a/OpenChange/MAPIStoreFallbackContext.m +++ b/OpenChange/MAPIStoreFallbackContext.m @@ -1,8 +1,6 @@ /* MAPIStoreFallbackContext.m - this file is part of SOGo * - * Copyright (C) 2011-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-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 @@ -26,7 +24,7 @@ #import "MAPIStoreUserContext.h" #import "NSString+MAPIStore.h" -#import "SOGoMAPIDBFolder.h" +#import #import "MAPIStoreFallbackContext.h" @@ -51,7 +49,7 @@ inMemCtx: (TALLOC_CTX *) memCtx { struct mapistore_contexts_list *firstContext = NULL, *context; - SOGoMAPIDBFolder *root; + SOGoCacheGCSFolder *root; NSArray *names; NSUInteger count, max; NSString *baseURL, *url, *name; @@ -70,7 +68,7 @@ /* Maybe emsmdbp_provisioning should be fixed in order to only take the uri returned above to avoid deleting its entries... */ - root = [SOGoMAPIDBFolder objectWithName: [self MAPIModuleName] + root = [SOGoCacheGCSFolder objectWithName: [self MAPIModuleName] inContainer: nil]; [root setOwner: userName]; userContext = [MAPIStoreUserContext userContextWithUsername: userName diff --git a/OpenChange/MAPIStoreFolder.h b/OpenChange/MAPIStoreFolder.h index 860e19636..94a1cb634 100644 --- a/OpenChange/MAPIStoreFolder.h +++ b/OpenChange/MAPIStoreFolder.h @@ -1,8 +1,6 @@ /* MAPIStoreFolder.h - this file is part of SOGo * - * Copyright (C) 2011-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-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 @@ -38,7 +36,7 @@ @class MAPIStoreMessageTable; @class MAPIStorePermissionsTable; @class SOGoFolder; -@class SOGoMAPIDBFolder; +@class SOGoCacheGCSFolder; @class SOGoMAPIDBMessage; #import "MAPIStoreSOGoObject.h" @@ -50,7 +48,7 @@ // NSArray *faiMessageKeys; // NSArray *folderKeys; - SOGoMAPIDBFolder *dbFolder; + SOGoCacheGCSFolder *dbFolder; // SOGoMAPIDBFolder *faiFolder; // SOGoMAPIDBFolder *propsFolder; // SOGoMAPIDBMessage *propsMessage; @@ -60,7 +58,7 @@ - (void) setupAuxiliaryObjects; -- (SOGoMAPIDBFolder *) dbFolder; +- (SOGoCacheGCSFolder *) dbFolder; - (NSArray *) activeMessageTables; - (NSArray *) activeFAIMessageTables; diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 2e54838b3..3dba819b8 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -1,8 +1,6 @@ /* MAPIStoreFolder.m - this file is part of SOGo * - * Copyright (C) 2011-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-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 @@ -51,8 +49,9 @@ #import "NSDate+MAPIStore.h" #import "NSString+MAPIStore.h" #import "NSObject+MAPIStore.h" -#import "SOGoMAPIDBFolder.h" +#import #import "SOGoMAPIDBMessage.h" +#import "SOGoCacheGCSObject+MAPIStore.h" #include @@ -120,7 +119,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [userContext ensureFolderTableExists]; ASSIGN (dbFolder, - [SOGoMAPIDBFolder objectWithName: folderName + [SOGoCacheGCSFolder objectWithName: folderName inContainer: [container dbFolder]]); [dbFolder setTableUrl: [userContext folderTableURL]]; if (!container && [path length] > 0) @@ -138,7 +137,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe // ASSIGN (propsMessage, // [SOGoMAPIDBMessage objectWithName: @"properties.plist" // inContainer: dbFolder]); - // [propsMessage setObjectType: MAPIDBObjectTypeInternal]; + // [propsMessage setObjectType: MAPIInternalCacheObject]; // [propsMessage reloadIfNeeded]; [properties release]; properties = [dbFolder properties]; @@ -192,7 +191,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe [super dealloc]; } -- (SOGoMAPIDBFolder *) dbFolder +- (SOGoCacheGCSFolder *) dbFolder { return dbFolder; } @@ -1221,7 +1220,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe - (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { - return [dbFolder childKeysOfType: MAPIDBObjectTypeFAI + return [dbFolder childKeysOfType: MAPIFAICacheObject includeDeleted: NO matchingQualifier: qualifier andSortOrderings: sortOrderings]; @@ -1531,7 +1530,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe newKey = [NSString stringWithFormat: @"%@.plist", [SOGoObject globallyUniqueObjectId]]; dbObject = [SOGoMAPIDBMessage objectWithName: newKey inContainer: dbFolder]; - [dbObject setObjectType: MAPIDBObjectTypeFAI]; + [dbObject setObjectType: MAPIFAICacheObject]; [dbObject setIsNew: YES]; newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject inContainer: self]; diff --git a/OpenChange/MAPIStoreGCSFolder.m b/OpenChange/MAPIStoreGCSFolder.m index 52d2b0866..ce4bb870f 100644 --- a/OpenChange/MAPIStoreGCSFolder.m +++ b/OpenChange/MAPIStoreGCSFolder.m @@ -74,7 +74,7 @@ static Class NSNumberK; ASSIGN (versionsMessage, [SOGoMAPIDBMessage objectWithName: @"versions.plist" inContainer: dbFolder]); - [versionsMessage setObjectType: MAPIDBObjectTypeInternal]; + [versionsMessage setObjectType: MAPIInternalCacheObject]; } - (void) dealloc diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index bcc0253ef..f0912d8f0 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -59,7 +59,7 @@ #import "NSData+MAPIStore.h" #import "NSString+MAPIStore.h" #import "SOGoMAPIDBMessage.h" -#import "SOGoMAPIDBFolder.h" +#import #import "MAPIStoreMailVolatileMessage.h" @@ -106,7 +106,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK; ASSIGN (versionsMessage, [SOGoMAPIDBMessage objectWithName: @"versions.plist" inContainer: dbFolder]); - [versionsMessage setObjectType: MAPIDBObjectTypeInternal]; + [versionsMessage setObjectType: MAPIInternalCacheObject]; } - (BOOL) ensureFolderExists @@ -1172,9 +1172,9 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP) - (MAPIStoreMessage *) createMessage { - SOGoMAPIObject *childObject; + SOGoCacheObject *childObject; - childObject = [SOGoMAPIObject objectWithName: [SOGoMAPIObject + childObject = [SOGoCacheObject objectWithName: [SOGoCacheObject globallyUniqueObjectId] inContainer: sogoObject]; return [MAPIStoreMailVolatileMessage diff --git a/OpenChange/MAPIStoreMailVolatileMessage.m b/OpenChange/MAPIStoreMailVolatileMessage.m index ed60a8620..c640ab7db 100644 --- a/OpenChange/MAPIStoreMailVolatileMessage.m +++ b/OpenChange/MAPIStoreMailVolatileMessage.m @@ -62,7 +62,7 @@ #import "NSData+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" -#import "SOGoMAPIObject.h" +#import #import "MAPIStoreMailVolatileMessage.h" diff --git a/OpenChange/MAPIStoreTable.m b/OpenChange/MAPIStoreTable.m index 842fa3afc..3b8270b6c 100644 --- a/OpenChange/MAPIStoreTable.m +++ b/OpenChange/MAPIStoreTable.m @@ -25,7 +25,7 @@ #import #import -#import "EOBitmaskQualifier.h" +#import #import "MAPIStoreActiveTables.h" #import "MAPIStoreObject.h" #import "MAPIStoreTypes.h" diff --git a/OpenChange/MAPIStoreUserContext.m b/OpenChange/MAPIStoreUserContext.m index 7432f91fd..d0284eb6f 100644 --- a/OpenChange/MAPIStoreUserContext.m +++ b/OpenChange/MAPIStoreUserContext.m @@ -42,7 +42,7 @@ #import #import -#import "GCSSpecialQueries+OpenChange.h" +#import #import "MAPIApplication.h" #import "MAPIStoreAuthenticator.h" #import "MAPIStoreMapping.h" @@ -295,7 +295,7 @@ static NSMapTable *contextsTable = nil; { /* If "OCSFolderInfoURL" is properly configured, we must have 5 parts in this url. */ - ocFSTableName = [NSString stringWithFormat: @"socfs_%@", + ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@", [username asCSSIdentifier]]; [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; folderTableURL @@ -329,7 +329,7 @@ static NSMapTable *contextsTable = nil; tableName]]) { queries = [channel specialQueries]; - query = [queries createOpenChangeFSTableWithName: tableName]; + query = [queries createSOGoCacheGCSFolderTableWithName: tableName]; if ([channel evaluateExpressionX: query]) [NSException raise: @"MAPIStoreIOException" format: @"could not create special table '%@'", tableName]; diff --git a/OpenChange/NSObject+PropertyList.m b/OpenChange/NSObject+PropertyList.m index ce01bd227..07a42d976 100644 --- a/OpenChange/NSObject+PropertyList.m +++ b/OpenChange/NSObject+PropertyList.m @@ -1,8 +1,6 @@ /* dbmsgdump.m - this file is part of SOGo * - * Copyright (C) 2011-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-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 @@ -33,7 +31,7 @@ #import #import -#import "BSONCodec.h" +#import const char *indentationStep = " "; diff --git a/OpenChange/SOGoCacheGCSObject+MAPIStore.h b/OpenChange/SOGoCacheGCSObject+MAPIStore.h new file mode 100644 index 000000000..da8452e3d --- /dev/null +++ b/OpenChange/SOGoCacheGCSObject+MAPIStore.h @@ -0,0 +1,32 @@ +/* SOGoCacheGCSObject+MAPIStore.h - this file is part of SOGo + * + * Copyright (C) 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 + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOCACHEGCSOBJECTMAPISTORE +#define SOGOCACHEGCSOBJECTMAPISTORE + +#include + +@interface SOGoCacheGCSObject (MAPIStore) + +- (Class) mapistoreMessageClass; + +@end + +#endif // SOGOCACHEGCSOBJECTMAPISTORE diff --git a/OpenChange/SOGoCacheGCSObject+MAPIStore.m b/OpenChange/SOGoCacheGCSObject+MAPIStore.m new file mode 100644 index 000000000..679bee089 --- /dev/null +++ b/OpenChange/SOGoCacheGCSObject+MAPIStore.m @@ -0,0 +1,68 @@ +/* SOGoCacheGCSObject+MAPIStore.m - this file is part of SOGo + * + * Copyright (C) 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 + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import + +#include "MAPIStoreTypes.h" + +#include "SOGoCacheGCSObject+MAPIStore.h" + +@implementation SOGoCacheGCSObject (MAPIStore) + +- (Class) mapistoreMessageClass +{ + NSString *className, *mapiMsgClass; + + switch (objectType) + { + case MAPIMessageCacheObject: + mapiMsgClass = [properties + objectForKey: MAPIPropertyKey (PidTagMessageClass)]; + if (mapiMsgClass) + { + if ([mapiMsgClass isEqualToString: @"IPM.StickyNote"]) + className = @"MAPIStoreNotesMessage"; + else + className = @"MAPIStoreDBMessage"; + //[self logWithFormat: @"PidTagMessageClass = '%@', returning '%@'", + // mapiMsgClass, className]; + } + else + { + //[self warnWithFormat: @"PidTagMessageClass is not set, falling back" + // @" to 'MAPIStoreDBMessage'"]; + className = @"MAPIStoreDBMessage"; + } + break; + case MAPIFAICacheObject: + className = @"MAPIStoreFAIMessage"; + break; + default: + [NSException raise: @"MAPIStoreIOException" + format: @"message class should not be queried for objects" + @" of type '%d'", objectType]; + } + + return NSClassFromString (className); +} + +@end diff --git a/OpenChange/SOGoMAPIDBMessage.h b/OpenChange/SOGoMAPIDBMessage.h index 29909a60a..4211988f6 100644 --- a/OpenChange/SOGoMAPIDBMessage.h +++ b/OpenChange/SOGoMAPIDBMessage.h @@ -1,8 +1,6 @@ /* SOGoMAPIDBMessage.h - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -23,12 +21,12 @@ #ifndef SOGOMAPIDBMESSAGE_H #define SOGOMAPIDBMESSAGE_H -#import "SOGoMAPIDBObject.h" +#import @class NSDate; @class NSString; -@interface SOGoMAPIDBMessage : SOGoMAPIDBObject +@interface SOGoMAPIDBMessage : SOGoCacheGCSObject @end #endif /* SOGOMAPIDBMESSAGE_H */ diff --git a/OpenChange/SOGoMAPIDBMessage.m b/OpenChange/SOGoMAPIDBMessage.m index f17093be1..343b90334 100644 --- a/OpenChange/SOGoMAPIDBMessage.m +++ b/OpenChange/SOGoMAPIDBMessage.m @@ -30,7 +30,7 @@ #import -#import "SOGoMAPIDBFolder.h" +#import #import "SOGoMAPIDBMessage.h" diff --git a/OpenChange/dbmsgreader.m b/OpenChange/dbmsgreader.m index 0119a00c9..97b3356d0 100644 --- a/OpenChange/dbmsgreader.m +++ b/OpenChange/dbmsgreader.m @@ -34,7 +34,7 @@ #import #import "MAPIStoreUserContext.h" -#import "SOGoMAPIDBObject.h" +#import #import "NSObject+PropertyList.m" diff --git a/Scripts/openchange_user_cleanup b/Scripts/openchange_user_cleanup index 8c32431fc..411479819 100755 --- a/Scripts/openchange_user_cleanup +++ b/Scripts/openchange_user_cleanup @@ -22,7 +22,7 @@ sogoUserDefaultsFile = os.path.expanduser("~sogo/GNUstep/Defaults/.GNUstepDefaul # - removes the entry in samba's ldap tree via ldbedit (NOTYET) # - remove the user's directory under mapistore/ and mapistore/SOGo # - cleanup Junk Folders and Sync Issues imap folders -# - Delete the socfs_ table for the username. +# - Delete the sogo_cache_folder_ table for the username. def usage(): print """ @@ -210,7 +210,7 @@ On RHEL, install it using 'yum install MySQL-python'""" conn = MySQLdb.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, db=dbname) c=conn.cursor() - tablename="socfs_%s" % (username) + tablename="sogo_cache_folder_%s" % (username) c.execute("TRUNCATE TABLE %s" % tablename) print "Table %s emptied" @@ -225,7 +225,7 @@ On RHEL, install it using 'yum install python-pgsql'""" raise Exception(msg) conn = pg.connect(host=dbhost, port=int(dbport), user=dbuser, passwd=dbpass, dbname=dbname) - tablename = "socfs_%s" % username + tablename = "sogo_cache_folder_%s" % username conn.query("DELETE FROM %s" % tablename) print "Table '%s' emptied" % tablename @@ -278,7 +278,7 @@ def sqlCleanup(username): print "Starting SQL cleanup" OCSFolderInfoURL = getOCSFolderInfoURL() if OCSFolderInfoURL is None: - raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the socfs_%s table should be truncated manually" % (username)) + raise Exception("Couldn't fetch OCSFolderInfoURL or it is not set. the sogo_cache_folder_%s table should be truncated manually" % (username)) # postgresql://sogo:sogo@127.0.0.1:5432/sogo/sogo_folder_info m = re.search("(.+)://(.+):(.+)@(.+):(\d+)/(.+)/(.+)", OCSFolderInfoURL) diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 8552ae72f..61cef70dc 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2013 Inverse inc. + Copyright (C) 2007-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. @@ -1280,13 +1280,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir [baseWhere addObject: privacySQLString]; if ([title length]) - [baseWhere - addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'", + if ([filters length]) + { + if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"]) + { + [baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')", + [title stringByReplacingString: @"'" withString: @"\\'\\'"], + [title stringByReplacingString: @"'" withString: @"\\'\\'"], + [title stringByReplacingString: @"'" withString: @"\\'\\'"]]]; + } + } + else + [baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'", [title stringByReplacingString: @"'" withString: @"\\'\\'"]]]; - if ([filters length]) - [baseWhere addObject: [NSString stringWithFormat: @"(%@)", filters]]; - /* prepare mandatory fields */ fields = [NSMutableArray arrayWithArray: _fields]; diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 883564a7e..99b73a636 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -1,7 +1,7 @@ /* SOGoAppointmentFolders.m - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-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 @@ -56,13 +56,6 @@ #import "SOGoAppointmentFolders.h" -@interface SOGoParentFolder (Private) - -- (NSException *) _fetchPersonalFolders: (NSString *) sql - withChannel: (EOAdaptorChannel *) fc; - -@end - static SoSecurityManager *sm = nil; @implementation SOGoAppointmentFolders @@ -596,8 +589,9 @@ static SoSecurityManager *sm = nil; } } -- (NSException *) _fetchPersonalFolders: (NSString *) sql - withChannel: (EOAdaptorChannel *) fc +- (NSException *) fetchSpecialFolders: (NSString *) sql + withChannel: (EOAdaptorChannel *) fc + andFolderType: (SOGoFolderType) folderType { BOOL isWebRequest; NSException *error; @@ -607,7 +601,7 @@ static SoSecurityManager *sm = nil; SOGoWebAppointmentFolder *webFolder; NSString *name; - error = [super _fetchPersonalFolders: sql withChannel: fc]; + error = [super fetchSpecialFolders: sql withChannel: fc andFolderType: folderType]; if (!error) { isWebRequest = [[context request] handledByDefaultHandler]; diff --git a/SoObjects/Appointments/SOGoAppointmentInboxFolder.m b/SoObjects/Appointments/SOGoAppointmentInboxFolder.m index a30242fcf..c57d7090d 100644 --- a/SoObjects/Appointments/SOGoAppointmentInboxFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentInboxFolder.m @@ -1,8 +1,6 @@ /* SOGoAppointmentInboxFolder.m - this file is part of SOGo * - * Copyright (C) 2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-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 diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index b7da9c344..1712ec7ea 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -148,14 +148,14 @@ object = [folder lookupName: nameInContainer inContext: context acquire: NO]; - if ([object isKindOfClass: [NSException class]]) + if ([object isKindOfClass: [NSException class]] || [object isNew]) { possibleName = [folder resourceNameForEventUID: eventUID]; if (possibleName) { object = [folder lookupName: possibleName inContext: context acquire: NO]; - if ([object isKindOfClass: [NSException class]]) + if ([object isKindOfClass: [NSException class]] || [object isNew]) object = nil; } else diff --git a/SoObjects/Appointments/product.plist b/SoObjects/Appointments/product.plist index ac04c7d41..62408d864 100644 --- a/SoObjects/Appointments/product.plist +++ b/SoObjects/Appointments/product.plist @@ -67,7 +67,7 @@ SOGoAppointmentInboxFolder = { superclass = "SOGoAppointmentFolder"; defaultRoles = { - "Access Contents Information" = ( "Owner" ); + "Access Contents Information" = ( "Owner", "AuthorizedSubscriber" ); }; }; SOGoCalendarComponent = { diff --git a/SoObjects/Contacts/Czech.lproj/Localizable.strings b/SoObjects/Contacts/Czech.lproj/Localizable.strings index 23e568748..3ea6054dc 100644 --- a/SoObjects/Contacts/Czech.lproj/Localizable.strings +++ b/SoObjects/Contacts/Czech.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Osobní kontakty"; +"Collected Address Book" = "Sebrané kontakty"; diff --git a/SoObjects/Contacts/English.lproj/Localizable.strings b/SoObjects/Contacts/English.lproj/Localizable.strings index 56404b013..0d040749c 100644 --- a/SoObjects/Contacts/English.lproj/Localizable.strings +++ b/SoObjects/Contacts/English.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Personal Address Book"; +"Collected Address Book" = "Collected Address Book"; diff --git a/SoObjects/Contacts/French.lproj/Localizable.strings b/SoObjects/Contacts/French.lproj/Localizable.strings index af4d87c3a..2bef020a1 100644 --- a/SoObjects/Contacts/French.lproj/Localizable.strings +++ b/SoObjects/Contacts/French.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Carnet d'adresses personnel"; +"Collected Address Book" = "Adresses collectées"; diff --git a/SoObjects/Contacts/Hungarian.lproj/Localizable.strings b/SoObjects/Contacts/Hungarian.lproj/Localizable.strings index 281d9289f..7e925d5aa 100644 --- a/SoObjects/Contacts/Hungarian.lproj/Localizable.strings +++ b/SoObjects/Contacts/Hungarian.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Személyes címjegyzék"; +"Collected Address Book" = "Összegyűjtöt címek jegyzéke"; diff --git a/SoObjects/Contacts/Polish.lproj/Localizable.strings b/SoObjects/Contacts/Polish.lproj/Localizable.strings index 1e5341c06..2e8e3379e 100644 --- a/SoObjects/Contacts/Polish.lproj/Localizable.strings +++ b/SoObjects/Contacts/Polish.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Osobista książka adresowa"; +"Collected Address Book" = "Zbierana Książka Adresowa"; diff --git a/SoObjects/Contacts/Russian.lproj/Localizable.strings b/SoObjects/Contacts/Russian.lproj/Localizable.strings index 8927024a7..478705eaa 100644 --- a/SoObjects/Contacts/Russian.lproj/Localizable.strings +++ b/SoObjects/Contacts/Russian.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Личная адресная книга"; +"Collected Address Book" = "Собранные адреса"; diff --git a/SoObjects/Contacts/SOGoContactFolders.h b/SoObjects/Contacts/SOGoContactFolders.h index 1b685c85b..bb984f0b6 100644 --- a/SoObjects/Contacts/SOGoContactFolders.h +++ b/SoObjects/Contacts/SOGoContactFolders.h @@ -25,12 +25,19 @@ @interface SOGoContactFolders : SOGoParentFolder +- (NSString *) defaultFolderName; +- (NSString *) collectedFolderName; + - (NSException *) renameLDAPAddressBook: (NSString *) sourceID withDisplayName: (NSString *) newDisplayName; - (NSException *) removeLDAPAddressBook: (NSString *) sourceID; - (NSDictionary *) systemSources; +- (NSArray *) allContactsFromFilter: (NSString *) theFilter + excludeGroups: (BOOL) excludeGroups + excludeLists: (BOOL) excludeLists; + @end #endif /* SOGOCONTACTFOLDERS_H */ diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index 3c3e938f0..b0428f7f4 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -22,12 +22,18 @@ #import #import #import +#import #import #import #import #import +#import +#import +#import +#import + #import #import #import @@ -40,10 +46,17 @@ #import "SOGoContactFolders.h" +Class SOGoContactSourceFolderK; + #define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" @implementation SOGoContactFolders ++ (void) initialize +{ + SOGoContactSourceFolderK = [SOGoContactSourceFolder class]; +} + + (NSString *) gcsFolderType { return @"Contact"; @@ -110,6 +123,38 @@ return result; } +- (NSException *) appendCollectedSources +{ + GCSChannelManager *cm; + EOAdaptorChannel *fc; + NSURL *folderLocation; + NSString *sql, *gcsFolderType; + NSException *error; + + cm = [GCSChannelManager defaultChannelManager]; + folderLocation = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; + fc = [cm acquireOpenChannelForURL: folderLocation]; + if ([fc isOpen]) + { + gcsFolderType = [[self class] gcsFolderType]; + + sql = [NSString stringWithFormat: (@"SELECT c_path4 FROM %@" + @" WHERE c_path2 = '%@'" + @" AND c_folder_type = '%@'"), + [folderLocation gcsTableName], owner, gcsFolderType]; + + error = [super fetchSpecialFolders: sql withChannel: fc andFolderType: SOGoCollectedFolder]; + + [cm releaseChannel: fc]; + } + else + error = [NSException exceptionWithName: @"SOGoDBException" + reason: @"database connection could not be open" + userInfo: nil]; + + return error; +} + - (NSDictionary *) systemSources { NSMutableDictionary *systemSources; @@ -225,7 +270,7 @@ if ([sourceID isEqualToString: @"personal"]) result = [NSException exceptionWithHTTPStatus: 403 - reason: @"folder 'personal' cannot be deleted"]; + reason: (@"folder '%@' cannot be deleted", sourceID)]; else { result = nil; @@ -250,6 +295,11 @@ return [self labelForKey: @"Personal Address Book"]; } +- (NSString *) collectedFolderName +{ + return [self labelForKey: @"Collected Address Book"]; +} + - (NSArray *) toManyRelationshipKeys { NSMutableArray *keys; @@ -371,4 +421,89 @@ asWebDAVValue]; } +- (NSArray *) allContactsFromFilter: (NSString *) theFilter + excludeGroups: (BOOL) excludeGroups + excludeLists: (BOOL) excludeLists +{ + SOGoFolder *folder; + NSString *mail, *domain; + NSArray *folders, *contacts, *descriptors, *sortedContacts; + NSMutableArray *sortedFolders; + NSMutableDictionary *contact, *uniqueContacts; + unsigned int i, j, max; + NSSortDescriptor *commonNameDescriptor; + + // NSLog(@"Search all contacts: %@", searchText); + + domain = [[context activeUser] domain]; + folders = nil; + NS_DURING + folders = [self subFolders]; + NS_HANDLER + /* We need to specifically test for @"SOGoDBException", which is + raised explicitly in SOGoParentFolder. Any other exception should + be re-raised. */ + if ([[localException name] isEqualToString: @"SOGoDBException"]) + folders = nil; + else + [localException raise]; + NS_ENDHANDLER; + max = [folders count]; + sortedFolders = [NSMutableArray arrayWithCapacity: max]; + uniqueContacts = [NSMutableDictionary dictionary]; + for (i = 0; i < max; i++) + { + folder = [folders objectAtIndex: i]; + /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ + if ([folder isKindOfClass: SOGoContactSourceFolderK]) + [sortedFolders insertObject: folder atIndex: 0]; + else + [sortedFolders addObject: folder]; + } + for (i = 0; i < max; i++) + { + folder = [sortedFolders objectAtIndex: i]; + //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); + contacts = [folder lookupContactsWithFilter: theFilter + onCriteria: @"name_or_address" + sortBy: @"c_cn" + ordering: NSOrderedAscending + inDomain: domain]; + for (j = 0; j < [contacts count]; j++) + { + contact = [contacts objectAtIndex: j]; + mail = [contact objectForKey: @"c_mail"]; + //NSLog(@" found %@ (%@) ? %@", [contact objectForKey: @"c_name"], mail, + // [contact description]); + if (!excludeLists && [[contact objectForKey: @"c_component"] + isEqualToString: @"vlist"]) + { + [contact setObject: [folder nameInContainer] + forKey: @"container"]; + [uniqueContacts setObject: contact + forKey: [contact objectForKey: @"c_name"]]; + } + else if ([mail length] + && [uniqueContacts objectForKey: mail] == nil + && !(excludeGroups && [contact objectForKey: @"isGroup"])) + [uniqueContacts setObject: contact forKey: mail]; + } + } + if ([uniqueContacts count] > 0) + { + // Sort the contacts by display name + commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn" + ascending:YES]; + descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil]; + [commonNameDescriptor release]; + sortedContacts = [[uniqueContacts allValues] + sortedArrayUsingDescriptors: descriptors]; + } + else + sortedContacts = [NSArray array]; + + return sortedContacts; +} + + @end diff --git a/SoObjects/Contacts/Slovak.lproj/Localizable.strings b/SoObjects/Contacts/Slovak.lproj/Localizable.strings index 1e3853747..5c5a2cdbe 100644 --- a/SoObjects/Contacts/Slovak.lproj/Localizable.strings +++ b/SoObjects/Contacts/Slovak.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Osobné kontakty"; +"Collected Address Book" = "Pozbierané osobné kontakty"; diff --git a/SoObjects/Contacts/SpanishArgentina.lproj/Localizable.strings b/SoObjects/Contacts/SpanishArgentina.lproj/Localizable.strings index 0c1a73e45..47f857201 100644 --- a/SoObjects/Contacts/SpanishArgentina.lproj/Localizable.strings +++ b/SoObjects/Contacts/SpanishArgentina.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Libreta personal de direcciones"; +"Collected Address Book" = "Direcciones recopiladas"; diff --git a/SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings b/SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings index 0c1a73e45..d8f50ba84 100644 --- a/SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings +++ b/SoObjects/Contacts/SpanishSpain.lproj/Localizable.strings @@ -1 +1,2 @@ "Personal Address Book" = "Libreta personal de direcciones"; +"Collected Address Book" = "Libreta de direcciones recogidas"; diff --git a/SoObjects/Mailer/Czech.lproj/Localizable.strings b/SoObjects/Mailer/Czech.lproj/Localizable.strings index 0cbf2f1f9..7e6f056b1 100644 --- a/SoObjects/Mailer/Czech.lproj/Localizable.strings +++ b/SoObjects/Mailer/Czech.lproj/Localizable.strings @@ -1,2 +1,2 @@ -"SieveFolderName" = "Filtry"; "OtherUsersFolderName" = "Ostatní uživatelé"; +"SharedFoldersName" = "Sdílené složky"; diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 7e7f1fbaf..48c850b9e 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -417,7 +417,15 @@ { if (!ignoreContent) { - [result appendString: [NSString stringWithCharacters: characters length: length]]; + // Append a text node + if (ignoreContentTags) + // We are converting a HTML message to plain text (htmlToTextContentHandler): + // include the HTML tags in the text + [result appendString: [NSString stringWithCharacters: characters length: length]]; + else + // We are sanitizing an HTML message (sanitizerContentHandler): + // escape the HTML entitites so they are visible + [result appendString: [[NSString stringWithCharacters: characters length: length] stringByEscapingHTMLString]]; } } diff --git a/SoObjects/Mailer/Polish.lproj/Localizable.strings b/SoObjects/Mailer/Polish.lproj/Localizable.strings index 6ae698eb8..2b3c55c11 100644 --- a/SoObjects/Mailer/Polish.lproj/Localizable.strings +++ b/SoObjects/Mailer/Polish.lproj/Localizable.strings @@ -1,2 +1,2 @@ -"SieveFolderName" = "Filtry"; "OtherUsersFolderName" = "Inni użytkownicy"; +"SharedFoldersName" = "Foldery współdzielone"; diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 94c5d2888..4004d32a9 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -102,6 +102,8 @@ - (NSData *) mimeMessageAsData; /* operations */ +- (NSArray *) allRecipients; +- (NSArray *) allBareRecipients; - (NSException *) delete; - (NSException *) sendMail; diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index bd5a0d469..ac6aaeed4 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -45,6 +45,7 @@ #import #import #import +#import #import #import #import @@ -63,6 +64,11 @@ #import #import +#import + +#import +#import + #import "NSData+Mail.h" #import "NSString+Mail.h" #import "SOGoMailAccount.h" @@ -260,7 +266,7 @@ static NSString *userAgent = nil; { id headerValue; unsigned int count; - NSString *messageID, *priority, *pureSender, *replyTo; + NSString *messageID, *priority, *pureSender, *replyTo, *receipt; for (count = 0; count < 8; count++) { @@ -279,26 +285,57 @@ static NSString *userAgent = nil; [headers setObject: messageID forKey: @"message-id"]; } - priority = [newHeaders objectForKey: @"priority"]; - if (!priority || [priority isEqualToString: @"NORMAL"]) + priority = [newHeaders objectForKey: @"X-Priority"]; + if (priority) { - [headers removeObjectForKey: @"X-Priority"]; - } - else if ([priority isEqualToString: @"HIGHEST"]) - { - [headers setObject: @"1 (Highest)" forKey: @"X-Priority"]; - } - else if ([priority isEqualToString: @"HIGH"]) - { - [headers setObject: @"2 (High)" forKey: @"X-Priority"]; - } - else if ([priority isEqualToString: @"LOW"]) - { - [headers setObject: @"4 (Low)" forKey: @"X-Priority"]; + // newHeaders come from MIME message; convert X-Priority to Web representation + [headers setObject: priority forKey: @"X-Priority"]; + [headers removeObjectForKey: @"priority"]; + if ([priority isEqualToString: @"1 (Highest)"]) + { + [headers setObject: @"HIGHEST" forKey: @"priority"]; + } + else if ([priority isEqualToString: @"2 (High)"]) + { + [headers setObject: @"HIGH" forKey: @"priority"]; + } + else if ([priority isEqualToString: @"4 (Low)"]) + { + [headers setObject: @"LOW" forKey: @"priority"]; + } + else if ([priority isEqualToString: @"5 (Lowest)"]) + { + [headers setObject: @"LOWEST" forKey: @"priority"]; + } } else { - [headers setObject: @"5 (Lowest)" forKey: @"X-Priority"]; + // newHeaders come from Web form; convert priority to MIME header representation + priority = [newHeaders objectForKey: @"priority"]; + if (!priority || [priority isEqualToString: @"NORMAL"]) + { + [headers removeObjectForKey: @"X-Priority"]; + } + else if ([priority isEqualToString: @"HIGHEST"]) + { + [headers setObject: @"1 (Highest)" forKey: @"X-Priority"]; + } + else if ([priority isEqualToString: @"HIGH"]) + { + [headers setObject: @"2 (High)" forKey: @"X-Priority"]; + } + else if ([priority isEqualToString: @"LOW"]) + { + [headers setObject: @"4 (Low)" forKey: @"X-Priority"]; + } + else + { + [headers setObject: @"5 (Lowest)" forKey: @"X-Priority"]; + } + if (priority) + { + [headers setObject: priority forKey: @"priority"]; + } } replyTo = [headers objectForKey: @"replyTo"]; @@ -308,14 +345,30 @@ static NSString *userAgent = nil; } [headers removeObjectForKey: @"replyTo"]; - if ([[newHeaders objectForKey: @"receipt"] isEqualToString: @"true"]) + receipt = [newHeaders objectForKey: @"Disposition-Notification-To"]; + if ([receipt length] > 0) { - pureSender = [[newHeaders objectForKey: @"from"] pureEMailAddress]; - if (pureSender) - [headers setObject: pureSender forKey: @"Disposition-Notification-To"]; + [headers setObject: @"true" forKey: @"receipt"]; + [headers setObject: receipt forKey: @"Disposition-Notification-To"]; } else - [headers removeObjectForKey: @"Disposition-Notification-To"]; + { + receipt = [newHeaders objectForKey: @"receipt"]; + if ([receipt isEqualToString: @"true"]) + { + [headers setObject: receipt forKey: @"receipt"]; + pureSender = [[newHeaders objectForKey: @"from"] pureEMailAddress]; + if (pureSender) + { + [headers setObject: pureSender forKey: @"Disposition-Notification-To"]; + } + } + else + { + [headers removeObjectForKey: @"receipt"]; + [headers removeObjectForKey: @"Disposition-Notification-To"]; + } + } } - (NSDictionary *) headers @@ -799,8 +852,10 @@ static NSString *userAgent = nil; { NSString *subject, *msgid; NSMutableDictionary *info; + NSDictionary *h; NSMutableArray *addresses; NGImap4Envelope *sourceEnvelope; + id priority, receipt; [sourceMail fetchCoreInfos]; @@ -830,6 +885,15 @@ static NSString *userAgent = nil; [self _addEMailsOfAddresses: [sourceEnvelope replyTo] toArray: addresses]; if ([addresses count] > 0) [info setObject: addresses forKey: @"replyTo"]; + + h = [sourceMail mailHeaders]; + priority = [h objectForKey: @"x-priority"]; + if ([priority isNotEmpty] && [priority isKindOfClass: [NSString class]]) + [info setObject: (NSString*)priority forKey: @"X-Priority"]; + receipt = [h objectForKey: @"disposition-notification-to"]; + if ([receipt isNotEmpty] && [receipt isKindOfClass: [NSString class]]) + [info setObject: (NSString*)receipt forKey: @"Disposition-Notification-To"]; + [self setHeaders: info]; [self setText: [sourceMail contentForEditing]]; @@ -1660,7 +1724,7 @@ static NSString *userAgent = nil; { recipients = [headers objectForKey: fieldNames[count]]; if ([recipients count] > 0) - [allRecipients addObjectsFromArray: recipients]; + [allRecipients addObjectsFromArray: recipients]; } return allRecipients; @@ -1689,6 +1753,63 @@ static NSString *userAgent = nil; // - (NSException *) sendMail { + SOGoUserDefaults *ud; + ud = [[context activeUser] userDefaults]; + + if ([ud mailAddOutgoingAddresses]) + { + SOGoContactFolders *contactFolders; + NGMailAddressParser *parser; + id parsedRecipient; + SOGoContactFolder *folder; + SOGoContactGCSEntry *newContact; + NGVCard *card; + Class contactGCSEntry; + NSMutableArray *recipients; + NSString *recipient, *emailAddress, *addressBook, *uid; + NSArray *matchingContacts; + int i; + + // Get all the addressbooks + contactFolders = [[[context activeUser] homeFolderInContext: context] + lookupName: @"Contacts" + inContext: context + acquire: NO]; + // Get all the recipients from the current email + recipients = [self allRecipients]; + for (i = 0; i < [recipients count]; i++) + { + // The address contains a string. ex: "John Doe " + recipient = [recipients objectAtIndex: i]; + parser = [NGMailAddressParser mailAddressParserWithString: recipient]; + parsedRecipient = [parser parse]; + emailAddress = [parsedRecipient address]; + + matchingContacts = [contactFolders allContactsFromFilter: emailAddress + excludeGroups: YES + excludeLists: YES]; + } + // If we don't get any results from the autocompletion code, we add it.. + if ([matchingContacts count] == 0) + { + // Get the selected addressbook from the user preferences where the new address will be added + addressBook = [ud selectedAddressBook]; + folder = [contactFolders lookupName: addressBook inContext: context acquire: NO]; + uid = [folder globallyUniqueObjectId]; + + if (folder && uid) + { + card = [NGVCard cardWithUid: uid]; + [card addEmail: emailAddress types: nil]; + + contactGCSEntry = NSClassFromString(@"SOGoContactGCSEntry"); + newContact = [contactGCSEntry objectWithName: uid + inContainer: folder]; + [newContact setIsNew: YES]; + [newContact saveContentString: [card versitString]]; + } + } + } return [self sendMailAndCopyToSent: YES]; } diff --git a/SoObjects/Mailer/SOGoMailAccount.h b/SoObjects/Mailer/SOGoMailAccount.h index 2efc05fc1..7bff26e6f 100644 --- a/SoObjects/Mailer/SOGoMailAccount.h +++ b/SoObjects/Mailer/SOGoMailAccount.h @@ -84,6 +84,8 @@ typedef enum { - (NSArray *) allFolderPaths; - (NSArray *) allFoldersMetadata; +- (NSDictionary *) imapFolderGUIDs; + - (BOOL) isInDraftsFolder; /* special folders */ diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 5f9a67c49..5bc3ff2ae 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -60,6 +60,8 @@ #import "SOGoUser+Mailer.h" #import "SOGoMailAccount.h" +#import + #define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" @@ -659,6 +661,62 @@ static NSString *inboxFolderName = @"INBOX"; return password; } + +- (NSDictionary *) imapFolderGUIDs +{ + NSDictionary *result, *nresult, *folderData; + NSMutableDictionary *folders; + NGImap4Client *client; + SOGoUserDefaults *ud; + NSArray *folderList; + NSEnumerator *e; + NSString *guid; + id object; + + BOOL subscribedOnly; + + ud = [[context activeUser] userDefaults]; + subscribedOnly = [ud mailShowSubscribedFoldersOnly]; + + folderList = [[self imap4Connection] allFoldersForURL: [self imap4URL] + onlySubscribedFolders: subscribedOnly]; + + folders = [NSMutableDictionary dictionary]; + + client = [[self imap4Connection] client]; + result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"]; + + e = [folderList objectEnumerator]; + + while (object = [e nextObject]) + { + guid = [[[[result objectForKey: @"FolderList"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"/comment"] objectForKey: @"value.priv"]; + + if (!guid) + { + guid = [[NSProcessInfo processInfo] globallyUniqueString]; + nresult = [client annotation: [object substringFromIndex: 1] entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid]; + + if (![[nresult objectForKey: @"result"] boolValue]) + { + // Need to implement X-GUID query for Dovecot - this requires modification in SOPE to support following command: + // 1 list "" "*" return (status (x-guid)) -> this would avoid firing a command per folder to IMAP + nresult = [client status: [object substringFromIndex: 1] flags: [NSArray arrayWithObject: @"x-guid"]]; + guid = [nresult objectForKey: @"x-guid"]; + if (!guid) + { + guid = [NSString stringWithFormat: @"%@", [object substringFromIndex: 1]]; + } + } + } + + [folders setObject: guid forKey: [object substringFromIndex: 1]]; + } + + return folders; +} + + /* name lookup */ - (id) lookupName: (NSString *) _key diff --git a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod index 9afd448d9..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod +++ b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod @@ -13,6 +13,11 @@ from: WOString { escapeHTML = NO; } +newLine: WOString { + value = newLine; + escapeHTML = NO; +} + hasReplyTo: WOConditional { condition = hasReplyTo; } diff --git a/SoObjects/Mailer/SOGoMailBrazilianPortugueseReply.wo/SOGoMailBrazilianPortugueseReply.wod b/SoObjects/Mailer/SOGoMailBrazilianPortugueseReply.wo/SOGoMailBrazilianPortugueseReply.wod index df3789cc3..3fbed6d61 100644 --- a/SoObjects/Mailer/SOGoMailBrazilianPortugueseReply.wo/SOGoMailBrazilianPortugueseReply.wod +++ b/SoObjects/Mailer/SOGoMailBrazilianPortugueseReply.wo/SOGoMailBrazilianPortugueseReply.wod @@ -22,6 +22,11 @@ from: WOString { escapeHTML = NO; } +newLine: WOString { + value = newLine; + escapeHTML = NO; +} + hasReplyTo: WOConditional { condition = hasReplyTo; } diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index eac1aec50..84e51d9ca 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2011 Inverse inc. + Copyright (C) 2009-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo @@ -94,7 +94,6 @@ - (NSCalendarDate *) mostRecentMessageDate; -- (NSString *) davCollectionTagFromId: (NSString *) theId; - (NSString *) davCollectionTag; - (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 7f45ec814..3607b9dd2 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -72,6 +72,28 @@ static NSString *defaultUserID = @"anyone"; +static NSComparisonResult +_compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) +{ + static NSNumber *zeroNumber = nil; + NSNumber *modseq1, *modseq2; + + if (!zeroNumber) + { + zeroNumber = [NSNumber numberWithUnsignedLongLong: 0]; + [zeroNumber retain]; + } + + modseq1 = [entry1 objectForKey: @"modseq"]; + if (!modseq1) + modseq1 = zeroNumber; + modseq2 = [entry2 objectForKey: @"modseq"]; + if (!modseq2) + modseq2 = zeroNumber; + + return [modseq1 compare: modseq2]; +} + @interface NGImap4Connection (PrivateMethods) - (NSString *) imap4FolderNameForURL: (NSURL *) url; @@ -291,10 +313,18 @@ static NSString *defaultUserID = @"anyone"; path = [[imap4URL path] stringByDeletingLastPathComponent]; if (![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; - destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme] - host: [imap4URL host] - path: [NSString stringWithFormat: @"%@%@", - path, [newName stringByEncodingImap4FolderName]]]; + + // If new name contains the path - dont't need to add + if ([newName rangeOfString: @"/"].location == NSNotFound) + destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme] + host: [imap4URL host] + path: [NSString stringWithFormat: @"%@%@", + path, [newName stringByEncodingImap4FolderName]]]; + else + destURL = [[NSURL alloc] initWithScheme: [imap4URL scheme] + host: [imap4URL host] + path: [NSString stringWithFormat: @"%@", + [newName stringByEncodingImap4FolderName]]]; [destURL autorelease]; error = [imap4 moveMailboxAtURL: imap4URL toURL: destURL]; @@ -652,8 +682,12 @@ static NSString *defaultUserID = @"anyone"; // Destination folder is in a different account SOGoMailAccounts *accounts; SOGoMailAccount *account; - accounts = [[self container] container]; + SOGoUserFolder *userFolder; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accounts = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; account = [accounts lookupName: [folders objectAtIndex: 1] inContext: localContext acquire: NO]; + if ([account isKindOfClass: [NSException class]]) { result = [NSException exceptionWithHTTPStatus: 500 @@ -796,8 +830,9 @@ static NSString *defaultUserID = @"anyone"; - (NSArray *) fetchUIDs: (NSArray *) _uids parts: (NSArray *) _parts { - return [[self imap4Connection] fetchUIDs: _uids inURL: [self imap4URL] - parts: _parts]; + return [[self imap4Connection] fetchUIDs: _uids + inURL: [self imap4URL] + parts: _parts]; } - (NSArray *) fetchUIDsOfVanishedItems: (uint64_t) modseq @@ -823,7 +858,7 @@ static NSString *defaultUserID = @"anyone"; toFolderURL: [self imap4URL]]; return [NSException exceptionWithHTTPStatus: 502 /* Bad Gateway */ - reason: [NSString stringWithFormat: @"%@ is not an IMAP4 folder", [self relativeImap4Name]]]; + reason: [NSString stringWithFormat: @"%@ is not an IMAP4 folder", [self relativeImap4Name]]]; } - (NSException *) expunge @@ -2001,16 +2036,45 @@ static NSString *defaultUserID = @"anyone"; // // Check updated items // +// . UID FETCH 1:* (UID) (CHANGEDSINCE 1) +// * 1 FETCH (UID 542 MODSEQ (7)) +// * 2 FETCH (UID 553 MODSEQ (14)) +// * 3 FETCH (UID 554 MODSEQ (16)) +// * 4 FETCH (UID 555 MODSEQ (15)) +// * 5 FETCH (UID 559 MODSEQ (17)) +// * 6 FETCH (UID 560 MODSEQ (18)) +// * 7 FETCH (UID 561 MODSEQ (19)) // -// . uid fetch 1:* (UID) (changedsince 171) +// SORT + MODSEQ: http://www.watersprings.org/pub/id/draft-melnikov-condstore-sort-00.txt +// With date, not modseq +// . UID SORT (DATE) UTF-8 (NOT DELETED) (SINCE "15-Mar-2014") +// * SORT 553 542 555 554 601 559 560 561 565 602 603 605 611 610 612 613 614 615 616 617 618 621 619 620 622 623 // +// . UID SORT (DATE) UTF-8 ((MODSEQ 64) (NOT DELETED)) (SINCE "15-Mar-2014") +// * SORT 623 624 (MODSEQ 65) +// . OK Completed (2 msgs in 0.000 secs) +// +// ".. the server MUST also append (to the end of the untagged SORT response) the highest mod-sequence for all messages being returned." +// // To get the modseq of a specific message: // -// . uid fetch 124569:124569 uid (changedsince 1) +// . UID FETCH 124569:124569 (UID MODSEQ) +// * 4900 FETCH (UID 124569 MODSEQ (2)) // // -// Deleted: "UID FETCH 1:* (UID) (CHANGEDSINCE 171 VANISHED)" +// To get deleted messages +// +// . UID FETCH 1:* (UID) (CHANGEDSINCE 1 VANISHED) +// * VANISHED (EARLIER) 1:541,543:552,556:558,562:564,566:600,604,606:609 +// * 1 FETCH (UID 542 MODSEQ (7)) +// * 2 FETCH (UID 553 MODSEQ (14)) +// * 3 FETCH (UID 554 MODSEQ (16)) +// * 4 FETCH (UID 555 MODSEQ (15)) +// * 5 FETCH (UID 559 MODSEQ (17)) +// * 6 FETCH (UID 560 MODSEQ (18)) +// * 7 FETCH (UID 561 MODSEQ (19)) +// // fetchUIDsOfVanishedItems .. // // . uid fetch 1:* (FLAGS) (changedsince 176 vanished) @@ -2027,6 +2091,7 @@ static NSString *defaultUserID = @"anyone"; NSMutableArray *allTokens; NSArray *a, *uids; NSDictionary *d; + id fetchResults; int uidnext, highestmodseq, i; @@ -2053,7 +2118,7 @@ static NSString *defaultUserID = @"anyone"; EOKeyValueQualifier *kvQualifier; NSNumber *nextModseq; - nextModseq = [NSNumber numberWithUnsignedLongLong: highestmodseq + 1]; + nextModseq = [NSNumber numberWithUnsignedLongLong: highestmodseq]; kvQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"modseq" operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo @@ -2071,9 +2136,10 @@ static NSString *defaultUserID = @"anyone"; if (theStartDate) { - EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: - @"(DATE >= %@)", theStartDate]; + EOQualifier *sinceDateQualifier; + sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(DATE >= %@)", theStartDate]; searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: searchQualifier, sinceDateQualifier, nil]; [searchQualifier autorelease]; @@ -2084,24 +2150,28 @@ static NSString *defaultUserID = @"anyone"; uids = [self fetchUIDsMatchingQualifier: searchQualifier sortOrdering: nil]; - for (i = 0; i < [uids count]; i++) - { - // New messages - if ([[uids objectAtIndex: i] intValue] >= uidnext) - { - d = [NSDictionary dictionaryWithObject: @"added" forKey: [uids objectAtIndex: i]]; - } - // Changed messages - else - { - d = [NSDictionary dictionaryWithObject: @"changed" forKey: [uids objectAtIndex: i]]; - } + fetchResults = [(NSDictionary *)[self fetchUIDs: uids + parts: [NSArray arrayWithObject: @"modseq"]] + objectForKey: @"fetch"]; + + /* NOTE: we sort items manually because Cyrus does not properly sort + entries with a MODSEQ of 0 */ + fetchResults + = [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ + context: NULL]; + for (i = 0; i < [fetchResults count]; i++) + { + d = [NSDictionary dictionaryWithObject: [[fetchResults objectAtIndex: i] objectForKey: @"modseq"] + forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]]; [allTokens addObject: d]; } // We fetch deleted ones + if (highestmodseq == 0) + highestmodseq = 1; + if (highestmodseq > 0) { id uid; @@ -2110,8 +2180,8 @@ static NSString *defaultUserID = @"anyone"; for (i = 0; i < [uids count]; i++) { - uid = [uids objectAtIndex: i]; - d = [NSDictionary dictionaryWithObject: @"deleted" forKey: uid]; + uid = [[uids objectAtIndex: i] stringValue]; + d = [NSDictionary dictionaryWithObject: [NSNull null] forKey: uid]; [allTokens addObject: d]; } } diff --git a/SoObjects/Mailer/SOGoMailLabel.h b/SoObjects/Mailer/SOGoMailLabel.h index 54bd2496a..5a64f908d 100644 --- a/SoObjects/Mailer/SOGoMailLabel.h +++ b/SoObjects/Mailer/SOGoMailLabel.h @@ -25,7 +25,7 @@ #import #import -#import "../../UI/SOGoUI/UIxComponent.h"; +#import "../../UI/SOGoUI/UIxComponent.h" @interface SOGoMailLabel : NSObject { diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 4ec55af30..f2fa35eac 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -770,6 +770,9 @@ static BOOL debugSoParts = NO; [mimeType hasPrefix: @"image/"] || [mimeType hasPrefix: @"video/"]) filename = [NSString stringWithFormat: @"unknown_%@", path]; + else if ([mimeType isEqualToString: @"message/rfc822"]) + filename = [NSString stringWithFormat: @"email_%@.eml", path]; + if (filename) { diff --git a/SoObjects/Mailer/Slovak.lproj/Localizable.strings b/SoObjects/Mailer/Slovak.lproj/Localizable.strings index 10bf639be..408877945 100644 --- a/SoObjects/Mailer/Slovak.lproj/Localizable.strings +++ b/SoObjects/Mailer/Slovak.lproj/Localizable.strings @@ -1,2 +1,2 @@ -"SieveFolderName" = "Filtre"; "OtherUsersFolderName" = "Ostatní užívatelia"; +"SharedFoldersName" = "Zdielané adresáre"; diff --git a/SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings b/SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings index 3ed21de7d..df3b61c67 100644 --- a/SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings +++ b/SoObjects/Mailer/SpanishArgentina.lproj/Localizable.strings @@ -1,2 +1,2 @@ -"SieveFolderName" = "Filtros"; "OtherUsersFolderName" = "Otros usuarios"; +"SharedFoldersName" = "Carpetas compartidas"; diff --git a/OpenChange/BSONCodec.h b/SoObjects/SOGo/BSONCodec.h similarity index 100% rename from OpenChange/BSONCodec.h rename to SoObjects/SOGo/BSONCodec.h diff --git a/OpenChange/BSONCodec.m b/SoObjects/SOGo/BSONCodec.m similarity index 100% rename from OpenChange/BSONCodec.m rename to SoObjects/SOGo/BSONCodec.m diff --git a/OpenChange/EOBitmaskQualifier.h b/SoObjects/SOGo/EOBitmaskQualifier.h similarity index 92% rename from OpenChange/EOBitmaskQualifier.h rename to SoObjects/SOGo/EOBitmaskQualifier.h index 405a1f1fa..96b11f035 100644 --- a/OpenChange/EOBitmaskQualifier.h +++ b/SoObjects/SOGo/EOBitmaskQualifier.h @@ -1,8 +1,6 @@ /* EOBitmaskQualifier.h - this file is part of SOGo * - * Copyright (C) 2010-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-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 diff --git a/OpenChange/EOBitmaskQualifier.m b/SoObjects/SOGo/EOBitmaskQualifier.m similarity index 94% rename from OpenChange/EOBitmaskQualifier.m rename to SoObjects/SOGo/EOBitmaskQualifier.m index 14f3eee93..8ee4a0a69 100644 --- a/OpenChange/EOBitmaskQualifier.m +++ b/SoObjects/SOGo/EOBitmaskQualifier.m @@ -1,8 +1,6 @@ /* EOBitmaskQualifier.m - this file is part of SOGo * - * Copyright (C) 2010-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-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 diff --git a/OpenChange/EOQualifier+MAPI.h b/SoObjects/SOGo/EOQualifier+SOGoCacheObject.h similarity index 67% rename from OpenChange/EOQualifier+MAPI.h rename to SoObjects/SOGo/EOQualifier+SOGoCacheObject.h index 9fabef4b9..05fe81204 100644 --- a/OpenChange/EOQualifier+MAPI.h +++ b/SoObjects/SOGo/EOQualifier+SOGoCacheObject.h @@ -1,8 +1,6 @@ -/* EOQualifier+MAPI.h - this file is part of SOGo +/* EOQualifier+SOGoCacheObject.h - this file is part of SOGo * - * Copyright (C) 2010-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-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 @@ -20,17 +18,17 @@ * Boston, MA 02111-1307, USA. */ -#ifndef EOQUALIFIER_MAPI_H -#define EOQUALIFIER_MAPI_H +#ifndef EOQUALIFIER_SOGOCACHEOBJECT_H +#define EOQUALIFIER_SOGOCACHEOBJECT_H #import -@class SOGoMAPIDBObject; +@class SOGoCacheGCSObject; -@interface EOQualifier (MAPIStoreRestrictions) +@interface EOQualifier (SOGoCacheObjectRestrictions) -- (BOOL) evaluateSOGoMAPIDBObject: (SOGoMAPIDBObject *) object; +- (BOOL) evaluateSOGoMAPIDBObject: (SOGoCacheGCSObject *) object; @end -#endif /* EOQUALIFIER_MAPI_H */ +#endif /* EOQUALIFIER_CACHEOBJECT_H */ diff --git a/OpenChange/EOQualifier+MAPI.m b/SoObjects/SOGo/EOQualifier+SOGoCacheObject.m similarity index 83% rename from OpenChange/EOQualifier+MAPI.m rename to SoObjects/SOGo/EOQualifier+SOGoCacheObject.m index 81e77d833..f86f2a11c 100644 --- a/OpenChange/EOQualifier+MAPI.m +++ b/SoObjects/SOGo/EOQualifier+SOGoCacheObject.m @@ -1,8 +1,6 @@ -/* EOQualifier+MAPI.m - this file is part of SOGo +/* EOQualifier+SOGoCacheObject.m - this file is part of SOGo * - * Copyright (C) 2010-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-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 @@ -29,11 +27,11 @@ #import #import "EOBitmaskQualifier.h" -#import "SOGoMAPIDBObject.h" +#import "SOGoCacheGCSObject.h" -#import "EOQualifier+MAPI.h" +#import "EOQualifier+SOGoCacheObject.h" -@implementation EOQualifier (MAPIStoreRestrictions) +@implementation EOQualifier (SOGoCacheObjectRestrictions) - (BOOL) _evaluateSOGoMAPIDBObject: (NSDictionary *) properties { @@ -41,7 +39,7 @@ return NO; } -- (BOOL) evaluateSOGoMAPIDBObject: (SOGoMAPIDBObject *) object +- (BOOL) evaluateSOGoMAPIDBObject: (SOGoCacheGCSObject *) object { NSDictionary *properties; BOOL rc; @@ -58,7 +56,7 @@ @end -@implementation EOAndQualifier (MAPIStoreRestrictionsPrivate) +@implementation EOAndQualifier (SOGoCacheRestrictionsPrivate) - (BOOL) _evaluateSOGoMAPIDBObject: (NSDictionary *) properties { @@ -76,7 +74,7 @@ @end -@implementation EOOrQualifier (MAPIStoreRestrictionsPrivate) +@implementation EOOrQualifier (SOGoCacheObjectRestrictionsPrivate) - (BOOL) _evaluateSOGoMAPIDBObject: (NSDictionary *) properties { @@ -94,7 +92,7 @@ @end -@implementation EONotQualifier (MAPIStoreRestrictionsPrivate) +@implementation EONotQualifier (SOGoCacheObjectRestrictionsPrivate) - (BOOL) _evaluateSOGoMAPIDBObject: (NSDictionary *) properties { @@ -103,7 +101,7 @@ @end -@implementation EOKeyValueQualifier (MAPIStoreRestrictionsPrivate) +@implementation EOKeyValueQualifier (SOGoCacheObjectRestrictionsPrivate) typedef BOOL (*EOComparator) (id, SEL, id); @@ -134,7 +132,7 @@ typedef BOOL (*EOComparator) (id, SEL, id); @end -@implementation EOBitmaskQualifier (MAPIStoreRestrictionsPrivate) +@implementation EOBitmaskQualifier (SOGoCacheObjectRestrictionsPrivate) - (BOOL) _evaluateSOGoMAPIDBObject: (NSDictionary *) properties { diff --git a/OpenChange/GCSSpecialQueries+OpenChange.h b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h similarity index 66% rename from OpenChange/GCSSpecialQueries+OpenChange.h rename to SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h index 4caaca54b..0f8e9c58a 100644 --- a/OpenChange/GCSSpecialQueries+OpenChange.h +++ b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.h @@ -1,8 +1,6 @@ -/* GCSSpecialQueries+OpenChange.h - this file is part of SOGo +/* GCSSpecialQueries+SOGoCacheObject.h - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -20,15 +18,15 @@ * Boston, MA 02111-1307, USA. */ -#ifndef GCSSPECIALQUERIES_OPENCHANGE_H -#define GCSSPECIALQUERIES_OPENCHANGE_H +#ifndef GCSSPECIALQUERIES_SOGOCACHEOBJECT_H +#define GCSSPECIALQUERIES_SOGOCACHEOBJECT_H #import -@interface GCSSpecialQueries (OpenChangeHelpers) +@interface GCSSpecialQueries (SOGoCacheObject) -- (NSString *) createOpenChangeFSTableWithName: (NSString *) tableName; +- (NSString *) createSOGoCacheGCSFolderTableWithName: (NSString *) tableName; @end -#endif /* GCSSPECIALQUERIES_OPENCHANGE_H */ +#endif /* GCSSPECIALQUERIES_SOGOCACHEOBJECT_H */ diff --git a/OpenChange/GCSSpecialQueries+OpenChange.m b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m similarity index 72% rename from OpenChange/GCSSpecialQueries+OpenChange.m rename to SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m index 2e6376b15..b0e9eea52 100644 --- a/OpenChange/GCSSpecialQueries+OpenChange.m +++ b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m @@ -1,8 +1,6 @@ -/* GCSSpecialQueries+OpenChange.m - this file is part of SOGo +/* GCSSpecialQueries+SOGoCacheObject.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -22,22 +20,22 @@ #import -#import "GCSSpecialQueries+OpenChange.h" +#import "GCSSpecialQueries+SOGoCacheObject.h" -@interface GCSPostgreSQLSpecialQueries (OpenChangeHelpers) +@interface GCSPostgreSQLSpecialQueries (SOGoObjectCache) @end -@interface GCSMySQLSpecialQueries (OpenChangeHelpers) +@interface GCSMySQLSpecialQueries (SOGoObjectCache) @end -@interface GCSOracleSpecialQueries (OpenChangeHelpers) +@interface GCSOracleSpecialQueries (SOGoObjectCache) @end -@implementation GCSSpecialQueries (OpenChangeHelpers) +@implementation GCSSpecialQueries (SOGoObjectCache) /* FIXME: c_parent_path should be indexed */ -- (NSString *) createOpenChangeFSTableWithName: (NSString *) tableName +- (NSString *) createSOGoCacheGCSFolderTableWithName: (NSString *) tableName { [self subclassResponsibility: _cmd]; @@ -46,9 +44,9 @@ @end -@implementation GCSPostgreSQLSpecialQueries (OpenChangeHelpers) +@implementation GCSPostgreSQLSpecialQueries (SOGoObjectCache) -- (NSString *) createOpenChangeFSTableWithName: (NSString *) tableName +- (NSString *) createSOGoCacheGCSFolderTableWithName: (NSString *) tableName { static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (" @@ -66,9 +64,9 @@ @end -@implementation GCSMySQLSpecialQueries (OpenChangeHelpers) +@implementation GCSMySQLSpecialQueries (SOGoObjectCache) -- (NSString *) createOpenChangeFSTableWithName: (NSString *) tableName +- (NSString *) createSOGoCacheGCSFolderTableWithName: (NSString *) tableName { static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (" @@ -86,9 +84,9 @@ @end -@implementation GCSOracleSpecialQueries (OpenChangeHelpers) +@implementation GCSOracleSpecialQueries (SOGoObjectCache) -- (NSString *) createOpenChangeFSTableWithName: (NSString *) tableName +- (NSString *) createSOGoCacheGCSFolderTableWithName: (NSString *) tableName { static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (" diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index f9010db09..f1ee391bd 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -15,7 +15,14 @@ SOGo_HEADER_FILES = \ SOGoBuild.h \ SOGoProductLoader.h \ \ + BSONCodec.h \ + EOBitmaskQualifier.h \ + EOQualifier+SOGoCacheObject.h \ + GCSSpecialQueries+SOGoCacheObject.h \ SOGoCache.h \ + SOGoCacheGCSFolder.h \ + SOGoCacheGCSObject.h \ + SOGoCacheObject.h \ SOGoConstants.h \ SOGoObject.h \ SOGoContentObject.h \ @@ -80,7 +87,14 @@ SOGo_OBJC_FILES = \ SOGoBuild.m \ SOGoProductLoader.m \ \ + BSONCodec.m \ + EOBitmaskQualifier.m \ + EOQualifier+SOGoCacheObject.m \ + GCSSpecialQueries+SOGoCacheObject.m \ SOGoCache.m \ + SOGoCacheGCSFolder.m \ + SOGoCacheGCSObject.m \ + SOGoCacheObject.m \ SOGoConstants.m \ SOGoObject.m \ SOGoContentObject.m \ @@ -158,6 +172,9 @@ ifeq ($(saml2_config), yes) SOGoSAML2Exceptions.h SOGoSAML2Exceptions.m: gen-saml2-exceptions.py $(ECHO_CREATING) ./gen-saml2-exceptions.py $(LASSO_CFLAGS) $(END_ECHO) +distclean clean:: + -rm -f SOGoSAML2Exceptions.h SOGoSAML2Exceptions.m + endif ifeq ($(ldap_config),yes) diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 25a9c403d..8d26d34a3 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -46,7 +46,7 @@ static NSString **cssEscapingStrings = NULL; static unichar *cssEscapingCharacters = NULL; static int cssEscapingCount; -static unichar thisCharCode[29]; +static unichar thisCharCode[30]; static NSString *controlCharString = nil; static NSCharacterSet *controlCharSet = nil; @@ -285,8 +285,8 @@ static NSCharacterSet *controlCharSet = nil; int i, j; // Create an array of chars for all control characters between 0x00 and 0x1F, - // apart from \t, \n, \f and \r (0x08, 0x09, 0x0A, 0x0C and 0x0D) - for (i = 0, j = 0x00; j < 0x08; i++, j++) { + // apart from \t, \n, \f and \r (0x09, 0x0A, 0x0C and 0x0D) + for (i = 0, j = 0x00; j <= 0x08; i++, j++) { thisCharCode[i] = j; } thisCharCode[i++] = 0x0B; diff --git a/OpenChange/SOGoMAPIDBFolder.h b/SoObjects/SOGo/SOGoCacheGCSFolder.h similarity index 75% rename from OpenChange/SOGoMAPIDBFolder.h rename to SoObjects/SOGo/SOGoCacheGCSFolder.h index d055291bc..1b63ae683 100644 --- a/OpenChange/SOGoMAPIDBFolder.h +++ b/SoObjects/SOGo/SOGoCacheGCSFolder.h @@ -1,8 +1,6 @@ -/* SOGoMAPIDBFolder.h - this file is part of SOGo +/* SOGoCacheGCSFolder.h - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -20,10 +18,10 @@ * Boston, MA 02111-1307, USA. */ -#ifndef SOGOMAPIDBFOLDER_H -#define SOGOMAPIDBFOLDER_H +#ifndef SOGOCACHEGCSFOLDER_H +#define SOGOCACHEGCSFOLDER_H -#import "SOGoMAPIDBObject.h" +#import "SOGoCacheGCSObject.h" @class NSArray; @class NSMutableString; @@ -32,12 +30,10 @@ @class EOQualifier; -@class SOGoMAPIDBMessage; - -@interface SOGoMAPIDBFolder : SOGoMAPIDBObject +@interface SOGoCacheGCSFolder : SOGoCacheGCSObject { NSString *pathPrefix; /* for root folders */ - SOGoMAPIDBObject *aclMessage; + SOGoCacheGCSObject *aclMessage; } - (void) setPathPrefix: (NSString *) newPathPrefix; @@ -47,7 +43,7 @@ - (NSArray *) toOneRelationshipKeys; - (NSArray *) toManyRelationshipKeys; -- (NSArray *) childKeysOfType: (MAPIDBObjectType) type +- (NSArray *) childKeysOfType: (SOGoCacheObjectType) type includeDeleted: (BOOL) includeDeleted matchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings; @@ -56,4 +52,4 @@ @end -#endif /* SOGOMAPIDBFOLDER_H */ +#endif /* SOGOCACHEGCSFOLDER_H */ diff --git a/OpenChange/SOGoMAPIDBFolder.m b/SoObjects/SOGo/SOGoCacheGCSFolder.m similarity index 90% rename from OpenChange/SOGoMAPIDBFolder.m rename to SoObjects/SOGo/SOGoCacheGCSFolder.m index 1ed8d23ff..f8d25d381 100644 --- a/OpenChange/SOGoMAPIDBFolder.m +++ b/SoObjects/SOGo/SOGoCacheGCSFolder.m @@ -1,8 +1,6 @@ -/* SOGoMAPIDBFolder.m - this file is part of SOGo +/* SOGoCacheGCSFolder.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -33,34 +31,32 @@ #import #import #import -// #import #import #import #import #import -#import "EOQualifier+MAPI.h" -#import "GCSSpecialQueries+OpenChange.h" -#import "SOGoMAPIDBMessage.h" +#import "EOQualifier+SOGoCacheObject.h" +#import "GCSSpecialQueries+SOGoCacheObject.h" -#import "SOGoMAPIDBFolder.h" +#import "SOGoCacheGCSFolder.h" #undef DEBUG -#include -#include -#include -#include -#include -#include -#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include -Class SOGoMAPIDBObjectK = Nil; +Class SOGoCacheGCSObjectK = Nil; -@implementation SOGoMAPIDBFolder +@implementation SOGoCacheGCSFolder + (void) initialize { - SOGoMAPIDBObjectK = [SOGoMAPIDBObject class]; + SOGoCacheGCSObjectK = [SOGoCacheGCSObject class]; } - (id) init @@ -77,10 +73,10 @@ Class SOGoMAPIDBObjectK = Nil; { if ((self = [super initWithName: name inContainer: newContainer])) { - objectType = MAPIDBObjectTypeFolder; - aclMessage = [SOGoMAPIDBObject objectWithName: @"permissions" + objectType = MAPIFolderCacheObject; + aclMessage = [SOGoCacheGCSObject objectWithName: @"permissions" inContainer: self]; - [aclMessage setObjectType: MAPIDBObjectTypeInternal]; + [aclMessage setObjectType: MAPIInternalCacheObject]; [aclMessage retain]; } @@ -135,7 +131,7 @@ Class SOGoMAPIDBObjectK = Nil; // return [SOGoMAPIDBMessage objectWithName: filename inContainer: self]; // } -- (NSArray *) childKeysOfType: (MAPIDBObjectType) type +- (NSArray *) childKeysOfType: (SOGoCacheObjectType) type includeDeleted: (BOOL) includeDeleted matchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings @@ -148,7 +144,7 @@ Class SOGoMAPIDBObjectK = Nil; NSArray *records; NSDictionary *record; NSUInteger childPathPrefixLen, count, max; - SOGoMAPIDBObject *currentChild; + SOGoCacheGCSObject *currentChild; /* query construction */ sql = [NSMutableString stringWithCapacity: 256]; @@ -182,7 +178,7 @@ Class SOGoMAPIDBObjectK = Nil; { if (qualifier) { - currentChild = [SOGoMAPIDBObject objectWithName: childKey + currentChild = [SOGoCacheGCSObject objectWithName: childKey inContainer: self]; [currentChild setupFromRecord: record]; if ([qualifier evaluateSOGoMAPIDBObject: currentChild]) @@ -201,7 +197,7 @@ Class SOGoMAPIDBObjectK = Nil; - (NSArray *) toManyRelationshipKeys { - return [self childKeysOfType: MAPIDBObjectTypeFolder + return [self childKeysOfType: MAPIFolderCacheObject includeDeleted: NO matchingQualifier: nil andSortOrderings: nil]; @@ -209,7 +205,7 @@ Class SOGoMAPIDBObjectK = Nil; - (NSArray *) toOneRelationshipKeys { - return [self childKeysOfType: MAPIDBObjectTypeMessage + return [self childKeysOfType: MAPIMessageCacheObject includeDeleted: NO matchingQualifier: nil andSortOrderings: nil]; @@ -359,10 +355,10 @@ Class SOGoMAPIDBObjectK = Nil; record = [self lookupRecord: childPath newerThanVersion: -1]; if (record) { - if ([[record objectForKey: @"c_type"] intValue] == MAPIDBObjectTypeFolder) + if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject) objectClass = isa; else - objectClass = SOGoMAPIDBObjectK; + objectClass = SOGoCacheGCSObjectK; object = [objectClass objectWithName: childName inContainer: self]; @@ -379,7 +375,7 @@ Class SOGoMAPIDBObjectK = Nil; { id object; - object = [SOGoMAPIDBFolder objectWithName: folderName + object = [SOGoCacheGCSFolder objectWithName: folderName inContainer: self]; [object reloadIfNeeded]; diff --git a/OpenChange/SOGoMAPIDBObject.h b/SoObjects/SOGo/SOGoCacheGCSObject.h similarity index 68% rename from OpenChange/SOGoMAPIDBObject.h rename to SoObjects/SOGo/SOGoCacheGCSObject.h index 6bf1750bb..90f7d1ba1 100644 --- a/OpenChange/SOGoMAPIDBObject.h +++ b/SoObjects/SOGo/SOGoCacheGCSObject.h @@ -1,8 +1,6 @@ -/* SOGoMAPIDBObject.h - this file is part of SOGo +/* SOGoCacheGCSObject.h - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -20,10 +18,10 @@ * Boston, MA 02111-1307, USA. */ -#ifndef SOGOMAPIDBOBJECT_H -#define SOGOMAPIDBOBJECT_H +#ifndef SOGOCACHEGCSOBJECT_H +#define SOGOCACHEGCSOBJECT_H -#import "SOGoMAPIObject.h" +#import "SOGoCacheObject.h" @class NSArray; @class NSMutableDictionary; @@ -34,18 +32,20 @@ @class EOAdaptor; typedef enum { - MAPIDBObjectTypeFolder = 1, - MAPIDBObjectTypeMessage = 2, - MAPIDBObjectTypeFAI = 3, - MAPIDBObjectTypeInternal = 99 /* object = property list */ -} MAPIDBObjectType; + MAPIFolderCacheObject = 1, + MAPIMessageCacheObject = 2, + MAPIFAICacheObject = 3, + MAPIInternalCacheObject = 99, /* object = property list */ + ActiveSyncGlobalCacheObject = 200, + ActiveSyncFolderCacheObject = 201 +} SOGoCacheObjectType; -@interface SOGoMAPIDBObject : SOGoMAPIObject +@interface SOGoCacheGCSObject : SOGoCacheObject { NSURL *tableUrl; BOOL initialized; /* safe guard */ - MAPIDBObjectType objectType; + SOGoCacheObjectType objectType; NSInteger version; BOOL deleted; } @@ -67,8 +67,11 @@ typedef enum { - (NSDictionary *) lookupRecord: (NSString *) path newerThanVersion: (NSInteger) startVersion; -- (void) setObjectType: (MAPIDBObjectType) newObjectType; -- (MAPIDBObjectType) objectType; /* message, fai, folder */ +- (NSArray *) folderList: (NSString *) deviceId + newerThanVersion: (NSInteger) startVersion; + +- (void) setObjectType: (SOGoCacheObjectType) newObjectType; +- (SOGoCacheObjectType) objectType; /* message, fai, folder */ /* automatically set from actions */ - (BOOL) deleted; @@ -82,4 +85,4 @@ typedef enum { @end -#endif /* SOGOMAPIDBOBJECT_H */ +#endif /* SOGOCACHEGCSOBJECT_H */ diff --git a/OpenChange/SOGoMAPIDBObject.m b/SoObjects/SOGo/SOGoCacheGCSObject.m similarity index 80% rename from OpenChange/SOGoMAPIDBObject.m rename to SoObjects/SOGo/SOGoCacheGCSObject.m index 6407fbda3..03269d9d7 100644 --- a/OpenChange/SOGoMAPIDBObject.m +++ b/SoObjects/SOGo/SOGoCacheGCSObject.m @@ -1,8 +1,6 @@ -/* SOGoMAPIDBObject.m - this file is part of SOGo +/* SOGoCacheGCSObject.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -43,38 +41,20 @@ #import #import -#import "GCSSpecialQueries+OpenChange.h" -#import "MAPIStoreTypes.h" -#import "SOGoMAPIDBFolder.h" +#import "GCSSpecialQueries+SOGoCacheObject.h" +#import "SOGoCacheGCSFolder.h" #import "BSONCodec.h" -#import "SOGoMAPIDBObject.h" - -// -// This defines the storage for internal properly list, stored in the -// database. Possible values are: -// -// NSPropertyListGNUstepFormat = 1000 -// NSPropertyListGNUstepBinaryFormat = 1001 -// NSPropertyListOpenStepFormat = 1 -// NSPropertyListXMLFormat_v1_0 = 100 -// NSPropertyListBinaryFormat_v1_0 = 200 -// -static NSPropertyListFormat plistFormat; +#import "SOGoCacheGCSObject.h" static EOAttribute *textColumn = nil; -@implementation SOGoMAPIDBObject +@implementation SOGoCacheGCSObject + (void) initialize { NSDictionary *description; - plistFormat = [[NSUserDefaults standardUserDefaults] integerForKey: @"SOGoPropertyListFormat"]; - - if (!plistFormat) - plistFormat = NSPropertyListGNUstepBinaryFormat; - if (!textColumn) { /* TODO: this is a hack for providing an EOAttribute definition that is @@ -92,19 +72,14 @@ static EOAttribute *textColumn = nil; /* = (@"CREATE TABLE %@ (" @" c_path VARCHAR(255) PRIMARY KEY," - @" c_type VARCHAR(20) NOT NULL," + @" c_parent_path VARCHAR(255)," + @" c_type SMALLINT NOT NULL," @" c_creationdate INT4 NOT NULL," @" c_lastmodified INT4 NOT NULL," @" c_version INT4 NOT NULL DEFAULT 0," @" c_deleted SMALLINT NOT NULL DEFAULT 0," + @" c_content TEXT)"); */ - -/* indexes: - c_path (primary key) - c_counter - c_path, c_type - c_path, c_creationdate */ - - (id) init { if ((self = [super init])) @@ -137,7 +112,7 @@ static EOAttribute *textColumn = nil; tableUrl = [container tableUrl]; [tableUrl retain]; if (!tableUrl) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"table url is not set for object '%@'", self]; } @@ -157,9 +132,8 @@ static EOAttribute *textColumn = nil; - (void) setupFromRecord: (NSDictionary *) record { NSInteger intValue; - NSString *propsValue;//, *error; + NSString *propsValue; NSDictionary *newValues; - //NSPropertyListFormat format; objectType = [[record objectForKey: @"c_type"] intValue]; intValue = [[record objectForKey: @"c_creationdate"] intValue]; @@ -195,7 +169,7 @@ static EOAttribute *textColumn = nil; path = [NSMutableString stringWithFormat: @"/%@", nameInContainer]; if ([path rangeOfString: @"//"].location != NSNotFound) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"object path has not been properly set for" " folder '%@' (%@)", self, path]; @@ -203,12 +177,12 @@ static EOAttribute *textColumn = nil; return path; } -- (void) setObjectType: (MAPIDBObjectType) newObjectType +- (void) setObjectType: (SOGoCacheObjectType) newObjectType { objectType = newObjectType; } -- (MAPIDBObjectType) objectType /* message, fai, folder */ +- (SOGoCacheObjectType) objectType /* message, fai, folder */ { return objectType; } @@ -216,7 +190,7 @@ static EOAttribute *textColumn = nil; - (NSCalendarDate *) creationDate { if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; return creationDate; @@ -225,7 +199,7 @@ static EOAttribute *textColumn = nil; - (NSCalendarDate *) lastModified { if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; return lastModified; @@ -236,43 +210,6 @@ static EOAttribute *textColumn = nil; return deleted; } -- (Class) mapistoreMessageClass -{ - NSString *className, *mapiMsgClass; - - switch (objectType) - { - case MAPIDBObjectTypeMessage: - mapiMsgClass = [properties - objectForKey: MAPIPropertyKey (PidTagMessageClass)]; - if (mapiMsgClass) - { - if ([mapiMsgClass isEqualToString: @"IPM.StickyNote"]) - className = @"MAPIStoreNotesMessage"; - else - className = @"MAPIStoreDBMessage"; - //[self logWithFormat: @"PidTagMessageClass = '%@', returning '%@'", - // mapiMsgClass, className]; - } - else - { - //[self warnWithFormat: @"PidTagMessageClass is not set, falling back" - // @" to 'MAPIStoreDBMessage'"]; - className = @"MAPIStoreDBMessage"; - } - break; - case MAPIDBObjectTypeFAI: - className = @"MAPIStoreFAIMessage"; - break; - default: - [NSException raise: @"MAPIStoreIOException" - format: @"message class should not be queried for objects" - @" of type '%d'", objectType]; - } - - return NSClassFromString (className); -} - /* actions */ - (void) setNameInContainer: (NSString *) newNameInContainer { @@ -413,7 +350,7 @@ static EOAttribute *textColumn = nil; EOAdaptor *adaptor; if ([path hasSuffix: @"/"]) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"path ends with a slash: %@", path]; tableName = [self tableName]; @@ -438,6 +375,47 @@ static EOAttribute *textColumn = nil; return record; } +// get a list of all folders +- (NSArray *) folderList: (NSString *) deviceId + newerThanVersion: (NSInteger) startVersion +{ + NSMutableArray *recordsOut; + NSArray *records; + NSString *tableName, *pathValue; + NSMutableString *sql; + EOAdaptor *adaptor; + NSUInteger count, max; + + if ([deviceId hasSuffix: @"/"]) + [NSException raise: @"SOGoCacheIOException" + format: @"path ends with a slash: %@", deviceId]; + + tableName = [self tableName]; + adaptor = [self tableChannelAdaptor]; + pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@+folder%", deviceId] + forAttribute: textColumn]; + + /* query */ + sql = [NSMutableString stringWithFormat: + @"SELECT * FROM %@ WHERE c_path LIKE %@ AND c_deleted <> 1", + tableName, pathValue]; + if (startVersion > -1) + [sql appendFormat: @" AND c_version > %d", startVersion]; + + /* execution */ + records = [self performSQLQuery: sql]; + + max = [records count]; + recordsOut = [[NSMutableArray alloc] init]; + for (count = 0; count < max; count++) + { + [recordsOut addObject: [[records objectAtIndex: count] objectForKey: @"c_path"]]; + } + + return recordsOut; +} + + - (void) reloadIfNeeded { /* if object is uninitialized: reload without condition, otherwise, load if @@ -491,7 +469,7 @@ static EOAttribute *textColumn = nil; NSException *result; if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; cm = [GCSChannelManager defaultChannelManager]; @@ -503,12 +481,6 @@ static EOAttribute *textColumn = nil; now = [NSCalendarDate date]; ASSIGN (lastModified, now); - /* -- (NSException *)insertRowX:(NSDictionary *)_row forEntity:(EOEntity *)_entity; -- (NSException *)updateRowX:(NSDictionary*)aRow - describedByQualifier:(EOSQLQualifier*)aQualifier; - */ - adaptor = [[channel adaptorContext] adaptor]; pathValue = [adaptor formatValue: [self path] forAttribute: textColumn]; @@ -516,7 +488,7 @@ static EOAttribute *textColumn = nil; lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970]; if (objectType == -1) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"object type has not been set for object '%@'", self]; @@ -567,12 +539,6 @@ static EOAttribute *textColumn = nil; if (result) [self errorWithFormat: @"could not insert/update record for record %@" @" in %@: %@", pathValue, tableName, result]; - // @" c_path VARCHAR(255) PRIMARY KEY," - // @" c_type SMALLINT NOT NULL," - // @" c_creationdate INT4 NOT NULL," - // @" c_lastmodified INT4 NOT NULL," - // @" c_deleted SMALLINT NOT NULL DEFAULT 0," - // @" c_content BLOB"); [cm releaseChannel: channel]; } diff --git a/OpenChange/SOGoMAPIObject.h b/SoObjects/SOGo/SOGoCacheObject.h similarity index 81% rename from OpenChange/SOGoMAPIObject.h rename to SoObjects/SOGo/SOGoCacheObject.h index fbebaf641..0d0a78492 100644 --- a/OpenChange/SOGoMAPIObject.h +++ b/SoObjects/SOGo/SOGoCacheObject.h @@ -1,8 +1,6 @@ -/* SOGoMAPIObject.h - this file is part of SOGo +/* SOGoCacheObject.h - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -20,14 +18,14 @@ * Boston, MA 02111-1307, USA. */ -#ifndef SOGOMAPIOBJECT_H -#define SOGOMAPIOBJECT_H +#ifndef SOGOCACHEOBJECT_H +#define SOGOCACHEOBJECT_H #import @class NSMutableDictionary; -@interface SOGoMAPIObject : SOGoObject +@interface SOGoCacheObject : SOGoObject { BOOL isNew; NSMutableDictionary *properties; @@ -46,4 +44,4 @@ @end -#endif /* SOGOMAPIOBJECT_H */ +#endif /* SOGOCACHEOBJECT_H */ diff --git a/OpenChange/SOGoMAPIObject.m b/SoObjects/SOGo/SOGoCacheObject.m similarity index 89% rename from OpenChange/SOGoMAPIObject.m rename to SoObjects/SOGo/SOGoCacheObject.m index 178202963..877437ab5 100644 --- a/OpenChange/SOGoMAPIObject.m +++ b/SoObjects/SOGo/SOGoCacheObject.m @@ -1,8 +1,6 @@ -/* SOGoMAPIObject.m - this file is part of SOGo +/* SOGoCacgeObject.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-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 @@ -20,12 +18,12 @@ * Boston, MA 02111-1307, USA. */ -#import #import +#import -#import "SOGoMAPIObject.h" +#import "SOGoCacheObject.h" -@implementation SOGoMAPIObject +@implementation SOGoCacheObject - (id) init { diff --git a/SoObjects/SOGo/SOGoConstants.h b/SoObjects/SOGo/SOGoConstants.h index 433760346..830d2bea4 100644 --- a/SoObjects/SOGo/SOGoConstants.h +++ b/SoObjects/SOGo/SOGoConstants.h @@ -51,4 +51,10 @@ typedef enum EventUpdated = 2, } SOGoEventOperation; +typedef enum +{ + SOGoPersonalFolder = 0, + SOGoCollectedFolder = 1, +} SOGoFolderType; + #endif /* _SOGOCONSTANTS_H_ */ diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index 248b653f4..d23fa971a 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -14,6 +14,7 @@ SOGoZipPath = "/usr/bin/zip"; SOGoEncryptionKey = "MySOGoEncryptionKey"; + SOGoSieveFolderEncoding = "UTF-7"; WOUseRelativeURLs = YES; WOMessageUseUTF8 = YES; @@ -53,6 +54,7 @@ SOGoIMAPServer = "localhost"; SOGoMailDomain = "localhost"; + SOGoSelectedAddressBook = "collected"; SOGoMailMessageCheck = "manually"; SOGoMailMessageForwarding = "inline"; SOGoMailReplyPlacement = "below"; @@ -83,4 +85,6 @@ $label4 = ("To Do", "#3333FF"); $label5 = ("Later", "#993399"); }; + + SOGoSubscriptionFolderFormat = "%{FolderName} (%{UserName} <%{Email}>)"; } diff --git a/SoObjects/SOGo/SOGoDomainDefaults.h b/SoObjects/SOGo/SOGoDomainDefaults.h index 694714ae6..a55bb572c 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.h +++ b/SoObjects/SOGo/SOGoDomainDefaults.h @@ -67,6 +67,7 @@ - (NSArray *) calendarDefaultRoles; - (NSArray *) contactsDefaultRoles; - (NSArray *) mailPollingIntervals; +- (NSString *) subscriptionFolderFormat; - (NSString *) calendarDefaultCategoryColor; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.m b/SoObjects/SOGo/SOGoDomainDefaults.m index 98e675ddf..b602e8a9a 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.m +++ b/SoObjects/SOGo/SOGoDomainDefaults.m @@ -1,6 +1,6 @@ /* SOGoDomainDefaults.m - this file is part of SOGo * - * Copyright (C) 2009-2012 Inverse inc. + * Copyright (C) 2009-2014 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -176,6 +176,11 @@ return [self stringArrayForKey: @"SOGoContactsDefaultRoles"]; } +- (NSString *) subscriptionFolderFormat +{ + return [self stringForKey: @"SOGoSubscriptionFolderFormat"]; +} + // // In v2.0.4, SOGoForceIMAPLoginWithEmail was renamed to SOGoForceExternalLoginWithEmail // but we keep backward compatbility for now with previous versions. diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index ec6795bd2..794ebc9ac 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -53,6 +53,7 @@ NSString *ocsPath; GCSFolder *ocsFolder; NSMutableDictionary *childRecords; + NSMutableDictionary *folderSubscriptionValues; BOOL userCanAccessAllObjects; /* i.e. user obtains 'Access Object' on subobjects */ } diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 135a07dc0..3f626bc61 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -1,9 +1,7 @@ /* SOGoGCSFolder.m - this file is part of SOGo * * Copyright (C) 2004-2005 SKYRIX Software AG - * Copyright (C) 2006-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * 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 @@ -198,6 +196,7 @@ static NSArray *childRecordFields = nil; ocsPath = nil; ocsFolder = nil; childRecords = [NSMutableDictionary new]; + folderSubscriptionValues = nil; userCanAccessAllObjects = NO; } @@ -209,6 +208,7 @@ static NSArray *childRecordFields = nil; [ocsFolder release]; [ocsPath release]; [childRecords release]; + [folderSubscriptionValues release]; [super dealloc]; } @@ -277,29 +277,38 @@ static NSArray *childRecordFields = nil; { NSString *primaryDN; NSDictionary *ownerIdentity; - + NSString *subjectFormat; + SOGoDomainDefaults *dd; + primaryDN = [row objectForKey: @"c_foldername"]; if ([primaryDN length]) + { + displayName = [NSMutableString new]; + if ([primaryDN isEqualToString: [container defaultFolderName]]) + [displayName appendString: [self labelForKey: primaryDN + inContext: context]]; + else + [displayName appendString: primaryDN]; + + if (!activeUserIsOwner) { - displayName = [NSMutableString new]; - if ([primaryDN isEqualToString: [container defaultFolderName]]) - [displayName appendString: [self labelForKey: primaryDN - inContext: context]]; - else - [displayName appendString: primaryDN]; + // We MUST NOT use SOGoUser instances here (by calling -primaryIdentity) + // as it'll load user defaults and user settings which is _very costly_ + // since it involves JSON parsing and database requests + ownerIdentity = [[SOGoUserManager sharedUserManager] + contactInfosForUserWithUIDorEmail: owner]; - if (!activeUserIsOwner) - { - // We MUST NOT use SOGoUser instances here (by calling -primaryIdentity) - // as it'll load user defaults and user settings which is _very costly_ - // since it involves JSON parsing and database requests - ownerIdentity = [[SOGoUserManager sharedUserManager] - contactInfosForUserWithUIDorEmail: owner]; - - [displayName appendFormat: @" (%@ <%@>)", [ownerIdentity objectForKey: @"cn"], - [ownerIdentity objectForKey: @"c_email"]]; - } + folderSubscriptionValues = [[NSMutableDictionary alloc] initWithObjectsAndKeys: displayName, @"FolderName", + [ownerIdentity objectForKey: @"cn"], @"UserName", + [ownerIdentity objectForKey: @"c_email"], @"Email", nil]; + + dd = [[context activeUser] domainDefaults]; + subjectFormat = [dd subscriptionFolderFormat]; + + displayName = [folderSubscriptionValues keysWithFormat: subjectFormat]; + [displayName retain]; } + } } /* This method fetches the display name defined by the owner, but is also the @@ -1358,11 +1367,15 @@ static NSArray *childRecordFields = nil; } NSZoneFree (NULL, selectors); - + + /* If we haven't gotten any result to return, let's use the previously + supplied sync-token */ + if (max == 0) + newToken = syncToken; /* If the most recent c_lastmodified is "now", we need to return "now - 1" in order to make sure during the next sync that every records that might get added at the same moment are not lost. */ - if (!newToken || newToken == now) + else if (!newToken || newToken == now) newToken = now - 1; newTokenStr = [NSString stringWithFormat: @"%d", newToken]; diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h index 0413faafd..73ee5deeb 100644 --- a/SoObjects/SOGo/SOGoParentFolder.h +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -1,6 +1,6 @@ /* SOGoParentFolder.h - this file is part of SOGo * - * Copyright (C) 2006-2013 Inverse inc. + * 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 @@ -22,7 +22,9 @@ #define SOGOPARENTFOLDERS_H #import "SOGoFolder.h" +#import "SOGoConstants.h" +@class EOAdaptorChannel; @class NSMutableDictionary; @class NSString; @class WOResponse; @@ -39,6 +41,7 @@ + (Class) subFolderClass; - (NSString *) defaultFolderName; +- (NSString *) collectedFolderName; - (NSException *) appendPersonalSources; - (void) removeSubFolder: (NSString *) subfolderName; @@ -58,6 +61,10 @@ - (id) lookupPersonalFolder: (NSString *) name ignoringRights: (BOOL) ignoreRights; +- (NSException *) fetchSpecialFolders: (NSString *) sql + withChannel: (EOAdaptorChannel *) fc + andFolderType: (SOGoFolderType) folderType; + @end #endif /* SOGOPARENTFOLDERS_H */ diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index da5a1c5b5..4c7c7c31a 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -38,7 +38,7 @@ #import #import #import - +#import #import #import "NSObject+DAV.h" @@ -153,9 +153,15 @@ static SoSecurityManager *sm = nil; return @"Personal"; } -- (void) _createPersonalFolder +- (NSString *) collectedFolderName +{ + return @"Collected"; +} + +- (void) createSpecialFolder: (SOGoFolderType) folderType { NSArray *roles; + NSString *folderName; SOGoGCSFolder *folder; SOGoUser *folderOwner; @@ -165,50 +171,70 @@ static SoSecurityManager *sm = nil; // 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])) { - folder = [subFolderClass objectWithName: @"personal" inContainer: self]; - [folder setDisplayName: [self defaultFolderName]]; - [folder - setOCSPath: [NSString stringWithFormat: @"%@/personal", OCSPath]]; + 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]]; + } + [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]]; + if ([folder create]) - [subFolders setObject: folder forKey: @"personal"]; + [subFolders setObject: folder forKey: folderName]; } } -- (NSException *) _fetchPersonalFolders: (NSString *) sql - withChannel: (EOAdaptorChannel *) fc +- (NSException *) fetchSpecialFolders: (NSString *) sql + withChannel: (EOAdaptorChannel *) fc + andFolderType: (SOGoFolderType) folderType { NSArray *attrs; NSDictionary *row; SOGoGCSFolder *folder; NSString *key; NSException *error; + SOGoUserDefaults *ud; + ud = [[context activeUser] userDefaults]; if (!subFolderClass) subFolderClass = [[self class] subFolderClass]; error = [fc evaluateExpressionX: sql]; if (!error) + { + attrs = [fc describeResults: NO]; + while ((row = [fc fetchAttributes: attrs withZone: NULL])) { - attrs = [fc describeResults: NO]; - while ((row = [fc fetchAttributes: attrs withZone: NULL])) - { - key = [row objectForKey: @"c_path4"]; - if ([key isKindOfClass: [NSString class]]) - { - folder = [subFolderClass objectWithName: key inContainer: self]; - [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", - OCSPath, key]]; - [subFolders setObject: folder forKey: key]; - } - } - - if (![subFolders objectForKey: @"personal"]) - [self _createPersonalFolder]; + key = [row objectForKey: @"c_path4"]; + if ([key isKindOfClass: [NSString class]]) + { + folder = [subFolderClass objectWithName: key inContainer: self]; + [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, key]]; + [subFolders setObject: folder forKey: key]; + } } - + if (folderType == SOGoPersonalFolder) + { + if (![subFolders objectForKey: @"personal"]) + [self createSpecialFolder: SOGoPersonalFolder]; + } + else if (folderType == SOGoCollectedFolder) + { + if (![subFolders objectForKey: @"collected"]) + if ([[ud selectedAddressBook] isEqualToString:@"collected"]) + [self createSpecialFolder: SOGoCollectedFolder]; + } + } return error; } @@ -221,25 +247,21 @@ static SoSecurityManager *sm = nil; NSException *error; cm = [GCSChannelManager defaultChannelManager]; - folderLocation - = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; + folderLocation = [[GCSFolderManager defaultFolderManager] folderInfoLocation]; fc = [cm acquireOpenChannelForURL: folderLocation]; if ([fc isOpen]) - { - gcsFolderType = [[self class] gcsFolderType]; - - sql - = [NSString stringWithFormat: (@"SELECT c_path4 FROM %@" - @" WHERE c_path2 = '%@'" - @" AND c_folder_type = '%@'"), - [folderLocation gcsTableName], - owner, - gcsFolderType]; - error = [self _fetchPersonalFolders: sql withChannel: fc]; - [cm releaseChannel: fc]; -// sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", -// uidColumnName, [self uid]]; - } + { + gcsFolderType = [[self class] gcsFolderType]; + + sql = [NSString stringWithFormat: (@"SELECT c_path4 FROM %@" + @" WHERE c_path2 = '%@'" + @" AND c_folder_type = '%@'"), + [folderLocation gcsTableName], owner, gcsFolderType]; + + error = [self fetchSpecialFolders: sql withChannel: fc andFolderType: SOGoPersonalFolder]; + + [cm releaseChannel: fc]; + } else error = [NSException exceptionWithName: @"SOGoDBException" reason: @"database connection could not be open" @@ -248,6 +270,7 @@ static SoSecurityManager *sm = nil; return error; } + - (NSException *) appendSystemSources { return nil; @@ -388,12 +411,15 @@ static SoSecurityManager *sm = nil; subFolders = [NSMutableDictionary new]; error = [self appendPersonalSources]; if (!error) - error = [self appendSystemSources]; + if ([self respondsToSelector:@selector(appendCollectedSources)]) + error = [self appendCollectedSources]; + if (!error) + error = [self appendSystemSources]; // TODO : Not really a testcase, see function if (error) - { - [subFolders release]; - subFolders = nil; - } + { + [subFolders release]; + subFolders = nil; + } } else error = nil; diff --git a/SoObjects/SOGo/SOGoPermissions.m b/SoObjects/SOGo/SOGoPermissions.m index 9a66d8888..7f9cb56ce 100644 --- a/SoObjects/SOGo/SOGoPermissions.m +++ b/SoObjects/SOGo/SOGoPermissions.m @@ -1,8 +1,6 @@ /* SOGoPermissions.m - this file is part of SOGo * - * Copyright (C) 2006 Inverse inc. - * - * Author: Wolfgang Sourdeau + * 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 diff --git a/SoObjects/SOGo/SOGoSieveManager.m b/SoObjects/SOGo/SOGoSieveManager.m index 765ad32d6..7c335109f 100644 --- a/SoObjects/SOGo/SOGoSieveManager.m +++ b/SoObjects/SOGo/SOGoSieveManager.m @@ -584,7 +584,7 @@ static NSString *sieveScriptName = @"sogo"; int count, max; NSDictionary *currentScript; - sieveScript = [NSMutableString stringWithCapacity: 8192]; + sieveScript = [NSMutableString string]; ASSIGN (requirements, newRequirements); [scriptError release]; @@ -755,7 +755,7 @@ static NSString *sieveScriptName = @"sogo"; return nil; } - return client; + return [client autorelease]; } diff --git a/SoObjects/SOGo/SOGoSystemDefaults.h b/SoObjects/SOGo/SOGoSystemDefaults.h index 3678516ff..bf6032c9b 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -42,6 +42,7 @@ - (BOOL) trustProxyAuthentication; - (NSString *) encryptionKey; - (BOOL) useRelativeURLs; +- (NSString *) sieveFolderEncoding; - (BOOL) isWebAccessEnabled; - (BOOL) isCalendarDAVAccessEnabled; @@ -95,6 +96,7 @@ - (int) maximumPingInterval; - (int) maximumSyncInterval; - (int) internalSyncInterval; +- (int) maximumSyncWindowSize; @end diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index 52a35f868..01150e287 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -356,6 +356,12 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return [self boolForKey: @"WOUseRelativeURLs"]; } +- (NSString *) sieveFolderEncoding +{ + return [self stringForKey: @"SOGoSieveFolderEncoding"]; +} + + - (BOOL) isWebAccessEnabled { return [self boolForKey: @"SOGoWebAccessEnabled"]; @@ -615,4 +621,9 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return v; } +- (int) maximumSyncWindowSize +{ + return [self integerForKey: @"SOGoMaximumSyncWindowSize"]; +} + @end diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index 83d6c18bb..b8df5621b 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -87,6 +87,9 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (NSString *) language; /* mail */ +- (void) setMailAddOutgoingAddresses: (BOOL) newValue; +- (BOOL) mailAddOutgoingAddresses; + - (void) setMailShowSubscribedFoldersOnly: (BOOL) newValue; - (BOOL) mailShowSubscribedFoldersOnly; @@ -111,6 +114,9 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (void) setMailListViewColumnsOrder: (NSArray *) newValue; - (NSArray *) mailListViewColumnsOrder; +- (void) setSelectedAddressBook: (NSString *) newValue; +- (NSString *) selectedAddressBook; + - (void) setMailMessageCheck: (NSString *) newValue; - (NSString *) mailMessageCheck; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index b862c5523..d6d1c3999 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -185,8 +185,8 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; { migratedKeys = [NSDictionary dictionaryWithObjectsAndKeys: - @"SOGoLoginModule", @"SOGoUIxDefaultModule", - @"SOGoLoginModule", @"SOGoDefaultModule", + @"SOGoLoginModule", @"SOGoUIxDefaultModule", + @"SOGoLoginModule", @"SOGoDefaultModule", @"SOGoTimeFormat", @"TimeFormat", @"SOGoShortDateFormat", @"ShortDateFormat", @"SOGoLongDateFormat", @"LongDateFormat", @@ -197,6 +197,7 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; @"SOGoLanguage", @"SOGoDefaultLanguage", @"SOGoLanguage", @"Language", @"SOGoMailComposeMessageType", @"ComposeMessagesType", + @"SOGoSelectedAddressBook", @"SelectedAddressBook", @"SOGoMailMessageCheck", @"MessageCheck", @"SOGoMailMessageForwarding", @"MessageForwarding", @"SOGoMailSignature", @"MailSignature", @@ -384,6 +385,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return userLanguage; } +- (void) setMailAddOutgoingAddresses: (BOOL) newValue +{ + [self setBool: newValue forKey: @"SOGoMailAddOutgoingAddresses"]; +} + +- (BOOL) mailAddOutgoingAddresses +{ + return [self boolForKey: @"SOGoMailAddOutgoingAddresses"]; +} + - (void) setMailShowSubscribedFoldersOnly: (BOOL) newValue { [self setBool: newValue forKey: @"SOGoMailShowSubscribedFoldersOnly"]; @@ -467,6 +478,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self stringArrayForKey: @"SOGoMailListViewColumnsOrder"]; } +- (void) setSelectedAddressBook:(NSString *) newValue +{ + [self setObject: newValue forKey: @"SOGoSelectedAddressBook"]; +} + +- (NSString *) selectedAddressBook +{ + return [self stringForKey: @"SOGoSelectedAddressBook"]; +} + - (void) setMailMessageCheck: (NSString *) newValue { [self setObject: newValue forKey: @"SOGoMailMessageCheck"]; diff --git a/SoObjects/SOGo/SOGoUserSettings.h b/SoObjects/SOGo/SOGoUserSettings.h index 673f148c8..25de5aaea 100644 --- a/SoObjects/SOGo/SOGoUserSettings.h +++ b/SoObjects/SOGo/SOGoUserSettings.h @@ -1,6 +1,6 @@ /* SOGoUserSettings.h - this file is part of SOGo * - * Copyright (C) 2009-2013 Inverse inc. + * Copyright (C) 2009-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 @@ -34,13 +34,6 @@ - (NSArray *) subscribedCalendars; - (NSArray *) subscribedAddressBooks; - -/* Microsoft Active Sync support */ -- (void) setMicrosoftActiveSyncMetadata: (NSDictionary *) theMetadata - forDevice: (NSString *) theDeviceID; - -- (NSMutableDictionary *) microsoftActiveSyncMetadataForDevice: (NSString *) theDevice; - @end #endif /* SOGOUSERSETTINGS_H */ diff --git a/SoObjects/SOGo/SOGoUserSettings.m b/SoObjects/SOGo/SOGoUserSettings.m index 107b5d835..cca5f78ac 100644 --- a/SoObjects/SOGo/SOGoUserSettings.m +++ b/SoObjects/SOGo/SOGoUserSettings.m @@ -1,6 +1,6 @@ /* SOGoUserSettings.m - this file is part of SOGo * - * Copyright (C) 2009-2013 Inverse inc. + * Copyright (C) 2009-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 @@ -69,28 +69,4 @@ static Class SOGoUserProfileKlass = Nil; return [self _subscribedFoldersForModule: @"Contacts"]; } -/* Microsoft Active Sync support */ -- (void) setMicrosoftActiveSyncMetadata: (NSDictionary *) theMetadata - forDevice: (NSString *) theDeviceID -{ - if (theMetadata && theDeviceID) - { - NSMutableDictionary *d; - - d = [NSMutableDictionary dictionaryWithDictionary: [self dictionaryForKey: @"SOGoMicrosoftActiveSyncMetadata"]]; - [d setObject: theMetadata forKey: theDeviceID]; - - [self setObject: d forKey: @"SOGoMicrosoftActiveSyncMetadata"]; - } -} - -- (NSMutableDictionary *) microsoftActiveSyncMetadataForDevice: (NSString *) theDevice -{ - NSDictionary *d; - - d = [self dictionaryForKey: @"SOGoMicrosoftActiveSyncMetadata"]; - - return [NSMutableDictionary dictionaryWithDictionary: [d objectForKey: theDevice]]; -} - @end diff --git a/SoObjects/SOGo/WOResponse+SOGo.m b/SoObjects/SOGo/WOResponse+SOGo.m index b9768a29d..282a264fe 100644 --- a/SoObjects/SOGo/WOResponse+SOGo.m +++ b/SoObjects/SOGo/WOResponse+SOGo.m @@ -1,8 +1,6 @@ /* WOResponse+SOGo.m - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-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 diff --git a/Tests/Integration/test-webdavsync.py b/Tests/Integration/test-webdavsync.py index e28048ddc..e84288136 100755 --- a/Tests/Integration/test-webdavsync.py +++ b/Tests/Integration/test-webdavsync.py @@ -51,7 +51,7 @@ class WebdavSyncTest(unittest.TestCase): # non-empty numerical value. Anything else will trigger an exception token = int(token_node.text) - self.assertTrue(token > 0) + self.assertTrue(token >= 0) query1EndTime = int(math.ceil(query1.start + query1.duration)) self.assertTrue(token <= query1EndTime, "token = %d > query1EndTime = %d" % (token, query1EndTime)) diff --git a/Tests/Unit/GNUmakefile b/Tests/Unit/GNUmakefile index a7a8376bf..316e5cf4e 100644 --- a/Tests/Unit/GNUmakefile +++ b/Tests/Unit/GNUmakefile @@ -42,5 +42,5 @@ ADDITIONAL_LDFLAGS += -Wl,--no-as-needed include $(GNUSTEP_MAKEFILES)/test-tool.make -include GNUmakefile.postamble -check :: +check :: $(TEST_TOOL) ./obj/sogo-tests diff --git a/Tests/Unit/TestVersit.m b/Tests/Unit/TestVersit.m index 41cb7e67a..92ddf02bb 100644 --- a/Tests/Unit/TestVersit.m +++ b/Tests/Unit/TestVersit.m @@ -37,6 +37,7 @@ CardElement *element; CardVersitRenderer *renderer; NSString *result; + NSString *error; renderer = [CardVersitRenderer new]; [renderer autorelease]; @@ -78,7 +79,11 @@ [element setSingleValue: @"1,2,3" forKey: @"named2"]; [element setSingleValue: @"text1;text2" forKey: @"named3"]; result = [renderer render: element]; - testEquals(result, @"ELEM:NAMED1=1,2,3;NAMED2=1\\,2\\,3;NAMED3=text1\\;text2\r\n"); + error = [NSString stringWithFormat: @"string '%@' elements not the same as in 'ELEM:NAMED1=1,2,3;NAMED2=1\\,2\\,3;NAMED3=text1\\;text2'", + result]; + testWithMessage([result isEqual: @"ELEM:NAMED1=1,2,3;NAMED2=1\\,2\\,3;NAMED3=text1\\;text2\r\n"] + || [result isEqual: @"ELEM:NAMED3=text1\\;text2;NAMED1=1,2,3;NAMED2=1\\,2\\,3\r\n"], + error); /* 6. values with 1 ordered value with a whitespace starting subvalues */ element = [CardElement elementWithTag: @"elem"]; @@ -146,10 +151,10 @@ element = [group firstChildWithTag: @"element"]; testEquals([element flattenedValueAtIndex: 0 forKey: @""], @"value"); - versit = @"BEGIN:GROUP1\r\nELEMENT:value1;value2\r\nEND:GROUP1"; + versit = @"BEGIN:GROUP1\r\nN:value1;value2\r\nEND:GROUP1"; group = [CardGroup parseSingleFromSource: versit]; testEquals([group versitString], versit); - element = [group firstChildWithTag: @"element"]; + element = [group firstChildWithTag: @"n"]; testEquals([element flattenedValueAtIndex: 0 forKey: @""], @"value1"); testEquals([element flattenedValueAtIndex: 1 forKey: @""], @"value2"); @@ -165,12 +170,12 @@ element = [group firstChildWithTag: @"element"]; testEquals([element valuesAtIndex: 0 forKey: @""], ([NSArray arrayWithObjects: @"value", @"with comma", nil])); - versit = @"BEGIN:GROUP1\r\nELEMENT:NAMED1=subvalue;NAMED2=subvalue1,subvalue2\r\nEND:GROUP1"; + versit = @"BEGIN:GROUP1\r\nN:NAMED1=subvalue;NAMED2=subvalue1,subvalue2\r\nEND:GROUP1"; group = [CardGroup parseSingleFromSource: versit]; /* we avoid this test here as nothing guarantees that the order of named values will be preserved... */ // testEquals([group versitString], versit); - element = [group firstChildWithTag: @"element"]; + element = [group firstChildWithTag: @"n"]; testEquals([element flattenedValueAtIndex: 0 forKey: @"NAMED1"], @"subvalue"); testEquals([element valuesAtIndex: 0 forKey: @"named2"], ([NSArray arrayWithObjects: @"subvalue1", @"subvalue2", nil])); diff --git a/UI/Common/Hungarian.lproj/Localizable.strings b/UI/Common/Hungarian.lproj/Localizable.strings index e7cad04e0..4a337cbb8 100644 --- a/UI/Common/Hungarian.lproj/Localizable.strings +++ b/UI/Common/Hungarian.lproj/Localizable.strings @@ -29,7 +29,7 @@ "Add..." = "Hozzáadás..."; "Remove" = "Törlés"; -"Subscribe User" = "Felíratkozott felhasználók"; +"Subscribe User" = "Felhasználót felirathat"; "Any Authenticated User" = "Bejelentkezett felhasználók"; "Public Access" = "Nyilvános hozzáférés"; @@ -103,10 +103,8 @@ "Location:" = "Hely:"; /* mail labels */ -/* Mail labels */ "Important" = "Fontos"; "Work" = "Hivatalos"; -"Work" = "Hivatalos"; "Personal" = "Személyes"; "To Do" = "Teendő"; "Later" = "Később"; diff --git a/UI/Common/SpanishArgentina.lproj/Localizable.strings b/UI/Common/SpanishArgentina.lproj/Localizable.strings index 2f655bd7e..11ddb4d91 100644 --- a/UI/Common/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Common/SpanishArgentina.lproj/Localizable.strings @@ -69,6 +69,7 @@ "You cannot create a list in a shared address book." = "No es posible crear una lista en una libreta de direcciones compartida."; "Warning" = "Atención"; +"Can't contact server" = "Ha ocurrido un error mientras se contactaba al servidor. Por favor intente de nuevo"; "You are not allowed to access this module or this system. Please contact your system administrator." = "No esta permitido el acceso a este módulo o éste sistema. Por favor contacte con su administrador de sistemas."; @@ -101,10 +102,9 @@ "Due Date:" = "Vencimiento:"; "Location:" = "Lugar:"; -/* Mail labels */ +/* mail labels */ "Important" = "Importante"; "Work" = "Trabajo"; -"Work" = "Trabajo"; "Personal" = "Personal"; "To Do" = "Por hacer"; "Later" = "Más tarde"; diff --git a/UI/Common/UIxToolbar.m b/UI/Common/UIxToolbar.m index c9cc5d866..b4376b311 100644 --- a/UI/Common/UIxToolbar.m +++ b/UI/Common/UIxToolbar.m @@ -35,7 +35,7 @@ { NSArray *toolbarConfig; NSArray *toolbarGroup; - NSString *toolbar; + NSString *toolbar; NSDictionary *buttonInfo; } @@ -60,7 +60,7 @@ [toolbarGroup release]; [toolbarConfig release]; [buttonInfo release]; - [toolbar release]; + [toolbar release]; [super dealloc]; } diff --git a/UI/Contacts/Czech.lproj/Localizable.strings b/UI/Contacts/Czech.lproj/Localizable.strings index 90d7e821b..d420fbf84 100644 --- a/UI/Contacts/Czech.lproj/Localizable.strings +++ b/UI/Contacts/Czech.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "smazat"; "edit" = "upravit"; "invalidemailwarn" = "Specifikovaný e-mail je neplatný"; -"invaliddatewarn" = "The specified date is invalid."; "new" = "nový"; "Preferred Phone" = "Upřednostňovaný telefon"; @@ -64,6 +63,7 @@ "New Card" = "Nový kontakt"; "New List" = "Nová skupina"; +"Edit" = "Upravit"; "Properties" = "Vlastnosti"; "Sharing..." = "Sdílení..."; "Write" = "Napsat"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Nemůžete smazat kontakt \"%{0}\"."; -"Address Book Name" = "Název adresáře"; + "You cannot subscribe to a folder that you own!" = "Nemůžete se přihlásit k odebírání vlastní složky! "; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Do adresáře bylo importováno %{0} kontaktů."; "Reload" = "Aktualizovat"; + +/* Properties window */ +"Address Book Name:" = "Název adresáře:"; +"Links to this Address Book" = "Odkazy na tento adresář"; +"Authenticated User Access" = "Přístup pro ověřené uživatele"; +"CardDAV URL: " = "CardDAV URL:"; + diff --git a/UI/Contacts/English.lproj/Localizable.strings b/UI/Contacts/English.lproj/Localizable.strings index 5c2239220..39da819e1 100644 --- a/UI/Contacts/English.lproj/Localizable.strings +++ b/UI/Contacts/English.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "delete"; "edit" = "edit"; "invalidemailwarn" = "The specified email is invalid"; -"invaliddatewarn" = "The specified date is invalid."; "new" = "new"; "Preferred Phone" = "Preferred Phone"; @@ -64,6 +63,7 @@ "New Card" = "New Card"; "New List" = "New List"; +"Edit" = "Edit"; "Properties" = "Properties"; "Sharing..." = "Sharing..."; "Write" = "Write"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "You cannot delete the card of \"%{0}\"."; -"Address Book Name" = "Address Book Name"; + "You cannot subscribe to a folder that you own!" = "You cannot subscribe to a folder that you own."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "A total of %{0} cards were imported in the addressbook."; "Reload" = "Reload"; + +/* Properties window */ +"Address Book Name:" = "Address Book Name:"; +"Links to this Address Book" = "Links to this Address Book"; +"Authenticated User Access" = "Authenticated User Access"; +"CardDAV URL: " = "CardDAV URL: "; + diff --git a/UI/Contacts/French.lproj/Localizable.strings b/UI/Contacts/French.lproj/Localizable.strings index 31a269473..f321d9b56 100644 --- a/UI/Contacts/French.lproj/Localizable.strings +++ b/UI/Contacts/French.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "Effacer"; "edit" = "Éditer"; "invalidemailwarn" = "Le champ de l'adresse électronique est invalide"; -"invaliddatewarn" = "La date spécifiée est invalide."; "new" = "Nouveau"; "Preferred Phone" = "Numéro préféré"; @@ -64,6 +63,7 @@ "New Card" = "Nouvelle fiche"; "New List" = "Nouvelle liste"; +"Edit" = "Éditer"; "Properties" = "Modifier"; "Sharing..." = "Partage..."; "Write" = "Écrire"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Vous ne pouvez pas supprimer la fiche de \"%{0}\"."; -"Address Book Name" = "Nom du carnet d'adresses"; + "You cannot subscribe to a folder that you own!" = "Vous ne pouvez pas vous inscrire à un dossier qui vous appartient."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Un total de %{0} contacts ont été importés dans le carnet."; "Reload" = "Actualiser"; + +/* Properties window */ +"Address Book Name:" = "Nom :"; +"Links to this Address Book" = "Liens vers ce carnet"; +"Authenticated User Access" = "Accès aux utilisateurs authentifiés"; +"CardDAV URL: " = "Accès en CardDAV :"; + diff --git a/UI/Contacts/GNUmakefile b/UI/Contacts/GNUmakefile index b29b0373d..6e32c50db 100644 --- a/UI/Contacts/GNUmakefile +++ b/UI/Contacts/GNUmakefile @@ -22,7 +22,8 @@ ContactsUI_OBJC_FILES = \ UIxListEditor.m \ UIxContactsListActions.m \ UIxContactFoldersView.m \ - UIxContactFolderActions.m + UIxContactFolderActions.m \ + UIxContactFolderProperties.m ContactsUI_RESOURCE_FILES += \ product.plist \ diff --git a/UI/Contacts/Hungarian.lproj/Localizable.strings b/UI/Contacts/Hungarian.lproj/Localizable.strings index afe91a3f3..89e73d499 100644 --- a/UI/Contacts/Hungarian.lproj/Localizable.strings +++ b/UI/Contacts/Hungarian.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "Töröl"; "edit" = "Szerkeszt"; "invalidemailwarn" = "A megadott email cím érvénytelen"; -"invaliddatewarn" = "A megadott dátum érvénytelen."; "new" = "új"; "Preferred Phone" = "Preferált telefon"; @@ -64,6 +63,7 @@ "New Card" = "Új névjegy"; "New List" = "Új csoportlista"; +"Edit" = "Szerkesztés"; "Properties" = "Tulajdonságok"; "Sharing..." = "Megosztás..."; "Write" = "Üzenet írása"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Az alábbi névjegy nem törölhető: \"%{0}\"."; -"Address Book Name" = "Címjegyzék neve"; + "You cannot subscribe to a folder that you own!" = "Saját tulajdonú mappára nem lehet feliratkozni."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Az összes %{0} névjegy importálástra került a címjegyzékbe."; "Reload" = "Újratöltés"; + +/* Properties window */ +"Address Book Name:" = "Címjegyzék neve:"; +"Links to this Address Book" = "Hivatkozások erre a címjegyzékre"; +"Authenticated User Access" = "Bejelentkezett felhasználó hozzáférése"; +"CardDAV URL: " = "CardDAV URL:"; + diff --git a/UI/Contacts/Polish.lproj/Localizable.strings b/UI/Contacts/Polish.lproj/Localizable.strings index d8ec02965..012134657 100644 --- a/UI/Contacts/Polish.lproj/Localizable.strings +++ b/UI/Contacts/Polish.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "usuń"; "edit" = "edytuj"; "invalidemailwarn" = "Podany adres e-mail jest nieprawidłowy"; -"invaliddatewarn" = "Podana data jest nieprawidłowa."; "new" = "nowy"; "Preferred Phone" = "Preferowany telefon"; @@ -64,6 +63,7 @@ "New Card" = "Nowy kontakt"; "New List" = "Nowa lista"; +"Edit" = "Edytuj"; "Properties" = "Właściwości"; "Sharing..." = "Udostępnianie"; "Write" = "Napisz"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Nie możesz skasować kontaktu do \"%{0}\"."; -"Address Book Name" = "Nazwa książki adresowej"; + "You cannot subscribe to a folder that you own!" = "Nie możesz zrezygnować z subskrypcji foldera, którego jesteś właścicielem."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Liczba wszystkich kontaktów zaimportowanych do książki adresowej: %{0}."; "Reload" = "Przeładuj"; + +/* Properties window */ +"Address Book Name:" = "Nazwa książki adresowej:"; +"Links to this Address Book" = "Linki do tej książki adresowej"; +"Authenticated User Access" = "Dostęp dla zalogowanych użytkowników"; +"CardDAV URL: " = "CardDAV URL:"; + diff --git a/UI/Contacts/Russian.lproj/Localizable.strings b/UI/Contacts/Russian.lproj/Localizable.strings index b04d20f0e..8653d9cab 100644 --- a/UI/Contacts/Russian.lproj/Localizable.strings +++ b/UI/Contacts/Russian.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "удалить"; "edit" = "редактировать"; "invalidemailwarn" = "The specified email is invalid"; -"invaliddatewarn" = "Указанная дата является неверной."; "new" = "новый"; "Preferred Phone" = "Preferred Phone"; @@ -64,6 +63,7 @@ "New Card" = "Новый контакт"; "New List" = "Новый список"; +"Edit" = "Изменить"; "Properties" = "Свойства"; "Sharing..." = "Общий доступ..."; "Write" = "Записать"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Вы не можете удалить запись \"%{0}\"."; -"Address Book Name" = "Название адресной книги"; + "You cannot subscribe to a folder that you own!" = "Вы не можете подписаться на папку, которой владеете."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Всего %{0} записей было импортировано в адресную книгу."; "Reload" = "Перезагрузить"; + +/* Properties window */ +"Address Book Name:" = "Название адресной книги:"; +"Links to this Address Book" = "Ссылки на эту адресную книгу"; +"Authenticated User Access" = "Доступ авторизированных пользователей"; +"CardDAV URL: " = "CardDAV URL: "; + diff --git a/UI/Contacts/Slovak.lproj/Localizable.strings b/UI/Contacts/Slovak.lproj/Localizable.strings index 00c9c1bc9..2aa2e901c 100644 --- a/UI/Contacts/Slovak.lproj/Localizable.strings +++ b/UI/Contacts/Slovak.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "zmazať"; "edit" = "editovať"; "invalidemailwarn" = "Zvolený email je neplatný"; -"invaliddatewarn" = "Zvolený dátum je neplatný"; "new" = "nový"; "Preferred Phone" = "Preferovaný telefón"; @@ -64,6 +63,7 @@ "New Card" = "Nová vizitka"; "New List" = "Nový zoznam"; +"Edit" = "Upraviť"; "Properties" = "Možnosti"; "Sharing..." = "Zdielanie..."; "Write" = "Napíš"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "Nemôžete odstrániť vizitku \"%{0}\"."; -"Address Book Name" = "Meno adresára"; + "You cannot subscribe to a folder that you own!" = "Nemôžete sa prihlásiť na odber svojej vlastnej zložky."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Dokopy bolo do adresára importovaných %{0} vizitiek."; "Reload" = "Znovu načítaj"; + +/* Properties window */ +"Address Book Name:" = "Meno adresára"; +"Links to this Address Book" = "Odkazy k tomuto adresáru"; +"Authenticated User Access" = "Prístup pre overeného užívateľa"; +"CardDAV URL: " = "CardDAV url:"; + diff --git a/UI/Contacts/SpanishArgentina.lproj/Localizable.strings b/UI/Contacts/SpanishArgentina.lproj/Localizable.strings index c6f890d78..3adb1eafa 100644 --- a/UI/Contacts/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Contacts/SpanishArgentina.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "borrar"; "edit" = "modificar"; "invalidemailwarn" = "La dirección de correo indicada no es válida."; -"invaliddatewarn" = "La fecha especificada no es válida."; "new" = "nuevo"; "Preferred Phone" = "Teléfono preferido"; @@ -64,6 +63,7 @@ "New Card" = "Crear contacto"; "New List" = "Crear lista"; +"Edit" = "Editar"; "Properties" = "Modificar"; "Sharing..." = "Compartir..."; "Write" = "Redactar"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "No puede borrar el contacto de \"%{0}\"."; -"Address Book Name" = "Nombre de la libreta de direcciones"; + "You cannot subscribe to a folder that you own!" = "No puede suscribirse a una carpeta que es suya."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Un total de %{0} contactos han sido importados a la libreta de direcciones."; "Reload" = "Recargar"; + +/* Properties window */ +"Address Book Name:" = "Nombre de la libreta de direcciones:"; +"Links to this Address Book" = "Enlaces a esta libreta de direcciones"; +"Authenticated User Access" = "Acceso a usuarios autenticados"; +"CardDAV URL: " = "URL CardDAV:"; + diff --git a/UI/Contacts/SpanishSpain.lproj/Localizable.strings b/UI/Contacts/SpanishSpain.lproj/Localizable.strings index e326d624b..a8ba01958 100644 --- a/UI/Contacts/SpanishSpain.lproj/Localizable.strings +++ b/UI/Contacts/SpanishSpain.lproj/Localizable.strings @@ -36,7 +36,6 @@ "delete" = "borrar"; "edit" = "modificar"; "invalidemailwarn" = "La dirección de correo indicada no es válida."; -"invaliddatewarn" = "La fecha especificada no es válida."; "new" = "nuevo"; "Preferred Phone" = "Teléfono preferido"; @@ -64,6 +63,7 @@ "New Card" = "Crear contacto"; "New List" = "Crear lista"; +"Edit" = "Modificar"; "Properties" = "Modificar"; "Sharing..." = "Compartir..."; "Write" = "Redactar"; @@ -147,7 +147,7 @@ "You cannot delete the card of \"%{0}\"." = "No puede borrar el contacto de \"%{0}\"."; -"Address Book Name" = "Nombre de la libreta de direcciones"; + "You cannot subscribe to a folder that you own!" = "No puede suscribirse a una carpeta que es suya."; @@ -206,3 +206,10 @@ "A total of %{0} cards were imported in the addressbook." = "Un total de %{0} contactos han sido importados a la libreta de direcciones."; "Reload" = "Recargar"; + +/* Properties window */ +"Address Book Name:" = "Nombre de la Libreta de Direcciones:"; +"Links to this Address Book" = "Vinculos para esta Libreta de Direcciones"; +"Authenticated User Access" = "Acceso para usuario autenticado"; +"CardDAV URL: " = "URl CardDAV:"; + diff --git a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar index 8a3a99286..4f85f8f51 100644 --- a/UI/Contacts/Toolbars/SOGoContactFolder.toolbar +++ b/UI/Contacts/Toolbars/SOGoContactFolder.toolbar @@ -14,7 +14,7 @@ ), ( { link = "#"; - label = "Properties"; + label = "Edit"; onclick = "return onToolbarEditSelectedContacts(this);"; image = "properties.png"; tooltip = "Edit the selected card"; }, diff --git a/UI/Contacts/UIxContactFolderProperties.h b/UI/Contacts/UIxContactFolderProperties.h new file mode 100644 index 000000000..9d8fd6a94 --- /dev/null +++ b/UI/Contacts/UIxContactFolderProperties.h @@ -0,0 +1,35 @@ +/* UIxContactFolderProperties.h - this file is part of SOGo + * + * Copyright (C) 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +@class NSString; + +@class SOGoContactGCSFolder; + +@interface UIxContactFolderProperties : UIxComponent +{ + SOGoContactGCSFolder *addressBook; + NSString *baseCardDAVURL, *basePublicCardDAVURL; +} + +- (NSString *) addressBookName; + +@end diff --git a/UI/Contacts/UIxContactFolderProperties.m b/UI/Contacts/UIxContactFolderProperties.m new file mode 100644 index 000000000..391d82f3d --- /dev/null +++ b/UI/Contacts/UIxContactFolderProperties.m @@ -0,0 +1,102 @@ +/* UIxContactFolderProperties.m - this file is part of SOGo + * + * Copyright (C) 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import "UIxContactFolderProperties.h" + +@implementation UIxContactFolderProperties + +- (id) init +{ + if ((self = [super init])) + { + addressBook = [self clientObject]; + baseCardDAVURL = nil; + basePublicCardDAVURL = nil; + } + + return self; +} + +- (void) dealloc +{ + [baseCardDAVURL release]; + [basePublicCardDAVURL release]; + [super dealloc]; +} + +- (NSString *) addressBookName +{ + return [addressBook displayName]; +} + +- (NSString *) _baseCardDAVURL +{ + NSString *davURL; + + if (!baseCardDAVURL) + { + davURL = [[addressBook realDavURL] absoluteString]; + if ([davURL hasSuffix: @"/"]) + baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1]; + else + baseCardDAVURL = davURL; + [baseCardDAVURL retain]; + } + + return baseCardDAVURL; +} + +- (NSString *) cardDavURL +{ + return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]]; +} + +- (NSString *) _basePublicCardDAVURL +{ + NSString *davURL; + + if (!basePublicCardDAVURL) + { + davURL = [[addressBook publicDavURL] absoluteString]; + if ([davURL hasSuffix: @"/"]) + basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1]; + else + basePublicCardDAVURL = davURL; + [basePublicCardDAVURL retain]; + } + + return basePublicCardDAVURL; +} + +- (NSString *) publicCardDavURL +{ + return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]]; +} + +- (BOOL) isPublicAccessEnabled +{ + // NOTE: This method is the same found in Common/UIxAclEditor.m + return [[SOGoSystemDefaults sharedSystemDefaults] + enablePublicAccess]; +} + +@end diff --git a/UI/Contacts/UIxContactFoldersView.m b/UI/Contacts/UIxContactFoldersView.m index f38a3532f..aaf35d6c2 100644 --- a/UI/Contacts/UIxContactFoldersView.m +++ b/UI/Contacts/UIxContactFoldersView.m @@ -158,88 +158,23 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK; - (id ) allContactSearchAction { id result; - SOGoFolder *folder; - NSString *searchText, *mail, *domain; + NSString *searchText; NSDictionary *data; - NSArray *folders, *contacts, *descriptors, *sortedContacts; - NSMutableArray *sortedFolders; - NSMutableDictionary *contact, *uniqueContacts; - unsigned int i, j, max; - NSSortDescriptor *commonNameDescriptor; + NSArray *sortedContacts; + BOOL excludeGroups, excludeLists; searchText = [self queryParameterForKey: @"search"]; if ([searchText length] > 0) { - // NSLog(@"Search all contacts: %@", searchText); excludeGroups = [[self queryParameterForKey: @"excludeGroups"] boolValue]; excludeLists = [[self queryParameterForKey: @"excludeLists"] boolValue]; - domain = [[context activeUser] domain]; - folders = nil; - NS_DURING - folders = [[self clientObject] subFolders]; - NS_HANDLER - /* We need to specifically test for @"SOGoDBException", which is - raised explicitly in SOGoParentFolder. Any other exception should - be re-raised. */ - if ([[localException name] isEqualToString: @"SOGoDBException"]) - folders = nil; - else - [localException raise]; - NS_ENDHANDLER; - max = [folders count]; - sortedFolders = [NSMutableArray arrayWithCapacity: max]; - uniqueContacts = [NSMutableDictionary dictionary]; - for (i = 0; i < max; i++) - { - folder = [folders objectAtIndex: i]; - /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ - if ([folder isKindOfClass: SOGoContactSourceFolderK]) - [sortedFolders insertObject: folder atIndex: 0]; - else - [sortedFolders addObject: folder]; - } - for (i = 0; i < max; i++) - { - folder = [sortedFolders objectAtIndex: i]; - //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); - contacts = [folder lookupContactsWithFilter: searchText - onCriteria: @"name_or_address" - sortBy: @"c_cn" - ordering: NSOrderedAscending - inDomain: domain]; - for (j = 0; j < [contacts count]; j++) - { - contact = [contacts objectAtIndex: j]; - mail = [contact objectForKey: @"c_mail"]; - //NSLog(@" found %@ (%@) ? %@", [contact objectForKey: @"c_name"], mail, - // [contact description]); - if (!excludeLists && [[contact objectForKey: @"c_component"] - isEqualToString: @"vlist"]) - { - [contact setObject: [folder nameInContainer] - forKey: @"container"]; - [uniqueContacts setObject: contact - forKey: [contact objectForKey: @"c_name"]]; - } - else if ([mail length] - && [uniqueContacts objectForKey: mail] == nil - && !(excludeGroups && [contact objectForKey: @"isGroup"])) - [uniqueContacts setObject: contact forKey: mail]; - } - } - if ([uniqueContacts count] > 0) - { - // Sort the contacts by display name - commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn" - ascending:YES]; - descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil]; - [commonNameDescriptor release]; - sortedContacts = [[uniqueContacts allValues] - sortedArrayUsingDescriptors: descriptors]; - } - else - sortedContacts = [NSArray array]; + + sortedContacts = [[self clientObject] allContactsFromFilter: searchText + excludeGroups: excludeGroups + excludeLists: excludeLists]; + + data = [NSDictionary dictionaryWithObjectsAndKeys: searchText, @"searchText", sortedContacts, @"contacts", nil]; diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index a2e2847ae..7ed3b6314 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -118,6 +118,10 @@ pageName = "UIxContactsUserRightsEditor"; actionName = "saveUserRights"; }; + properties = { + protectedBy = "View"; + pageName = "UIxContactFolderProperties"; + }; }; }; diff --git a/UI/MailPartViewers/Slovak.lproj/Localizable.strings b/UI/MailPartViewers/Slovak.lproj/Localizable.strings index 3cc9becbf..57546015e 100644 --- a/UI/MailPartViewers/Slovak.lproj/Localizable.strings +++ b/UI/MailPartViewers/Slovak.lproj/Localizable.strings @@ -12,6 +12,8 @@ publish_info_text = "Odosielateľ Vás informuje o priloženej udalosti."; cancel_info_text = "Vaša pozvánka alebo celá udalosť bola zrušená."; request_info_no_attendee = "navrhuje stretnutie účastníkom. Tento email je notifikácia, nie ste dohodnutý účastník."; Appointment = "Schôdzka"; +"Status Update" = "Aktualizácia stavu"; +was = "bol"; Organizer = "Organizátor"; Time = "Čas"; diff --git a/UI/MailPartViewers/SpanishArgentina.lproj/Localizable.strings b/UI/MailPartViewers/SpanishArgentina.lproj/Localizable.strings index 7b52eba2a..1a906cff4 100644 --- a/UI/MailPartViewers/SpanishArgentina.lproj/Localizable.strings +++ b/UI/MailPartViewers/SpanishArgentina.lproj/Localizable.strings @@ -12,6 +12,8 @@ publish_info_text = "El remitente le informa del evento adjunto."; cancel_info_text = "Su invitación o el evento ha sido cancelado."; request_info_no_attendee = "ha propuesto un evento a los participantes. Usted recibe este correo como notificación. Nota: Usted no está registrado como participante"; Appointment = "Cita"; +"Status Update" = "Actualización del estado"; +was = "era"; Organizer = "Organizador"; Time = "Hora"; diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index e9e45fbbc..df981a470 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -559,6 +559,13 @@ static NSData* _sanitizeContent(NSData *theData) && ![value hasPrefix: @"mailto:"] && ![value hasPrefix: @"#"]); } + // Avoid:
+ else if ([name isEqualToString: @"style"]) + { + value = [_attributes valueAtIndex: count]; + if ([value rangeOfString: @"url" options: NSCaseInsensitiveSearch].location != NSNotFound) + name = [NSString stringWithFormat: @"unsafe-%@", name]; + } else if ( // Mouse Events [name isEqualToString: @"onclick"] || @@ -594,12 +601,13 @@ static NSData* _sanitizeContent(NSData *theData) } else value = [_attributes valueAtIndex: count]; + if (!skipAttribute) [resultPart appendFormat: @" %@=\"%@\"", name, [value stringByReplacingString: @"\"" withString: @"\\\""]]; } - + if ([VoidTags containsObject: lowerName]) [resultPart appendString: @"/"]; [resultPart appendString: @">"]; @@ -686,16 +694,16 @@ static NSData* _sanitizeContent(NSData *theData) [self _appendStyle: _chars length: _len]; else if (inBody) { - NSString *tmpString; + NSString *s; - tmpString = [NSString stringWithCharacters: _chars length: _len]; + s = [NSString stringWithCharacters: _chars length: _len]; // HACK: This is to avoid appending the useless junk in the tag // that Outlook adds. It seems to confuse the XML parser for // forwarded messages as we get this in the _body_ of the email // while we really aren't in it! - if (![tmpString hasPrefix: @" xmlns:v=\"urn:schemas-microsoft-com:vml\""]) - [result appendString: [tmpString stringByEscapingHTMLString]]; + if (![s hasPrefix: @" xmlns:v=\"urn:schemas-microsoft-com:vml\""]) + [result appendString: [s stringByEscapingHTMLString]]; } } } diff --git a/UI/MailerUI/Czech.lproj/Localizable.strings b/UI/MailerUI/Czech.lproj/Localizable.strings index fd58de7af..981857cd5 100644 --- a/UI/MailerUI/Czech.lproj/Localizable.strings +++ b/UI/MailerUI/Czech.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Odebrat tuto složku"; "Erase mails from this folder" = "Vymazat maily v této složce"; "Expunge this folder" = "Vyškrtnout tuto složku"; -"Archive This Folder" = "Archivovat složku"; +"Export This Folder" = "Exportovat složku"; "Modify the acl of this folder" = "Upravit acl této složky"; "Saved Messages.zip" = "zpravy.zip"; diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index 47d70ffab..345d4c09b 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Remove this folder"; "Erase mails from this folder" = "Erase mails from this folder"; "Expunge this folder" = "Expunge this folder"; -"Archive This Folder" = "Archive this folder"; +"Export This Folder" = "Export This Folder"; "Modify the acl of this folder" = "Modify the acl of this folder"; "Saved Messages.zip" = "Saved Messages.zip"; diff --git a/UI/MailerUI/French.lproj/Localizable.strings b/UI/MailerUI/French.lproj/Localizable.strings index 373bfbaf9..d0e64a0db 100644 --- a/UI/MailerUI/French.lproj/Localizable.strings +++ b/UI/MailerUI/French.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Effacer ce dossier"; "Erase mails from this folder" = "Effacer des messages de ce dossier"; "Expunge this folder" = "Compacter ce dossier"; -"Archive This Folder" = "Archiver ce dossier"; +"Export This Folder" = "Exporter ce dossier"; "Modify the acl of this folder" = "Administrer les droits sur ce dossier"; "Saved Messages.zip" = "Messages sauvegardés.zip"; diff --git a/UI/MailerUI/Hungarian.lproj/Localizable.strings b/UI/MailerUI/Hungarian.lproj/Localizable.strings index 6aa1076d6..6e10cd452 100644 --- a/UI/MailerUI/Hungarian.lproj/Localizable.strings +++ b/UI/MailerUI/Hungarian.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Mappa törlése"; "Erase mails from this folder" = "Üzenetek törlése a mappából"; "Expunge this folder" = "Mappa megjelölése töröltként"; -"Archive This Folder" = "Mappa archiválása"; +"Export This Folder" = "Aktuális mappa exportálása"; "Modify the acl of this folder" = "Mappa jogosultságainak szerkesztése"; "Saved Messages.zip" = "Messages.zip elmentve"; diff --git a/UI/MailerUI/Polish.lproj/Localizable.strings b/UI/MailerUI/Polish.lproj/Localizable.strings index a90f48de4..ffafe5612 100644 --- a/UI/MailerUI/Polish.lproj/Localizable.strings +++ b/UI/MailerUI/Polish.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Usuń ten folder"; "Erase mails from this folder" = "Usuń wiadomości z tego folderu"; "Expunge this folder" = "Wyczyść ostatecznie ten folder"; -"Archive This Folder" = "Zarchiwizuj ten folder"; +"Export This Folder" = "Eksportuj ten folder"; "Modify the acl of this folder" = "Modyfikuj uprawnienia ACL tego folderu"; "Saved Messages.zip" = "Zapisano Messages.zip"; diff --git a/UI/MailerUI/Russian.lproj/Localizable.strings b/UI/MailerUI/Russian.lproj/Localizable.strings index ea6500111..4507c2315 100644 --- a/UI/MailerUI/Russian.lproj/Localizable.strings +++ b/UI/MailerUI/Russian.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Удалять эту папку"; "Erase mails from this folder" = "Удалять сообщения из этой папки"; "Expunge this folder" = "Помечать письма как удаленные"; -"Archive This Folder" = "Archive This Folder"; +"Export This Folder" = "Экспортировать эту папку"; "Modify the acl of this folder" = "Управлять правами доступа к этой папке"; "Saved Messages.zip" = "Saved Messages.zip"; diff --git a/UI/MailerUI/Slovak.lproj/Localizable.strings b/UI/MailerUI/Slovak.lproj/Localizable.strings index 28820b5b6..31a5958a1 100644 --- a/UI/MailerUI/Slovak.lproj/Localizable.strings +++ b/UI/MailerUI/Slovak.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Zmazať adresár"; "Erase mails from this folder" = "Zmazať správy z adresára"; "Expunge this folder" = "Vyprázdniť adresár"; -"Archive This Folder" = "Archivovať adresár"; +"Export This Folder" = "Exportovať tento adresár"; "Modify the acl of this folder" = "Uprav ACL tejto zložky"; "Saved Messages.zip" = "Uložené Správy.zip"; @@ -102,7 +102,7 @@ "Attach Web Page..." = "Prilož WWW stránku..."; "file" = "súbor"; "files" = "súbory"; -"Save all" = "Save all"; +"Save all" = "Uložiť všetko"; "to" = "Pre"; "cc" = "Kópia"; diff --git a/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings b/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings index c8211dfeb..101bcfa44 100644 --- a/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings +++ b/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Borrar esta carpeta"; "Erase mails from this folder" = "Borrar los correos de esta carpeta"; "Expunge this folder" = "Compactar esta carpeta"; -"Archive This Folder" = "Archivar esta carpeta"; +"Export This Folder" = "Exportar esta carpeta"; "Modify the acl of this folder" = "Modificar los permisos de esta carpeta"; "Saved Messages.zip" = "Mensajes Guardados.zip"; @@ -97,11 +97,12 @@ "Reply-To" = "Responder a"; "Add address" = "Añadir dirección"; -"Attachments:" = "Adjuntos:"; "Open" = "Abrir"; "Select All" = "Seleccionar todo"; "Attach Web Page..." = "Adjuntar página web..."; -"Attach File(s)..." = "Adjuntar archivo(s)..."; +"file" = "archivo"; +"files" = "archivos"; +"Save all" = "Guardar todo"; "to" = "Para"; "cc" = "Cc"; @@ -278,6 +279,9 @@ "error_missingsubject" = "No ha indicado el asunto"; "error_missingrecipients" = "No ha indicado el/los destinatario(s)"; "Send Anyway" = "Enviar de cualquier forma"; +"Error while saving the draft:" = "Ocurrió un error mientras se guardaba el borrador:"; +"Error while uploading the file \"%{0}\":" = "Ocurrió un error mientras se subía el archivo \"%{0}\":"; +"There is an active file upload. Closing the window will interrupt it." = "Se está subiendo un archivo. Si cierra la ventana se cancelará la operación."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "No se puede enviar el mensaje: (smtp) todos los destinatarios han sido descartados"; diff --git a/UI/MailerUI/SpanishSpain.lproj/Localizable.strings b/UI/MailerUI/SpanishSpain.lproj/Localizable.strings index d72c83f1e..2327f9abd 100644 --- a/UI/MailerUI/SpanishSpain.lproj/Localizable.strings +++ b/UI/MailerUI/SpanishSpain.lproj/Localizable.strings @@ -79,7 +79,7 @@ "Remove this folder" = "Borrar esta carpeta"; "Erase mails from this folder" = "Borrar los correos de esta carpeta"; "Expunge this folder" = "Compactar esta carpeta"; -"Archive This Folder" = "Archivar esta carpeta"; +"Export This Folder" = "Exportar Esta Carpeta"; "Modify the acl of this folder" = "Modificar la lista de permisos de esta carpeta"; "Saved Messages.zip" = "Guardar Mensaje.zip"; diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index c32a4f80c..b21cd160f 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -23,6 +23,7 @@ #import #import +#import #import #import #import @@ -31,9 +32,10 @@ #import #import #import +#import +#import #import - #import "../Common/WODirectAction+SOGo.h" #import "UIxMailActions.h" @@ -76,12 +78,18 @@ SOGoMailObject *co; SOGoDraftsFolder *folder; SOGoDraftObject *newMail; + SOGoUserDefaults *ud; NSString *newLocation; + BOOL htmlComposition; co = [self clientObject]; account = [co mailAccountFolder]; folder = [account draftsFolderInContext: context]; newMail = [folder newDraft]; + ud = [[context activeUser] userDefaults]; + htmlComposition = [[ud mailComposeMessageType] isEqualToString: @"html"]; + + [newMail setIsHTML: htmlComposition]; [newMail fetchMailForForwarding: co]; newLocation = [NSString stringWithFormat: @"%@/edit", diff --git a/UI/PreferencesUI/Czech.lproj/Localizable.strings b/UI/PreferencesUI/Czech.lproj/Localizable.strings index ab5e0cbd3..bcf32ab2c 100644 --- a/UI/PreferencesUI/Czech.lproj/Localizable.strings +++ b/UI/PreferencesUI/Czech.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Čas mimo pracovní dobu zobrazovat jako obsazený"; "First week of year :" = "První týden roku :"; "Enable reminders for Calendar items" = "Povolit upozornění pro události v kalendáři"; -"Play a sound when a reminder comes due" -= "Při upozornění přehrát zvuk"; +"Play a sound when a reminder comes due" = "Při upozornění přehrát zvuk"; "Default reminder :" = "Výchozí upozornění :"; "firstWeekOfYear_January1" = "Začíná 1. ledna"; @@ -130,6 +129,7 @@ "Label" = "Označkovat"; "Show subscribed mailboxes only" = "Ukázat pouze odebírané mailové schránky"; "Sort messages by threads" = "Třídit zprávy podle souvislostí"; +"When sending mail, add unknown recipients to my" = "Automaticky přidávat odchozí e-mailovou adresu do složky:"; "Check for new mail:" = "Zkontrolovat novou poštu:"; "messagecheck_manually" = "Manuálně"; "messagecheck_every_minute" = "Každou minutu"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Nikdy"; "displayremoteinlineimages_always" = "Vždy"; +/* Contact */ +"Personal Address Book" = "Osobní kontakty"; +"Collected Address Book" = "Sebrané kontakty"; + /* IMAP Accounts */ "New Mail Account" = "Nový poštovní účet"; @@ -204,6 +208,7 @@ "Mail" = "Pošta"; "Last" = "Naposledy použitý"; "Default module :" = "Výchozí modul :"; +"SOGo version:" = "SOGo verze:"; "Language :" = "Jazyk :"; "choose" = "Vybrat ..."; @@ -248,6 +253,8 @@ "Active" = "Aktivní"; "Move Up" = "Posunout nahoru"; "Move Down" = "Posunout dolů"; +"Connection error" = "Chyba spojení"; +"Service temporarily unavailable" = "Služba dočasně nedostupná"; /* Filters - UIxFilterEditor */ "Filter name:" = "Jméno filtru:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Neošetřená chyba: %{0}"; "Unhandled error response" = "Neošetřená chyba"; "Password change is not supported." = "Změna hesla není podporována."; -"Unhandled HTTP error code: %{0}" = "Neošetřený HTTP chybový kód: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Neošetřený HTTP chybový kód: %{0}"; diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index afa832c3e..66f8729e0 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Show time as busy outside working hours"; "First week of year :" = "First week of year:"; "Enable reminders for Calendar items" = "Enable reminders for Calendar items"; -"Play a sound when a reminder comes due" -= "Play a sound when a reminder comes due"; +"Play a sound when a reminder comes due" = "Play a sound when a reminder comes due"; "Default reminder :" = "Default reminder:"; "firstWeekOfYear_January1" = "Starts on january 1"; @@ -130,6 +129,7 @@ "Label" = "Label"; "Show subscribed mailboxes only" = "Show subscribed mailboxes only"; "Sort messages by threads" = "Sort messages by threads"; +"When sending mail, add unknown recipients to my" = "When sending mail, add unknown recipients to my"; "Check for new mail:" = "Check for new mail:"; "messagecheck_manually" = "Manually"; "messagecheck_every_minute" = "Every minute"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Never"; "displayremoteinlineimages_always" = "Always"; +/* Contact */ +"Personal Address Book" = "Personal Address Book"; +"Collected Address Book" = "Collected Address Book"; + /* IMAP Accounts */ "New Mail Account" = "New Mail Account"; @@ -204,6 +208,7 @@ "Mail" = "Mail"; "Last" = "Last used"; "Default module :" = "Default module:"; +"SOGo version:" = "SOGo version:"; "Language :" = "Language:"; "choose" = "Choose ..."; @@ -248,6 +253,8 @@ "Active" = "Active"; "Move Up" = "Move Up"; "Move Down" = "Move Down"; +"Connection error" = "Connection error"; +"Service temporarily unavailable" = "Service temporarily unavailable"; /* Filters - UIxFilterEditor */ "Filter name:" = "Filter name:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Unhandled policy error: %{0}"; "Unhandled error response" = "Unhandled error response"; "Password change is not supported." = "Password change is not supported."; -"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index b3d49aab2..b4d291e61 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Afficher comme occupé pendant les heures non-ouvrables"; "First week of year :" = "Première semaine de l'année :"; "Enable reminders for Calendar items" = "Activer les rappels pour les éléments du calendrier"; -"Play a sound when a reminder comes due" -= "Émettre un signal sonore à l'échéance du rappel"; +"Play a sound when a reminder comes due" = "Émettre un signal sonore à l'échéance du rappel"; "Default reminder :" = "Rappel par défaut :"; "firstWeekOfYear_January1" = "Commence le 1er janvier"; @@ -130,6 +129,7 @@ "Label" = "Étiquette"; "Show subscribed mailboxes only" = "Afficher seulement les abonnements"; "Sort messages by threads" = "Grouper les discussions"; +"When sending mail, add unknown recipients to my" = "Lors de l'envoi d'un message, ajouter les destinataires inconnus au carnet"; "Check for new mail:" = "Vérifier l'arrivée de messages :"; "messagecheck_manually" = "Manuellement"; "messagecheck_every_minute" = "Chaque minute"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Jamais"; "displayremoteinlineimages_always" = "Toujours"; +/* Contact */ +"Personal Address Book" = "Adresses personnelles"; +"Collected Address Book" = "Adresses collectées"; + /* IMAP Accounts */ "New Mail Account" = "Nouveau compte"; @@ -204,6 +208,7 @@ "Mail" = "Courrier"; "Last" = "Dernier utilisé"; "Default module :" = "Module par défaut :"; +"SOGo version:" = "Version :"; "Language :" = "Langue :"; "choose" = "Choisir ..."; @@ -248,6 +253,8 @@ "Active" = "Actif"; "Move Up" = "Remonter"; "Move Down" = "Abaisser"; +"Connection error" = "Erreur de connexion"; +"Service temporarily unavailable" = "Service momentanément indisponible"; /* Filters - UIxFilterEditor */ "Filter name:" = "Nom du filtre :"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Erreur inconnue pour le ppolicy: %{0}"; "Unhandled error response" = "Erreur inconnue"; "Password change is not supported." = "Changement de mot de passe non-supporté."; -"Unhandled HTTP error code: %{0}" = "Code HTTP non-géré: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Code HTTP non-géré: %{0}"; diff --git a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings index a200befbe..7e521a263 100644 --- a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "A munkaidőn túli időszakokra foglaltság jelzése"; "First week of year :" = "Év első hete:"; "Enable reminders for Calendar items" = "Emlékeztető engedélyezése a naptárbejegyzésekhez"; -"Play a sound when a reminder comes due" -= "Hang lejátszása az emlékeztetőhöz"; +"Play a sound when a reminder comes due" = "Hang lejátszása az emlékeztetőhöz"; "Default reminder :" = "Alapértelmezett emlékeztető:"; "firstWeekOfYear_January1" = "Január 1-jétől"; @@ -130,6 +129,7 @@ "Label" = "Címke"; "Show subscribed mailboxes only" = "Csak azok a fiókok mutatása, amelyre feliratkozott"; "Sort messages by threads" = "Üzenetek beszélgetések szerinti rendezése "; +"When sending mail, add unknown recipients to my" = "Levél küldésekor az ismeretlen címeket adja hozzá a következőhöz"; "Check for new mail:" = "Új üzenetek letöltése:"; "messagecheck_manually" = "Kézi"; "messagecheck_every_minute" = "Percenként"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Soha"; "displayremoteinlineimages_always" = "Mindig"; +/* Contact */ +"Personal Address Book" = "Személyes címjegyzék"; +"Collected Address Book" = "Összegyűjtöt címek jegyzéke"; + /* IMAP Accounts */ "New Mail Account" = "Új email fiók"; @@ -204,6 +208,7 @@ "Mail" = "Levél"; "Last" = "Utoljára használt"; "Default module :" = "Alapértelmezett modul"; +"SOGo version:" = "SOGo verzió:"; "Language :" = "Nyelv :"; "choose" = "Válasszon ..."; @@ -248,6 +253,8 @@ "Active" = "Aktív"; "Move Up" = "Mozgatás fel"; "Move Down" = "Mozgatás le"; +"Connection error" = "Kapcsolódási hiba"; +"Service temporarily unavailable" = "A szolgáltatás átmenetileg nem érhető el"; /* Filters - UIxFilterEditor */ "Filter name:" = "Szűrő neve:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Nem kezelt szabályrendszer hiba: %{0}"; "Unhandled error response" = "Nem kezelt hiba válasz"; "Password change is not supported." = "Jelszó változtatása nem támogatott."; -"Unhandled HTTP error code: %{0}" = "Nem kezelt HTTP hiba kód: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Nem kezelt HTTP hiba kód: %{0}"; diff --git a/UI/PreferencesUI/Polish.lproj/Localizable.strings b/UI/PreferencesUI/Polish.lproj/Localizable.strings index 788db6408..1401f7040 100644 --- a/UI/PreferencesUI/Polish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Polish.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Oznacz termin jako zajęty poza godzinami pracy"; "First week of year :" = "Pierwszy tydzień roku :"; "Enable reminders for Calendar items" = "Włącz przypomnienia pozycji kalendarza"; -"Play a sound when a reminder comes due" -= "Odtwórz dźwięk w momencie przypomnienia"; +"Play a sound when a reminder comes due" = "Odtwórz dźwięk w momencie przypomnienia"; "Default reminder :" = "Domyślne przypomnienie :"; "firstWeekOfYear_January1" = "rozpoczyna się 1 stycznia"; @@ -130,6 +129,7 @@ "Label" = "Etykieta"; "Show subscribed mailboxes only" = "Pokaż tylko subskrybowane konta pocztowe"; "Sort messages by threads" = "Sortuj wiadomości według wątków"; +"When sending mail, add unknown recipients to my" = "Gdy wysyłam e-mail, dodaj adresy nowych odbiorców do"; "Check for new mail:" = "Sprawdzaj nowe wiadomości:"; "messagecheck_manually" = "ręcznie"; "messagecheck_every_minute" = "co minutę"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Nigdy"; "displayremoteinlineimages_always" = "Zawsze"; +/* Contact */ +"Personal Address Book" = "Prywatna Książka Adresowa"; +"Collected Address Book" = "Zbierana Książka Adresowa"; + /* IMAP Accounts */ "New Mail Account" = "Nowe konto"; @@ -204,6 +208,7 @@ "Mail" = "Poczta"; "Last" = "Ostatnio używane"; "Default module :" = "Tryb domyślny:"; +"SOGo version:" = "Wersja SOGo:"; "Language :" = "Język:"; "choose" = "Wybierz"; @@ -248,6 +253,8 @@ "Active" = "Aktywny"; "Move Up" = "Przenieś do góry"; "Move Down" = "Przenieś w dół"; +"Connection error" = "Błąd połączenia"; +"Service temporarily unavailable" = "Usługa chwilowo niedostępna"; /* Filters - UIxFilterEditor */ "Filter name:" = "Nazwa filtra:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Nieznany błąd: %{0}"; "Unhandled error response" = "Nieznany błąd"; "Password change is not supported." = "Zmiana hasła jest nieobsługiwana."; -"Unhandled HTTP error code: %{0}" = "Nieznany kod błędu HTTP: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Nieznany kod błędu HTTP: %{0}"; diff --git a/UI/PreferencesUI/Russian.lproj/Localizable.strings b/UI/PreferencesUI/Russian.lproj/Localizable.strings index 29a9a2137..b9fcba4d9 100644 --- a/UI/PreferencesUI/Russian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Russian.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Показывать время, как занятое, если оно за границами рабочего времени."; "First week of year :" = "Первая неделя года :"; "Enable reminders for Calendar items" = "Включить напоминания для событий календаря"; -"Play a sound when a reminder comes due" -= "Проигрывать звук когда срабатывает оповещение"; +"Play a sound when a reminder comes due" = "Проигрывать звук когда срабатывает оповещение"; "Default reminder :" = "Обычное оповещение :"; "firstWeekOfYear_January1" = "Начинается 1 января"; @@ -130,6 +129,7 @@ "Label" = "Метка"; "Show subscribed mailboxes only" = "Показывать только почтовые ящики, на которые подписан"; "Sort messages by threads" = "Сортировать сообщения по нитям"; +"When sending mail, add unknown recipients to my" = "При отправке писем добавлять неизвестных получателей в мою"; "Check for new mail:" = "Проверять новую почту:"; "messagecheck_manually" = "Вручную"; "messagecheck_every_minute" = "Каждую минуту"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Никогда"; "displayremoteinlineimages_always" = "Всегда"; +/* Contact */ +"Personal Address Book" = "Личная адресная книга"; +"Collected Address Book" = "Собранные адреса"; + /* IMAP Accounts */ "New Mail Account" = "New Mail Account"; @@ -204,6 +208,7 @@ "Mail" = "Почта"; "Last" = "Последнее использование"; "Default module :" = "Модуль по умолчанию :"; +"SOGo version:" = "Версия SOGo:"; "Language :" = "Язык :"; "choose" = "Выбрать ..."; @@ -248,6 +253,8 @@ "Active" = "Активные"; "Move Up" = "Передвинуть вверх"; "Move Down" = "Передвинуть вниз"; +"Connection error" = "Ошибка соединения"; +"Service temporarily unavailable" = "Сервис временно недоступен"; /* Filters - UIxFilterEditor */ "Filter name:" = "Имя фильтра:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Unhandled policy error: %{0}"; "Unhandled error response" = "Unhandled error response"; "Password change is not supported." = "Изменение пароля не поддерживается"; -"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; diff --git a/UI/PreferencesUI/Slovak.lproj/Localizable.strings b/UI/PreferencesUI/Slovak.lproj/Localizable.strings index 0f4cd14d5..dc8208fa2 100644 --- a/UI/PreferencesUI/Slovak.lproj/Localizable.strings +++ b/UI/PreferencesUI/Slovak.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Mimo pracovných hodín ukáž čas ako obsadený"; "First week of year :" = "Prvý týždeň roka:"; "Enable reminders for Calendar items" = "Zapni pripomienky pre položky Kalendára"; -"Play a sound when a reminder comes due" -= "Prehraj zvuk keď začne pripomienka"; +"Play a sound when a reminder comes due" = "Prehraj zvuk keď začne pripomienka"; "Default reminder :" = "Predvolená pripomienka:"; "firstWeekOfYear_January1" = "Začína 1. Januárom"; @@ -111,21 +110,26 @@ "personalCalendar" = "Osobný kalendár"; "firstCalendar" = "Prvý zapnutý kalendár"; +"reminder_NONE" = "Žiadna pripomienka"; "reminder_5_MINUTES_BEFORE" = "5 minút"; "reminder_10_MINUTES_BEFORE" = "10 minút"; "reminder_15_MINUTES_BEFORE" = "15 minút"; "reminder_30_MINUTES_BEFORE" = "30 minút"; +"reminder_45_MINUTES_BEFORE" = "45 minút pred"; "reminder_1_HOUR_BEFORE" = "1 hodina"; "reminder_2_HOURS_BEFORE" = "2 hodiny"; -"reminder_5_HOURS_BEFORE"= "5 hodiny"; -"reminder_15_HOURS_BEFORE"= "15 hodín"; +"reminder_5_HOURS_BEFORE" = "5 hodiny"; +"reminder_15_HOURS_BEFORE" = "15 hodín"; "reminder_1_DAY_BEFORE" = "1 deň"; "reminder_2_DAYS_BEFORE" = "2 dni"; +"reminder_1_WEEK_BEFORE" = "1 týždeň pred"; /* Mailer */ +"Labels" = "Štítok"; "Label" = "Štítok"; "Show subscribed mailboxes only" = "Ukazuj iba odoberané účty"; "Sort messages by threads" = "Zoraď správy do konverzácií"; +"When sending mail, add unknown recipients to my" = "Pri odosielaní emailu, pridaj neznámych príjemcov do môjho"; "Check for new mail:" = "Kontrola nových mailov:"; "messagecheck_manually" = "Ručne"; "messagecheck_every_minute" = "Každú minútu"; @@ -153,6 +157,10 @@ "displayremoteinlineimages_never" = "Nikdy"; "displayremoteinlineimages_always" = "Vždy"; +/* Contact */ +"Personal Address Book" = "Osobné kontakty"; +"Collected Address Book" = "Pozbierané osobné kontakty"; + /* IMAP Accounts */ "New Mail Account" = "Nový mailový účet"; @@ -200,6 +208,7 @@ "Mail" = "Pošta"; "Last" = "Naposledy použité"; "Default module :" = "Predvolený modul:"; +"SOGo version:" = "Verzia SOGo:"; "Language :" = "Jazyk:"; "choose" = "Vyber..."; @@ -244,6 +253,8 @@ "Active" = "Aktívne"; "Move Up" = "Posuň hore"; "Move Down" = "Posuň dole"; +"Connection error" = "Chyba spojenia"; +"Service temporarily unavailable" = "Služba je dočasne nedostupná"; /* Filters - UIxFilterEditor */ "Filter name:" = "Meno filtra:"; @@ -261,6 +272,7 @@ "To or Cc" = "Pre alebo Kópia"; "Size (Kb)" = "Vľkosť (Kb)"; "Header" = "Hlavička"; +"Body" = "Telo"; "Flag the message with:" = "Nasledujúce správy označ zástavkou:"; "Discard the message" = "Zahoď správu"; "File the message in:" = "Súbor správy v:"; @@ -287,12 +299,8 @@ "Flagged" = "Označené zástavkou"; "Junk" = "Spam"; "Not Junk" = "Nie je Spam"; -"Label 1" = "Označenie 1"; -"Label 2" = "Označenie 2"; -"Label 3" = "Označenie 3"; -"Label 4" = "Označenie 4"; -"Label 5" = "Označenie 5"; +/* Password policy */ "The password was changed successfully." = "Heslo bolo úspešne zmenené."; "Password must not be empty." = "Heslo nemôže byť prázdne."; "The passwords do not match. Please try again." = "Heslá sa nezhodujú. Skúste to znova."; diff --git a/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings b/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings index 703c33c31..c2b003477 100644 --- a/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings +++ b/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Mostrar el tiempo fuera de las horas de trabajo como ocupado"; "First week of year :" = "Primera semana del año: "; "Enable reminders for Calendar items" = "Habilitar recordatorios para los elementos del calendario"; -"Play a sound when a reminder comes due" -= "Señal acústica para los recordatorios"; +"Play a sound when a reminder comes due" = "Señal acústica para los recordatorios"; "Default reminder :" = "Recordatorio por defecto: "; "firstWeekOfYear_January1" = "Empieza el 1 de enero"; @@ -111,21 +110,26 @@ "personalCalendar" = "Calendario personal"; "firstCalendar" = "Primer calendario habilitado"; +"reminder_NONE" = "No recordar"; "reminder_5_MINUTES_BEFORE" = "5 minutos"; "reminder_10_MINUTES_BEFORE" = "10 minutos"; "reminder_15_MINUTES_BEFORE" = "15 minutos"; "reminder_30_MINUTES_BEFORE" = "30 minutos"; +"reminder_45_MINUTES_BEFORE" = "45 minutos antes"; "reminder_1_HOUR_BEFORE" = "1 hora"; "reminder_2_HOURS_BEFORE" = "2 horas"; -"reminder_5_HOURS_BEFORE"= "5 horas"; -"reminder_15_HOURS_BEFORE"= "15 horas"; +"reminder_5_HOURS_BEFORE" = "5 horas"; +"reminder_15_HOURS_BEFORE" = "15 horas"; "reminder_1_DAY_BEFORE" = "1 día"; "reminder_2_DAYS_BEFORE" = "2 días"; +"reminder_1_WEEK_BEFORE" = "1 semana antes"; /* Mailer */ +"Labels" = "Etiquetas"; "Label" = "Etiquetar"; "Show subscribed mailboxes only" = "Mostrar sólo buzones suscritos"; "Sort messages by threads" = "Ordenar mensajes por conversaciones"; +"When sending mail, add unknown recipients to my" = "Al enviar correos, agregar destinatarios desconocidos a mi"; "Check for new mail:" = "Comprobar si hay nuevos correos: "; "messagecheck_manually" = "Manualmente"; "messagecheck_every_minute" = "Cada minuto"; @@ -153,6 +157,10 @@ "displayremoteinlineimages_never" = "Nunca"; "displayremoteinlineimages_always" = "Siempre"; +/* Contact */ +"Personal Address Book" = "Libreta de direcciones personal"; +"Collected Address Book" = "Libreta de direcciones recopiladas"; + /* IMAP Accounts */ "New Mail Account" = "Nueva Cuenta de Correo"; @@ -200,6 +208,7 @@ "Mail" = "Correo"; "Last" = "Ultimo usado"; "Default module :" = "Módulo por defecto :"; +"SOGo version:" = "Versión de SOGo:"; "Language :" = "Idioma :"; "choose" = "Elija ..."; @@ -244,6 +253,8 @@ "Active" = "Activo"; "Move Up" = "Subir"; "Move Down" = "Bajar"; +"Connection error" = "Error de conexión"; +"Service temporarily unavailable" = "Servicio temporalmente no disponible"; /* Filters - UIxFilterEditor */ "Filter name:" = "Nombre del filtro:"; @@ -261,6 +272,7 @@ "To or Cc" = "Para o Cc"; "Size (Kb)" = "Tamaño (Kb)"; "Header" = "Cabecera"; +"Body" = "Cuerpo"; "Flag the message with:" = "Marcar el mensaje con:"; "Discard the message" = "Descartar el mensaje"; "File the message in:" = "Archivar el mensaje en:"; @@ -287,12 +299,8 @@ "Flagged" = "Marcado"; "Junk" = "Spam"; "Not Junk" = "No es Spam"; -"Label 1" = "Etiqueta 1"; -"Label 2" = "Etiqueta 2"; -"Label 3" = "Etiqueta 3"; -"Label 4" = "Etiqueta 4"; -"Label 5" = "Etiqueta 5"; +/* Password policy */ "The password was changed successfully." = "El cambio de clave fue exitoso"; "Password must not be empty." = "La contraseña no puede estar en blanco"; "The passwords do not match. Please try again." = "Las contraseñas no coinciden. Por favor intente de nuevo"; diff --git a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings index efd2588e4..a0abeb7aa 100644 --- a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +++ b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings @@ -97,8 +97,7 @@ "Show time as busy outside working hours" = "Mostrar tiempo ocupado fuera del horario laboral"; "First week of year :" = "Primera semana del año: "; "Enable reminders for Calendar items" = "Habilitar recordatorios para elementos del calendario"; -"Play a sound when a reminder comes due" -= "Señal acústica para los recordatorios"; +"Play a sound when a reminder comes due" = "Señal acústica para los recordatorios"; "Default reminder :" = "Recordatorio por defecto: "; "firstWeekOfYear_January1" = "Empieza el 1 de enero"; @@ -130,6 +129,7 @@ "Label" = "Etiquetar"; "Show subscribed mailboxes only" = "Mostrar sólo buzones suscritos"; "Sort messages by threads" = "Ordenar mensajes por temas"; +"When sending mail, add unknown recipients to my" = "Cuando se envía correo, añade destinatarios desconocidos a mi"; "Check for new mail:" = "Comprobar correo nuevo: "; "messagecheck_manually" = "Manualmente"; "messagecheck_every_minute" = "Cada minuto"; @@ -157,6 +157,10 @@ "displayremoteinlineimages_never" = "Nunca"; "displayremoteinlineimages_always" = "Siempre"; +/* Contact */ +"Personal Address Book" = "Libreta de direcciones personal"; +"Collected Address Book" = "Libreta de direcciones recogidas"; + /* IMAP Accounts */ "New Mail Account" = "Nueva Cuenta de Correo"; @@ -204,6 +208,7 @@ "Mail" = "Correo"; "Last" = "Ultimo usado"; "Default module :" = "Módulo por defecto :"; +"SOGo version:" = "Versión de SOGo:"; "Language :" = "Idioma :"; "choose" = "Elija ..."; @@ -248,6 +253,8 @@ "Active" = "Activo"; "Move Up" = "Subir"; "Move Down" = "Bajar"; +"Connection error" = "Error al conectar"; +"Service temporarily unavailable" = "Servicio temporalmente no disponible"; /* Filters - UIxFilterEditor */ "Filter name:" = "Nombre del filtro:"; @@ -306,4 +313,4 @@ "Unhandled policy error: %{0}" = "Error de la política no controlada:% {0}"; "Unhandled error response" = "Respuesta de error no controlado"; "Password change is not supported." = "Cambio de contraseña no compatible."; -"Unhandled HTTP error code: %{0}" = "Código de error HTTP no controlada:% {0}"; \ No newline at end of file +"Unhandled HTTP error code: %{0}" = "Código de error HTTP no controlada:% {0}"; diff --git a/UI/PreferencesUI/UIxFilterEditor.m b/UI/PreferencesUI/UIxFilterEditor.m index f6fe59d2b..964ff44b6 100644 --- a/UI/PreferencesUI/UIxFilterEditor.m +++ b/UI/PreferencesUI/UIxFilterEditor.m @@ -28,6 +28,7 @@ #import #import #import +#import #import @@ -35,6 +36,7 @@ { NSString *filterId; NSDictionary *labels; + NSString *folderEncoding; } @end @@ -112,4 +114,9 @@ return [labels jsonRepresentation]; } +- (NSString *) folderEncoding +{ + return [[SOGoSystemDefaults sharedSystemDefaults] folderEncoding]; +} + @end diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index 3040e67ae..bbbb0a2b1 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -22,6 +22,7 @@ #define UIXPREFERENCES_H #import +#import @class NSString; @@ -32,6 +33,10 @@ { id item; SOGoUser *user; + NGSieveClient *client; + + // Addressbook + NSMutableDictionary *addressBooksIDWithDisplayName; // Calendar categories NSString *category; @@ -58,6 +63,8 @@ } - (NSString *) userLongDateFormat; +- (BOOL) isSieveServerAvailable; +- (id) sieveClient; @end diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index d95e6fd34..833f6f045 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -1,6 +1,6 @@ /* UIxPreferences.m - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-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 @@ -45,11 +45,15 @@ #import #import #import +#import #import +#import #import #import #import +#import + #import "UIxPreferences.h" #warning this class is not finished @@ -110,10 +114,12 @@ static NSArray *reminderValues = nil; - (id) init { SOGoDomainDefaults *dd; - + if ((self = [super init])) { item = nil; + addressBooksIDWithDisplayName = nil; + client = nil; #warning user should be the owner rather than the activeUser ASSIGN (user, [context activeUser]); ASSIGN (today, [NSCalendarDate date]); @@ -125,7 +131,7 @@ static NSArray *reminderValues = nil; label = nil; mailLabels = nil; - + ASSIGN (daysOfWeek, [locale objectForKey: NSWeekDayNameArray]); dd = [user domainDefaults]; @@ -174,6 +180,8 @@ static NSArray *reminderValues = nil; [contactsCategories release]; [forwardOptions release]; [daysOfWeek release]; + [addressBooksIDWithDisplayName release]; + [client release]; [super dealloc]; } @@ -219,7 +227,7 @@ static NSArray *reminderValues = nil; NSEnumerator *zones; BOOL found; unsigned int offset; - + found = NO; now = [NSCalendarDate calendarDate]; offset = [[userDefaults timeZone] secondsFromGMTForDate: now]; @@ -337,7 +345,7 @@ static NSArray *reminderValues = nil; if (![longDateFormatsList containsObject: [self userLongDateFormat]]) [longDateFormatsList addObject: [self userLongDateFormat]]; - + return longDateFormatsList; } @@ -363,7 +371,7 @@ static NSArray *reminderValues = nil; - (NSString *) userLongDateFormat { NSString *longDateFormat; - + longDateFormat = [userDefaults longDateFormat]; if (!longDateFormat) longDateFormat = @"default"; @@ -555,7 +563,7 @@ static NSArray *reminderValues = nil; index = NSNotFound; value = @"NONE"; - + if (theReminder && [theReminder caseInsensitiveCompare: @"-"] != NSOrderedSame) index = [reminderItems indexOfObject: theReminder]; @@ -578,7 +586,7 @@ static NSArray *reminderValues = nil; if (index != NSNotFound) return [reminderItems objectAtIndex: index]; } - + return @"NONE"; } @@ -656,6 +664,16 @@ static NSArray *reminderValues = nil; } /* Mailer */ +- (void) setAddOutgoingAddresses: (BOOL) addOutgoingAddresses +{ + [userDefaults setMailAddOutgoingAddresses: addOutgoingAddresses]; +} + +- (BOOL) addOutgoingAddresses +{ + return [userDefaults mailAddOutgoingAddresses]; +} + - (void) setShowSubscribedFoldersOnly: (BOOL) showSubscribedFoldersOnly { [userDefaults setMailShowSubscribedFoldersOnly: showSubscribedFoldersOnly]; @@ -676,6 +694,69 @@ static NSArray *reminderValues = nil; return [userDefaults mailSortByThreads]; } +- (NSArray *) addressBookList +{ + /* 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]; + 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]; + } + + // 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; + } + // Create the dictionary for the next function : itemAddressBookText. + if (!addressBooksIDWithDisplayName) + addressBooksIDWithDisplayName = [[NSMutableDictionary alloc] initWithObjects:availableAddressBooksName + forKeys:availableAddressBooksID]; + if (!collectedAlreadyExist) + { + [availableAddressBooksID addObject: @"collected"]; + [addressBooksIDWithDisplayName setObject: [self labelForKey: @"Collected Address Book"] forKey: @"collected"]; + } + + return availableAddressBooksID; +} +- (NSString *) itemAddressBookText +{ + return [addressBooksIDWithDisplayName objectForKey: item]; +} + +- (NSString *) userAddressBook +{ + return [userDefaults selectedAddressBook]; +} + +- (void) setUserAddressBook: (NSString *) newSelectedAddressBook +{ + [userDefaults setSelectedAddressBook: newSelectedAddressBook]; +} + - (NSArray *) messageCheckList { NSArray *intervalsList; @@ -840,26 +921,15 @@ static NSArray *reminderValues = nil; - (NSString *) sieveCapabilities { -#warning sieve caps should be deduced from the server static NSArray *capabilities = nil; - SOGoMailAccounts *folder; - SOGoMailAccount *account; - SOGoSieveManager *manager; - NGSieveClient *client; if (!capabilities) { - folder = [[self clientObject] mailAccountsFolder: @"Mail" - inContext: context]; - account = [folder lookupName: @"0" inContext: context acquire: NO]; - manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; - client = [manager clientForAccount: account]; - - if (client) - capabilities = [client capabilities]; + if ([self sieveClient]) + capabilities = [[self sieveClient] capabilities]; else capabilities = [NSArray array]; - [capabilities retain]; + [capabilities retain]; } return [capabilities jsonRepresentation]; @@ -940,7 +1010,7 @@ static NSArray *reminderValues = nil; - (NSString *) autoReplyEmailAddresses { NSArray *addressesList; - + addressesList = [vacationOptions objectForKey: @"autoReplyEmailAddresses"]; return (addressesList @@ -993,7 +1063,7 @@ static NSArray *reminderValues = nil; obj = [vacationOptions objectForKey: @"ignoreLists"]; if (obj == nil) - ignore = YES; // defaults to true + ignore = YES; // defaults to YES else ignore = [obj boolValue]; @@ -1019,7 +1089,7 @@ static NSArray *reminderValues = nil; - (void) setVacationEndDate: (NSCalendarDate *) endDate { NSNumber *time; - + time = [NSNumber numberWithInt: [endDate timeIntervalSince1970]]; [vacationOptions setObject: time forKey: @"endDate"]; @@ -1137,19 +1207,47 @@ static NSArray *reminderValues = nil; } } +- (NSString *) sogoVersion +{ + // The variable SOGoVersion comes from the import: SOGo/Build.h + return [NSString stringWithString: SOGoVersion]; +} + +- (id) sieveClient +{ + SOGoMailAccount *account; + SOGoMailAccounts *folder; + 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]; + } + + return client; +} + +- (BOOL) isSieveServerAvailable +{ + return (([(NGSieveClient *)[self sieveClient] isConnected]) + ? YES + : NO); +} + - (id ) defaultAction { id results; - WORequest *request; SOGoDomainDefaults *dd; - NSString *method; + SOGoMailAccount *account; + SOGoMailAccounts *folder; + WORequest *request; request = [context request]; if ([[request method] isEqualToString: @"POST"]) { - SOGoMailAccount *account; - SOGoMailAccounts *folder; - dd = [[context activeUser] domainDefaults]; if ([dd sieveScriptsEnabled]) [userDefaults setSieveFilters: sieveFilters]; @@ -1158,19 +1256,24 @@ static NSArray *reminderValues = nil; if ([dd forwardEnabled]) [userDefaults setForwardOptions: forwardOptions]; - [userDefaults synchronize]; + if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]) || [self isSieveServerAvailable]) + { + [userDefaults synchronize]; + folder = [[self clientObject] mailAccountsFolder: @"Mail" + inContext: context]; + account = [folder lookupName: @"0" inContext: context acquire: NO]; - folder = [[self clientObject] mailAccountsFolder: @"Mail" - inContext: context]; - account = [folder lookupName: @"0" inContext: context acquire: NO]; - [account updateFilters]; + if ([account updateFilters]) + results = [self responseWithStatus: 200 + andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:hasChanged], @"hasChanged", nil]]; - if (hasChanged) - method = @"window.location.reload()"; - else - method = nil; - - results = [self jsCloseWithRefreshMethod: method]; + else + 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; @@ -1250,11 +1353,11 @@ static NSArray *reminderValues = nil; if (!mailLabels) { NSDictionary *v; - + v = [[[context activeUser] userDefaults] mailLabelsColors]; ASSIGN(mailLabels, [SOGoMailLabel labelsFromDefaults: v component: self]); } - + return mailLabels; } @@ -1353,7 +1456,7 @@ static NSArray *reminderValues = nil; componentsSeparatedByString: @","]; if (!categoryLabels) categoryLabels = [NSArray array]; - + return [categoryLabels trimmedComponents]; } @@ -1469,7 +1572,7 @@ static NSArray *reminderValues = nil; action = [receipts objectForKey: @"receiptAction"]; [userDefaults setAllowUserReceipt: [action isEqualToString: @"allow"]]; - + action = [receipts objectForKey: @"receiptNonRecipientAction"]; if ([self _validateReceiptAction: action]) [userDefaults setUserReceiptNonRecipientAction: action]; @@ -1477,7 +1580,7 @@ static NSArray *reminderValues = nil; action = [receipts objectForKey: @"receiptOutsideDomainAction"]; if ([self _validateReceiptAction: action]) [userDefaults setUserReceiptOutsideDomainAction: action]; - + action = [receipts objectForKey: @"receiptAnyAction"]; if ([self _validateReceiptAction: action]) [userDefaults setUserReceiptAnyAction: action]; diff --git a/UI/SOGoUI/UIxComponent.m b/UI/SOGoUI/UIxComponent.m index f80e9e9ec..336bd734d 100644 --- a/UI/SOGoUI/UIxComponent.m +++ b/UI/SOGoUI/UIxComponent.m @@ -359,6 +359,7 @@ static SoProduct *commonProduct = nil; { SOGoObject *currentClient, *parent; BOOL found; + NSString *hostLessURL; Class objectClass, userFolderClass; // , groupFolderClass @@ -386,8 +387,10 @@ static SoProduct *commonProduct = nil; } else currentClient = [WOApplication application]; + + hostLessURL = [[currentClient baseURLInContext: context] hostlessURL]; - return [[currentClient baseURLInContext: context] hostlessURL]; + return [hostLessURL substringToIndex: [hostLessURL length] - 1]; } - (NSString *) ownPath diff --git a/UI/Scheduler/Czech.lproj/Localizable.strings b/UI/Scheduler/Czech.lproj/Localizable.strings index 4b0688657..1b24ca753 100644 --- a/UI/Scheduler/Czech.lproj/Localizable.strings +++ b/UI/Scheduler/Czech.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Vytvořit novou událost"; "Create a new task" = "Vytvořit nový úkol"; "Edit this event or task" = "Upravit tuto událost nebo úkol"; +"Print the current calendar view" = "Aktuální pohled"; "Delete this event or task" = "Smazat tuto událost nebo úkol"; "Go to today" = "Přejde na dnešní den"; "Switch to day view" = "Přepnout na denní zobrazení"; @@ -53,7 +54,7 @@ "New Calendar..." = "Nový kalendář..."; "Delete Calendar" = "Smazat kalendář"; -"Unsubscribe Calendar" = "Unsubscribe Calendar"; +"Unsubscribe Calendar" = "Odhlásit odebírání kalendáře"; "Sharing..." = "Sdílení..."; "Export Calendar..." = "Exportovat kalendář..."; "Import Events..." = "Importovat události..."; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Název kalendáře"; "new" = "Nový"; -"printview" = "Náhled tisku"; +"Print view" = "Tisk"; "edit" = "Upravit"; "delete" = "Smazat"; "proposal" = "Návrh"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Skrýt již přijaté a odmítnuté schůzky"; "Show already accepted and rejected appointments" = "Zobrazit již přijaté a odmítnuté schůzky"; +/* Print view */ + +"LIST" = "Seznam"; +"Print Settings" = "Nastavení tisku"; +"Title:" = "Název:"; +"Layout:" = "Vzhled:"; +"What to Print" = "Rozsah tisku"; +"Options" = "Možnosti"; +"Tasks with no due date" = "Úkoly bez termínu"; +"Completed tasks" = "Dokončené úkoly"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Všechny budoucí události"; "view_selectedday" = "Zvolený den"; +"view_not_started" = "Nezapočaté úkoly"; +"view_overdue" = "Nesplněné úkoly"; +"view_incomplete" = "Nedokončené úkoly"; + "View:" = "Zobrazit:"; -"Title or Description" = "Název nebo popis"; +"Title, category or location" = "Název, kategorie nebo místo"; +"Entire content" = "Celý obsah"; "Search" = "Vyhledat"; "Search attendees" = "Vyhledat účastníky"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Důvěrný úkol)"; "Links to this Calendar" = "Odkazy na tento kalendář"; "Authenticated User Access" = "Přístup pro ověřené uživatele"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "CalDAV URL:"; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 900fa9614..32dc2db3d 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Create a new event"; "Create a new task" = "Create a new task"; "Edit this event or task" = "Edit this event or task"; +"Print the current calendar view" = "Print the current calendar view"; "Delete this event or task" = "Delete this event or task"; "Go to today" = "Go to today"; "Switch to day view" = "Switch to day view"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Name of the Calendar"; "new" = "New"; -"printview" = "Print View"; +"Print view" = "Print view"; "edit" = "Edit"; "delete" = "Delete"; "proposal" = "Proposal"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Hide already accepted and rejected appointments"; "Show already accepted and rejected appointments" = "Show already accepted and rejected appointments"; +/* Print view */ + +"LIST" = "List"; +"Print Settings" = "Print Settings"; +"Title:" = "Title:"; +"Layout:" = "Layout:"; +"What to Print" = "What to Print"; +"Options" = "Options"; +"Tasks with no due date" = "Tasks with no due date"; +"Completed tasks" = "Completed Tasks"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "All Future Events"; "view_selectedday" = "Selected Day"; +"view_not_started" = "Not started tasks"; +"view_overdue" = "Overdue tasks"; +"view_incomplete" = "Incomplete tasks"; + "View:" = "View:"; -"Title or Description" = "Title or Description"; +"Title, category or location" = "Title, category or location"; +"Entire content" = "Entire content"; "Search" = "Search"; "Search attendees" = "Search attendees"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Confidential task)"; "Links to this Calendar" = "Links to this Calendar"; "Authenticated User Access" = "Authenticated User Access"; -"CalDAV URL" = "CalDAV URL"; +"CalDAV URL" = "CalDAV URL: "; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 4a044df33..42cdb99f7 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Créer un nouvel événement"; "Create a new task" = "Créer une nouvelle tâche"; "Edit this event or task" = "Modifier l'événement ou la tâche sélectionnée"; +"Print the current calendar view" = "Imprimer la vue courante du calendrier"; "Delete this event or task" = "Supprimer l'événement ou la tâche sélectionnée"; "Go to today" = "Revenir à la date d'aujourd'hui"; "Switch to day view" = "Afficher la journée"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Nom de l'agenda"; "new" = "Nouveau"; -"printview" = "Version imprimable"; +"Print view" = "Imprimer"; "edit" = "Éditer"; "delete" = "Supprimer"; "proposal" = "Recherche de plages horaires"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Cacher les invitations refusées"; "Show already accepted and rejected appointments" = "Montrer aussi les invitations refusées"; +/* Print view */ + +"LIST" = "Liste"; +"Print Settings" = "Paramètres d'impression"; +"Title:" = "Titre :"; +"Layout:" = "Disposition :"; +"What to Print" = "Contenu"; +"Options" = "Options"; +"Tasks with no due date" = "Tâches sans date d'échéance"; +"Completed tasks" = "Tâches complétées"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Tous les événements futurs"; "view_selectedday" = "Jour courant"; +"view_not_started" = "Tâches non-commencées"; +"view_overdue" = "Tâches en retard"; +"view_incomplete" = "Tâches à compléter"; + "View:" = "Voir :"; -"Title or Description" = "Le titre ou la description"; +"Title, category or location" = "Titre, catégorie ou lieu"; +"Entire content" = "Tout le contenu"; "Search" = "Rechercher"; "Search attendees" = "Recherche de participants"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Tâche confidentielle)"; "Links to this Calendar" = "Liens vers cet agenda"; "Authenticated User Access" = "Accès aux utilisateurs authentifiés"; -"CalDAV URL" = "Accès en CalDAV"; +"CalDAV URL" = "Accès en CalDAV :"; "WebDAV ICS URL" = "Représentation ICS en WebDAV"; "WebDAV XML URL" = "Représentation XML en WebDAV"; diff --git a/UI/Scheduler/GNUmakefile b/UI/Scheduler/GNUmakefile index c3bc3b3d7..9f96857a6 100644 --- a/UI/Scheduler/GNUmakefile +++ b/UI/Scheduler/GNUmakefile @@ -23,6 +23,7 @@ SchedulerUI_OBJC_FILES = \ UIxCalFolderActions.m \ \ UIxCalView.m \ + UIxCalViewPrint.m \ UIxCalDayView.m \ UIxCalMulticolumnDayView.m \ UIxCalWeekView.m \ diff --git a/UI/Scheduler/Hungarian.lproj/Localizable.strings b/UI/Scheduler/Hungarian.lproj/Localizable.strings index 24f2382fb..d5923b66b 100644 --- a/UI/Scheduler/Hungarian.lproj/Localizable.strings +++ b/UI/Scheduler/Hungarian.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Új esemény hozzáadása"; "Create a new task" = "Új feladat hozzáadása"; "Edit this event or task" = "Esemény vagy feladat szerkesztése"; +"Print the current calendar view" = "Aktuális naptár nézet nyomtatása"; "Delete this event or task" = "Esemény vagy feladat törlése"; "Go to today" = "Ugrás a mai napra"; "Switch to day view" = "Napi nézetre váltás"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "A naptár neve:"; "new" = "Új"; -"printview" = "Nyomtatási nézet"; +"Print view" = "Nyomtatási nézet"; "edit" = "Szerkesztés"; "delete" = "Törlés"; "proposal" = "Javaslat"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Már elfogadott, valamint visszautasított találkozók elrejtése."; "Show already accepted and rejected appointments" = "Már elfogadott, valamint visszautasított találkozók megmutatása"; +/* Print view */ + +"LIST" = "Lista"; +"Print Settings" = "Nyomtatási beállítások"; +"Title:" = "Cím:"; +"Layout:" = "Elrendezés:"; +"What to Print" = "Mi kerüljön nyomtatásra"; +"Options" = "Beállítások"; +"Tasks with no due date" = "Befejező dátum nélküli feladatok"; +"Completed tasks" = "Befejezett feladatok"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Összes jövőbeni esemény"; "view_selectedday" = "Kijelölt nap"; +"view_not_started" = "Nem megkezdett feladatok"; +"view_overdue" = "Lejárt feladatok"; +"view_incomplete" = "Befejezetlen feladat"; + "View:" = "Nézet:"; -"Title or Description" = "Cím vagy leírás"; +"Title, category or location" = "Cím, kategória vagy helyszin"; +"Entire content" = "Teljes tartalom"; "Search" = "Keresés"; "Search attendees" = "Résztvevők keresése"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Bizalmas feladat)"; "Links to this Calendar" = "Hivatkozások ehhez a naptárhoz"; "Authenticated User Access" = "Belépett felhasználók"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "CalDAV URL:"; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/Polish.lproj/Localizable.strings b/UI/Scheduler/Polish.lproj/Localizable.strings index 633957780..f4975b374 100644 --- a/UI/Scheduler/Polish.lproj/Localizable.strings +++ b/UI/Scheduler/Polish.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Utwórz nowe wydarzenie"; "Create a new task" = "Utwórz nowe zadanie"; "Edit this event or task" = "Edytuj to wydarzenie lub zadanie"; +"Print the current calendar view" = "Wydrukuj bieżący widok kalendarza"; "Delete this event or task" = "Usuń to wydarzenie lub zadanie"; "Go to today" = "Przejdź do dnia dzisiejszego"; "Switch to day view" = "Przełącz na widok dnia"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Nazwa kalendarza"; "new" = "Nowy"; -"printview" = "Widok wydruku"; +"Print view" = "Widok wydruku"; "edit" = "Edytuj"; "delete" = "Usuń"; "proposal" = "Propozycja"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Ukryj zaakceptowane i odrzucone spoktania"; "Show already accepted and rejected appointments" = "Pokaż zaakceptowane i odrzucone spotkania"; +/* Print view */ + +"LIST" = "Lista"; +"Print Settings" = "Ustawienia wydruku"; +"Title:" = "Tytuł:"; +"Layout:" = "Układ:"; +"What to Print" = "Elementy do wydruku"; +"Options" = "Opcje"; +"Tasks with no due date" = "Zadania bez terminu"; +"Completed tasks" = "Zadania ukończone"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "wszystkie przyszłe wydarzenia"; "view_selectedday" = "Zaznaczony dzień"; +"view_not_started" = "Zadania nie rozpoczęte"; +"view_overdue" = "Zadania zaległe"; +"view_incomplete" = "Zadania nieukończone"; + "View:" = "Widok:"; -"Title or Description" = "Tytuł lub opis"; +"Title, category or location" = "Tytuł, kategoria lub położenie"; +"Entire content" = "Cała zawartość"; "Search" = "Szukaj"; "Search attendees" = "Szukaj uczestników"; @@ -416,8 +432,8 @@ validate_endbeforestart = "Podana data końca jest wcześniejsza niż data po "Workweek days only" = "Tylko dni powszednie"; "Tasks in View" = "Zadania w widoku"; -"eventDeleteConfirmation" = "Usunięcie tego wydarzenia będzie trwałe."; -"taskDeleteConfirmation" = "Usunięcie tego zadania będzie trwałe."; +"eventDeleteConfirmation" = "Następujące wydarzenia zostaną usunięte:"; +"taskDeleteConfirmation" = "Następujące zadania zostaną usunięte:"; "Would you like to continue?" = "Czy chcesz kontynuować?"; "You cannot remove nor unsubscribe from your personal calendar." @@ -506,7 +522,7 @@ vtodo_class2 = "(Zadanie poufne)"; "Links to this Calendar" = "Odnośniki do tego kalendarza"; "Authenticated User Access" = "Dostęp dla zalogowanych użytkowników"; -"CalDAV URL" = "CalDAV URL"; +"CalDAV URL" = "CalDAV URL:"; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/Russian.lproj/Localizable.strings b/UI/Scheduler/Russian.lproj/Localizable.strings index 992c40d15..00e85e0ee 100644 --- a/UI/Scheduler/Russian.lproj/Localizable.strings +++ b/UI/Scheduler/Russian.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Создать новое событие"; "Create a new task" = "Создать новую задачу"; "Edit this event or task" = "Редактировать это событие или задачу"; +"Print the current calendar view" = "Напечатать календарь в этом виде"; "Delete this event or task" = "Удалить это событие или задачу"; "Go to today" = "Перейти на сегодня"; "Switch to day view" = "Перейти к обзору дня"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Название календаря"; "new" = "Новый"; -"printview" = "Печатать"; +"Print view" = "Печать видимой области"; "edit" = "Изменить"; "delete" = "Удалить"; "proposal" = "Предложение"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Скрыть согласившихся и отказавшихся."; "Show already accepted and rejected appointments" = "Показать согласившихся и отказавшихся."; +/* Print view */ + +"LIST" = "Список"; +"Print Settings" = "Настройки печати"; +"Title:" = "Заголовок:"; +"Layout:" = "Разметка:"; +"What to Print" = "Что печатать"; +"Options" = "Опции"; +"Tasks with no due date" = "Задачи без указания времени окончания"; +"Completed tasks" = "Выполненные задачи"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Все будущие мероприятия"; "view_selectedday" = "Выбранный день"; +"view_not_started" = "Не начатые задачи"; +"view_overdue" = "Просроченные задачи"; +"view_incomplete" = "Незаконченные задачи"; + "View:" = "Вид:"; -"Title or Description" = "Заголовок или описание"; +"Title, category or location" = "Заголовок, категория или место"; +"Entire content" = "Всё содержимое"; "Search" = "Поиск"; "Search attendees" = "Поиск участников"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Конфиденциальное задание)"; "Links to this Calendar" = "Ссылки на этот календарь"; "Authenticated User Access" = "Доступ авторизированных пользователей"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "CalDAV URL: "; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/Slovak.lproj/Localizable.strings b/UI/Scheduler/Slovak.lproj/Localizable.strings index d88998233..be50e57cb 100644 --- a/UI/Scheduler/Slovak.lproj/Localizable.strings +++ b/UI/Scheduler/Slovak.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Vytvoriť novú udalosť"; "Create a new task" = "Vytvoriť novú úlohu"; "Edit this event or task" = "Upraviť túto udalosť alebo úlohu"; +"Print the current calendar view" = "Vytlačiť aktuálny náhľad kalendára"; "Delete this event or task" = "Odstrániť túto udalosť alebo úlohu"; "Go to today" = "Prejsť na dnešný deň"; "Switch to day view" = "Prepnúť na denné zobrazenie"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Názov kalendára"; "new" = "Nový"; -"printview" = "Náhľad tlače"; +"Print view" = "Náhľad tlače"; "edit" = "Upraviť"; "delete" = "Odstrániť"; "proposal" = "Návrh"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Skryť prijaté a odmietnuté schôdzky"; "Show already accepted and rejected appointments" = "Zobraziť prijaté a odmietnuté schôdzky"; +/* Print view */ + +"LIST" = "Zoznam"; +"Print Settings" = "Nastavenia tlače"; +"Title:" = "Názov:"; +"Layout:" = "Zobrazenie:"; +"What to Print" = "Čo chcete tlačiť"; +"Options" = "Možnosti"; +"Tasks with no due date" = "Úlohy bez časového obmedzenia"; +"Completed tasks" = "Dokončené úlohy"; /* Appointments */ @@ -179,6 +190,8 @@ "Reminder:" = "Pripomenutie:"; "General:" = "Hlavný:"; "Reply:" = "Odpoveď:"; +"Created by:" = "Vytvorené:"; + "Target:" = "Cieľ:"; @@ -223,8 +236,13 @@ "view_future" = "Všetky budúce udalosti"; "view_selectedday" = "Zvolený deň"; +"view_not_started" = "Nezačaté úlohy"; +"view_overdue" = "Vypršané úlohy"; +"view_incomplete" = "Nedokončené úlohy"; + "View:" = "Zobraziť:"; -"Title or Description" = "Názov alebo popis"; +"Title, category or location" = "Názov, kategória alebo miesto"; +"Entire content" = "Celý obsah"; "Search" = "Vyhľadať"; "Search attendees" = "Vyhľadať účastníkov"; @@ -414,8 +432,8 @@ validate_endbeforestart = "Zadaný dátum konca je pred začiatkom udalosti." "Workweek days only" = "Len pracovné dni"; "Tasks in View" = "Zobrazené úlohy"; -"eventDeleteConfirmation" = "Nasledujúca udalosť(i) bude odstránená:"; -"taskDeleteConfirmation" = "Odstránenie nasledujúcej úlohy(oh) je nevratné."; +"eventDeleteConfirmation" = "Táto udalosť(i) bude odstránená:"; +"taskDeleteConfirmation" = "Táto udalosť(i) bude odstránená:"; "Would you like to continue?" = "Chcete pokračovať?"; "You cannot remove nor unsubscribe from your personal calendar." @@ -504,7 +522,7 @@ vtodo_class2 = "(Dôverná úloha)"; "Links to this Calendar" = "Odkazy na tento kalendár"; "Authenticated User Access" = "Prístup pre overeného užívateľa"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "CalDAV url:"; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings index dc5390d14..893f9d4dd 100644 --- a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Crear un nuevo evento"; "Create a new task" = "Crear una nueva tarea"; "Edit this event or task" = "Modificar éste evento o tarea"; +"Print the current calendar view" = "Imprimir la vista actual del calendario"; "Delete this event or task" = "Borrar éste evento o tarea"; "Go to today" = "Ir a hoy"; "Switch to day view" = "Cambiar a vista diaria"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Nombre del calendario"; "new" = "Nuevo"; -"printview" = "Vista previa"; +"Print view" = "Imprimir esta vista"; "edit" = "Modificar"; "delete" = "Borrar"; "proposal" = "Propuesta"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Ocultar eventos ya confirmados o rechazados"; "Show already accepted and rejected appointments" = "Mostrar eventos ya confirmados o rechazados"; +/* Print view */ + +"LIST" = "Lista"; +"Print Settings" = "Opciones de impresión"; +"Title:" = "Título:"; +"Layout:" = "Disposición:"; +"What to Print" = "Qué imprimir"; +"Options" = "Opciones"; +"Tasks with no due date" = "Tareas sin fecha de vencimiento"; +"Completed tasks" = "Tareas completadas"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Todos los eventos futuros"; "view_selectedday" = "Día seleccionado"; +"view_not_started" = "Tareas que no han comenzado"; +"view_overdue" = "Tareas que han vencido"; +"view_incomplete" = "Tareas incompletas"; + "View:" = "Ver:"; -"Title or Description" = "Título o descripción"; +"Title, category or location" = "Título, categoría o lugar"; +"Entire content" = "Contenido completo"; "Search" = "Buscar"; "Search attendees" = "Buscar asistentes"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Tarea confidencial)"; "Links to this Calendar" = "Vínculos a éste calendario"; "Authenticated User Access" = "Acceso a usuario autenticado"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "URL CalDAV:"; "WebDAV ICS URL" = "URL ICS WebDAV"; "WebDAV XML URL" = "URL XML WebDAV"; diff --git a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings index aca45c2cb..7756be8d8 100644 --- a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings @@ -5,6 +5,7 @@ "Create a new event" = "Crear un nuevo evento"; "Create a new task" = "Crear una nueva tarea"; "Edit this event or task" = "Modificar éste evento o tarea"; +"Print the current calendar view" = "Imprimir la vista actual del calendario"; "Delete this event or task" = "Borrar éste evento o tarea"; "Go to today" = "Ir a hoy"; "Switch to day view" = "Cambiar a vista diaria"; @@ -117,7 +118,7 @@ "Name of the Calendar" = "Nombre del calendario"; "new" = "Nuevo"; -"printview" = "Vista previa"; +"Print view" = "Vista previa"; "edit" = "Modificar"; "delete" = "Borrar"; "proposal" = "Propuesta"; @@ -145,6 +146,16 @@ "Hide already accepted and rejected appointments" = "Ocultar eventos ya confirmados o rechazados"; "Show already accepted and rejected appointments" = "Mostrar eventos ya confirmados o rechazados"; +/* Print view */ + +"LIST" = "Lista"; +"Print Settings" = "Opciones de impresión"; +"Title:" = "Título:"; +"Layout:" = "Disposición"; +"What to Print" = "Que imprimir"; +"Options" = "Parametros"; +"Tasks with no due date" = "Tareas sin fecha de vencimiento"; +"Completed tasks" = "Tareas Finalizadas"; /* Appointments */ @@ -225,8 +236,13 @@ "view_future" = "Todos los eventos futuros"; "view_selectedday" = "Día seleccionado"; +"view_not_started" = "Tareas sin iniciar"; +"view_overdue" = "Tareas vencidas"; +"view_incomplete" = "Tareas Incompletas"; + "View:" = "Ver:"; -"Title or Description" = "Título o descripción"; +"Title, category or location" = "Titulo, Categoría o localización"; +"Entire content" = "Contenido Completo"; "Search" = "Buscar"; "Search attendees" = "Buscar asistentes"; @@ -506,7 +522,7 @@ vtodo_class2 = "(Tarea confidencial)"; "Links to this Calendar" = "Vínculos a éste calendario"; "Authenticated User Access" = "Acceso a usuario autenticado"; -"CalDAV URL" = "CalDAV url"; +"CalDAV URL" = "URL CalDAV:"; "WebDAV ICS URL" = "WebDAV ICS URL"; "WebDAV XML URL" = "WebDAV XML URL"; diff --git a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar index 8091a7053..54a5eae10 100644 --- a/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar +++ b/UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar @@ -37,6 +37,12 @@ onclick = "return onMonthOverview();"; image = "month-view.png"; tooltip = "Switch to month view"; } ), + ( { link = "#"; + onclick = "return printView();"; + cssClass = "tbicon_print single-window-not-conditional"; + image = "tb-mail-print-flat-24x24.png"; + label = "Print view"; + tooltip = "Print the current calendar view"; } ), ( { link = "#"; label="Delete"; onclick = "return deleteEvent(this);"; diff --git a/UI/Scheduler/UIxCalFilterPanel.m b/UI/Scheduler/UIxCalFilterPanel.m index c9ebf5be9..21a5b2a57 100644 --- a/UI/Scheduler/UIxCalFilterPanel.m +++ b/UI/Scheduler/UIxCalFilterPanel.m @@ -39,14 +39,14 @@ static NSArray *filters = nil; + (void) initialize { static NSString *quals[] - = { // @"view_all", + = {@"view_all", @"view_today", @"view_next7", @"view_next14", @"view_next31", @"view_thismonth", @"view_future", @"view_selectedday" }; if (!filters) { - filters = [NSArray arrayWithObjects: quals count: 7]; + filters = [NSArray arrayWithObjects: quals count: 8]; [filters retain]; } } diff --git a/UI/Scheduler/UIxCalListingActions.h b/UI/Scheduler/UIxCalListingActions.h index 02c13488e..4f12d8cfe 100644 --- a/UI/Scheduler/UIxCalListingActions.h +++ b/UI/Scheduler/UIxCalListingActions.h @@ -40,7 +40,8 @@ NSMutableDictionary *componentsData; NSCalendarDate *startDate; NSCalendarDate *endDate; - NSString *title; + NSString *value; + NSString *criteria; NSString *userLogin; BOOL dayBasedView; WORequest *request; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 5f99777a7..62eaac684 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -37,6 +37,8 @@ #import #import #import +#import +#import #import #import @@ -70,57 +72,57 @@ static NSArray *tasksFields = nil; #define quarterLength 900 // number of seconds in 15 minutes #define offsetHours (24 * 5) // number of hours in invitation window #define offsetSeconds (offsetHours * 60 * 60) // number of seconds in - // invitation window +// invitation window /* 1 block = 15 minutes */ #define offsetBlocks (offsetHours * 4) // number of 15-minute blocks in invitation window #define maxBlocks (offsetBlocks * 2) // maximum number of blocks to search - // for a free slot (10 days) +// for a free slot (10 days) @implementation UIxCalListingActions + (void) initialize { if (!eventsFields) - { - eventsFields = [NSArray arrayWithObjects: @"c_name", @"c_folder", - @"calendarName", - @"c_status", @"c_title", @"c_startdate", - @"c_enddate", @"c_location", @"c_isallday", - @"c_classification", @"c_category", - @"c_partmails", @"c_partstates", @"c_owner", - @"c_iscycle", @"c_nextalarm", - @"c_recurrence_id", @"isException", @"editable", - @"erasable", @"ownerIsOrganizer", nil]; - [eventsFields retain]; - } + { + eventsFields = [NSArray arrayWithObjects: @"c_name", @"c_folder", + @"calendarName", + @"c_status", @"c_title", @"c_startdate", + @"c_enddate", @"c_location", @"c_isallday", + @"c_classification", @"c_category", + @"c_partmails", @"c_partstates", @"c_owner", + @"c_iscycle", @"c_nextalarm", + @"c_recurrence_id", @"isException", @"editable", + @"erasable", @"ownerIsOrganizer", nil]; + [eventsFields retain]; + } if (!tasksFields) - { - tasksFields = [NSArray arrayWithObjects: @"c_name", @"c_folder", - @"calendarName", - @"c_status", @"c_title", @"c_enddate", - @"c_classification", @"c_location", @"c_category", - @"editable", @"erasable", - @"c_priority", nil]; - [tasksFields retain]; - } + { + tasksFields = [NSArray arrayWithObjects: @"c_name", @"c_folder", + @"calendarName", + @"c_status", @"c_title", @"c_enddate", + @"c_classification", @"c_location", @"c_category", + @"editable", @"erasable", + @"c_priority", nil]; + [tasksFields retain]; + } } - (id) initWithRequest: (WORequest *) newRequest { SOGoUser *user; - + if ((self = [super initWithRequest: newRequest])) - { - componentsData = [NSMutableDictionary new]; - startDate = nil; - endDate = nil; - ASSIGN (request, newRequest); - user = [[self context] activeUser]; - ASSIGN (dateFormatter, [user dateFormatterInContext: context]); - ASSIGN (userTimeZone, [[user userDefaults] timeZone]); - dayBasedView = NO; - } - + { + componentsData = [NSMutableDictionary new]; + startDate = nil; + endDate = nil; + ASSIGN (request, newRequest); + user = [[self context] activeUser]; + ASSIGN (dateFormatter, [user dateFormatterInContext: context]); + ASSIGN (userTimeZone, [[user userDefaults] timeZone]); + dayBasedView = NO; + } + return self; } @@ -134,121 +136,122 @@ static NSArray *tasksFields = nil; } - (void) _setupDatesWithPopup: (NSString *) popupValue - andUserTZ: (NSTimeZone *) userTZ + andUserTZ: (NSTimeZone *) userTZ { NSCalendarDate *newDate; NSString *param; - + if ([popupValue isEqualToString: @"view_today"]) - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [newDate beginOfDay]; - endDate = [newDate endOfDay]; - } + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [newDate beginOfDay]; + endDate = [newDate endOfDay]; + } else if ([popupValue isEqualToString: @"view_all"]) - { - startDate = nil; - endDate = nil; - } + { + startDate = nil; + endDate = nil; + } else if ([popupValue isEqualToString: @"view_next7"]) - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [newDate beginOfDay]; - endDate = [[startDate dateByAddingYears: 0 months: 0 days: 6] endOfDay]; - } + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [newDate beginOfDay]; + endDate = [[startDate dateByAddingYears: 0 months: 0 days: 6] endOfDay]; + } else if ([popupValue isEqualToString: @"view_next14"]) - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [newDate beginOfDay]; - endDate = [[startDate dateByAddingYears: 0 months: 0 days: 13] endOfDay]; - } + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [newDate beginOfDay]; + endDate = [[startDate dateByAddingYears: 0 months: 0 days: 13] endOfDay]; + } else if ([popupValue isEqualToString: @"view_next31"]) - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [newDate beginOfDay]; - endDate = [[startDate dateByAddingYears: 0 months: 0 days: 30] endOfDay]; - } + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [newDate beginOfDay]; + endDate = [[startDate dateByAddingYears: 0 months: 0 days: 30] endOfDay]; + } else if ([popupValue isEqualToString: @"view_thismonth"]) - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [[newDate firstDayOfMonth] beginOfDay]; - endDate = [[newDate lastDayOfMonth] endOfDay]; - } + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [[newDate firstDayOfMonth] beginOfDay]; + endDate = [[newDate lastDayOfMonth] endOfDay]; + } else if ([popupValue isEqualToString: @"view_future"]) + { + newDate = [NSCalendarDate calendarDate]; + [newDate setTimeZone: userTZ]; + startDate = [newDate beginOfDay]; + endDate = nil; + } + else if ([popupValue isEqualToString: @"view_selectedday"]) + { + param = [request formValueForKey: @"day"]; + if ([param length] > 0) + startDate = [[NSCalendarDate dateFromShortDateString: param + andShortTimeString: nil + inTimeZone: userTZ] beginOfDay]; + else { newDate = [NSCalendarDate calendarDate]; [newDate setTimeZone: userTZ]; startDate = [newDate beginOfDay]; - endDate = nil; - } - else if ([popupValue isEqualToString: @"view_selectedday"]) - { - param = [request formValueForKey: @"day"]; - if ([param length] > 0) - startDate = [[NSCalendarDate dateFromShortDateString: param - andShortTimeString: nil - inTimeZone: userTZ] beginOfDay]; - else - { - newDate = [NSCalendarDate calendarDate]; - [newDate setTimeZone: userTZ]; - startDate = [newDate beginOfDay]; - } - endDate = [startDate endOfDay]; } + endDate = [startDate endOfDay]; + } } - (void) _setupContext { SOGoUser *user; NSString *param; - + user = [context activeUser]; userLogin = [user login]; - - title = [request formValueForKey: @"search"]; + + criteria = [request formValueForKey: @"search"]; + value = [request formValueForKey: @"value"]; param = [request formValueForKey: @"filterpopup"]; if ([param length]) - { - [self _setupDatesWithPopup: param andUserTZ: userTimeZone]; - } + { + [self _setupDatesWithPopup: param andUserTZ: userTimeZone]; + } else - { - param = [request formValueForKey: @"sd"]; - if ([param length] > 0) - startDate = [[NSCalendarDate dateFromShortDateString: param - andShortTimeString: nil - inTimeZone: userTimeZone] beginOfDay]; - else - startDate = nil; - - param = [request formValueForKey: @"ed"]; - if ([param length] > 0) - endDate = [[NSCalendarDate dateFromShortDateString: param - andShortTimeString: nil - inTimeZone: userTimeZone] endOfDay]; - else - endDate = nil; - - param = [request formValueForKey: @"view"]; - dayBasedView = ![param isEqualToString: @"monthview"]; - } + { + param = [request formValueForKey: @"sd"]; + if ([param length] > 0) + startDate = [[NSCalendarDate dateFromShortDateString: param + andShortTimeString: nil + inTimeZone: userTimeZone] beginOfDay]; + else + startDate = nil; + + param = [request formValueForKey: @"ed"]; + if ([param length] > 0) + endDate = [[NSCalendarDate dateFromShortDateString: param + andShortTimeString: nil + inTimeZone: userTimeZone] endOfDay]; + else + endDate = nil; + + param = [request formValueForKey: @"view"]; + dayBasedView = ![param isEqualToString: @"monthview"]; + } } - (void) _fixComponentTitle: (NSMutableDictionary *) component withType: (NSString *) type { NSString *labelKey; - + labelKey = [NSString stringWithFormat: @"%@_class%@", - type, [component objectForKey: @"c_classification"]]; + type, [component objectForKey: @"c_classification"]]; [component setObject: [self labelForKey: labelKey] - forKey: @"c_title"]; + forKey: @"c_title"]; } /* @@ -264,173 +267,235 @@ static NSArray *tasksFields = nil; int daylightOffset; unsigned int count; static NSString *fields[] = { @"startDate", @"c_startdate", - @"endDate", @"c_enddate" }; + @"endDate", @"c_enddate" }; /* WARNING: This condition has been put and removed many times, please leave - it. Here is the story... - If _fixDates: is conditional to dayBasedView, the recurrences are computed - properly but the display time is wrong. - If _fixDates: is non-conditional, the reverse occurs. - If only this part of _fixDates: is conditional, both are right. - - Regarding all day events, we need to execute this code no matter what the - date and the view are, otherwise the event will span on two days. - - ref bugs: - http://www.sogo.nu/bugs/view.php?id=909 - http://www.sogo.nu/bugs/view.php?id=678 - ... - */ + it. Here is the story... + If _fixDates: is conditional to dayBasedView, the recurrences are computed + properly but the display time is wrong. + If _fixDates: is non-conditional, the reverse occurs. + If only this part of _fixDates: is conditional, both are right. + + Regarding all day events, we need to execute this code no matter what the + date and the view are, otherwise the event will span on two days. + + ref bugs: + http://www.sogo.nu/bugs/view.php?id=909 + http://www.sogo.nu/bugs/view.php?id=678 + ... + */ //NSLog(@"***[UIxCalListingActions _fixDates:] %@", [theRecord objectForKey: @"c_title"]); if (dayBasedView || [[theRecord objectForKey: @"c_isallday"] boolValue]) + { + for (count = 0; count < 2; count++) { - for (count = 0; count < 2; count++) - { - aDateField = fields[count * 2]; - aDate = [theRecord objectForKey: aDateField]; - daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate] - - [userTimeZone secondsFromGMTForDate: startDate]); - //NSLog(@"***[UIxCalListingActions _fixDates:] %@ = %@ (%i)", aDateField, aDate, daylightOffset); - if (daylightOffset) - { - aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 - minutes: 0 seconds: daylightOffset]; - [theRecord setObject: aDate forKey: aDateField]; - aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]]; - [theRecord setObject: aDateValue forKey: fields[count * 2 + 1]]; - } - } + aDateField = fields[count * 2]; + aDate = [theRecord objectForKey: aDateField]; + daylightOffset = (int) ([userTimeZone secondsFromGMTForDate: aDate] + - [userTimeZone secondsFromGMTForDate: startDate]); + //NSLog(@"***[UIxCalListingActions _fixDates:] %@ = %@ (%i)", aDateField, aDate, daylightOffset); + if (daylightOffset) + { + aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 + minutes: 0 seconds: daylightOffset]; + [theRecord setObject: aDate forKey: aDateField]; + aDateValue = [NSNumber numberWithInt: [aDate timeIntervalSince1970]]; + [theRecord setObject: aDateValue forKey: fields[count * 2 + 1]]; + } } + } } - (NSArray *) _fetchFields: (NSArray *) fields - forComponentOfType: (NSString *) component + forComponentOfType: (NSString *) component { NSEnumerator *folders, *currentInfos; - SOGoAppointmentFolder *currentFolder; NSMutableDictionary *newInfo; - NSMutableArray *infos, *newInfoForComponent; + NSMutableArray *infos, *newInfoForComponent, *quickInfos, *allInfos, *quickInfosName; NSNull *marker; + NSString *owner, *role, *calendarName, *filters, *iCalString; + NSRange match; + iCalCalendar *calendar; + iCalObject *master; + SOGoAppointmentFolder *currentFolder; SOGoAppointmentFolders *clientObject; SOGoUser *ownerUser; - NSString *owner, *role, *calendarName; - BOOL isErasable, folderIsRemote; + + BOOL isErasable, folderIsRemote, quickInfosFlag = NO; id currentInfo; int i, count; - + infos = [NSMutableArray array]; marker = [NSNull null]; clientObject = [self clientObject]; - + folders = [[clientObject subFolders] objectEnumerator]; while ((currentFolder = [folders nextObject])) + { + if ([currentFolder isActive]) { - if ([currentFolder isActive]) + folderIsRemote = [currentFolder isKindOfClass: [SOGoWebAppointmentFolder class]]; + + if ([criteria isEqualToString:@"title_Category_Location"]) + currentInfos = [[currentFolder fetchCoreInfosFrom: startDate + to: endDate + title: value + component: component + additionalFilters: criteria] objectEnumerator]; + + else if ([criteria isEqualToString:@"entireContent"]) + { + // First research : Through the quick table inside the location, category and title columns + quickInfos = [currentFolder fetchCoreInfosFrom: startDate + to: endDate + title: value + component: component + additionalFilters: criteria]; + + // Save the c_name in another array to compare with + if ([quickInfos count] > 0) { - folderIsRemote - = [currentFolder isKindOfClass: [SOGoWebAppointmentFolder class]]; - currentInfos - = [[currentFolder fetchCoreInfosFrom: startDate - to: endDate - title: title - component: component] objectEnumerator]; - owner = [currentFolder ownerInContext: context]; - ownerUser = [SOGoUser userWithLogin: owner]; - isErasable = ([owner isEqualToString: userLogin] - || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]); - while ((newInfo = [currentInfos nextObject])) + quickInfosFlag = YES; + quickInfosName = [NSMutableArray arrayWithCapacity:[quickInfos count]]; + for (i = 0; i < [quickInfos count]; i++) + [quickInfosName addObject:[[quickInfos objectAtIndex:i] objectForKey:@"c_name"]]; + } + + // Second research : Every objects except for those already in the quickInfos array + allInfos = [currentFolder fetchCoreInfosFrom: startDate + to: endDate + title: nil + component: component]; + if (quickInfosFlag == YES) + { + for (i = ([allInfos count] - 1); i >= 0 ; i--) { + if([quickInfosName containsObject:[[allInfos objectAtIndex:i] objectForKey:@"c_name"]]) + [allInfos removeObjectAtIndex:i]; + } + } + + + for (i = 0; i < [allInfos count]; i++) + { + iCalString = [[allInfos objectAtIndex:i] objectForKey:@"c_content"]; + calendar = [iCalCalendar parseSingleFromSource: iCalString]; + master = [calendar firstChildWithTag:component]; + if (master) { + if ([[master comment] length] > 0) { - if ([fields containsObject: @"editable"]) - { - if (folderIsRemote) - // .ics subscriptions are not editable - [newInfo setObject: [NSNumber numberWithInt: 0] - forKey: @"editable"]; - else - { - // Identifies whether the active user can edit the event. - role = - [currentFolder roleForComponentsWithAccessClass: - [[newInfo objectForKey: @"c_classification"] intValue] - forUser: userLogin]; - if ([role isEqualToString: @"ComponentModifier"] - || [role length] == 0) - [newInfo setObject: [NSNumber numberWithInt: 1] - forKey: @"editable"]; - else - [newInfo setObject: [NSNumber numberWithInt: 0] - forKey: @"editable"]; - } - } - if ([fields containsObject: @"ownerIsOrganizer"]) - { - // Identifies whether the active user is the organizer - // of this event. - NSString *c_orgmail; - c_orgmail = [newInfo objectForKey: @"c_orgmail"]; + match = [[master comment] rangeOfString:value options:NSCaseInsensitiveSearch]; + if (match.length > 0) { + [quickInfos addObject:[allInfos objectAtIndex:i]]; + } + } + } + } + + currentInfos = [quickInfos objectEnumerator]; + } + - if ([c_orgmail isKindOfClass: [NSString class]] && [ownerUser hasEmail: c_orgmail]) - [newInfo setObject: [NSNumber numberWithInt: 1] - forKey: @"ownerIsOrganizer"]; - else - [newInfo setObject: [NSNumber numberWithInt: 0] - forKey: @"ownerIsOrganizer"]; - } + else + currentInfos = [[currentFolder fetchCoreInfosFrom: startDate + to: endDate + title: value + component: component] objectEnumerator]; + + owner = [currentFolder ownerInContext: context]; + ownerUser = [SOGoUser userWithLogin: owner]; + isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]); + + while ((newInfo = [currentInfos nextObject])) + { + if ([fields containsObject: @"editable"]) + { + if (folderIsRemote) + // .ics subscriptions are not editable + [newInfo setObject: [NSNumber numberWithInt: 0] + forKey: @"editable"]; + else + { + // Identifies whether the active user can edit the event. + role = [currentFolder roleForComponentsWithAccessClass:[[newInfo objectForKey: @"c_classification"] intValue] + forUser: userLogin]; + if ([role isEqualToString: @"ComponentModifier"] || [role length] == 0) + [newInfo setObject: [NSNumber numberWithInt: 1] + forKey: @"editable"]; + else + [newInfo setObject: [NSNumber numberWithInt: 0] + forKey: @"editable"]; + } + } + if ([fields containsObject: @"ownerIsOrganizer"]) + { + // Identifies whether the active user is the organizer + // of this event. + NSString *c_orgmail; + c_orgmail = [newInfo objectForKey: @"c_orgmail"]; + + if ([c_orgmail isKindOfClass: [NSString class]] && [ownerUser hasEmail: c_orgmail]) + [newInfo setObject: [NSNumber numberWithInt: 1] + forKey: @"ownerIsOrganizer"]; + else + [newInfo setObject: [NSNumber numberWithInt: 0] + forKey: @"ownerIsOrganizer"]; + } if (isErasable) - [newInfo setObject: [NSNumber numberWithInt: 1] - forKey: @"erasable"]; + [newInfo setObject: [NSNumber numberWithInt: 1] + forKey: @"erasable"]; else - [newInfo setObject: [NSNumber numberWithInt: 0] - forKey: @"erasable"]; + [newInfo setObject: [NSNumber numberWithInt: 0] + forKey: @"erasable"]; + [newInfo setObject: [currentFolder nameInContainer] - forKey: @"c_folder"]; - [newInfo setObject: [currentFolder ownerInContext: context] - forKey: @"c_owner"]; - calendarName = [currentFolder displayName]; - if (calendarName == nil) - calendarName = @""; - [newInfo setObject: calendarName - forKey: @"calendarName"]; - if (![[newInfo objectForKey: @"c_title"] length]) - [self _fixComponentTitle: newInfo withType: component]; + forKey: @"c_folder"]; + [newInfo setObject: [currentFolder ownerInContext: context] + forKey: @"c_owner"]; + calendarName = [currentFolder displayName]; + if (calendarName == nil) + calendarName = @""; + [newInfo setObject: calendarName + forKey: @"calendarName"]; + if (![[newInfo objectForKey: @"c_title"] length]) + [self _fixComponentTitle: newInfo withType: component]; // Possible improvement: only call _fixDates if event is recurrent // or the view range span a daylight saving time change - [self _fixDates: newInfo]; - newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields - notFoundMarker: marker]]; - // Escape HTML - count = [newInfoForComponent count]; - for (i = 0; i < count; i++) - { - currentInfo = [newInfoForComponent objectAtIndex: i]; - if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)]) - [newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]]; - } - [infos addObject: newInfoForComponent]; - } + [self _fixDates: newInfo]; + newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields + notFoundMarker: marker]]; + // Escape HTML + count = [newInfoForComponent count]; + for (i = 0; i < count; i++) + { + currentInfo = [newInfoForComponent objectAtIndex: i]; + if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)]) + [newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]]; } + [infos addObject: newInfoForComponent]; + } } - + } + return infos; } - (WOResponse *) _responseWithData: (NSArray *) data { WOResponse *response; - + response = [self responseWithStatus: 200]; [response appendContentString: [data jsonRepresentation]]; - + return response; } - (NSString *) _formattedDateForSeconds: (unsigned int) seconds - forAllDay: (BOOL) forAllDay + forAllDay: (BOOL) forAllDay { NSCalendarDate *date; NSString *formattedDate; - + date = [NSCalendarDate dateWithTimeIntervalSince1970: seconds]; // Adjust for daylight saving time? (wrt to startDate) //NSLog(@"***[UIxCalListingActions _formattedDateForSeconds] user timezone is %@", userTimeZone); @@ -439,13 +504,13 @@ static NSArray *tasksFields = nil; formattedDate = [dateFormatter formattedDate: date]; else formattedDate = [dateFormatter formattedDateAndTime: date]; - - return formattedDate; + + return formattedDate; } // // We return: -// +// // [[calendar name (full path), complete Event ID (full path), Fire date (UTC)], ..] // // Called when each module is loaded or whenever a calendar component is created, modified, deleted @@ -466,42 +531,42 @@ static NSArray *tasksFields = nil; NSEnumerator *folders; WOResponse *response; unsigned int browserTime, laterTime; - + // We look for alarms in the next 48 hours browserTime = [[[context request] formValueForKey: @"browserTime"] intValue]; laterTime = browserTime + 60*60*48; clientObject = [self clientObject]; allAlarms = [NSMutableArray array]; - + folders = [[clientObject subFolders] objectEnumerator]; while ((currentFolder = [folders nextObject])) + { + if ([currentFolder isActive] && [currentFolder showCalendarAlarms]) { - if ([currentFolder isActive] && [currentFolder showCalendarAlarms]) - { - NSDictionary *entry; - NSArray *alarms; - BOOL isCycle; - int i; - - alarms = [currentFolder fetchAlarmInfosFrom: [NSNumber numberWithInt: browserTime] - to: [NSNumber numberWithInt: laterTime]]; - - for (i = 0; i < [alarms count]; i++) + NSDictionary *entry; + NSArray *alarms; + BOOL isCycle; + int i; + + alarms = [currentFolder fetchAlarmInfosFrom: [NSNumber numberWithInt: browserTime] + to: [NSNumber numberWithInt: laterTime]]; + + for (i = 0; i < [alarms count]; i++) { entry = [alarms objectAtIndex: i]; isCycle = [[entry objectForKey: @"c_iscycle"] boolValue]; if (!isCycle) - { - [allAlarms addObject: [NSArray arrayWithObjects: - [currentFolder nameInContainer], - [entry objectForKey: @"c_name"], - [entry objectForKey: @"c_nextalarm"], - nil]]; - } + { + [allAlarms addObject: [NSArray arrayWithObjects: + [currentFolder nameInContainer], + [entry objectForKey: @"c_name"], + [entry objectForKey: @"c_nextalarm"], + nil]]; + } } - } } + } response = [self responseWithStatus: 200]; @@ -514,16 +579,16 @@ static NSArray *tasksFields = nil; { NSString *filter; SOGoUserSettings *us; - + filter = [[context request] formValueForKey: @"filterpopup"]; if ([filter length] && ![filter isEqualToString: @"view_all"] && ![filter isEqualToString: @"view_future"]) - { - us = [[context activeUser] userSettings]; - [us setObject: filter forKey: @"CalendarDefaultFilter"]; - [us synchronize]; - } + { + us = [[context activeUser] userSettings]; + [us setObject: filter forKey: @"CalendarDefaultFilter"]; + [us synchronize]; + } } - (WOResponse *) eventsListAction @@ -534,25 +599,25 @@ static NSArray *tasksFields = nil; unsigned int interval; BOOL isAllDay; NSString *sort, *ascending; - + [self _setupContext]; [self checkFilterValue]; - + newEvents = [NSMutableArray array]; events = [[self _fetchFields: eventsFields - forComponentOfType: @"vevent"] objectEnumerator]; + forComponentOfType: @"vevent"] objectEnumerator]; while ((oldEvent = [events nextObject])) - { - newEvent = [NSMutableArray arrayWithArray: oldEvent]; - isAllDay = [[oldEvent objectAtIndex: eventIsAllDayIndex] boolValue]; - interval = [[oldEvent objectAtIndex: eventStartDateIndex] intValue]; - [newEvent addObject: [self _formattedDateForSeconds: interval - forAllDay: isAllDay]]; - interval = [[oldEvent objectAtIndex: eventEndDateIndex] intValue]; - [newEvent addObject: [self _formattedDateForSeconds: interval - forAllDay: isAllDay]]; - [newEvents addObject: newEvent]; - } + { + newEvent = [NSMutableArray arrayWithArray: oldEvent]; + isAllDay = [[oldEvent objectAtIndex: eventIsAllDayIndex] boolValue]; + interval = [[oldEvent objectAtIndex: eventStartDateIndex] intValue]; + [newEvent addObject: [self _formattedDateForSeconds: interval + forAllDay: isAllDay]]; + interval = [[oldEvent objectAtIndex: eventEndDateIndex] intValue]; + [newEvent addObject: [self _formattedDateForSeconds: interval + forAllDay: isAllDay]]; + [newEvents addObject: newEvent]; + } sort = [[context request] formValueForKey: @"sort"]; if ([sort isEqualToString: @"title"]) @@ -565,26 +630,25 @@ static NSArray *tasksFields = nil; [newEvents sortUsingSelector: @selector (compareEventsCalendarNameAscending:)]; else [newEvents sortUsingSelector: @selector (compareEventsStartDateAscending:)]; - + ascending = [[context request] formValueForKey: @"asc"]; if (![ascending boolValue]) [newEvents reverseArray]; - + return [self _responseWithData: newEvents]; } -static inline void -_feedBlockWithDayBasedData (NSMutableDictionary *block, unsigned int start, - unsigned int end, unsigned int dayStart) +static inline void _feedBlockWithDayBasedData (NSMutableDictionary *block, unsigned int start, + unsigned int end, unsigned int dayStart) { unsigned int delta, quarterStart, length, swap; if (start > end) - { - swap = end; - end = start; - start = swap; - } + { + swap = end; + end = start; + start = swap; + } quarterStart = (start - dayStart) / quarterLength; delta = end - dayStart; if ((delta % quarterLength)) @@ -593,9 +657,9 @@ _feedBlockWithDayBasedData (NSMutableDictionary *block, unsigned int start, if (!length) length = 1; [block setObject: [NSNumber numberWithUnsignedInt: quarterStart] - forKey: @"start"]; + forKey: @"start"]; [block setObject: [NSNumber numberWithUnsignedInt: length] - forKey: @"length"]; + forKey: @"length"]; } static inline void @@ -605,26 +669,26 @@ _feedBlockWithMonthBasedData (NSMutableDictionary *block, unsigned int start, { NSCalendarDate *eventStartDate; NSString *startHour; - + eventStartDate = [NSCalendarDate dateWithTimeIntervalSince1970: start]; [eventStartDate setTimeZone: userTimeZone]; startHour = [dateFormatter formattedTime: eventStartDate]; [block setObject: startHour forKey: @"starthour"]; [block setObject: [NSNumber numberWithUnsignedInt: start] - forKey: @"start"]; + forKey: @"start"]; } - (NSMutableDictionary *) _eventBlockWithStart: (unsigned int) start - end: (unsigned int) end - number: (NSNumber *) number - onDay: (unsigned int) dayStart - recurrenceTime: (unsigned int) recurrenceTime - userState: (iCalPersonPartStat) userState + end: (unsigned int) end + number: (NSNumber *) number + onDay: (unsigned int) dayStart + recurrenceTime: (unsigned int) recurrenceTime + userState: (iCalPersonPartStat) userState { NSMutableDictionary *block; - + block = [NSMutableDictionary dictionary]; - + if (dayBasedView) _feedBlockWithDayBasedData (block, start, end, dayStart); else @@ -632,11 +696,11 @@ _feedBlockWithMonthBasedData (NSMutableDictionary *block, unsigned int start, [block setObject: number forKey: @"nbr"]; if (recurrenceTime) [block setObject: [NSNumber numberWithInt: recurrenceTime] - forKey: @"recurrenceTime"]; + forKey: @"recurrenceTime"]; if (userState != iCalPersonPartStatOther) [block setObject: [NSNumber numberWithInt: userState] - forKey: @"userState"]; - + forKey: @"userState"]; + return block; } @@ -648,155 +712,155 @@ _userStateInEvent (NSArray *event) NSString *partList, *stateList; NSArray *participants, *states; SOGoUser *user; - + participants = nil; state = iCalPersonPartStatOther; - + partList = [event objectAtIndex: eventPartMailsIndex]; stateList = [event objectAtIndex: eventPartStatesIndex]; if ([partList length] && [stateList length]) + { + participants = [partList componentsSeparatedByString: @"\n"]; + states = [stateList componentsSeparatedByString: @"\n"]; + count = 0; + max = [participants count]; + while (state == iCalPersonPartStatOther && count < max) { - participants = [partList componentsSeparatedByString: @"\n"]; - states = [stateList componentsSeparatedByString: @"\n"]; - count = 0; - max = [participants count]; - while (state == iCalPersonPartStatOther && count < max) - { - user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex] - roles: nil]; - if ([user hasEmail: [participants objectAtIndex: count]]) - state = [[states objectAtIndex: count] intValue]; - else - count++; - } + user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex] + roles: nil]; + if ([user hasEmail: [participants objectAtIndex: count]]) + state = [[states objectAtIndex: count] intValue]; + else + count++; } - + } + return state; } - (void) _fillBlocks: (NSArray *) blocks - withEvent: (NSArray *) event - withNumber: (NSNumber *) number + withEvent: (NSArray *) event + withNumber: (NSNumber *) number { int currentDayStart, startSecs, endsSecs, currentStart, eventStart, - eventEnd, computedEventEnd, offset, recurrenceTime, swap; + eventEnd, computedEventEnd, offset, recurrenceTime, swap; NSMutableArray *currentDay; NSMutableDictionary *eventBlock; iCalPersonPartStat userState; - + eventStart = [[event objectAtIndex: eventStartDateIndex] intValue]; if (eventStart < 0) [self errorWithFormat: @"event '%@' has negative start: %d (skipped)", - [event objectAtIndex: eventNameIndex], eventStart]; + [event objectAtIndex: eventNameIndex], eventStart]; else + { + eventEnd = [[event objectAtIndex: eventEndDateIndex] intValue]; + if (eventEnd < 0) + [self errorWithFormat: @"event '%@' has negative end: %d (skipped)", + [event objectAtIndex: eventNameIndex], eventEnd]; + else { - eventEnd = [[event objectAtIndex: eventEndDateIndex] intValue]; - if (eventEnd < 0) - [self errorWithFormat: @"event '%@' has negative end: %d (skipped)", - [event objectAtIndex: eventNameIndex], eventEnd]; + if (eventEnd < eventStart) + { + swap = eventStart; + eventStart = eventEnd; + eventEnd = swap; + [self warnWithFormat: @"event '%@' has end < start: %d < %d", + [event objectAtIndex: eventNameIndex], eventEnd, eventStart]; + } + + startSecs = (unsigned int) [startDate timeIntervalSince1970]; + endsSecs = (unsigned int) [endDate timeIntervalSince1970]; + + if ([[event objectAtIndex: eventIsCycleIndex] boolValue]) + recurrenceTime = [[event objectAtIndex: eventRecurrenceIdIndex] unsignedIntValue]; else + recurrenceTime = 0; + + currentStart = eventStart; + if (currentStart < startSecs) + { + currentStart = startSecs; + offset = 0; + } + else + offset = ((currentStart - startSecs) + / dayLength); + if (offset >= [blocks count]) + [self errorWithFormat: @"event '%@' has a computed offset that" + @" overflows the amount of blocks (skipped)", + [event objectAtIndex: eventNameIndex]]; + else + { + currentDay = [blocks objectAtIndex: offset]; + currentDayStart = startSecs + dayLength * offset; + + if (eventEnd > endsSecs) + eventEnd = endsSecs; + + if (eventEnd < startSecs) + // The event doesn't end in the covered period. + // This special case occurs with a DST change. + return; + + userState = _userStateInEvent (event); + while (currentDayStart + dayLength < eventEnd) { - if (eventEnd < eventStart) - { - swap = eventStart; - eventStart = eventEnd; - eventEnd = swap; - [self warnWithFormat: @"event '%@' has end < start: %d < %d", - [event objectAtIndex: eventNameIndex], eventEnd, eventStart]; - } - - startSecs = (unsigned int) [startDate timeIntervalSince1970]; - endsSecs = (unsigned int) [endDate timeIntervalSince1970]; - - if ([[event objectAtIndex: eventIsCycleIndex] boolValue]) - recurrenceTime = [[event objectAtIndex: eventRecurrenceIdIndex] unsignedIntValue]; - else - recurrenceTime = 0; - - currentStart = eventStart; - if (currentStart < startSecs) - { - currentStart = startSecs; - offset = 0; - } - else - offset = ((currentStart - startSecs) - / dayLength); - if (offset >= [blocks count]) - [self errorWithFormat: @"event '%@' has a computed offset that" - @" overflows the amount of blocks (skipped)", - [event objectAtIndex: eventNameIndex]]; - else - { - currentDay = [blocks objectAtIndex: offset]; - currentDayStart = startSecs + dayLength * offset; - - if (eventEnd > endsSecs) - eventEnd = endsSecs; - - if (eventEnd < startSecs) - // The event doesn't end in the covered period. - // This special case occurs with a DST change. - return; - - userState = _userStateInEvent (event); - while (currentDayStart + dayLength < eventEnd) - { - eventBlock = [self _eventBlockWithStart: currentStart - end: currentDayStart + dayLength - 1 - number: number - onDay: currentDayStart - recurrenceTime: recurrenceTime - userState: userState]; - [currentDay addObject: eventBlock]; - currentDayStart += dayLength; - currentStart = currentDayStart; - offset++; - currentDay = [blocks objectAtIndex: offset]; - } - + eventBlock = [self _eventBlockWithStart: currentStart + end: currentDayStart + dayLength - 1 + number: number + onDay: currentDayStart + recurrenceTime: recurrenceTime + userState: userState]; + [currentDay addObject: eventBlock]; + currentDayStart += dayLength; + currentStart = currentDayStart; + offset++; + currentDay = [blocks objectAtIndex: offset]; + } + computedEventEnd = eventEnd; - + // We add 5 mins to the end date of an event if the end date // is equal or smaller than the event's start date. if (eventEnd <= currentStart) - computedEventEnd = currentStart + (5*60); + computedEventEnd = currentStart + (5*60); eventBlock = [self _eventBlockWithStart: currentStart - end: computedEventEnd - number: number - onDay: currentDayStart - recurrenceTime: recurrenceTime - userState: userState]; + end: computedEventEnd + number: number + onDay: currentDayStart + recurrenceTime: recurrenceTime + userState: userState]; [currentDay addObject: eventBlock]; } - } } + } } - (void) _prepareEventBlocks: (NSMutableArray **) blocks - withAllDays: (NSMutableArray **) allDayBlocks + withAllDays: (NSMutableArray **) allDayBlocks { unsigned int count, nbrDays; int seconds; - + seconds = [endDate timeIntervalSinceDate: startDate]; if (seconds > 0) + { + nbrDays = 1 + (unsigned int) (seconds / dayLength); + *blocks = [NSMutableArray arrayWithCapacity: nbrDays]; + *allDayBlocks = [NSMutableArray arrayWithCapacity: nbrDays]; + for (count = 0; count < nbrDays; count++) { - nbrDays = 1 + (unsigned int) (seconds / dayLength); - *blocks = [NSMutableArray arrayWithCapacity: nbrDays]; - *allDayBlocks = [NSMutableArray arrayWithCapacity: nbrDays]; - for (count = 0; count < nbrDays; count++) - { - [*blocks addObject: [NSMutableArray array]]; - [*allDayBlocks addObject: [NSMutableArray array]]; - } + [*blocks addObject: [NSMutableArray array]]; + [*allDayBlocks addObject: [NSMutableArray array]]; } + } else - { - *blocks = nil; - *allDayBlocks = nil; - } + { + *blocks = nil; + *allDayBlocks = nil; + } } - (NSArray *) _horizontalBlocks: (NSMutableArray *) day @@ -805,119 +869,115 @@ _userStateInEvent (NSArray *event) NSMutableArray *currentBlock, *blocks; NSDictionary *currentEvent; unsigned int count, max, qCount, qMax, qOffset; - + blocks = [NSMutableArray array]; - + bzero (quarters, 96 * sizeof (NSMutableArray *)); - + max = [day count]; for (count = 0; count < max; count++) + { + currentEvent = [day objectAtIndex: count]; + qMax = [[currentEvent objectForKey: @"length"] unsignedIntValue]; + qOffset = [[currentEvent objectForKey: @"start"] unsignedIntValue]; + for (qCount = 0; qCount < qMax; qCount++) { - currentEvent = [day objectAtIndex: count]; - qMax = [[currentEvent objectForKey: @"length"] unsignedIntValue]; - qOffset = [[currentEvent objectForKey: @"start"] unsignedIntValue]; - for (qCount = 0; qCount < qMax; qCount++) - { - currentBlock = quarters[qCount + qOffset]; - if (!currentBlock) + currentBlock = quarters[qCount + qOffset]; + if (!currentBlock) { currentBlock = [NSMutableArray array]; quarters[qCount + qOffset] = currentBlock; [blocks addObject: currentBlock]; } - [currentBlock addObject: currentEvent]; - } + [currentBlock addObject: currentEvent]; } - + } + return blocks; } -static inline unsigned int -_computeMaxBlockSiblings (NSArray *block) +static inline unsigned int _computeMaxBlockSiblings (NSArray *block) { unsigned int count, max, maxSiblings, siblings; NSNumber *nbrEvents; - + max = [block count]; maxSiblings = max; for (count = 0; count < max; count++) + { + nbrEvents = [[block objectAtIndex: count] objectForKey: @"siblings"]; + if (nbrEvents) { - nbrEvents = [[block objectAtIndex: count] objectForKey: @"siblings"]; - if (nbrEvents) - { - siblings = [nbrEvents unsignedIntValue]; - if (siblings > maxSiblings) - maxSiblings = siblings; - } + siblings = [nbrEvents unsignedIntValue]; + if (siblings > maxSiblings) + maxSiblings = siblings; } - + } + return maxSiblings; } -static inline void -_propagateBlockSiblings (NSArray *block, NSNumber *maxSiblings) +static inline void _propagateBlockSiblings (NSArray *block, NSNumber *maxSiblings) { unsigned int count, max; NSMutableDictionary *event; NSNumber *realSiblings; - + max = [block count]; realSiblings = [NSNumber numberWithUnsignedInt: max]; for (count = 0; count < max; count++) - { - event = [block objectAtIndex: count]; - [event setObject: maxSiblings forKey: @"siblings"]; - [event setObject: realSiblings forKey: @"realSiblings"]; - } + { + event = [block objectAtIndex: count]; + [event setObject: maxSiblings forKey: @"siblings"]; + [event setObject: realSiblings forKey: @"realSiblings"]; + } } /* this requires two vertical passes */ -static inline void -_computeBlocksSiblings (NSArray *blocks) +static inline void _computeBlocksSiblings (NSArray *blocks) { NSArray *currentBlock; unsigned int count, max, maxSiblings; - + max = [blocks count]; for (count = 0; count < max; count++) - { - currentBlock = [blocks objectAtIndex: count]; - maxSiblings = _computeMaxBlockSiblings (currentBlock); - _propagateBlockSiblings (currentBlock, - [NSNumber numberWithUnsignedInt: maxSiblings]); - } + { + currentBlock = [blocks objectAtIndex: count]; + maxSiblings = _computeMaxBlockSiblings (currentBlock); + _propagateBlockSiblings (currentBlock, + [NSNumber numberWithUnsignedInt: maxSiblings]); + } } -static inline void -_computeBlockPosition (NSArray *block) +static inline void _computeBlockPosition (NSArray *block) { unsigned int count, max, j, siblings; NSNumber *position; NSMutableDictionary *event; NSMutableDictionary **positions; - + max = [block count]; event = [block objectAtIndex: 0]; siblings = [[event objectForKey: @"siblings"] unsignedIntValue]; positions = NSZoneCalloc (NULL, siblings, sizeof (NSMutableDictionary *)); - + for (count = 0; count < max; count++) + { + event = [block objectAtIndex: count]; + position = [event objectForKey: @"position"]; + if (position) + *(positions + [position unsignedIntValue]) = event; + else { - event = [block objectAtIndex: count]; - position = [event objectForKey: @"position"]; - if (position) - *(positions + [position unsignedIntValue]) = event; - else - { - j = 0; - while (j < max && *(positions + j)) - j++; - *(positions + j) = event; - [event setObject: [NSNumber numberWithUnsignedInt: j] - forKey: @"position"]; - } + j = 0; + while (j < max && *(positions + j)) + j++; + *(positions + j) = event; + [event setObject: [NSNumber numberWithUnsignedInt: j] + forKey: @"position"]; } - + } + NSZoneFree (NULL, positions); } @@ -961,22 +1021,22 @@ _computeBlocksPosition (NSArray *blocks) { NSArray *block; unsigned int count, max; -// NSMutableDictionary **positions; - + // NSMutableDictionary **positions; + max = [blocks count]; for (count = 0; count < max; count++) - { - block = [blocks objectAtIndex: count]; - _computeBlockPosition (block); -// _addBlockMultipliers (block, positions); -// NSZoneFree (NULL, positions); - } + { + block = [blocks objectAtIndex: count]; + _computeBlockPosition (block); + // _addBlockMultipliers (block, positions); + // NSZoneFree (NULL, positions); + } } - (void) _addBlocksWidth: (NSMutableArray *) day { NSArray *blocks; - + blocks = [self _horizontalBlocks: day]; _computeBlocksSiblings (blocks); _computeBlocksSiblings (blocks); @@ -991,73 +1051,73 @@ _computeBlocksPosition (NSArray *blocks) NSMutableArray *allDayBlocks, *blocks, *currentDay; NSNumber *eventNbr; BOOL isAllDay; - + [self _setupContext]; - + [self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks]; events = [self _fetchFields: eventsFields - forComponentOfType: @"vevent"]; + forComponentOfType: @"vevent"]; eventsBlocks - = [NSArray arrayWithObjects: events, allDayBlocks, blocks, nil]; + = [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]; - } - + { + 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]; - } - + { + currentDay = [blocks objectAtIndex: count]; + [currentDay sortUsingSelector: @selector (compareEventByStart:)]; + [self _addBlocksWidth: currentDay]; + } + return [self _responseWithData: eventsBlocks]; -// timeIntervalSinceDate: + // timeIntervalSinceDate: } - (NSString *) _getStatusClassForStatusCode: (int) statusCode - andEndDateStamp: (unsigned int) endDateStamp + andEndDateStamp: (unsigned int) endDateStamp { NSCalendarDate *taskDate, *now; NSString *statusClass; - + if (statusCode == 1) statusClass = @"completed"; else + { + if (endDateStamp) { - if (endDateStamp) - { - now = [NSCalendarDate calendarDate]; - taskDate + now = [NSCalendarDate calendarDate]; + taskDate = [NSCalendarDate dateWithTimeIntervalSince1970: endDateStamp]; - [taskDate setTimeZone: userTimeZone]; - if ([taskDate earlierDate: now] == taskDate) - statusClass = @"overdue"; - else - { - if ([taskDate isToday]) - statusClass = @"duetoday"; - else - statusClass = @"duelater"; - } - } + [taskDate setTimeZone: userTimeZone]; + if ([taskDate earlierDate: now] == taskDate) + statusClass = @"overdue"; else - statusClass = @"noduedate"; + { + if ([taskDate isToday]) + statusClass = @"duetoday"; + else + statusClass = @"duelater"; + } } - + else + statusClass = @"noduedate"; + } + return statusClass; } @@ -1065,46 +1125,67 @@ _computeBlocksPosition (NSArray *blocks) { NSMutableArray *filteredTasks, *filteredTask; NSString *sort, *ascending; - NSString *statusFlag; + NSString *statusFlag, *tasksView; SOGoUserSettings *us; NSEnumerator *tasks; NSArray *task; - + unsigned int endDateStamp; BOOL showCompleted; int statusCode; - + int startSecs; + int endsSecs; + filteredTasks = [NSMutableArray array]; - + [self _setupContext]; - + + startSecs = (unsigned int) [startDate timeIntervalSince1970]; + endsSecs = (unsigned int) [endDate timeIntervalSince1970]; + tasksView = [request formValueForKey: @"filterpopup"]; + #warning see TODO in SchedulerUI.js about "setud" showCompleted = [[request formValueForKey: @"show-completed"] intValue]; if ([request formValueForKey: @"setud"]) - { - us = [[context activeUser] userSettings]; - [us setBool: showCompleted forKey: @"ShowCompletedTasks"]; - [us synchronize]; - } - + { + us = [[context activeUser] userSettings]; + [us setBool: showCompleted forKey: @"ShowCompletedTasks"]; + [us synchronize]; + } + tasks = [[self _fetchFields: tasksFields - forComponentOfType: @"vtodo"] objectEnumerator]; + forComponentOfType: @"vtodo"] objectEnumerator]; + while ((task = [tasks nextObject])) + { + statusCode = [[task objectAtIndex: 3] intValue]; + if (statusCode != 1 || showCompleted) { - statusCode = [[task objectAtIndex: 3] intValue]; - if (statusCode != 1 || showCompleted) - { - filteredTask = [NSMutableArray arrayWithArray: task]; - endDateStamp = [[task objectAtIndex: 5] intValue]; - statusFlag = [self _getStatusClassForStatusCode: statusCode - andEndDateStamp: endDateStamp]; - [filteredTask addObject: statusFlag]; - if (endDateStamp > 0) - [filteredTask addObject: [self _formattedDateForSeconds: endDateStamp - forAllDay: NO]]; - [filteredTasks addObject: filteredTask]; - } + filteredTask = [NSMutableArray arrayWithArray: task]; + endDateStamp = [[task objectAtIndex: 5] intValue]; + statusFlag = [self _getStatusClassForStatusCode: statusCode + andEndDateStamp: endDateStamp]; + [filteredTask addObject: statusFlag]; + if (endDateStamp > 0) + [filteredTask addObject: [self _formattedDateForSeconds: endDateStamp + forAllDay: NO]]; + + if (([tasksView isEqualToString:@"view_today"] || + [tasksView isEqualToString:@"view_next7"] || + [tasksView isEqualToString:@"view_next14"] || + [tasksView isEqualToString:@"view_next31"] || + [tasksView isEqualToString:@"view_thismonth"]) && ((endDateStamp <= endsSecs) && (endDateStamp >= startSecs))) + [filteredTasks addObject: filteredTask]; + else if ([tasksView isEqualToString:@"view_all"]) + [filteredTasks addObject: filteredTask]; + else if (([tasksView isEqualToString:@"view_overdue"]) && ([[filteredTask objectAtIndex:12] isEqualToString:@"overdue"])) + [filteredTasks addObject: filteredTask]; + else if ([tasksView isEqualToString:@"view_incomplete"] && (![[filteredTask objectAtIndex:12] isEqualToString:@"completed"])) + [filteredTasks addObject: filteredTask]; + else if ([tasksView isEqualToString:@"view_not_started"] && ([[[filteredTask objectAtIndex:3] stringValue] isEqualToString:@"0"])) + [filteredTasks addObject: filteredTask]; } + } sort = [[context request] formValueForKey: @"sort"]; if ([sort isEqualToString: @"title"]) [filteredTasks sortUsingSelector: @selector (compareTasksTitleAscending:)]; @@ -1118,13 +1199,13 @@ _computeBlocksPosition (NSArray *blocks) [filteredTasks sortUsingSelector: @selector (compareTasksCategoryAscending:)]; else if ([sort isEqualToString: @"calendarname"]) [filteredTasks sortUsingSelector: @selector (compareTasksCalendarNameAscending:)]; - else + else [filteredTasks sortUsingSelector: @selector (compareTasksAscending:)]; - + ascending = [[context request] formValueForKey: @"asc"]; if (![ascending boolValue]) [filteredTasks reverseArray]; - + return [self _responseWithData: filteredTasks]; } diff --git a/UI/Scheduler/UIxCalMainView.m b/UI/Scheduler/UIxCalMainView.m index 7846b43f8..58362137f 100644 --- a/UI/Scheduler/UIxCalMainView.m +++ b/UI/Scheduler/UIxCalMainView.m @@ -1,8 +1,7 @@ /* UIxCalMainView.m - this file is part of SOGo * - * Copyright (C) 2006-2009 Inverse inc. + * Copyright (C) 2006-2014 Inverse inc. * - * Author: Wolfgang Sourdeau * * 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 @@ -132,6 +131,30 @@ return yearMenuItems; } +- (NSArray *) tasksFilters +{ + return [NSArray arrayWithObjects: @"view_all", @"view_today", @"view_next7", + @"view_next14", @"view_next31", @"view_thismonth", + @"view_not_started", @"view_overdue", @"view_incomplete", nil]; +} + +- (NSString *) tasksFilterLabel +{ + return [self labelForKey: [self valueForKey:@"taskFilter"]]; +} + +- (NSString *) selectedTasksFilter +{ + NSString *selectedFilter; + + selectedFilter = [self queryParameterForKey: @"tasksFilterpopup"]; + + if (![selectedFilter length]) + selectedFilter = @"view_today"; + + return selectedFilter; +} + - (void) setYearMenuItem: (NSNumber *) aYearMenuItem { yearMenuItem = aYearMenuItem; diff --git a/UI/Scheduler/UIxCalViewPrint.h b/UI/Scheduler/UIxCalViewPrint.h new file mode 100644 index 000000000..ac3de23a8 --- /dev/null +++ b/UI/Scheduler/UIxCalViewPrint.h @@ -0,0 +1,30 @@ +/* UIxCalViewPrint.h - this file is part of SOGo + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +@interface UIxCalViewPrint : UIxComponent +{ + id item; +} + +@end + diff --git a/UI/Scheduler/UIxCalViewPrint.m b/UI/Scheduler/UIxCalViewPrint.m new file mode 100644 index 000000000..af94b9792 --- /dev/null +++ b/UI/Scheduler/UIxCalViewPrint.m @@ -0,0 +1,82 @@ +/* UIxCalViewPrint.m - this file is part of SOGo + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import + +#import + +static NSArray *layoutItems = nil; + +@implementation UIxCalViewPrint + ++ (void) initialize +{ + if (!layoutItems) + { + layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Weekly", nil]; + [layoutItems retain]; + } + +} +- (id) init +{ + item = nil; + return [super init]; +} + +- (void) dealloc +{ + [item release]; + [super dealloc]; +} + +/****************************************************************/ +/* Interfacing; populating the popup list for the print layouts */ + +- (void) setItem: (NSString *) newItem +{ + ASSIGN (item, newItem); +} + +- (NSString *) item +{ + return item; +} + +- (NSArray *) printLayoutList +{ + return layoutItems; +} + +- (NSString *) itemPrintLayoutText +{ + return [self labelForKey: [NSString stringWithFormat: item]]; +} + +- (NSString *) layoutSelectedByUser +{ + return nil; +} +/******************************************************************/ +/* */ + +@end diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index ba592a562..ca2eca4ea 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -1,6 +1,6 @@ /* UIxComponentEditor.h - this file is part of SOGo * - * Copyright (C) 2006-2013 Inverse inc. + * 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 diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 86bd0cb0b..ce56a9644 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -1162,7 +1162,7 @@ iRANGE(2); if ([[self clientObject] isNew]) { NSString *value; - int index; + NSUInteger index; value = [userDefaults calendarDefaultReminder]; index = [reminderValues indexOfObject: value]; diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 3c3af6be9..417b9e2d6 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -40,6 +40,11 @@ protectedBy = "View"; pageName = "UIxCalMainView"; }; + printView = { + protectedBy = ""; + pageName = "UIxCalViewPrint"; + actionClass = "UIxCalViewPrint"; + }; addWebCalendar = { protectedBy = "View"; actionClass = "UIxCalMainActions"; diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index 14f4206c3..75bbae736 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -10,39 +10,47 @@ title="name" const:userDefaultsKeys="SOGoContactsCategories" const:popup="YES" - > + const:cssFiles="datepicker.css" + const:jsFiles="datepicker.js"> +
-
+ +
- + -
+ var:selection="componentAddressBook"/> +
+
  • -
  • -
  • + +
  • + +
  • +
  • -
  • -
  • -
  • + + + +
  • + +
  • +
  • -
  • + +
+
@@ -171,18 +179,18 @@
- -
-
-
- + + +
+
+ + +
- +
@@ -339,42 +347,40 @@ + var:value="ldifRecord.mozillaworkurl"/> +
-
- -
- + +
+ + + +
+
+
- +
- - - - - - - +
+ + +
- + +
@@ -382,8 +388,8 @@
- + + diff --git a/UI/Templates/ContactsUI/UIxContactFolderProperties.wox b/UI/Templates/ContactsUI/UIxContactFolderProperties.wox new file mode 100644 index 000000000..bcecf8ba8 --- /dev/null +++ b/UI/Templates/ContactsUI/UIxContactFolderProperties.wox @@ -0,0 +1,63 @@ + + + + + + diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 5bf6d92d9..9ee97a9ee 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -75,7 +75,7 @@