Merge to 2.2.13

This commit is contained in:
Ludovic Marcotte
2014-12-30 08:01:52 -05:00
37 changed files with 422 additions and 154 deletions

View File

@@ -193,11 +193,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ((o = [self note]))
{
// It is very important here to NOT set <Truncated>0</Truncated> in the response,
// otherwise it'll prevent WP8 phones from sync'ing. See #3028 for details.
o = [o activeSyncRepresentationInContext: context];
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
[s appendFormat: @"<Type>%d</Type>", 1];
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", [o length]];
[s appendFormat: @"<Truncated>%d</Truncated>", 0];
[s appendFormat: @"<Data>%@</Data>", o];
[s appendString: @"</Body>"];
}

View File

@@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <Foundation/NSCalendarDate.h>
#include <Foundation/NSData.h>
#include <Foundation/NSDate.h>
#include <Foundation/NSTimeZone.h>
#include <SOGo/NSString+Utilities.h>
#include <SOGo/NSData+Crypto.h>
@@ -127,13 +128,17 @@ static NSArray *easCommandParameters = nil;
//
- (NSCalendarDate *) calendarDate
{
NSString *s;
id o;
o = [NSCalendarDate dateWithString: self calendarFormat: @"%Y%m%dT%H%M%SZ"];
// We force parsing in the GMT timezone. If we don't do that, the date will be parsed
// in the default timezone.
s = [NSString stringWithFormat: @"%@ GMT", self];
o = [NSCalendarDate dateWithString: s calendarFormat: @"%Y%m%dT%H%M%SZ %Z"];
if (!o)
o = [NSCalendarDate dateWithString: self calendarFormat: @"%Y-%m-%dT%H:%M:%S.%FZ"];
o = [NSCalendarDate dateWithString: s calendarFormat: @"%Y-%m-%dT%H:%M:%S.%FZ %Z"];
return o;
}

View File

@@ -531,6 +531,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncGetChanges: (id <DOMElement>) theDocumentElement
inCollection: (id) theCollection
withWindowSize: (unsigned int) theWindowSize
withMaxSyncResponseSize: (unsigned int) theMaxSyncResponseSize
withSyncKey: (NSString *) theSyncKey
withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
withFilterType: (NSCalendarDate *) theFilterType
@@ -562,7 +563,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
dateCache = [folderMetadata objectForKey: @"DateCache"];
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
theFilterType)
{
@@ -590,7 +591,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
softdelete_count++;
}
if (softdelete_count >= theWindowSize)
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
{
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
@@ -653,7 +654,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
pool = [[NSAutoreleasePool alloc] init];
// Check for the WindowSize and slice accordingly
if (return_count >= theWindowSize)
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
{
more_available = YES;
@@ -668,7 +669,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
deleted = [[component objectForKey: @"c_deleted"] intValue];
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
continue;
{
DESTROY(pool);
continue;
}
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
@@ -692,10 +696,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (![syncCache objectForKey: uid])
updated = NO;
else if ([[component objectForKey: @"c_lastmodified"] intValue] == [[syncCache objectForKey: uid] intValue])
continue;
{
DESTROY(pool);
continue;
}
return_count++;
sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType]
inContext: context
acquire: 0];
@@ -705,7 +712,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else
componentObject = [sogoObject component: NO secure: NO];
//
// We do NOT synchronize NEW events that are in fact, invitations
// to events. This is due to the fact that Outlook 2013 creates
@@ -732,17 +738,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
[syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid];
// No need to set dateCache for Contacts
if ((theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder))
{
NSCalendarDate *d;
if ([[component objectForKey: @"c_cycleenddate"] intValue])
d = [NSCalendarDate dateWithTimeIntervalSince1970: [[component objectForKey: @"c_cycleenddate"] intValue]];
else if ([[component objectForKey: @"c_enddate"] intValue])
d = [NSCalendarDate dateWithTimeIntervalSince1970: [[component objectForKey: @"c_enddate"] intValue]];
else
d = [NSCalendarDate distantFuture];
[dateCache setObject: d forKey: uid];
}
if (updated)
[s appendString: @"<Change xmlns=\"AirSync:\">"];
else
{
// no need to set dateCache for Contacts
if ((theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder))
[dateCache setObject: [componentObject startDate] ? [componentObject startDate] : [NSCalendarDate date] forKey: uid]; // FIXME: need to set proper date for recurring events - softDelete
[s appendString: @"<Add xmlns=\"AirSync:\">"];
}
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
@@ -829,7 +844,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else
found_in_cache = NO;
if (found_in_cache)
k = j+1;
else
@@ -847,7 +861,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
pool = [[NSAutoreleasePool alloc] init];
// Check for the WindowSize and slice accordingly
if (return_count >= theWindowSize)
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
{
NSString *lastSequence;
more_available = YES;
@@ -1047,6 +1061,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (void) processSyncCollection: (id <DOMElement>) theDocumentElement
inBuffer: (NSMutableString *) theBuffer
changeDetected: (BOOL *) changeDetected
maxSyncResponseSize: (int) theMaxSyncResponseSize
{
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *lastServerKey;
SOGoMicrosoftActiveSyncFolderType folderType;
@@ -1076,7 +1091,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[theBuffer appendFormat: @"<CollectionId>%@</CollectionId>", collectionId];
//[theBuffer appendFormat: @"<Status>%d</Status>", 8];
//[theBuffer appendString: @"</Collection>"];
return;
}
@@ -1139,6 +1153,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[self processSyncGetChanges: theDocumentElement
inCollection: collection
withWindowSize: windowSize
withMaxSyncResponseSize: theMaxSyncResponseSize
withSyncKey: syncKey
withFolderType: folderType
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
@@ -1324,15 +1339,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSArray *allCollections;
NSData *d;
int i, j, defaultInterval, heartbeatInterval, internalInterval;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
BOOL changeDetected;
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
// We initialize our output buffer
output = [[NSMutableString alloc] init];
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
//
// We don't support yet empty Sync requests. See: http://msdn.microsoft.com/en-us/library/ee203280(v=exchg.80).aspx
// We return '13' - see http://msdn.microsoft.com/en-us/library/gg675457(v=exchg.80).aspx
//
if (!theDocumentElement || [[(id)[theDocumentElement getElementsByTagName: @"Partial"] lastObject] textValue])
{
[output appendString: @"<Status>13</Status>"];
[output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d];
return;
}
defaults = [SOGoSystemDefaults sharedSystemDefaults];
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
@@ -1363,7 +1395,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
aCollection = [allCollections objectAtIndex: j];
[self processSyncCollection: aCollection inBuffer: s changeDetected: &changeDetected];
[self processSyncCollection: aCollection
inBuffer: s
changeDetected: &changeDetected
maxSyncResponseSize: maxSyncResponseSize];
}
if (changeDetected)
@@ -1378,21 +1413,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
}
// We always return the last generated response.
// If we only return <Sync><Collections/></Sync>,
// iOS powered devices will simply crash.
[output appendString: s];
// Only send a response if there are changes otherwise send an empty response.
if (changeDetected)
{
// We always return the last generated response.
// If we only return <Sync><Collections/></Sync>,
// iOS powered devices will simply crash.
[output appendString: s];
[output appendString: @"</Collections></Sync>"];
[output appendString: @"</Collections></Sync>"];
d = [output dataUsingEncoding: NSUTF8StringEncoding];
d = [d xml2wbxml];
[theResponse setContent: d];
}
// Avoid overloading the autorelease pool here, as Sync command can
// generate fairly large responses.
d = [output dataUsingEncoding: NSUTF8StringEncoding];
RELEASE(output);
d = [d xml2wbxml];
[theResponse setContent: d];
}
@end

View File

@@ -793,10 +793,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
cKey = [allKeys objectAtIndex: i];
// ignore invalid folder in cache caused by fs code bugs
if ([cKey isEqualToString:@"(null)"])
continue;
// if a cache entry is not found in imapGUIDs its either an imap which has been deleted or its an other folder type which can be checked via lookupName.
if (![imapGUIDs allKeysForObject: cKey])
{
@@ -807,7 +803,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
if ([cKey hasPrefix: @"folder"])
if ([cKey hasPrefix: @"folder"] || [cKey isEqualToString:@"(null)"])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [[NSString stringWithFormat: @"mail/%@", [cKey substringFromIndex: 6]] stringByEscapingURL]] ;
command_count++;
@@ -849,6 +845,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
folderMetadata = [allFoldersMetadata objectAtIndex: i];
nameInCache = [NSString stringWithFormat: @"folder%@", [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]];
// we have no guid - ignore the folder
if (![imapGUIDs objectForKey: nameInCache])
continue;
serverId = [NSString stringWithFormat: @"mail/%@", [[imapGUIDs objectForKey: nameInCache] substringFromIndex: 6]];
name = [folderMetadata objectForKey: @"displayName"];
@@ -2480,8 +2481,12 @@ 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 && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame)
return [NSException exceptionWithHTTPStatus: 500];
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
{
RELEASE(context);
RELEASE(pool);
return [NSException exceptionWithHTTPStatus: 500];
}
}
if (d)

View File

@@ -55,7 +55,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[o setUID: theUID];
[o setSequence: theSequence];
return o;
return [o autorelease];
}
- (void) dealloc

View File

@@ -332,7 +332,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
iCalTimeZone *tz;
id o;
NSInteger tzOffset;
BOOL isAllDay;
if ((o = [theValues objectForKey: @"UID"]))
@@ -384,10 +383,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Some Windows devices don't send during event updates.
//if ((o = [theValues objectForKey: @"TimeZone"]))
// {
userTimeZone = [[[context activeUser] userDefaults] timeZone];
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[(iCalCalendar *) parent addTimeZone: tz];
//}
// }
//else
{
// We haven't received a timezone, let's use the user's timezone
// specified in SOGo for now.
userTimeZone = [[[context activeUser] userDefaults] timeZone];
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[(iCalCalendar *) parent addTimeZone: tz];
}
// FIXME: merge with iCalToDo
if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"]))
@@ -402,21 +406,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"];
[start setTimeZone: tz];
if (isAllDay)
{
tzOffset = [userTimeZone secondsFromGMTForDate: o];
o = [o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: tzOffset];
if (isAllDay)
{
[start setDate: o];
[start setTimeZone: nil];
}
else
{
tzOffset = [userTimeZone secondsFromGMTForDate: o];
o = [o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: tzOffset];
[start setDateTime: o];
}
}
@@ -429,19 +425,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (isAllDay)
{
tzOffset = [userTimeZone secondsFromGMTForDate: o];
o = [o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: tzOffset];
[end setDate: o];
[end setTimeZone: nil];
}
else
{
tzOffset = [userTimeZone secondsFromGMTForDate: o];
o = [o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: tzOffset];
[end setDateTime: o];
}
}

View File

@@ -143,8 +143,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
iCalTimeZone *tz;
id o;
NSInteger tzOffset;
userTimeZone = [[[context activeUser] userDefaults] timeZone];
tz = [iCalTimeZone timeZoneForName: [userTimeZone name]];
[(iCalCalendar *) parent addTimeZone: tz];
@@ -165,10 +163,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
o = [o calendarDate];
completed = (iCalDateTime *) [self uniqueChildWithTag: @"completed"];
//tzOffset = [[o timeZone] secondsFromGMTForDate: o];
//o = [o dateByAddingYears: 0 months: 0 days: 0
// hours: 0 minutes: 0
// seconds: -tzOffset];
[completed setDate: o];
[self setStatus: @"COMPLETED"];
}
@@ -177,15 +171,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{
iCalDateTime *due;
o = [o calendarDate];
due = (iCalDateTime *) [self uniqueChildWithTag: @"due"];
[due setTimeZone: tz];
tzOffset = [userTimeZone secondsFromGMTForDate: o];
o = [o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: tzOffset];
[due setDateTime: o];
}

137
ChangeLog
View File

@@ -1,3 +1,122 @@
commit ead665de85e2202dbde926c316cbba927d38dfa7
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 29 16:19:10 2014 -0500
fix tz issue when the user one was different from the system one with EAS
M ActiveSync/NSString+ActiveSync.m
M ActiveSync/iCalEvent+ActiveSync.m
M ActiveSync/iCalToDo+ActiveSync.m
M NEWS
commit a0c1ce8f3b7c22002661d40c24e95a1233b6a6e8
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 29 12:43:20 2014 -0500
Improved handling of non-existant vs. subscribed folders over EAS
M ActiveSync/SOGoActiveSyncDispatcher.m
M SoObjects/Mailer/SOGoMailAccount.m
M SoObjects/Mailer/SOGoMailFolder.m
commit 204a62aa6ac8feb5a13d5a2ee162bb26cc495a50
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Dec 23 10:25:53 2014 -0500
Improved comments in the code
M SoObjects/Appointments/SOGoAppointmentFolder.m
commit 31cffdffd34429d1f4873d07d67e822bff82b7cf
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Tue Dec 23 10:24:16 2014 -0500
Fix freebusy info not always returned
M NEWS
M SoObjects/Appointments/SOGoAppointmentFolder.m
M SoObjects/SOGo/SOGoFolder.h
M SoObjects/SOGo/SOGoFolder.m
commit 255bcbe92fa6b4610edf5f4d05c97b74537ecb6e
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 19:59:33 2014 -0500
Fixed memory leaks in SOGoSyncCacheObject and correctly kill the cache upon each EAS iteration
M ActiveSync/SOGoSyncCacheObject.m
M SoObjects/Mailer/SOGoMailBaseObject.m
M SoObjects/SOGo/NSObject+Utilities.m
M UI/MainUI/SOGoMicrosoftActiveSyncActions.m
commit 72732879fafe8ef928593c0cf3e39d962b096ec9
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 19:32:17 2014 -0500
Added memory statistics - set SOGoDebugLeaks = YES and call [[self class] memoryStatistics]
M SoObjects/SOGo/NSObject+Utilities.h
M SoObjects/SOGo/NSObject+Utilities.m
commit 24a934275f5678f7ab86cb706aa817dfb5ef8991
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 16:12:26 2014 -0500
Fix small memory leak incase of errors
M ActiveSync/SOGoActiveSyncDispatcher.m
commit 58f634bffe35b3a1f6a02072ba0103fea2155a45
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 15:26:22 2014 -0500
Cosmetic improvements to the code
M SoObjects/Appointments/SOGoAppointmentObject.m
M SoObjects/SOGo/SOGoMailer.h
M SoObjects/SOGo/SOGoMailer.m
commit ca4a754f2c12784ff437df34c255d3dccbf5c6f2
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 12:39:58 2014 -0500
Use the right cutoff date
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
M NEWS
M SoObjects/SOGo/SOGoGCSFolder.m
commit 8015688df31a3e3254093b260851d4664d725ea6
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 11:50:51 2014 -0500
Added SOGoMaximumSyncResponseSize to support memory-limited EAS syncs
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
M Documentation/SOGoInstallationGuide.asciidoc
M NEWS
M SoObjects/SOGo/SOGoSystemDefaults.h
M SoObjects/SOGo/SOGoSystemDefaults.m
commit b07913d66d4a47919e09cbcd0c41d92bd97ea0a3
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Mon Dec 22 08:36:55 2014 -0500
See NEWS file
M ActiveSync/NGVCard+ActiveSync.m
M ActiveSync/SOGoActiveSyncDispatcher+Sync.m
M ActiveSync/SOGoActiveSyncDispatcher.m
M NEWS
commit df8a0d8715d97873e782ccfbfaab8b580c88c66e
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Fri Dec 19 09:03:37 2014 -0500
Update ChangeLog
M ChangeLog
commit e6cc56dca1126e09cb8336bdbd4e5dabc7b83cf1
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Fri Dec 19 09:03:19 2014 -0500
@@ -15,6 +134,24 @@ Date: Fri Dec 19 09:01:39 2014 -0500
M NEWS
M SoObjects/Mailer/SOGoDraftObject.m
commit 0e56527e05566e78ac8fe4687d19423de78b0276
Author: Chris Rosenhain <chris@rosenha.in>
Date: Fri Dec 19 11:04:05 2014 +1030
Change ACL modification text to non-gender specific terms
M SoObjects/Appointments/SOGoAppointmentObject.m
M SoObjects/Appointments/SOGoCalendarComponent.m
M SoObjects/SOGo/SOGoGCSFolder.m
M SoObjects/SOGo/SOGoUserFolder.h
M UI/MainUI/SOGoRootPage.m
M UI/Templates/SOGoACLDanishModificationAdvisory.wox
M UI/Templates/SOGoACLDanishRemovalAdvisory.wox
M UI/Templates/SOGoACLDutchModificationAdvisory.wox
M UI/Templates/SOGoACLDutchRemovalAdvisory.wox
M UI/Templates/SOGoACLEnglishModificationAdvisory.wox
M UI/Templates/SOGoACLEnglishRemovalAdvisory.wox
commit db911f323d23f263f5fa9c5fb02d8234127687b3
Author: Ludovic Marcotte <lmarcotte@inverse.ca>
Date: Thu Dec 18 08:56:23 2014 -0500

View File

@@ -2390,6 +2390,15 @@ _SOGoMaximumPingInterval_.
If not set, it defaults to `10` seconds.
|S |SOGoMaximumSyncResponseSize
|Parameter used to overwrite the maximum response size during
a Sync operation. The value is in kilobytes. Setting this to 512
means the response size will be of 524288 bytes or less. Note that
if you set the value too low and a mail message (or any other object)
surpasses it, it will still be synced but only this item will be.
Defaults to `0`, which means no overwrite is performed.
|S |SOGoMaximumSyncWindowSize
|Parameter used to overwrite the maximum number of items returned during
a Sync operation.

View File

@@ -1,7 +1,7 @@
<!-- TODO have the build system take care of this -->
<releaseinfo>Version 2.2.12 - December 2014</releaseinfo>
<subtitle>for version 2.2.12</subtitle>
<date>2014-12-18</date>
<releaseinfo>Version 2.2.13 - December 2014</releaseinfo>
<subtitle>for version 2.2.13</subtitle>
<date>2014-12-30</date>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>

View File

@@ -13,6 +13,6 @@
// TODO have the build system take care of this
:release_version: 2.2.12
:release_version: 2.2.13
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:

14
NEWS
View File

@@ -1,3 +1,17 @@
2.2.13 (2014-12-30)
-------------------
Bug fixes
- fix contact description truncation on WP8 phones (#3028)
- fix freebusy information not always returned
- fix tz issue when the user one was different from the system one with EAS
Enhancements
- initial support for empty sync request/response for EAS
- added the SOGoMaximumSyncResponseSize EAS configuration parameter to
support memory-limited sync response sizes
- we now not only use the creation date for event's cutoff date (EAS)
2.2.12a (2014-12-19)
--------------------

View File

@@ -492,6 +492,9 @@ static Class iCalEventK = nil;
//
// If the user is NOT the owner of the calendar, by default we exclude the freebusy information.
//
// We must include the freebusy information of other users if we are actually looking at their freebusy information
// but we aren't necessarily subscribed to their calendars.
//
- (BOOL) includeInFreeBusy
{
NSNumber *excludeFromFreeBusy;
@@ -500,16 +503,31 @@ static Class iCalEventK = nil;
userLogin = [[context activeUser] login];
is_owner = [userLogin isEqualToString: [self ownerInContext: context]];
// Check if the owner (not the active user) has excluded the calendar from her/his free busy data.
excludeFromFreeBusy
= [self folderPropertyValueInCategory: @"FreeBusyExclusions"
forUser: [SOGoUser userWithLogin: userLogin]];
if (!excludeFromFreeBusy && !is_owner)
return NO;
return ![excludeFromFreeBusy boolValue];
if ([self isSubscription])
{
// If the user has not yet set an include/not include fb information let's EXCLUDE it.
if (!excludeFromFreeBusy)
return NO;
else
return ![excludeFromFreeBusy boolValue];
}
else if (is_owner)
{
// We are the owner but we haven't included/excluded freebusy info, let's INCLUDE it.
if (!excludeFromFreeBusy)
return YES;
else
return ![excludeFromFreeBusy boolValue];
}
// It's not a subscribtion and we aren't the owner. Let's INCLUDE the freebusy info.
return YES;
}
- (void) setIncludeInFreeBusy: (BOOL) newInclude

View File

@@ -400,9 +400,9 @@
{
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
owner: owner];
[self _addOrUpdateEvent: newEvent
forUID: currentUID
owner: owner];
}
[self sendEMailUsingTemplateNamed: @"Update"
@@ -443,7 +443,7 @@
us = [user userSettings];
moduleSettings = [us objectForKey:@"Calendar"];
// Check if the user prevented his account from beeing invited to events
// Check if the user prevented their account from beeing invited to events
if (![user isResource] && [[moduleSettings objectForKey:@"PreventInvitations"] boolValue])
{
// Check if the user have a whiteList
@@ -734,7 +734,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// the modification was actually NOT made on the master event
if ([theEvent recurrenceId])
return;
events = [[theEvent parent] events];
for (i = 0; i < [events count]; i++)
@@ -743,12 +743,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([e recurrenceId])
for (j = 0; j < [theAttendees count]; j++)
if (shouldAdd)
[e addToAttendees: [theAttendees objectAtIndex: j]];
else
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
[e addToAttendees: [theAttendees objectAtIndex: j]];
else
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
}
}
//
//
//
@@ -803,14 +803,17 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// We insert the attendees in all exception occurences, if
// the attendees were added to the master event.
[self _addOrDeleteAttendees: addedAttendees
inRecurrenceExceptionsForEvent: newEvent
add: YES];
inRecurrenceExceptionsForEvent: newEvent
add: YES];
if ([changes sequenceShouldBeIncreased])
{
[newEvent increaseSequence];
// Update attendees calendars and send them an update
// notification by email
// notification by email. We ignore the newly added
// attendees as we don't want to send them invitation
// update emails
[self _handleSequenceUpdateInEvent: newEvent
ignoringAttendees: addedAttendees
fromOldEvent: oldEvent];
@@ -1093,7 +1096,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
{
// We must REMOVE any SENT-BY here. This is important since if A accepted
// the event for B and then, B changes by himself his participation status,
// the event for B and then, B changes by theirself their participation status,
// we don't want to keep the previous SENT-BY attribute there.
[(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"];
}
@@ -1191,7 +1194,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
else
{
// We must REMOVE any SENT-BY here. This is important since if A accepted
// the event for B and then, B changes by himself his participation status,
// the event for B and then, B changes by theirself their participation status,
// we don't want to keep the previous SENT-BY attribute there.
[(NSMutableDictionary *)[attendee attributes] removeObjectForKey: @"SENT-BY"];
}
@@ -1208,7 +1211,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
delegatedUID = [otherDelegate uid];
if (delegatedUID)
// Delegate attendee is a local user; remove event from his calendar
// Delegate attendee is a local user; remove event from their calendar
[self _removeEventFromUID: delegatedUID
owner: [theOwnerUser login]
withRecurrenceId: [event recurrenceId]];
@@ -1240,7 +1243,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[event addToAttendees: delegate];
if (delegatedUID)
// Delegate attendee is a local user; add event to his calendar
// Delegate attendee is a local user; add event to their calendar
[self _addOrUpdateEvent: event
forUID: delegatedUID
owner: [theOwnerUser login]];

View File

@@ -644,8 +644,8 @@
// As much as we can, we try to use c_name == c_uid in order
// to avoid tricky scenarios with some CalDAV clients. For example,
// if Alice invites Bob (both use SOGo) and Bob accepts the invitation
// using Lightning before having refreshed his calendar, he'll end up
// with a duplicate of the event in his database tables.
// using Lightning before having refreshed their calendar, they'll end up
// with a duplicate of the event in their database tables.
if (isNew)
{
newUid = nameInContainer;

View File

@@ -40,6 +40,7 @@
#import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Client.h>
#import <NGImap4/NGImap4Context.h>
#import <NGImap4/NSString+Imap4.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
@@ -667,19 +668,14 @@ static NSString *inboxFolderName = @"INBOX";
NSDictionary *result, *nresult, *namespaceDict;
NSMutableDictionary *folders;
NGImap4Client *client;
SOGoUserDefaults *ud;
NSArray *folderList;
NSEnumerator *e;
NSString *guid;
id object;
BOOL subscribedOnly, hasAnnotatemore;
BOOL hasAnnotatemore;
ud = [[context activeUser] userDefaults];
subscribedOnly = [ud mailShowSubscribedFoldersOnly];
folderList = [[self imap4Connection] allFoldersForURL: [self imap4URL]
onlySubscribedFolders: subscribedOnly];
folderList = [self allFolderPaths];
folders = [NSMutableDictionary dictionary];
@@ -688,19 +684,19 @@ static NSString *inboxFolderName = @"INBOX";
hasAnnotatemore = [self hasCapability: @"annotatemore"];
if (hasAnnotatemore)
result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"];
result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"];
else
result = [client lstatus: @"*" flags: [NSArray arrayWithObjects: @"x-guid", nil]];
result = [client lstatus: @"*" flags: [NSArray arrayWithObjects: @"x-guid", nil]];
e = [folderList objectEnumerator];
while ((object = [e nextObject]))
{
if (hasAnnotatemore)
guid = [[[[result objectForKey: @"FolderList"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"/comment"] objectForKey: @"value.priv"];
guid = [[[[result objectForKey: @"FolderList"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"/comment"] objectForKey: @"value.priv"];
else
guid = [[[result objectForKey: @"status"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"x-guid"];
guid = [[[result objectForKey: @"status"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"x-guid"];
if (!guid)
{
// Don't generate a GUID for "Other users" and "Shared" namespace folders - user foldername instead
@@ -710,18 +706,28 @@ static NSString *inboxFolderName = @"INBOX";
[folders setObject: [NSString stringWithFormat: @"folder%@", [object substringFromIndex: 1]] forKey: [NSString stringWithFormat: @"folder%@", [object substringFromIndex: 1]]];
continue;
}
guid = [[NSProcessInfo processInfo] globallyUniqueString];
nresult = [client annotation: [object substringFromIndex: 1] entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid];
if (![[nresult objectForKey: @"result"] boolValue])
guid = [NSString stringWithFormat: @"%@", [object substringFromIndex: 1]];
// if folder doesn't exists - ignore it
nresult = [client status: [object substringFromIndex: 1]
flags: [NSArray arrayWithObject: @"UIDVALIDITY"]];
if (![[nresult valueForKey: @"result"] boolValue])
continue;
if (hasAnnotatemore)
{
guid = [[NSProcessInfo processInfo] globallyUniqueString];
nresult = [client annotation: [object substringFromIndex: 1] entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid];
}
// setannotation failed or annotatemore is not available
if (![[nresult objectForKey: @"result"] boolValue] || !hasAnnotatemore)
guid = [NSString stringWithFormat: @"%@", [object substringFromIndex: 1]];
}
[folders setObject: [NSString stringWithFormat: @"folder%@", guid] forKey: [NSString stringWithFormat: @"folder%@", [object substringFromIndex: 1]]];
}
return folders;
}

View File

@@ -43,10 +43,6 @@
@implementation SOGoMailBaseObject
#if 0
static BOOL debugOn = YES;
#endif
- (id) initWithImap4URL: (NSURL *) _url
inContainer: (id) _container
{

View File

@@ -1069,7 +1069,11 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
NSException *error;
if ([self imap4Connection])
error = [imap4 deleteMailboxAtURL: [self imap4URL]];
{
error = [imap4 deleteMailboxAtURL: [self imap4URL]];
if (!error)
[[imap4 client] unsubscribe: [[self imap4URL] path]];
}
else
error = [NSException exceptionWithName: @"SOGoMailException"
reason: @"IMAP connection is invalid"

View File

@@ -40,6 +40,8 @@
- (NSString *) labelForKey: (NSString *) key
inContext: (WOContext *) context;
+ (void) memoryStatistics;
@end
#endif /* NSOBJECT+UTILITIES_H */

View File

@@ -22,6 +22,7 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
@@ -122,4 +123,33 @@
return label;
}
//
// Set SOGoDebugLeaks = YES in your defaults to enable.
//
+ (void) memoryStatistics
{
Class *classList = GSDebugAllocationClassList ();
Class *pointer;
int i, count, total, peak;
NSString *className;
pointer = classList;
i = 0;
printf("Class count total peak\n");
while (pointer[i] != NULL)
{
className = NSStringFromClass (pointer[i]);
count = GSDebugAllocationCount (pointer[i]);
total = GSDebugAllocationTotal (pointer[i]);
peak = GSDebugAllocationPeak (pointer[i]);
printf("%s %d %d %d\n", [className UTF8String], count, total, peak);
i++;
}
NSZoneFree(NSDefaultMallocZone(), classList);
printf("Done!\n");
}
@end

View File

@@ -1,6 +1,6 @@
/* SOGoFolder.h - this file is part of SOGo
*
* Copyright (C) 2007-2013 Inverse inc.
* Copyright (C) 2007-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -1,8 +1,6 @@
/* SOGoFolder.m - this file is part of SOGo
*
* Copyright (C) 2007-2011 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2007-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -302,7 +302,7 @@ static NSArray *childRecordFields = nil;
}
/* This method fetches the display name defined by the owner, but is also the
fallback when a subscriber has not redefined the display name yet in his
fallback when a subscriber has not redefined the display name yet in their
environment. */
- (NSString *) _displayNameFromOwner
{
@@ -895,7 +895,7 @@ static NSArray *childRecordFields = nil;
allUsers = [NSMutableArray arrayWithArray: [aGroup members]];
// We remove the active user from the group (if present) in order to
// not subscribe him to his own resource!
// not subscribe him to their own resource!
[allUsers removeObject: [context activeUser]];
}
else
@@ -1165,7 +1165,14 @@ static NSArray *childRecordFields = nil;
int syncTokenInt;
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_component",
@"c_creationdate", @"c_lastmodified", nil];
@"c_creationdate", @"c_lastmodified", nil];
if ([[self folderType] isEqualToString: @"Appointment"])
{
[fields addObject: @"c_enddate"];
[fields addObject: @"c_cycleenddate"];
}
addFields = [[properties allValues] objectEnumerator];
while ((currentField = [addFields nextObject]))
if ([currentField length])
@@ -1181,7 +1188,9 @@ static NSArray *childRecordFields = nil;
if (theStartDate)
{
EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat:
@"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]];
@"(c_enddate > %d OR c_enddate = NULL) OR (c_iscycle = 1 and (c_cycleenddate > %d OR c_cycleenddate = NULL))",
(int)[theStartDate timeIntervalSince1970],
(int)[theStartDate timeIntervalSince1970]];
qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier,
nil];
@@ -1211,7 +1220,9 @@ static NSArray *childRecordFields = nil;
if (theStartDate)
{
EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat:
@"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]];
@"(c_enddate > %d OR c_enddate = NULL) OR (c_iscycle = 1 and (c_cycleenddate > %d OR c_cycleenddate = NULL))",
(int)[theStartDate timeIntervalSince1970],
(int)[theStartDate timeIntervalSince1970]];
qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier,
nil];

View File

@@ -1,8 +1,6 @@
/* SOGoMailer.h - this file is part of SOGo
*
* Copyright (C) 2007 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2007-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -1,8 +1,6 @@
/* SOGoMailer.m - this file is part of SOGo
*
* Copyright (C) 2007-2013 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2007-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -99,6 +99,7 @@
- (int) maximumSyncInterval;
- (int) internalSyncInterval;
- (int) maximumSyncWindowSize;
- (int) maximumSyncResponseSize;
@end

View File

@@ -636,4 +636,16 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return [self integerForKey: @"SOGoMaximumSyncWindowSize"];
}
- (int) maximumSyncResponseSize
{
int v;
v = [self integerForKey: @"SOGoMaximumSyncResponseSize"];
if (v > 0)
v = v * 1024;
return v;
}
@end

View File

@@ -31,7 +31,7 @@
Child objects:
'Calendar': SOGoAppointmentFolder
The SOGoUserFolder is the "home directory" of the user where all his
The SOGoUserFolder is the "home directory" of the user where all their
processing starts. It is the 'znek' in such a path:
/SOGo/so/znek/Calendar
*/

View File

@@ -21,6 +21,8 @@
#import <Foundation/NSBundle.h>
#import <SOGo/SOGoCache.h>
#import <SOGo/NSObject+Utilities.h>
#import <SOGo/SOGoFolder.h>
#import <NGObjWeb/NSException+HTTP.h>
@@ -54,13 +56,17 @@
ex = [dispatcher dispatchRequest: request inResponse: response context: context];
//[[self class] memoryStatistics];
if (ex)
{
return [NSException exceptionWithHTTPStatus: 500];
}
RELEASE(dispatcher);
[[SOGoCache sharedCache] killCache];
return response;
}

View File

@@ -361,7 +361,7 @@
if (login)
{
/* We redirect the user to his "homepage" when newLocation could not be
/* We redirect the user to their "homepage" when newLocation could not be
deduced from the "cas-location" cookie and the current action is not a
login callback (ticket != nil). */
if (!newLocation || !ticket)

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can subscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>subscribe?mail-invitation=YES

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can unsubscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>unsubscribe?mail-invitation=YES

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can subscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>subscribe?mail-invitation=YES

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can unsubscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>unsubscribe?mail-invitation=YES

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has modified your access rights for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can subscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>subscribe?mail-invitation=YES

View File

@@ -12,7 +12,7 @@
</var:if>
<var:if condition="isBody">
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for his <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<var:string value="currentUserName" const:escapeHTML="NO"/> has removed you from the access list for their <var:string const:value='"' const:escapeHTML="NO"/><var:string value="resourceName" const:escapeHTML="NO"/><var:string const:value='"' const:escapeHTML="NO"/> folder.
<!--
You can unsubscribe directly to that folder by following this link:
<var:string value="httpAdvisoryURL" const:escapeHTML="NO"/>unsubscribe?mail-invitation=YES

View File

@@ -4,4 +4,4 @@
MAJOR_VERSION=2
MINOR_VERSION=2
SUBMINOR_VERSION=12a
SUBMINOR_VERSION=13