diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 8c206baaf..024b2a2a7 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -126,6 +126,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] addEntriesFromDictionary: theFolderMetadata]; [o save]; @@ -477,21 +478,70 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. lastServerKey: (NSString **) theLastServerKey { - NSMutableDictionary *folderMetadata; + NSMutableDictionary *folderMetadata, *dateCache, *syncCache; NSMutableString *s; BOOL more_available; int i, max; + s = [NSMutableString string]; + + more_available = NO; + + if (theFolderType == ActiveSyncMailFolder && !([theSyncKey isEqualToString: @"-1"]) && theFilterType) + { + NSArray *allKeys; + NSString *key; + int softdelete_count; + + softdelete_count = 0; + + folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + + allKeys = [dateCache allKeys]; + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + + if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending) + { + [s appendString: @""]; + [s appendFormat: @"%@", key]; + [s appendString: @""]; + + [syncCache removeObjectForKey: key]; + [dateCache removeObjectForKey: key]; + + softdelete_count++; + } + + if (softdelete_count >= theWindowSize) + { + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; + + more_available = YES; + *theLastServerKey = theSyncKey; + + // Since WindowSize is reached don't even try to add more to the response, let's just + // jump to the end and return the response immediately + goto return_response; + } + } + + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; + } + // // No changes in the collection - 2.2.2.19.1.1 Empty Sync Request. // We check this and we don't generate any commands if we don't have to. // - if ([theSyncKey isEqualToString: [theCollection davCollectionTag]]) + if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length])) return; - s = [NSMutableString string]; - more_available = NO; switch (theFolderType) @@ -614,7 +664,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncMailFolder: default: { - NSMutableDictionary *syncCache, *dateCache; SOGoSyncCacheObject *lastCacheObject, *aCacheObject; NSMutableArray *allCacheObjects, *sortedBySequence; @@ -644,6 +693,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; } + // Check whether GUID in cache is equal to the GUID from imap - this is to avoid cache corruptions if a folder has been renamed and a new folder // with the same name has been created but folderSync has not yet updated the cache if (!([[theCollection nameInContainer] isEqualToString: @@ -805,6 +855,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. break; } // switch (folderType) ... + return_response: + if ([s length]) { [theBuffer appendString: @""]; @@ -1008,7 +1060,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. davCollectionTag = [collection davCollectionTag]; } - // Generate the response buffer [theBuffer appendString: @""]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index e84e7f966..82c1868fc 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -64,6 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import #import @@ -170,6 +171,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return [o properties]; } +- (unsigned int) _softDeleteCountWithFilter: (NSCalendarDate *) theFilter + collectionId: (NSString *) theCollectionId +{ + NSMutableDictionary *dateCache; + NSMutableArray *sdUids; + SOGoCacheGCSObject *o; + NSArray *allKeys; + NSString *key; + + int i; + + sdUids = [NSMutableArray array]; + + if (theFilter) + { + o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + dateCache = [[o properties] objectForKey: @"DateCache"]; + allKeys = [dateCache allKeys]; + + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + + if ([[dateCache objectForKey:key] compare: theFilter ] == NSOrderedAscending) + [sdUids addObject: [dateCache objectForKey:key]]; + } + } + + return [sdUids count]; +} + - (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate type: (SOGoMicrosoftActiveSyncFolderType) theFolderType { @@ -919,6 +955,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sortOrdering: @"REVERSE ARRIVAL" threaded: NO]; count = [uids count]; + + // Add the number of UIDs expected to "soft delete" + count += [self _softDeleteCountWithFilter: filter collectionId: realCollectionId]; + } else { @@ -1899,9 +1939,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. value = [theDocumentElement getElementsByTagName: @"ReplaceMime"]; - // ReplaceMime isn't specified so we must NOT use the server copy + // ReplaceMime IS specified so we must NOT use the server copy // but rather take the data as-is from the client. - if (![value count]) + if ([value count]) { [self processSendMail: theDocumentElement inResponse: theResponse]; @@ -1921,11 +1961,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSData *data; NGMimeMessageGenerator *generator; - NGMimeBodyPart *bodyPart; + NGMimeBodyPart *bodyPart; NGMutableHashMap *map; NGMimeFileData *fdata; NSException *error; - id body; + + id body, bodyFromSmartForward; userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; @@ -1941,10 +1982,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. parser = [[NGMimeMessageParser alloc] init]; data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]; messageFromSmartForward = [parser parsePartFromData: data]; - RELEASE(parser); - // We create a new MIME multipart/mixed message. The first part will be the text part // of our "smart forward" and the second part will be the message/rfc822 part of the // "smart forwarded" message. @@ -1954,11 +1993,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease]; - // First part + // First part - either a text/* or a multipart/*. If it's a multipart, + // we take the first part text/* part we see. map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; - [map setObject: @"text/plain" forKey: @"content-type"]; + bodyFromSmartForward = nil; + + if ([[messageFromSmartForward body] isKindOfClass: [NGMimeMultipartBody class]]) + { + NGMimeBodyPart *part; + NSArray *parts; + int i; + + parts = [[messageFromSmartForward body] parts]; + + for (i = 0; i < [parts count]; i++) + { + part = [parts objectAtIndex: i]; + + if ([[[part contentType] type] isEqualToString: @"text"]) + { + [map setObject: [[part contentType] stringValue] forKey: @"content-type"]; + bodyFromSmartForward = [part body]; + break; + } + } + } + else + { + [map setObject: [[messageFromSmartForward contentType] stringValue] forKey: @"content-type"]; + bodyFromSmartForward = [messageFromSmartForward body]; + } + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; - [bodyPart setBody: [messageFromSmartForward body]]; + [bodyPart setBody: bodyFromSmartForward]; [body addBodyPart: bodyPart]; // Second part diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 58dca5cf1..e5f349092 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -238,6 +238,8 @@ struct GlobalObjectId { if (key) { + NSString *s, *charset; + d = [[self fetchPlainTextParts] objectForKey: key]; encoding = [[self lookupInfoForBodyPart: key] objectForKey: @"encoding"]; @@ -246,6 +248,14 @@ struct GlobalObjectId { d = [d dataByDecodingBase64]; else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame) d = [d dataByDecodingQuotedPrintableTransferEncoding]; + + charset = [[[self lookupInfoForBodyPart: key] objectForKey: @"parameterList"] objectForKey: @"charset"]; + + if (![charset length]) + charset = @"us-ascii"; + + s = [NSString stringWithData: d usingEncodingNamed: charset]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } return d; @@ -296,13 +306,13 @@ struct GlobalObjectId { if ([body isKindOfClass: [NSData class]]) { NSString *charset; - int encoding; - + charset = [[thePart contentType] valueOfParameter: @"charset"]; - encoding = [NGMimeType stringEncodingForCharset: charset]; + + if (![charset length]) + charset = @"us-ascii"; - s = [[NSString alloc] initWithData: body encoding: encoding]; - AUTORELEASE(s); + s = [NSString stringWithData: body usingEncodingNamed: charset]; } else { @@ -396,6 +406,13 @@ struct GlobalObjectId { { if ([type isEqualToString: @"text"]) { + NSString *s, *charset; + + charset = [[[self lookupInfoForBodyPart: @""] objectForKey: @"parameterList"] objectForKey: @"charset"]; + + if (![charset length]) + charset = @"us-ascii"; + d = [[self fetchPlainTextParts] objectForKey: @""]; // We check if we have base64 encoded parts. If so, we just @@ -407,17 +424,15 @@ struct GlobalObjectId { else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame) d = [d dataByDecodingQuotedPrintableTransferEncoding]; + s = [NSString stringWithData: d usingEncodingNamed: charset]; + // Check if we must convert html->plain if (theType == 1 && [subtype isEqualToString: @"html"]) { - NSString *s; - - s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; - AUTORELEASE(s); - s = [s htmlToText]; - d = [s dataUsingEncoding: NSUTF8StringEncoding]; } + + d = [s dataUsingEncoding: NSUTF8StringEncoding]; } else if ([type isEqualToString: @"multipart"]) { diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index aaaf94024..068556826 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/NEWS b/NEWS index fb1aa4abe..d83925097 100644 --- a/NEWS +++ b/NEWS @@ -5,11 +5,19 @@ Enhancements - contacts photos are now synchronized using ActiveSync (#2807) - implemented the GetAttachment ActiveSync command (#2808) - implemented the Ping ActiveSync command + - added "soft deletes" support for ActiveSync (#2734) Bug fixes - better handling of empty "Flag" messages over ActiveSync (#2806) - fixed Chinese charset handling (#2809) - fixed folder name (calendars and contacts) of new subscriptions (#2801) + - fixed the reply/forward operation over ActiveSync (#2805) + - fixed regression when attaching files to a reply + - wait 20 seconds (instead of 2) before deleting temporary download forms (#2811) + - avoid raising exceptions when the db is down and we try to access the preferences module (#2813) + - we now ignore the SCHEDULE-AGENT property when Thunderbird/Ligthning sends it to avoid + not-generating invitation responses for externally received IMIP messages + - improved charset handling over ActiveSync (#2810) 2.2.5 (2014-06-05) ------------------ diff --git a/SOPE/NGCards/NGCardsSaxHandler.m b/SOPE/NGCards/NGCardsSaxHandler.m index 5314dee08..fca1a64e6 100644 --- a/SOPE/NGCards/NGCardsSaxHandler.m +++ b/SOPE/NGCards/NGCardsSaxHandler.m @@ -232,7 +232,7 @@ static NSArray *privilegedTagNames = nil; } - (void) characters: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { if (_len && _chars) { diff --git a/SOPE/NGCards/samples/vcf2xml.m b/SOPE/NGCards/samples/vcf2xml.m index 3f56c2c88..31d61f026 100644 --- a/SOPE/NGCards/samples/vcf2xml.m +++ b/SOPE/NGCards/samples/vcf2xml.m @@ -217,10 +217,10 @@ printf("\n", [_localName cString]); } -- (void)characters:(unichar *)_chars length:(int)_len { +- (void)characters:(unichar *)_chars length:(NSUInteger)_len { NSString *str; id tmp; - unsigned i, len; + NSUInteger i, len; if (_len == 0) { [self indent]; @@ -228,7 +228,7 @@ return; } - for (i = 0; i < (unsigned)_len; i++) { + for (i = 0; i < _len; i++) { if (_chars[i] > 255) { NSLog(@"detected large char: o%04o d%03i h%04X", _chars[i], _chars[i], _chars[i]); @@ -246,7 +246,7 @@ [self indent]; printf("\"%s\"\n", [str cString]); } -- (void)ignorableWhitespace:(unichar *)_chars length:(int)_len { +- (void)ignorableWhitespace:(unichar *)_chars length:(NSUInteger)_len { NSString *data; id tmp; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index ee703b1d5..05e760909 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -80,6 +80,8 @@ typedef enum { - (NSArray *) calendarUIDs; +- (NSNumber *) activeTasks; + /* vevent UID handling */ - (NSString *) resourceNameForEventUID: (NSString *) _uid; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 61cef70dc..96a14d536 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -230,6 +230,8 @@ static iCalEvent *iCalEventK = nil; abstract: NO withEquivalent: SoPerm_AddDocumentsImagesAndFiles asChildOf: davElement (@"write", XMLNS_WEBDAV)]; + + /* read-acl and write-acl are defined in RFC3744 */ [aclManager registerDAVPermission: davElement (@"admin", nsI) abstract: YES withEquivalent: nil @@ -242,6 +244,72 @@ static iCalEvent *iCalEventK = nil; abstract: YES withEquivalent: SoPerm_ChangePermissions asChildOf: davElement (@"admin", nsI)]; + + /* Default permissions for calendars. These are very important so that DAV client can + detect permission changes on calendars and reload all items, if necessary */ + + /* Public ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholePublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-public-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToPublicRecords + asChildOf: davElement (@"admin", nsI)]; + + /* Private ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholePrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-private-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToPrivateRecords + asChildOf: davElement (@"admin", nsI)]; + + /* Condifential ones */ + [aclManager registerDAVPermission: davElement (@"viewwhole-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewWholeConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"viewdant-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ViewDAndTOfConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"modify-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_ModifyConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; + + [aclManager registerDAVPermission: davElement (@"respondto-confidential-records", nsI) + abstract: YES + withEquivalent: SOGoCalendarPerm_RespondToConfidentialRecords + asChildOf: davElement (@"admin", nsI)]; } return aclManager; @@ -522,7 +590,6 @@ static iCalEvent *iCalEventK = nil; { /* this is used for group calendars (this folder just returns itself) */ NSString *s; - s = [[self container] nameInContainer]; // [self logWithFormat:@"CAL UID: %@", s]; return [s isNotNull] ? [NSArray arrayWithObjects:&s count:1] : nil; @@ -3270,4 +3337,25 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return users; } +- (NSNumber *) activeTasks +{ + NSArray *tasksList; + NSMutableArray *fields; + NSNumber *activeTasks; + + fields = [NSMutableArray arrayWithObjects: @"c_component", @"c_status", nil]; + + tasksList = [self bareFetchFields: fields + from: nil + to: nil + title: nil + component: @"vtodo" + additionalFilters: @"c_status != 1 AND c_status != 3"]; + + activeTasks = [NSNumber numberWithInt:[tasksList count]]; + + return activeTasks; +} + + @end /* SOGoAppointmentFolder */ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 1712ec7ea..0e397928f 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -1428,9 +1428,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // - (BOOL) _shouldScheduleEvent: (iCalPerson *) theOrganizer { + NSArray *userAgents; NSString *v; BOOL b; - + int i; + b = YES; if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"])) @@ -1440,6 +1442,27 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent b = NO; } + // + // If we have to deal with Thunderbird/Lightning, we always send invitation + // reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT + // to NONE/CLIENT when responding to an external invitation received by + // SOGo - so no invitation responses are ever sent by Lightning. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=997784 + // + userAgents = [[context request] headersForKey: @"User-Agent"]; + + for (i = 0; i < [userAgents count]; i++) + { + if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound && + [[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound) + { + b = YES; + break; + } + } + + return b; } diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 48c850b9e..1efdcb62f 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -430,7 +430,7 @@ } - (void) ignorableWhitespace: (unichar *) whitespaces - length: (int) length + length: (NSUInteger) length { showWhoWeAre(); } diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 7fd1a4c35..4d0f9aa38 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1761,26 +1761,26 @@ static NSString *userAgent = nil; { SOGoUserDefaults *ud; ud = [[context activeUser] userDefaults]; - + if ([ud mailAddOutgoingAddresses]) { + Class contactGCSEntry; SOGoContactFolders *contactFolders; - NGMailAddressParser *parser; - id parsedRecipient; SOGoContactFolder *folder; SOGoContactGCSEntry *newContact; NGVCard *card; - Class contactGCSEntry; + NGMailAddressParser *parser; + NSArray *matchingContacts; NSMutableArray *recipients; NSString *recipient, *emailAddress, *addressBook, *uid; - NSArray *matchingContacts; + id parsedRecipient; int i; - + // Get all the addressbooks contactFolders = [[[context activeUser] homeFolderInContext: context] - lookupName: @"Contacts" - inContext: context - acquire: NO]; + lookupName: @"Contacts" + inContext: context + acquire: NO]; // Get all the recipients from the current email recipients = [self allRecipients]; for (i = 0; i < [recipients count]; i++) @@ -1790,7 +1790,7 @@ static NSString *userAgent = nil; parser = [NGMailAddressParser mailAddressParserWithString: recipient]; parsedRecipient = [parser parse]; emailAddress = [parsedRecipient address]; - + matchingContacts = [contactFolders allContactsFromFilter: emailAddress excludeGroups: YES excludeLists: YES]; diff --git a/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m index b0e9eea52..cef17775b 100644 --- a/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m +++ b/SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m @@ -72,7 +72,7 @@ = (@"CREATE TABLE %@ (" @" c_path VARCHAR(255) PRIMARY KEY," @" c_parent_path VARCHAR(255)," - @" c_type TINYINT NOT NULL," + @" c_type TINYINT UNSIGNED NOT NULL," @" c_creationdate INT NOT NULL," @" c_lastmodified INT NOT NULL," @" c_version INT NOT NULL DEFAULT 0," diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 3f626bc61..b0110f58d 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -329,17 +329,24 @@ static NSArray *childRecordFields = nil; fc = [cm acquireOpenChannelForURL: folderLocation]; if (fc) { - sql - = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" - @" WHERE c_path = '%@'"), - [folderLocation gcsTableName], ocsPath]; - [fc evaluateExpressionX: sql]; - attrs = [fc describeResults: NO]; - row = [fc fetchAttributes: attrs withZone: NULL]; - if (row) - [self _setDisplayNameFromRow: row]; - [fc cancelFetch]; - [cm releaseChannel: fc]; + // We use an exception handler here in case the database is down when + // performing the query. This could have unexpected results. + NS_DURING + { + sql + = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@" + @" WHERE c_path = '%@'"), + [folderLocation gcsTableName], ocsPath]; + [fc evaluateExpressionX: sql]; + attrs = [fc describeResults: NO]; + row = [fc fetchAttributes: attrs withZone: NULL]; + if (row) + [self _setDisplayNameFromRow: row]; + [fc cancelFetch]; + [cm releaseChannel: fc]; + } + NS_HANDLER; + NS_ENDHANDLER; } } diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 4c7c7c31a..164296b83 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -164,34 +164,45 @@ static SoSecurityManager *sm = nil; NSString *folderName; SOGoGCSFolder *folder; SOGoUser *folderOwner; - + SOGoUserDefaults *ud; + roles = [[context activeUser] rolesForObject: self inContext: context]; folderOwner = [SOGoUser userWithLogin: [self ownerInContext: context]]; - + + // We autocreate the calendars if the user is the owner, a superuser or // if it's a resource as we won't necessarily want to login as a resource // in order to create its database tables. // FolderType is an enum where 0 = Personal and 1 = collected if ([roles containsObject: SoRole_Owner] || (folderOwner && [folderOwner isResource])) + { + if (folderType == SOGoPersonalFolder) { - if (folderType == SOGoPersonalFolder) - { - folderName = @"personal"; - folder = [subFolderClass objectWithName: folderName inContainer: self]; - [folder setDisplayName: [self defaultFolderName]]; - } - else if (folderType == SOGoCollectedFolder) - { - folderName = @"collected"; - folder = [subFolderClass objectWithName: folderName inContainer: self]; - [folder setDisplayName: [self collectedFolderName]]; - } + folderName = @"personal"; + folder = [subFolderClass objectWithName: folderName inContainer: self]; + [folder setDisplayName: [self defaultFolderName]]; [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]]; if ([folder create]) - [subFolders setObject: folder forKey: folderName]; + [subFolders setObject: folder forKey: folderName]; } + else if (folderType == SOGoCollectedFolder) + { + ud = [[context activeUser] userDefaults]; + if ([ud mailAddOutgoingAddresses]) { + folderName = @"collected"; + folder = [subFolderClass objectWithName: folderName inContainer: self]; + [folder setDisplayName: [self collectedFolderName]]; + [folder setOCSPath: [NSString stringWithFormat: @"%@/%@", OCSPath, folderName]]; + + if ([folder create]) + [subFolders setObject: folder forKey: folderName]; + + [ud setSelectedAddressBook:folderName]; + } + } + } } - (NSException *) fetchSpecialFolders: (NSString *) sql diff --git a/Tests/Integration/test-davacl.py b/Tests/Integration/test-davacl.py index a254f25f9..a8a1a5bac 100755 --- a/Tests/Integration/test-davacl.py +++ b/Tests/Integration/test-davacl.py @@ -401,7 +401,17 @@ class DAVCalendarAclTest(DAVAclTest): else: expStatus = 207 privileges = self._currentUserPrivilegeSet(self.resource, expStatus) - self._comparePrivilegeSets(expectedPrivileges, privileges) + + # When comparing privileges on DAV collection, we remove all 'default' + # privileges on the collection. + privileges_set = set(privileges); + for x in ("public", "private", "confidential"): + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}viewwhole-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}viewdant-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}modify-%s-records" % x) + privileges_set.discard("{urn:inverse:params:xml:ns:inverse-dav}respondto-%s-records" %x) + + self._comparePrivilegeSets(expectedPrivileges, list(privileges_set)) def _testEventDAVAcl(self, event_class, right, error_code): icsClass = self.classToICSClass[event_class].lower() diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 0b525680b..7160f6ba2 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -419,9 +419,9 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) _appendStyle: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { - unsigned int count, length; + NSUInteger count, length; unichar *start, *currentChar; start = _chars; @@ -688,7 +688,7 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) characters: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); if (!ignoredContent) @@ -712,7 +712,7 @@ static NSData* _sanitizeContent(NSData *theData) } - (void) ignorableWhitespace: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); } @@ -735,7 +735,7 @@ static NSData* _sanitizeContent(NSData *theData) /* SaxLexicalHandler */ - (void) comment: (unichar *) _chars - length: (int) _len + length: (NSUInteger) _len { showWhoWeAre(); if (inStyle) diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 28de05922..f78843b53 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -644,8 +644,9 @@ static NSArray *infoKeys = nil; if (!attachmentAttrs || ![co imap4URL]) { [co fetchInfo]; - if (![co inReplyTo] && [co IMAP4ID] > -1) + if ((![co inReplyTo] || currentAttachment) && [co IMAP4ID] > -1) { + // When currentAttachment is defined, it means we just attached a new file to the mail mail = [[[SOGoMailObject alloc] initWithImap4URL: [co imap4URL] inContainer: [co container]] autorelease]; a = [mail fetchFileAttachmentKeys]; ASSIGN (attachmentAttrs, a); diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 54c0df6b8..0900c4d3a 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -74,7 +74,7 @@ static NSArray *reminderValues = nil; if (!reminderItems && !reminderValues) { reminderItems = [NSArray arrayWithObjects: - @"5_MINUTES_BEFORE", + @"5_MINUTES_BEFORE", @"10_MINUTES_BEFORE", @"15_MINUTES_BEFORE", @"30_MINUTES_BEFORE", @@ -90,7 +90,7 @@ static NSArray *reminderValues = nil; @"1_WEEK_BEFORE", nil]; reminderValues = [NSArray arrayWithObjects: - @"-PT5M", + @"-PT5M", @"-PT10M", @"-PT15M", @"-PT30M", @@ -699,49 +699,53 @@ static NSArray *reminderValues = nil; /* We want all the SourceIDS */ NSMutableArray *folders, *availableAddressBooksID, *availableAddressBooksName; SOGoParentFolder *contactFolders; - + int i, count; BOOL collectedAlreadyExist; - + contactFolders = [[[context activeUser] homeFolderInContext: context] - lookupName: @"Contacts" - inContext: context - acquire: NO]; + lookupName: @"Contacts" + inContext: context + acquire: NO]; folders = [NSMutableArray arrayWithArray: [contactFolders subFolders]]; count = [folders count]-1; - + // Inside this loop we remove all the public or shared addressbooks for (; count >= 0; count--) - { - if (![[folders objectAtIndex: count] isKindOfClass: [SOGoContactGCSFolder class]]) - [folders removeObjectAtIndex: count]; - } - + { + if (![[folders objectAtIndex: count] isKindOfClass: [SOGoContactGCSFolder class]]) + [folders removeObjectAtIndex: count]; + } + // Parse the objects in order to have only the displayName of the addressbooks to be displayed on the preferences interface availableAddressBooksID = [NSMutableArray arrayWithCapacity: [folders count]]; availableAddressBooksName = [NSMutableArray arrayWithCapacity: [folders count]]; count = [folders count]-1; collectedAlreadyExist = NO; - - for (i = 0; i <= count ; i++) { - [availableAddressBooksID addObject:[[folders objectAtIndex:i] realNameInContainer]]; - [availableAddressBooksName addObject:[[folders objectAtIndex:i] displayName]]; - - if ([[availableAddressBooksID objectAtIndex:i] isEqualToString: @"collected"]) - collectedAlreadyExist = YES; - } + + for (i = 0; i <= count ; i++) + { + [availableAddressBooksID addObject:[[folders objectAtIndex:i] realNameInContainer]]; + [availableAddressBooksName addObject:[[folders objectAtIndex:i] displayName]]; + + if ([[availableAddressBooksID objectAtIndex:i] isEqualToString: @"collected"]) + collectedAlreadyExist = YES; + } // Create the dictionary for the next function : itemAddressBookText. if (!addressBooksIDWithDisplayName) - addressBooksIDWithDisplayName = [[NSMutableDictionary alloc] initWithObjects:availableAddressBooksName - forKeys:availableAddressBooksID]; - if (!collectedAlreadyExist) + { + addressBooksIDWithDisplayName = [[NSMutableDictionary alloc] initWithObjects:availableAddressBooksName + forKeys:availableAddressBooksID]; + } + if (!collectedAlreadyExist) { [availableAddressBooksID addObject: @"collected"]; - [addressBooksIDWithDisplayName setObject: [self labelForKey: @"Collected Address Book"] forKey: @"collected"]; + [addressBooksIDWithDisplayName setObject: [self labelForKey: @"Collected Address Book"] forKey: @"collected"]; } - + return availableAddressBooksID; } + - (NSString *) itemAddressBookText { return [addressBooksIDWithDisplayName objectForKey: item]; @@ -929,7 +933,7 @@ static NSArray *reminderValues = nil; capabilities = [[self sieveClient] capabilities]; else capabilities = [NSArray array]; - [capabilities retain]; + [capabilities retain]; } return [capabilities jsonRepresentation]; @@ -1220,12 +1224,12 @@ static NSArray *reminderValues = nil; SOGoSieveManager *manager; if (!client) - { - folder = [[self clientObject] mailAccountsFolder: @"Mail" inContext: context]; - account = [folder lookupName: @"0" inContext: context acquire: NO]; - manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; - client = [[manager clientForAccount: account] retain]; - } + { + folder = [[self clientObject] mailAccountsFolder: @"Mail" inContext: context]; + account = [folder lookupName: @"0" inContext: context acquire: NO]; + manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; + client = [[manager clientForAccount: account] retain]; + } return client; } @@ -1273,9 +1277,9 @@ static NSArray *reminderValues = nil; results = [self responseWithStatus: 502 andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Connection error", @"textStatus", nil]]; } - else - results = [self responseWithStatus: 503 - andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Service temporarily unavailable", @"textStatus", nil]]; + else + results = [self responseWithStatus: 503 + andJSONRepresentation: [NSDictionary dictionaryWithObjectsAndKeys: @"Service temporarily unavailable", @"textStatus", nil]]; } else results = self; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 7b6445e78..7c34b8f71 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -1,9 +1,6 @@ /* UIxCalListingActions.m - this file is part of SOGo * - * Copyright (C) 2006-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2006-2014 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -484,7 +481,7 @@ static NSArray *tasksFields = nil; return infos; } -- (WOResponse *) _responseWithData: (NSArray *) data +- (WOResponse *) _responseWithData: (id) data { WOResponse *response; @@ -1282,4 +1279,28 @@ _computeBlocksPosition (NSArray *blocks) return [self _responseWithData: filteredTasks]; } +- (WOResponse *) activeTasksAction +{ + NSMutableDictionary *activeTasksByCalendars; + SOGoAppointmentFolder *folder; + SOGoAppointmentFolders *co; + NSArray *folders; + + int i; + + co = [self clientObject]; + folders = [co subFolders]; + activeTasksByCalendars = [NSMutableDictionary dictionaryWithCapacity: [folders count]]; + + for (i = 0; i < [folders count]; i++) + { + folder = [folders objectAtIndex: i]; + + [activeTasksByCalendars setObject: [folder activeTasks] + forKey: [folder nameInContainer]]; + } + + return [self _responseWithData: activeTasksByCalendars]; +} + @end diff --git a/UI/Scheduler/UIxCalendarSelector.m b/UI/Scheduler/UIxCalendarSelector.m index d5d9a56b1..2c8a6910d 100644 --- a/UI/Scheduler/UIxCalendarSelector.m +++ b/UI/Scheduler/UIxCalendarSelector.m @@ -93,8 +93,8 @@ _intValueFromHex (NSString *hexString) NSMutableDictionary *calendar; unsigned int count, max; NSString *folderName, *fDisplayName; - NSNumber *isActive; - + NSNumber *isActive, *fActiveTasks; + if (!calendars) { co = [self clientObject]; @@ -120,6 +120,8 @@ _intValueFromHex (NSString *hexString) [calendar setObject: isActive forKey: @"active"]; [calendar setObject: [folder ownerInContext: context] forKey: @"owner"]; + fActiveTasks = [folder activeTasks]; + [calendar setObject:fActiveTasks forKey:@"activeTasks" ]; [calendars addObject: calendar]; } } diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 417b9e2d6..aab66ebfc 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -94,6 +94,11 @@ actionClass = "UIxCalListingActions"; actionName = "tasksList"; }; + activeTasks = { + protectedBy = "View"; + actionClass = "UIxCalListingActions"; + actionName = "activeTasks"; + }; dayview = { protectedBy = "View"; pageName = "UIxCalDayView"; diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index fbfc0b30a..9cc6675a2 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -299,7 +299,8 @@
+ var:checked="addOutgoingAddresses" + onChange = "onAddOutgoingAddressesCheck(this);"/>
  • -
  • +
    + + +
    diff --git a/UI/WebServerResources/MailerUI.js b/UI/WebServerResources/MailerUI.js index 2c0575995..4f87b2a84 100644 --- a/UI/WebServerResources/MailerUI.js +++ b/UI/WebServerResources/MailerUI.js @@ -1749,7 +1749,7 @@ function download(url) { setTimeout(function () { form.remove(); div.remove(); - }, 2000); + }, 20000); } function saveAttachment(event) { diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 898c33f7d..8d2004357 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -1102,142 +1102,166 @@ function eventsListCallback(http) { log ("eventsListCallback Ajax error"); } -function tasksListCallback(http) { - if (http.readyState == 4 - && http.status == 200) { - var div = $("tasksListView"); - document.tasksListAjaxRequest = null; - var table = $("tasksList"); - lastClickedRow = -1; // from generic.js - - var rows = table.select("TBODY TR"); - rows.each(function(e) { - e.remove(); - }); - - if (http.responseText.length > 0) { - var data = http.responseText.evalJSON(true); - - // [0] Task ID - // [1] Calendar ID - // [2] Calendar name - // [3] Status (0, 1 = completed, 2) - // [4] Title - // [5] Due date (int) - // [6] Classification (0 = public, 1, = private, 2 = confidential) - // [7] Location - // [8] Category - // [9] Editable? - // [10] Erasable? - // [11] Priority (0, 1 = important, 9 = low) - // [12] Status CSS class (duelater, completed, etc) - // (13) Due date (formatted) - - for (var i = 0; i < data.length; i++) { - var row = createElement("tr"); - table.tBodies[0].appendChild(row); - - row.on("dblclick", editDoubleClickedEvent); - - var calendar = escape(data[i][1]); - var cname = escape(data[i][0]); - row.setAttribute("id", calendar + "-" + cname); - //listItem.addClassName(data[i][5]); // Classification - row.addClassName(data[i][12]); // status - row.calendar = calendar; - row.cname = cname; - row.erasable = data[i][10] || IsSuperUser; - if (parseInt(data[i][11]) == 1) { - row.addClassName("important"); - } - else if (parseInt(data[i][11]) == 9) { - row.addClassName("low"); - } - - var cell = createElement("td"); - row.appendChild(cell); - var input = createElement("input"); - input.setAttribute("type", "checkbox"); - cell.appendChild(input); - input.setAttribute("value", "1"); - if (parseInt(data[i][9]) == 0) // editable? - input.setAttribute("disabled", true); - input.addClassName("checkBox"); - if (parseInt(data[i][3]) == 1) // completed? - input.setAttribute("checked", "checked"); - input.observe("click", updateTaskStatus, true); - - cell = createElement("td"); - row.appendChild(cell); - if (data[i][11] != null) { - cell.update(_("prio_" + data[i][11])); // Priority - } - else { - cell.update(""); // Priority - } - - cell = createElement("td"); - row.appendChild(cell); - var colorDiv = createElement("div", false, "colorBox calendarFolder" + calendar); - cell.appendChild(colorDiv); - colorDiv.update(' '); - var t = new Element ("span"); - cell.appendChild(t); - t.update(data[i][4]); // title - - cell = createElement("td"); - row.appendChild(cell); - if (data[i][13]) - cell.update(data[i][13]); // end date - - cell = createElement("td"); - row.appendChild(cell); - cell.update(data[i][7]); // location - - cell = createElement("td"); - row.appendChild(cell); - cell.update(data[i][8]); // category - - cell = createElement("td"); - row.appendChild(cell); - cell.update(data[i][2]); // calendar name - } - - table.scrollTop = table.previousScroll; - - if (sorting["task-attribute"] && sorting["task-attribute"].length > 0) { - var sortHeader = $(sorting["task-header"]); - - if (sortHeader) { - var sortImages = $(table.tHead).select(".sortImage"); - $(sortImages).each(function(item) { - item.remove(); - }); - - var sortImage = createElement("img", "messageSortImage", "sortImage"); - sortHeader.insertBefore(sortImage, sortHeader.firstChild); - if (sorting["task-ascending"]) - sortImage.src = ResourcesURL + "/arrow-up.png"; - else - sortImage.src = ResourcesURL + "/arrow-down.png"; - } - } - if (http.callbackData) { - var selectedNodesId = http.callbackData; - for (var i = 0; i < selectedNodesId.length; i++) { - // log(selectedNodesId[i] + " (" + i + ") is selected"); - var node = $(selectedNodesId[i]); - if (node) { - node.selectElement(); - } - } - } - else - log ("tasksListCallback: no data"); +function activeTasksCallback(http) { + if (http.readyState == 4 && http.status == 200) { + if (http.responseText.length > 0) { + var data = http.responseText.evalJSON(true); + var list = $("calendarList"); + + var items = list.childNodesWithTag("li"); + for (var i = 0; i < items.length; i++) { + var id = items[i].getAttribute("id").substr(1); + var number = data[id]; + var input = items[i].childNodesWithTag("input")[0]; + var activeTasks = items[i].childNodesWithTag("span")[0]; + $(input).observe("click", clickEventWrapper(updateCalendarStatus)); + if (number == "0") { + activeTasks.innerHTML = ""; } + else { + activeTasks.innerHTML = "(" + number + ")"; + } + } } - else - log ("tasksListCallback Ajax error"); + } +} + +function tasksListCallback(http) { + if (http.readyState == 4 + && http.status == 200) { + var div = $("tasksListView"); + document.tasksListAjaxRequest = null; + var table = $("tasksList"); + lastClickedRow = -1; // from generic.js + + var rows = table.select("TBODY TR"); + rows.each(function(e) { + e.remove(); + }); + + if (http.responseText.length > 0) { + var data = http.responseText.evalJSON(true); + + // [0] Task ID + // [1] Calendar ID + // [2] Calendar name + // [3] Status (0, 1 = completed, 2) + // [4] Title + // [5] Due date (int) + // [6] Classification (0 = public, 1, = private, 2 = confidential) + // [7] Location + // [8] Category + // [9] Editable? + // [10] Erasable? + // [11] Priority (0, 1 = important, 9 = low) + // [12] Status CSS class (duelater, completed, etc) + // (13) Due date (formatted) + + for (var i = 0; i < data.length; i++) { + var row = createElement("tr"); + table.tBodies[0].appendChild(row); + + row.on("dblclick", editDoubleClickedEvent); + + var calendar = escape(data[i][1]); + var cname = escape(data[i][0]); + row.setAttribute("id", calendar + "-" + cname); + //listItem.addClassName(data[i][5]); // Classification + row.addClassName(data[i][12]); // status + row.calendar = calendar; + row.cname = cname; + row.erasable = data[i][10] || IsSuperUser; + if (parseInt(data[i][11]) == 1) { + row.addClassName("important"); + } + else if (parseInt(data[i][11]) == 9) { + row.addClassName("low"); + } + + var cell = createElement("td"); + row.appendChild(cell); + var input = createElement("input"); + input.setAttribute("type", "checkbox"); + cell.appendChild(input); + input.setAttribute("value", "1"); + if (parseInt(data[i][9]) == 0) // editable? + input.setAttribute("disabled", true); + input.addClassName("checkBox"); + if (parseInt(data[i][3]) == 1) // completed? + input.setAttribute("checked", "checked"); + input.observe("click", updateTaskStatus, true); + + cell = createElement("td"); + row.appendChild(cell); + if (data[i][11] != null) { + cell.update(_("prio_" + data[i][11])); // Priority + } + else { + cell.update(""); // Priority + } + + cell = createElement("td"); + row.appendChild(cell); + var colorDiv = createElement("div", false, "colorBox calendarFolder" + calendar); + cell.appendChild(colorDiv); + colorDiv.update(' '); + var t = new Element ("span"); + cell.appendChild(t); + t.update(data[i][4]); // title + + cell = createElement("td"); + row.appendChild(cell); + if (data[i][13]) + cell.update(data[i][13]); // end date + + cell = createElement("td"); + row.appendChild(cell); + cell.update(data[i][7]); // location + + cell = createElement("td"); + row.appendChild(cell); + cell.update(data[i][8]); // category + + cell = createElement("td"); + row.appendChild(cell); + cell.update(data[i][2]); // calendar name + } + + table.scrollTop = table.previousScroll; + + if (sorting["task-attribute"] && sorting["task-attribute"].length > 0) { + var sortHeader = $(sorting["task-header"]); + + if (sortHeader) { + var sortImages = $(table.tHead).select(".sortImage"); + $(sortImages).each(function(item) { + item.remove(); + }); + + var sortImage = createElement("img", "messageSortImage", "sortImage"); + sortHeader.insertBefore(sortImage, sortHeader.firstChild); + if (sorting["task-ascending"]) + sortImage.src = ResourcesURL + "/arrow-up.png"; + else + sortImage.src = ResourcesURL + "/arrow-down.png"; + } + } + if (http.callbackData) { + var selectedNodesId = http.callbackData; + for (var i = 0; i < selectedNodesId.length; i++) { + // log(selectedNodesId[i] + " (" + i + ") is selected"); + var node = $(selectedNodesId[i]); + if (node) { + node.selectElement(); + } + } + } + else + log ("tasksListCallback: no data"); + } + } + else + log ("tasksListCallback Ajax error"); } /* in dateselector */ @@ -2331,22 +2355,25 @@ function _loadEventHref(href) { } function _loadTasksHref(href) { - if (document.tasksListAjaxRequest) { - document.tasksListAjaxRequest.aborted = true; - document.tasksListAjaxRequest.abort(); - } - url = ApplicationBaseURL + "/" + href; - - var tasksList = $("tasksList"); - var selectedIds; - if (tasksList) - selectedIds = tasksList.getSelectedNodesId(); - else - selectedIds = null; - document.tasksListAjaxRequest - = triggerAjaxRequest(url, tasksListCallback, selectedIds); - - return true; + if (document.tasksListAjaxRequest) { + document.tasksListAjaxRequest.aborted = true; + document.tasksListAjaxRequest.abort(); + } + url = ApplicationBaseURL + "/" + href; + urlActiveTasks = ApplicationBaseURL + "/activeTasks"; + + var tasksList = $("tasksList"); + var selectedIds; + if (tasksList) + selectedIds = tasksList.getSelectedNodesId(); + else + selectedIds = null; + + document.tasksListAjaxRequest = triggerAjaxRequest(url, tasksListCallback, selectedIds); + + triggerAjaxRequest(urlActiveTasks, activeTasksCallback); + + return true; } function onHeaderClick(event) { @@ -3038,27 +3065,35 @@ function configureDragHandles() { } function initCalendarSelector() { - var selector = $("calendarSelector"); - updateCalendarStatus(); // triggers the initial events refresh - selector.changeNotification = updateCalendarsList; - - var list = $("calendarList"); - list.on("mousedown", onCalendarSelectionChange); - list.on("dblclick", onCalendarModify); - list.on("selectstart", listRowMouseDownHandler); - list.attachMenu("calendarsMenu"); - - var items = list.childNodesWithTag("li"); - for (var i = 0; i < items.length; i++) { - var input = items[i].childNodesWithTag("input")[0]; - $(input).observe("click", clickEventWrapper(updateCalendarStatus)); + var selector = $("calendarSelector"); + updateCalendarStatus(); // triggers the initial events refresh + selector.changeNotification = updateCalendarsList; + + var list = $("calendarList"); + list.on("mousedown", onCalendarSelectionChange); + list.on("dblclick", onCalendarModify); + list.on("selectstart", listRowMouseDownHandler); + list.attachMenu("calendarsMenu"); + + var items = list.childNodesWithTag("li"); + for (var i = 0; i < items.length; i++) { + var input = items[i].childNodesWithTag("input")[0]; + var activeTasks = items[i].childNodesWithTag("span")[0]; + $(input).observe("click", clickEventWrapper(updateCalendarStatus)); + if (activeTasks.textContent == "0") { + activeTasks.innerHTML = ""; } - - var links = $("calendarSelectorButtons").childNodesWithTag("a"); - $(links[0]).observe("click", clickEventWrapper(onCalendarNew)); - $(links[1]).observe("click", clickEventWrapper(onCalendarWebAdd)); - $(links[2]).observe("click", clickEventWrapper(onCalendarAdd)); - $(links[3]).observe("click", clickEventWrapper(onCalendarRemove)); + else { + activeTasks.innerHTML = "(" + activeTasks.innerText + ")"; + } + + } + + var links = $("calendarSelectorButtons").childNodesWithTag("a"); + $(links[0]).observe("click", clickEventWrapper(onCalendarNew)); + $(links[1]).observe("click", clickEventWrapper(onCalendarWebAdd)); + $(links[2]).observe("click", clickEventWrapper(onCalendarAdd)); + $(links[3]).observe("click", clickEventWrapper(onCalendarRemove)); } function onCalendarSelectionChange(event) { diff --git a/UI/WebServerResources/UIxPreferences.js b/UI/WebServerResources/UIxPreferences.js index 396fd2206..c21dcf88b 100644 --- a/UI/WebServerResources/UIxPreferences.js +++ b/UI/WebServerResources/UIxPreferences.js @@ -291,6 +291,7 @@ function initPreferences() { $("vacationEndDate_date").disable(); }); } + onAddOutgoingAddressesCheck(); } function initSieveFilters() { @@ -1172,8 +1173,15 @@ function serializeContactsCategories() { } /* / contact categories */ - - + +function onAddOutgoingAddressesCheck(checkBox) { + if (!checkBox) { + checkBox = $("addOutgoingAddresses"); + } + $("addressBookList").disabled = !checkBox.checked; + +} + function onReplyPlacementListChange() { if ($("replyPlacementList").value == 0) { // Reply placement is above quote, signature can be place before of after quote