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("%s>\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