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 #3326
pull/208/head
Francis Lachapelle 2016-05-13 15:50:24 -04:00
parent 199a5f5215
commit e396e29430
11 changed files with 188 additions and 28 deletions

2
NEWS
View File

@ -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)

View File

@ -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

View File

@ -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";

View File

@ -193,8 +193,7 @@
NSString *folderName;
[self _setupContext];
folderSubscription
= [moduleSettings objectForKey: @"InactiveFolders"];
folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"];
if (!folderSubscription)
{
folderSubscription = [NSMutableArray array];

View File

@ -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;
}

View File

@ -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]])
{

View File

@ -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>

View File

@ -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;
};

View File

@ -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(response) {
if (response.status == 401) {
// Web calendar requires authentication
d.resolve(calendar);
}
else {
d.reject();
}
});
d.resolve();
}, function() {
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

View File

@ -309,7 +309,9 @@
}
function reload() {
$rootScope.$emit('calendars:list');
Calendar.reloadWebCalendars().finally(function() {
$rootScope.$emit('calendars:list');
});
}
function cancelSearch() {

View File

@ -127,8 +127,48 @@
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) {