Compare commits
270
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc3741f436 | ||
|
|
feac8bb8f4 | ||
|
|
9f11e68de8 | ||
|
|
15de70b938 | ||
|
|
ea24e9586b | ||
|
|
4e9911419a | ||
|
|
bfa3cf379c | ||
|
|
4002da9323 | ||
|
|
d5c09f6114 | ||
|
|
737a19e653 | ||
|
|
43248fb5ea | ||
|
|
1118dbceff | ||
|
|
369060a52f | ||
|
|
4da53b88b6 | ||
|
|
8924a2b670 | ||
|
|
62537e0c15 | ||
|
|
7ef976bfe4 | ||
|
|
05515673b7 | ||
|
|
84f4698225 | ||
|
|
e5fdc28241 | ||
|
|
7ddf7be705 | ||
|
|
b63ee6998d | ||
|
|
0b3095f7ed | ||
|
|
e88af22708 | ||
|
|
d68008b5fd | ||
|
|
e2d6e8ffe9 | ||
|
|
2ad02b8386 | ||
|
|
58afdedcc1 | ||
|
|
e6be7e00ba | ||
|
|
74eb0846e8 | ||
|
|
4f4b00cc54 | ||
|
|
ad40bff91f | ||
|
|
13f4e5a4de | ||
|
|
459b183a42 | ||
|
|
9fa9b7acab | ||
|
|
a393128d7a | ||
|
|
73ac55acfe | ||
|
|
e1393c3af2 | ||
|
|
d27c792324 | ||
|
|
42d306a24b | ||
|
|
6a7e06b0bb | ||
|
|
e36e9fd826 | ||
|
|
aface8f8cf | ||
|
|
790fc8e940 | ||
|
|
35d1ca8775 | ||
|
|
e8ca329070 | ||
|
|
d07e254026 | ||
|
|
0f08571a83 | ||
|
|
45bc5a5368 | ||
|
|
d51bef162a | ||
|
|
1535660a31 | ||
|
|
90063ae563 | ||
|
|
b27cfc2bd8 | ||
|
|
5496dd97d9 | ||
|
|
31f2e4b549 | ||
|
|
ec8c2d08e2 | ||
|
|
1ce9d71635 | ||
|
|
a6c440d245 | ||
|
|
e3d3eb171d | ||
|
|
3a4365b15a | ||
|
|
7f3d57c8dc | ||
|
|
e93853d910 | ||
|
|
fcba6f2ddb | ||
|
|
eb50f94b2b | ||
|
|
9f7b69b037 | ||
|
|
c6cd3b0e1d | ||
|
|
31026a5bed | ||
|
|
08fbc00d7a | ||
|
|
957336b24c | ||
|
|
2a925de52c | ||
|
|
cf8dd02411 | ||
|
|
da37b3f868 | ||
|
|
24450a9c20 | ||
|
|
964f79db35 | ||
|
|
c0f51cf731 | ||
|
|
ae3ce7186d | ||
|
|
5034a72ed1 | ||
|
|
fdad1e061a | ||
|
|
d5d54822b0 | ||
|
|
81cdf5be7b | ||
|
|
959ea0f62f | ||
|
|
1c666e8ead | ||
|
|
7692832672 | ||
|
|
2bfd9f983d | ||
|
|
2a5e19a762 | ||
|
|
258f5e0b25 | ||
|
|
3609e41205 | ||
|
|
2ea2edbccd | ||
|
|
fab9e938fc | ||
|
|
9a2f3914e2 | ||
|
|
f13b3dd201 | ||
|
|
413550a025 | ||
|
|
06ac255f1a | ||
|
|
0682b24438 | ||
|
|
a01926b878 | ||
|
|
58ff8139e9 | ||
|
|
70bb512bb8 | ||
|
|
7b580f8af6 | ||
|
|
11d9765a69 | ||
|
|
24a3ecd2d6 | ||
|
|
139ed39663 | ||
|
|
875d23aea0 | ||
|
|
c4ad8a64fa | ||
|
|
c464791d92 | ||
|
|
92bf494973 | ||
|
|
47ab4b837c | ||
|
|
e30fb0d1fb | ||
|
|
3efbf65f80 | ||
|
|
0982d50643 | ||
|
|
532105f1fa | ||
|
|
7cdfaf4d9c | ||
|
|
8cb19546df | ||
|
|
6dc02abe80 | ||
|
|
f70876aa05 | ||
|
|
3efdbf0166 | ||
|
|
348c81bf6c | ||
|
|
44158af057 | ||
|
|
4bb1fa0995 | ||
|
|
05882362f0 | ||
|
|
90a3fcacaf | ||
|
|
45ac6845a5 | ||
|
|
b4af6f5033 | ||
|
|
6bbd182559 | ||
|
|
7290fa2ea2 | ||
|
|
788f36b96e | ||
|
|
cb0d327f90 | ||
|
|
b646e35f5d | ||
|
|
f3a5df37ae | ||
|
|
7a7547c7c3 | ||
|
|
11e7af3450 | ||
|
|
16d312eb98 | ||
|
|
295b53df66 | ||
|
|
cab6a8c1e2 | ||
|
|
59ad8a2e5a | ||
|
|
ab8feff67d | ||
|
|
b9294da996 | ||
|
|
18a300ca5b | ||
|
|
b8b45eda75 | ||
|
|
370be4f835 | ||
|
|
2720aebf04 | ||
|
|
bf02b3046f | ||
|
|
3d4a965412 | ||
|
|
372206e69e | ||
|
|
9dc00acd53 | ||
|
|
9d5f42eefa | ||
|
|
f4dcd37570 | ||
|
|
c4858ffb2b | ||
|
|
d72188e2da | ||
|
|
3647f066da | ||
|
|
e207cc79a7 | ||
|
|
eb84e618e6 | ||
|
|
669bd99a2d | ||
|
|
3b0ead7bbd | ||
|
|
4483d47e89 | ||
|
|
a5213b3321 | ||
|
|
e6e34ae053 | ||
|
|
815b3b4b03 | ||
|
|
b08d98e2e1 | ||
|
|
1d2c0ed350 | ||
|
|
0bdbe5bbac | ||
|
|
acfa96263b | ||
|
|
99b1353faf | ||
|
|
27e57e397e | ||
|
|
778612bf41 | ||
|
|
9d92318524 | ||
|
|
cf9b7fab36 | ||
|
|
f1037e7d48 | ||
|
|
233b9c0613 | ||
|
|
89206f3705 | ||
|
|
a87daf282c | ||
|
|
fb4889a15e | ||
|
|
3fdabf8671 | ||
|
|
66c52f22dd | ||
|
|
8f7334f11d | ||
|
|
624833eca9 | ||
|
|
431c53b84d | ||
|
|
c9d9f7b47a | ||
|
|
1667f8879d | ||
|
|
3611c4021d | ||
|
|
bba0d6f44d | ||
|
|
bf390f17dd | ||
|
|
834aba85a6 | ||
|
|
d878c69c15 | ||
|
|
d9d59c6b52 | ||
|
|
69000a0929 | ||
|
|
876d9cfa21 | ||
|
|
117e6b6bc1 | ||
|
|
01e2256483 | ||
|
|
49e7cf0994 | ||
|
|
b3d6973fb1 | ||
|
|
aae522259f | ||
|
|
68ef8784a0 | ||
|
|
da821ea6c7 | ||
|
|
4dee99e0ba | ||
|
|
eea08d083d | ||
|
|
8055acfd4a | ||
|
|
1fc947d8c8 | ||
|
|
7b3a238fb9 | ||
|
|
99d121fd3e | ||
|
|
eab3ee111a | ||
|
|
eeb6be1f08 | ||
|
|
29108bbb62 | ||
|
|
9c6884bd7d | ||
|
|
0aac4897fa | ||
|
|
2a99d1fd80 | ||
|
|
ba148eec5b | ||
|
|
49c4884cc9 | ||
|
|
e5d6ee93c3 | ||
|
|
40eff05d4e | ||
|
|
be031fe2c9 | ||
|
|
0fe1419289 | ||
|
|
9db633cada | ||
|
|
8a2d7d56e0 | ||
|
|
0c17e745f1 | ||
|
|
18bc75e50b | ||
|
|
68ad3c1045 | ||
|
|
85c8aebbd4 | ||
|
|
0a8834193f | ||
|
|
91e4c2cefc | ||
|
|
b80d8dbb06 | ||
|
|
525d1ad80f | ||
|
|
332dd0d96d | ||
|
|
a45000d15d | ||
|
|
3301bdb055 | ||
|
|
2f82b5cfd3 | ||
|
|
e3b64e035d | ||
|
|
1470258b5c | ||
|
|
83fee27ca8 | ||
|
|
e5b0596bc9 | ||
|
|
26e8276b08 | ||
|
|
078ba95f8e | ||
|
|
65525dd33b | ||
|
|
da81f7bc58 | ||
|
|
8fcd594be6 | ||
|
|
548f056b9c | ||
|
|
dffdbf318c | ||
|
|
161bcfac87 | ||
|
|
c6f17cd45c | ||
|
|
38713a6c67 | ||
|
|
8cef98eba1 | ||
|
|
2144d3122a | ||
|
|
9d272ae063 | ||
|
|
fcd0140ffd | ||
|
|
d783782af7 | ||
|
|
869b3c4cd1 | ||
|
|
2da2937391 | ||
|
|
5034d6655a | ||
|
|
d1446f7dc1 | ||
|
|
c5a219add8 | ||
|
|
bc2c839fbc | ||
|
|
2e736490d5 | ||
|
|
20bf66d931 | ||
|
|
864a7f8028 | ||
|
|
7b5541b5d1 | ||
|
|
744cba4422 | ||
|
|
ded467e0a2 | ||
|
|
a718b87d91 | ||
|
|
6e2e71b202 | ||
|
|
04e63e5edf | ||
|
|
f1a50bcb74 | ||
|
|
0672b96b8b | ||
|
|
93fa74d279 | ||
|
|
ffa4079fa8 | ||
|
|
8f75bdce2d | ||
|
|
4f53ea70e1 | ||
|
|
b72a9383a4 | ||
|
|
d3d9a720dc | ||
|
|
02daebe36b | ||
|
|
336e4c93e9 | ||
|
|
47252affba |
No files matched your search
+3
-5
@@ -9,6 +9,7 @@
|
||||
*/*/obj/
|
||||
*/obj/
|
||||
.scss-lint-config.yml_
|
||||
ActiveSync/ActiveSync.SOGo
|
||||
Documentation/*.docbook
|
||||
Documentation/*.pdf
|
||||
SoObjects/SOGo/SOGo.framework/
|
||||
@@ -20,11 +21,8 @@ UI/WebServerResources/bower_components/
|
||||
UI/WebServerResources/css/
|
||||
UI/WebServerResources/css/styles.css
|
||||
UI/WebServerResources/css/styles.css.map
|
||||
UI/WebServerResources/js/Common.js*
|
||||
UI/WebServerResources/js/Contacts.js*
|
||||
UI/WebServerResources/js/Mailer.js*
|
||||
UI/WebServerResources/js/Preferences.js*
|
||||
UI/WebServerResources/js/Scheduler.js*
|
||||
UI/WebServerResources/js/*.js
|
||||
UI/WebServerResources/js/*.js.map
|
||||
UI/WebServerResources/js/vendor/
|
||||
UI/WebServerResources/node_modules/
|
||||
UI/WebServerResources/scss/.sass-cache/
|
||||
|
||||
@@ -112,6 +112,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher (Sync)
|
||||
|
||||
- (void) _setOrUnsetSyncInProgress: (BOOL) set
|
||||
invalidate: (BOOL) invalidate
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
if (set)
|
||||
if (invalidate)
|
||||
[[o properties] setObject: [NSCalendarDate date] forKey: @"InvalidateSyncInProgress"];
|
||||
else
|
||||
[[o properties] setObject: [NSCalendarDate date] forKey: @"SyncInProgress"];
|
||||
else
|
||||
{
|
||||
[[o properties] removeObjectForKey: @"SyncInProgress"];
|
||||
[[o properties] removeObjectForKey: @"InvalidateSyncInProgress"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
||||
forKey: (NSString *) theFolderKey
|
||||
{
|
||||
@@ -133,6 +157,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
|
||||
[[o properties] addEntriesFromDictionary: values];
|
||||
[o save];
|
||||
@@ -159,7 +184,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSString *nameInCache;
|
||||
|
||||
if (theFolderType == ActiveSyncMailFolder)
|
||||
nameInCache= [[[theCollection mailAccountFolder] imapFolderGUIDs] objectForKey: [theCollection nameInContainer]];
|
||||
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
||||
else
|
||||
{
|
||||
NSString *component_name;
|
||||
@@ -170,7 +195,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
nameInCache= [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
||||
}
|
||||
|
||||
return nameInCache;
|
||||
@@ -404,7 +429,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
|
||||
}
|
||||
break;
|
||||
case ActiveSyncEventFolder:
|
||||
@@ -415,14 +439,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
|
||||
}
|
||||
break;
|
||||
case ActiveSyncMailFolder:
|
||||
default:
|
||||
{
|
||||
NSDictionary *result;
|
||||
NSString *modseq;
|
||||
NSNumber *modseq;
|
||||
|
||||
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
||||
|
||||
@@ -430,11 +453,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
||||
|
||||
if (modseq)
|
||||
[syncCache setObject: modseq forKey: serverId];
|
||||
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
||||
}
|
||||
}
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
|
||||
[theBuffer appendString: @"<Change>"];
|
||||
@@ -577,36 +600,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
withFilterType: (NSCalendarDate *) theFilterType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
lastServerKey: (NSString **) theLastServerKey
|
||||
|
||||
{
|
||||
NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
|
||||
NSString *davCollectionTagToStore;
|
||||
NSAutoreleasePool *pool;
|
||||
NSMutableString *s;
|
||||
|
||||
BOOL more_available;
|
||||
BOOL cleanup_needed, more_available;
|
||||
int i, max;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
more_available = NO;
|
||||
cleanup_needed = more_available = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
// If this is a new sync operation, DateCache and SyncCache needs to be deleted
|
||||
// If this is a new sync operation, DateCache and SyncCache need to be deleted
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
||||
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
||||
}
|
||||
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
|
||||
{
|
||||
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
|
||||
// We need to cleanup this mess.
|
||||
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login]];
|
||||
cleanup_needed = YES;
|
||||
}
|
||||
|
||||
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
||||
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
||||
|
||||
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
||||
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation
|
||||
theFilterType)
|
||||
(cleanup_needed ||
|
||||
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
||||
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
|
||||
theFilterType
|
||||
)
|
||||
{
|
||||
NSArray *allKeys;
|
||||
NSString *key;
|
||||
@@ -622,14 +652,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
||||
{
|
||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||
[s appendString: @"</SoftDelete>"];
|
||||
if ([syncCache objectForKey:key])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete %@", key];
|
||||
|
||||
[syncCache removeObjectForKey: key];
|
||||
[dateCache removeObjectForKey: key];
|
||||
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
||||
[s appendString: @"</SoftDelete>"];
|
||||
|
||||
[syncCache removeObjectForKey: key];
|
||||
//[dateCache removeObjectForKey: key];
|
||||
|
||||
softdelete_count++;
|
||||
softdelete_count++;
|
||||
}
|
||||
else if (cleanup_needed)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
|
||||
|
||||
// With this we make sure that a SoftDelete is set again on next sync.
|
||||
[syncCache setObject: @"0" forKey: key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
|
||||
|
||||
// Now we are save to remove the dateCache entry.
|
||||
[dateCache removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
||||
@@ -656,8 +708,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
|
||||
return;
|
||||
|
||||
more_available = NO;
|
||||
|
||||
davCollectionTagToStore = [theCollection davCollectionTag];
|
||||
|
||||
@@ -673,7 +723,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSDictionary *component;
|
||||
NSArray *allComponents;
|
||||
|
||||
BOOL updated;
|
||||
BOOL updated, initialLoadInProgress;
|
||||
int deleted, return_count;
|
||||
|
||||
if (theFolderType == ActiveSyncContactFolder)
|
||||
@@ -683,8 +733,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
else
|
||||
component_name = @"vtodo";
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObjects: [[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending:YES], nil]];
|
||||
initialLoadInProgress = NO;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
||||
matchingSyncToken: theSyncKey
|
||||
fromDate: theFilterType
|
||||
initialLoad: initialLoadInProgress];
|
||||
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
||||
|
||||
|
||||
// Check for the WindowSize
|
||||
@@ -847,12 +913,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSMutableArray *allCacheObjects, *sortedBySequence;
|
||||
|
||||
SOGoMailObject *mailObject;
|
||||
NSArray *allMessages;
|
||||
NSArray *allMessages, *a;
|
||||
NSString *lastSequence, *firstUidAdded;
|
||||
|
||||
int j, k, return_count;
|
||||
BOOL found_in_cache;
|
||||
int j, k, return_count, highestmodseq;
|
||||
BOOL found_in_cache, initialLoadInProgress;
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
initialLoadInProgress = NO;
|
||||
found_in_cache = NO;
|
||||
lastSequence = nil;
|
||||
firstUidAdded = nil;
|
||||
|
||||
if ([theSyncKey isEqualToString: @"-1"])
|
||||
{
|
||||
highestmodseq = 0;
|
||||
|
||||
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
||||
}
|
||||
else
|
||||
{
|
||||
a = [theSyncKey componentsSeparatedByString: @"-"];
|
||||
highestmodseq = [[a objectAtIndex: 1] intValue];
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
||||
{
|
||||
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
||||
initialLoadInProgress = YES;
|
||||
else
|
||||
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType initialLoad: initialLoadInProgress];
|
||||
max = [allMessages count];
|
||||
|
||||
allCacheObjects = [NSMutableArray array];
|
||||
@@ -860,7 +953,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
||||
}
|
||||
|
||||
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
@@ -869,15 +962,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
||||
|
||||
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]);
|
||||
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]);
|
||||
if (debugOn)
|
||||
{
|
||||
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
|
||||
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
|
||||
}
|
||||
|
||||
lastCacheObject = [sortedBySequence lastObject];
|
||||
|
||||
//
|
||||
// Cleanup the mess
|
||||
//
|
||||
if (cleanup_needed)
|
||||
{
|
||||
NSMutableArray *sortedByUID;
|
||||
int uidnextFromCache;
|
||||
|
||||
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
||||
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
||||
|
||||
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
|
||||
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
|
||||
uidnextFromCache = [[a objectAtIndex: 0] intValue];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
|
||||
|
||||
// Remove all entries from cache beginning with the first uid added by the last sync request.
|
||||
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
|
||||
|
||||
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
||||
}
|
||||
|
||||
RELEASE(sortedByUID);
|
||||
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
|
||||
//and we would skip the cache when generating the response.
|
||||
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
||||
|
||||
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
|
||||
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject)
|
||||
if (!cleanup_needed &&
|
||||
[folderMetadata objectForKey: @"MoreAvailable"] &&
|
||||
lastCacheObject &&
|
||||
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
|
||||
{
|
||||
for (j = 0; j < [allCacheObjects count]; j++)
|
||||
{
|
||||
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
|
||||
([[allCacheObjects objectAtIndex: j] sequence] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
|
||||
{
|
||||
// We need to continue with adds or deletes from here.
|
||||
found_in_cache = YES;
|
||||
j--;
|
||||
break;
|
||||
}
|
||||
|
||||
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
||||
{
|
||||
// Found out where we're at, let's continue from there...
|
||||
@@ -892,12 +1052,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if (found_in_cache)
|
||||
k = j+1;
|
||||
else
|
||||
{
|
||||
k = 0;
|
||||
j = 0;
|
||||
}
|
||||
|
||||
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
|
||||
j = k = 0;
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
|
||||
|
||||
return_count = 0;
|
||||
|
||||
@@ -908,23 +1066,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Check for the WindowSize and slice accordingly
|
||||
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
||||
{
|
||||
NSString *lastSequence;
|
||||
more_available = YES;
|
||||
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence];
|
||||
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey);
|
||||
if (!firstUidAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
firstUidAdded = [a objectAtIndex: 0];
|
||||
RETAIN(firstUidAdded);
|
||||
}
|
||||
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
||||
//RETAIN(lastSequence);
|
||||
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUidAdded, lastSequence];
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
|
||||
|
||||
DESTROY(pool);
|
||||
break;
|
||||
}
|
||||
|
||||
aCacheObject = [allCacheObjects objectAtIndex: k];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
|
||||
|
||||
// If found in cache, it's either a Change or a Delete operation.
|
||||
if ([syncCache objectForKey: [aCacheObject uid]])
|
||||
{
|
||||
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - DELETE!"];
|
||||
|
||||
// Deleted
|
||||
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
@@ -942,9 +1115,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
mailObject = [theCollection lookupName: [aCacheObject uid]
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
|
||||
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - CHANGE!"];
|
||||
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
@@ -960,6 +1136,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - ADD!"];
|
||||
|
||||
// Added
|
||||
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
||||
{
|
||||
@@ -992,11 +1171,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
||||
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
||||
|
||||
// Save the frist UID we add. We will use it for the synckey late.
|
||||
if (!firstUidAdded)
|
||||
{
|
||||
firstUidAdded = [aCacheObject uid];
|
||||
RETAIN(firstUidAdded);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - first uid added %@", firstUidAdded];
|
||||
}
|
||||
|
||||
return_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]);
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,16 +1195,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (more_available)
|
||||
{
|
||||
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
||||
//[folderMetadata setObject: lastSequence forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
||||
//RELEASE(lastSequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||
|
||||
if (firstUidAdded)
|
||||
{
|
||||
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
||||
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUidAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
|
||||
}
|
||||
else
|
||||
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
||||
}
|
||||
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
RELEASE(firstUidAdded);
|
||||
RELEASE(*theLastServerKey);
|
||||
|
||||
} // default:
|
||||
@@ -1113,15 +1314,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
changeDetected: (BOOL *) changeDetected
|
||||
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
||||
{
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
id collection, value;
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
BOOL getChanges, first_sync;
|
||||
unsigned int windowSize, v, status;
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
|
||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
id collection, value;
|
||||
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
unsigned int windowSize, v, status;
|
||||
BOOL getChanges, first_sync;
|
||||
|
||||
changeBuffer = [NSMutableString string];
|
||||
commandsBuffer = [NSMutableString string];
|
||||
|
||||
@@ -1144,7 +1345,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//[theBuffer appendString: @"</Collection>"];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// First check if we have any concurrent Sync requests going on for this device.
|
||||
// If we do and we are still within our maximumSyncInterval, we let our EAS
|
||||
// device know to retry.
|
||||
//
|
||||
folderKey = [self _getNameInCache: collection withType: folderType];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// We check for a window size, default to 100 if not specfied or out of bounds
|
||||
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
||||
|
||||
@@ -1169,8 +1378,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
first_sync = NO;
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
|
||||
if ([syncKey isEqualToString: @"0"])
|
||||
{
|
||||
davCollectionTag = @"-1";
|
||||
@@ -1225,7 +1432,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
|
||||
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
||||
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]];
|
||||
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1271,7 +1478,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
lastServerKey: &lastServerKey];
|
||||
}
|
||||
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
|
||||
folderMetadata = [self _folderMetadataForKey: folderKey];
|
||||
|
||||
// If we got any changes or if we have applied any commands
|
||||
// let's regenerate our SyncKey based on the collection tag.
|
||||
@@ -1433,16 +1640,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
NSArray *allCollections;
|
||||
NSData *d;
|
||||
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize;
|
||||
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
|
||||
BOOL changeDetected;
|
||||
|
||||
changeDetected = NO;
|
||||
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
|
||||
// We initialize our output buffer
|
||||
output = [[NSMutableString alloc] init];
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
|
||||
[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:\">"];
|
||||
@@ -1457,12 +1663,53 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[output appendString: @"</Sync>"];
|
||||
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
[theResponse setContent: d];
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([[self globalMetadataForDevice] objectForKey: @"SyncInProgress"])
|
||||
{
|
||||
NSCalendarDate *syncStartDate;
|
||||
|
||||
// An other sync is going on right now. Lets break it and wait
|
||||
// until the other process clears it up.
|
||||
[self _setOrUnsetSyncInProgress: YES invalidate: YES];
|
||||
total_sleep = 0;
|
||||
|
||||
syncStartDate = [[self globalMetadataForDevice] objectForKey: @"SyncInProgress"];
|
||||
|
||||
while ([[self globalMetadataForDevice] objectForKey: @"SyncInProgress"])
|
||||
{
|
||||
// Don't go into a heartbeat loop.
|
||||
heartbeatInterval = 0;
|
||||
|
||||
// We waited too long. Return a fatal error to the client.
|
||||
if (abs([syncStartDate timeIntervalSinceNow]) > defaultInterval)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - We waited too long. syncStartDate: %@ %@", syncStartDate, [self globalMetadataForDevice]];
|
||||
|
||||
[theResponse setStatus: 503];
|
||||
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - globalMetadataForDevice %@", [self globalMetadataForDevice]];
|
||||
|
||||
[self logWithFormat: @"Sync in progress for device %@ (login: %@). Lock will expire in %d seconds", [context objectForKey: @"DeviceId"], [[context activeUser] login], defaultInterval-total_sleep];
|
||||
sleep(5);
|
||||
total_sleep += 5;
|
||||
}
|
||||
}
|
||||
|
||||
// No lock or broke the existing one, lets grab it and proceed
|
||||
[self _setOrUnsetSyncInProgress: YES invalidate: NO];
|
||||
|
||||
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
changeDetected = NO;
|
||||
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
||||
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
||||
defaultInterval = [defaults maximumSyncInterval];
|
||||
internalInterval = [defaults internalSyncInterval];
|
||||
|
||||
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
||||
@@ -1472,11 +1719,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// We check to see if our heartbeat interval falls into the supported ranges.
|
||||
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
||||
{
|
||||
int limit;
|
||||
// Interval is too long, inform the client.
|
||||
heartbeatInterval = defaultInterval;
|
||||
|
||||
// Outlook doesn't like this...
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval];
|
||||
// When Status = 14, the Wait interval is specified in minutes while
|
||||
// defaultInterval is specifed in seconds. Adjust accordinlgy.
|
||||
limit = defaultInterval/60;
|
||||
if (limit < 1) limit = 1;
|
||||
if (limit > 59) limit = 59;
|
||||
//[output appendFormat: @"<Limit>%d</Limit>", limit];
|
||||
//[output appendFormat: @"<Status>%d</Status>", 14];
|
||||
}
|
||||
|
||||
@@ -1492,7 +1744,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
for (j = 0; j < [allCollections count]; j++)
|
||||
{
|
||||
aCollection = [allCollections objectAtIndex: j];
|
||||
|
||||
|
||||
[self processSyncCollection: aCollection
|
||||
inBuffer: s
|
||||
changeDetected: &changeDetected
|
||||
@@ -1504,13 +1756,46 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (changeDetected)
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
// Don't return a response if an other Sync is waiting and this is a heartbeat request.
|
||||
if ([[self globalMetadataForDevice] objectForKey: @"InvalidateSyncInProgress"] && heartbeatInterval > 1)
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Heartbeat stopped - discard response %@", [self globalMetadataForDevice]];
|
||||
|
||||
[theResponse setStatus: 503];
|
||||
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
|
||||
RELEASE(output);
|
||||
return;
|
||||
}
|
||||
|
||||
[self logWithFormat: @"Change detected during Sync, we push the content."];
|
||||
break;
|
||||
}
|
||||
else if (heartbeatInterval > 1)
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
total_sleep = 0;
|
||||
|
||||
while (total_sleep < internalInterval)
|
||||
{
|
||||
// We check if we must break the current synchronization since an other Sync
|
||||
// has just arrived.
|
||||
if ([[self globalMetadataForDevice] objectForKey: @"InvalidateSyncInProgress"])
|
||||
{
|
||||
if (debugOn)
|
||||
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
|
||||
|
||||
// Make sure we end the heardbeat-loop.
|
||||
heartbeatInterval = internalInterval = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
|
||||
sleep(5);
|
||||
total_sleep += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1518,8 +1803,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response.
|
||||
//
|
||||
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
|
||||
// otherwise send an empty response.
|
||||
//
|
||||
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
||||
{
|
||||
// We always return the last generated response.
|
||||
@@ -1537,6 +1824,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
// Avoid overloading the autorelease pool here, as Sync command can
|
||||
// generate fairly large responses.
|
||||
RELEASE(output);
|
||||
|
||||
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2014, Inverse inc.
|
||||
Copyright (c) 2014-2015, Inverse inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -32,14 +32,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "SOGoActiveSyncConstants.h"
|
||||
|
||||
@class NSException;
|
||||
@class NSMutableDictionary;
|
||||
@class NSURL;
|
||||
|
||||
@interface SOGoActiveSyncDispatcher : NSObject
|
||||
{
|
||||
NSURL *folderTableURL;
|
||||
NSDictionary *imapFolderGUIDS;
|
||||
id context;
|
||||
BOOL debugOn;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) globalMetadataForDevice;
|
||||
|
||||
- (id) collectionFromId: (NSString *) theCollectionId
|
||||
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSLocale.h>
|
||||
#import <Foundation/NSProcessInfo.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
@@ -143,20 +144,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@implementation SOGoActiveSyncDispatcher
|
||||
|
||||
static BOOL debugOn = NO;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
[super init];
|
||||
|
||||
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] easDebugEnabled];
|
||||
folderTableURL = nil;
|
||||
imapFolderGUIDS = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(folderTableURL);
|
||||
RELEASE(imapFolderGUIDS);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -169,16 +170,16 @@ static BOOL debugOn = NO;
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
[[o properties] removeAllObjects];
|
||||
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]];
|
||||
[[o properties] setObject: theSyncKey
|
||||
forKey: @"FolderSyncKey"];
|
||||
[o save];
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) _globalMetadataForDevice
|
||||
- (NSMutableDictionary *) globalMetadataForDevice
|
||||
{
|
||||
SOGoCacheGCSObject *o;
|
||||
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil];
|
||||
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
@@ -202,7 +203,7 @@ static BOOL debugOn = NO;
|
||||
if (theFilter)
|
||||
{
|
||||
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
|
||||
[o setObjectType: ActiveSyncGlobalCacheObject];
|
||||
[o setObjectType: ActiveSyncFolderCacheObject];
|
||||
[o setTableUrl: [self folderTableURL]];
|
||||
[o reloadIfNeeded];
|
||||
|
||||
@@ -229,23 +230,27 @@ static BOOL debugOn = NO;
|
||||
SOGoMailAccounts *accountsFolder;
|
||||
SOGoMailAccount *accountFolder;
|
||||
SOGoUserFolder *userFolder;
|
||||
NSDictionary *imapGUIDs;
|
||||
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapGUIDs = [accountFolder imapFolderGUIDs];
|
||||
|
||||
//return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0];
|
||||
return [[[imapGUIDs allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
if (!imapFolderGUIDS)
|
||||
{
|
||||
userFolder = [[context activeUser] homeFolderInContext: context];
|
||||
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
|
||||
accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO];
|
||||
|
||||
// Get the GUID of the IMAP folder
|
||||
imapFolderGUIDS = [accountFolder imapFolderGUIDs];
|
||||
[imapFolderGUIDS retain];
|
||||
|
||||
}
|
||||
|
||||
return [[[imapFolderGUIDS allKeysForObject: [NSString stringWithFormat: @"folder%@", theIdToTranslate]] objectAtIndex: 0] substringFromIndex: 6] ;
|
||||
}
|
||||
|
||||
return theIdToTranslate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -710,7 +715,7 @@ static BOOL debugOn = NO;
|
||||
BOOL first_sync;
|
||||
|
||||
sm = [SoSecurityManager sharedSecurityManager];
|
||||
metadata = [self _globalMetadataForDevice];
|
||||
metadata = [self globalMetadataForDevice];
|
||||
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
s = [NSMutableString string];
|
||||
|
||||
@@ -938,6 +943,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
[o save];
|
||||
|
||||
command_count++;
|
||||
@@ -1023,6 +1029,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1046,6 +1053,7 @@ static BOOL debugOn = NO;
|
||||
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
||||
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
||||
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
||||
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
||||
}
|
||||
|
||||
[o save];
|
||||
@@ -1204,7 +1212,7 @@ static BOOL debugOn = NO;
|
||||
filter = [NSCalendarDate dateFromFilterType: [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"FilterType"] lastObject] textValue]];
|
||||
syncKey = [[(id)[[allCollections objectAtIndex: j] getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter];
|
||||
allMessages = [currentCollection syncTokenFieldsWithProperties: nil matchingSyncToken: syncKey fromDate: filter initialLoad: NO];
|
||||
|
||||
count = [allMessages count];
|
||||
|
||||
@@ -2024,13 +2032,13 @@ static BOOL debugOn = NO;
|
||||
|
||||
if ([foldersWithChanges count])
|
||||
{
|
||||
[self logWithFormat: @"Change detected, we push the content."];
|
||||
[self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
|
||||
status = 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval];
|
||||
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
|
||||
sleep(internalInterval);
|
||||
}
|
||||
}
|
||||
@@ -2619,6 +2627,11 @@ static BOOL debugOn = NO;
|
||||
[map setObject: [mailObject messageId] forKey: @"in-reply-to"];
|
||||
|
||||
references = [[[[[mailObject mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] mutableCopy] autorelease];
|
||||
|
||||
// If there is no References: header, initialize it with In-Reply-To.
|
||||
if ([mailObject inReplyTo] && ![references count])
|
||||
references = [NSMutableArray arrayWithObject: [mailObject inReplyTo]];
|
||||
|
||||
if ([references count] > 0)
|
||||
{
|
||||
// If there are more than ten identifiers listed, we eliminate the second one.
|
||||
@@ -2935,7 +2948,9 @@ static BOOL debugOn = NO;
|
||||
{
|
||||
NSString *value;
|
||||
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil];
|
||||
value = [[NSDate date] descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]
|
||||
locale: [[[NSLocale alloc] initWithLocaleIdentifier: @"en_US"] autorelease]];
|
||||
s = [NSString stringWithFormat: @"Date: %@\n%@", value, [theRequest contentAsString]];
|
||||
}
|
||||
else
|
||||
|
||||
@@ -541,7 +541,7 @@ struct GlobalObjectId {
|
||||
// We sanitize the content *ONLY* for Outlook clients and if the content-transfer-encoding is 8bit. Outlook has strange issues
|
||||
// with quoted-printable/base64 encoded text parts. It just doesn't decode them.
|
||||
encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"];
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"] || ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
||||
if (encoding && ([encoding caseInsensitiveCompare: @"8bit"] == NSOrderedSame))
|
||||
d = [self _sanitizedMIMEMessage];
|
||||
else
|
||||
d = [self content];
|
||||
@@ -1071,9 +1071,15 @@ struct GlobalObjectId {
|
||||
if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.0"] ||
|
||||
[[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"14.1"])
|
||||
{
|
||||
id value;
|
||||
NSString *reference;
|
||||
|
||||
reference = [[[[self mailHeaders] objectForKey: @"references"] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
value = [[self mailHeaders] objectForKey: @"references"];
|
||||
|
||||
if ([value isKindOfClass: [NSArray class]])
|
||||
reference = [[[value objectAtIndex: 0] componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
else
|
||||
reference = [[value componentsSeparatedByString: @" "] objectAtIndex: 0];
|
||||
|
||||
if ([reference length] > 0)
|
||||
[s appendFormat: @"<ConversationId xmlns=\"Email2:\">%@</ConversationId>", [[reference dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]];
|
||||
|
||||
@@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@interface SOGoSyncCacheObject : NSObject
|
||||
{
|
||||
@public
|
||||
id _uid;
|
||||
id _sequence;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Foundation/NSNull.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
static Class NSNullK;
|
||||
|
||||
@implementation SOGoSyncCacheObject
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSNullK = [NSNull class];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
@@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence;
|
||||
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [[self alloc] init];
|
||||
|
||||
[o setUID: theUID];
|
||||
[o setSequence: theSequence];
|
||||
|
||||
[o setUID: [NSNumber numberWithInt: [theUID intValue]]];
|
||||
[o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
|
||||
|
||||
return [o autorelease];
|
||||
}
|
||||
@@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (id) uid
|
||||
{
|
||||
return _uid;
|
||||
return [_uid description];
|
||||
}
|
||||
|
||||
- (void) setUID: (id) theUID
|
||||
@@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (id) sequence
|
||||
{
|
||||
return _sequence;
|
||||
return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
|
||||
}
|
||||
|
||||
- (void) setSequence: (id) theSequence
|
||||
@@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
return [[self uid] compare: [theSyncCacheObject uid]];
|
||||
return [self->_uid compare: theSyncCacheObject->_uid];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
|
||||
{
|
||||
if ([[self sequence] isEqual: [NSNull null]] &&
|
||||
[[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] &&
|
||||
[theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedDescending;
|
||||
|
||||
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]])
|
||||
if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
|
||||
return NSOrderedAscending;
|
||||
|
||||
// Must check this here, to avoid comparing NSNull objects
|
||||
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame)
|
||||
if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
|
||||
return [self compareUID: theSyncCacheObject];
|
||||
|
||||
return [[self sequence] compare: [theSyncCacheObject sequence]];
|
||||
return [self->_sequence compare: theSyncCacheObject->_sequence];
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
@@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -233,6 +233,7 @@ Installation
|
||||
This section will guide you through the installation of the native
|
||||
Microsoft Outlook compatibility layer SOGo offers.
|
||||
|
||||
////
|
||||
Red Hat Enterprise Linux v6 x86_64
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -316,7 +317,7 @@ from this guide.
|
||||
On Ubuntu 12.04, the Samba init scripts need to be modified to
|
||||
disable the upstart check. For more details, refer to:
|
||||
https://wiki.samba.org/index.php/Samba4/InitScript
|
||||
|
||||
////
|
||||
|
||||
Debian 8 (Jessie) and Ubuntu 14.04 (Trusty Tahr)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -395,7 +396,7 @@ samba-tool user setexpiry administrator --noexpiry
|
||||
----
|
||||
|
||||
You might consider changing the realm and domain used, to suit your
|
||||
enviroment.
|
||||
environment.
|
||||
|
||||
You might also have to
|
||||
remove `/etc/samba/smb.conf` prior running this command.
|
||||
@@ -441,7 +442,7 @@ Your Samba 4 configuration file should look like this:
|
||||
OpenChange Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
OpenChange 2.2 stores its metadata in MySQL so you need to have it installed.
|
||||
Since v2.2, OpenChange stores its metadata in MySQL so you need to have it installed.
|
||||
|
||||
First, create the OpenChange MySQL user:
|
||||
|
||||
@@ -512,9 +513,11 @@ mapistore:indexing_backend = mysql://openchange-user:openchange$123@localhost/op
|
||||
mapiproxy:openchangedb = mysql://openchange-user:openchange$123@localhost/openchange
|
||||
----
|
||||
|
||||
////
|
||||
On RHEL, make sure SELinux is disabled:
|
||||
|
||||
setenforce 0
|
||||
////
|
||||
|
||||
Next, you can start Samba using the usual command:
|
||||
|
||||
@@ -545,9 +548,11 @@ This service runs as a WSGI application under apache (mod_wsgi).
|
||||
While HTTPS is not required to access this service, it is strongly
|
||||
recommended.
|
||||
|
||||
////
|
||||
On RHEL-based distributions, the apache configuration required by these
|
||||
services can be found in `/etc/httpd/conf.d/ocsmanager.conf` and
|
||||
`/etc/httpd/conf.d/rpcproxy.conf`.
|
||||
////
|
||||
|
||||
For Debian-based distributions, these files can be found
|
||||
in `/etc/apache2/conf.d/` or `/etc/apache2/conf-available`.
|
||||
@@ -563,8 +568,10 @@ The OCS Manager and RPC Proxy configuration module can be enabled using:
|
||||
a2enconf ocsmanager
|
||||
a2enconf rpcproxy
|
||||
|
||||
////
|
||||
On RHEL-based distributions, make sure the `LoadModule` directive is
|
||||
uncommented in `/etc/httpd/conf.d/wsgi.conf`.
|
||||
////
|
||||
|
||||
The _reqtimeout_ apache module is known to cause problems when using the
|
||||
default configuration shipped with Debian-based systems. On such
|
||||
@@ -580,10 +587,12 @@ To avoid this problem, use a much higher timeout or disable the module:
|
||||
a2dismod reqtimeout
|
||||
|
||||
You should now restart the Apache service and make sure it will start on
|
||||
boot. On RHEL-based distributions, do:
|
||||
boot.
|
||||
////
|
||||
On RHEL-based distributions, do:
|
||||
|
||||
chkconfig httpd on && /etc/init.d/httpd restart
|
||||
|
||||
////
|
||||
On Debian-based distributions, do:
|
||||
|
||||
update-rc.d apache2 defaults && /etc/init.d/apache2 restart
|
||||
@@ -813,7 +822,7 @@ the `openchange_user_cleanup` script distributed with SOGo. The script
|
||||
can be found in `/usr/share/doc/sogo/` (`/usr/share/sogo-VERSION/` on
|
||||
RHEL).
|
||||
To reset a user, run the script as root:
|
||||
`python openchange_user_cleanup username`. See the usage output for additional options.
|
||||
`openchange_user_cleanup username`. See the usage output for additional options.
|
||||
* The "Out of Office Assistant" will not currently work. This feature
|
||||
has not been implemented.
|
||||
* Creating folders below INBOX (when not normally permitted by the IMAP
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
2.3.x (2015-MM-DD)
|
||||
2.3.3 (2015-mm-dd)
|
||||
------------------
|
||||
|
||||
New features
|
||||
|
||||
Enhancements
|
||||
- we no longer always entirely rewrite messages for Outlook 2013 when using EAS
|
||||
|
||||
Bug fixes
|
||||
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
|
||||
- correctly handle the References header over EAS (#3365)
|
||||
- make sure English is always used when generating Date headers using EAS (#3356)
|
||||
|
||||
2.3.2 (2015-09-16)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- improved EAS speed and memory usage, avoiding many IMAP LIST commands (#3294)
|
||||
- improved EAS speed during initial syncing of large mailboxes (#3293)
|
||||
- updated CKEditor to version 4.5.3
|
||||
|
||||
Bug fixes
|
||||
- fixed display of whitelisted attendees in Preferences window on Firefox (#3285)
|
||||
- Non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- Fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- Folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- Use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- non-latin subfolder names are displayed correctly on Outlook (Zentyal)
|
||||
- fixed several sync issues on environments with multiple users (Zentyal)
|
||||
- folders from other users will no longer appear on your Outlook (Zentyal)
|
||||
- use right auth in multidomain environments in contacts and calendar from Outlook (Zentyal)
|
||||
- session fix when SOGoEnableDomainBasedUID is enabled but logins are domain-less
|
||||
- less sync issues when setting read flag (Zentyal)
|
||||
- attachments with non-latin filenames sent by Outlook are now received (Zentyal)
|
||||
- support attachments from more mail clients (Zentyal)
|
||||
- avoid conflicting message on saving a draft mail (Zentyal)
|
||||
- less conflicting messages in Outlook while moving messages between folders (Zentyal)
|
||||
- start/end shifting by 1 hour due to timezone change on last Sunday of October 2015 (#3344)
|
||||
- fixed localization of calendar categories with empty profile (#3295)
|
||||
- fixed options availability in contextual menu of Contacts module (#3342)
|
||||
|
||||
2.3.1 (2015-07-23)
|
||||
------------------
|
||||
@@ -19,6 +46,7 @@ Enhancements
|
||||
- added create-folder subcommand to sogo-tool to create contact and calendar folders
|
||||
- group mail addresses can be used as recipient in Outlook
|
||||
- added 'ActiveSync' module constraints
|
||||
- updated CKEditor to version 4.5.1
|
||||
- added Slovenian translation - thanks to Jens Riecken
|
||||
- added Chinese (Taiwan) translation
|
||||
|
||||
|
||||
@@ -671,7 +671,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK;
|
||||
return newAttachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#import "MAPIStoreDBFolder.h"
|
||||
#import "MAPIStoreDBMessage.h"
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -104,6 +106,105 @@
|
||||
return objectVersion;
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList
|
||||
{
|
||||
BOOL updated;
|
||||
enum mapistore_error rc;
|
||||
NSData *currentChangeList, *changeKey;
|
||||
NSMutableArray *changeKeys;
|
||||
NSMutableData *newChangeList;
|
||||
NSUInteger count, len;
|
||||
struct SizedXid *changes;
|
||||
struct SPropValue property;
|
||||
struct SRow aRow;
|
||||
struct XID *currentChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t nChanges;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: [self objectVersion]];
|
||||
|
||||
currentChangeList = [properties objectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
if (!currentChangeList)
|
||||
{
|
||||
/* Create a new PredecessorChangeList */
|
||||
len = [changeKey length];
|
||||
newChangeList = [NSMutableData dataWithCapacity: len + 1];
|
||||
[newChangeList appendUInt8: len];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update current predecessor change list with new change key */
|
||||
changes = [currentChangeList asSizedXidArrayInMemCtx: localMemCtx
|
||||
with: &nChanges];
|
||||
|
||||
updated = NO;
|
||||
currentChangeKey = [changeKey asXIDInMemCtx: localMemCtx];
|
||||
for (count = 0; count < nChanges && !updated; count++)
|
||||
{
|
||||
if (GUID_equal(&changes[count].XID.NameSpaceGuid, ¤tChangeKey->NameSpaceGuid))
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
oldGlobCnt = [NSData dataWithBytes: changes[count].XID.LocalId.data length: changes[count].XID.LocalId.length];
|
||||
globCnt = [NSData dataWithBytes: currentChangeKey->LocalId.data length: currentChangeKey->LocalId.length];
|
||||
if ([globCnt compare: oldGlobCnt] == NSOrderedDescending)
|
||||
{
|
||||
if ([globCnt length] != [oldGlobCnt length])
|
||||
{
|
||||
[self errorWithFormat: @"Cannot compare globcnt with different length: %@ and %@", globCnt, oldGlobCnt];
|
||||
abort();
|
||||
}
|
||||
memcpy (changes[count].XID.LocalId.data, currentChangeKey->LocalId.data, currentChangeKey->LocalId.length);
|
||||
updated = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialise it */
|
||||
changeKeys = [NSMutableArray array];
|
||||
|
||||
if (!updated)
|
||||
[changeKeys addObject: changeKey];
|
||||
|
||||
for (count = 0; count < nChanges; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithXID: &changes[count].XID];
|
||||
[changeKeys addObject: changeKey];
|
||||
}
|
||||
|
||||
[changeKeys sortUsingFunction: MAPIChangeKeyGUIDCompare context: localMemCtx];
|
||||
|
||||
newChangeList = [NSMutableData data];
|
||||
len = [changeKeys count];
|
||||
for (count = 0; count < len; count++)
|
||||
{
|
||||
changeKey = [changeKeys objectAtIndex: count];
|
||||
[newChangeList appendUInt8: [changeKey length]];
|
||||
[newChangeList appendData: changeKey];
|
||||
}
|
||||
}
|
||||
|
||||
if ([newChangeList length] > 0)
|
||||
{
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *[newChangeList asBinaryInMemCtx: localMemCtx];
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [self addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
[self errorWithFormat: @"Impossible to add a new predecessor change list: %d", rc];
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: how this can happen?
|
||||
//
|
||||
@@ -166,6 +267,9 @@
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: newVersion]
|
||||
forKey: @"version"];
|
||||
|
||||
/* Update PredecessorChangeList accordingly */
|
||||
[self _updatePredecessorChangeList];
|
||||
|
||||
[self logWithFormat: @"%d props in dict", [properties count]];
|
||||
|
||||
[sogoObject save];
|
||||
@@ -209,4 +313,36 @@
|
||||
return [sogoObject lastModified];
|
||||
}
|
||||
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
/* Modify PidTagMessageFlags from SetMessageReadFlag and
|
||||
SyncImportReadStateChanges ROPs */
|
||||
NSNumber *flags;
|
||||
uint32_t newFlag;
|
||||
|
||||
flags = [properties objectForKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
if (flags)
|
||||
{
|
||||
newFlag = [flags unsignedLongValue];
|
||||
if (flag & SUPPRESS_RECEIPT)
|
||||
newFlag |= MSGFLAG_READ;
|
||||
if (flag & CLEAR_RN_PENDING)
|
||||
newFlag &= ~MSGFLAG_RN_PENDING;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag &= ~MSGFLAG_READ;
|
||||
if (flag & CLEAR_NRN_PENDING)
|
||||
newFlag &= ~MSGFLAG_NRN_PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFlag = MSGFLAG_READ;
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
newFlag = 0x0;
|
||||
}
|
||||
[properties setObject: [NSNumber numberWithUnsignedLong: newFlag]
|
||||
forKey: MAPIPropertyKey (PR_MESSAGE_FLAGS)];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -121,6 +121,7 @@
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) want_copy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx;
|
||||
|
||||
|
||||
@@ -642,6 +642,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMID: (uint64_t) targetMid
|
||||
andChangeKey: (struct Binary_r *) targetChangeKey
|
||||
andPredecessorChangeList: (struct Binary_r *) targetPredecessorChangeList
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -669,15 +670,18 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
|
||||
[sourceMsg copyToMessage: destMsg inMemCtx: memCtx];
|
||||
|
||||
if (targetChangeKey)
|
||||
if (targetPredecessorChangeList)
|
||||
{
|
||||
property.ulPropTag = PidTagChangeKey;
|
||||
property.value.bin = *targetChangeKey;
|
||||
property.ulPropTag = PidTagPredecessorChangeList;
|
||||
property.value.bin = *targetPredecessorChangeList;
|
||||
aRow.cValues = 1;
|
||||
aRow.lpProps = &property;
|
||||
rc = [destMsg addPropertiesFromRow: &aRow];
|
||||
if (rc != MAPISTORE_SUCCESS)
|
||||
goto end;
|
||||
{
|
||||
[self errorWithFormat: @"Cannot add PredecessorChangeList on move"];
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
[destMsg save: memCtx];
|
||||
if (!wantCopy)
|
||||
@@ -696,6 +700,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
@@ -705,7 +710,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
NSString *oldMessageURL;
|
||||
MAPIStoreMapping *mapping;
|
||||
SOGoUser *ownerUser;
|
||||
struct Binary_r *targetChangeKey;
|
||||
struct Binary_r *targetChangeKey, *targetPredecessorChangeList;
|
||||
//TALLOC_CTX *memCtx;
|
||||
|
||||
//memCtx = talloc_zero (NULL, TALLOC_CTX);
|
||||
@@ -726,14 +731,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe
|
||||
if (oldMessageURL)
|
||||
{
|
||||
[oldMessageURLs addObject: oldMessageURL];
|
||||
if (targetChangeKeys)
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
if (targetChangeKeys && targetPredecessorChangeList)
|
||||
{
|
||||
targetChangeKey = targetChangeKeys[count];
|
||||
targetPredecessorChangeList = targetPredecessorChangeLists[count];
|
||||
}
|
||||
else
|
||||
targetChangeKey = NULL;
|
||||
{
|
||||
targetChangeKey = NULL;
|
||||
targetPredecessorChangeList = NULL;
|
||||
}
|
||||
rc = [self _moveCopyMessageWithMID: srcMids[count]
|
||||
fromFolder: sourceFolder
|
||||
withMID: targetMids[count]
|
||||
andChangeKey: targetChangeKey
|
||||
andPredecessorChangeList: targetPredecessorChangeList
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
/* synchronisation */
|
||||
- (BOOL) synchroniseCache;
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey;
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl;
|
||||
- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSString *) changeNumber;
|
||||
- (NSString *) changeNumberForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
@@ -75,6 +75,7 @@ static Class NSNumberK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
@@ -261,7 +262,6 @@ static Class NSNumberK;
|
||||
*/
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
inChangeListOnly: (BOOL) inChangeListOnly
|
||||
{
|
||||
struct XID *xid;
|
||||
NSString *guid;
|
||||
@@ -270,19 +270,15 @@ static Class NSNumberK;
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
if (!inChangeListOnly)
|
||||
{
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
}
|
||||
/* 1. set change key association */
|
||||
changeKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil];
|
||||
[messageEntry setObject: changeKeyDict forKey: @"ChangeKey"];
|
||||
|
||||
/* 2. append/update predecessor change list */
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
@@ -296,6 +292,77 @@ static Class NSNumberK;
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeList: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
withOldChangeKey: (NSData *) oldChangeKey
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *changeKeyDict;
|
||||
NSString *guid;
|
||||
NSMutableDictionary *changeList;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid, *givenChangeKey;
|
||||
TALLOC_CTX *localMemCtx;
|
||||
uint32_t i, length;
|
||||
|
||||
localMemCtx = talloc_new (NULL);
|
||||
if (!localMemCtx)
|
||||
{
|
||||
[self errorWithFormat: @"No more memory"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (predecessorChangeList)
|
||||
{
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: localMemCtx with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldChangeKey)
|
||||
{
|
||||
givenChangeKey = [oldChangeKey asXIDInMemCtx: localMemCtx];
|
||||
if (givenChangeKey) {
|
||||
guid = [NSString stringWithGUID: &givenChangeKey->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: givenChangeKey->LocalId.data length: givenChangeKey->LocalId.length];
|
||||
|
||||
changeKeyDict = [messageEntry objectForKey: @"ChangeKey"];
|
||||
if (!changeKeyDict ||
|
||||
([guid isEqualToString: [changeKeyDict objectForKey: @"GUID"]]
|
||||
&& ([globCnt compare: [changeKeyDict objectForKey: @"LocalId"]] == NSOrderedDescending)))
|
||||
{
|
||||
/* The given change key is greater than current one stored in
|
||||
metadata or it does not exist */
|
||||
[messageEntry setObject: [NSDictionary dictionaryWithObjectsAndKeys: guid, @"GUID",
|
||||
globCnt, @"LocalId",
|
||||
nil]
|
||||
forKey: @"ChangeKey"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free (localMemCtx);
|
||||
}
|
||||
|
||||
- (EOQualifier *) componentQualifier
|
||||
{
|
||||
if (!componentQualifier)
|
||||
@@ -465,8 +532,7 @@ static Class NSNumberK;
|
||||
// A GLOBCNT structure is a 6-byte global namespace counter,
|
||||
// we strip the first 2 bytes. The first two bytes is the ReplicaId
|
||||
changeKey = [self getReplicaKeyFromGlobCnt: newChangeNum >> 16];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: NO];
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
}
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
@@ -483,12 +549,13 @@ static Class NSNumberK;
|
||||
}
|
||||
|
||||
- (void) updateVersionsForMessageWithKey: (NSString *) messageKey
|
||||
withChangeKey: (NSData *) newChangeKey
|
||||
withChangeKey: (NSData *) oldChangeKey
|
||||
andPredecessorChangeList: (NSData *) pcl
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
|
||||
[self synchroniseCache];
|
||||
if (newChangeKey)
|
||||
if (oldChangeKey || pcl)
|
||||
{
|
||||
messages = [[versionsMessage properties] objectForKey: @"Messages"];
|
||||
messageEntry = [messages objectForKey: messageKey];
|
||||
@@ -496,8 +563,8 @@ static Class NSNumberK;
|
||||
[NSException raise: @"MAPIStoreIOException"
|
||||
format: @"no version record found for message '%@'",
|
||||
messageKey];
|
||||
[self _setChangeKey: newChangeKey forMessageEntry: messageEntry
|
||||
inChangeListOnly: YES];
|
||||
[self _updatePredecessorChangeList: pcl forMessageEntry: messageEntry
|
||||
withOldChangeKey: oldChangeKey];
|
||||
[versionsMessage save];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +209,16 @@
|
||||
|
||||
- (void) updateVersions
|
||||
{
|
||||
NSData *newChangeKey;
|
||||
/* Update ChangeKey and PredecessorChangeList on message's save */
|
||||
NSData *newChangeKey, *predecessorChangeList;
|
||||
|
||||
newChangeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
predecessorChangeList = [properties objectForKey: MAPIPropertyKey (PR_PREDECESSOR_CHANGE_LIST)];
|
||||
|
||||
[(MAPIStoreGCSFolder *) container
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey];
|
||||
updateVersionsForMessageWithKey: [self nameInContainer]
|
||||
withChangeKey: newChangeKey
|
||||
andPredecessorChangeList: predecessorChangeList];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "MAPIStoreMailMessage.h"
|
||||
@@ -108,7 +109,7 @@
|
||||
static char recordBytes[] = {0xd9, 0xd8, 0x11, 0xa3, 0xe2, 0x90, 0x18, 0x41,
|
||||
0x9e, 0x04, 0x58, 0x46, 0x9d, 0x6d, 0x1b,
|
||||
0x68};
|
||||
|
||||
|
||||
*data = [[NSData dataWithBytes: recordBytes length: 16]
|
||||
asBinaryInMemCtx: memCtx];
|
||||
|
||||
@@ -117,19 +118,7 @@
|
||||
|
||||
- (NSString *) _fileName
|
||||
{
|
||||
NSString *fileName;
|
||||
NSDictionary *parameters;
|
||||
|
||||
fileName = [[bodyInfo objectForKey: @"parameterList"]
|
||||
objectForKey: @"name"];
|
||||
if (!fileName)
|
||||
{
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
fileName = [parameters objectForKey: @"filename"];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return [bodyInfo filename];
|
||||
}
|
||||
|
||||
- (int) getPidTagAttachLongFilename: (void **) data
|
||||
@@ -178,7 +167,7 @@
|
||||
|
||||
- (int) getPidTagAttachContentId: (void **) data
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
{
|
||||
*data = [[bodyInfo objectForKey: @"bodyId"]
|
||||
asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
- (NSString *) changeNumberForMessageUID: (NSString *) messageUid;
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey;
|
||||
- (NSData *) predecessorChangeListForMessageWithKey: (NSString *) messageKey;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
|
||||
static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
|
||||
#include <gen_ndr/exchange.h>
|
||||
|
||||
#undef DEBUG
|
||||
#include <util/attr.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
@@ -109,6 +111,7 @@ static Class SOGoMailFolderK, MAPIStoreMailFolderK, MAPIStoreOutboxFolderK;
|
||||
[SOGoMAPIDBMessage objectWithName: @"versions.plist"
|
||||
inContainer: dbFolder]);
|
||||
[versionsMessage setObjectType: MAPIInternalCacheObject];
|
||||
[versionsMessage reloadIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL) ensureFolderExists
|
||||
@@ -516,6 +519,44 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return [modseq1 compare: modseq2];
|
||||
}
|
||||
|
||||
- (void) _updatePredecessorChangeListWith: (NSData *) predecessorChangeList
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct SizedXid *sizedXIDList;
|
||||
struct XID xid;
|
||||
uint32_t i, length;
|
||||
|
||||
sizedXIDList = [predecessorChangeList asSizedXidArrayInMemCtx: NULL with: &length];
|
||||
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (!changeList)
|
||||
{
|
||||
changeList = [NSMutableDictionary new];
|
||||
[messageEntry setObject: changeList
|
||||
forKey: @"PredecessorChangeList"];
|
||||
[changeList release];
|
||||
}
|
||||
|
||||
if (sizedXIDList) {
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
xid = sizedXIDList[i].XID;
|
||||
guid = [NSString stringWithGUID: &xid.NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid.LocalId.data length: xid.LocalId.length];
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
}
|
||||
|
||||
talloc_free (sizedXIDList);
|
||||
}
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (void) _setChangeKey: (NSData *) changeKey
|
||||
forMessageEntry: (NSMutableDictionary *) messageEntry
|
||||
{
|
||||
@@ -526,8 +567,8 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
NSMutableDictionary *changeList;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->GUID];
|
||||
globCnt = [NSData dataWithBytes: xid->Data length: xid->Size];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
/* 1. set change key association */
|
||||
@@ -924,8 +965,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
return changeNumber;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
- (NSMutableDictionary *) _messageEntryFromMessageKey: (NSString *) messageKey
|
||||
{
|
||||
NSMutableDictionary *messages, *messageEntry;
|
||||
NSString *messageUid;
|
||||
@@ -936,7 +976,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
messageEntry = [messages objectForKey: messageUid];
|
||||
if (!messageEntry)
|
||||
{
|
||||
[self warnWithFormat: @"attempting to synchronise to set the change key for "
|
||||
[self warnWithFormat: @"attempting to synchronise to get the message entry for "
|
||||
@"this message %@", messageKey];
|
||||
synced = [self synchroniseCacheForUID: messageUid];
|
||||
if (synced)
|
||||
@@ -947,11 +987,57 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
[self _setChangeKey: changeKey forMessageEntry: messageEntry];
|
||||
|
||||
return messageEntry;
|
||||
}
|
||||
|
||||
- (void) setChangeKey: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
[self _setChangeKey: changeKey
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
|
||||
[versionsMessage save];
|
||||
}
|
||||
|
||||
- (BOOL) updatePredecessorChangeListWith: (NSData *) changeKey
|
||||
forMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
/* Update predecessor change list property given the change key. It
|
||||
returns if the change key has been added to the list or not */
|
||||
BOOL added = NO;
|
||||
NSData *globCnt, *oldGlobCnt;
|
||||
NSDictionary *messageEntry;
|
||||
NSMutableDictionary *changeList;
|
||||
NSString *guid;
|
||||
struct XID *xid;
|
||||
|
||||
xid = [changeKey asXIDInMemCtx: NULL];
|
||||
guid = [NSString stringWithGUID: &xid->NameSpaceGuid];
|
||||
globCnt = [NSData dataWithBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
talloc_free (xid);
|
||||
|
||||
messageEntry = [self _messageEntryFromMessageKey: messageKey];
|
||||
if (messageEntry)
|
||||
{
|
||||
changeList = [messageEntry objectForKey: @"PredecessorChangeList"];
|
||||
if (changeList)
|
||||
{
|
||||
oldGlobCnt = [changeList objectForKey: guid];
|
||||
if (!oldGlobCnt || ([globCnt compare: oldGlobCnt] == NSOrderedDescending))
|
||||
{
|
||||
[changeList setObject: globCnt forKey: guid];
|
||||
[versionsMessage save];
|
||||
added = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Missing predecessor change list to update"];
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
- (NSData *) changeKeyForMessageWithKey: (NSString *) messageKey
|
||||
{
|
||||
NSDictionary *messages, *changeKeyDict;
|
||||
@@ -1217,6 +1303,7 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
fromFolder: (MAPIStoreFolder *) sourceFolder
|
||||
withMIDs: (uint64_t *) targetMids
|
||||
andChangeKeys: (struct Binary_r **) targetChangeKeys
|
||||
andPredecessorChangeLists: (struct Binary_r **) targetPredecessorChangeLists
|
||||
wantCopy: (uint8_t) wantCopy
|
||||
inMemCtx: (TALLOC_CTX *) memCtx
|
||||
|
||||
@@ -1231,12 +1318,13 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
NSDictionary *result;
|
||||
NSUInteger count;
|
||||
NSArray *a;
|
||||
NSData *changeKey;
|
||||
NSData *changeList;
|
||||
|
||||
if (![sourceFolder isKindOfClass: [MAPIStoreMailFolder class]])
|
||||
return [super moveCopyMessagesWithMIDs: srcMids andCount: midCount
|
||||
fromFolder: sourceFolder withMIDs: targetMids
|
||||
andChangeKeys: targetChangeKeys
|
||||
andPredecessorChangeLists: targetPredecessorChangeLists
|
||||
wantCopy: wantCopy
|
||||
inMemCtx: memCtx];
|
||||
|
||||
@@ -1325,11 +1413,11 @@ _parseCOPYUID (NSString *line, NSArray **destUIDsP)
|
||||
[self synchroniseCache];
|
||||
for (count = 0; count < midCount; count++)
|
||||
{
|
||||
changeKey = [NSData dataWithBinary: targetChangeKeys[count]];
|
||||
changeList = [NSData dataWithBinary: targetPredecessorChangeLists[count]];
|
||||
messageKey = [NSString stringWithFormat: @"%@.eml",
|
||||
[destUIDs objectAtIndex: count]];
|
||||
[self setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
[self _updatePredecessorChangeListWith: changeList
|
||||
forMessageEntry: [self _messageEntryFromMessageKey: messageKey]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#import <Mailer/NSData+Mail.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <Mailer/NSDictionary+Mail.h>
|
||||
|
||||
#import "Codepages.h"
|
||||
#import "NSData+MAPIStore.h"
|
||||
@@ -196,7 +197,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
count1 = [keys indexOfObject: data1];
|
||||
data2 = [entry2 objectForKey: @"mimeType"];
|
||||
count2 = [keys indexOfObject: data2];
|
||||
|
||||
|
||||
if (count1 == count2)
|
||||
{
|
||||
data1 = [entry1 objectForKey: @"key"];
|
||||
@@ -529,7 +530,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
else
|
||||
stringValue = @"";
|
||||
*data = [stringValue asUnicodeInMemCtx: memCtx];
|
||||
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -624,7 +625,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v = 0;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
|
||||
@@ -636,7 +637,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([[self attachmentKeys]
|
||||
count] > 0)
|
||||
v |= MSGFLAG_HASATTACH;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -656,7 +657,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
v = 2;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -668,15 +669,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
NSDictionary *coreInfos;
|
||||
NSArray *flags;
|
||||
unsigned int v;
|
||||
|
||||
|
||||
coreInfos = [sogoObject fetchCoreInfos];
|
||||
|
||||
|
||||
flags = [coreInfos objectForKey: @"flags"];
|
||||
if ([flags containsObject: @"flagged"])
|
||||
v = 6;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -755,7 +756,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
if ([ngAddress isKindOfClass: [NGMailAddress class]])
|
||||
{
|
||||
cn = [ngAddress displayName];
|
||||
|
||||
|
||||
// If we don't have a displayName, we use the email address instead. This
|
||||
// avoid bug #2119 - where Outlook won't display anything in the "From" field,
|
||||
// nor in the recipient field if we reply to the email.
|
||||
@@ -809,7 +810,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
entryId = MAPIStoreExternalEntryId (cn, email);
|
||||
|
||||
*data = [entryId asBinaryInMemCtx: memCtx];
|
||||
|
||||
|
||||
rc = MAPISTORE_SUCCESS;
|
||||
}
|
||||
else
|
||||
@@ -966,15 +967,15 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
{
|
||||
uint32_t v;
|
||||
NSString *s;
|
||||
|
||||
|
||||
s = [[sogoObject mailHeaders] objectForKey: @"x-priority"];
|
||||
v = 0x1;
|
||||
|
||||
|
||||
if ([s hasPrefix: @"1"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"2"]) v = 0x2;
|
||||
else if ([s hasPrefix: @"4"]) v = 0x0;
|
||||
else if ([s hasPrefix: @"5"]) v = 0x0;
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, v);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1163,14 +1164,14 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
|
||||
if (!headerSetup)
|
||||
[self _fetchHeaderData];
|
||||
|
||||
|
||||
if ([headerMimeType isEqualToString: @"text/plain"])
|
||||
format = EDITOR_FORMAT_PLAINTEXT;
|
||||
else if ([headerMimeType isEqualToString: @"text/html"])
|
||||
format = EDITOR_FORMAT_HTML;
|
||||
else
|
||||
format = 0; /* EDITOR_FORMAT_DONTKNOW */
|
||||
|
||||
|
||||
*data = MAPILongValue (memCtx, format);
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
@@ -1517,7 +1518,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
p = 0;
|
||||
recipient->data = talloc_array (msgData, void *, msgData->columns->cValues);
|
||||
memset (recipient->data, 0, msgData->columns->cValues * sizeof (void *));
|
||||
|
||||
|
||||
// PR_OBJECT_TYPE = MAPI_MAILUSER (see MAPI_OBJTYPE)
|
||||
recipient->data[p] = MAPILongValue (msgData, MAPI_MAILUSER);
|
||||
p++;
|
||||
@@ -1525,7 +1526,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_DISPLAY_TYPE = DT_MAILUSER (see MS-NSPI)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0);
|
||||
p++;
|
||||
|
||||
|
||||
// PR_7BIT_DISPLAY_NAME_UNICODE
|
||||
recipient->data[p] = [cn asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
@@ -1533,7 +1534,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
// PR_SMTP_ADDRESS_UNICODE
|
||||
recipient->data[p] = [email asUnicodeInMemCtx: msgData];
|
||||
p++;
|
||||
|
||||
|
||||
// PR_SEND_INTERNET_ENCODING = 0x00060000 (plain text, see OXCMAIL)
|
||||
recipient->data[p] = MAPILongValue (msgData, 0x00060000);
|
||||
p++;
|
||||
@@ -1566,12 +1567,9 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
withPrefix: (NSString *) keyPrefix
|
||||
{
|
||||
NSArray *parts;
|
||||
NSDictionary *parameters;
|
||||
NSUInteger count, max;
|
||||
|
||||
parameters = [[bodyInfo objectForKey: @"disposition"]
|
||||
objectForKey: @"parameterList"];
|
||||
if ([[parameters objectForKey: @"filename"] length] > 0)
|
||||
if ([[bodyInfo filename] length] > 0)
|
||||
{
|
||||
if ([keyPrefix length] == 0)
|
||||
keyPrefix = @"0";
|
||||
@@ -1645,15 +1643,29 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
BOOL modified = NO;
|
||||
BOOL alreadyRead = NO;
|
||||
NSString *imapFlag = @"\\Seen";
|
||||
|
||||
alreadyRead = [[[sogoObject fetchCoreInfos] objectForKey: @"flags"]
|
||||
containsObject: @"seen"];
|
||||
|
||||
/* TODO: notifications should probably be emitted from here */
|
||||
if (flag & CLEAR_READ_FLAG)
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
{
|
||||
[sogoObject removeFlags: imapFlag];
|
||||
modified = alreadyRead;
|
||||
}
|
||||
else
|
||||
[sogoObject addFlags: imapFlag];
|
||||
{
|
||||
[sogoObject addFlags: imapFlag];
|
||||
modified = !alreadyRead;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
[(MAPIStoreMailFolder *)[self container] synchroniseCache];
|
||||
|
||||
return MAPISTORE_SUCCESS;
|
||||
}
|
||||
@@ -1694,7 +1706,7 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data)
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSNumber *value;
|
||||
|
||||
|
||||
@@ -161,15 +161,30 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK;
|
||||
//[self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]];
|
||||
//[self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]];
|
||||
if (modseq)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
{
|
||||
if (res->relop == RELOP_GT)
|
||||
modseq = [NSNumber numberWithUnsignedLongLong:
|
||||
[modseq unsignedLongLongValue] + 1];
|
||||
|
||||
}
|
||||
else
|
||||
modseq = [NSNumber numberWithUnsignedLongLong: 0];
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
|
||||
if (res->relop == RELOP_GT || res->relop == RELOP_GE)
|
||||
{
|
||||
*qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ"
|
||||
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
|
||||
value: modseq];
|
||||
[*qualifier autorelease];
|
||||
rc = MAPIRestrictionStateNeedsEval;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Ignore other operations as IMAP only support MODSEQ >= X */
|
||||
[self warnWithFormat: @"Ignoring %@ as only supported operators are > and >=",
|
||||
[self operatorFromRestrictionOperator: res->relop]];
|
||||
rc = MAPIRestrictionStateAlwaysTrue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSObject+Values.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
@@ -285,7 +286,7 @@ static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" };
|
||||
version = [properties objectForKey: @"version"];
|
||||
|
||||
return (version
|
||||
? exchange_globcnt ([version unsignedLongLongValue])
|
||||
? [version unsignedLongLongValue]
|
||||
: ULLONG_MAX);
|
||||
}
|
||||
|
||||
@@ -1110,7 +1111,8 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
|
||||
- (void) save: (TALLOC_CTX *) memCtx
|
||||
{
|
||||
NSString *folderName, *flag, *newIdString, *messageKey;
|
||||
BOOL updatedMetadata;
|
||||
NSString *folderName, *flag, *newIdString, *messageKey, *changeNumber;
|
||||
NSData *changeKey, *messageData;
|
||||
NGImap4Connection *connection;
|
||||
NGImap4Client *client;
|
||||
@@ -1146,21 +1148,33 @@ MakeMessageBody (NSDictionary *mailProperties, NSDictionary *attachmentParts, NS
|
||||
[sogoObject setNameInContainer: messageKey];
|
||||
[mapping registerURL: [self url] withID: mid];
|
||||
|
||||
/* synchronise the cache and update the change key with the one provided
|
||||
by the client. Before doing this, lets issue a unselect/select combo
|
||||
because of timing issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if we do
|
||||
a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)" command (where
|
||||
XYZ is the HIGHESTMODSEQ+1) immediately after IMAP APPEND */
|
||||
/* synchronise the cache and update the predecessor change list
|
||||
with the change key provided by the client. Before doing
|
||||
this, lets issue a unselect/select combo because of timing
|
||||
issues with Dovecot in obtaining the latest modseq.
|
||||
Sometimes, Dovecot doesn't return the newly appended UID if
|
||||
we do a "UID SORT (DATE) UTF-8 (MODSEQ XYZ) (NOT DELETED)"
|
||||
command (where XYZ is the HIGHESTMODSEQ+1) immediately after
|
||||
IMAP APPEND */
|
||||
[client unselect];
|
||||
[client select: folderName];
|
||||
|
||||
[(MAPIStoreMailFolder *) container synchroniseCache];
|
||||
changeKey = [properties objectForKey: MAPIPropertyKey (PR_CHANGE_KEY)];
|
||||
if (changeKey)
|
||||
[(MAPIStoreMailFolder *) container
|
||||
setChangeKey: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
{
|
||||
updatedMetadata = [(MAPIStoreMailFolder *) container updatePredecessorChangeListWith: changeKey
|
||||
forMessageWithKey: messageKey];
|
||||
if (!updatedMetadata)
|
||||
[self warnWithFormat: @"Predecessor change list not updated with client data"];
|
||||
}
|
||||
|
||||
/* Update version property (PR_CHANGE_KEY indeed) as it is
|
||||
requested once it is saved */
|
||||
changeNumber = [(MAPIStoreMailFolder *) container changeNumberForMessageUID: newIdString];
|
||||
if (changeNumber)
|
||||
[properties setObject: [NSNumber numberWithUnsignedLongLong: [changeNumber unsignedLongLongValue] >> 16]
|
||||
forKey: @"version"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
withAID: (uint32_t) aid;
|
||||
- (int) getAttachmentTable: (MAPIStoreAttachmentTable **) tablePtr
|
||||
andRowCount: (uint32_t *) countPtr;
|
||||
- (int) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag;
|
||||
- (enum mapistore_error) saveMessage: (TALLOC_CTX *) memCtx;
|
||||
|
||||
- (NSArray *) activeContainerMessageTables;
|
||||
|
||||
@@ -549,11 +549,12 @@ rtf2html (NSData *compressedRTF)
|
||||
}
|
||||
|
||||
[self save: memCtx];
|
||||
/* We make sure that any change-related properties are removes from the
|
||||
/* We make sure that any change-related properties are removed from the
|
||||
properties dictionary, to make sure that related methods will be
|
||||
invoked the next time they are requested. */
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeKey)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagChangeNumber)];
|
||||
[properties removeObjectForKey: MAPIPropertyKey (PidTagPredecessorChangeList)];
|
||||
|
||||
if ([container isKindOfClass: MAPIStoreFolderK])
|
||||
{
|
||||
@@ -918,7 +919,7 @@ rtf2html (NSData *compressedRTF)
|
||||
return [self getNo: data inMemCtx: memCtx];;
|
||||
}
|
||||
|
||||
- (int) setReadFlag: (uint8_t) flag
|
||||
- (enum mapistore_error) setReadFlag: (uint8_t) flag
|
||||
{
|
||||
// [self subclassResponsibility: _cmd];
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
uint32_t mid_count,
|
||||
uint64_t *src_mids, uint64_t *t_mids,
|
||||
struct Binary_r **target_change_keys,
|
||||
struct Binary_r **target_predecessor_change_lists,
|
||||
uint8_t want_copy)
|
||||
{
|
||||
MAPIStoreFolder *sourceFolder, *targetFolder;
|
||||
@@ -698,6 +699,7 @@ sogo_folder_move_copy_messages(void *folder_object,
|
||||
fromFolder: sourceFolder
|
||||
withMIDs: t_mids
|
||||
andChangeKeys: target_change_keys
|
||||
andPredecessorChangeLists: target_predecessor_change_lists
|
||||
wantCopy: want_copy
|
||||
inMemCtx: mem_ctx];
|
||||
TRYCATCH_END(pool)
|
||||
@@ -1118,7 +1120,7 @@ sogo_message_set_read_flag (void *message_object, uint8_t flag)
|
||||
struct MAPIStoreTallocWrapper *wrapper;
|
||||
NSAutoreleasePool *pool;
|
||||
MAPIStoreMessage *message;
|
||||
int rc;
|
||||
enum mapistore_error rc;
|
||||
|
||||
if (message_object)
|
||||
{
|
||||
|
||||
@@ -531,7 +531,7 @@
|
||||
}
|
||||
[vToDo setTimeStampAsDate: now];
|
||||
|
||||
[sogoObject saveComponent: vCalendar];
|
||||
[sogoObject saveCalendar: vCalendar];
|
||||
|
||||
[self updateVersions];
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
|
||||
+ (id) dataWithXID: (const struct XID *) xid;
|
||||
- (struct XID *) asXIDInMemCtx: (void *) memCtx;
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length;
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "MAPIStoreTypes.h"
|
||||
#import "NSObject+MAPIStore.h"
|
||||
#import "NSString+MAPIStore.h"
|
||||
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#undef DEBUG
|
||||
#include <stdbool.h>
|
||||
#include <libmapi/libmapi.h>
|
||||
#include <talloc.h>
|
||||
#include <util/time.h>
|
||||
#include <gen_ndr/exchange.h>
|
||||
@@ -136,11 +138,11 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
NSMutableData *xidData;
|
||||
struct FlatUID_r flatUID;
|
||||
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->GUID);
|
||||
_fillFlatUIDWithGUID (&flatUID, &xid->NameSpaceGuid);
|
||||
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->Size];
|
||||
xidData = [NSMutableData dataWithCapacity: 16 + xid->LocalId.length];
|
||||
[xidData appendBytes: flatUID.ab length: 16];
|
||||
[xidData appendBytes: xid->Data length: xid->Size];
|
||||
[xidData appendBytes: xid->LocalId.data length: xid->LocalId.length];
|
||||
|
||||
return xidData;
|
||||
}
|
||||
@@ -156,12 +158,12 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
{
|
||||
xid = talloc_zero (memCtx, struct XID);
|
||||
|
||||
[self _extractGUID: &xid->GUID];
|
||||
[self _extractGUID: &xid->NameSpaceGuid];
|
||||
|
||||
xid->Size = max - 16;
|
||||
xid->LocalId.length = max - 16;
|
||||
|
||||
bytes = (uint8_t *) [self bytes];
|
||||
xid->Data = talloc_memdup (xid, (bytes+16), xid->Size);
|
||||
xid->LocalId.data = talloc_memdup (xid, (bytes+16), xid->LocalId.length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -172,6 +174,38 @@ static void _fillFlatUIDWithGUID (struct FlatUID_r *flatUID, const struct GUID *
|
||||
return xid;
|
||||
}
|
||||
|
||||
- (struct SizedXid *) asSizedXidArrayInMemCtx: (void *) memCtx
|
||||
with: (uint32_t *) length
|
||||
{
|
||||
struct Binary_r bin;
|
||||
struct SizedXid *sizedXIDArray;
|
||||
|
||||
bin.cb = [self length];
|
||||
bin.lpb = (uint8_t *)[self bytes];
|
||||
|
||||
sizedXIDArray = get_SizedXidArray(memCtx, &bin, length);
|
||||
if (!sizedXIDArray)
|
||||
{
|
||||
NSLog (@"Impossible to parse SizedXID array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sizedXIDArray;
|
||||
}
|
||||
|
||||
- (NSComparisonResult) compare: (NSData *) otherGlobCnt
|
||||
{
|
||||
uint64_t globCnt = 0, oGlobCnt = 0;
|
||||
|
||||
if ([self length] > 0)
|
||||
globCnt = *(uint64_t *) [self bytes];
|
||||
|
||||
if ([otherGlobCnt length] > 0)
|
||||
oGlobCnt = *(uint64_t *) [otherGlobCnt bytes];
|
||||
|
||||
return MAPICNCompare (globCnt, oGlobCnt, NULL);
|
||||
}
|
||||
|
||||
+ (id) dataWithChangeKeyGUID: (NSString *) guidString
|
||||
andCnt: (NSData *) globCnt;
|
||||
{
|
||||
|
||||
@@ -161,8 +161,11 @@
|
||||
minute: [tzStart minuteOfHour] second: 0
|
||||
timeZone: [NSTimeZone timeZoneWithName: @"GMT"]];
|
||||
|
||||
tmpDate = [tmpDate addYear: 0 month: ((pos > 0) ? 0 : 1)
|
||||
day: 0 hour: 0 minute: 0
|
||||
tmpDate = [tmpDate addYear: 0
|
||||
month: ((pos > 0) ? 0 : 1)
|
||||
day: 0
|
||||
hour: 0
|
||||
minute: 0
|
||||
second: 0];
|
||||
|
||||
/* If the day of the time change is "-XSU", we need to determine whether the
|
||||
@@ -197,12 +200,41 @@
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
|
||||
The time changes occure on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
The time changes occur on a Sunday, but in March, the 1st is a Sunday itself and in November
|
||||
the 1st is also a Sunday. If we don't decrement "pos" by one, tmpDate (which is set to March or November 1st
|
||||
because of "day: 1" will have 14 more days added for March and 7 more days added for November - which will
|
||||
effectively shift the time change by a whole week.
|
||||
|
||||
In Europe/Berlin, we have a different use-case for November. In 2015, November 1st is a Sunday.
|
||||
The time change in November must occur on October 25th but since tmpDate will be November 1st,
|
||||
so a Sunday, dateDayOfWeek will be 0 and dayOfWeek will also be 0 we would decrement tmpDate by 14 days,
|
||||
which is incorrect because it would shift the timezone change one week earlier. We take care about this
|
||||
one with check if pos is greater or equal than 0 and if so, we don't decrement it.
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Inverse inc.//NONSGML Olson 2014g//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
*/
|
||||
if (dayOfWeek == dateDayOfWeek)
|
||||
if (dayOfWeek == dateDayOfWeek && pos >= 0)
|
||||
pos--;
|
||||
|
||||
offset = (dayOfWeek - dateDayOfWeek) + (pos * 7);
|
||||
|
||||
@@ -2988,7 +2988,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
theUser = [SOGoUser userWithLogin: theUID];
|
||||
aParent = [theUser calendarsFolderInContext: context];
|
||||
|
||||
|
||||
if ([aParent isKindOfClass: [NSException class]])
|
||||
return nil;
|
||||
|
||||
aFolders = [aParent subFolders];
|
||||
e = [aFolders objectEnumerator];
|
||||
while ((aFolder = [e nextObject]))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* NGVCard+SOGo.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009-2014 Inverse inc.
|
||||
* Copyright (C) 2009-2015 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
|
||||
@@ -31,6 +31,10 @@
|
||||
- (CardElement *) elementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type;
|
||||
|
||||
- (void) addElementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value;
|
||||
|
||||
- (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord;
|
||||
- (NSMutableDictionary *) asLDIFRecord;
|
||||
|
||||
|
||||
@@ -188,8 +188,8 @@ convention:
|
||||
}
|
||||
|
||||
- (void) addElementWithTag: (NSString *) elementTag
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value
|
||||
ofType: (NSString *) type
|
||||
withValue: (id) value
|
||||
{
|
||||
NSArray *allValues;
|
||||
NSEnumerator *list;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2014 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2006-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -32,9 +31,13 @@
|
||||
@class NSString;
|
||||
|
||||
@interface SOGoContactGCSFolder : SOGoGCSFolder <SOGoContactFolder>
|
||||
|
||||
{
|
||||
NSString *baseCardDAVURL, *basePublicCardDAVURL;
|
||||
}
|
||||
- (void) fixupContactRecord: (NSMutableDictionary *) contactRecord;
|
||||
- (NSDictionary *) lookupContactWithName: (NSString *) aName;
|
||||
- (NSString *) cardDavURL;
|
||||
- (NSString *) publicCardDavURL;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2013 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2006-2013-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -67,6 +66,24 @@ static NSArray *folderListingFields = nil;
|
||||
@"c_component", nil];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
baseCardDAVURL = nil;
|
||||
basePublicCardDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCardDAVURL release];
|
||||
[basePublicCardDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (Class) objectClassForContent: (NSString *) content
|
||||
{
|
||||
CardGroup *cardEntry;
|
||||
@@ -436,4 +453,48 @@ static NSArray *folderListingFields = nil;
|
||||
return [NSString stringWithFormat: @"%@:%@", nsRep, nodeName];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCardDAVURL)
|
||||
{
|
||||
davURL = [[self realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCardDAVURL = davURL;
|
||||
[baseCardDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) cardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCardDAVURL)
|
||||
{
|
||||
davURL = [[self publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCardDAVURL = davURL;
|
||||
[basePublicCardDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]];
|
||||
}
|
||||
|
||||
@end /* SOGoContactGCSFolder */
|
||||
@@ -97,10 +97,10 @@
|
||||
|
||||
- (NSString *) davCollectionTag;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
/* flags */
|
||||
|
||||
- (NSException *) addFlagsToAllMessages: (id) _f;
|
||||
|
||||
@@ -2106,7 +2106,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
// * 5 FETCH (UID 559 MODSEQ (17))
|
||||
// * 6 FETCH (UID 560 MODSEQ (18))
|
||||
// * 7 FETCH (UID 561 MODSEQ (19))
|
||||
|
||||
//
|
||||
//
|
||||
// fetchUIDsOfVanishedItems ..
|
||||
//
|
||||
@@ -2119,6 +2119,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties
|
||||
matchingSyncToken: (NSString *) theSyncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
EOQualifier *searchQualifier;
|
||||
NSMutableArray *allTokens;
|
||||
@@ -2195,6 +2196,9 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
|
||||
for (i = 0; i < [fetchResults count]; i++)
|
||||
{
|
||||
if ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"] && initialLoadInProgress)
|
||||
continue;
|
||||
|
||||
d = [NSDictionary dictionaryWithObject: ([[[fetchResults objectAtIndex: i] objectForKey: @"flags"] containsObject: @"deleted"]) ? [NSNull null] : [[fetchResults objectAtIndex: i] objectForKey: @"modseq"]
|
||||
forKey: [[[fetchResults objectAtIndex: i] objectForKey: @"uid"] stringValue]];
|
||||
[allTokens addObject: d];
|
||||
@@ -2205,7 +2209,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
|
||||
if (highestmodseq == 0)
|
||||
highestmodseq = 1;
|
||||
|
||||
if (highestmodseq > 0)
|
||||
if (highestmodseq > 0 && !initialLoadInProgress)
|
||||
{
|
||||
id uid;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSArray+Utilities.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSArray+Utilities.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2011 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSObject+Utilities.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2007-2015 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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* NSObject+Utilities.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2007-2015 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
|
||||
|
||||
@@ -373,6 +373,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) asCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *cssIdentifier;
|
||||
unichar currentChar;
|
||||
int count, max, idx;
|
||||
@@ -381,10 +382,12 @@ static int cssEscapingCount;
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
cssIdentifier = [NSMutableString string];
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
max = [self length];
|
||||
|
||||
if (max > 0)
|
||||
{
|
||||
if (isdigit([self characterAtIndex: 0]))
|
||||
if ([numericSet characterIsMember: [self characterAtIndex: 0]])
|
||||
// A CSS identifier can't start with a digit; we add an underscore
|
||||
[cssIdentifier appendString: @"_"];
|
||||
for (count = 0; count < max; count++)
|
||||
@@ -415,6 +418,7 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) fromCSSIdentifier
|
||||
{
|
||||
NSCharacterSet *numericSet;
|
||||
NSMutableString *newString;
|
||||
NSString *currentString;
|
||||
int count, length, max, idx;
|
||||
@@ -423,12 +427,14 @@ static int cssEscapingCount;
|
||||
if (!cssEscapingStrings)
|
||||
[self _setupCSSEscaping];
|
||||
|
||||
numericSet = [NSCharacterSet decimalDigitCharacterSet];
|
||||
newString = [NSMutableString string];
|
||||
max = [self length];
|
||||
count = 0;
|
||||
|
||||
if (max > 0
|
||||
&& [self characterAtIndex: 0] == '_'
|
||||
&& isdigit([self characterAtIndex: 1]))
|
||||
&& [numericSet characterIsMember: [self characterAtIndex: 1]])
|
||||
{
|
||||
/* If the identifier starts with an underscore followed by a digit,
|
||||
we remove the underscore */
|
||||
|
||||
@@ -56,6 +56,8 @@ typedef enum {
|
||||
- (void) reloadIfNeeded;
|
||||
- (void) save;
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache;
|
||||
|
||||
/* accessors */
|
||||
- (NSMutableString *) path; /* full filename */
|
||||
|
||||
|
||||
@@ -103,11 +103,22 @@ static EOAttribute *textColumn = nil;
|
||||
}
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer
|
||||
{
|
||||
return [self objectWithName: key
|
||||
inContainer: theContainer
|
||||
useCache: YES];
|
||||
}
|
||||
|
||||
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache
|
||||
{
|
||||
SOGoCache *cache;
|
||||
id o;
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
|
||||
if (!useCache)
|
||||
[cache unregisterObjectWithName: key inContainer: theContainer];
|
||||
|
||||
o = [cache objectNamed: key inContainer: theContainer];
|
||||
|
||||
if (!o)
|
||||
|
||||
@@ -128,8 +128,8 @@
|
||||
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate;
|
||||
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress;
|
||||
|
||||
/* multiget helper */
|
||||
- (WOResponse *) performMultigetInContext: (WOContext *) queryContext
|
||||
|
||||
@@ -1151,6 +1151,7 @@ static NSArray *childRecordFields = nil;
|
||||
- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties
|
||||
matchingSyncToken: (NSString *) syncToken
|
||||
fromDate: (NSCalendarDate *) theStartDate
|
||||
initialLoad: (BOOL) initialLoadInProgress
|
||||
{
|
||||
/* TODO:
|
||||
- validation:
|
||||
@@ -1200,13 +1201,17 @@ static NSArray *childRecordFields = nil;
|
||||
mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: YES]];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
if (!initialLoadInProgress)
|
||||
{
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:
|
||||
@"c_lastmodified > %d and c_deleted == 1",
|
||||
syncTokenInt];
|
||||
fields = [NSMutableArray arrayWithObjects: @"c_name", @"c_lastmodified", @"c_deleted", nil];
|
||||
[mRecords addObjectsFromArray: [self _fetchFields: fields
|
||||
withQualifier: qualifier
|
||||
ignoreDeleted: NO]];
|
||||
}
|
||||
|
||||
records = mRecords;
|
||||
}
|
||||
else
|
||||
@@ -1499,7 +1504,9 @@ static NSArray *childRecordFields = nil;
|
||||
properties = [self parseDAVRequestedProperties: propElement];
|
||||
records = [self syncTokenFieldsWithProperties: properties
|
||||
matchingSyncToken: syncToken
|
||||
fromDate: nil];
|
||||
fromDate: nil
|
||||
initialLoad: NO];
|
||||
|
||||
[self _appendComponentProperties: [properties allKeys]
|
||||
fromRecords: records
|
||||
matchingSyncToken: [syncToken intValue]
|
||||
|
||||
@@ -778,7 +778,10 @@ static Class NSNullK;
|
||||
NSRange r;
|
||||
|
||||
r = [theUID rangeOfString: [NSString stringWithFormat: @"@%@", theDomain]];
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
|
||||
// We check if the range is ok here since we could be using DomainLessLogin
|
||||
if (r.location != NSNotFound)
|
||||
theUID = [theUID substringToIndex: r.location];
|
||||
}
|
||||
|
||||
sogoSources = [[self authenticationSourceIDsInDomain: theDomain] objectEnumerator];
|
||||
|
||||
@@ -70,7 +70,11 @@ class Carddav:
|
||||
if not self.cards:
|
||||
url = "/SOGo/so/%s/Contacts/personal/view" % (self.login)
|
||||
content = self._get(url)
|
||||
self.cards = content['cards']
|
||||
#print "\nCONTENT:", content
|
||||
if 'cards' in content:
|
||||
self.cards = content['cards']
|
||||
else:
|
||||
self.cards = []
|
||||
return self.cards
|
||||
|
||||
def get_cards(self, pattern):
|
||||
|
||||
@@ -89,9 +89,9 @@ class preferences:
|
||||
get = HTTPPreferencesGET(url)
|
||||
get.cookie = self.cookie
|
||||
self.client.execute(get)
|
||||
if DEBUG: print "LUC (url):", url
|
||||
if DEBUG: print "LUC (status):", get.response["status"]
|
||||
if DEBUG: print "LUC (body):", get.response['body']
|
||||
if DEBUG: print "DEBUG (url):", url
|
||||
if DEBUG: print "DEBUG (status):", get.response["status"]
|
||||
if DEBUG: print "DEBUG (body):", get.response['body']
|
||||
content = simplejson.loads(get.response['body'])
|
||||
result = None
|
||||
try:
|
||||
@@ -117,7 +117,7 @@ class preferences:
|
||||
# Get the right sub-dict and change the key/value
|
||||
subdict = self.find_key(self.preferences, preference)
|
||||
if not subdict:
|
||||
print "LUC(nosubdict):", preference, self.preferences
|
||||
raise AttributeError("ERROR(nosubdict): looking for %s in: %s" %(preference, str(self.preferences)))
|
||||
subdict[preference] = value
|
||||
|
||||
def set(self, preference, value=None):
|
||||
|
||||
@@ -85,6 +85,13 @@ class JsonDavPhoneTests(unittest.TestCase):
|
||||
self.allphones = list(self.newphone)
|
||||
self.allphones.extend(self.newphones_difftype)
|
||||
self.allphones.extend(self.newphones_sametype)
|
||||
#- In case there are no cards for this user
|
||||
try:
|
||||
self._get_card()
|
||||
except IndexError:
|
||||
path = 'Contacts/personal'
|
||||
(card, path, gid) = self._create_new_card(path)
|
||||
self._save_card(card)
|
||||
|
||||
def tearDown(self):
|
||||
self._connect_as_user()
|
||||
@@ -102,6 +109,23 @@ class JsonDavPhoneTests(unittest.TestCase):
|
||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
||||
self.dv = carddav.Carddav(newuser, newpassword)
|
||||
|
||||
def _create_new_card(self, path):
|
||||
gid = self.dv.newguid(path)
|
||||
card = {'c_categories': None,
|
||||
'c_cn': 'John Doe',
|
||||
'c_component': 'vcard',
|
||||
'c_givenname': 'John Doe',
|
||||
'c_mail': 'johndoe@nothere.com',
|
||||
'c_name': gid,
|
||||
'c_o': '',
|
||||
'c_screenname': '',
|
||||
'c_sn': '',
|
||||
'c_telephonenumber': '123.456.7890',
|
||||
'emails': [{'type': 'pref', 'value': 'johndoe@nothere.com'}],
|
||||
'phones': [{'type': 'home', 'value': '111.222.3333'}],
|
||||
'id': gid}
|
||||
return (card, path, gid)
|
||||
|
||||
def _get_card(self, name="John Doe"):
|
||||
tmp_card = self.dv.get_cards(name)[0]
|
||||
self.card = self.dv.get_card(tmp_card['c_name'])
|
||||
|
||||
@@ -28,12 +28,14 @@ class preventInvitationsTest(unittest.TestCase):
|
||||
self.caldav = CalDAVSchedulingTest(self)
|
||||
cal = self.prefs.get("Calendar")
|
||||
if "PreventInvitationsWhitelist" not in cal:
|
||||
#print "LUC (cal):", cal
|
||||
cal["PreventInvitationsWhitelist"] = None
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
if "PreventInvitations" not in cal:
|
||||
cal["PreventInvitations"] = 0
|
||||
self.prefs.set("PreventInvitations", 0)
|
||||
|
||||
def tearDown(self):
|
||||
self.prefs.set("autoReplyText", "")
|
||||
#self.prefs.set("autoReplyText", "")
|
||||
self.prefs.set('PreventInvitations', 0)
|
||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
||||
#- Manual Cleanup, not called because classs is not derived from unittest
|
||||
|
||||
@@ -21,8 +21,8 @@ class UIPostsTests(unittest.TestCase):
|
||||
self.cookie = sogoLogin.getAuthCookie(hostname, port, username, password)
|
||||
|
||||
def _urlPostData(self, client, url, data, exp_status=200):
|
||||
post = webdavlib.HTTPPOST(url, data)
|
||||
post.content_type = "application/x-www-form-urlencoded"
|
||||
post = webdavlib.HTTPPOST(url, simplejson.dumps(data))
|
||||
post.content_type = "application/json"
|
||||
post.cookie = self.cookie
|
||||
|
||||
client.execute(post)
|
||||
@@ -43,16 +43,14 @@ class UIPostsTests(unittest.TestCase):
|
||||
""" Add Web Calendar """
|
||||
|
||||
ret=True
|
||||
data = "url=%s" % webCalendarURL
|
||||
data = {"url":"%s" % webCalendarURL}
|
||||
calendarBaseURL="/SOGo/so/%s/Calendar" % username
|
||||
addWebCalendarURL = "%s/addWebCalendar" % calendarBaseURL
|
||||
response = self._urlPostData(self.client, addWebCalendarURL, data)
|
||||
|
||||
respJSON = simplejson.loads(response['body'])
|
||||
folderID = respJSON['folderID']
|
||||
calID = respJSON['id']
|
||||
|
||||
#sogo1:Calendar/C07-5006F300-1-370E2480
|
||||
(_, calID) = folderID.split('/', 1)
|
||||
self.assertNotEqual(calID, None)
|
||||
|
||||
# reload the cal
|
||||
|
||||
@@ -22,6 +22,8 @@ $(TEST_TOOL)_OBJC_FILES += \
|
||||
TestSBJsonParser.m \
|
||||
\
|
||||
TestNGMimeAddressHeaderFieldGenerator.m \
|
||||
TestNGMimeMessageGenerator.m \
|
||||
\
|
||||
TestNSData+Crypto.m \
|
||||
TestNSString+Crypto.m \
|
||||
TestNSString+URLEscaping.m \
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@class NSArray;
|
||||
@@ -52,6 +54,8 @@
|
||||
|
||||
- (BOOL) run;
|
||||
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2;
|
||||
@end
|
||||
|
||||
#define test(c) { \
|
||||
|
||||
@@ -185,4 +185,79 @@ static NSString *SOGoTestAssertException = @"SOGoTestAssertException";
|
||||
return YES;
|
||||
}
|
||||
|
||||
/* Helper function for diffForString:andString */
|
||||
NSString *_stringForCharacterAtIndex(NSUInteger index, NSString *str, NSUInteger length)
|
||||
{
|
||||
NSString *chrStr;
|
||||
unichar chr;
|
||||
if (index < length)
|
||||
{
|
||||
chr = [str characterAtIndex: index];
|
||||
if (isprint(chr))
|
||||
{
|
||||
chrStr = [NSString stringWithFormat: @"%c", chr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chr == 10)
|
||||
chrStr = @"[NL]";
|
||||
else if (chr == 0)
|
||||
chrStr = @"[\0]";
|
||||
else
|
||||
chrStr = [NSString stringWithFormat: @"[NP: %u]", chr];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
chrStr = @"[none]";
|
||||
}
|
||||
|
||||
return chrStr;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a string with a very verbose diff of the two strings.
|
||||
In case the strings are equal it returns an empty string.
|
||||
Example output for the strings 'flower' and 'flotera':
|
||||
<begin of example>
|
||||
0 |f|
|
||||
1 |l|
|
||||
2 |o|
|
||||
3 |w|t|<--
|
||||
4 |e|
|
||||
5 |r|
|
||||
6 |[none]|a|<--
|
||||
<end of example>
|
||||
*/
|
||||
- (NSString*) stringFromDiffBetween: (NSString*) str1
|
||||
and: (NSString*) str2
|
||||
{
|
||||
BOOL differencesFound = NO;
|
||||
NSString *finalSTR = @"";
|
||||
NSUInteger i, length1, length2;
|
||||
NSString *sc1, *sc2;
|
||||
|
||||
length1 = [str1 length];
|
||||
length2 = [str2 length];
|
||||
for (i = 0; i < length1 || i < length2; i++)
|
||||
{
|
||||
sc1 = _stringForCharacterAtIndex(i, str1, length1);
|
||||
sc2 = _stringForCharacterAtIndex(i, str2, length2);
|
||||
|
||||
if ([sc1 isEqualToString: sc2])
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|\n", i, sc1];
|
||||
else
|
||||
{
|
||||
finalSTR = [finalSTR stringByAppendingFormat: @"%u |%@|%@|<--\n", i, sc1, sc2];
|
||||
differencesFound = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!differencesFound)
|
||||
return @"";
|
||||
|
||||
return finalSTR;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,134 @@
|
||||
/* TestNGMimeMessageGenerator.m - this file is part of SOGo
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import "SOGoTest.h"
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
@interface TestNGMimeMessageGenerator : SOGoTest
|
||||
@end
|
||||
|
||||
@implementation TestNGMimeMessageGenerator
|
||||
|
||||
- (void) test_generateDataForHeaderField_value
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
NSArray *cases = [NSArray arrayWithObjects:
|
||||
[NSArray arrayWithObjects: @"Message-ID", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", @"<CADCKkzo+9X1SniJFY3yc7YGafNrmAts419RmcqNkMzd-PBqNbA@mail.gmail.com>", nil],
|
||||
[NSArray arrayWithObjects: @"Content-Type",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
@"text/plain; charset=utf-8; format=flowed",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneHebrewOneLatin",
|
||||
@"עs",
|
||||
@"=?utf-8?q?=D7=A2s?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneLatineOneHebrew",
|
||||
@"sע",
|
||||
@"=?utf-8?q?s=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterHebrew",
|
||||
@"ע",
|
||||
@"=?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderOneCharacterRussian",
|
||||
@"Б",
|
||||
@"=?utf-8?q?=D0=91?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-FullHeaderParameter",
|
||||
@"parameter=ע",
|
||||
@"parameter==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderParameters",
|
||||
@"plain; parameter=ע; parameter-plain; parameter2=ea",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=;\n parameter-plain; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndNoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n =?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderAndTwoParameter",
|
||||
@"plain; parameter=ע; parameter-plain; z=ע",
|
||||
@"plain;\n parameter==?utf-8?q?=D7=A2?=; parameter-plain;\n z==?utf-8?q?=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-MixedHeaderExtrablanks",
|
||||
@"plain; parameter=ע; parameter 2spaces; parameter2=ea",
|
||||
@"plain;\n \\ parameter==?utf-8?q?=D7=A2?=;\n parameter 2spaces; parameter2=ea",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"X-Encoded-Unbalanced-Paramter-Quote",
|
||||
@"text/plain; name=\"ע",
|
||||
@"text/plain;\n name==?utf-8?q?=22=D7=A2?=",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-type",
|
||||
@"text/plain; name=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"text/plain;\n name=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-disposition",
|
||||
@"attachment; filename=\"АБВГДЕЁЖЗИЙ, КЛМНОПРСТУФ y ЦЧШЩЪЫЬЭЮЯ.txt\"",
|
||||
@"attachment;\n filename=\"=?utf-8?q?=D0=90=D0=91=D0=92=D0=93=D0=94=D0=95=D0=81=D0=96=D0=97=D0=98=D0=99=2C_=D0=9A=D0=9B=D0=9C=D0=9D=D0=9E=D0=9F=D0=A0=D0=A1=D0=A2=D0=A3=D0=A4_y_=D0=A6=D0=A7=D0=A8=D0=A9=D0=AA=D0=AB=D0=AC=D0=AD=D0=AE=D0=AF=2Etxt?=\"",
|
||||
nil],
|
||||
[NSArray arrayWithObjects: @"content-length", @"2912", @"2912", nil],
|
||||
[NSArray arrayWithObjects: @"content-transfer-encoding", @"quoted-printable", @"quoted-printable", nil],
|
||||
nil
|
||||
];
|
||||
NSEnumerator *enumerator;
|
||||
NSArray *testCase;
|
||||
|
||||
[NGMimeMessageGenerator initialize];
|
||||
generator = [[NGMimeMessageGenerator alloc] init];
|
||||
[generator autorelease];
|
||||
|
||||
enumerator = [cases objectEnumerator];
|
||||
while ((testCase = [enumerator nextObject]) != nil)
|
||||
{
|
||||
NSData *result;
|
||||
NSMutableData *resultWithNulByte;
|
||||
NSString *header = [testCase objectAtIndex: 0];
|
||||
NSData *headerData = [testCase objectAtIndex: 1];
|
||||
NSString *expected = [testCase objectAtIndex: 2];
|
||||
result = [generator generateDataForHeaderField: header
|
||||
value: headerData];
|
||||
if (result == nil)
|
||||
result = [@"[nil]" dataUsingEncoding: NSUTF8StringEncoding];
|
||||
|
||||
resultWithNulByte = [result mutableCopy];
|
||||
[resultWithNulByte appendBytes: "\0" length: 1];
|
||||
NSString *resultString = [NSString stringWithCString:[resultWithNulByte bytes]];
|
||||
|
||||
|
||||
BOOL testResult = [resultString isEqualToString: expected];
|
||||
|
||||
NSString *diff = [self stringFromDiffBetween: [NSString stringWithString: resultString]
|
||||
and: [NSString stringWithString: expected]];
|
||||
NSString *testErrorMsg = [NSString
|
||||
stringWithFormat: @">> For %@ header received:\n%@[END]\n>> instead of:\n%@[END]\n>> for:\n%@\n>> diff:\n%@\n>> lengthReceived: %u lengthExpected: %u",
|
||||
header,
|
||||
resultString,
|
||||
expected,
|
||||
headerData,
|
||||
diff,
|
||||
[resultString length],
|
||||
[expected length]
|
||||
];
|
||||
|
||||
testWithMessage(testResult, testErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -35,7 +35,7 @@
|
||||
{
|
||||
const char *inStrings[] = { "éléphant", "2š", "sogo+test", NULL };
|
||||
const char **inString;
|
||||
NSString *outStrings[] = { @"%C3%A9l%C3%A9phant", @"2%C5%A1", @"sogo+test" };
|
||||
NSString *outStrings[] = { @"%C3%A9l%C3%A9phant", @"2%C5%A1", @"sogo%2Btest" };
|
||||
NSString **outString;
|
||||
NSString *result, *error;
|
||||
|
||||
|
||||
@@ -116,13 +116,19 @@ static NSURL *tableURL = nil;
|
||||
- (void) _removeUserFolders: (NSString *) userId
|
||||
{
|
||||
NSArray *folderPaths;
|
||||
NSString *path;
|
||||
int count, max;
|
||||
|
||||
folderPaths = [self _userFolderPaths: userId];
|
||||
max = [folderPaths count];
|
||||
if (max > 0)
|
||||
for (count = 0; count < max; count++)
|
||||
[fm deleteFolderAtPath: [folderPaths objectAtIndex: count]];
|
||||
{
|
||||
path = [folderPaths objectAtIndex: count];
|
||||
[fm deleteFolderAtPath: path];
|
||||
if (verbose)
|
||||
NSLog (@"Deleting %@", path);
|
||||
}
|
||||
else
|
||||
NSLog (@"No folder returned for user '%@'", userId);
|
||||
}
|
||||
@@ -144,6 +150,8 @@ static NSURL *tableURL = nil;
|
||||
userId];
|
||||
if ([fc evaluateExpressionX: sql])
|
||||
NSLog (@"Unable to delete the preference record for '%@'", userId);
|
||||
else if (verbose)
|
||||
NSLog (@"Removed preference record for '%@'", userId);
|
||||
[cm releaseChannel: fc];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ AdministrationUI_OBJC_FILES = \
|
||||
AdministrationUIProduct.m \
|
||||
\
|
||||
UIxAdministration.m \
|
||||
UIxAdministrationAclEditor.m \
|
||||
UIxAdministrationFilterPanel.m
|
||||
|
||||
AdministrationUI_RESOURCE_FILES += \
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Administration";
|
||||
}
|
||||
|
||||
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
|
||||
inContext: (WOContext*) context
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* UIxAdministrationAclEditor.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2014 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef UIXACLEDITOR_H
|
||||
#define UIXACLEDITOR_H
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@interface UIxAdministrationAclEditor : UIxComponent
|
||||
{
|
||||
BOOL prepared;
|
||||
BOOL publishInFreeBusy;
|
||||
NSArray *aclUsers;
|
||||
NSArray *savedUIDs;
|
||||
NSMutableDictionary *users;
|
||||
NSString *currentUser;
|
||||
NSString *defaultUserID;
|
||||
}
|
||||
|
||||
- (NSArray *) aclsForObject;
|
||||
- (void) setCurrentUser: (NSString *) newCurrentUser;
|
||||
- (NSString *) currentUser;
|
||||
- (NSString *) currentUserClass;
|
||||
- (NSDictionary *) currentUserInfos;
|
||||
- (NSString *) currentUserDisplayName;
|
||||
- (BOOL) currentUserIsSubscribed;
|
||||
- (BOOL) isPublicAccessEnabled;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* UIXACLEDITOR_H */
|
||||
@@ -0,0 +1,279 @@
|
||||
/* UIxAdministrationAclEditor.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSKeyValueCoding.h>
|
||||
|
||||
#import <NGObjWeb/SoSecurityManager.h>
|
||||
#import <NGObjWeb/SoUser.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/SOGoContentObject.h>
|
||||
#import <SOGo/SOGoGCSFolder.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
|
||||
#import "UIxAdministrationAclEditor.h"
|
||||
|
||||
@implementation UIxAdministrationAclEditor
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
aclUsers = nil;
|
||||
prepared = NO;
|
||||
publishInFreeBusy = NO;
|
||||
users = [NSMutableDictionary new];
|
||||
currentUser = nil;
|
||||
defaultUserID = nil;
|
||||
savedUIDs = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[savedUIDs release];
|
||||
[users release];
|
||||
[currentUser release];
|
||||
[defaultUserID release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSArray *) aclsForObject
|
||||
{
|
||||
if (!aclUsers)
|
||||
aclUsers = [[self clientObject] aclUsers];
|
||||
|
||||
return aclUsers;
|
||||
}
|
||||
|
||||
- (NSString *) defaultUserID
|
||||
{
|
||||
if (!defaultUserID)
|
||||
ASSIGN (defaultUserID, [[self clientObject] defaultUserID]);
|
||||
|
||||
return defaultUserID;
|
||||
}
|
||||
|
||||
- (BOOL) canSubscribeUsers
|
||||
{
|
||||
return [[self clientObject]
|
||||
respondsToSelector: @selector (subscribeUserOrGroup:reallyDo:response:)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /so/:username/:folderPath/acls List users with rights
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetAcls
|
||||
* @apiGroup Common
|
||||
* @apiExample {curl} Example usage:
|
||||
* curl -i http://localhost/SOGo/so/sogo1/Calendar/personal/acls
|
||||
*
|
||||
* @apiSuccess (Success 200) {Object[]} users List of users with ACL for the folder
|
||||
* @apiSuccess (Success 200) {String} users.uid User ID
|
||||
* @apiSuccess (Success 200) {String} users.userClass Either 'normal-user', 'normal-group' or 'public-access'
|
||||
* @apiSuccess (Success 200) {Number} users.isSubscribed 1 if the user is subscribed to the folder
|
||||
* @apiSuccess (Success 200) {String} [users.cn] User fullname
|
||||
* @apiSuccess (Success 200) {String} [users.c_email] User main email address
|
||||
*/
|
||||
- (id <WOActionResults>) aclsAction
|
||||
{
|
||||
NSString *currentUID, *ownerLogin, *info;
|
||||
NSDictionary *currentUserInfos;
|
||||
NSMutableDictionary *userData;
|
||||
id <WOActionResults> result;
|
||||
NSEnumerator *aclsEnum;
|
||||
|
||||
if (!prepared)
|
||||
{
|
||||
ownerLogin = [[self clientObject] ownerInContext: context];
|
||||
if (!defaultUserID)
|
||||
ASSIGN (defaultUserID, [[self clientObject] defaultUserID]);
|
||||
|
||||
aclsEnum = [[self aclsForObject] objectEnumerator];
|
||||
while ((currentUID = [aclsEnum nextObject]))
|
||||
{
|
||||
if (!([currentUID isEqualToString: ownerLogin]
|
||||
|| [currentUID isEqualToString: defaultUserID]
|
||||
|| [currentUID isEqualToString: @"anonymous"]))
|
||||
{
|
||||
// Set the current user in order to get information associated with it
|
||||
[self setCurrentUser: currentUID];
|
||||
|
||||
// Build the object associated to the current UID
|
||||
currentUserInfos = [self currentUserInfos];
|
||||
userData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
currentUser, @"uid",
|
||||
[self currentUserClass], @"userClass",
|
||||
[NSNumber numberWithBool: [self currentUserIsSubscribed]], @"isSubscribed",
|
||||
nil];
|
||||
if ((info = [currentUserInfos objectForKey: @"cn"]) && [info length])
|
||||
[userData setObject: info forKey: @"cn"];
|
||||
if ((info = [currentUserInfos objectForKey: @"c_email"]) && [info length])
|
||||
[userData setObject: info forKey: @"c_email"];
|
||||
[users setObject: userData forKey: currentUID];
|
||||
}
|
||||
}
|
||||
|
||||
// Add the 'Any authenticated' user
|
||||
if ([self canSubscribeUsers])
|
||||
{
|
||||
userData = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"<default>", @"uid",
|
||||
[self labelForKey: @"Any Authenticated User"], @"cn",
|
||||
@"public-user", @"userClass",
|
||||
nil];
|
||||
[users setObject: userData forKey: @"<default>"];
|
||||
}
|
||||
|
||||
if ([self canSubscribeUsers] && [self isPublicAccessEnabled])
|
||||
{
|
||||
// Add the 'public access' user
|
||||
userData = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"anonymous", @"uid",
|
||||
[self labelForKey: @"Public Access"], @"cn",
|
||||
@"public-user", @"userClass",
|
||||
nil];
|
||||
[users setObject: userData forKey: @"anonymous"];
|
||||
}
|
||||
|
||||
prepared = YES;
|
||||
}
|
||||
|
||||
result = [self responseWithStatus: 200
|
||||
andJSONRepresentation: [NSDictionary dictionaryWithObject: users forKey: @"users"]];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void) setCurrentUser: (NSString *) newCurrentUser
|
||||
{
|
||||
ASSIGN (currentUser, newCurrentUser);
|
||||
}
|
||||
|
||||
- (NSString *) currentUser
|
||||
{
|
||||
return ([currentUser hasPrefix: @"@"]
|
||||
? [currentUser substringFromIndex: 1]
|
||||
: currentUser);
|
||||
}
|
||||
|
||||
- (NSString *) currentUserClass
|
||||
{
|
||||
return ([currentUser hasPrefix: @"@"]
|
||||
? @"normal-group"
|
||||
: @"normal-user");
|
||||
}
|
||||
|
||||
- (NSString *) currentUserDisplayName
|
||||
{
|
||||
NSDictionary *infos;
|
||||
NSString *uid;
|
||||
SOGoUserManager *um;
|
||||
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
uid = [self currentUser];
|
||||
infos = [um contactInfosForUserWithUIDorEmail: uid inDomain: [[context activeUser] domain]];
|
||||
if (infos)
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@ <%@>",
|
||||
[infos objectForKey: @"cn"],
|
||||
[infos objectForKey: @"c_email"]];
|
||||
}
|
||||
else
|
||||
return uid;
|
||||
}
|
||||
|
||||
- (NSDictionary *) currentUserInfos
|
||||
{
|
||||
SOGoUserManager *um;
|
||||
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
|
||||
return [um contactInfosForUserWithUIDorEmail: [self currentUser]];
|
||||
}
|
||||
|
||||
- (BOOL) currentUserIsSubscribed
|
||||
{
|
||||
SOGoGCSFolder *folder;
|
||||
|
||||
folder = [self clientObject];
|
||||
|
||||
return ([folder respondsToSelector: @selector (userIsSubscriber:)]
|
||||
&& [folder userIsSubscriber: currentUser]);
|
||||
}
|
||||
|
||||
- (void) setUserUIDS: (NSString *) retainedUsers
|
||||
{
|
||||
if ([retainedUsers length] > 0)
|
||||
{
|
||||
savedUIDs = [retainedUsers componentsSeparatedByString: @","];
|
||||
[savedUIDs retain];
|
||||
}
|
||||
else
|
||||
savedUIDs = [NSArray new];
|
||||
}
|
||||
|
||||
- (NSString *) folderID
|
||||
{
|
||||
return [[self clientObject] nameInContainer];
|
||||
}
|
||||
|
||||
- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
return ([[request method] isEqualToString: @"POST"]);
|
||||
}
|
||||
|
||||
- (id <WOActionResults>) saveAclsAction
|
||||
{
|
||||
NSEnumerator *aclsEnum;
|
||||
SOGoObject *clientObject;
|
||||
NSString *currentUID, *ownerLogin;
|
||||
|
||||
clientObject = [self clientObject];
|
||||
ownerLogin = [clientObject ownerInContext: context];
|
||||
aclsEnum = [[self aclsForObject] objectEnumerator];
|
||||
while ((currentUID = [[aclsEnum nextObject] objectForKey: @"c_uid"]))
|
||||
if ([currentUID isEqualToString: ownerLogin]
|
||||
|| [savedUIDs containsObject: currentUID])
|
||||
[users removeObjectForKey: currentUID];
|
||||
[clientObject removeAclsForUsers: [users allKeys]];
|
||||
|
||||
return [self jsCloseWithRefreshMethod: nil];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] enablePublicAccess];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,6 +15,10 @@
|
||||
protectedBy = "View";
|
||||
pageName = "UIxAdministration";
|
||||
};
|
||||
UIxAdministrationAclEditor = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxAdministrationAclEditor";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -249,14 +249,7 @@
|
||||
|
||||
- (NSString *) commonLocalizableStrings
|
||||
{
|
||||
NSString *rc;
|
||||
|
||||
if (isPopup)
|
||||
rc = @"";
|
||||
else
|
||||
rc = [NSString stringWithFormat: @"var clabels = %@;", [self _stringsForFramework: nil]];
|
||||
|
||||
return rc;
|
||||
return [NSString stringWithFormat: @"var clabels = %@;", [self _stringsForFramework: nil]];
|
||||
}
|
||||
|
||||
- (NSString *) productLocalizableStrings
|
||||
@@ -440,40 +433,6 @@
|
||||
return ([[self productCSSURL] length] > 0);
|
||||
}
|
||||
|
||||
- (BOOL) _moduleIs: (NSString *) moduleName
|
||||
{
|
||||
NSString *frameworkName;
|
||||
|
||||
frameworkName = [[context page] frameworkName];
|
||||
|
||||
return [frameworkName isEqualToString: moduleName];
|
||||
}
|
||||
|
||||
- (BOOL) isCalendar
|
||||
{
|
||||
return [self _moduleIs: @"SchedulerUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isContacts
|
||||
{
|
||||
return [self _moduleIs: @"ContactsUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isMail
|
||||
{
|
||||
return [self _moduleIs: @"MailerUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isPreferences
|
||||
{
|
||||
return [self _moduleIs: @"PreferencesUI"];
|
||||
}
|
||||
|
||||
- (BOOL) isAdministration
|
||||
{
|
||||
return [self _moduleIs: @"AdministrationUI"];
|
||||
}
|
||||
|
||||
- (void) setToolbar: (NSString *) newToolbar
|
||||
{
|
||||
ASSIGN (toolbar, newToolbar);
|
||||
@@ -509,6 +468,12 @@
|
||||
return [[context activeUser] cn];
|
||||
}
|
||||
|
||||
- (NSString *) userEmail
|
||||
{
|
||||
return [[context activeUser] systemEmail];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) canLogoff
|
||||
{
|
||||
BOOL canLogoff;
|
||||
|
||||
@@ -43,6 +43,27 @@
|
||||
"Copy To" = "Copy To";
|
||||
"Add to:" = "Add to:";
|
||||
|
||||
/* Subheader of empty addressbook */
|
||||
"No contact" = "No contact";
|
||||
|
||||
/* Subheader of system addressbook */
|
||||
"Start a search to browse this address book" = "Start a search to browse this address book";
|
||||
|
||||
/* Number of contacts in addressbook; string is prefixed by number */
|
||||
"contacts" = "contacts";
|
||||
|
||||
/* No contact matching search criteria */
|
||||
"No matching contact" = "No matching contact";
|
||||
|
||||
/* Number of contacts matching search criteria; string is prefixed by number */
|
||||
"matching contacts" = "matching contacts";
|
||||
|
||||
/* Number of selected contacts in list */
|
||||
"selected" = "selected";
|
||||
|
||||
/* Empty right pane */
|
||||
"No contact selected" = "No contact selected";
|
||||
|
||||
/* Tooltips */
|
||||
|
||||
"Create a new address book card" = "Create a new address book card";
|
||||
|
||||
@@ -367,6 +367,8 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
day = [date dayOfMonth];
|
||||
[card setBday: [NSString stringWithFormat: @"%.4d%.2d%.2d", year, month, day]];
|
||||
}
|
||||
else
|
||||
[card setBday: nil];
|
||||
|
||||
if ([[attributes objectForKey: @"addresses"] isKindOfClass: [NSArray class]])
|
||||
{
|
||||
@@ -430,8 +432,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([attrs isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"tel" ofType: [attrs objectForKey: @"type"]];
|
||||
[element setSingleValue: [attrs objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"tel"
|
||||
ofType: [attrs objectForKey: @"type"]
|
||||
withValue: [attrs objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,8 +451,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([o isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"email" ofType: [o objectForKey: @"type"]];
|
||||
[element setSingleValue: [o objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"email"
|
||||
ofType: [o objectForKey: @"type"]
|
||||
withValue: [o objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -466,8 +470,9 @@ static Class SOGoContactGCSEntryK = Nil;
|
||||
{
|
||||
if ([attrs isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
element = [card elementWithTag: @"url" ofType: [attrs objectForKey: @"type"]];
|
||||
[element setSingleValue: [attrs objectForKey: @"value"] forKey: @""];
|
||||
[card addElementWithTag: @"url"
|
||||
ofType: [attrs objectForKey: @"type"]
|
||||
withValue: [attrs objectForKey: @"value"]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +112,11 @@
|
||||
|
||||
request = [context request];
|
||||
rc = [NSMutableDictionary dictionary];
|
||||
data = [request formValueForKey: @"contactsFile"];
|
||||
if ([data respondsToSelector: @selector(isEqualToString:)])
|
||||
fileContent = (NSString *) data;
|
||||
else
|
||||
{
|
||||
fileContent = [[NSString alloc] initWithData: (NSData *) data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
[fileContent autorelease];
|
||||
}
|
||||
data = [[[[[request httpRequest] body] parts] lastObject] body];
|
||||
|
||||
fileContent = [[NSString alloc] initWithData: (NSData *) data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
[fileContent autorelease];
|
||||
|
||||
if (fileContent && [fileContent length])
|
||||
{
|
||||
@@ -132,12 +128,10 @@
|
||||
imported = 0;
|
||||
}
|
||||
|
||||
[rc setObject: [NSNumber numberWithInt: imported]
|
||||
forKey: @"imported"];
|
||||
[rc setObject: [NSNumber numberWithInt: imported] forKey: @"imported"];
|
||||
|
||||
response = [self responseWithStatus: 200];
|
||||
[response setHeader: @"text/html"
|
||||
forKey: @"content-type"];
|
||||
[response setHeader: @"text/html" forKey: @"content-type"];
|
||||
[(WOResponse*)response appendContentString: [rc jsonRepresentation]];
|
||||
|
||||
return response;
|
||||
|
||||
@@ -20,14 +20,6 @@
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@class NSString;
|
||||
|
||||
@class SOGoContactGCSFolder;
|
||||
|
||||
@interface UIxContactFolderLinksTemplate : UIxComponent
|
||||
{
|
||||
SOGoContactGCSFolder *addressBook;
|
||||
NSString *baseCardDAVURL, *basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -24,69 +24,6 @@
|
||||
|
||||
@implementation UIxContactFolderLinksTemplate
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
addressBook = [self clientObject];
|
||||
baseCardDAVURL = nil;
|
||||
basePublicCardDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCardDAVURL release];
|
||||
[basePublicCardDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCardDAVURL)
|
||||
{
|
||||
davURL = [[addressBook realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCardDAVURL = davURL;
|
||||
[baseCardDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) cardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCardDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCardDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCardDAVURL)
|
||||
{
|
||||
davURL = [[addressBook publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCardDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCardDAVURL = davURL;
|
||||
[basePublicCardDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCardDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCardDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCardDAVURL]];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
|
||||
@@ -71,6 +71,11 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Contacts";
|
||||
}
|
||||
|
||||
- (void) _setupContext
|
||||
{
|
||||
SOGoUser *activeUser;
|
||||
@@ -275,32 +280,70 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return [[self queryParameterForKey: @"popup"] boolValue];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] enablePublicAccess];
|
||||
}
|
||||
|
||||
- (NSString *) contactFolders
|
||||
{
|
||||
SOGoContactFolders *folderContainer;
|
||||
NSArray *folders;
|
||||
NSMutableDictionary *urls, *acls;
|
||||
NSMutableArray *foldersAttrs;
|
||||
NSString *userLogin, *owner;
|
||||
NSArray *folders, *allACLs;
|
||||
NSDictionary *folderAttrs;
|
||||
id currentFolder;
|
||||
|
||||
BOOL objectCreator, objectEditor, objectEraser;
|
||||
int max, i;
|
||||
|
||||
userLogin = [[context activeUser] login];
|
||||
folderContainer = [self clientObject];
|
||||
|
||||
// return [folderContainer subFolders];
|
||||
|
||||
folders = [folderContainer subFolders];
|
||||
|
||||
max = [folders count];
|
||||
foldersAttrs = [NSMutableArray arrayWithCapacity: max];
|
||||
urls = nil;
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
currentFolder = [folders objectAtIndex: i];
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
|
||||
// We extract URLs for this address book
|
||||
if ([currentFolder respondsToSelector: @selector(cardDavURL)])
|
||||
{
|
||||
urls = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
||||
[currentFolder cardDavURL], @"cardDavURL",
|
||||
nil];
|
||||
if ([self isPublicAccessEnabled])
|
||||
{
|
||||
[urls setObject: [currentFolder publicCardDavURL] forKey: @"publicCardDavURL"];
|
||||
}
|
||||
}
|
||||
|
||||
// We extract ACLs for this address book
|
||||
allACLs = ([owner isEqualToString: userLogin] ? nil : [currentFolder aclsForUser: userLogin]);
|
||||
objectCreator = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectCreator]);
|
||||
objectEditor = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectEditor]);
|
||||
objectEraser = ([owner isEqualToString: userLogin] || [allACLs containsObject: SOGoRole_ObjectEraser]);
|
||||
acls = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: objectCreator], @"objectCreator",
|
||||
[NSNumber numberWithBool: objectEditor], @"objectEditor",
|
||||
[NSNumber numberWithBool: objectEraser], @"objectEraser", nil];
|
||||
|
||||
// NOTE: keep urls as the last key/value here, to avoid chopping the dictionary
|
||||
// if it is not a GCS folder
|
||||
folderAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSString stringWithFormat: @"%@", [currentFolder nameInContainer]], @"id",
|
||||
[currentFolder displayName], @"name",
|
||||
[currentFolder ownerInContext: context], @"owner",
|
||||
owner, @"owner",
|
||||
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoGCSFolderK]], @"isEditable",
|
||||
[NSNumber numberWithBool: [currentFolder isKindOfClass: SOGoContactSourceFolderK]
|
||||
&& ![currentFolder isPersonalSource]], @"isRemote",
|
||||
acls, @"acls",
|
||||
urls, @"urls",
|
||||
nil];
|
||||
[foldersAttrs addObject: folderAttrs];
|
||||
}
|
||||
@@ -419,12 +462,6 @@ Class SOGoContactSourceFolderK, SOGoGCSFolderK;
|
||||
return [super defaultAction];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] enablePublicAccess];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface UIxContactViewTemplate : UIxComponent
|
||||
|
||||
@@ -838,11 +838,10 @@
|
||||
o = [card birthday];
|
||||
if (o)
|
||||
{
|
||||
NSNumber *time = [NSNumber numberWithInt: [o timeIntervalSince1970]];
|
||||
[data setObject: time forKey: @"birthday"];
|
||||
[data setObject: [NSNumber numberWithInt: [o timeIntervalSince1970]]
|
||||
forKey: @"birthday"];
|
||||
}
|
||||
// o = [card source];
|
||||
// if (o) [data setObject: o forKey: @"source"];
|
||||
|
||||
o = [card tz];
|
||||
if (o) [data setObject: o forKey: @"tz"];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2006-2014 Inverse inc.
|
||||
Copyright (C) 2006-2015 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#import <Contacts/SOGoContactFolder.h>
|
||||
#import <Contacts/SOGoContactFolders.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
|
||||
#import <NGCards/NGVCard.h>
|
||||
@@ -83,32 +84,33 @@
|
||||
s = [rq formValueForKey: @"sort"];
|
||||
if (![s length])
|
||||
s = [self defaultSortKey];
|
||||
else
|
||||
[self saveSortValue: s];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
- (void) saveSortValue: (NSString *) sort
|
||||
{
|
||||
NSString *ascending;
|
||||
SOGoUserSettings *us;
|
||||
- (void) saveSortValue
|
||||
{
|
||||
NSMutableDictionary *contactSettings;
|
||||
|
||||
NSString *ascending, *sort;
|
||||
SOGoUserSettings *us;
|
||||
|
||||
sort = [[context request] formValueForKey: @"sort"];
|
||||
ascending = [[context request] formValueForKey: @"asc"];
|
||||
|
||||
if ([sort length])
|
||||
{
|
||||
us = [[context activeUser] userSettings];
|
||||
contactSettings = [us objectForKey: @"Contact"];
|
||||
// Must create if it doesn't exist
|
||||
if (!contactSettings)
|
||||
{
|
||||
contactSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: contactSettings forKey: @"Contact"];
|
||||
us = [[context activeUser] userSettings];
|
||||
contactSettings = [us objectForKey: @"Contact"];
|
||||
// Must create if it doesn't exist
|
||||
if (!contactSettings)
|
||||
{
|
||||
contactSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: contactSettings forKey: @"Contact"];
|
||||
}
|
||||
[contactSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", [ascending intValue]], nil]
|
||||
forKey: @"SortingState"];
|
||||
[us synchronize];
|
||||
}
|
||||
[contactSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", (ascending?1:0)], nil] forKey: @"SortingState"];
|
||||
[us synchronize];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *) contactInfos
|
||||
@@ -123,6 +125,8 @@
|
||||
WORequest *rq;
|
||||
unsigned int i;
|
||||
|
||||
[self saveSortValue];
|
||||
|
||||
if (!contactInfos)
|
||||
{
|
||||
folder = [self clientObject];
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
id info, viewer;
|
||||
NSArray *parts;
|
||||
NSMutableArray *renderedParts;
|
||||
NSString *contentType;
|
||||
NSUInteger i, max;
|
||||
|
||||
parts = [[self bodyInfo] objectForKey: @"parts"];
|
||||
@@ -102,9 +103,13 @@
|
||||
[viewer setPartPath: [self childPartPath]];
|
||||
[renderedParts addObject: [viewer renderedPart]];
|
||||
}
|
||||
contentType = [NSString stringWithFormat: @"%@/%@",
|
||||
[[self bodyInfo] objectForKey: @"type"],
|
||||
[[self bodyInfo] objectForKey: @"subtype"]];
|
||||
|
||||
return [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[self className], @"type",
|
||||
contentType, @"contentType",
|
||||
renderedParts, @"content",
|
||||
nil];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* UIxMailPartTextViewer.h - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* Copyright (C) 2006 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 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
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
/*
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of OpenGroupware.org.
|
||||
|
||||
OGo is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU Lesser General Public License as published by the
|
||||
Free Software Foundation; either version 2, or (at your option) any
|
||||
later version.
|
||||
|
||||
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with OGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
/* UIxMailPartTextViewer.m - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
UIxMailPartTextViewer
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
"Account: " = "Account: ";
|
||||
"Shared Account: " = "Shared Account: ";
|
||||
|
||||
/* Empty right pane */
|
||||
"No message selected" = "No message selected";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Access rights to";
|
||||
"For user" = "For user";
|
||||
@@ -228,6 +231,9 @@
|
||||
"Delete Message" = "Delete Message";
|
||||
"Delete Selected Messages" = "Delete Selected Messages";
|
||||
|
||||
/* Number of selected messages in list */
|
||||
"selected" = "selected";
|
||||
|
||||
"This Folder" = "This Folder";
|
||||
|
||||
/* Label popup menu */
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
errorFormat = [self labelForKey: @"The folder with name \"%@\" could not be created."];
|
||||
errorFormat = [self labelForKey: @"The folder with name \"%@\" could not be created." inContext: context];
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [NSString stringWithFormat: errorFormat, folderName]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
@@ -101,7 +101,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing 'name' parameter."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing 'name' parameter." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andJSONRepresentation: jsonResponse];
|
||||
@@ -149,7 +149,7 @@
|
||||
|
||||
if (!newFolderName || [newFolderName length] == 0)
|
||||
{
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing name parameter"]
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing name parameter" inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [message jsonRepresentation]];
|
||||
@@ -160,7 +160,7 @@
|
||||
error = [co renameTo: newFolderName];
|
||||
if (error)
|
||||
{
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to rename folder."]
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to rename folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [message jsonRepresentation]];
|
||||
@@ -251,7 +251,7 @@
|
||||
error = [connection moveMailboxAtURL: srcURL toURL: destURL];
|
||||
if (error)
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [jsonResponse jsonRepresentation]];
|
||||
@@ -283,7 +283,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder."]
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [self labelForKey: @"Unable to move folder." inContext: context]
|
||||
forKey: @"error"];
|
||||
response = [self responseWithStatus: 500
|
||||
andString: [jsonResponse jsonRepresentation]];
|
||||
@@ -374,7 +374,7 @@
|
||||
{
|
||||
uids = [value componentsSeparatedByString: @","];
|
||||
response = [co archiveUIDs: uids
|
||||
inArchiveNamed: [self labelForKey: @"Saved Messages.zip"]
|
||||
inArchiveNamed: [self labelForKey: @"Saved Messages.zip" inContext: context]
|
||||
inContext: context];
|
||||
if (!response)
|
||||
response = [self responseWith204];
|
||||
@@ -739,6 +739,7 @@
|
||||
NSDictionary *content, *result;
|
||||
BOOL addOrRemove;
|
||||
NGImap4Client *client;
|
||||
id flag;
|
||||
|
||||
int i;
|
||||
|
||||
@@ -751,7 +752,13 @@
|
||||
|
||||
// We unescape our flags
|
||||
for (i = [flags count]-1; i >= 0; i--)
|
||||
[flags replaceObjectAtIndex: i withObject: [[flags objectAtIndex: i] fromCSSIdentifier]];
|
||||
{
|
||||
flag = [flags objectAtIndex: i];
|
||||
if ([flag isKindOfClass: [NSString class]])
|
||||
[flags replaceObjectAtIndex: i withObject: [flag fromCSSIdentifier]];
|
||||
else
|
||||
[flags removeObjectAtIndex: i];
|
||||
}
|
||||
|
||||
co = [self clientObject];
|
||||
client = [[co imap4Connection] client];
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#import <Mailer/SOGoSentFolder.h>
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSObject+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoDateFormatter.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
@@ -131,7 +132,7 @@
|
||||
else if ([now dayOfCommonEra] - [messageDate dayOfCommonEra] == 1)
|
||||
{
|
||||
// Yesterday
|
||||
return [self labelForKey: @"Yesterday"];
|
||||
return [self labelForKey: @"Yesterday" inContext: context];
|
||||
}
|
||||
else if ([now dayOfCommonEra] - [messageDate dayOfCommonEra] < 7)
|
||||
{
|
||||
@@ -163,13 +164,15 @@
|
||||
//
|
||||
// Sometimes, the MUAs don't send over the string in () so we ignore it.
|
||||
//
|
||||
- (NSString *) messagePriority
|
||||
- (NSDictionary *) messagePriority
|
||||
{
|
||||
NSString *result;
|
||||
NSUInteger priority;
|
||||
NSString *description;
|
||||
NSData *data;
|
||||
|
||||
data = [message objectForKey: @"header"];
|
||||
result = @"";
|
||||
priority = 3;
|
||||
description = [self labelForKey: @"normal" inContext: context];
|
||||
|
||||
if (data)
|
||||
{
|
||||
@@ -190,15 +193,34 @@
|
||||
s = [[s substringFromIndex: r.location+1]
|
||||
stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
if ([s hasPrefix: @"1"]) result = [self labelForKey: @"highest"];
|
||||
else if ([s hasPrefix: @"2"]) result = [self labelForKey: @"high"];
|
||||
else if ([s hasPrefix: @"4"]) result = [self labelForKey: @"low"];
|
||||
else if ([s hasPrefix: @"5"]) result = [self labelForKey: @"lowest"];
|
||||
if ([s hasPrefix: @"1"])
|
||||
{
|
||||
priority = 1;
|
||||
description = [self labelForKey: @"highest" inContext: context];
|
||||
}
|
||||
else if ([s hasPrefix: @"2"])
|
||||
{
|
||||
priority = 2;
|
||||
description = [self labelForKey: @"high" inContext: context];
|
||||
}
|
||||
else if ([s hasPrefix: @"4"])
|
||||
{
|
||||
priority = 4;
|
||||
description = [self labelForKey: @"low" inContext: context];
|
||||
}
|
||||
else if ([s hasPrefix: @"5"])
|
||||
{
|
||||
priority = 5;
|
||||
description = [self labelForKey: @"lowest" inContext: context];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt: priority], @"level",
|
||||
description, @"name",
|
||||
nil];
|
||||
}
|
||||
|
||||
- (NSString *) messageSubject
|
||||
@@ -242,7 +264,7 @@
|
||||
{
|
||||
NSString *s;
|
||||
|
||||
s = [self labelForKey:@"View Mail Folder"];
|
||||
s = [self labelForKey:@"View Mail Folder" inContext: context];
|
||||
s = [s stringByAppendingString:@": "];
|
||||
s = [s stringByAppendingString:[self objectTitle]];
|
||||
return s;
|
||||
@@ -622,11 +644,12 @@
|
||||
- (NSDictionary *) getUIDsInFolder: (SOGoMailFolder *) folder
|
||||
withHeaders: (BOOL) includeHeaders
|
||||
{
|
||||
NSMutableDictionary *data;
|
||||
NSArray *uids, *threadedUids, *headers;
|
||||
NSRange r;
|
||||
NSMutableDictionary *data;
|
||||
SOGoMailAccount *account;
|
||||
id quota;
|
||||
|
||||
NSRange r;
|
||||
int count;
|
||||
|
||||
data = [NSMutableDictionary dictionary];
|
||||
@@ -644,10 +667,14 @@
|
||||
if (includeHeaders)
|
||||
{
|
||||
// Also retrieve the first headers, up to 'headersPrefetchMaxSize'
|
||||
count = [[uids flattenedArray] count];
|
||||
if (count > headersPrefetchMaxSize) count = headersPrefetchMaxSize;
|
||||
NSArray *a;
|
||||
|
||||
a = [uids flattenedArray];
|
||||
count = [a count];
|
||||
if (count > headersPrefetchMaxSize)
|
||||
count = headersPrefetchMaxSize;
|
||||
r = NSMakeRange(0, count);
|
||||
headers = [self getHeadersForUIDs: [[uids flattenedArray] subarrayWithRange: r]
|
||||
headers = [self getHeadersForUIDs: [a subarrayWithRange: r]
|
||||
inFolder: folder];
|
||||
|
||||
[data setObject: headers forKey: @"headers"];
|
||||
@@ -716,6 +743,8 @@
|
||||
* @apiSuccess (Success 200) {String} headers.From.email Sender's email address
|
||||
* @apiSuccess (Success 200) {Number} headers.isRead 1 if message is read
|
||||
* @apiSuccess (Success 200) {String} headers.Priority Priority
|
||||
* @apiSuccess (Success 200) {String} headers.Priority.level Priority number
|
||||
* @apiSuccess (Success 200) {String} headers.Priority.name Priority description
|
||||
* @apiSuccess (Success 200) {String} headers.RelativeDate Message date relative to now
|
||||
* @apiSuccess (Success 200) {String} headers.Size Formatted message size
|
||||
* @apiSuccess (Success 200) {String[]} headers.Flags Flags, such as "answered" and "seen"
|
||||
@@ -744,7 +773,6 @@
|
||||
noHeaders = [[[requestContent objectForKey: @"sortingAttributes"] objectForKey:@"noHeaders"] boolValue];
|
||||
data = [self getUIDsInFolder: folder
|
||||
withHeaders: !noHeaders];
|
||||
|
||||
[response appendContentString: [data jsonRepresentation]];
|
||||
|
||||
return response;
|
||||
@@ -878,7 +906,7 @@
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"No UID specified", @"error", nil];
|
||||
return [self responseWithStatus: 404 /* Not Found */
|
||||
andString: [data jsonRepresentation]];
|
||||
andString: [data jsonRepresentation]];
|
||||
}
|
||||
|
||||
uids = [data objectForKey: @"uids"];
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Mail";
|
||||
}
|
||||
|
||||
- (void) _setupContext
|
||||
{
|
||||
SOGoUser *activeUser;
|
||||
@@ -118,6 +123,18 @@
|
||||
return [accounts jsonRepresentation];
|
||||
}
|
||||
|
||||
- (WOResponse *) mailAccountsAction
|
||||
{
|
||||
WOResponse *response;
|
||||
NSString *s;
|
||||
|
||||
s = [self mailAccounts];
|
||||
response = [self responseWithStatus: 200
|
||||
andString: s];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
- (NSString *) userNames
|
||||
{
|
||||
NSArray *accounts, *userNames;
|
||||
@@ -374,14 +391,14 @@
|
||||
- (WOResponse *) saveFoldersStateAction
|
||||
{
|
||||
WORequest *request;
|
||||
NSString *expandedFolders;
|
||||
NSArray *expandedFolders;
|
||||
|
||||
[self _setupContext];
|
||||
request = [context request];
|
||||
expandedFolders = [request formValueForKey: @"expandedFolders"];
|
||||
expandedFolders = [[request contentAsString] objectFromJSONString];
|
||||
|
||||
[moduleSettings setObject: expandedFolders
|
||||
forKey: @"ExpandedFolders"];
|
||||
forKey: @"ExpandedFolders"];
|
||||
|
||||
[us synchronize];
|
||||
|
||||
|
||||
@@ -342,10 +342,19 @@
|
||||
actionClass = "UIxMailMainFrame";
|
||||
actionName = "unseenCount";
|
||||
};
|
||||
mailAccounts = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailMainFrame";
|
||||
actionName = "mailAccounts";
|
||||
};
|
||||
// viewerTemplate = {
|
||||
// protectedBy = "View";
|
||||
// pageName = "UIxMailViewTemplate";
|
||||
// };
|
||||
UIxMailPopupView = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailPopupView";
|
||||
};
|
||||
UIxMailEditor = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailEditor";
|
||||
|
||||
@@ -183,7 +183,6 @@
|
||||
SOGoWebAuthenticator *auth;
|
||||
SOGoAppointmentFolders *calendars;
|
||||
SOGoUserDefaults *ud;
|
||||
SOGoSystemDefaults *sd;
|
||||
SOGoUser *loggedInUser;
|
||||
NSDictionary *params;
|
||||
NSString *username, *password, *language, *domain, *remoteHost;
|
||||
@@ -226,13 +225,15 @@
|
||||
|
||||
response = [self responseWithStatus: 200
|
||||
andJSONRepresentation: json];
|
||||
|
||||
|
||||
// We get the proper username for cookie creation. If we are using a multidomain
|
||||
// environment with SOGoEnableDomainBasedUID, we could have to append the domain
|
||||
// to the username. Also when SOGoEnableDomainBasedUID is enabled, we could be in
|
||||
// the DomainLessLogin situation, so we would NOT add the domain. -getUIDForEmail
|
||||
// has all the logic for this, so lets use it.
|
||||
if ([domain isNotNull])
|
||||
{
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([sd enableDomainBasedUID] &&
|
||||
[username rangeOfString: @"@"].location == NSNotFound)
|
||||
username = [NSString stringWithFormat: @"%@@%@", username, domain];
|
||||
username = [[SOGoUserManager sharedUserManager] getUIDForEmail: username];
|
||||
}
|
||||
|
||||
authCookie = [auth cookieWithUsername: username
|
||||
|
||||
@@ -636,7 +636,7 @@
|
||||
* @apiExample {curl} Example usage:
|
||||
* curl -i http://localhost/SOGo/so/sogo1/foldersSearch?type=contact
|
||||
*
|
||||
* @apiParam {String} type Either 'calendar' or 'contact'
|
||||
* @apiParam {String} type Either 'calendar' or 'contact'. If nothing is specifed, its both.
|
||||
*
|
||||
* @apiSuccess (Success 200) {Object[]} folders List of matching folders
|
||||
* @apiSuccess (Success 200) {String} folders.name Path of folder
|
||||
@@ -648,27 +648,31 @@
|
||||
- (id <WOActionResults>) foldersSearchAction
|
||||
{
|
||||
NSString *folderType;
|
||||
NSArray *folders;
|
||||
NSMutableArray *folders;
|
||||
NSDictionary *message;
|
||||
id <WOActionResults> result;
|
||||
SOGoUserFolder *userFolder;
|
||||
|
||||
|
||||
folderType = [self queryParameterForKey: @"type"];
|
||||
folders = [NSMutableArray array];
|
||||
userFolder = [self clientObject];
|
||||
|
||||
if ([folderType length])
|
||||
{
|
||||
userFolder = [self clientObject];
|
||||
folders = [userFolder foldersOfType: folderType
|
||||
forUID: [userFolder ownerInContext: context]];
|
||||
result = [self _foldersResponseForResults: folders];
|
||||
[folders addObjectsFromArray: [userFolder foldersOfType: folderType
|
||||
forUID: [userFolder ownerInContext: context]]];
|
||||
}
|
||||
else
|
||||
{
|
||||
message = [NSDictionary dictionaryWithObject: [self labelForKey: @"Missing type parameter"]
|
||||
forKey: @"error"];
|
||||
result = [self responseWithStatus: 400 andJSONRepresentation: message];
|
||||
[folders addObjectsFromArray: [userFolder foldersOfType: @"calendar"
|
||||
forUID: [userFolder ownerInContext: context]]];
|
||||
[folders addObjectsFromArray: [userFolder foldersOfType: @"contact"
|
||||
forUID: [userFolder ownerInContext: context]]];
|
||||
}
|
||||
|
||||
result = [self _foldersResponseForResults: folders];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* toolbar */
|
||||
"Save and Close" = "Save and Close";
|
||||
"Close" = "Close";
|
||||
"Preferences saved" = "Preferences saved";
|
||||
|
||||
/* tabs */
|
||||
"General" = "General";
|
||||
@@ -317,6 +318,9 @@
|
||||
"matches regex" = "matches regex";
|
||||
"does not match regex" = "does not match regex";
|
||||
|
||||
/* Placeholder for the value field of a condition */
|
||||
"Value" = "Value";
|
||||
|
||||
"Seen" = "Seen";
|
||||
"Deleted" = "Deleted";
|
||||
"Answered" = "Answered";
|
||||
|
||||
@@ -255,6 +255,16 @@ static SoProduct *preferencesProduct = nil;
|
||||
forKey: @"PreventInvitationsWhitelist"];
|
||||
}
|
||||
|
||||
// Initialize some default values
|
||||
if (![settings objectForKey: @"Calendar"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Calendar"];
|
||||
|
||||
if (![settings objectForKey: @"Contact"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Contact"];
|
||||
|
||||
if (![settings objectForKey: @"Mail"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Mail"];
|
||||
|
||||
return [self _makeResponse: [[settings source] values]];
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,11 @@ static NSArray *reminderValues = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Preferences";
|
||||
}
|
||||
|
||||
// - (void) setHasChanged: (BOOL) newHasChanged
|
||||
// {
|
||||
// hasChanged = newHasChanged;
|
||||
@@ -1064,7 +1069,7 @@ static NSArray *reminderValues = nil;
|
||||
|
||||
if (!capabilities)
|
||||
{
|
||||
if ([self _sieveClient])
|
||||
if ([self isSieveScriptsEnabled] && [self _sieveClient])
|
||||
capabilities = [[self _sieveClient] capabilities];
|
||||
else
|
||||
capabilities = [NSArray array];
|
||||
@@ -2086,6 +2091,10 @@ static NSArray *reminderValues = nil;
|
||||
// We convert our object into a mutable one
|
||||
v = [[v mutableCopy] autorelease];
|
||||
|
||||
if ([[v objectForKey: @"SOGoLoginModule"] isEqualToString: @"Last"])
|
||||
[v setObject: [NSNumber numberWithBool: YES] forKey: @"SOGoRememberLastModule"];
|
||||
else
|
||||
[v setObject: [NSNumber numberWithBool: NO] forKey: @"SOGoRememberLastModule"];
|
||||
|
||||
// We remove short/long date formats if they are default ones
|
||||
if ([[v objectForKey: @"SOGoShortDateFormat"] isEqualToString: @"default"])
|
||||
|
||||
@@ -360,40 +360,19 @@ static SoProduct *commonProduct = nil;
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
SOGoObject *currentClient, *parent;
|
||||
BOOL found;
|
||||
NSString *hostLessURL;
|
||||
Class objectClass, userFolderClass;
|
||||
// , groupFolderClass
|
||||
|
||||
currentClient = [self clientObject];
|
||||
if (currentClient
|
||||
&& [currentClient isKindOfClass: [SOGoObject class]])
|
||||
if ([[self parent] respondsToSelector: @selector(modulePath)])
|
||||
{
|
||||
// groupFolderClass = [SOGoCustomGroupFolder class];
|
||||
userFolderClass = [SOGoUserFolder class];
|
||||
NSString *baseURL;
|
||||
|
||||
objectClass = [currentClient class];
|
||||
// found = (objectClass == groupFolderClass || objectClass == userFolderClass);
|
||||
found = (objectClass == userFolderClass);
|
||||
while (!found && currentClient)
|
||||
{
|
||||
parent = [currentClient container];
|
||||
objectClass = [parent class];
|
||||
if (// objectClass == groupFolderClass
|
||||
// ||
|
||||
objectClass == userFolderClass)
|
||||
found = YES;
|
||||
else
|
||||
currentClient = parent;
|
||||
}
|
||||
baseURL = [[self clientObject] baseURLInContext: context];
|
||||
|
||||
if ([baseURL hasSuffix: [NSString stringWithFormat: @"%@/", [[self parent] modulePath]]])
|
||||
return baseURL;
|
||||
|
||||
return [NSString stringWithFormat: @"%@%@", baseURL, [[self parent] modulePath]];
|
||||
}
|
||||
else
|
||||
currentClient = [WOApplication application];
|
||||
|
||||
hostLessURL = [[currentClient baseURLInContext: context] hostlessURL];
|
||||
|
||||
return [hostLessURL substringToIndex: [hostLessURL length] - 1];
|
||||
return @"SOGo";
|
||||
}
|
||||
|
||||
- (NSString *) ownPath
|
||||
|
||||
@@ -439,6 +439,9 @@ validate_endbeforestart = "The end date that you entered occurs before the st
|
||||
"Month View" = "Month View";
|
||||
"Reload" = "Reload";
|
||||
|
||||
/* Number of selected components in events or tasks list */
|
||||
"selected" = "selected";
|
||||
|
||||
"eventPartStatModificationError" = "Your participation status could not be modified.";
|
||||
|
||||
/* menu */
|
||||
|
||||
@@ -595,12 +595,12 @@
|
||||
* @apiSuccess (Success 200) {Boolean} alarm.attendees Alert attendees by email if true and action is email
|
||||
* @apiSuccess (Success 200) {Boolean} alarm.organizer Alert organizer by email if true and action is email
|
||||
*
|
||||
* @apiSuccess {_} ... _From [iCalEvent+SOGo attributes]_
|
||||
* @apiSuccess {_} ... _From [iCalEvent+SOGo attributesInContext:]_
|
||||
*
|
||||
* @apiSuccess (Success 200) {Number} isAllDay 1 if event is all-day
|
||||
* @apiSuccess (Success 200) {Number} isTransparent 1 if the event is not opaque
|
||||
*
|
||||
* @apiSuccess {_} .... _From [iCalEntityObject+SOGo attributes]_
|
||||
* @apiSuccess {_} .... _From [iCalEntityObject+SOGo attributesInContext:]_
|
||||
*
|
||||
* @apiSuccess (Success 200) {Number} sendAppointmentNotifications 1 if notifications must be sent
|
||||
* @apiSuccess (Success 200) {String} component "vevent"
|
||||
@@ -626,7 +626,7 @@
|
||||
* @apiSuccess (Success 200) {String} [attendees.delegatedTo] User that the original request was delegated to
|
||||
* @apiSuccess (Success 200) {String} [attendees.delegatedFrom] User the request was delegated from
|
||||
*
|
||||
* @apiSuccess {_} ..... _From [iCalRepeatableEntityObject+SOGo attributes]_
|
||||
* @apiSuccess {_} ..... _From [iCalRepeatableEntityObject+SOGo attributesInContext:]_
|
||||
*
|
||||
* @apiSuccess (Success 200) {Object} [repeat] Recurrence rule definition
|
||||
* @apiSuccess (Success 200) {String} repeat.frequency Either daily, (every weekday), weekly, (bi-weekly), monthly, or yearly
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
NSArray *weekDays;
|
||||
NSString *currentView, *timeFormat, *currentTableHour;
|
||||
NSCalendarDate *startDate, *currentTableDay;
|
||||
NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay;
|
||||
NSMutableArray *daysToDisplay, *calendarsToDisplay, *currentCalendar, *hoursToDisplay;
|
||||
unsigned int numberOfDays;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/WOResourceManager+SOGo.h>
|
||||
|
||||
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
|
||||
#import <SoObjects/Appointments/SOGoAppointmentFolders.h>
|
||||
|
||||
#import "UIxCalDayTable.h"
|
||||
|
||||
@class SOGoAppointment;
|
||||
@@ -184,7 +187,6 @@
|
||||
{
|
||||
if (!calendarsToDisplay)
|
||||
{
|
||||
int max=0, i;
|
||||
NSArray *folders;
|
||||
SOGoAppointmentFolders *co;
|
||||
SOGoAppointmentFolder *folder;
|
||||
@@ -292,6 +294,39 @@
|
||||
return [weekDays objectAtIndex: [currentTableDay dayOfWeek]];
|
||||
}
|
||||
|
||||
- (NSString *) labelForMonth
|
||||
{
|
||||
NSCalendarDate *nextDay;
|
||||
NSString *calendarFormat;
|
||||
BOOL isLastOrFirstDay;
|
||||
|
||||
isLastOrFirstDay = NO;
|
||||
calendarFormat = @"%b";
|
||||
|
||||
if ([currentView hasSuffix: @"dayview"])
|
||||
{
|
||||
isLastOrFirstDay = YES;
|
||||
calendarFormat = @"%B";
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([currentTableDay dayOfMonth] == 1)
|
||||
{
|
||||
isLastOrFirstDay = YES;
|
||||
}
|
||||
else if ([currentTableDay dayOfMonth] > 27)
|
||||
{
|
||||
nextDay = [currentTableDay dateByAddingYears: 0
|
||||
months: 0
|
||||
days: 1];
|
||||
if ([nextDay dayOfMonth] == 1)
|
||||
isLastOrFirstDay = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return isLastOrFirstDay? [currentTableDay descriptionWithCalendarFormat: calendarFormat locale: locale] : nil;
|
||||
}
|
||||
|
||||
- (NSString *) labelForDate
|
||||
{
|
||||
return [dateFormatter shortFormattedDate: currentTableDay];
|
||||
@@ -397,18 +432,23 @@
|
||||
numberOfDays];
|
||||
}
|
||||
|
||||
- (NSString *) daysViewClasses
|
||||
{
|
||||
NSString *daysView;
|
||||
// - (NSString *) daysViewClasses
|
||||
// {
|
||||
// NSString *daysView;
|
||||
|
||||
if ([currentView isEqualToString:@"multicolumndayview"])
|
||||
daysView = @"daysView daysViewForMultipleDays";
|
||||
// if ([currentView isEqualToString:@"multicolumndayview"])
|
||||
// daysView = @"daysView daysViewForMultipleDays";
|
||||
|
||||
else
|
||||
daysView = [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays];
|
||||
// else
|
||||
// daysView = [NSString stringWithFormat: @"daysView daysViewFor%dDays", numberOfDays];
|
||||
|
||||
return daysView;
|
||||
}
|
||||
// return daysView;
|
||||
//}
|
||||
|
||||
// - (NSString *) daysViewHeaderClasses
|
||||
// {
|
||||
// return [NSString stringWithFormat: @"%@ daysHeader", [self daysViewClasses]];
|
||||
// }
|
||||
|
||||
- (NSString *) dayClasses
|
||||
{
|
||||
@@ -416,22 +456,18 @@
|
||||
unsigned int currentDayNbr, realDayOfWeek;
|
||||
|
||||
classes = [NSMutableString string];
|
||||
if ([currentView isEqualToString:@"multicolumndayview"])
|
||||
[classes appendFormat:@"day dayColumn"];
|
||||
else {
|
||||
currentDayNbr = [daysToDisplay indexOfObject: currentTableDay];
|
||||
realDayOfWeek = [currentTableDay dayOfWeek];
|
||||
currentDayNbr = [daysToDisplay indexOfObject: currentTableDay];
|
||||
realDayOfWeek = [currentTableDay dayOfWeek];
|
||||
|
||||
[classes appendFormat: @"day day%d", currentDayNbr];
|
||||
[classes appendFormat: @"day day%d", currentDayNbr];
|
||||
|
||||
if (numberOfDays > 1)
|
||||
if (numberOfDays > 1)
|
||||
{
|
||||
if (realDayOfWeek == 0 || realDayOfWeek == 6)
|
||||
[classes appendString: @" weekEndDay"];
|
||||
if ([currentTableDay isToday])
|
||||
[classes appendString: @" dayOfToday"];
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
@@ -468,4 +504,9 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) isWeekView
|
||||
{
|
||||
return [currentView isEqualToString:@"weekview"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -94,7 +94,7 @@ static NSArray *tasksFields = nil;
|
||||
@"c_classification", @"c_category",
|
||||
@"c_partmails", @"c_partstates", @"c_owner",
|
||||
@"c_iscycle", @"c_nextalarm",
|
||||
@"c_recurrence_id", @"isException", @"editable",
|
||||
@"c_recurrence_id", @"isException", @"viewable", @"editable",
|
||||
@"erasable", @"ownerIsOrganizer", @"c_description", nil];
|
||||
[eventsFields retain];
|
||||
}
|
||||
@@ -104,7 +104,7 @@ static NSArray *tasksFields = nil;
|
||||
@"calendarName",
|
||||
@"c_status", @"c_title", @"c_enddate",
|
||||
@"c_classification", @"c_location", @"c_category",
|
||||
@"editable", @"erasable",
|
||||
@"viewable", @"editable", @"erasable",
|
||||
@"c_priority", @"c_owner",
|
||||
@"c_iscycle", @"c_nextalarm",
|
||||
@"c_recurrence_id", @"isException", @"c_description", nil];
|
||||
@@ -258,7 +258,7 @@ static NSArray *tasksFields = nil;
|
||||
|
||||
labelKey = [NSString stringWithFormat: @"%@_class%@",
|
||||
type, [component objectForKey: @"c_classification"]];
|
||||
[component setObject: [self labelForKey: labelKey]
|
||||
[component setObject: [self labelForKey: labelKey inContext: context]
|
||||
forKey: @"c_title"];
|
||||
}
|
||||
|
||||
@@ -328,16 +328,24 @@ static NSArray *tasksFields = nil;
|
||||
iCalPersonPartStat stat;
|
||||
NSUInteger count, max;
|
||||
|
||||
if ([[theRecord objectForKey: @"c_partmails"] length] > 0 &&
|
||||
[[theRecord objectForKey: @"c_partstates"] length] > 0)
|
||||
states = nil;
|
||||
|
||||
if ([[theRecord objectForKey: @"c_partmails"] length] > 0)
|
||||
{
|
||||
mails = [[theRecord objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"];
|
||||
states = [[theRecord objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
|
||||
|
||||
// We add some robustness here, in case the quick table had c_partmails defined but
|
||||
// no corresponding c_partstates entries. This can happen if folks toy around the database
|
||||
// or if Funambol was used in the past, with a broken connector.
|
||||
if ([[theRecord objectForKey: @"c_partstates"] length] > 0)
|
||||
states = [[theRecord objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"];
|
||||
|
||||
max = [mails count];
|
||||
statesDescription = [NSMutableArray arrayWithCapacity: max];
|
||||
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
stat = [[states objectAtIndex: count] intValue];
|
||||
stat = (states ? [[states objectAtIndex: count] intValue] : 0);
|
||||
[statesDescription addObject: [[iCalPerson descriptionForParticipationStatus: stat] lowercaseString]];
|
||||
}
|
||||
|
||||
@@ -441,21 +449,35 @@ static NSArray *tasksFields = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
id foo;
|
||||
|
||||
foo = [currentFolder fetchCoreInfosFrom: startDate
|
||||
currentInfos = [[currentFolder fetchCoreInfosFrom: startDate
|
||||
to: endDate
|
||||
title: value
|
||||
component: component];
|
||||
currentInfos = [foo objectEnumerator];
|
||||
component: component]
|
||||
objectEnumerator];
|
||||
}
|
||||
|
||||
owner = [currentFolder ownerInContext: context];
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
isErasable = ([owner isEqualToString: userLogin] || [[currentFolder aclsForUser: userLogin] containsObject: SOGoRole_ObjectEraser]);
|
||||
|
||||
|
||||
while ((newInfo = [currentInfos nextObject]))
|
||||
{
|
||||
if ([fields containsObject: @"viewable"])
|
||||
{
|
||||
if ([owner isEqualToString: userLogin])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
|
||||
else
|
||||
{
|
||||
role = [currentFolder roleForComponentsWithAccessClass: [[newInfo objectForKey: @"c_classification"] intValue]
|
||||
forUser : userLogin];
|
||||
|
||||
if ([role isEqualToString: @"ComponentViewer"])
|
||||
[newInfo setObject: [NSNumber numberWithInt: 1] forKey: @"viewable"];
|
||||
else
|
||||
[newInfo setObject: [NSNumber numberWithInt: 0] forKey: @"viewable"];
|
||||
}
|
||||
}
|
||||
|
||||
if ([fields containsObject: @"editable"])
|
||||
{
|
||||
if (folderIsRemote)
|
||||
@@ -548,19 +570,19 @@ static NSArray *tasksFields = nil;
|
||||
{
|
||||
// Same day
|
||||
if (forAllDay)
|
||||
return [self labelForKey: @"Today"];
|
||||
return [self labelForKey: @"Today" inContext: context];
|
||||
else
|
||||
return [dateFormatter formattedTime: date];
|
||||
}
|
||||
else if ([now dayOfCommonEra] - [date dayOfCommonEra] == 1)
|
||||
{
|
||||
// Yesterday
|
||||
return [self labelForKey: @"Yesterday"];
|
||||
return [self labelForKey: @"Yesterday" inContext: context];
|
||||
}
|
||||
else if ([date dayOfCommonEra] - [now dayOfCommonEra] == 1)
|
||||
{
|
||||
// Tomorrow
|
||||
return [self labelForKey: @"Tomorrow"];
|
||||
return [self labelForKey: @"Tomorrow" inContext: context];
|
||||
}
|
||||
else if (abs(delta = [date dayOfCommonEra] - [now dayOfCommonEra]) < 7)
|
||||
{
|
||||
@@ -573,7 +595,7 @@ static NSArray *tasksFields = nil;
|
||||
return dayOfWeek;
|
||||
else
|
||||
// With the past 7 days
|
||||
return [NSString stringWithFormat: [self labelForKey: @"last %@"], dayOfWeek];
|
||||
return [NSString stringWithFormat: [self labelForKey: @"last %@" inContext: context], dayOfWeek];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -695,7 +717,8 @@ static NSArray *tasksFields = nil;
|
||||
calendarSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: calendarSettings forKey: @"Calendar"];
|
||||
}
|
||||
[calendarSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", (ascending?1:0)], nil] forKey: submodule];
|
||||
[calendarSettings setObject: [NSArray arrayWithObjects: [sort lowercaseString], [NSString stringWithFormat: @"%d", [ascending intValue]], nil]
|
||||
forKey: submodule];
|
||||
[us synchronize];
|
||||
}
|
||||
}
|
||||
@@ -731,10 +754,11 @@ static NSArray *tasksFields = nil;
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partmails Participants email addresses
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partstates Participants states
|
||||
* @apiSuccess (Success 200) {String} events.c_owner Event's owner
|
||||
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic/recurring
|
||||
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
@@ -876,10 +900,10 @@ static inline iCalPersonPartStat _userStateInEvent (NSArray *event)
|
||||
states = [event objectAtIndex: eventPartStatesIndex];
|
||||
count = 0;
|
||||
max = [participants count];
|
||||
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
|
||||
roles: nil];
|
||||
while (state == iCalPersonPartStatOther && count < max)
|
||||
{
|
||||
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
|
||||
roles: nil];
|
||||
if ([user hasEmail: [participants objectAtIndex: count]])
|
||||
state = [[states objectAtIndex: count] intValue];
|
||||
else
|
||||
@@ -1201,7 +1225,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
SOGoAppointmentFolder *folder;
|
||||
NSMutableArray *selectedCalendars;
|
||||
NSArray *folders;
|
||||
NSString *fUID;
|
||||
NSString *fUID, *fName;
|
||||
NSNumber *isActive;
|
||||
unsigned int count, foldersCount;
|
||||
|
||||
@@ -1215,7 +1239,11 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
isActive = [NSNumber numberWithBool: [folder isActive]];
|
||||
if ([isActive intValue] != 0) {
|
||||
fUID = [folder nameInContainer];
|
||||
[selectedCalendars addObject: fUID];
|
||||
fName = [folder displayName];
|
||||
[selectedCalendars addObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
fUID, @"id",
|
||||
fName, @"name",
|
||||
nil]];
|
||||
}
|
||||
}
|
||||
return selectedCalendars;
|
||||
@@ -1253,6 +1281,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
@@ -1272,9 +1301,10 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
{
|
||||
int count, max;
|
||||
NSArray *events, *event, *calendars;
|
||||
NSDictionary *eventsBlocks;
|
||||
NSMutableArray *allDayBlocks, *blocks, *currentDay, *eventsByCalendars, *eventsForCalendar;
|
||||
NSDictionary *eventsBlocks, *calendar;
|
||||
NSMutableArray *allDayBlocks, *blocks, *currentDay, *eventsForCalendar, *eventsByCalendars;
|
||||
NSNumber *eventNbr;
|
||||
NSString *calendarName, *calendarId;
|
||||
BOOL isAllDay;
|
||||
int i, j;
|
||||
|
||||
@@ -1288,16 +1318,21 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
eventsByCalendars = [NSMutableArray arrayWithCapacity:[calendars count]];
|
||||
for (i = 0; i < [calendars count]; i++) // For each calendar
|
||||
{
|
||||
calendar = [calendars objectAtIndex:i];
|
||||
calendarName =[calendar objectForKey: @"name"];
|
||||
calendarId = [calendar objectForKey: @"id"];
|
||||
eventsForCalendar = [NSMutableArray array];
|
||||
[self _prepareEventBlocks: &blocks withAllDays: &allDayBlocks];
|
||||
for (j = 0; j < [events count]; j++) {
|
||||
if ([[[events objectAtIndex:j] objectAtIndex:eventFolderIndex] isEqualToString:[calendars objectAtIndex:i]]) {
|
||||
if ([[[events objectAtIndex:j] objectAtIndex:eventFolderIndex] isEqualToString: calendarId]) {
|
||||
// Event is in current calendar
|
||||
[eventsForCalendar addObject: [events objectAtIndex:j]];
|
||||
}
|
||||
}
|
||||
eventsBlocks = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
eventsFields, @"eventsFields",
|
||||
calendarId , @"id",
|
||||
calendarName, @"calendarName",
|
||||
eventsFields, @"eventsFields",
|
||||
eventsForCalendar, @"events",
|
||||
allDayBlocks, @"allDayBlocks",
|
||||
blocks, @"blocks", nil];
|
||||
@@ -1319,8 +1354,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
|
||||
[self _addBlocksWidth: currentDay];
|
||||
}
|
||||
|
||||
[eventsByCalendars insertObject:eventsBlocks atIndex:i];
|
||||
[eventsByCalendars addObject: eventsBlocks];
|
||||
}
|
||||
return [self _responseWithData: eventsByCalendars];
|
||||
}
|
||||
@@ -1356,7 +1390,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
[currentDay sortUsingSelector: @selector (compareEventByStart:)];
|
||||
[self _addBlocksWidth: currentDay];
|
||||
}
|
||||
return [self _responseWithData: eventsBlocks];
|
||||
return [self _responseWithData: [NSArray arrayWithObject: eventsBlocks]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,6 +1451,7 @@ _computeBlocksPosition (NSArray *blocks)
|
||||
* @apiSuccess (Success 200) {Number} tasks.c_classification 0: Public, 1: Private, 2: Confidential
|
||||
* @apiSuccess (Success 200) {String} tasks.c_location Location
|
||||
* @apiSuccess (Success 200) {String} tasks.c_category Category
|
||||
* @apiSuccess (Success 200) {Number} tasks.viewable 1 if task is viewable by the active user
|
||||
* @apiSuccess (Success 200) {Number} tasks.editable 1 if task is editable by the active user
|
||||
* @apiSuccess (Success 200) {Number} tasks.erasable 1 if task is erasable by the active user
|
||||
* @apiSuccess (Success 200) {String} tasks.c_priority Priority (0-9)
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
|
||||
@implementation UIxCalMainView
|
||||
|
||||
- (NSString *) modulePath
|
||||
{
|
||||
return @"Calendar";
|
||||
}
|
||||
|
||||
- (void) checkDefaultModulePreference
|
||||
{
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* UIxCalMonthView.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006-2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 Inverse inc.
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -69,7 +67,6 @@
|
||||
|
||||
- (void) setCurrentTableDay: (NSCalendarDate *) newCurrentTableDay;
|
||||
- (NSCalendarDate *) currentTableDay;
|
||||
- (NSString *) labelForCurrentDayCell;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/* UIxCalMonthView.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2006, 2007 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Copyright (C) 2006-2015 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
|
||||
@@ -235,27 +233,6 @@
|
||||
return currentWeek;
|
||||
}
|
||||
|
||||
- (NSString *) labelForCurrentDayCell
|
||||
{
|
||||
NSCalendarDate *lastDayOfMonth;
|
||||
NSString *label, *monthOfYear;
|
||||
int dayOfMonth;
|
||||
|
||||
dayOfMonth = [currentTableDay dayOfMonth];
|
||||
lastDayOfMonth = [currentTableDay lastDayOfMonth];
|
||||
if (dayOfMonth == 1
|
||||
|| [currentTableDay isDateOnSameDay: lastDayOfMonth])
|
||||
{
|
||||
monthOfYear
|
||||
= [monthNames objectAtIndex: [currentTableDay monthOfYear]];
|
||||
label = [NSString stringWithFormat: @"%d %@", dayOfMonth, monthOfYear];
|
||||
}
|
||||
else
|
||||
label = [NSString stringWithFormat: @"%d", dayOfMonth];
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
- (NSString *) headerDayCellClasses
|
||||
{
|
||||
unsigned int dayOfWeek;
|
||||
@@ -265,24 +242,19 @@
|
||||
return [NSString stringWithFormat: @"headerDay day%d", dayOfWeek];
|
||||
}
|
||||
|
||||
- (NSString *) dayHeaderNumber
|
||||
- (NSString *) dayHeaderMonth
|
||||
{
|
||||
NSString *nameOfMonth, *dayHeaderNumber;
|
||||
NSString *nameOfMonth = nil;
|
||||
unsigned int dayOfMonth;
|
||||
|
||||
dayOfMonth = [currentTableDay dayOfMonth];
|
||||
if (dayOfMonth == 1
|
||||
|| [currentTableDay isDateOnSameDay: [currentTableDay lastDayOfMonth]])
|
||||
{
|
||||
nameOfMonth
|
||||
= [self localizedNameForMonthOfYear: [currentTableDay monthOfYear]];
|
||||
dayHeaderNumber = [NSString stringWithFormat: @"%d %@", dayOfMonth,
|
||||
nameOfMonth];
|
||||
nameOfMonth = [self localizedNameForMonthOfYear: [currentTableDay monthOfYear]];
|
||||
}
|
||||
else
|
||||
dayHeaderNumber = [NSString stringWithFormat: @"%d", dayOfMonth];
|
||||
|
||||
return dayHeaderNumber;
|
||||
return nameOfMonth;
|
||||
}
|
||||
|
||||
- (NSString *) dayCellClasses
|
||||
|
||||
@@ -20,14 +20,6 @@
|
||||
|
||||
#import <SOGoUI/UIxComponent.h>
|
||||
|
||||
@class NSString;
|
||||
|
||||
@class SOGoAppointmentFolder;
|
||||
|
||||
@interface UIxCalendarFolderLinksTemplate : UIxComponent
|
||||
{
|
||||
SOGoAppointmentFolder *calendar;
|
||||
NSString *baseCalDAVURL, *basePublicCalDAVURL;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,89 +28,6 @@
|
||||
|
||||
@implementation UIxCalendarFolderLinksTemplate
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
calendar = [self clientObject];
|
||||
baseCalDAVURL = nil;
|
||||
basePublicCalDAVURL = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[baseCalDAVURL release];
|
||||
[basePublicCalDAVURL release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *) _baseCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!baseCalDAVURL)
|
||||
{
|
||||
davURL = [[calendar realDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
baseCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
baseCalDAVURL = davURL;
|
||||
[baseCalDAVURL retain];
|
||||
}
|
||||
|
||||
return baseCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) calDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _baseCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) webDavICSURL
|
||||
{
|
||||
return [calendar webDavICSURL];
|
||||
}
|
||||
|
||||
- (NSString *) webDavXMLURL
|
||||
{
|
||||
return [calendar webDavXMLURL];
|
||||
}
|
||||
|
||||
- (NSString *) _basePublicCalDAVURL
|
||||
{
|
||||
NSString *davURL;
|
||||
|
||||
if (!basePublicCalDAVURL)
|
||||
{
|
||||
davURL = [[calendar publicDavURL] absoluteString];
|
||||
if ([davURL hasSuffix: @"/"])
|
||||
basePublicCalDAVURL = [davURL substringToIndex: [davURL length] - 1];
|
||||
else
|
||||
basePublicCalDAVURL = davURL;
|
||||
[basePublicCalDAVURL retain];
|
||||
}
|
||||
|
||||
return basePublicCalDAVURL;
|
||||
}
|
||||
|
||||
- (NSString *) publicCalDavURL
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@/", [self _basePublicCalDAVURL]];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavICSURL
|
||||
{
|
||||
return [calendar publicWebDavICSURL];
|
||||
}
|
||||
|
||||
- (NSString *) publicWebDavXMLURL
|
||||
{
|
||||
return [calendar publicWebDavXMLURL];
|
||||
}
|
||||
|
||||
- (BOOL) isPublicAccessEnabled
|
||||
{
|
||||
// NOTE: This method is the same found in Common/UIxAclEditor.m
|
||||
@@ -118,14 +35,4 @@
|
||||
enablePublicAccess];
|
||||
}
|
||||
|
||||
- (BOOL) isWebCalendar
|
||||
{
|
||||
return ([calendar isKindOfClass: [SOGoWebAppointmentFolder class]]);
|
||||
}
|
||||
|
||||
- (NSString *) webCalendarURL
|
||||
{
|
||||
return [calendar folderPropertyValueInCategory: @"WebCalendars"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -842,7 +842,7 @@ static NSArray *reminderValues = nil;
|
||||
|
||||
clientObject = [self clientObject];
|
||||
ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
|
||||
if ([clientObject isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||
if ([[clientObject container] isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||
rc = componentReadableOnly;
|
||||
else
|
||||
{
|
||||
|
||||
@@ -182,6 +182,10 @@
|
||||
actionClass = "UIxCalFolderActions";
|
||||
actionName = "import";
|
||||
};
|
||||
UIxCalUserRightsEditor = {
|
||||
protectedBy = "ReadAcls";
|
||||
pageName = "UIxCalUserRightsEditor";
|
||||
};
|
||||
userRights = {
|
||||
protectedBy = "ReadAcls";
|
||||
pageName = "UIxCalUserRightsEditor";
|
||||
@@ -193,7 +197,7 @@
|
||||
actionName = "saveUserRights";
|
||||
};
|
||||
links = {
|
||||
protectedBy = "View";
|
||||
protectedBy = "Access Contents Information";
|
||||
pageName = "UIxCalendarFolderLinksTemplate";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,40 +1,123 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE var:component>
|
||||
<var:component xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:rsrc="OGo:url"
|
||||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:toolbar="UIxAdministration.toolbar"
|
||||
const:jsFiles="dtree.js">
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:rsrc="OGo:url"
|
||||
xmlns:label="OGo:label"
|
||||
className="UIxPageFrame"
|
||||
title="title"
|
||||
const:jsFiles="Common.js, Administration.js,
|
||||
Administration.services.js, Preferences.services.js,
|
||||
Contacts.services.js, Scheduler.services.js">
|
||||
|
||||
<div id="helpDialog" style="display: none" class="dialog left">
|
||||
<div>
|
||||
<h3><!-- module title --></h3>
|
||||
<div><!-- module description --></div>
|
||||
<a href="#" class="button" id="helpDialogClose"><span><var:string label:value="Close"/></span></a>
|
||||
<main class="view layout-fill" ui-view="administration" layout="row" ng-controller="navController">
|
||||
<!-- administration -->
|
||||
</main>
|
||||
|
||||
<script type="text/ng-template" id="administration.html">
|
||||
<md-sidenav id="left-sidenav" class="md-sidenav-left md-whiteframe-z1" md-component-id="left" md-is-locked-open="$mdMedia('gt-md')" layout="column">
|
||||
<var:component className="UIxSidenavToolbarTemplate" />
|
||||
<md-content md-scroll-y="md-scroll-y" class="md-flex md-hue-2">
|
||||
<md-list>
|
||||
<md-list-item ng-click="app.go('rights')"
|
||||
ui-sref="administration.rights"
|
||||
ui-sref-active="md-default-theme md-background md-bg md-hue-1">
|
||||
<md-icon>security</md-icon>
|
||||
<p class="sg-item-name"><var:string label:value="Rights"/></p>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</md-sidenav>
|
||||
|
||||
<section layout="column" class="sg-app-content layout-fill" ui-view="module"><!-- admin module --></section>
|
||||
|
||||
</script>
|
||||
|
||||
<!--
|
||||
RIGHTS ADMINISTRATION PANEL
|
||||
-->
|
||||
<script type="text/ng-template" id="rights.html">
|
||||
<!-- TOP RIGHT TOOLBAR -->
|
||||
<md-toolbar layout="row" layout-align="space-between center" class="toolbar-main">
|
||||
<var:component className="UIxTopnavToolbarTemplate" />
|
||||
</md-toolbar>
|
||||
|
||||
<div layout="row" class="md-flex">
|
||||
|
||||
<div class="view-list" layout="column">
|
||||
<md-toolbar>
|
||||
<!-- search toolbar -->
|
||||
<div class="md-toolbar-tools sg-toolbar-secondary"
|
||||
sg-search="app.filter(searchText)">
|
||||
<md-icon>search</md-icon>
|
||||
<md-input-container md-no-float="md-no-float">
|
||||
<input name="folderSearch" type="search" ng-minlength="3"
|
||||
label:placeholder="Search Users"/>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-content id="usersList" layout="column" class="md-flex">
|
||||
<md-list class="sg-section-list">
|
||||
<div ng-repeat="user in app.users">
|
||||
<md-list-item ng-class="{ 'sg-collapsed': user.uid != app.selectedUser.uid,
|
||||
'sg-expanded': user.uid == app.selectedUser.uid }"
|
||||
ng-click="app.selectUser($index)">
|
||||
<sg-avatar-image class="md-avatar"
|
||||
ng-if="::user.isGroup === 0"
|
||||
sg-email="::user.c_email"
|
||||
size="40">
|
||||
<!-- contact avatar -->
|
||||
</sg-avatar-image>
|
||||
<div class="sg-list-avatar"
|
||||
ng-show="::user.isGroup">
|
||||
<!-- list avatar -->
|
||||
</div>
|
||||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead">
|
||||
<div>{{::user.cn}}</div>
|
||||
</div>
|
||||
<div class="sg-md-body">
|
||||
<div>{{::user.c_email}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<md-button class="md-icon-button"><md-icon ng-class="{'md-rotate-180': user.uid == app.selectedUser.uid}">expand_more</md-icon></md-button>
|
||||
</md-list-item>
|
||||
<div ng-show="user == app.selectedUser">
|
||||
<!-- folders list -->
|
||||
<md-list-item ng-show="user.$$folders.length == 0">
|
||||
<md-icon>warning</md-icon>
|
||||
<var:string label:value="No resource"/>
|
||||
</md-list-item>
|
||||
<md-list-item class="sg-folder" ng-repeat="folder in user.$$folders">
|
||||
<md-button class=""
|
||||
aria-label="{{::folder.displayName}}" title="{{::folder.displayName}}"
|
||||
ng-click="app.selectFolder(folder)">
|
||||
<md-icon ng-class="{ 'icon-contacts': folder.type == 'Contact',
|
||||
'icon-event': folder.type == 'Appointment' }"><!--icon--></md-icon>
|
||||
{{::folder.displayName}}
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
<md-divider><!-- divider --></md-divider>
|
||||
</div>
|
||||
</div>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</div>
|
||||
|
||||
<div id="detailView" class="view-detail" layout="column" layout-align="start center"
|
||||
ng-class="{ 'sg-close': !app.selectedUser.selectedFolder }"
|
||||
ui-view="acl">
|
||||
<md-toolbar class="md-whiteframe-z1 hide-sm"><!-- empty toolbar --></md-toolbar>
|
||||
<md-content class="hide-sm md-flex layout-fill md-hue-1" layout="column">
|
||||
<md-input-container layout-align="center center">
|
||||
<label class="sg-md-title"><var:string label:value="No resource selected"/></label>
|
||||
</md-input-container>
|
||||
</md-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div id="leftPanel">
|
||||
<div class="titlediv"><var:string label:value="Modules" /></div>
|
||||
<div id="administrationModules">
|
||||
<ul>
|
||||
<li id="ACLs" class="_selected"><var:string label:value="ACLs"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dragHandle" id="verticalDragHandle"><!-- space --></div>
|
||||
|
||||
<div id="rightPanel">
|
||||
<span><h1 id="moduleTitle"><var:string label:value="ACLs_title"/></h1>
|
||||
<div id="moduleDescription" style="display: none"><var:string label:value="ACLs_description" const:escapeHTML="NO"/></div></span>
|
||||
<var:component className="UIxAdministrationFilterPanel" qualifier="qualifier" />
|
||||
<div id="administrationContent"><!-- space --></div>
|
||||
</div>
|
||||
|
||||
<var:string value="errorAlertJavaScript" const:escapeHTML="NO" />
|
||||
</var:component>
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE container>
|
||||
<container
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:var="http://www.skyrix.com/od/binding"
|
||||
xmlns:const="http://www.skyrix.com/od/constant"
|
||||
xmlns:label="OGo:label"
|
||||
>
|
||||
<div layout="column" class="layout-fill">
|
||||
<md-toolbar class="hide show-sm">
|
||||
<div class="md-toolbar-tools">
|
||||
<md-button class="sg-icon-button"
|
||||
label:aria-label="Close"
|
||||
ng-click="acl.close()">
|
||||
<md-icon>close</md-icon>
|
||||
</md-button>
|
||||
<div class="md-flex"><!-- spacer --></div>
|
||||
<md-button class="md-icon-button"
|
||||
ng-click="acl.save()">
|
||||
<md-icon>save</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-toolbar class="md-tall">
|
||||
<div layout="row">
|
||||
<sg-avatar-image class="md-tile-left"
|
||||
ng-show="acl.user.isGroup === 0"
|
||||
sg-email="::acl.user.c_email"
|
||||
size="40"><!-- avatar --></sg-avatar-image>
|
||||
<div class="sg-list-avatar" ng-show="acl.user.isGroup"><!--list avatar--></div>
|
||||
<div class="msg-header-content">
|
||||
<h1 class="sg-md-display-2--thin">{{::acl.user.cn}}</h1>
|
||||
<h6 class="sg-md-display-2-subheader"><md-icon ng-class="{ 'icon-contacts': acl.folderType == 'AddressBook',
|
||||
'icon-event': acl.folderType == 'Calendar' }"><!--icon--></md-icon>
|
||||
{{::acl.folder.name}}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<!-- only show the fab button on large screens -->
|
||||
<md-button type="button" class="hide-sm md-fab md-fab-bottom-right md-fab-overlap-bottom"
|
||||
ng-click="acl.save()">
|
||||
<md-icon>save</md-icon>
|
||||
</md-button>
|
||||
</md-toolbar>
|
||||
<md-content class="md-flex md-static">
|
||||
<div class="viewer">
|
||||
<div layout="row">
|
||||
<md-icon>search</md-icon>
|
||||
<md-autocomplete
|
||||
class="md-flex"
|
||||
md-input-name="addUser"
|
||||
md-search-text="acl.searchText"
|
||||
md-selected-item="acl.userToAdd"
|
||||
md-selected-item-change="acl.addUser(user)"
|
||||
md-items="user in acl.userFilter(acl.searchText)"
|
||||
md-item-text="user.empty"
|
||||
md-min-length="3"
|
||||
md-delay="300"
|
||||
md-no-cache="true"
|
||||
label:md-floating-label="Add User">
|
||||
<md-item-template>
|
||||
<span class="md-contact-suggestion">
|
||||
<span class="md-contact-name"
|
||||
md-highlight-text="acl.searchText"
|
||||
md-highlight-flags="^i">{{user.cn}}</span>
|
||||
<span class="md-contact-email"
|
||||
md-highlight-text="acl.searchText"
|
||||
md-highlight-flags="^i">{{user.c_email}}</span>
|
||||
</span>
|
||||
</md-item-template>
|
||||
</md-autocomplete>
|
||||
</div>
|
||||
<md-card ng-repeat="user in acl.users | orderBy:['userClass', 'displayName']"
|
||||
class="sg-collapsed"
|
||||
ng-class="{ 'sg-expanded': user.uid == acl.selectedUid }">
|
||||
<md-button ng-click="acl.selectUser(user)">
|
||||
<div layout="row" layout-align="start center" class="md-flex">
|
||||
<span class="card-picture" ng-switch="user.userClass">
|
||||
<div ng-switch-when="normal-user">
|
||||
<sg-avatar-image class="md-avatar"
|
||||
sg-email="user.c_email"
|
||||
size="40"><!-- avatar --></sg-avatar-image>
|
||||
</div>
|
||||
<div ng-switch-when="normal-group" class="md-avatar sg-avatar-list"><!-- normal-group --></div>
|
||||
<div ng-switch-when="public-user" class="md-avatar sg-avatar-list"><!-- public-user --></div>
|
||||
</span>
|
||||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead"><div>{{user.cn}}</div></div>
|
||||
<div class="sg-md-body"><div>{{user.c_email}}</div></div>
|
||||
</div>
|
||||
<md-button class="md-icon-button md-secondary" type="button"
|
||||
ng-click="acl.removeUser(user)"
|
||||
ng-hide="user.uid != acl.selectedUid || user.$isSpecial()">
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-button>
|
||||
<md-card-content id="AccessRightList" ng-show="user.uid == acl.selectedUid">
|
||||
<var:if condition="canSubscribeUsers">
|
||||
<md-checkbox ng-model="user.isSubscribed"
|
||||
label:arial-label="Subscribe User"
|
||||
ng-disabled="user.wasSubscribed"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0"
|
||||
ng-hide="user.$isSpecial()">
|
||||
<var:string label:value="Subscribe User"/>
|
||||
</md-checkbox>
|
||||
</var:if>
|
||||
<div ng-include="acl.getTemplate()"></div>
|
||||
|
||||
<!-- <script type="text/ng-template" id="UIxContactsUserRightsEditor">
|
||||
<var:component className="UIxContactsUserRightsEditor" />
|
||||
</script>
|
||||
|
||||
<script type="text/ng-template" id="UIxCalUserRightsEditor">
|
||||
<var:component className="UIxCalUserRightsEditor" />
|
||||
</script> -->
|
||||
|
||||
</md-card-content>
|
||||
</md-card>
|
||||
</div>
|
||||
</md-content>
|
||||
</div>
|
||||
</container>
|
||||
Loaded 100 of 283 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user