Fix handling of Web calendars
- handle Web calendars that require authentication; - properly save the "reload on login" option; - reload Web calendars when clicking on the reload button on top of the events/tasks list; - properly activate the checkbox of new calendars (as they are enabled by default). Fixes #3326pull/208/head
parent
199a5f5215
commit
e396e29430
2
NEWS
2
NEWS
|
@ -20,6 +20,7 @@ Enhancements
|
|||
- [web] style events depending on the user participation state
|
||||
- [web] style transparent events (show time as free) (#3192)
|
||||
- [web] improved input parsing of time picker (#3659)
|
||||
- [web] restored support for Web calendars that require authentication
|
||||
|
||||
Bug fixes
|
||||
- [core] properly escape wide characters (#3616)
|
||||
|
@ -53,6 +54,7 @@ Bug fixes
|
|||
- [web] adapted time picker to match changes of md calendar picker
|
||||
- [web] fixed sender addresses of draft when using multiple IMAP accounts (#3577)
|
||||
- [web] create a new message when clicking on a "mailto" link (#3588)
|
||||
- [web] fixed handling of Web calendars option "reload on login"
|
||||
- [dav] we now handle the default classifications for tasks (#3541)
|
||||
- [eas] properly unfold long mail headers (#3152)
|
||||
- [eas] correctly set EAS message class for S/MIME messages (#3576)
|
||||
|
|
|
@ -206,8 +206,7 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
|
|||
|
||||
- (BOOL) reloadOnLogin
|
||||
{
|
||||
return [[self folderPropertyValueInCategory: @"AutoReloadedWebCalendars"]
|
||||
boolValue];
|
||||
return [[self folderPropertyValueInCategory: @"AutoReloadedWebCalendars"] boolValue];
|
||||
}
|
||||
|
||||
- (NSException *) delete
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
= "Potentially anyone on the Internet will be able to access your address book \"%{0}\", even if they do not have an account on this system. Is this information suitable for the public Internet?";
|
||||
"Give Access" = "Give Access";
|
||||
"Keep Private" = "Keep Private";
|
||||
|
||||
/* generic.js */
|
||||
"Unable to subscribe to that folder!"
|
||||
= "Unable to subscribe to that folder!";
|
||||
|
@ -76,16 +77,19 @@
|
|||
"45 minutes" = "45 minutes";
|
||||
"1 hour" = "1 hour";
|
||||
"1 day" = "1 day";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
"Cancel" = "Cancel";
|
||||
"Yes" = "Yes";
|
||||
"No" = "No";
|
||||
|
||||
/* alarms */
|
||||
"Reminder" = "Reminder";
|
||||
"Start" = "Start";
|
||||
"Due Date" = "Due Date";
|
||||
"Location" = "Location";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Important";
|
||||
"Work" = "Work";
|
||||
|
@ -105,5 +109,14 @@
|
|||
"No such user." = "No such user.";
|
||||
"You cannot (un)subscribe to a folder that you own!" = "You cannot (un)subscribe to a folder that you own!";
|
||||
|
||||
/* Authentication username */
|
||||
"Username" = "Username";
|
||||
|
||||
/* Authentication password */
|
||||
"Password" = "Password";
|
||||
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Wrong username or password.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters";
|
|
@ -193,8 +193,7 @@
|
|||
NSString *folderName;
|
||||
|
||||
[self _setupContext];
|
||||
folderSubscription
|
||||
= [moduleSettings objectForKey: @"InactiveFolders"];
|
||||
folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"];
|
||||
if (!folderSubscription)
|
||||
{
|
||||
folderSubscription = [NSMutableArray array];
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2006-2015 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
Copyright (C) 2006-2016 Inverse inc.
|
||||
|
||||
This file is part of SOGo
|
||||
|
||||
|
@ -15,7 +14,7 @@
|
|||
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
|
||||
License along with SOGo; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
||||
02111-1307, USA.
|
||||
*/
|
||||
|
@ -34,6 +33,7 @@
|
|||
#import <NGCards/iCalCalendar.h>
|
||||
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
|
||||
#import <Appointments/SOGoWebAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentFolderICS.h>
|
||||
|
@ -142,22 +142,24 @@
|
|||
httpCode = 200;
|
||||
results = [[self clientObject] loadWebCalendar];
|
||||
|
||||
if ([results objectForKey: @"error"])
|
||||
httpCode = 500;
|
||||
if ([results objectForKey: @"status"])
|
||||
httpCode = [[results objectForKey: @"status"] intValue];
|
||||
|
||||
return [self responseWithStatus: httpCode andJSONRepresentation: results];
|
||||
}
|
||||
|
||||
- (WOResponse *) setCredentialsAction
|
||||
{
|
||||
NSDictionary *params;
|
||||
NSString *username, *password;
|
||||
WORequest *request;
|
||||
WOResponse *response;
|
||||
NSString *username, *password;
|
||||
|
||||
request = [context request];
|
||||
params = [[request contentAsString] objectFromJSONString];
|
||||
|
||||
username = [[request formValueForKey: @"username"] stringByTrimmingSpaces];
|
||||
password = [[request formValueForKey: @"password"] stringByTrimmingSpaces];
|
||||
username = [[params objectForKey: @"username"] stringByTrimmingSpaces];
|
||||
password = [[params objectForKey: @"password"] stringByTrimmingSpaces];
|
||||
if ([username length] > 0 && [password length] > 0)
|
||||
{
|
||||
[[self clientObject] setUsername: username
|
||||
|
@ -165,10 +167,10 @@
|
|||
response = [self responseWith204];
|
||||
}
|
||||
else
|
||||
response
|
||||
= (WOResponse *) [NSException exceptionWithHTTPStatus: 400
|
||||
reason: @"missing 'username' and/or"
|
||||
@" 'password' parameters"];
|
||||
response = [self responseWithStatus: 400
|
||||
andJSONRepresentation: [NSDictionary dictionaryWithObject: @"missing 'username' and/or"
|
||||
@" 'password' parameters"
|
||||
forKey: @"message"]];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,10 @@
|
|||
if ([o isKindOfClass: [NSNumber class]])
|
||||
[calendar setSynchronize: [o boolValue]];
|
||||
|
||||
o = [params objectForKey: @"reloadOnLogin"];
|
||||
if ([o isKindOfClass: [NSNumber class]] && [calendar isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||
[(SOGoWebAppointmentFolder *) calendar setReloadOnLogin: [o boolValue]];
|
||||
|
||||
values = [params objectForKey: @"notifications"];
|
||||
if ([values isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
|
|
|
@ -195,6 +195,11 @@
|
|||
<var:string label:value="Unsubscribe Calendar"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.exportCalendar(calendar)">
|
||||
<var:string label:value="Export"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-divider><!-- divider --></md-menu-divider>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="app.showLinks(calendar)">
|
||||
|
@ -227,7 +232,13 @@
|
|||
ng-true-value="1"
|
||||
ng-false-value="0"
|
||||
label:aria-label="Enable"><!-- enable --></md-checkbox>
|
||||
<p class="sg-item-name" ng-show="app.editMode != calendar.id">{{calendar.name}}</p>
|
||||
<p class="sg-item-name" ng-show="app.editMode != calendar.id">
|
||||
<span class="ng-hide" ng-show="calendar.$error">
|
||||
<md-icon class="md-warn">warning</md-icon>
|
||||
<md-tooltip>{{ calendar.$error }}</md-tooltip>
|
||||
</span>
|
||||
{{calendar.name}}
|
||||
</p>
|
||||
<md-input-container class="md-flex"
|
||||
ng-show="app.editMode == calendar.id">
|
||||
<input class="sg-item-name" type="text"
|
||||
|
@ -645,6 +656,7 @@
|
|||
</section>
|
||||
</script>
|
||||
|
||||
<!-- modal inner content for acl editor -->
|
||||
<script type="text/ng-template" id="UIxUserRightsEditor">
|
||||
<var:component className="UIxCalUserRightsEditor" />
|
||||
</script>
|
||||
|
@ -724,4 +736,37 @@
|
|||
</md-dialog>
|
||||
</script>
|
||||
|
||||
<!-- modal for Web calendar authentication -->
|
||||
<script type="text/ng-template" id="UIxWebCalendarAuthDialog">
|
||||
<md-dialog flex="50" flex-xs="90">
|
||||
<form name="authForm" ng-submit="$WebCalendarAuthDialogController.authenticate(authForm)">
|
||||
<md-dialog-content class="md-dialog-content" layout="column">
|
||||
<h2 class="md-title" ng-bind="$WebCalendarAuthDialogController.title"><!-- title --></h2>
|
||||
<md-input-container>
|
||||
<label>{{ ::'Username' | loc }}</label>
|
||||
<input type="text" name="username"
|
||||
ng-model="$WebCalendarAuthDialogController.username" md-autofocus="true" required="required" />
|
||||
</md-input-container>
|
||||
<md-input-container>
|
||||
<label>{{ ::'Password' | loc }}</label>
|
||||
<input type="password" name="password"
|
||||
ng-model="$WebCalendarAuthDialogController.password" required="required" />
|
||||
<div ng-messages="authForm.password.$error">
|
||||
<div ng-message="credentials">{{ ::'Wrong username or password.' | loc }}</div>
|
||||
</div>
|
||||
</md-input-container>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions>
|
||||
<md-button ng-click="$WebCalendarAuthDialogController.cancel()"
|
||||
aria-label="::'Cancel' | loc"
|
||||
ng-bind="::'Cancel' | loc"><!-- Cancel --></md-button>
|
||||
<md-button class="md-primary" type="submit"
|
||||
ng-disabled="authForm.$error.required"
|
||||
aria-label="::'OK' | loc"
|
||||
ng-bind="::'OK' | loc"><!-- Submit --></md-button>
|
||||
</md-dialog-actions>
|
||||
</form>
|
||||
</md-dialog>
|
||||
</script>
|
||||
|
||||
</var:component>
|
||||
|
|
|
@ -77,10 +77,7 @@
|
|||
})
|
||||
.then(function(response) {
|
||||
return deferred.resolve(response.data);
|
||||
}, function(response) {
|
||||
if (response.status == 404)
|
||||
return deferred.reject();
|
||||
});
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
|
|
@ -231,20 +231,48 @@
|
|||
urls: { webCalendarURL: url }
|
||||
});
|
||||
var calendar = new Calendar(calendarData);
|
||||
Calendar.$add(calendar);
|
||||
Calendar.$$resource.fetch(calendar.id, 'reload').then(function(data) {
|
||||
// TODO: show a toast of the reload status
|
||||
Calendar.$log.debug(JSON.stringify(data, undefined, 2));
|
||||
});
|
||||
Calendar.$add(calendar);
|
||||
d.resolve();
|
||||
}, function() {
|
||||
}, function(response) {
|
||||
if (response.status == 401) {
|
||||
// Web calendar requires authentication
|
||||
d.resolve(calendar);
|
||||
}
|
||||
else {
|
||||
d.reject();
|
||||
}
|
||||
});
|
||||
}, d.reject);
|
||||
}
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function reloadWebCalendars
|
||||
* @memberof Calendar
|
||||
* @desc Reload all Web calendars
|
||||
* @return a promise combining the results of all HTTP operations
|
||||
*/
|
||||
Calendar.reloadWebCalendars = function() {
|
||||
var promises = [];
|
||||
|
||||
_.forEach(this.$webcalendars, function(calendar) {
|
||||
var promise = Calendar.$$resource.fetch(calendar.id, 'reload');
|
||||
promise.then(function(data) {
|
||||
calendar.$error = false;
|
||||
}, function(response) {
|
||||
calendar.$error = l(response.statusText);
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
return Calendar.$q.all(promises);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function $deleteComponents
|
||||
* @memberof Calendar
|
||||
|
@ -292,6 +320,7 @@
|
|||
*/
|
||||
Calendar.prototype.init = function(data) {
|
||||
this.color = this.color || '#AAAAAA';
|
||||
this.active = 1;
|
||||
angular.extend(this, data);
|
||||
if (this.id) {
|
||||
this.$acl = new Calendar.$$Acl('Calendar/' + this.id);
|
||||
|
@ -438,6 +467,34 @@
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @function setCredentials
|
||||
* @memberof Calendar.prototype
|
||||
* @desc Set the credentials for a Web calendar that requires authentication
|
||||
* @returns a promise of the HTTP operation
|
||||
*/
|
||||
Calendar.prototype.setCredentials = function(username, password) {
|
||||
var _this = this,
|
||||
d = Calendar.$q.defer();
|
||||
|
||||
Calendar.$$resource.post(this.id, 'set-credentials', { username: username, password: password }).then(function() {
|
||||
Calendar.$$resource.fetch(_this.id, 'reload').then(function(data) {
|
||||
Calendar.$add(_this);
|
||||
d.resolve();
|
||||
}, function(response) {
|
||||
if (response.status == 401) {
|
||||
// Authentication failed
|
||||
d.reject(l('Wrong username or password'));
|
||||
}
|
||||
else {
|
||||
d.reject(response.statusText);
|
||||
}
|
||||
});
|
||||
}, d.reject);
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function export
|
||||
* @memberof Calendar.prototype
|
||||
|
|
|
@ -309,7 +309,9 @@
|
|||
}
|
||||
|
||||
function reload() {
|
||||
Calendar.reloadWebCalendars().finally(function() {
|
||||
$rootScope.$emit('calendars:list');
|
||||
});
|
||||
}
|
||||
|
||||
function cancelSearch() {
|
||||
|
|
|
@ -127,9 +127,49 @@
|
|||
function addWebCalendar() {
|
||||
Dialog.prompt(l('Subscribe to a web calendar...'), l('URL of the Calendar'), {inputType: 'url'})
|
||||
.then(function(url) {
|
||||
Calendar.$addWebCalendar(url);
|
||||
Calendar.$addWebCalendar(url).then(function(calendar) {
|
||||
if (angular.isObject(calendar)) {
|
||||
// Web calendar requires HTTP authentication
|
||||
$mdDialog.show({
|
||||
parent: angular.element(document.body),
|
||||
clickOutsideToClose: true,
|
||||
escapeToClose: true,
|
||||
templateUrl: 'UIxWebCalendarAuthDialog',
|
||||
controller: WebCalendarAuthDialogController,
|
||||
controllerAs: '$WebCalendarAuthDialogController',
|
||||
locals: {
|
||||
url: url,
|
||||
calendar: calendar
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
WebCalendarAuthDialogController.$inject = ['scope', '$mdDialog', 'url', 'calendar'];
|
||||
function WebCalendarAuthDialogController(scope, $mdDialog, url, calendar) {
|
||||
var vm = this,
|
||||
parts = url.split("/"),
|
||||
hostname = parts[2];
|
||||
|
||||
vm.title = l("Please identify yourself to %{0}").formatted(hostname);
|
||||
vm.authenticate = function(form) {
|
||||
if (form.$valid || !form.$error.required) {
|
||||
calendar.setCredentials(vm.username, vm.password).then(function(message) {
|
||||
$mdDialog.hide();
|
||||
}, function(reason) {
|
||||
form.password.$setValidity('credentials', false);
|
||||
});
|
||||
}
|
||||
};
|
||||
vm.cancel = function() {
|
||||
$mdDialog.cancel();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(folder) {
|
||||
if (folder.isSubscription) {
|
||||
|
|
Loading…
Reference in New Issue