Merge to 2.2.6

This commit is contained in:
Ludovic Marcotte
2014-07-02 11:41:46 -04:00
63 changed files with 4288 additions and 1996 deletions
+12 -3
View File
@@ -40,6 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Contacts/NGVCard+SOGo.h>
#import <SOGo/NSString+Utilities.h>
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
@@ -199,6 +201,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}
if ((o = [self photo]))
[s appendFormat: @"<Picture xmlns=\"Contacts:\">%@</Picture>", o];
return s;
}
@@ -285,13 +290,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"Email1Address"]))
{
element = [self elementWithTag: @"email" ofType: @"work"];
[element setSingleValue: o forKey: @""];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
if ((o = [theValues objectForKey: @"Email2Address"]))
{
element = [self elementWithTag: @"email" ofType: @"home"];
[element setSingleValue: o forKey: @""];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
// SOGo currently only supports 2 email addresses ... but AS clients might send 3
@@ -299,7 +304,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"Email3Address"]))
{
element = [self elementWithTag: @"email" ofType: @"three"];
[element setSingleValue: o forKey: @""];
[element setSingleValue: [o pureEMailAddress] forKey: @""];
}
// Formatted name
@@ -346,6 +351,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [theValues objectForKey: @"NickName"]))
[self setNickname: o];
if ((o = [theValues objectForKey: @"Picture"]))
[self setPhoto: o];
}
@end
+1
View File
@@ -47,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSCalendarDate *) calendarDate;
- (NSString *) deviceId;
- (NSString *) deviceType;
- (NSString *) attachmentName;
- (NSString *) command;
- (NSData *) convertHexStringToBytes;
+17
View File
@@ -186,6 +186,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return s;
}
// This method extracts the "AttachmentName" from a URI:
//
// /SOGo/Microsoft-Server-ActiveSync?Cmd=GetAttachment&User=sogo&DeviceId=HTCa04b4932597acd3f2dc1a918b9728&DeviceType=htcvision&AttachmentName=mail/TestFldr/8/2
//
- (NSString *) attachmentName
{
NSString *s;
s = [self _valueForParameter: @"ATTACHMENTNAME="];
if (!s)
s = @"Unknown";
return s;
}
//
//
//
+71 -8
View File
@@ -123,8 +123,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
[[o properties] removeObjectForKey: @"SyncKey"];
[[o properties] removeObjectForKey: @"SyncCache"];
[[o properties] removeObjectForKey: @"DateCache"];
[[o properties] removeObjectForKey: @"MoreAvailable"];
[[o properties] addEntriesFromDictionary: theFolderMetadata];
[o save];
@@ -476,20 +478,70 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
lastServerKey: (NSString **) theLastServerKey
{
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
NSMutableString *s;
BOOL more_available;
int i, max;
s = [NSMutableString string];
more_available = NO;
if (theFolderType == ActiveSyncMailFolder && !([theSyncKey isEqualToString: @"-1"]) && theFilterType)
{
NSArray *allKeys;
NSString *key;
int softdelete_count;
softdelete_count = 0;
folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]];
dateCache = [folderMetadata objectForKey: @"DateCache"];
syncCache = [folderMetadata objectForKey: @"SyncCache"];
allKeys = [dateCache allKeys];
for (i = 0; i < [allKeys count]; i++)
{
key = [allKeys objectAtIndex: i];
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
{
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
[syncCache removeObjectForKey: key];
[dateCache removeObjectForKey: key];
softdelete_count++;
}
if (softdelete_count >= theWindowSize)
{
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
[self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]];
more_available = YES;
*theLastServerKey = theSyncKey;
// Since WindowSize is reached don't even try to add more to the response, let's just
// jump to the end and return the response immediately
goto return_response;
}
}
[folderMetadata removeObjectForKey: @"MoreAvailable"];
[self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]];
}
//
// No changes in the collection - 2.2.2.19.1.1 Empty Sync Request.
// We check this and we don't generate any commands if we don't have to.
//
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]])
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
return;
s = [NSMutableString string];
more_available = NO;
switch (theFolderType)
@@ -602,12 +654,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[s appendString: @"</Add>"];
}
} // for ...
folderMetadata = [NSDictionary dictionaryWithObject: [theCollection davCollectionTag]
forKey: @"SyncKey"];
[self _setFolderMetadata: folderMetadata
forKey: [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]]];
}
break;
case ActiveSyncMailFolder:
default:
{
NSMutableDictionary *syncCache, *dateCache, *folderMetadata;
SOGoSyncCacheObject *lastCacheObject, *aCacheObject;
NSMutableArray *allCacheObjects, *sortedBySequence;
@@ -637,6 +693,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
}
// Check whether GUID in cache is equal to the GUID from imap - this is to avoid cache corruptions if a folder has been renamed and a new folder
// with the same name has been created but folderSync has not yet updated the cache
if (!([[theCollection nameInContainer] isEqualToString:
@@ -782,17 +839,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
if (more_available)
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
{
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
}
else
[folderMetadata removeObjectForKey: @"MoreAvailable"];
{
[folderMetadata removeObjectForKey: @"MoreAvailable"];
[folderMetadata setObject: [theCollection davCollectionTag] forKey: @"SyncKey"];
}
[self _setFolderMetadata: folderMetadata
forKey: [theCollection nameInContainer]];
} // default:
break;
} // switch (folderType) ...
return_response:
if ([s length])
{
[theBuffer appendString: @"<Commands>"];
@@ -996,7 +1060,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
davCollectionTag = [collection davCollectionTag];
}
// Generate the response buffer
[theBuffer appendString: @"<Collection>"];
+313 -28
View File
@@ -64,6 +64,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeFileData.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <NGMail/NGMimeMessageParser.h>
#import <NGMail/NGMimeMessage.h>
#import <NGMail/NGMimeMessageGenerator.h>
@@ -122,6 +123,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
@interface SOGoActiveSyncDispatcher (Sync)
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey;
@end
@implementation SOGoActiveSyncDispatcher
- (id) init
@@ -164,6 +171,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return [o properties];
}
- (unsigned int) _softDeleteCountWithFilter: (NSCalendarDate *) theFilter
collectionId: (NSString *) theCollectionId
{
NSMutableDictionary *dateCache;
NSMutableArray *sdUids;
SOGoCacheGCSObject *o;
NSArray *allKeys;
NSString *key;
int i;
sdUids = [NSMutableArray array];
if (theFilter)
{
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
dateCache = [[o properties] objectForKey: @"DateCache"];
allKeys = [dateCache allKeys];
for (i = 0; i < [allKeys count]; i++)
{
key = [allKeys objectAtIndex: i];
if ([[dateCache objectForKey:key] compare: theFilter ] == NSOrderedAscending)
[sdUids addObject: [dateCache objectForKey:key]];
}
}
return [sdUids count];
}
- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType
{
@@ -409,10 +451,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (!error)
{
NSString *syncKey, *key;
SOGoCacheGCSObject *o;
NSMutableString *s;
NSString *syncKey;
NSData *d;
//
// We mark the cache object as deleted
//
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], [folderToDelete nameInContainer]];
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
[o delete];
//
// We update the FolderSync's synckey
//
@@ -484,10 +536,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Handle new name exist
if (!error)
{
NSString *syncKey, *key;
SOGoCacheGCSObject *o;
NSMutableString *s;
NSString *syncKey;
NSData *d;
//
// We update our cache
//
key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], serverId];
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
key = [NSString stringWithFormat: @"/%@+%@", [context objectForKey: @"DeviceId"], [folderToUpdate nameInContainer]];
[o changePathTo: key];
//
// We update the FolderSync's synckey
//
@@ -499,8 +563,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self _setFolderSyncKey: syncKey];
// FIXME - TODO: We *MUST* update the path in our cache! See -changePathTo in SOGoCacheGCSObject
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
@@ -781,7 +843,51 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processGetAttachment: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *fileReference, *realCollectionId;
SOGoMicrosoftActiveSyncFolderType folderType;
fileReference = [context objectForKey: @"AttachmentName"];
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
if (folderType == ActiveSyncMailFolder)
{
id currentFolder, currentCollection, currentBodyPart;
NSString *folderName, *messageName, *pathToPart;
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
SOGoMailObject *mailObject;
NSRange r1, r2;
r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName]
inContext: context
acquire: NO];
mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO];
currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context];
[theResponse setHeader: [NSString stringWithFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]
forKey: @"Content-Type"];
[theResponse setContent: [currentBodyPart fetchBLOB] ];
}
else
{
[theResponse setStatus: 500];
}
}
//
@@ -849,6 +955,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sortOrdering: @"REVERSE ARRIVAL"
threaded: NO];
count = [uids count];
// Add the number of UIDs expected to "soft delete"
count += [self _softDeleteCountWithFilter: filter collectionId: realCollectionId];
}
else
{
@@ -891,7 +1001,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
fileReference = [[[(id)[theDocumentElement getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL];
realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType];
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
if (folderType == ActiveSyncMailFolder)
{
@@ -1218,30 +1327,73 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
//
// Ping requests make a little sense because the request
// doesn't contain the SyncKey on the client. So we can't
// really know if something has changed on the server. What we
// do for now is simply return Status=5 with the HeartbeatInterval
// set at 60 seconds or we wait 60 seconds before responding with
// Status=1
// <?xml version="1.0"?>
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
// <Ping xmlns="Ping:">
// <HeartbeatInterval>3540</HeartbeatInterval>
// <Folders>
// <Folder>
// <Id>mail%2Fsogo_680f_193506d5_0</Id>
// <Class>Email</Class>
// </Folder>
// <Folder>
// <Id>vevent/personal</Id>
// <Class>Calendar</Class>
// </Folder>
// <Folder>
// <Id>vcard/personal</Id>
// <Class>Contacts</Class>
// </Folder>
// <Folder>
// <Id>mail%2Fsogo_680f_193506d5_1</Id>
// <Class>Email</Class>
// </Folder>
// <Folder>
// <Id>mail%2Fsogo_680f_193506d5_2</Id>
// <Class>Email</Class>
// </Folder>
// <Folder>
// <Id>vtodo/personal</Id>
// <Class>Tasks</Class>
// </Folder>
// <Folder>
// <Id>mail%2Fsogo_753e_193511a1_0</Id>
// <Class>Email</Class>
// </Folder>
// <Folder>
// <Id>mail%2Fsogo_753e_193511a1_1</Id>
// <Class>Email</Class>
// </Folder>
// </Folders>
// </Ping>
//
- (void) processPing: (id <DOMElement>) theDocumentElement
inResponse: (WOResponse *) theResponse
{
NSString *collectionId, *realCollectionId, *syncKey;
NSMutableArray *foldersWithChanges, *allFoldersID;
SOGoMicrosoftActiveSyncFolderType folderType;
NSMutableDictionary *folderMetadata;
SOGoSystemDefaults *defaults;
id <DOMElement> aCollection;
NSArray *allCollections;
NSMutableString *s;
id collection;
NSData *d;
int heartbeatInterval, defaultInterval, status;
int i, j, heartbeatInterval, defaultInterval, internalInterval, status;
defaults = [SOGoSystemDefaults sharedSystemDefaults];
defaultInterval = [defaults maximumPingInterval];
internalInterval = [defaults internalSyncInterval];
if (theDocumentElement)
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
else
heartbeatInterval = defaultInterval;
if (heartbeatInterval > defaultInterval || heartbeatInterval == 0)
{
heartbeatInterval = defaultInterval;
@@ -1252,15 +1404,116 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
status = 1;
}
NSLog(@"Got Ping request with valid interval - sleeping for %d seconds.", heartbeatInterval);
sleep(heartbeatInterval);
// We build the list of folders to "ping". When the payload is empty, we use the list
// of "cached" folders.
allCollections = (id)[theDocumentElement getElementsByTagName: @"Folders"];
allFoldersID = [NSMutableArray array];
if (![allCollections count])
{
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
SOGoUserFolder *userFolder;
NSArray *allValues;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
allValues = [[accountFolder imapFolderGUIDs] allValues];
for (i = 0; i < [allValues count]; i++)
[allFoldersID addObject: [NSString stringWithFormat: @"mail/%@", [allValues objectAtIndex: i]]];
// FIXME: handle multiple GCS collecitons
[allFoldersID addObject: @"vcard/personal"];
[allFoldersID addObject: @"vevent/personal"];
[allFoldersID addObject: @"vtodo/personal"];
}
else
{
for (i = 0; i < [allCollections count]; i++)
{
aCollection = [allCollections objectAtIndex: i];
collectionId = [[(id) [aCollection getElementsByTagName: @"Id"] lastObject] textValue];
[allFoldersID addObject: collectionId];
}
}
foldersWithChanges = [NSMutableArray array];
// We enter our loop detection change
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
{
for (j = 0; j < [allFoldersID count]; j++)
{
collectionId = [allFoldersID objectAtIndex: j];
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
collection = [self collectionFromId: realCollectionId type: folderType];
switch (folderType)
{
case ActiveSyncContactFolder:
folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vcard/%@", [collection nameInContainer]]];
break;
case ActiveSyncEventFolder:
folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vevent/%@", [collection nameInContainer]]];
break;
case ActiveSyncTaskFolder:
folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"vtodo/%@", [collection nameInContainer]]];
break;
default:
folderMetadata = [self _folderMetadataForKey: [collection nameInContainer]];
}
syncKey = [folderMetadata objectForKey: @"SyncKey"];
if (![syncKey isEqualToString: [collection davCollectionTag]])
{
[foldersWithChanges addObject: collectionId];
}
}
if ([foldersWithChanges count])
{
NSLog(@"Change detected, we push the content.");
status = 2;
break;
}
else
{
NSLog(@"Sleeping %d seconds while detecting changes...", internalInterval);
sleep(internalInterval);
}
}
// We generate our response
s = [NSMutableString string];
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<Ping xmlns=\"Ping:\">"];
[s appendFormat: @"<Status>%d</Status>", status];
if ([foldersWithChanges count])
{
[s appendString: @"<Folders>"];
for (i = 0; i < [foldersWithChanges count]; i++)
{
// A bit tricky here because we must call stringByEscapingURL on mail folders, but not on GCS ones.
// We do the same thing in -processFolderSync
collectionId = [foldersWithChanges objectAtIndex: i];
if ([collectionId hasPrefix: @"mail/"])
collectionId = [collectionId stringByEscapingURL];
[s appendFormat: @"<Folder>%@</Folder>", collectionId];
}
[s appendString: @"</Folders>"];
}
if (status == 5)
{
@@ -1686,9 +1939,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
value = [theDocumentElement getElementsByTagName: @"ReplaceMime"];
// ReplaceMime isn't specified so we must NOT use the server copy
// ReplaceMime IS specified so we must NOT use the server copy
// but rather take the data as-is from the client.
if (![value count])
if ([value count])
{
[self processSendMail: theDocumentElement
inResponse: theResponse];
@@ -1708,11 +1961,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSData *data;
NGMimeMessageGenerator *generator;
NGMimeBodyPart *bodyPart;
NGMimeBodyPart *bodyPart;
NGMutableHashMap *map;
NGMimeFileData *fdata;
NSException *error;
id body;
id body, bodyFromSmartForward;
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
@@ -1728,10 +1982,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
parser = [[NGMimeMessageParser alloc] init];
data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding];
messageFromSmartForward = [parser parsePartFromData: data];
RELEASE(parser);
// We create a new MIME multipart/mixed message. The first part will be the text part
// of our "smart forward" and the second part will be the message/rfc822 part of the
// "smart forwarded" message.
@@ -1741,11 +1993,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease];
body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease];
// First part
// First part - either a text/* or a multipart/*. If it's a multipart,
// we take the first part text/* part we see.
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
[map setObject: @"text/plain" forKey: @"content-type"];
bodyFromSmartForward = nil;
if ([[messageFromSmartForward body] isKindOfClass: [NGMimeMultipartBody class]])
{
NGMimeBodyPart *part;
NSArray *parts;
int i;
parts = [[messageFromSmartForward body] parts];
for (i = 0; i < [parts count]; i++)
{
part = [parts objectAtIndex: i];
if ([[[part contentType] type] isEqualToString: @"text"])
{
[map setObject: [[part contentType] stringValue] forKey: @"content-type"];
bodyFromSmartForward = [part body];
break;
}
}
}
else
{
[map setObject: [[messageFromSmartForward contentType] stringValue] forKey: @"content-type"];
bodyFromSmartForward = [messageFromSmartForward body];
}
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
[bodyPart setBody: [messageFromSmartForward body]];
[bodyPart setBody: bodyFromSmartForward];
[body addBodyPart: bodyPart];
// Second part
@@ -1826,6 +2106,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
deviceId = [[theRequest uri] deviceId];
[context setObject: deviceId forKey: @"DeviceId"];
[context setObject: [[theRequest uri] deviceType] forKey: @"DeviceType"];
[context setObject: [[theRequest uri] attachmentName] forKey: @"AttachmentName"];
cmdName = [[theRequest uri] command];
@@ -1869,7 +2151,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
// We check if it's a Ping command with no body.
// See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame)
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame)
return [NSException exceptionWithHTTPStatus: 500];
}
@@ -1923,9 +2205,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([parts count] == 5)
{
/* If "OCSFolderInfoURL" is properly configured, we must have 5
parts in this url. */
parts in this url. We strip the '-' character in case we have
this in the domain part - like foo@bar-zot.com */
ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@",
[[user loginInDomain] asCSSIdentifier]];
[[[user loginInDomain] asCSSIdentifier]
stringByReplacingOccurrencesOfString: @"-"
withString: @"_"]];
[parts replaceObjectAtIndex: 4 withObject: ocFSTableName];
folderTableURL
= [NSURL URLWithString: [parts componentsJoinedByString: @"/"]];
+42 -16
View File
@@ -238,6 +238,8 @@ struct GlobalObjectId {
if (key)
{
NSString *s, *charset;
d = [[self fetchPlainTextParts] objectForKey: key];
encoding = [[self lookupInfoForBodyPart: key] objectForKey: @"encoding"];
@@ -246,6 +248,14 @@ struct GlobalObjectId {
d = [d dataByDecodingBase64];
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
d = [d dataByDecodingQuotedPrintableTransferEncoding];
charset = [[[self lookupInfoForBodyPart: key] objectForKey: @"parameterList"] objectForKey: @"charset"];
if (![charset length])
charset = @"us-ascii";
s = [NSString stringWithData: d usingEncodingNamed: charset];
d = [s dataUsingEncoding: NSUTF8StringEncoding];
}
return d;
@@ -296,13 +306,13 @@ struct GlobalObjectId {
if ([body isKindOfClass: [NSData class]])
{
NSString *charset;
int encoding;
charset = [[thePart contentType] valueOfParameter: @"charset"];
encoding = [NGMimeType stringEncodingForCharset: charset];
if (![charset length])
charset = @"us-ascii";
s = [[NSString alloc] initWithData: body encoding: encoding];
AUTORELEASE(s);
s = [NSString stringWithData: body usingEncodingNamed: charset];
}
else
{
@@ -396,6 +406,13 @@ struct GlobalObjectId {
{
if ([type isEqualToString: @"text"])
{
NSString *s, *charset;
charset = [[[self lookupInfoForBodyPart: @""] objectForKey: @"parameterList"] objectForKey: @"charset"];
if (![charset length])
charset = @"us-ascii";
d = [[self fetchPlainTextParts] objectForKey: @""];
// We check if we have base64 encoded parts. If so, we just
@@ -407,17 +424,15 @@ struct GlobalObjectId {
else if ([encoding caseInsensitiveCompare: @"quoted-printable"] == NSOrderedSame)
d = [d dataByDecodingQuotedPrintableTransferEncoding];
s = [NSString stringWithData: d usingEncodingNamed: charset];
// Check if we must convert html->plain
if (theType == 1 && [subtype isEqualToString: @"html"])
{
NSString *s;
s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
AUTORELEASE(s);
s = [s htmlToText];
d = [s dataUsingEncoding: NSUTF8StringEncoding];
}
d = [s dataUsingEncoding: NSUTF8StringEncoding];
}
else if ([type isEqualToString: @"multipart"])
{
@@ -761,7 +776,7 @@ struct GlobalObjectId {
// Flags
[s appendString: @"<Flag xmlns=\"Email:\">"];
[s appendFormat: @"<FlagStatus>%d</FlagStatus>", 0];
[s appendFormat: @"<FlagStatus>%d</FlagStatus>", ([self flagged] ? 2 : 0)];
[s appendString: @"</Flag>"];
// FIXME - support these in the future
@@ -799,10 +814,21 @@ struct GlobalObjectId {
if ((o = [theValues objectForKey: @"Flag"]))
{
o = [o objectForKey: @"FlagStatus"];
if ([o intValue])
[self addFlags: @"\\Flagged"];
// We must handle empty flags -> {Flag = ""; } - some ActiveSync clients, like the HTC Desire
// will send an empty Flag message when "unflagging" a mail.
if (([o isKindOfClass: [NSMutableDictionary class]]))
{
if ((o = [o objectForKey: @"FlagStatus"]))
{
// 0 = The flag is cleared.
// 1 = The status is set to complete.
// 2 = The status is set to active.
if (([o isEqualToString: @"2"]))
[self addFlags: @"\\Flagged"];
else
[self removeFlags: @"\\Flagged"];
}
}
else
[self removeFlags: @"\\Flagged"];
}
+451
View File
@@ -1,3 +1,454 @@
commit 5dd2088d42388b397e8cc3fde35455b2455f7f3e
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jul 2 11:32:06 2014 -0400
Updated for the release
M NEWS
M Version
commit 9c3c2cb92657809945eeaf25310039173efb58b0
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jul 2 11:23:57 2014 -0400
Updated the doc for the release
M Documentation/SOGo Installation Guide.odt
M Documentation/SOGo Mozilla Thunderbird Configuration.odt
M Documentation/SOGo Native Microsoft Outlook Configuration.odt
commit 55422b5f037b1c410c25a685afa6d3077c3fce21
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jul 2 10:11:13 2014 -0400
Fixed typo
M NEWS
commit 899a1eb026503165436a333110fa6eb73522c5a6
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jul 2 08:39:01 2014 -0400
Updated NEWS file
M NEWS
commit 660f39a980513bf9bcff14bdc8d74dd092c50717
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Mon Jun 30 14:13:52 2014 -0400
delete notice and put grey background for multi-columns view
M UI/Scheduler/English.lproj/Localizable.strings
M UI/WebServerResources/SchedulerUI.js
commit 8335c388308d381afc8b4b544f0b9b88e2140c25
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Mon Jun 30 13:57:10 2014 -0400
fix infinite loop bug
M UI/WebServerResources/SchedulerUI.js
commit 0fb7135335810a31c8936a3e58680f93c550b0c2
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Mon Jun 30 13:36:12 2014 -0400
bugfixes
M UI/Scheduler/UIxCalListingActions.m
M UI/WebServerResources/SchedulerUI.js
M UI/WebServerResources/UIxCalViewPrint.js
A UI/WebServerResources/error.png
A UI/WebServerResources/notice.png
A UI/WebServerResources/success.png
A UI/WebServerResources/warning.png
commit 6b58e034f3db20f3433eccfd425f0e57349aeced
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Mon Jun 30 10:42:07 2014 -0400
printView display the parentView by default
M UI/Scheduler/UIxCalViewPrint.h
M UI/Scheduler/UIxCalViewPrint.m
M UI/Templates/SchedulerUI/UIxCalViewPrint.wox
M UI/WebServerResources/UIxCalViewPrint.js
commit ff015afde8700b0a4163c89ce4b9cdca5f6261e5
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 30 10:35:55 2014 -0400
Fixed indent
M UI/PreferencesUI/UIxPreferences.m
commit 6da03bc75ca64a79785001897eb629b6250b2cb8
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Sat Jun 28 08:31:07 2014 -0400
Cleaned up code for older gcc
M UI/Scheduler/UIxCalListingActions.m
commit 2b119c63e0bd0fa96361369152a5aface05ffe32
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Fri Jun 27 17:21:18 2014 -0400
Applied comments
M UI/Scheduler/UIxCalDayTable.m
M UI/Scheduler/UIxCalListingActions.m
commit 5b61df8ca4035d3bd66bb024a1f5b62cd6054a84
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Fri Jun 27 17:00:32 2014 -0400
activeTasks updates dynamically
M UI/Scheduler/UIxCalListingActions.m
M UI/Scheduler/product.plist
M UI/WebServerResources/SchedulerUI.js
commit 5b9c7e07d1d6b6eba1901d3fa40ee22cbbf73ead
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Thu Jun 26 21:22:12 2014 -0400
text wraping and text alignment in list view
M UI/WebServerResources/UIxCalViewPrint.css
M UI/WebServerResources/generic.css
commit 7a69bc46eb5c2300e93d4b85c6954638a4271132
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Thu Jun 26 16:42:05 2014 -0400
applied comments
M SoObjects/Appointments/SOGoAppointmentFolder.m
M UI/Scheduler/UIxCalendarSelector.m
M UI/WebServerResources/SchedulerUI.js
commit 302e40a1c3c1f13578499d82e40c75e50beff79e
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Thu Jun 26 16:08:47 2014 -0400
bugFix#2823
M SoObjects/Mailer/SOGoDraftObject.m
M SoObjects/SOGo/SOGoParentFolder.m
M UI/PreferencesUI/UIxPreferences.m
M UI/Templates/PreferencesUI/UIxPreferences.wox
M UI/WebServerResources/UIxPreferences.js
commit b6be4e6e2fb8e00c8f0b29e4f493936d0fcfbb2e
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Thu Jun 26 11:01:16 2014 -0400
Applied patch from bug #2810
M ActiveSync/SOGoMailObject+ActiveSync.m
M NEWS
commit dabd8d3282c1e1f4a4d3622637c7952e1e295cb5
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Thu Jun 26 10:05:48 2014 -0400
Fix list view printing with multiColumns view
M UI/WebServerResources/UIxCalViewPrint.js
commit d7d458a934b407bea6aa7e1818e71b5d544cf705
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Wed Jun 25 17:39:48 2014 -0400
multiColumns print view + bugFixes
M UI/Scheduler/English.lproj/Localizable.strings
M UI/Scheduler/UIxCalViewPrint.m
M UI/Templates/SchedulerUI/UIxCalViewPrint.wox
M UI/WebServerResources/SchedulerUI.css
M UI/WebServerResources/SchedulerUI.js
M UI/WebServerResources/UIxCalViewPrint.css
M UI/WebServerResources/UIxCalViewPrint.js
commit 5419f411e5bae58cc17ab22b3b95a88efab74252
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jun 25 15:05:25 2014 -0400
Fix for bug #2734
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
M ActiveSync/SOGoActiveSyncDispatcher.m
M NEWS
commit 22f5ed62fef337f9fa2f84318e3c24962ee3c207
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jun 25 10:14:28 2014 -0400
Fixed tests regarding default permissions on CalDAV collections
M Tests/Integration/test-davacl.py
commit 3523dc489f00c147279217faab9ad12504ca7340
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 23 12:19:02 2014 -0400
Fix for bug #2825
M SoObjects/SOGo/GCSSpecialQueries+SOGoCacheObject.m
commit 5b3de04fc19daef85749fe34b969724f78a6b7ab
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Fri Jun 20 13:27:04 2014 -0400
Fix error from the last commit
M UI/Scheduler/UIxCalDayTable.m
commit eb3cc8916df223dae773243dcbcf65269e8b0451
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Fri Jun 20 13:16:55 2014 -0400
The multiColumns view
M UI/Scheduler/English.lproj/Localizable.strings
M UI/Scheduler/Toolbars/SOGoAppointmentFolders.toolbar
M UI/Scheduler/UIxCalDayTable.h
M UI/Scheduler/UIxCalDayTable.m
M UI/Scheduler/UIxCalListingActions.h
M UI/Scheduler/UIxCalListingActions.m
M UI/Scheduler/UIxCalMulticolumnDayView.m
M UI/Scheduler/UIxCalendarSelector.m
M UI/Templates/SchedulerUI/UIxCalDayTable.wox
M UI/Templates/SchedulerUI/UIxCalDayView.wox
M UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox
M UI/Templates/SchedulerUI/UIxCalWeekView.wox
M UI/WebServerResources/SchedulerUI.css
M UI/WebServerResources/SchedulerUI.js
commit 9fa0b42e19c76184d4195a1530039dcad5cb5a80
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Fri Jun 20 11:25:02 2014 -0400
Ignore SCHEDULE-AGENT for Thunderbird/Lightning
M NEWS
M SoObjects/Appointments/SOGoAppointmentObject.m
commit e048346cb715d03fe6bd32d837b96107fdb6d44b
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Thu Jun 19 16:08:15 2014 -0400
Include, in our private NS, all ACLs defined on a calendar collectiob
M SoObjects/Appointments/SOGoAppointmentFolder.m
commit 333b561a5932acbe66d5b62c8963dd166b9de7f2
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 16 15:30:26 2014 -0400
Applied patch from bug #2736
M SOPE/NGCards/NGCardsSaxHandler.m
M SOPE/NGCards/samples/vcf2xml.m
M SoObjects/Mailer/NSString+Mail.m
M UI/MailPartViewers/UIxMailPartHTMLViewer.m
commit 50c0b49a6fe210b438d2e5a7abac7219bdb15b8f
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 16 11:00:39 2014 -0400
Fix for bug #2813
M NEWS
M SoObjects/SOGo/SOGoGCSFolder.m
commit 8202d0588fb8e1774e502941fc01223fb9005334
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Mon Jun 16 10:20:02 2014 -0400
Add documentation for SOGoSubscriptionFolderFormat
Fixes #2799
M Documentation/SOGo Installation Guide.odt
commit d9b578b869da27b876f019bc10acf72839220fd3
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Mon Jun 16 09:52:29 2014 -0400
Wait 20 seconds before deleting temporary forms
Those temporary forms are created to download attachments.
Fixes #2811
M NEWS
M UI/WebServerResources/MailerUI.js
commit 5bb64916c6fc96bf20bc258a075c33413784ca01
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Thu Jun 12 09:09:08 2014 -0400
Fix regression when attaching files to a reply
M NEWS
M UI/MailerUI/UIxMailEditor.m
commit e7ddfb0546fcb70f1ac3401375656936ea00c380
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Wed Jun 11 12:55:51 2014 -0400
Fix for bug #2805
M ActiveSync/SOGoActiveSyncDispatcher.m
M NEWS
commit 0e182f84360aa35c87d39320cab08d7eaeb55d90
Author: Alexandre Cloutier <alexandre.cl07@gmail.com>
Date: Wed Jun 11 11:25:17 2014 -0400
Update SOGoAppointmentFolder.m
Fix the status filtering from 2 to 1
M SoObjects/Appointments/SOGoAppointmentFolder.m
commit de2ee2322af740251ac82ca06deb1f7585637024
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Wed Jun 11 10:24:04 2014 -0400
Change the NSString with NSNumber and add some JavaScript
M SoObjects/Appointments/SOGoAppointmentFolder.h
M SoObjects/Appointments/SOGoAppointmentFolder.m
M UI/Scheduler/UIxCalendarSelector.m
M UI/Templates/SchedulerUI/UIxCalendarSelector.wox
M UI/WebServerResources/SchedulerUI.js
commit ce07695a5597964354404557c9246eee5325abb2
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 16:35:36 2014 -0400
Make sure to keep the / path prefix
M ActiveSync/SOGoActiveSyncDispatcher.m
commit 43ac57b671ecf4f0d81daf3e40fab856a5d3b52a
Author: Alexandre Cloutier <acloutier@inverse.ca>
Date: Tue Jun 10 16:18:12 2014 -0400
display the number of active tasks in calendar view
M SoObjects/Appointments/SOGoAppointmentFolder.h
M SoObjects/Appointments/SOGoAppointmentFolder.m
M UI/Scheduler/UIxCalendarSelector.m
M UI/Templates/SchedulerUI/UIxCalendarSelector.wox
commit 7f7e3cb06a6b8789a1cd1667fb62418fad02aac1
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Tue Jun 10 14:48:25 2014 -0400
Fix folder name of new subscriptions
Fixes #2801
M NEWS
M UI/MainUI/SOGoUserHomePage.m
M UI/WebServerResources/UIxContactsUserFolders.js
commit 6b3a3f858baf2b73a7910a2039103f2976ebdc7c
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Mon Jun 9 22:07:44 2014 -0400
Update prototype.js to version 1.7.2
M UI/WebServerResources/prototype.js
commit 0133bb3b6bf13c81350b791be57c01d83356cba7
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 13:07:14 2014 -0400
Correctly update the cache on folder update/delete ops.
M ActiveSync/SOGoActiveSyncDispatcher.m
commit 91e3e19e40b843063cda21b84e6fcc072da2ad49
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 11:04:27 2014 -0400
Added the Ping ActiveSync command
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
M ActiveSync/SOGoActiveSyncDispatcher.m
M NEWS
commit 19a95a1818e34755bffd7c5f9dbd35e214123cac
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 08:58:44 2014 -0400
Applied fix from #2809
M NEWS
M UI/MailPartViewers/UIxMailPartHTMLViewer.m
commit 1292c191eca61027b9856b04840105fde588d42c
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 08:46:35 2014 -0400
Make sure we don't have '-' in table names.
M OpenChange/MAPIStoreUserContext.m
commit 77dad178d11b36da07660a9b5f0dd8c7a74cad50
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Jun 10 08:45:35 2014 -0400
Make sure we don't have '-' in table names.
M ActiveSync/SOGoActiveSyncDispatcher.m
commit f48839bf55917cb3a9756134930206b7b15d0279
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 9 09:34:00 2014 -0400
Added (slightly modified) patch from #2806
M ActiveSync/SOGoMailObject+ActiveSync.m
M NEWS
M SoObjects/Mailer/SOGoMailObject.h
M SoObjects/Mailer/SOGoMailObject.m
commit 0f04be2bcb003ff0b0c1099c524dffa3b40d0181
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 9 09:25:06 2014 -0400
Added patch from #2808
M ActiveSync/NSString+ActiveSync.h
M ActiveSync/NSString+ActiveSync.m
M ActiveSync/SOGoActiveSyncDispatcher.m
M NEWS
commit 3f8bbe8c6012e60334002affded0c94151db20e3
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Jun 9 09:14:30 2014 -0400
Applied patch from #2807
M ActiveSync/NGVCard+ActiveSync.m
M NEWS
M SOPE/NGCards/NGVCard.h
M SOPE/NGCards/NGVCard.m
commit 6b6ec8a2ae0f453dd0a127d8cf37082c80449c94
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Thu Jun 5 13:35:17 2014 -0400
Update ChangeLog
M ChangeLog
commit ad30df26af2c95f91ad42fd02a3b45ada115502b
Author: Francis Lachapelle <flachapelle@inverse.ca>
Date: Thu Jun 5 13:34:40 2014 -0400
Binary file not shown.
+25
View File
@@ -1,3 +1,28 @@
2.2.6 (2014-07-02)
------------------
New features
- add new 'multi-columns' calendar view (#1948)
Enhancements
- contacts photos are now synchronized using ActiveSync (#2807)
- implemented the GetAttachment ActiveSync command (#2808)
- implemented the Ping ActiveSync command
- added "soft deletes" support for ActiveSync (#2734)
- now display the active tasks next to calendar names (#2760)
Bug fixes
- better handling of empty "Flag" messages over ActiveSync (#2806)
- fixed Chinese charset handling (#2809)
- fixed folder name (calendars and contacts) of new subscriptions (#2801)
- fixed the reply/forward operation over ActiveSync (#2805)
- fixed regression when attaching files to a reply
- wait 20 seconds (instead of 2) before deleting temporary download forms (#2811)
- avoid raising exceptions when the db is down and we try to access the preferences module (#2813)
- we now ignore the SCHEDULE-AGENT property when Thunderbird/Lightning sends it to avoid
not-generating invitation responses for externally received IMIP messages
- improved charset handling over ActiveSync (#2810)
2.2.5 (2014-06-05)
------------------
+5 -2
View File
@@ -294,9 +294,12 @@ static NSMapTable *contextsTable = nil;
if ([parts count] == 5)
{
/* If "OCSFolderInfoURL" is properly configured, we must have 5
parts in this url. */
parts in this url. We strip the '-' character in case we have
this in the domain part - like foo@bar-zot.com */
ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@",
[username asCSSIdentifier]];
[[[user loginInDomain] asCSSIdentifier]
stringByReplacingOccurrencesOfString: @"-"
withString: @"_"]];
[parts replaceObjectAtIndex: 4 withObject: ocFSTableName];
folderTableURL
= [NSURL URLWithString: [parts componentsJoinedByString: @"/"]];
+1 -1
View File
@@ -232,7 +232,7 @@ static NSArray *privilegedTagNames = nil;
}
- (void) characters: (unichar *) _chars
length: (int) _len
length: (NSUInteger) _len
{
if (_len && _chars)
{
+3
View File
@@ -126,6 +126,9 @@ typedef enum
- (void) setCategories: (NSArray *) newCategories;
- (NSArray *) categories;
- (NSString *) photo;
- (void) setPhoto: (NSString *) _value;
// - (void) setN: (NGVCardName *) _v;
// - (NGVCardName *) n;
+19
View File
@@ -288,6 +288,25 @@
return [self uniqueChildWithTag: @"n"];
}
- (NSString *) photo
{
NGVCardPhoto *photo;
photo = (NGVCardPhoto *)[self uniqueChildWithTag: @"photo"];
return [[photo valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
}
- (void) setPhoto: (NSString *) _value
{
CardElement *photo;
photo = [self uniqueChildWithTag: @"photo"];
[photo setValue: 0 ofAttribute: @"encoding" to: @"BASE64"];
[photo setSingleValue: _value forKey: @""];
}
- (void) setOrg: (NSString *) anOrg
units: (NSArray *) someUnits
{
+4 -4
View File
@@ -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;
@@ -80,6 +80,8 @@ typedef enum {
- (NSArray *) calendarUIDs;
- (NSNumber *) activeTasks;
/* vevent UID handling */
- (NSString *) resourceNameForEventUID: (NSString *) _uid;
+89 -1
View File
@@ -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 */
+24 -1
View File
@@ -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;
}
+1 -1
View File
@@ -430,7 +430,7 @@
}
- (void) ignorableWhitespace: (unichar *) whitespaces
length: (int) length
length: (NSUInteger) length
{
showWhoWeAre();
}
+10 -10
View File
@@ -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];
+1
View File
@@ -114,6 +114,7 @@ NSArray *SOGoMailCoreInfoKeys;
- (NSException *) removeFlags:(id)_f;
- (BOOL) isNewMail; /* \Recent */
- (BOOL) flagged; /* \Flagged */
- (BOOL) read; /* \Unseen */
- (BOOL) replied; /* \Answered */
- (BOOL) forwarded; /* $forwarded */
+5
View File
@@ -1464,6 +1464,11 @@ static BOOL debugSoParts = NO;
return [self _hasFlag: @"seen"];
}
- (BOOL) flagged
{
return [self _hasFlag: @"flagged"];
}
- (BOOL) replied
{
return [self _hasFlag: @"answered"];
@@ -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,"
+18 -11
View File
@@ -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;
}
}
+26 -15
View File
@@ -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
+11 -1
View File
@@ -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()
+8 -5
View File
@@ -86,6 +86,9 @@ _xmlCharsetForCharset (NSString *charset)
{ @"windows-1251", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1251 -> utf8 conversion
{ @"windows-1257", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1257 -> utf8 conversion
{ @"gb2312", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
{ @"gbk", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
{ @"gb18030", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
{ @"big5", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
{ @"euc-jp", XML_CHAR_ENCODING_EUC_JP}};
unsigned count;
xmlCharEncoding encoding;
@@ -416,9 +419,9 @@ static NSData* _sanitizeContent(NSData *theData)
}
- (void) _appendStyle: (unichar *) _chars
length: (int) _len
length: (NSUInteger) _len
{
unsigned int count, length;
NSUInteger count, length;
unichar *start, *currentChar;
start = _chars;
@@ -685,7 +688,7 @@ static NSData* _sanitizeContent(NSData *theData)
}
- (void) characters: (unichar *) _chars
length: (int) _len
length: (NSUInteger) _len
{
showWhoWeAre();
if (!ignoredContent)
@@ -709,7 +712,7 @@ static NSData* _sanitizeContent(NSData *theData)
}
- (void) ignorableWhitespace: (unichar *) _chars
length: (int) _len
length: (NSUInteger) _len
{
showWhoWeAre();
}
@@ -732,7 +735,7 @@ static NSData* _sanitizeContent(NSData *theData)
/* SaxLexicalHandler */
- (void) comment: (unichar *) _chars
length: (int) _len
length: (NSUInteger) _len
{
showWhoWeAre();
if (inStyle)
+2 -1
View File
@@ -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);
+3 -5
View File
@@ -364,8 +364,7 @@
// We sort our array - this is pretty useful for the Web
// interface of SOGo.
allUsers = [users
sortedArrayUsingSelector: @selector (caseInsensitiveDisplayNameCompare:)];
allUsers = [users sortedArrayUsingSelector: @selector (caseInsensitiveDisplayNameCompare:)];
max = [allUsers count];
jsonResponse = [NSMutableArray arrayWithCapacity: max];
@@ -462,9 +461,8 @@
folderType = [self queryParameterForKey: @"type"];
userFolder = [self clientObject];
folders
= [userFolder foldersOfType: folderType
forUID: [userFolder ownerInContext: context]];
folders = [userFolder foldersOfType: folderType
forUID: [userFolder ownerInContext: context]];
result = [self _foldersResponseForResults: folders];
return result;
+40 -36
View File
@@ -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;
@@ -155,6 +155,7 @@
"What to Print" = "What to Print";
"Options" = "Options";
"Tasks with no due date" = "Tasks with no due date";
"Display working hours only" = "Display working hours only";
"Completed tasks" = "Completed Tasks";
/* Appointments */
@@ -313,6 +314,7 @@
"Repeat" = "Repeat";
"Daily" = "Daily";
"Multi-Columns" = "Multi-Columns";
"Weekly" = "Weekly";
"Monthly" = "Monthly";
"Yearly" = "Yearly";
@@ -542,6 +544,8 @@ vtodo_class2 = "(Confidential task)";
"tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?";
"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar.";
"EventCopyError" = "The copy failed. Please try to copy to a difference calendar.";
"Please select at least one calendar" = "Please select at least one calendar";
"Open Task..." = "Open Task...";
"Mark Completed" = "Mark Completed";
@@ -21,12 +21,11 @@
onclick = "return onDayOverview();";
image = "day-view.png";
tooltip = "Switch to day view"; },
/* disabled until we fix the view */
/* { link = "#";
label="Multicolumn Day View";
onclick = "return onMulticolumnDayOverview();";
image = "day-view-multicolumn.png";
tooltip = ""; }, */
{ link = "#";
label="Multi-Columns View";
onclick = "return onMulticolumnDayOverview();";
image = "day-view-multicolumn.png";
tooltip = "Switch to multi-columns day view"; },
{ link = "#";
label="Week View";
onclick = "return onWeekOverview();";
+8 -8
View File
@@ -35,27 +35,27 @@
@interface UIxCalDayTable : UIxCalView
{
unsigned int numberOfDays;
NSCalendarDate *startDate;
NSCalendarDate *currentTableDay;
NSString *currentTableHour;
NSMutableArray *daysToDisplay;
NSMutableArray *hoursToDisplay;
NSArray *weekDays;
SOGoDateFormatter *dateFormatter;
NSString *timeFormat;
NSArray *weekDays;
NSString *currentView, *timeFormat, *currentTableHour;
NSCalendarDate *startDate, *currentTableDay;
NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay;
unsigned int numberOfDays;
}
- (void) setNumberOfDays: (NSNumber *) aNumber;
- (NSNumber *) numberOfDays;
- (NSString *) currentView;
- (void) setStartDate: (NSCalendarDate *) aStartDate;
- (NSCalendarDate *) startDate;
- (NSCalendarDate *) endDate;
- (NSArray *) daysToDisplay;
- (NSArray *) calendarsToDisplay;
- (void) setCurrentTableDay: (NSCalendarDate *) aTableDay;
- (NSCalendarDate *) currentTableDay;
- (NSMutableArray *) currentCalendar;
@end
+127 -17
View File
@@ -55,9 +55,12 @@
ASSIGN (timeFormat, [ud timeFormat]);
daysToDisplay = nil;
calendarsToDisplay = nil;
hoursToDisplay = nil;
numberOfDays = 1;
startDate = nil;
currentView = nil;
currentCalendar = nil;
currentTableDay = nil;
currentTableHour = nil;
weekDays = [locale objectForKey: NSShortWeekDayNameArray];
@@ -75,6 +78,8 @@
// [allAppointments release];
[weekDays release];
[daysToDisplay release];
[calendarsToDisplay release];
[currentView release];
[hoursToDisplay release];
[dateFormatter release];
[timeFormat release];
@@ -119,6 +124,16 @@
return [endDate endOfDay];
}
- (void) setCurrentView: (NSString *) aView
{
ASSIGN(currentView, aView);
}
- (NSString *) currentView
{
return currentView;
}
- (NSArray *) hoursToDisplay
{
unsigned int currentHour, lastHour;
@@ -148,22 +163,68 @@
{
NSCalendarDate *currentDate;
int count;
if (!daysToDisplay)
{
daysToDisplay = [NSMutableArray new];
currentDate = [[self startDate] hour: [self dayStartHour]
minute: 0];
for (count = 0; count < numberOfDays; count++)
{
daysToDisplay = [NSMutableArray new];
currentDate = [[self startDate] hour: [self dayStartHour]
minute: 0];
for (count = 0; count < numberOfDays; count++)
{
[daysToDisplay addObject: currentDate];
currentDate = [currentDate tomorrow];
}
[daysToDisplay addObject: currentDate];
currentDate = [currentDate tomorrow];
}
}
return daysToDisplay;
}
- (NSArray *) calendarsToDisplay
{
if (!calendarsToDisplay)
{
int max=0, i;
NSArray *folders;
SOGoAppointmentFolders *co;
SOGoAppointmentFolder *folder;
NSMutableDictionary *calendar;
unsigned int count, foldersCount;
NSString *folderName, *fDisplayName;
NSNumber *isActive;
co = [self clientObject];
folders = [co subFolders];
foldersCount = [folders count];
calendarsToDisplay = [[NSMutableArray alloc] initWithCapacity: foldersCount];
for (count = 0; count < foldersCount; count++)
{
folder = [folders objectAtIndex: count];
isActive = [NSNumber numberWithBool: [folder isActive]];
if ([isActive intValue] != 0) {
calendar = [NSMutableDictionary dictionary];
folderName = [folder nameInContainer];
fDisplayName = [folder displayName];
if (fDisplayName == nil)
fDisplayName = @"";
if ([fDisplayName isEqualToString: [co defaultFolderName]])
fDisplayName = [self labelForKey: fDisplayName];
[calendar setObject: [NSString stringWithFormat: @"/%@", folderName]
forKey: @"id"];
[calendar setObject: fDisplayName forKey: @"displayName"];
[calendar setObject: folderName forKey: @"folder"];
[calendar setObject: [folder calendarColor] forKey: @"color"];
[calendar setObject: isActive forKey: @"active"];
[calendar setObject: [folder ownerInContext: context]
forKey: @"owner"];
[calendarsToDisplay addObject: calendar];
}
}
}
return calendarsToDisplay;
}
- (void) setCurrentTableDay: (NSCalendarDate *) aTableDay
{
currentTableDay = aTableDay;
@@ -174,6 +235,16 @@
return currentTableDay;
}
- (void) setCurrentCalendar: (NSMutableArray *) aCalendar
{
ASSIGN(currentCalendar, aCalendar);
}
- (NSMutableArray *) currentCalendar
{
return currentCalendar;
}
- (void) setCurrentTableHour: (NSString *) aTableHour
{
currentTableHour = aTableHour;
@@ -226,6 +297,16 @@
return [dateFormatter shortFormattedDate: currentTableDay];
}
- (NSString *) labelForCalendar
{
return [currentCalendar objectForKey: @"displayName"];
}
- (NSString *) colorForCalendar
{
return [currentCalendar objectForKey:@"color"];
}
// - (NSDictionary *) _adjustedAppointment: (NSDictionary *) anAppointment
// forStart: (NSCalendarDate *) start
// andEnd: (NSCalendarDate *) end
@@ -318,7 +399,15 @@
- (NSString *) daysViewClasses
{
return [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays];
NSString *daysView;
if ([currentView isEqualToString:@"multicolumndayview"])
daysView = @"daysView daysViewForMultipleDays";
else
daysView = [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays];
return daysView;
}
- (NSString *) dayClasses
@@ -326,19 +415,24 @@
NSMutableString *classes;
unsigned int currentDayNbr, realDayOfWeek;
currentDayNbr = [daysToDisplay indexOfObject: currentTableDay];
realDayOfWeek = [currentTableDay dayOfWeek];
classes = [NSMutableString string];
[classes appendFormat: @"day day%d", currentDayNbr];
if (numberOfDays > 1)
if ([currentView isEqualToString:@"multicolumndayview"])
[classes appendFormat:@"day dayColumn"];
else {
currentDayNbr = [daysToDisplay indexOfObject: currentTableDay];
realDayOfWeek = [currentTableDay dayOfWeek];
[classes appendFormat: @"day day%d", currentDayNbr];
if (numberOfDays > 1)
{
if (realDayOfWeek == 0 || realDayOfWeek == 6)
[classes appendString: @" weekEndDay"];
if ([currentTableDay isToday])
[classes appendString: @" dayOfToday"];
}
}
return classes;
}
@@ -358,4 +452,20 @@
return cellClass;
}
- (BOOL) isMultiColumnView
{
if ([currentView isEqualToString:@"multicolumndayview"])
return YES;
return NO;
}
- (BOOL) isNotMultiColumnView
{
if ([currentView isEqualToString:@"dayview"] || [currentView isEqualToString:@"weekview"])
return YES;
return NO;
}
@end
+1
View File
@@ -43,6 +43,7 @@
NSString *value;
NSString *criteria;
NSString *userLogin;
NSString *currentView;
BOOL dayBasedView;
WORequest *request;
SOGoDateFormatter *dateFormatter;
+125 -34
View File
@@ -1,9 +1,6 @@
/* UIxCalListingActions.m - this file is part of SOGo
*
* Copyright (C) 2006-2011 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Francis Lachapelle <flachapelle@inverse.ca>
* Copyright (C) 2006-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -78,6 +75,8 @@ static NSArray *tasksFields = nil;
#define maxBlocks (offsetBlocks * 2) // maximum number of blocks to search
// for a free slot (10 days)
@class SOGoAppointment;
@implementation UIxCalListingActions
+ (void) initialize
@@ -121,6 +120,7 @@ static NSArray *tasksFields = nil;
ASSIGN (dateFormatter, [user dateFormatterInContext: context]);
ASSIGN (userTimeZone, [[user userDefaults] timeZone]);
dayBasedView = NO;
currentView = nil;
}
return self;
@@ -239,6 +239,7 @@ static NSArray *tasksFields = nil;
endDate = nil;
param = [request formValueForKey: @"view"];
currentView = param;
dayBasedView = ![param isEqualToString: @"monthview"];
}
}
@@ -480,7 +481,7 @@ static NSArray *tasksFields = nil;
return infos;
}
- (WOResponse *) _responseWithData: (NSArray *) data
- (WOResponse *) _responseWithData: (id) data
{
WOResponse *response;
@@ -1044,47 +1045,113 @@ _computeBlocksPosition (NSArray *blocks)
/* ... _computeBlocksMultiplier() ... */
}
- (NSArray *) _selectedCalendars
{
SOGoAppointmentFolders *co;
SOGoAppointmentFolder *folder;
NSMutableArray *selectedCalendars;
NSArray *folders;
NSString *fUID;
NSNumber *isActive;
unsigned int count, foldersCount;
int max=0, i;
co = [self clientObject];
folders = [co subFolders];
foldersCount = [folders count];
selectedCalendars = [[NSMutableArray alloc] initWithCapacity: foldersCount];
for (count = 0; count < foldersCount; count++)
{
folder = [folders objectAtIndex: count];
isActive = [NSNumber numberWithBool: [folder isActive]];
if ([isActive intValue] != 0) {
fUID = [folder nameInContainer];
[selectedCalendars addObject: fUID];
}
}
return selectedCalendars;
}
- (WOResponse *) eventsBlocksAction
{
int count, max;
NSArray *events, *event, *eventsBlocks;
NSMutableArray *allDayBlocks, *blocks, *currentDay;
NSMutableArray *allDayBlocks, *blocks, *currentDay, *calendars, *eventsByCalendars, *eventsForCalendar;
NSNumber *eventNbr;
BOOL isAllDay;
int i, j;
[self _setupContext];
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
events = [self _fetchFields: eventsFields
forComponentOfType: @"vevent"];
eventsBlocks = [NSArray arrayWithObjects: events, allDayBlocks, blocks, nil];
max = [events count];
for (count = 0; count < max; count++)
{
event = [events objectAtIndex: count];
// NSLog(@"***[UIxCalListingActions eventsBlocksAction] %i = %@ : %@ / %@ / %@", count,
// [event objectAtIndex: eventTitleIndex],
// [event objectAtIndex: eventStartDateIndex],
// [event objectAtIndex: eventEndDateIndex],
// [event objectAtIndex: eventRecurrenceIdIndex]);
eventNbr = [NSNumber numberWithUnsignedInt: count];
isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue];
if (dayBasedView && isAllDay)
[self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr];
else
[self _fillBlocks: blocks withEvent: event withNumber: eventNbr];
}
events = [self _fetchFields: eventsFields forComponentOfType: @"vevent"];
max = [blocks count];
for (count = 0; count < max; count++)
if ([currentView isEqualToString: @"multicolumndayview"])
{
currentDay = [blocks objectAtIndex: count];
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
[self _addBlocksWidth: currentDay];
calendars = [self _selectedCalendars];
eventsByCalendars = [NSMutableArray arrayWithCapacity:[calendars count]];
for (i = 0; i < [calendars count]; i++) // For each calendar
{
eventsForCalendar = [NSMutableArray array];
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
for (j = 0; j < [events count]; j++) {
if ([[[events objectAtIndex:j] objectAtIndex:1] isEqualToString:[calendars objectAtIndex:i]]) {
[eventsForCalendar addObject: [events objectAtIndex:j]];
}
}
eventsBlocks = [NSArray arrayWithObjects:eventsForCalendar, allDayBlocks, blocks, nil];
max = [eventsForCalendar count];
for (count = 0; count < max; count++)
{
event = [eventsForCalendar objectAtIndex: count];
eventNbr = [NSNumber numberWithUnsignedInt: count];
isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue];
if (dayBasedView && isAllDay)
[self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr];
else
[self _fillBlocks: blocks withEvent: event withNumber: eventNbr];
}
max = [blocks count];
for (count = 0; count < max; count++)
{
currentDay = [blocks objectAtIndex: count];
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
[self _addBlocksWidth: currentDay];
}
[eventsByCalendars insertObject:eventsBlocks atIndex:i];
}
return [self _responseWithData: eventsByCalendars];
}
else
{
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
eventsBlocks = [NSArray arrayWithObjects: events, allDayBlocks, blocks, nil];
max = [events count];
for (count = 0; count < max; count++)
{
event = [events objectAtIndex: count];
// NSLog(@"***[UIxCalListingActions eventsBlocksAction] %i = %@ : %@ / %@ / %@", count,
// [event objectAtIndex: eventTitleIndex],
// [event objectAtIndex: eventStartDateIndex],
// [event objectAtIndex: eventEndDateIndex],
// [event objectAtIndex: eventRecurrenceIdIndex]);
eventNbr = [NSNumber numberWithUnsignedInt: count];
isAllDay = [[event objectAtIndex: eventIsAllDayIndex] boolValue];
if (dayBasedView && isAllDay)
[self _fillBlocks: allDayBlocks withEvent: event withNumber: eventNbr];
else
[self _fillBlocks: blocks withEvent: event withNumber: eventNbr];
}
max = [blocks count];
for (count = 0; count < max; count++)
{
currentDay = [blocks objectAtIndex: count];
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
[self _addBlocksWidth: currentDay];
}
return [self _responseWithData: eventsBlocks];
}
return [self _responseWithData: eventsBlocks];
// timeIntervalSinceDate:
}
- (NSString *) _getStatusClassForStatusCode: (int) statusCode
@@ -1208,4 +1275,28 @@ _computeBlocksPosition (NSArray *blocks)
return [self _responseWithData: filteredTasks];
}
- (WOResponse *) activeTasksAction
{
NSMutableDictionary *activeTasksByCalendars;
SOGoAppointmentFolder *folder;
SOGoAppointmentFolders *co;
NSArray *folders;
int i;
co = [self clientObject];
folders = [co subFolders];
activeTasksByCalendars = [NSMutableDictionary dictionaryWithCapacity: [folders count]];
for (i = 0; i < [folders count]; i++)
{
folder = [folders objectAtIndex: i];
[activeTasksByCalendars setObject: [folder activeTasks]
forKey: [folder nameInContainer]];
}
return [self _responseWithData: activeTasksByCalendars];
}
@end
+19 -22
View File
@@ -39,25 +39,25 @@
- (id) init
{
if ((self = [super init]))
{
// allAppointments = nil;
subscriptionUsers = nil;
hoursToDisplay = nil;
currentTableUser = nil;
currentTableHour = nil;
// dateFormatter = [[SOGoDateFormatter alloc]
// initWithLocale: [self locale]];
}
{
// allAppointments = nil;
subscriptionUsers = nil;
hoursToDisplay = nil;
currentTableUser = nil;
currentTableHour = nil;
// dateFormatter = [[SOGoDateFormatter alloc]
// initWithLocale: [self locale]];
}
return self;
}
- (void) dealloc
{
// [allAppointments release];
// [allAppointments release];
[subscriptionUsers release];
[hoursToDisplay release];
// [dateFormatter release];
// [dateFormatter release];
[super dealloc];
}
@@ -188,16 +188,6 @@
/* fetching */
// - (NSCalendarDate *) startDate
// {
// return [[self selectedDate] beginOfDay];
// }
// - (NSCalendarDate *) endDate
// {
// return [[self selectedDate] endOfDay];
// }
// - (NSArray *) appointmentsForCurrentUser
// {
// NSMutableArray *filteredAppointments;
@@ -292,4 +282,11 @@
* (100.0 / [users count]))];
}
- (id <WOActionResults>) defaultAction
{
[super setCurrentView: @"multicolumndayview"];
return self;
}
@end
+1
View File
@@ -24,6 +24,7 @@
@interface UIxCalViewPrint : UIxComponent
{
id item;
SOGoUserSettings *us;
}
@end
+28 -5
View File
@@ -22,6 +22,9 @@
#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoUser.h>
#import <UIxCalViewPrint.h>
static NSArray *layoutItems = nil;
@@ -32,21 +35,22 @@ static NSArray *layoutItems = nil;
{
if (!layoutItems)
{
layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Weekly", nil];
layoutItems = [NSArray arrayWithObjects: @"LIST", @"Daily", @"Multi-Columns", @"Weekly", nil];
[layoutItems retain];
}
}
- (id) init
{
item = nil;
return [super init];
return self;
}
- (void) dealloc
{
[item release];
[super dealloc];
}
/****************************************************************/
@@ -72,10 +76,29 @@ static NSArray *layoutItems = nil;
return [self labelForKey: [NSString stringWithFormat: item]];
}
- (NSString *) layoutSelectedByUser
- (NSString *) parentPrintLayout
{
return nil;
// The objective here is to return the parent view layout and select the print layout corresponding. Default print view: list view
SOGoUser *activeUser;
NSString *parentView;
activeUser = [context activeUser];
us = [activeUser userSettings];
parentView = [[us objectForKey:@"Calendar"] objectForKey:@"View" ];
if ([parentView isEqualToString:@"dayview"])
return @"Daily";
else if ([parentView isEqualToString:@"weekview"])
return @"Weekly";
else if ([parentView isEqualToString:@"multicolumndayview"])
return @"Multi-Columns";
else
return @"LIST";
}
/******************************************************************/
/* */
+30 -28
View File
@@ -93,37 +93,39 @@ _intValueFromHex (NSString *hexString)
NSMutableDictionary *calendar;
unsigned int count, max;
NSString *folderName, *fDisplayName;
NSNumber *isActive;
NSNumber *isActive, *fActiveTasks;
if (!calendars)
{
co = [self clientObject];
folders = [co subFolders];
max = [folders count];
calendars = [[NSMutableArray alloc] initWithCapacity: max];
for (count = 0; count < max; count++)
{
co = [self clientObject];
folders = [co subFolders];
max = [folders count];
calendars = [[NSMutableArray alloc] initWithCapacity: max];
for (count = 0; count < max; count++)
{
folder = [folders objectAtIndex: count];
calendar = [NSMutableDictionary dictionary];
folderName = [folder nameInContainer];
fDisplayName = [folder displayName];
if (fDisplayName == nil)
fDisplayName = @"";
if ([fDisplayName isEqualToString: [co defaultFolderName]])
fDisplayName = [self labelForKey: fDisplayName];
[calendar setObject: [NSString stringWithFormat: @"/%@", folderName]
forKey: @"id"];
[calendar setObject: fDisplayName forKey: @"displayName"];
[calendar setObject: folderName forKey: @"folder"];
[calendar setObject: [folder calendarColor] forKey: @"color"];
isActive = [NSNumber numberWithBool: [folder isActive]];
[calendar setObject: isActive forKey: @"active"];
[calendar setObject: [folder ownerInContext: context]
forKey: @"owner"];
[calendars addObject: calendar];
}
folder = [folders objectAtIndex: count];
calendar = [NSMutableDictionary dictionary];
folderName = [folder nameInContainer];
fDisplayName = [folder displayName];
if (fDisplayName == nil)
fDisplayName = @"";
if ([fDisplayName isEqualToString: [co defaultFolderName]])
fDisplayName = [self labelForKey: fDisplayName];
[calendar setObject: [NSString stringWithFormat: @"/%@", folderName]
forKey: @"id"];
[calendar setObject: fDisplayName forKey: @"displayName"];
[calendar setObject: folderName forKey: @"folder"];
[calendar setObject: [folder calendarColor] forKey: @"color"];
isActive = [NSNumber numberWithBool: [folder isActive]];
[calendar setObject: isActive forKey: @"active"];
[calendar setObject: [folder ownerInContext: context]
forKey: @"owner"];
fActiveTasks = [folder activeTasks];
[calendar setObject:fActiveTasks forKey:@"activeTasks" ];
[calendars addObject: calendar];
}
}
return calendars;
}
+5
View File
@@ -94,6 +94,11 @@
actionClass = "UIxCalListingActions";
actionName = "tasksList";
};
activeTasks = {
protectedBy = "View";
actionClass = "UIxCalListingActions";
actionName = "activeTasks";
};
dayview = {
protectedBy = "View";
pageName = "UIxCalDayView";
@@ -299,7 +299,8 @@
<dd><input type="checkbox"
const:name="addOutgoingAddresses"
const:id="addOutgoingAddresses"
var:checked="addOutgoingAddresses" />
var:checked="addOutgoingAddresses"
onChange = "onAddOutgoingAddressesCheck(this);"/>
<var:string label:value="When sending mail, add unknown recipients to my"/><br/>
<var:popup list="addressBookList" item="item"
const:id="addressBookList"
+95 -41
View File
@@ -7,51 +7,105 @@
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label">
<div id="calendarHeader" var:class="daysViewClasses">
<div class="dayLabels"
><var:foreach list="daysToDisplay" item="currentTableDay"
><div var:class="dayClasses"
><var:string value="labelForDate"/><br
/><span class="dayOfWeek"><var:string value="labelForDay"/></span
></div
></var:foreach>
</div>
<div class="days"
><var:foreach list="daysToDisplay" item="currentTableDay"
><div var:class="dayClasses"
var:day="currentTableDay.shortDateString"
var:id="currentAllDayId"
hour="allday"
><!-- space --></div
></var:foreach
></div>
<!-- MultiColumnView -->
<!-- Display the name of the calendar -->
<var:if condition="isMultiColumnView">
<div class="calendarLabels">
<var:foreach list="calendarsToDisplay" item="currentCalendar">
<div class="calendarsToDisplay">
<var:string value="labelForCalendar"/>
</div>
</var:foreach>
</div>
<!-- Display today abreviation and date -->
<div class="dayLabels">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses">
<var:string value="labelForDate"/><br/>
<span class="dayOfWeek"><var:string value="labelForDay"/></span>
</div>
</var:foreach>
</div>
<!-- All day cells -->
<div class="days">
<var:foreach list="calendarsToDisplay" item="currentCalendar">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses" var:day="currentTableDay.shortDateString" var:id="currentAllDayId" hour="allday"><!-- space --></div>
</var:foreach>
</var:foreach>
</div>
</var:if>
<!-- DayView or WeekView -->
<var:if condition="isNotMultiColumnView">
<!-- Display today abreviation and date -->
<div class="dayLabels">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses">
<var:string value="labelForDate"/><br/>
<span class="dayOfWeek"><var:string value="labelForDay"/></span>
</div>
</var:foreach>
</div>
<!-- All day cells -->
<div class="days">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses" var:day="currentTableDay.shortDateString" var:id="currentAllDayId" hour="allday"><!-- space --></div>
</var:foreach>
</div>
</var:if>
</div>
<!-- The hours cells -->
<div id="daysView" var:class="daysViewClasses">
<div class="hours"
><var:foreach list="hoursToDisplay" item="currentTableHour"
><div class="hour" var:id="currentHourId"
><var:string value="currentTableHour"/></div
></var:foreach>
</div>
<div class="days">
<var:foreach list="daysToDisplay" item="currentTableDay"
><div var:class="dayClasses"
var:id="currentDayId"
var:day-number="currentDayNumber"
var:day="currentTableDay.shortDateString">
<div class="hourCells">
<var:foreach list="hoursToDisplay" item="currentTableHour"
><div var:class="clickableHourCellClass"
var:day="currentTableDay.shortDateString"
var:hour="currentAppointmentHour">
<span class="minutes15"><!-- space --></span>
<span class="minutes30"><!-- space --></span>
<span class="minutes45"><!-- space --></span>
</div></var:foreach></div>
<div class="events"><!-- space --></div>
<div class="hours">
<var:foreach list="hoursToDisplay" item="currentTableHour">
<div class="hour" var:id="currentHourId">
<var:string value="currentTableHour"/>
</div>
</var:foreach>
</div>
<div class="days">
<!-- MultiColumnView -->
<var:if condition="isMultiColumnView">
<var:foreach list="calendarsToDisplay" item="currentCalendar">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses" var:id="currentDayId" var:day-number="currentDayNumber" var:day="currentTableDay.shortDateString">
<div class="hourCells">
<var:foreach list="hoursToDisplay" item="currentTableHour">
<div var:class="clickableHourCellClass" var:day="currentTableDay.shortDateString" var:hour="currentAppointmentHour">
<span class="minutes15"><!-- space --></span>
<span class="minutes30"><!-- space --></span>
<span class="minutes45"><!-- space --></span>
</div>
</var:foreach>
</div>
<div class="events"><!-- space --></div>
</div>
</var:foreach>
</var:foreach>
</var:if>
<var:if condition="isNotMultiColumnView">
<var:foreach list="daysToDisplay" item="currentTableDay">
<div var:class="dayClasses" var:id="currentDayId" var:day-number="currentDayNumber" var:day="currentTableDay.shortDateString">
<div class="hourCells">
<var:foreach list="hoursToDisplay" item="currentTableHour">
<div var:class="clickableHourCellClass" var:day="currentTableDay.shortDateString" var:hour="currentAppointmentHour">
<span class="minutes15"><!-- space --></span>
<span class="minutes30"><!-- space --></span>
<span class="minutes45"><!-- space --></span>
</div>
</var:foreach>
</div>
<div class="events"><!-- space --></div>
</div>
</var:foreach>
</var:if>
</div>
</div>
</container>
+2 -1
View File
@@ -40,6 +40,7 @@
className="UIxCalDayTable"
startDate="startDate"
const:CSSClass="dayOverview"
const:numberOfDays="1" />
const:numberOfDays="1"
const:currentView="dayview"/>
</div>
</container>
@@ -5,40 +5,33 @@
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label">
<a href="#"
id="leftNavigationArrow"
var:date="prevDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><img rsrc:src="arrow-left.png"/></a>
<div class="menu" id="currentViewMenu">
<ul>
<li><var:string label:value="New Event..."/></li>
<li><var:string label:value="New Task..."/></li>
<li><!-- separator --></li>
<li><var:string label:value="Previous Day"/></li>
<li><var:string label:value="Next Day"/></li>
<li><!-- separator --></li>
<li><var:string label:value="Delete Event"/></li>
<li><var:string label:value="Copy event to my calendar"/></li>
<li><var:string label:value="View Raw Source"/></li>
</ul>
</div>
<!-- Navigation header -->
<span class="daysHeader">
<span class="day2"><a href="#"
var:date="dayBeforePrevDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><var:string value="dayBeforeYesterdayName"
/></a></span
><span class="day1"><a href="#"
var:date="prevDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><var:string value="yesterdayName"
/></a></span
><span class="day0"><var:string value="currentDayName" /></span
><span class="day1"><a href="#"
var:date="nextDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><var:string value="tomorrowName"
/></a></span
><span class="day2"><a href="#"
var:date="dayAfterNextDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><var:string value="dayAfterTomorrowName"
/></a></span
></span>
<a href="#"
id="rightNavigationArrow"
var:date="nextDayQueryParameters.day"
onclick="return onCalendarGotoDay(this);"
><img rsrc:src="arrow-right.png"/></a>
<span class="day1">
<a href="#" id="leftNavigationArrow" var:date="prevDayQueryParameters.day" onclick="return onCalendarGotoDay(this);">
<img rsrc:src="arrow-left.png"/></a></span>
<span class="day0"><var:string value="currentDayName" /></span>
<span class="day1">
<a href="#" id="rightNavigationArrow" var:date="nextDayQueryParameters.day" onclick="return onCalendarGotoDay(this);">
<img rsrc:src="arrow-right.png"/></a></span>
<a href="#" id="listCollapse"><img var:class="collapseBtnClass" rsrc:src="collapse.png"/></a>
</span>
<!-- TODO : Move this inside the schedulerUI.css -->
<style type="text/css">
DIV.day
{
@@ -51,69 +44,13 @@
}
</var:foreach>
</style>
<!-- Display the calendar header and the table view -->
<div id="calendarContent">
<div id="daysView" class="daysView multicolumnDayView">
<div class="hours">
<var:foreach list="hoursToDisplay" item="currentTableHour"
><div class="hour"><var:string value="currentTableHour" />:00</div
></var:foreach>
</div>
<div class="hourLines">
<div class="hourLine hourLine0"><!-- space --></div
><div class="hourLine hourLine1"><!-- space --></div
><div class="hourLine hourLine2"><!-- space --></div
><div class="hourLine hourLine3"><!-- space --></div
><div class="hourLine hourLine4"><!-- space --></div
><div class="hourLine hourLine5"><!-- space --></div
><div class="hourLine hourLine6"><!-- space --></div
><div class="hourLine hourLine7"><!-- space --></div
><div class="hourLine hourLine8"><!-- space --></div
><div class="hourLine hourLine9"><!-- space --></div
><div class="hourLine hourLine10"><!-- space --></div
><div class="hourLine hourLine11"><!-- space --></div
><div class="hourLine hourLine12"><!-- space --></div
><div class="hourLine hourLine13"><!-- space --></div
><div class="hourLine hourLine14"><!-- space --></div
><div class="hourLine hourLine15"><!-- space --></div
><div class="hourLine hourLine16"><!-- space --></div
><div class="hourLine hourLine17"><!-- space --></div
><div class="hourLine hourLine18"><!-- space --></div
><div class="hourLine hourLine19"><!-- space --></div
><div class="hourLine hourLine20"><!-- space --></div
><div class="hourLine hourLine21"><!-- space --></div
><div class="hourLine hourLine22"><!-- space --></div
><div class="hourLine hourLine23"><!-- space --></div>
</div>
<div class="days" var:day="selectedDate.shortDateString">
<var:foreach list="subscriptionUsers" item="currentTableUser"
><div var:class="currentUserClasses"
var:user="currentTableUser"
><div class="header"><var:string value="currentTableUser" /></div>
<div class="appointments">
<var:foreach list="hoursToDisplay" item="currentTableHour"
><div var:class="clickableHourCellClass"
var:hour="currentAppointmentHour">
</div></var:foreach>
<var:foreach list="appointmentsForCurrentUser" item="currentAppointment"
><var:component className="UIxCalInlineAptView"
dayStartHour="dayStartHour"
dayEndHour="dayEndHour"
appointment="currentAppointment"
formatter="aptFormatter"
tooltipFormatter="aptTooltipFormatter"
url="appointmentViewURL"
const:style="dayoverview"
queryDictionary="currentDateQueryParameters"
referenceDate="selectedDate"
canAccess="canAccessApt"
/></var:foreach
>
</div>
</div>
</var:foreach>
</div>
</div>
<var:component
className="UIxCalDayTable"
startDate="startDate"
const:CSSClass="dayOverview"
const:currentView="multicolumndayview"/>
</div>
</container>
</container>
+12 -2
View File
@@ -25,10 +25,10 @@
<td>
<var:popup list="printLayoutList" item="item"
const:id="printLayoutList"
string="itemPrintLayoutText" /></td>
string="itemPrintLayoutText" selection="parentPrintLayout" /></td>
</tr>
</table>
<!-- What to print -->
<span class="caption"><var:string label:value="What to Print" /></span>
<table class="frame">
@@ -81,6 +81,16 @@
<!-- Options -->
<span class="caption"><var:string label:value="Options" /></span>
<table class="frame">
<tr>
<td>
<label>
<input type="checkbox" name="printWorkingHours" id="printHours" checked="true" onChange="onPrintWorkingHoursCheck(this);" />
<var:string label:value="Display working hours only" /></label></td></tr>
<tr>
<td>
<label>
<hr /></label></td></tr>
<tr>
<td>
<label>
+2 -1
View File
@@ -40,6 +40,7 @@
className="UIxCalDayTable"
startDate="startDate"
const:CSSClass="weekOverview"
const:numberOfDays="7" />
const:numberOfDays="7"
const:currentView="weekview"/>
</div>
</container>
@@ -38,9 +38,10 @@ div.colorBox.calendarFolder<var:string value="currentCalendar.folder" />
<var:foreach list="calendars" item="currentCalendar"
><li class="denied" var:id="currentCalendar.id" var:owner="currentCalendar.owner" >
<input type="checkbox" class="checkBox" const:disabled="disabled" var:checked="currentCalendar.active" />
<div var:class="currentCalendarClass"><entity name="nbsp"/></div
><var:string value="currentCalendar.displayName"
/></li>
<div var:class="currentCalendarClass"><entity name="nbsp"/></div>
<var:string value="currentCalendar.displayName" />
<span id="activeTasks"><var:string value="currentCalendar.activeTasks"/></span>
</li>
</var:foreach>
</ul>
</div>
+1 -1
View File
@@ -1749,7 +1749,7 @@ function download(url) {
setTimeout(function () {
form.remove();
div.remove();
}, 2000);
}, 20000);
}
function saveAttachment(event) {
+62 -11
View File
@@ -392,7 +392,7 @@ DIV#daysView
left: 0px; }
DIV#daysView
{ top: 120px;
{ top: 97px;
bottom: 0px;
border-top: 1px solid #ccc;
overflow: auto;
@@ -400,11 +400,12 @@ DIV#daysView
right: 0px;}
DIV#calendarHeader
{ top: 50px;
{ top: 25px;
border: 0px;
height: 100px;
height: 85px;
right: 0px; }
DIV#calendarHeader DIV.calendarLabels,
DIV#calendarHeader DIV.dayLabels,
DIV#calendarHeader DIV.days
{ position: absolute;
@@ -414,8 +415,16 @@ DIV#calendarHeader DIV.days
/* 'right' is computed from JS code when daysView is first drawn */
overflow: hidden; }
DIV#calendarHeader DIV.calendarLabels
{ top: 11px;
height: 20px;
border-left: 1px solid #ccc;
text-align: center;
font-style:italic;
font-size:150%;
}
DIV#calendarHeader DIV.dayLabels
{ top: 0px;
{ top: 32px;
height: 35px; }
DIV#calendarHeader DIV.dayLabels DIV.day
@@ -432,7 +441,11 @@ DIV#calendarHeader DIV.dayLabels
DIV#calendarHeader DIV.days
{ cursor: pointer;
bottom: 0px;
top: 35px; }
top: 67px;
z-index:0;
border-bottom:1px solid #ccc;
background:white;
}
DIV#calendarHeader DIV.day,
DIV#daysView DIV.day
@@ -530,7 +543,8 @@ DIV.monthView DIV.dayHeader
text-align: right;
cursor: pointer;
color: #666;
-moz-user-select: none; }
-moz-user-select: none;
}
DIV#monthDaysView DIV.selectedDay DIV.dayHeader
{ font-weight: bold; }
@@ -540,7 +554,8 @@ DIV.monthView DIV.days
cursor: pointer;
bottom: 0px;
left: 0px;
right: 0px; }
right: 0px;
}
DIV.monthView DIV.day
{ position: absolute;
@@ -1279,13 +1294,14 @@ DIV#daysView DIV.event.lasts96
DIV.event > DIV.eventInside
{ position: absolute;
overflow: hidden;
padding: 1px;
top: 0px;
bottom: 0px;
left: 2px;
right: 1px;
-webkit-border-radius: 2px;
border-radius: 2px; }
DIV.eventInside {
width:100%;
}
DIV.eventInside SPAN.location
{ font-size: smaller; }
@@ -1471,7 +1487,7 @@ DIV.event.draggable:hover DIV.rightDragGrip
#ghostEndHour
{ bottom: -14px; }
DIV#nowLineDisplay
DIV.nowLineDisplay
{ position: relative;
width: 100%;
height: 2px;
@@ -1486,3 +1502,38 @@ DIV#freeBusyReplicas DIV
{ display: inline-block; height: 2em; }
TD.attendees A.button
{ float: left; left: 19px; margin-top: 5px; }
DIV#calendarContent .alert-box {
color:#555;
border-radius:10px;
font-family:Tahoma,Geneva,Arial,sans-serif;font-size:11px;
padding:10px 10px 10px 36px;
margin:10px;
}
DIV#calendarContent .alert-box span {
font-weight:bold;
text-transform:uppercase;
}
.error {
background:#ffecec url('error.png') no-repeat 10px 50%;
border:1px solid #f5aca6;
}
.success {
background:#e9ffd9 url('success.png') no-repeat 10px 50%;
border:1px solid #a6ca8a;
}
.warning {
background:#fff8c4 url('warning.png') no-repeat 10px 50%;
border:1px solid #f2c779;
}
.notice {
background:#e3f7fc url('notice.png') no-repeat 10px 50%;
border:1px solid #8ed9f6;
}
+461 -261
View File
@@ -34,15 +34,32 @@ var categoriesStyleSheet = null;
var clipboard = null;
var eventsToCopy = [];
// This should probably go in the generic.js
function printView() {
var flag = false;
var calendarsList = $("calendarList").children;
for (var i = 0; i < calendarsList.length; i++) {
if (calendarsList[i].down("input").checked) {
flag = true;
break;
}
}
if (flag)
window.open("printView","","width=660,height=470");
else
showAlertDialog("Please select at least one calendar");
window.open("printView","","width=660,height=470");
return false; /* stop following the link */
return false;
}
function newEvent(type, day, hour, duration) {
var folder = null;
/* if (currentView == "multicolumndayview") {
Need to find where the click register is saved
}*/
if (UserDefaults['SOGoDefaultCalendar'] == 'personal')
folder = $("calendarList").down("li");
else if (UserDefaults['SOGoDefaultCalendar'] == 'first') {
@@ -400,13 +417,14 @@ function deleteEvent() {
}
function deleteEventFromListConfirm() {
if (document.deleteEventAjaxRequest) {
document.deleteEventAjaxRequest.aborted = true;
document.deleteEventAjaxRequest.abort();
}
_batchDeleteEvents();
disposeDialog();
if (document.deleteEventAjaxRequest) {
document.deleteEventAjaxRequest.aborted = true;
document.deleteEventAjaxRequest.abort();
}
_batchDeleteEvents();
refreshTasks();
disposeDialog();
}
function deleteEventFromViewConfirm() {
@@ -911,8 +929,8 @@ function onDaySelect(node) {
&& day != currentDay);
var td = $(node).getParentWithTagName("td");
var table = $(td).getParentWithTagName("table");
// var table = $(td).getParentWithTagName("table");
// log ("table.selected: " + table.selected);
if (document.selectedDate)
@@ -1085,142 +1103,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('&nbsp;');
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) {
document.activeTasksAjaxRequest = null;
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];
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('&nbsp;');
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 */
@@ -1589,7 +1631,7 @@ function refreshCalendarEvents(scrollEvent) {
if (!currentDay)
currentDay = todayDate.getDayString();
if (currentView == "dayview") {
if (currentView == "dayview" || currentView == "multicolumndayview") {
sd = currentDay;
ed = sd;
}
@@ -1627,28 +1669,54 @@ function refreshCalendarEvents(scrollEvent) {
"scrollEvent": scrollEvent});
}
function _parseEvents(list) {
function _parseEvents(list, calendars) {
var newCalendarEvents = {};
for (var i = 0; i < list.length; i++) {
if (currentView == "multicolumndayview") {
var list;
for (var i = 0; i < calendars.length; i++) {
list = calendars[i][0];
for (var j = 0; j < list.length; j++) {
var event = list[j];
var cname = event[0];
var calendar = event[1];
// log("parsed cname: " + cname + "; calendar: " + calendar);
var calendarDict = newCalendarEvents[calendar];
if (!calendarDict) {
calendarDict = {};
newCalendarEvents[calendar] = calendarDict;
}
var occurences = calendarDict[cname];
if (!occurences) {
occurences = [];
calendarDict[cname] = occurences;
}
event.blocks = [];
occurences.push(event);
}
}
}
else {
for (var i = 0; i < list.length; i++) {
var event = list[i];
var cname = event[0];
var calendar = event[1];
// log("parsed cname: " + cname + "; calendar: " + calendar);
var calendarDict = newCalendarEvents[calendar];
if (!calendarDict) {
calendarDict = {};
newCalendarEvents[calendar] = calendarDict;
calendarDict = {};
newCalendarEvents[calendar] = calendarDict;
}
var occurences = calendarDict[cname];
if (!occurences) {
occurences = [];
calendarDict[cname] = occurences;
occurences = [];
calendarDict[cname] = occurences;
}
event.blocks = [];
occurences.push(event);
}
}
return newCalendarEvents;
}
@@ -1680,15 +1748,28 @@ function refreshCalendarEventsCallback(http) {
if (http.readyState == 4
&& http.status == 200) {
if (http.responseText.length > 0) {
var eventsBlocks = http.responseText.evalJSON(true);
calendarEvents = _parseEvents(eventsBlocks[0]);
if (currentView == "monthview")
_drawMonthCalendarEvents(eventsBlocks[2], eventsBlocks[0]);
else {
_drawCalendarAllDayEvents(eventsBlocks[1], eventsBlocks[0]);
_drawCalendarEvents(eventsBlocks[2], eventsBlocks[0]);
}
_setupEventsDragAndDrop(eventsBlocks[0]);
var eventsBlocks = http.responseText.evalJSON(true);
if (currentView == "multicolumndayview") {
calendarEvents = _parseEvents(null, eventsBlocks);
_drawCalendarAllDayEvents(null, null, eventsBlocks);
_drawCalendarEvents(null, null, eventsBlocks);
for (var i = 0 ; i < eventsBlocks.length; i++) {
_setupEventsDragAndDrop(eventsBlocks[i][0]);
}
}
else {
calendarEvents = _parseEvents(eventsBlocks[0], null);
allDayEventsList = eventsBlocks[1];
eventsList = eventsBlocks[2];
if (currentView == "monthview")
_drawMonthCalendarEvents(eventsList, eventsBlocks[0], null);
else {
_drawCalendarAllDayEvents(allDayEventsList, eventsBlocks[0], null);
_drawCalendarEvents(eventsList, eventsBlocks[0], null);
}
_setupEventsDragAndDrop(eventsBlocks[0]);
}
resetCategoriesStyles();
onWindowResize(null);
}
@@ -1851,20 +1932,37 @@ function newBaseEventDIV(eventRep, event, eventText) {
return eventCell;
}
function _drawCalendarAllDayEvents(events, eventsData) {
function _drawCalendarAllDayEvents(events, eventsData, columnsData) {
var headerView = $("calendarHeader");
var subdivs = headerView.childNodesWithTag("div");
var days = subdivs[1].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
if (currentView == "multicolumndayview"){
var days = subdivs[2].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
var calendar = columnsData[i];
var calendarAllDayEvents = calendar[1][0];
var calendarAllDayEventsData = calendar[0];
for (var j = 0; j < calendarAllDayEvents.length; j++) {
var eventRep = calendarAllDayEvents[j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, calendarAllDayEventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
else {
var days = subdivs[1].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
for (var j = 0; j < events[i].length; j++) {
var eventRep = events[i][j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
var eventRep = events[i][j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
resizeCalendarHeaderDIV();
}
@@ -1876,15 +1974,17 @@ function resizeCalendarHeaderDIV() {
var daysView = $("daysView");
if (headerView && daysView) {
/* consts */
var headerViewBaseHeight = 70;
var daysViewBaseTop = 120;
var maxDelta = 80;
var headerViewBaseHeight = 90;
var daysViewBaseTop = 95;
/* /consts */
var maxEventPerDay = 0;
var subdivs = headerView.childNodesWithTag("div");
var days = subdivs[1].childNodesWithTag("div");
if (currentView == "multicolumndayview")
var days = subdivs[2].childNodesWithTag("div");
else
var days = subdivs[1].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
var divs = parentDiv.childNodesWithTag("div");
@@ -1892,16 +1992,31 @@ function resizeCalendarHeaderDIV() {
maxEventPerDay = divs.length;
}
}
if (maxEventPerDay > 2) {
var delta = ((maxEventPerDay - 2) * 22) + 10;
if (delta > maxDelta) {
delta = maxDelta;
}
daysView.style.top = String(delta + daysViewBaseTop) + "px";
headerView.style.height = String(delta + headerViewBaseHeight) + "px";
if (maxEventPerDay > 0 && maxEventPerDay <= 4) {
var moveDaysView = (maxEventPerDay * 19.5);
var expendHeaderHeight = ((maxEventPerDay - 1) * 20);
var deltaMax = 60;
if (expendHeaderHeight > deltaMax){
expendHeaderHeight = deltaMax;
moveDaysView = deltaMax;
}
daysView.style.top = String(daysViewBaseTop + moveDaysView) + "px";
headerView.style.height = String(headerViewBaseHeight + expendHeaderHeight) + "px";
}
else if (maxEventPerDay > 4) {
var deltaMax = 60;
daysView.style.top = String(daysViewBaseTop + deltaMax + 19) + "px";
headerView.style.height = String(headerViewBaseHeight + deltaMax) + "px";
}
else {
var headerDays = document.getElementsByClassName("days")[0];
headerDays.observe("mouseenter", function(event){
headerDays.style.zIndex = "1";
});
headerDays.observe("mouseleave", function(event){
headerDays.style.zIndex = "0";
});
daysView.style.top = null;
headerView.style.height = null;
}
@@ -1916,21 +2031,37 @@ function newAllDayEventDIV(eventRep, event) {
return eventCell;
}
function _drawCalendarEvents(events, eventsData) {
function _drawCalendarEvents(events, eventsData, columnsData) {
var daysView = $("daysView");
var subdivs = daysView.childNodesWithTag("div");
for (var i = 0; i < subdivs.length; i++) {
var subdiv = subdivs[i];
if (subdiv.hasClassName("days")) {
var days = subdiv.childNodesWithTag("div");
for (var j = 0; j < days.length; j++) {
if (currentView == "multicolumndayview") {
for (var j = 0; j < days.length; j++) {
var parentDiv = days[j].childNodesWithTag("div")[0];
var calendar = columnsData[j];
var calendarEvents = calendar[2][0];
var calendarEventsData = calendar[0];
for (var k = 0; k < calendarEvents.length; k++) {
var eventRep = calendarEvents[k];
var nbr = eventRep.nbr;
var eventCell = newEventDIV(eventRep, calendarEventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
else {
for (var j = 0; j < days.length; j++) {
var parentDiv = days[j].childNodesWithTag("div")[0];
for (var k = 0; k < events[j].length; k++) {
var eventRep = events[j][k];
var nbr = eventRep.nbr;
var eventCell = newEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
var eventRep = events[j][k];
var nbr = eventRep.nbr;
var eventCell = newEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
}
}
@@ -2009,7 +2140,8 @@ function adjustCalendarHeaderDIV() {
var delta = ch.clientWidth - dv.clientWidth - 1;
var styleElement = document.createElement("style");
styleElement.type = "text/css";
var selectors = ["DIV#calendarHeader DIV.dayLabels",
var selectors = ["DIV#calendarHeader DIV.calendarLabels",
"DIV#calendarHeader DIV.dayLabels",
"DIV#calendarHeader DIV.days"];
var rule = ("right: " + delta + "px");
if (styleElement.styleSheet && styleElement.styleSheet.addRule) {
@@ -2026,6 +2158,37 @@ function adjustCalendarHeaderDIV() {
}
}
function adjustMultiColumnCalendarHeaderDIV() {
var ch = $("calendarHeader");
var calendarLabels = ch.getElementsByClassName("calendarLabels")[0];
var calendarsToDisplay = calendarLabels.getElementsByClassName("calendarsToDisplay");
var dayLabels = ch.getElementsByClassName("dayLabels")[0].getElementsByClassName("dayColumn")[0];
var days = ch.getElementsByClassName("days")[0].getElementsByClassName("dayColumn");
var daysView = $("daysView").getElementsByClassName("dayColumn");
var nbCalendars = calendarsToDisplay.length;
if (nbCalendars > 0) {
var width = 100/nbCalendars;
var left = 0;
var position = "absolute";
for(var i=0; i < nbCalendars; i++){
calendarsToDisplay[i].setStyle({ width: width + '%', left: left + '%', position: position}).show();
days[i].setStyle({ width: width + '%', left: left + '%'}).show();
daysView[i].setStyle({ width: width + '%', left: left + '%'}).show();
left += width;
}
dayLabels.setStyle({ width: '100%'}).show();
}
else {
$("calendarHeader").remove();
$("daysView").remove();
$("calendarContent").style.height = "100%";
$("calendarContent").style.background = "#E7E7E7";
}
}
function calendarDisplayCallback(http) {
var div = $("calendarView");
var daysView = $("daysView");
@@ -2054,6 +2217,9 @@ function calendarDisplayCallback(http) {
currentView = http.callbackData["view"];
if (http.callbackData["day"])
currentDay = http.callbackData["day"];
if (currentView == "multicolumndayview")
adjustMultiColumnCalendarHeaderDIV();
// Initialize contextual menu
var menu = new Array(onMenuNewEventClick,
@@ -2067,10 +2233,7 @@ function calendarDisplayCallback(http) {
onMenuRawEvent
);
var observer;
if (currentView == 'dayview') {
observer = $("daysView");
}
else if (currentView == 'weekview') {
if (currentView == 'dayview' || currentView == 'weekview' || currentView == 'multicolumndayview') {
observer = $("daysView");
}
else {
@@ -2193,22 +2356,29 @@ 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();
}
if (document.activeTasksAjaxRequest) {
document.activeTasksAjaxRequest.aborted = true;
document.activeTasksAjaxRequest.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);
document.activeTasksAjaxRequest = triggerAjaxRequest(urlActiveTasks, activeTasksCallback);
return true;
}
function onHeaderClick(event) {
@@ -2249,9 +2419,9 @@ function onHeaderClick(event) {
function refreshCurrentFolder(id) {
if (id == 'tasks')
refreshTasks();
refreshTasks();
else
refreshEvents();
refreshEvents();
}
/* refreshes the "unifinder" list */
@@ -2900,27 +3070,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) {
@@ -3353,39 +3531,61 @@ function onWindowResize(event) {
}
function drawNowLine() {
var d = new Date();
var hours = d.getHours();
var minutes = d.getMinutes();
if (currentView == "dayview") {
var today = new Date();
var m = parseInt(today.getMonth()) + 1;
var d = today.getDate();
if (m < 10)
m = "0" + m;
if (d < 10)
d = "0" + d;
var day = today.getFullYear() + "" + m + "" + d;
var targets = $$("DIV#daysView DIV.days DIV.day[day=" + day
+ "] DIV.clickableHourCell");
}
else if (currentView == "weekview")
var targets = $$("DIV#daysView DIV.days DIV.dayOfToday DIV.clickableHourCell");
if (targets) {
var target = targets[hours];
var d = new Date();
var hours = d.getHours();
var minutes = d.getMinutes();
if (currentView == "dayview") {
var today = new Date();
var m = parseInt(today.getMonth()) + 1;
var d = today.getDate();
if (m < 10)
m = "0" + m;
if (d < 10)
d = "0" + d;
var day = today.getFullYear() + "" + m + "" + d;
var targets = $$("DIV#daysView DIV.days DIV.day[day=" + day
+ "] DIV.hourCells");
}
else if (currentView == "multicolumndayview") {
var targets = $$("DIV#daysView DIV.hourCells");
}
else if (currentView == "weekview")
var targets = $$("DIV#daysView DIV.days DIV.dayOfToday DIV.hourCells");
else {
var targets = [];
}
if (targets[0] != undefined) {
if (currentView == "multicolumndayview") {
var nbCalendars = targets.length;
for(var i = 0; i < nbCalendars; i++){
var target = targets[i].getElementsByClassName("clickableHourCell")[hours];
if (target) {
var div = $("nowLineDisplay");
if (!div)
div = new Element("div", {'id': 'nowLineDisplay'});
div.style.top = parseInt((minutes * target.offsetHeight / 60) - 1) + "px";
target.insertBefore(div, target.firstChild);
setTimeout("drawNowLine ();", 60000); // 1 min.
var div = targets[i].getElementsByClassName("nowLineDisplay")[0];
if (!div)
div = new Element("div", {'class': 'nowLineDisplay'});
div.style.top = parseInt((minutes * target.offsetHeight / 60) - 1) + "px";
target.insertBefore(div, target.firstChild);
}
}
}
else {
var target = targets[0].getElementsByClassName("clickableHourCell")[hours];
if (target) {
var div = targets[0].getElementsByClassName("nowLineDisplay")[0];
if (!div)
div = new Element("div", {'class': 'nowLineDisplay'});
div.style.top = parseInt((minutes * target.offsetHeight / 60) - 1) + "px";
target.insertBefore(div, target.firstChild);
}
}
setTimeout("drawNowLine ();", 60000); // 1 min.
}
}
function onListCollapse(event, element) {
+27 -5
View File
@@ -120,7 +120,7 @@ DIV#rightSide {
DIV#rightFrame TD.label {
color: #666;
text-align: right;
width: 30%;
min-width:52px;
}
/********** Overriding SchedulerUI.css **********/
@@ -128,10 +128,20 @@ DIV#rightSide {
DIV#calendarHeader {
position:relative;
top:0;
height:70px;
height:38px;
right: 0px;
}
DIV#calendarHeader DIV.calendarLabels
{ top: 1px;
height: 20px;
border-left: 1px solid #ccc;
text-align: center;
font-style:italic;
font-size:150%;
}
DIV#calendarHeader DIV.calendarLabels,
DIV#calendarHeader DIV.dayLabels,
DIV#calendarHeader DIV.days {
position:relative;
@@ -139,9 +149,12 @@ DIV#calendarHeader DIV.days {
margin-left:50px;
}
DIV#calendarHeader DIV.dayLabels
{ top: 1px; }
DIV#calendarHeader DIV.days {
top: 0;
height: 33px;
height: 1px;
left: 0;
right: 0;
}
@@ -170,9 +183,13 @@ DIV#daysView DIV.hour {
DIV.event {
z-index:1;
border:solid black 1px;
background-color:white;
background-color: white;
border-radius:5px;
border:1px solid black;
}
DIV.eventInside {
width:100%;
}
DIV#daysView DIV.event.starts0
@@ -596,10 +613,15 @@ LABEL {
position:static;
top:5px;
left:5px;
}
DIV#rightFrame {
border:none;
overflow:visible;
}
BODY {
background-color:white;
}
}
+359 -74
View File
@@ -26,7 +26,7 @@ var firstDayOfWeek = window.opener.firstDayOfWeek;
var printCompletedTasks=1;
var printNoDueDateTasks=1;
var eventsBlocks;
var currentView;
var currentPreview;
var currentDay = window.parentvar("currentDay");
var sd, ed;
@@ -38,7 +38,7 @@ function refreshContent() {
}
function updateDisplayView(data, newView) {
newView = ((newView) ? newView : currentView);
newView = ((newView) ? newView : currentPreview);
var url = ApplicationBaseURL + "/" + newView;
var day = null;
@@ -97,9 +97,24 @@ function previewDisplayCallback(http) {
$("currentViewMenu").remove();
$("listCollapse").remove();
// TODO : Month
_drawAllDayEvents(eventsBlocks[1], eventsBlocks[0]);
_drawEvents(eventsBlocks[2], eventsBlocks[0]);
if (currentPreview == "multicolumndayview") {
_drawCalendarAllDayEvents(null, null, eventsBlocks);
}
else {
allDayEventsList = eventsBlocks[1];
if (currentPreview == "monthview") {
//_drawMonthCalendarEvents(eventsList, eventsBlocks[0], null);
}
else {
_drawCalendarAllDayEvents(allDayEventsList, eventsBlocks[0], null);
}
}
// This ensure the diplay working hours checkbox when switching views
var printHoursCheckBox = $("printHours");
onPrintWorkingHoursCheck(printHoursCheckBox);
// Add events color for each calendars
addCalendarsColor();
}
else
log ("calendarDisplayCallback Ajax error ("+ http.readyState + "/" + http.status + ")");
@@ -107,17 +122,38 @@ function previewDisplayCallback(http) {
return false;
}
function addCalendarsColor () {
var allCalendars = window.parent$("calendarList");
var allColors = window.parentvar("UserSettings")['Calendar']['FolderColors'];
for (var i = 0; i < allCalendars.children.length; i++) {
if (allCalendars.children[i].down("input").checked){
owner = allCalendars.children[i].getAttribute("owner");
folderName = allCalendars.children[i].getAttribute("id").substr(1);
color = allColors[owner + ":Calendar/" + folderName];
if (!color) {
if(folderName.split("_")[1])
color = allColors[owner + ":Calendar/" + folderName.split("_")[1]];
else
color = "#AAAAAA";
}
appendStyleElement(folderName, color);
}
}
}
function refreshEvents() {
var todayDate = new Date();
if (!currentDay)
currentDay = todayDate.getDayString();
if (currentView == "dayview") {
if (currentPreview == "dayview" || currentPreview == "multicolumndayview") { // dayView and MultiColumns View
sd = currentDay;
ed = sd;
}
else if (currentView == "weekview") {
else if (currentPreview == "weekview") { // WeekView
var startDate;
startDate = currentDay.asDate();
startDate = startDate.beginOfWeek();
@@ -144,7 +180,7 @@ function refreshEvents() {
document.refreshEventsAjaxRequest.abort();
}
var url = (ApplicationBaseURL + "/eventsblocks?sd=" + sd + "&ed=" + ed
+ "&view=" + currentView);
+ "&view=" + currentPreview);
document.refreshEventsAjaxRequest
= triggerAjaxRequest(url, refreshEventsCallback,
@@ -157,9 +193,13 @@ function refreshTasks(){
document.tasksListAjaxRequest.abort();
}
var taskListFilter = window.parentvar("taskListFilter");
url = window.parentvar("ApplicationBaseURL") + "/" + "taskslist?show-completed=" + printCompletedTasks
+ "&asc=" + sorting["task-ascending"]
+ "&sort=" + sorting["task-attribute"];
+ "&sort=" + sorting["task-attribute"]
+ "&filterpopup=" + taskListFilter;
// TODO : Is that really necessary ?
var tasksList = window.parent$("tasksList");
var selectedIds;
if (tasksList)
@@ -176,11 +216,13 @@ function refreshEventsCallback(http) {
if (http.responseText.length > 0) {
eventsBlocks = http.responseText.evalJSON(true);
$("rightFrameEvents").innerHTML = "";
if ($("printLayoutList").value == "0")
if ($("printLayoutList").value == "0" && eventsBlocks.length > 0) {
_drawEventsCells(eventsBlocks);
else {
updateDisplayView(null, currentView);
}
else {
updateDisplayView(null, currentPreview);
}
adjustFrames();
}
}
else
@@ -193,8 +235,10 @@ function refreshTasksListCallback(http) {
if (http.responseText.length > 0) {
var tasksBlocks = http.responseText.evalJSON(true);
$("rightFrameTasks").innerHTML = "";
var layout = $("printLayoutList").value;
_drawTasksCells(tasksBlocks);
if (tasksBlocks.length > 0) {
_drawTasksCells(tasksBlocks);
adjustFrames();
}
}
}
else
@@ -204,10 +248,21 @@ function refreshTasksListCallback(http) {
function _drawEventsCells(eventsBlocks) {
var events = _("Events");
$("rightFrameEvents").insert("<h3>"+events+"</h3>");
for(var i=0; i < eventsBlocks[0].length; i++)
{
var event = _parseEvent(eventsBlocks[0][i]);
$("rightFrameEvents").insert(event);
if (currentPreview == "multicolumndayview") {
for(var i=0; i < eventsBlocks.length; i++) // calendars
{
for (var j = 0; j < eventsBlocks[i][0].length; j++) {
var event = _parseEvent(eventsBlocks[i][0][j]);
$("rightFrameEvents").insert(event);
}
}
}
else {
for(var i=0; i < eventsBlocks[0].length; i++)
{
var event = _parseEvent(eventsBlocks[0][i]);
$("rightFrameEvents").insert(event);
}
}
}
@@ -225,44 +280,113 @@ function _drawTasksCells(tasksBlocks) {
// TODO : Maybe use the drawfunction from the schedulerUI.js
function _drawEvents(events, eventsData) {
function _drawCalendarEvents(events, eventsData, columnsData) {
var daysView = $("daysView");
var subdivs = daysView.childNodesWithTag("div");
var printHoursCheckBox = $("printHours");
for (var i = 0; i < subdivs.length; i++) {
var subdiv = subdivs[i];
if (subdiv.hasClassName("days")) {
var days = subdiv.childNodesWithTag("div");
for (var j = 0; j < days.length; j++) {
var parentDiv = days[j].childNodesWithTag("div")[0];
for (var k = 0; k < events[j].length; k++) {
var eventRep = events[j][k];
var nbr = eventRep.nbr;
var eventCell = newEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
if (currentPreview == "multicolumndayview") {
for (var j = 0; j < days.length; j++) {
var parentDiv = days[j].childNodesWithTag("div")[0];
var calendar = columnsData[j];
var calendarEvents = calendar[2][0];
var calendarEventsData = calendar[0];
if (parentDiv.getElementsByClassName("event").length > 0) {
var oldEvents = parentDiv.getElementsByClassName("event");
var length = oldEvents.length - 1;
for (var x = length; x >= 0; x--){
oldEvents[x].remove();
}
}
for (var k = 0; k < calendarEvents.length; k++) {
var eventRep = calendarEvents[k];
var nbr = eventRep.nbr;
if (printHoursCheckBox.checked) {
var offset = _computeOffset(parentDiv);
if ((eventRep.start - offset[0]) > 0 && (eventRep.start - offset[0]) < offset[1]) {
var eventCell = newEventDIV(eventRep, calendarEventsData[nbr], offset[0]);
parentDiv.appendChild(eventCell);
}
}
else {
var eventCell = newEventDIV(eventRep, calendarEventsData[nbr], null);
parentDiv.appendChild(eventCell);
}
}
}
}
else {
for (var j = 0; j < days.length; j++) {
var parentDiv = days[j].childNodesWithTag("div")[0];
if (parentDiv.getElementsByClassName("event").length > 0) {
var oldEvents = parentDiv.getElementsByClassName("event");
var length = oldEvents.length - 1;
for (var x = length; x >= 0; x--){
oldEvents[x].remove();
}
}
for (var k = 0; k < events[j].length; k++) {
var eventRep = events[j][k];
var nbr = eventRep.nbr;
if (printHoursCheckBox.checked) {
var offset = _computeOffset(parentDiv);
if ((eventRep.start - offset[0]) > 0 && (eventRep.start - offset[0]) < offset[1]) {
var eventCell = newEventDIV(eventRep, eventsData[nbr], offset[0]);
parentDiv.appendChild(eventCell);
}
}
else {
var eventCell = newEventDIV(eventRep, eventsData[nbr], null);
parentDiv.appendChild(eventCell);
}
}
}
}
}
}
}
function _drawAllDayEvents(events, eventsData) {
function _drawCalendarAllDayEvents(events, eventsData, columnsData) {
var headerView = $("calendarHeader");
var subdivs = headerView.childNodesWithTag("div");
var days = subdivs[1].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
for (var j = 0; j < events[i].length; j++) {
var eventRep = events[i][j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
if (currentPreview == "multicolumndayview"){
var days = subdivs[2].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
var calendar = columnsData[i];
var calendarAllDayEvents = calendar[1][0];
var calendarAllDayEventsData = calendar[0];
for (var j = 0; j < calendarAllDayEvents.length; j++) {
var eventRep = calendarAllDayEvents[j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, calendarAllDayEventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
else {
var days = subdivs[1].childNodesWithTag("div");
for (var i = 0; i < days.length; i++) {
var parentDiv = days[i];
for (var j = 0; j < events[i].length; j++) {
var eventRep = events[i][j];
var nbr = eventRep.nbr;
var eventCell = newAllDayEventDIV(eventRep, eventsData[nbr]);
parentDiv.appendChild(eventCell);
}
}
}
adjustPreviewHeader();
}
// todo : month
function newEventDIV(eventRep, event) {
function newEventDIV(eventRep, event, offset) {
var eventCell = newBaseEventDIV(eventRep, event, event[4]);
var pc = 100 / eventRep.siblings;
@@ -270,7 +394,12 @@ function newEventDIV(eventRep, event) {
eventCell.style.left = left + "%";
var right = Math.floor(100 - (eventRep.position + 1) * pc);
eventCell.style.right = right + "%";
eventCell.addClassName("starts" + eventRep.start);
if (offset != null) {
eventCell.addClassName("starts" + (eventRep.start - offset));
}
else {
eventCell.addClassName("starts" + eventRep.start);
}
eventCell.addClassName("lasts" + eventRep.length);
if (event[7]) {
@@ -376,6 +505,17 @@ function newBaseEventDIV(eventRep, event, eventText) {
return eventCell;
}
function appendStyleElement(folderPath, color) {
if (document.styleSheets) {
var fgColor = getContrastingTextColor(color);
var styleElement = document.styleSheets[3];
styleElement.insertRule(".calendarFolder" + folderPath +
"{background-color: " + color + " !important;" +
" color: " + fgColor + " !important;}", styleElement.cssRules.length);
}
}
function _parseEvent(event) {
// Localized strings :
var start = _("Start:");
@@ -426,6 +566,29 @@ function _parseTask(task) {
return parsedTask;
}
function _computeOffset(hoursCells) {
var outOfDayCells = hoursCells.getElementsByClassName("outOfDay");
var count = 1;
var offset = [];
var buffer;
var j = 1;
for (var i = 0; i < outOfDayCells.length; i++) {
hourCell1 = parseInt(outOfDayCells[i].getAttribute("hour")) + 100;
hourCell2 = parseInt(outOfDayCells[j].getAttribute("hour"));
if (hourCell1 == hourCell2) {
count += 1;
}
else {
break;
}
j ++;
}
offset.push(count * 4);
offset.push((hourCell2 / 100 * 4) - (count * 4));
return offset;
}
/************************************** Preview Navigation *****************************************/
function onCalendarGotoDay(node) {
@@ -481,74 +644,157 @@ function dateSelectorCallback(http) {
return false;
}
/*********************** Input Field, Checkboxes, Radio and listMenu *********************************/
/*********************** Input Field, listMenu, Checkboxes and Radio *********************************/
function onInputTitleChange(event){
var inputFieldTitle = $("inputFieldTitle").value;
if (inputFieldTitle)
document.getElementById("rightFrameTitle").innerHTML = inputFieldTitle + "<br />";
else
document.getElementById("rightFrameTitle").innerHTML = inputFieldTitle;
document.getElementById("rightFrameTitle").remove();
return false;
}
function onPrintLayoutListChange() {
var selectedLayout = $("printLayoutList").value;
var parentView = window.parentvar("currentView");
var selectedLayout = $("printLayoutList").value;
document.getElementById("printHours").disabled = (selectedLayout == 0);
switch(selectedLayout) {
case "0": // List view
window.resizeTo(700,500);
currentView = parentView;
ajustFrames();
currentPreview = parentView;
break;
case "1": // Day view
window.resizeTo(1010,500);
currentView = "dayview";
ajustFrames(currentView);
currentPreview = "dayview";
break;
case "2": // Week view
case "2": // Multi-columns view
window.resizeTo(1010,500);
currentView = "weekview";
ajustFrames(currentView);
currentPreview = "multicolumndayview";
break;
//todo : month
case "3": // Week view
window.resizeTo(1010,500);
currentPreview = "weekview";
break;
/*case "4": // Month view
window.resizeTo(1010,500);
currentPreview = "monthview";
break;*/
}
refreshContent();
return false;
}
function ajustFrames(view) {
if (view == "dayview" || view == "weekview") {
function adjustPreviewHeader() {
// 1 - Check if there is any allDay Events. If not reduce the space taken
var selectedLayout = $("printLayoutList").value;
if (selectedLayout != 0) {
var calendarHeader = $("calendarHeader");
var allDayDisplay = $("calendarHeader").getElementsByClassName("days");
var allDayEvents = $("calendarHeader").getElementsByClassName("eventInside");
var eventHeight = 22;
var headerHeight = 38;
if (selectedLayout == 1) { // Since there is only one column in day view
height = allDayEvents.length * eventHeight;
}
else { // Applies only on week view and multi-columns view
var nbEventsMax = 0
var eventClass = $("calendarHeader").getElementsByClassName("event");
for (var i = 0; i < allDayDisplay[0].childNodes.length; i++) {
if (allDayDisplay[0].childNodes[i].firstChild != null) {
count = allDayDisplay[0].childNodes[i].getElementsByClassName("event").length;
if (count > nbEventsMax) {
nbEventsMax = count;
}
}
}
height = nbEventsMax * eventHeight;
if (selectedLayout == 2) {
headerHeight = 58;
adjustMultiColumnCalendarHeaderDIV();
}
}
calendarHeader.style.height = (height + headerHeight) + "px";
allDayDisplay[0].style.height = height + "px";
}
}
function adjustMultiColumnCalendarHeaderDIV() {
var ch = $("calendarHeader");
var calendarLabels = ch.getElementsByClassName("calendarLabels")[0];
var calendarsToDisplay = calendarLabels.getElementsByClassName("calendarsToDisplay");
var dayLabels = ch.getElementsByClassName("dayLabels")[0].getElementsByClassName("dayColumn")[0];
var days = ch.getElementsByClassName("days")[0].getElementsByClassName("dayColumn");
var daysView = $("daysView").getElementsByClassName("dayColumn");
var nbCalendars = calendarsToDisplay.length;
if (nbCalendars > 0) {
var width = 100/nbCalendars;
var left = 0;
var position = "absolute";
for(var i=0; i < nbCalendars; i++){
calendarsToDisplay[i].setStyle({ width: width + '%', left: left + '%', position: position}).show();
days[i].setStyle({ width: width + '%', left: left + '%'}).show();
daysView[i].setStyle({ width: width + '%', left: left + '%'}).show();
left += width;
}
dayLabels.setStyle({ width: '100%'}).show();
}
else {
$("calendarHeader").remove();
$("daysView").remove();
var htmlText = "<div class='alert-box notice'><span>" + _("notice:") + "</span>"+_("Please go ahead and select calendars")+"</div>";
$("calendarContent").innerHTML = htmlText;
}
}
function adjustFrames() {
var view = $("printLayoutList").value;
if (view == 0) {
var eventsCheckBox = $("printEvents");
var tasksCheckBox = $("printTasks");
onEventsCheck(eventsCheckBox);
onTasksCheck(tasksCheckBox);
document.getElementById("rightFrameTasks").style.pageBreakBefore = 'auto';
document.getElementById("rightFrameTasks").style.pageBreakInside = 'auto';
}
else {
document.getElementById("rightFrameEvents").style.width = '100%';
document.getElementById("rightFrameTasks").style.width = '100%';
document.getElementById("rightFrameTasks").style.pageBreakBefore = 'always';
document.getElementById("rightFrameTasks").style.pageBreakInside = 'avoid';
}
else {
document.getElementById("rightFrameEvents").style.width = '49.5%';
document.getElementById("rightFrameTasks").style.width = '49.5%';
document.getElementById("rightFrameTasks").style.pageBreakBefore = 'auto';
document.getElementById("rightFrameTasks").style.pageBreakInside = 'auto';
}
return false;
}
function onEventsCheck(checkBox) {
if(checkBox.checked){
document.getElementById("rightFrameEvents").style.display = 'block';
var printOptions = document.getElementById("printHours");
var selectedLayout = $("printLayoutList").value;
if (!checkBox.checked || selectedLayout == 0)
printOptions.disabled = true;
else
printOptions.disabled = false;
var events = $("rightFrameEvents").childNodesWithTag("DIV");
if(checkBox.checked && events.length > 0){
$("rightFrameEvents").style.display = 'block';
if ($("printLayoutList").value == 0){
document.getElementById("rightFrameTasks").style.width = '49.5%';
$("rightFrameTasks").style.width = '49.5%';
}
}
else {
document.getElementById("rightFrameEvents").style.display = 'none';
$("rightFrameEvents").style.display = 'none';
if ($("printLayoutList").value == 0){
document.getElementById("rightFrameTasks").style.width = '100%';
$("rightFrameTasks").style.width = '100%';
}
}
return false;
}
function onTasksCheck(checkBox) {
@@ -556,18 +802,58 @@ function onTasksCheck(checkBox) {
for (var i = 0; i < printOptions.length; i++)
printOptions[i].disabled = !checkBox.checked;
if(checkBox.checked) {
document.getElementById("rightFrameTasks").style.display = 'block';
var tasks = $("rightFrameTasks").childNodesWithTag("DIV");
if(checkBox.checked && tasks.length > 0) {
$("rightFrameTasks").style.display = 'block';
if ($("printLayoutList").value == 0){
document.getElementById("rightFrameEvents").style.width = '49.5%';
$("rightFrameEvents").style.width = '49.5%';
}
}
else {
document.getElementById("rightFrameTasks").style.display = 'none';
$("rightFrameTasks").style.display = 'none';
if ($("printLayoutList").value == 0){
document.getElementById("rightFrameEvents").style.width = '100%';
$("rightFrameEvents").style.width = '100%';
}
}
return false;
}
function onPrintWorkingHoursCheck(checkBox) {
var isCheked = checkBox.checked;
var outOfDayCells = $$("DIV#daysView .outOfDay");
var hours = $$("DIV#daysView .hour");
var hoursOutOfDay = [];
for (var i = 0; i < outOfDayCells.length; i++) {
var buffer = outOfDayCells[i].getAttribute("hour").substr(0,1);
if (buffer != "0") {
buffer += outOfDayCells[i].getAttribute("hour").substr(1,1);
}
else {
buffer = outOfDayCells[i].getAttribute("hour").substr(1,1);
}
if(isCheked) {
outOfDayCells[i].hide();
hours[buffer].hide();
}
else {
outOfDayCells[i].show();
hours[buffer].show();
}
}
if (currentPreview == "multicolumndayview") {
_drawCalendarEvents(null, null, eventsBlocks);
}
else {
eventsList = eventsBlocks[2];
if (currentPreview == "monthview") {
//_drawMonthCalendarEvents(eventsList, eventsBlocks[0], null);
}
else {
_drawCalendarEvents(eventsList, eventsBlocks[0], null);
}
}
return false;
}
/*function onPrintDateCheck() {
@@ -580,12 +866,12 @@ function onTasksCheck(checkBox) {
function onPrintCompletedTasksCheck(checkBox) {
printCompletedTasks = (checkBox.checked ? 1 : 0);
refreshTasks();
refreshContent();
}
function onPrintNoDueDateTasksCheck(checkBox) {
printNoDueDateTasks = (checkBox.checked ? 1 : 0);
refreshTasks();
refreshContent();
}
/************** Date picker functions *************
@@ -633,7 +919,6 @@ function onPrintClick(event) {
/**************************** Initialization *******************************************/
function init() {
initializePrintSettings();
//initializeWhatToPrint();
//initializeOptions();
@@ -661,6 +946,6 @@ function initializePrintSettings() {
}*/
/*function initializeOptions() {
}*/
}*/
document.observe("dom:loaded", init);
@@ -164,9 +164,9 @@ function foldersSearchCallback(http) {
dd.innerHTML = '';
for (var i = 1; i < folders.length - 1; i++)
dd.appendChild (addFolderBranchToTree(d, user, folders[i], nodeId, i, false));
dd.appendChild (addFolderBranchToTree(d, user, folders[folders.length-1], nodeId,
(folders.length - 1), true));
dd.appendChild(addFolderBranchToTree(d, user, folders[i], nodeId, i, false));
dd.appendChild(addFolderBranchToTree(d, user, folders[folders.length-1], nodeId,
(folders.length - 1), true));
//dd.update(str);
for (var i = 1; i < folders.length; i++) {
var sd = $("sd" + (nodeId + i));
@@ -193,13 +193,8 @@ function addFolderBranchToTree(tree, user, folder, nodeId, subId, isLast) {
icon += 'calendar-folder-16x16.png';
var folderId = user + ":" + folderInfos[1].substr(1);
// name has the format "Foldername (Firstname Lastname <email>)"
// We sanitize the value to avoid XSS issues
var name = folderInfos[0].escapeHTML();
var pos = name.lastIndexOf(' (');
if (pos > -1)
name = name.substring(0, pos); // strip the part with fullname and email
var name = folderInfos[0].escapeHTML();
var node = new dTreeNode(subId, nodeId, name, 0, '#', folderId,
folderInfos[2] + '-folder', '', '', icon, icon);
node._ls = isLast;
@@ -223,25 +218,19 @@ function onConfirmFolderSelection(event) {
var node = topNode.selectedEntry.parentNode;
var folder = node.getAttribute("dataname");
var type = node.getAttribute("datatype");
var folderName;
if (window.opener.userFolderType == "user") {
var span = $(topNode.selectedEntry).down("SPAN.nodeName");
var email = (span.innerHTML
.replace("&lt;", "<", "g")
.replace("&gt;", ">", "g"));
folderName = email.replace(/>,.*$/, ">", "g");
var resource = $(topNode.selectedEntry).down("SPAN.nodeName");
var description = (resource.innerHTML
.replace("&lt;", "<", "g")
.replace("&gt;", ">", "g"));
folderName = description.replace(/>,.*$/, ">", "g");
}
else {
var resource = $(topNode.selectedEntry).down("SPAN.nodeName");
var user = $(node.parentNode.previousSibling).down("SPAN.nodeName");
var email = (user.innerHTML
.replace("&lt;", "<", "g")
.replace("&gt;", ">", "g"));
folderName = resource.innerHTML;
folderName = folderName.replace(/>,.*(\))?$/, ">)$1", "g");
}
var data = { folderName: folderName, folder: folder, type: type, window: window };
if (parent$(accessToSubscribedFolder(folder)))
window.alert(_("You have already subscribed to that folder!"));
+10 -2
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

+2 -1
View File
@@ -28,7 +28,8 @@ TABLE, DIV, IMG
font-size: inherit;
border: 0px;
margin: 0px;
padding: 0px; }
padding: 0px;
word-break:break-all; }
FIELDSET
{ border: 1px solid #aaa;
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

+1648 -1205
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

+1 -1
View File
@@ -4,4 +4,4 @@
MAJOR_VERSION=2
MINOR_VERSION=2
SUBMINOR_VERSION=5
SUBMINOR_VERSION=6