From d56d9f8b08592f4bc3b82b62d55b7b082a58e03a Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Mon, 15 Feb 2016 15:58:58 -0500 Subject: [PATCH 1/9] Handle attendee conflicts in the Web interface See @d7b010 --- .../Appointments/SOGoComponentOccurence.m | 9 ++- UI/Scheduler/UIxAppointmentActions.m | 9 ++- UI/Scheduler/UIxAppointmentEditor.m | 9 ++- .../UIxAppointmentEditorTemplate.wox | 44 ++++++++++++- UI/Templates/SchedulerUI/UIxCalMainView.wox | 35 +++++++++++ .../js/Common/navController.js | 4 +- .../js/Scheduler/CalendarListController.js | 61 ++++++++++++++++--- .../js/Scheduler/Component.service.js | 14 ++++- .../js/Scheduler/ComponentController.js | 12 ++-- 9 files changed, 176 insertions(+), 21 deletions(-) diff --git a/SoObjects/Appointments/SOGoComponentOccurence.m b/SoObjects/Appointments/SOGoComponentOccurence.m index f81a73f5a..c8a16030c 100644 --- a/SoObjects/Appointments/SOGoComponentOccurence.m +++ b/SoObjects/Appointments/SOGoComponentOccurence.m @@ -1,6 +1,6 @@ /* SOGoComponentOccurence.m - this file is part of SOGo * - * Copyright (C) 2008-2014 Inverse inc. + * Copyright (C) 2008-2016 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 @@ -211,6 +211,13 @@ return [container saveComponent: newObject]; } +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject + force: (BOOL) forceSave +{ + return [container saveComponent: newObject + force: forceSave]; +} + #warning most of SOGoCalendarComponent and SOGoComponentOccurence share the same external interface... \ they should be siblings or SOGoComponentOccurence the parent class of SOGoCalendarComponent... - (NSException *) changeParticipationStatus: (NSString *) newStatus diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index 7568dde44..a96129b52 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -22,6 +22,7 @@ #import #import #import +#import #import @@ -68,6 +69,7 @@ SOGoAppointmentFolder *targetCalendar, *sourceCalendar; SOGoAppointmentFolders *folders; BOOL forceSave; + id error; rq = [context request]; params = [[rq contentAsString] objectFromJSONString]; @@ -76,7 +78,7 @@ startDelta = [params objectForKey: @"start"]; durationDelta = [params objectForKey: @"duration"]; destionationCalendar = [params objectForKey: @"destination"]; - forceSave = NO; + forceSave = [[params objectForKey: @"ignoreConflicts"] boolValue]; if (daysDelta || startDelta || durationDelta) { @@ -150,8 +152,11 @@ if ([ex respondsToSelector: @selector(httpStatus)]) httpStatus = [ex httpStatus]; + error = [[ex reason] objectFromJSONString]; + if (error == nil) + error = [ex reason]; jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: - [ex reason], @"message", + error, @"message", nil]; response = [self responseWithStatus: httpStatus diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 8cc46110c..507f71890 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -467,6 +467,7 @@ SOGoAppointmentObject *co; SoSecurityManager *sm; WORequest *request; + id error; unsigned int httpStatus; BOOL forceSave; @@ -490,7 +491,7 @@ else { [self setAttributes: params]; - forceSave = NO; + forceSave = [[params objectForKey: @"ignoreConflicts"] boolValue]; if ([event hasRecurrenceRules]) [self _adjustRecurrentRules]; @@ -546,9 +547,11 @@ if ([ex respondsToSelector: @selector(httpStatus)]) httpStatus = [ex httpStatus]; + error = [[ex reason] objectFromJSONString]; + if (error == nil) + error = [ex reason]; jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: - [ex reason], @"message", - nil]; + error, @"message", nil]; } else { diff --git a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox index 134b25b54..79666f52a 100644 --- a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox +++ b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox @@ -275,7 +275,7 @@ - + @@ -287,6 +287,48 @@ + + + +
+ + close + +
+ +
+ person {{editor.attendeeConflictError.attendee_name}} ({{editor.attendeeConflictError.attendee_email}}) +
+
+ + schedule +
+ +
{{conflict.startDate}} trending_flat
+
+
+ +
{{conflict.endDate}}
+
+
+ + + + + + + + + + + diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index d448c3d4e..d248c9486 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -737,4 +737,39 @@ + + + diff --git a/UI/WebServerResources/js/Common/navController.js b/UI/WebServerResources/js/Common/navController.js index a6f78b5d8..934de0961 100644 --- a/UI/WebServerResources/js/Common/navController.js +++ b/UI/WebServerResources/js/Common/navController.js @@ -66,7 +66,7 @@ function onHttpError(event, response) { var message; - if (response.data && response.data.message) + if (response.data && response.data.message && angular.isString(response.data.message)) message = response.data.message; else if (response.status) message = response.statusText; @@ -85,7 +85,7 @@ position: 'top right' }); else - console.debug('untrap error'); + $log.debug('untrap error'); } // Listen to HTTP errors broadcasted from HTTP interceptor diff --git a/UI/WebServerResources/js/Scheduler/CalendarListController.js b/UI/WebServerResources/js/Scheduler/CalendarListController.js index 93793a05c..644371def 100644 --- a/UI/WebServerResources/js/Scheduler/CalendarListController.js +++ b/UI/WebServerResources/js/Scheduler/CalendarListController.js @@ -162,8 +162,7 @@ component.setDelta(coordinates.duration * 15); newComponent(null, component).finally(function() { $timeout(function() { - Component.$ghost.pointerHandler = null; - Component.$ghost.component = null; + Component.$resetGhost(); }); }); } @@ -186,8 +185,11 @@ // Immediately perform the adjustments component.$adjust(params).then(function() { $rootScope.$emit('calendars:list'); + }, function(response) { + onComponentAdjustError(response, component, params); + }).finally(function() { $timeout(function() { - Component.$ghost = {}; + Component.$resetGhost(); }); }); else if (component.occurrenceId) { @@ -199,7 +201,7 @@ params: params }, template: [ - '', + '', ' ', '

' + l('editRepeatingItem') + '

', '
', @@ -214,7 +216,7 @@ $rootScope.$emit('calendars:list'); }).finally(function() { $timeout(function() { - Component.$ghost = {}; + Component.$resetGhost(); }); }); } @@ -226,13 +228,58 @@ RecurrentComponentDialogController.$inject = ['$scope', '$mdDialog', 'component', 'params']; function RecurrentComponentDialogController($scope, $mdDialog, component, params) { $scope.updateThisOccurrence = function() { - component.$adjust(params).then($mdDialog.hide, $mdDialog.cancel); + component.$adjust(params).then($mdDialog.hide, function(response) { + $mdDialog.cancel().then(function() { + onComponentAdjustError(response, component, params); + }); + }); }; $scope.updateAllOccurrences = function() { delete component.occurrenceId; - component.$adjust(params).then($mdDialog.hide, $mdDialog.cancel); + component.$adjust(params).then($mdDialog.hide, function(response) { + $mdDialog.cancel().then(function() { + onComponentAdjustError(response, component, params); + }); + }); }; } + + function onComponentAdjustError(response, component, params) { + if (response.status == 403 && + response.data && response.data.message && angular.isObject(response.data.message)) { + $mdDialog.show({ + parent: angular.element(document.body), + clickOutsideToClose: false, + escapeToClose: false, + templateUrl: 'UIxAttendeeConflictDialog', + controller: AttendeeConflictDialogController, + controllerAs: '$AttendeeConflictDialogController', + locals: { + component: component, + params: params, + conflictError: response.data.message + } + }).then(function() { + $rootScope.$emit('calendars:list'); + }); + } + } + + /** + * @ngInject + */ + AttendeeConflictDialogController.$inject = ['$scope', '$mdDialog', 'component', 'params', 'conflictError']; + function AttendeeConflictDialogController($scope, $mdDialog, component, params, conflictError) { + var vm = this; + + vm.conflictError = conflictError; + vm.cancel = $mdDialog.cancel; + vm.save = save; + + function save() { + component.$adjust(angular.extend({ ignoreConflicts: true }, params)).then($mdDialog.hide); + } + } } function filter(filterpopup) { diff --git a/UI/WebServerResources/js/Scheduler/Component.service.js b/UI/WebServerResources/js/Scheduler/Component.service.js index 7390fbe84..a6ab49487 100644 --- a/UI/WebServerResources/js/Scheduler/Component.service.js +++ b/UI/WebServerResources/js/Scheduler/Component.service.js @@ -414,6 +414,15 @@ }); }; + /** + * @function $resetGhost + * @desc Prepare the ghost object for the next drag by resetting appropriate attributes + */ + Component.$resetGhost = function() { + this.$ghost.pointerHandler = null; + this.$ghost.component = null; + }; + /** * @function $parseDate * @desc Parse a date string with format YYYY-MM-DDTHH:MM @@ -1035,8 +1044,9 @@ * @function $save * @memberof Component.prototype * @desc Save the component to the server. + * @param {object} extraAttributes - additional attributes to send to the server */ - Component.prototype.$save = function() { + Component.prototype.$save = function(extraAttributes) { var _this = this, options, path, component, date, dlp; component = this.$omit(); @@ -1106,6 +1116,8 @@ if (this.occurrenceId) path.push(this.occurrenceId); + angular.extend(component, extraAttributes); + return Component.$$resource.save(path.join('/'), component, options) .then(function(data) { // Make a copy of the data for an eventual reset diff --git a/UI/WebServerResources/js/Scheduler/ComponentController.js b/UI/WebServerResources/js/Scheduler/ComponentController.js index 3bc62fdfd..fc5f3df36 100644 --- a/UI/WebServerResources/js/Scheduler/ComponentController.js +++ b/UI/WebServerResources/js/Scheduler/ComponentController.js @@ -162,6 +162,7 @@ vm.addAttachUrl = addAttachUrl; vm.cancel = cancel; vm.save = save; + vm.attendeeConflictError = false; vm.attendeesEditor = { days: getDays(), hours: getHours() @@ -217,15 +218,18 @@ } } - function save(form) { + function save(form, options) { if (form.$valid) { - vm.component.$save() + vm.component.$save(options) .then(function(data) { $rootScope.$emit('calendars:list'); $mdDialog.hide(); Alarm.getAlarms(); - }, function(data, status) { - $log.debug('failed'); + }, function(response) { + if (response.status == 403 && + response.data && response.data.message && angular.isObject(response.data.message)) { + vm.attendeeConflictError = response.data.message; + } }); } } From 42131c564afa8c8956ed666bfc3ee2d84db52b29 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Mon, 15 Feb 2016 16:04:18 -0500 Subject: [PATCH 2/9] (fix) support EAS MIMETruncation --- ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 66 +++++++++++-------- ActiveSync/SOGoMailObject+ActiveSync.m | 74 ++++++++++++++++++---- 2 files changed, 102 insertions(+), 38 deletions(-) diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 0faa6a20c..705248cb2 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -1388,7 +1388,7 @@ 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, *folderKey; + NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *lastServerKey, *syncKeyInCache, *folderKey; NSMutableDictionary *folderMetadata, *folderOptions; NSMutableArray *supportedElements, *supportedElementNames; NSMutableString *changeBuffer, *commandsBuffer; @@ -1500,48 +1500,60 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; if (!bodyPreferenceType) - { - bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"]; + { + bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"]; - // By default, send MIME mails. See #3146 for details. - if (!bodyPreferenceType) - bodyPreferenceType = @"4"; + // By default, send MIME mails. See #3146 for details. + if (!bodyPreferenceType) + bodyPreferenceType = @"4"; - mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"]; + mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"]; + mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"]; - if (!mimeSupport) - mimeSupport = @"1"; - } + if (!mimeSupport) + mimeSupport = @"1"; + + if (!mimeTruncation) + mimeTruncation = @"8"; + } else - { - mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue]; + { + mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue]; + mimeTruncation = [[(id)[theDocumentElement getElementsByTagName: @"MIMETruncation"] lastObject] textValue]; - if (!mimeSupport) + if (!mimeSupport) mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"]; - if (!mimeSupport) + if (!mimeSupport) mimeSupport = @"0"; - if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"]) + if (!mimeTruncation) + mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"]; + + if (!mimeTruncation) + mimeTruncation = @"8"; + + if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"]) bodyPreferenceType = @"2"; - else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"]) + else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"]) bodyPreferenceType = @"4"; - else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"]) + else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"]) bodyPreferenceType = @"2"; - - // Avoid writing to cache if there is nothing to change. - if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] || - ![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport]) - { - folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil]; - [folderMetadata setObject: folderOptions forKey: @"FolderOptions"]; - [self _setFolderMetadata: folderMetadata forKey: folderKey]; - } - } + // Avoid writing to cache if there is nothing to change. + if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] || + ![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport] || + ![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"] isEqualToString: mimeTruncation]) + { + folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", mimeTruncation, @"MIMETruncation", bodyPreferenceType, @"BodyPreferenceType", nil]; + [folderMetadata setObject: folderOptions forKey: @"FolderOptions"]; + [self _setFolderMetadata: folderMetadata forKey: folderKey]; + } + } [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; [context setObject: mimeSupport forKey: @"MIMESupport"]; + [context setObject: mimeTruncation forKey: @"MIMETruncation"]; [context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"]; // diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 78391f2b5..b23416604 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -695,13 +695,14 @@ struct GlobalObjectId { NSMutableString *s; id value; - int preferredBodyType, mimeSupport, nativeBodyType; + int preferredBodyType, mimeSupport, mimeTruncation, nativeBodyType; uint32_t v; subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue]; mimeSupport = [[context objectForKey: @"MIMESupport"] intValue]; + mimeTruncation = [[context objectForKey: @"MIMETruncation"] intValue]; s = [NSMutableString string]; @@ -1007,9 +1008,64 @@ struct GlobalObjectId { AUTORELEASE(content); content = [content activeSyncRepresentationInContext: context]; - truncated = 0; - len = [content length]; + truncated = 1; + + // We handle MIMETruncation + switch (mimeTruncation) + { + case 0: + { + content = @""; + len = 0; + } + break; + case 1: + { + content = [content substringToIndex: 4096]; + len = 4096; + } + break; + case 2: + { + content = [content substringToIndex: 5120]; + len = 5120; + } + break; + case 3: + { + content = [content substringToIndex: 7168]; + len = 7168; + } + break; + case 4: + { + content = [content substringToIndex: 10240]; + len = 10240; + } + break; + case 5: + { + content = [content substringToIndex: 20480]; + len = 20480; + } + break; + case 6: + { + content = [content substringToIndex: 51200]; + len = 51200; + } + break; + case 7: + { + content = [content substringToIndex: 102400]; + len = 102400; + } + break; + case 8: + default: + truncated = 0; + } if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) { @@ -1017,7 +1073,7 @@ struct GlobalObjectId { [s appendFormat: @"%d", truncated]; } else - { + { [s appendString: @""]; // Set the correct type if client requested text/html but we got text/plain. @@ -1031,16 +1087,12 @@ struct GlobalObjectId { [s appendFormat: @"%d", truncated]; [s appendFormat: @""]; - - if (!truncated) - { - [s appendFormat: @"%@", content]; - [s appendFormat: @"%d", len]; - } + [s appendFormat: @"%@", content]; + [s appendFormat: @"%d", len]; [s appendString: @""]; } } - + // Attachments -namespace 16 attachmentKeys = [self fetchFileAttachmentKeys]; From 929915542c9b516401384394f5406b4b57cbc74b Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Mon, 15 Feb 2016 16:12:21 -0500 Subject: [PATCH 3/9] Make sure we don't go overbounds --- ActiveSync/SOGoMailObject+ActiveSync.m | 63 ++++++++++++++------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index b23416604..8af2089f4 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -1021,46 +1021,53 @@ struct GlobalObjectId { } break; case 1: - { - content = [content substringToIndex: 4096]; - len = 4096; - } + if ([content length] > 4096) + { + content = [content substringToIndex: 4096]; + len = 4096; + } break; case 2: - { - content = [content substringToIndex: 5120]; - len = 5120; - } + if ([content length] > 5120) + { + content = [content substringToIndex: 5120]; + len = 5120; + } break; case 3: - { - content = [content substringToIndex: 7168]; - len = 7168; - } + if ([content length] > 7168) + { + content = [content substringToIndex: 7168]; + len = 7168; + } break; case 4: - { - content = [content substringToIndex: 10240]; - len = 10240; - } + if ([content length] > 10240) + { + content = [content substringToIndex: 10240]; + len = 10240; + } break; case 5: - { - content = [content substringToIndex: 20480]; - len = 20480; - } + if ([content length] > 20480) + { + content = [content substringToIndex: 20480]; + len = 20480; + } break; case 6: - { - content = [content substringToIndex: 51200]; - len = 51200; - } + if ([content length] > 51200) + { + content = [content substringToIndex: 51200]; + len = 51200; + } break; case 7: - { - content = [content substringToIndex: 102400]; - len = 102400; - } + if ([content length] > 102400) + { + content = [content substringToIndex: 102400]; + len = 102400; + } break; case 8: default: From 635c647f48ee59fb03a55d5ca0eefbe0127ab6a9 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Mon, 15 Feb 2016 17:40:17 -0500 Subject: [PATCH 4/9] Updated NEWS file regarding previous commits --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 01d8b630b..aaa6687c5 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,7 @@ Enhancements - [web] improved confirm dialogs for deletions - [web] allow resources to prevent invitations (#3410) - [web] warn when double-booking attendees and offer force save option + - [eas] now support EAS MIME truncation Bug fixes - [web] handle birthday dates before 1970 From e51f8625d60c53e65d3d0c0da4caab11a28ea223 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Tue, 16 Feb 2016 08:32:04 -0500 Subject: [PATCH 5/9] (fix) don't mark content as truncated if it's not --- ActiveSync/SOGoMailObject+ActiveSync.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index 8af2089f4..aad7a7fce 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -1009,7 +1009,7 @@ struct GlobalObjectId { content = [content activeSyncRepresentationInContext: context]; len = [content length]; - truncated = 1; + truncated = 0; // We handle MIMETruncation switch (mimeTruncation) @@ -1024,49 +1024,49 @@ struct GlobalObjectId { if ([content length] > 4096) { content = [content substringToIndex: 4096]; - len = 4096; + len = 4096; truncated = 1; } break; case 2: if ([content length] > 5120) { content = [content substringToIndex: 5120]; - len = 5120; + len = 5120; truncated = 1; } break; case 3: if ([content length] > 7168) { content = [content substringToIndex: 7168]; - len = 7168; + len = 7168; truncated = 1; } break; case 4: if ([content length] > 10240) { content = [content substringToIndex: 10240]; - len = 10240; + len = 10240; truncated = 1; } break; case 5: if ([content length] > 20480) { content = [content substringToIndex: 20480]; - len = 20480; + len = 20480; truncated = 1; } break; case 6: if ([content length] > 51200) { content = [content substringToIndex: 51200]; - len = 51200; + len = 51200; truncated = 1; } break; case 7: if ([content length] > 102400) { content = [content substringToIndex: 102400]; - len = 102400; + len = 102400; truncated = 1; } break; case 8: From 2edacc94ce685a040af1a9024ace97a1e62c6a45 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 16 Feb 2016 10:35:20 -0500 Subject: [PATCH 6/9] (html) Fix email notification field for calendar Fixes #3522 --- UI/Templates/SchedulerUI/UIxCalendarProperties.wox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/Templates/SchedulerUI/UIxCalendarProperties.wox b/UI/Templates/SchedulerUI/UIxCalendarProperties.wox index d05041da0..174ff56f9 100644 --- a/UI/Templates/SchedulerUI/UIxCalendarProperties.wox +++ b/UI/Templates/SchedulerUI/UIxCalendarProperties.wox @@ -82,7 +82,7 @@ From 2d3a5b4243bf6e1c3d528b5630e185d34d72f42e Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 16 Feb 2016 10:38:38 -0500 Subject: [PATCH 7/9] (html) Improve actions buttons of event editor --- .../UIxAppointmentViewTemplate.wox | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/UI/Templates/SchedulerUI/UIxAppointmentViewTemplate.wox b/UI/Templates/SchedulerUI/UIxAppointmentViewTemplate.wox index 5b0bd694b..6f2bb09a6 100644 --- a/UI/Templates/SchedulerUI/UIxAppointmentViewTemplate.wox +++ b/UI/Templates/SchedulerUI/UIxAppointmentViewTemplate.wox @@ -211,26 +211,39 @@ - - - - - - - - - + + repeat_one + + + + + repeat + +
- - - - - - + + + arrow_drop_down + + + + + repeat_one + + + + + repeat + + + +
@@ -251,49 +264,41 @@ label:aria-label="Delete Event" ng-click="$mdOpenMenu()" md-menu-origin="md-menu-origin"> - + arrow_drop_down - - - - - - - - - + + repeat_one + + + + + repeat + +
- - - - - - - - - + - + arrow_drop_down - - + + repeat_one - - - - - - + + + + repeat + +
From 3d7192ffef888891d51803f47e2e53b63783cd0c Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 16 Feb 2016 10:41:06 -0500 Subject: [PATCH 8/9] Update NEWS file --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index aaa6687c5..80aa5b1c1 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,7 @@ Bug fixes - [web] fixed batched delete of components (#3516) - [web] fixed mail draft autosave in preferences (#3519) - [web] fixed password change (#3496) + - [web] fixed saving of notification email for calendar changes (#3522) - [eas] allow EAS attachments get on 2nd-level mailboxes (#3505) - [eas] fix EAS bday shift (#3518) From 4c0141769009f98e0f53fba39408ddf9633ecc4b Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Tue, 16 Feb 2016 12:40:59 -0500 Subject: [PATCH 9/9] Handle resource limits in the Web interface See @d7b010 --- .../Appointments/SOGoAppointmentObject.m | 6 +- .../UIxAppointmentEditorTemplate.wox | 67 ++++++++++++------- UI/Templates/SchedulerUI/UIxCalMainView.wox | 11 ++- .../js/Scheduler/ComponentController.js | 4 +- .../scss/components/dialog/dialog.scss | 16 +++++ 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 2e92b85aa..ad4153846 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -665,7 +665,7 @@ else { iCalCalendar *calendar; - NSDictionary *values; + NSDictionary *values, *info; NSString *reason; iCalEvent *event; @@ -682,8 +682,10 @@ reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; + info = [NSDictionary dictionaryWithObject: reason forKey: @"reject"]; + return [NSException exceptionWithHTTPStatus: 403 - reason: reason]; + reason: [info jsonRepresentation]]; } } // diff --git a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox index 79666f52a..6a1427bc4 100644 --- a/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox +++ b/UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox @@ -289,33 +289,32 @@ - -
- - close - -
- -
- person {{editor.attendeeConflictError.attendee_name}} ({{editor.attendeeConflictError.attendee_email}}) + +
+
+ + close + +
+
+ person {{editor.attendeeConflictError.attendee_name}} ({{editor.attendeeConflictError.attendee_email}}) +
+
+ schedule +
+ +
{{conflict.startDate}} trending_flat
+
+
+ +
{{conflict.endDate}}
- - - schedule -
- -
{{conflict.startDate}} trending_flat
-
- -
{{conflict.endDate}}
-
-
- + + @@ -329,6 +328,24 @@ + + + +
{{editor.attendeeConflictError.reject}}
+ + close + +
+ + + + + + + + diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index d248c9486..3d11e4aca 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -740,8 +740,9 @@