diff --git a/ChangeLog b/ChangeLog index 47e9ccc31..3164fbd24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,28 @@ 2012-04-18 Wolfgang Sourdeau + * UI/WebServerResources/generic.js (showAuthenticationDialog): new + dialog providing an interface for requesting a username and a + password to acallback passed as parameter. + (accessToSubscribedFolder): fixed method to return the proper url + for folders owned by the active user. + + * UI/WebServerResources/SchedulerUI.js (reloadWebCalendars): now + invokes "reloadAction" on every web calendar foldar, in a chain of + simultaneous requests. + (reloadWebCalendar): new function invoking "reloadAction" on a + single cal folder. + (reloadWebCalendarCallback): callback for the above invocation + that takes care of refreshing the views at the end of a refresh + chain, if present, and when useful. + + * UI/Scheduler/UIxCalFolderActions.m (reloadAction): new web + method that applies only to SOGoWebAppointmentFolder instances and + which returns a JSON representation of the result returned by + -[SOGoWebAppointmentFolder loadWebCalendar]. This method replaces + -[UIxCalMainActions -reloadWebCalendars]. + (setCredentialsAction): new web method acting as a proxy for + -[SOGoWebAppointmentFolder setUsername:andPassword:]. + * UI/WebServerResources/generic.js (clickEventWrapper): new function that returns a wrapper function for click callbacks which invokes "preventDefault" on the "event" parameter before it is @@ -7,6 +30,7 @@ * UI/Scheduler/UIxCalMainActions.m (-reloadWebCalendarsAndRedirectAction): removed obsolete method. + (-reloadWebCalendars): removed obsolete method. * SoObjects/Appointments/SOGoAppointmentFolders.m (-newWebCalendarWithName:atURL:): perform sanity checks on the url diff --git a/UI/Scheduler/UIxCalFolderActions.m b/UI/Scheduler/UIxCalFolderActions.m index abe0b3400..376accfc4 100644 --- a/UI/Scheduler/UIxCalFolderActions.m +++ b/UI/Scheduler/UIxCalFolderActions.m @@ -20,12 +20,13 @@ */ #import - -#import - +#import #import +#import + #import +#import #import #import "UIxCalFolderActions.h" @@ -95,4 +96,43 @@ return response; } +/* These methods are only available on instance of SOGoWebAppointmentFolder. */ +- (WOResponse *) reloadAction +{ + WOResponse *response; + NSDictionary *results; + + response = [self responseWithStatus: 200]; + [response setHeader: @"application/json" forKey: @"content-type"]; + results = [[self clientObject] loadWebCalendar]; + [response appendContentString: [results jsonRepresentation]]; + + return response; +} + +- (WOResponse *) setCredentialsAction +{ + WORequest *request; + WOResponse *response; + NSString *username, *password; + + request = [context request]; + + username = [[request formValueForKey: @"username"] stringByTrimmingSpaces]; + password = [[request formValueForKey: @"password"] stringByTrimmingSpaces]; + if ([username length] > 0 && [password length] > 0) + { + [[self clientObject] setUsername: username + andPassword: password]; + response = [self responseWith204]; + } + else + response + = (WOResponse *) [NSException exceptionWithHTTPStatus: 400 + reason: @"missing 'username' and/or" + @" 'password' parameters"]; + + return response; +} + @end /* UIxCalFolderActions */ diff --git a/UI/Scheduler/UIxCalMainActions.m b/UI/Scheduler/UIxCalMainActions.m index 216db791c..180c2f9ba 100644 --- a/UI/Scheduler/UIxCalMainActions.m +++ b/UI/Scheduler/UIxCalMainActions.m @@ -60,51 +60,41 @@ WORequest *r; WOResponse *response; SOGoWebAppointmentFolder *folder; - NSURL *url; NSString *urlString, *displayName; NSMutableDictionary *rc; SOGoAppointmentFolders *folders; - int imported = 0; r = [context request]; - rc = [NSMutableDictionary dictionary]; - // Just a check - urlString = [r formValueForKey: @"url"]; - url = [NSURL URLWithString: urlString]; - if (url) + urlString = [[r formValueForKey: @"url"] stringByTrimmingSpaces]; + if ([urlString length] > 0) { folders = [self clientObject]; - displayName = [self displayNameForUrl: [r formValueForKey: @"url"]]; + displayName = [self displayNameForUrl: urlString]; folder = [folders newWebCalendarWithName: displayName atURL: urlString]; if (folder) { - imported = [folder loadWebCalendar]; - if (imported >= 0) - { - [rc setObject: displayName forKey: @"displayname"]; - [rc setObject: [folder nameInContainer] forKey: @"name"]; - } - else - { - [folder delete]; - } - [rc setObject: [NSNumber numberWithInt: imported] - forKey: @"imported"]; + response = [self responseWithStatus: 200]; + [response setHeader: @"application/json" forKey: @"content-type"]; + + rc = [NSMutableDictionary dictionary]; + [rc setObject: [folder displayName] forKey: @"name"]; + [rc setObject: [folder folderReference] forKey: @"folderID"]; + [response appendContentString: [rc jsonRepresentation]]; } + else + response = (WOResponse *) + [NSException exceptionWithHTTPStatus: 400 + reason: @"folder was not created"]; } + else + response = (WOResponse *) + [NSException exceptionWithHTTPStatus: 400 + reason: @"missing 'url' parameter"]; - response = [self responseWithStatus: 200]; - [response appendContentString: [rc jsonRepresentation]]; + return response; } -- (WOResponse *) reloadWebCalendarsAction -{ - [[self clientObject] reloadWebCalendars: YES]; - - return [self responseWith204]; -} - @end diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 266f4cd4f..fa3f719e1 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -45,11 +45,6 @@ actionClass = "UIxCalMainActions"; actionName = "addWebCalendar"; }; - reloadWebCalendars = { - protectedBy = "View"; - actionClass = "UIxCalMainActions"; - actionName = "reloadWebCalendars"; - }; saveDragHandleState = { protectedBy = "View"; pageName = "UIxCalMainView"; @@ -176,6 +171,21 @@ }; }; + SOGoWebAppointmentFolder = { + methods = { + reload = { + protectedBy = "View"; + actionClass = "UIxCalFolderActions"; + actionName = "reload"; + }; + "set-credentials" = { + protectedBy = "View"; + actionClass = "UIxCalFolderActions"; + actionName = "setCredentials"; + }; + }; + }; + SOGoAppointmentFolderICS = { methods = { export = { diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 54784ac7e..c397455ac 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -1203,25 +1203,180 @@ function onMonthOverview() { return _ensureView("monthview"); } -function onCalendarReload() { +function refreshEventsAndTasks() { refreshEvents(); refreshTasks(); - reloadWebCalendars(); +} + +function onCalendarReload() { + if (!reloadWebCalendars()) { + refreshEventsAndTasks(); + } + return false; } function reloadWebCalendars() { - var url = ApplicationBaseURL + "reloadWebCalendars"; - if (document.reloadWebCalAjaxRequest) { - document.reloadWebCalAjaxRequest.aborted = true; - document.reloadWebCalAjaxRequest.abort(); + log("* reloadWebCalendars"); + var hasWebCalendars = false; + + var remaining = []; + var refreshOperations = { "remaining": remaining }; + if (UserSettings['Calendar'] + && UserSettings['Calendar']['WebCalendars']) { + var webCalendars = UserSettings['Calendar']['WebCalendars']; + + var folders = $("calendarList"); + var calendarNodes = folders.childNodesWithTag("li"); + for (var i = 0; i < calendarNodes.length; i++) { + var current = calendarNodes[i]; + var calendarID = current.getAttribute("id"); + var owner = current.getAttribute("owner"); + var realID = owner + ":Calendar/" + calendarID.substr(1); + if (webCalendars[realID]) { /* is web calendar ? */ + remaining.push(realID); + reloadWebCalendar(realID, refreshOperations); + } + } } - document.reloadWebCalAjaxRequest - = triggerAjaxRequest(url, reloadWebCalendarsCallback); + + return (remaining.length > 0); } -function reloadWebCalendarsCallback (http) { - changeCalendarDisplay(null, currentView); +var calendarReloadErrors = { "invalid-calendar-content": + _("the returned content was not valid calendar" + + " data"), + "http-error": _("an unknown http error occurred" + + " during the load operation"), + "bad-url": _("the url in use is invalid or the" + + " host is currently unreachable"), + "invalid-url": _("the url being used is invalid" + + " or not handled") }; + +function reloadWebCalendar(folderID, refreshOperations) { + var url = URLForFolderID(folderID) + "/reload"; + var cbData = { "folderID": folderID }; + if (refreshOperations) { + cbData["refreshOperations"] = refreshOperations; + } + triggerAjaxRequest(url, reloadWebCalendarCallback, cbData); +} + +function reloadWebCalendarCallback(http) { + var cbData = http.callbackData; + if (http.status == 200) { + var result = http.responseText.evalJSON(true); + var requireAuth = false; + var success = false; + if (result.status) { + if (result.status == 401) { + requireAuth = true; + } + else { + if (result.status == 200) { + success = true; + } + else { + var errorMessage = _("An error occurred while importing calendar."); + if (result["error"]) { + var message = calendarReloadErrors[result["error"]]; + errorMessage = (_("An error occurred while loading remote" + + " calendar: %{0}.").formatted(message)); + } + showAlertDialog (errorMessage); + } + } + } + else { + var errorMessage = _("An error occurred while importing calendar."); + if (result["error"]) { + var message = calendarReloadErrors[result["error"]]; + errorMessage = (_("An error occurred while loading remote" + + " calendar: %{0}.").formatted(message)); + } + showAlertDialog (errorMessage); + } + + if (requireAuth) { + reauthenticateWebCalendar(cbData["folderID"], cbData); + } + else { + var refreshOperations = cbData["refreshOperations"]; + if (refreshOperations) { + var remaining = refreshOperations["remaining"]; + var calIdx = remaining.indexOf(cbData["folderID"]); + remaining.splice(calIdx, 1); + if (remaining.length == 0) { + refreshEventsAndTasks(); + changeCalendarDisplay(null, currentView); + } + else { + var newFolderID = remaining[0]; + reloadWebCalendar(newFolderID, refreshOperations); + } + } + else { + if (success) { + refreshEventsAndTasks(); + changeCalendarDisplay(null, currentView); + } + } + } + } + else { + showAlertDialog(_("An error occurred while importing calendar.")); + var refreshOperations = cbData["refreshOperations"]; + if (refreshOperations) { + var remaining = refreshOperations["remaining"]; + var calIdx = remaining.indexOf(cbData["folderID"]); + remaining.splice(calIdx, 1); + if (remaining.length > 0) { + var newFolderID = remaining[0]; + reloadWebCalendar(newFolderID, refreshOperations); + } + } + } +} + +function reauthenticateWebCalendar(folderID, refreshCBData) { + var remoteURL = null; + if (UserSettings['Calendar'] && UserSettings['Calendar']['WebCalendars']) { + var webCalendars = UserSettings['Calendar']['WebCalendars']; + remoteURL = webCalendars[folderID]; + } + var parts = remoteURL.split("/"); + var hostname = parts[2]; + function authenticate(username, password) { + disposeDialog(); + var url = URLForFolderID(folderID) + "/set-credentials"; + var parameters = ("username=" + encodeURIComponent(username) + + "&password=" + encodeURIComponent(password)); + triggerAjaxRequest(url, authenticateWebCalendarCallback, refreshCBData, parameters, + { "Content-type": "application/x-www-form-urlencoded" }); + } + showAuthenticationDialog(_("Please identify yourself to \"%{0}\"...") + .formatted(hostname), + authenticate); +} + +function authenticateWebCalendarCallback(http) { + var cbData = http.callbackData; + var folderID = cbData["folderID"]; + var refreshOperations = cbData["refreshOperations"]; + if (isHttpStatus204(http.status)) { + reloadWebCalendar(folderID, refreshOperations); + } + else { + if (refreshOperations) { + var remaining = refreshOperations["remaining"]; + var calIdx = remaining.indexOf(folderID); + remaining.splice(calIdx, 1); + if (remaining.length > 0) { + var newFolderID = remaining[0]; + reloadWebCalendar(newFolderID, refreshOperations); + } + } + } } function scrollDayView(scrollEvent) { @@ -2567,29 +2722,51 @@ function onCalendarWebAdd(event) { } function onCalendarWebAddConfirm() { + disposeDialog(); var calendarUrl = this.value; if (calendarUrl) { - if (document.addWebCalendarRequest) { - document.addWebCalendarRequest.aborted = true; - document.addWebCalendarRequest.abort (); - } - var url = ApplicationBaseURL + "/addWebCalendar?url=" + escape (calendarUrl); - document.addWebCalendarRequest = - triggerAjaxRequest (url, addWebCalendarCallback); + var url = ApplicationBaseURL + "/addWebCalendar" + var parameters = "url=" + calendarUrl; + triggerAjaxRequest(url, addWebCalendarCallback, calendarUrl, parameters, + { "Content-type": "application/x-www-form-urlencoded" }); } - disposeDialog(); } -function addWebCalendarCallback (http) { - var data = http.responseText.evalJSON(true); - if (data.imported >= 0) { - appendCalendar(data.displayname, "/" + data.name); - refreshEvents(); - refreshTasks(); - changeCalendarDisplay(); + +function addWebCalendarCallback(http) { + if (http.status == 200) { + var data = http.responseText.evalJSON(true); + if (!data || data["error"] || !data["name"] || !data["folderID"]) { + showAlertDialog (_("An error occurred while importing calendar.")); + } + else { + if (UserSettings['Calendar']) { + var webCalendars = UserSettings['Calendar']['WebCalendars']; + if (!webCalendars) { + webCalendars = {}; + UserSettings['Calendar']['WebCalendars'] = webCalendars; + } + webCalendars[data["folderID"]] = http.callbackData; + } + + appendCalendar(data["name"], data["folderID"]); + reloadWebCalendar(data["folderID"]); + } } else { showAlertDialog (_("An error occurred while importing calendar.")); } + + // if (data.imported) { + // appendCalendar(data.displayname, "/" + data.name); + // refreshEvents(); + // refreshTasks(); + // changeCalendarDisplay(); + // } + // else if (data.status && data.status == 401) { + // reauthenticateWebCalendar(data.name, data.url); + // } + // else { + // } } function onCalendarExport(event) { diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 10789a15a..578e5e064 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -1266,11 +1266,18 @@ function accessToSubscribedFolder(serverFolder) { var parts = serverFolder.split(":"); if (parts.length > 1) { + var username = parts[0]; var paths = parts[1].split("/"); - folder = "/" + parts[0].asCSSIdentifier() + "_" + paths[2]; + if (username == UserLogin) { + folder = paths[1]; + } + else { + folder = "/" + username.asCSSIdentifier() + "_" + paths[1]; + } } - else + else { folder = serverFolder; + } return folder; } @@ -2095,6 +2102,53 @@ function _showSelectDialog(title, label, options, button, callbackFcn, callbackA dialog.appear({ duration: 0.2 }); } +function showAuthenticationDialog(label, callback) { + log("* showAuthenticationDialog"); + log(backtrace()); + + var div = $("bgDialogDiv"); + if (div && div.visible() && div.getOpacity() > 0) { + log("push"); + dialogsStack.push(_showAuthenticationDialog.bind(this, label, callback)); + } + else { + log("show"); + _showAuthenticationDialog(label, callback); + } +} + +function _showAuthenticationDialog(label, callback) { + var dialog = dialogs[label]; + if (dialog) { + $("bgDialogDiv").show(); + var inputs = dialog.getElementsByTagName("input"); + for (var i = 0; i < inputs.length; i++) { + inputs[i].value = ""; + } + } + else { + var fields = createElement("p", null, ["prompt"]); + fields.appendChild(document.createTextNode(_("Username:"))); + var un_input = createElement("input", null, "textField", + { type: "text", "value": "" }); + fields.appendChild(un_input); + fields.appendChild(document.createTextNode(_("Password:"))); + var pw_input = createElement("input", null, "textField", + { type: "password", "value": "" }); + fields.appendChild(pw_input); + function callbackWrapper() { + callback(un_input.value, pw_input.value); + } + fields.appendChild(createButton(null, _("OK"), callbackWrapper)); + fields.appendChild(createButton(null, _("Cancel"), disposeDialog)); + dialog = createDialog(null, label, null, fields, "none"); + document.body.appendChild(dialog); + dialogs[label] = dialog; + } + dialog.appear({ duration: 0.2, + afterFinish: function () { dialog.down("input").focus(); } }); +} + function disposeDialog() { $$("DIV.dialog").each(function(div) { if (div.visible() && div.getOpacity() == 1)