diff --git a/ChangeLog b/ChangeLog index b5fc64425..6e0d1acef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,53 @@ +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 + passed to the real callback. + + * UI/Scheduler/UIxCalMainActions.m + (-reloadWebCalendarsAndRedirectAction): removed obsolete method. + (-reloadWebCalendars): removed obsolete method. + + * SoObjects/Appointments/SOGoAppointmentFolders.m + (-newWebCalendarWithName:atURL:): perform sanity checks on the url + parameter. + + * SoObjects/Appointments/SOGoWebAppointmentFolder.m + (-setUsername:andPassword:): new method that associate an + encrypted login pair for authenticating to remove calendars. + (-loadWebCalendar): rewrote method to make use of curl, thereby + permitting HTTP authentication. A NSDictionary containing + different informations about the connection is not returned, + including the HTTP status and an error tag, when required. + + * SoObjects/Appointments/SOGoAppointmentFolder.m + (-importCalendar:): journals and freebusys are not + supported. + 2012-04-16 Jean Raby * SOPE/NGCards/iCalMonthlyRecurrenceCalculator.m diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 31cf4a442..146b38f89 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -2746,8 +2746,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir components = [[calendar events] mutableCopy]; [components autorelease]; [components addObjectsFromArray: [calendar todos]]; - [components addObjectsFromArray: [calendar journals]]; - [components addObjectsFromArray: [calendar freeBusys]]; + // [components addObjectsFromArray: [calendar journals]]; + // [components addObjectsFromArray: [calendar freeBusys]]; count = [components count]; for (i = 0; i < count; i++) { diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 3a8e020aa..24ddf9879 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -25,6 +25,7 @@ #import #import #import +#import #import #import @@ -133,25 +134,35 @@ static SoSecurityManager *sm = nil; - (SOGoWebAppointmentFolder *) newWebCalendarWithName: (NSString *) folderDisplayName - atURL: (NSString *) url + atURL: (NSString *) urlString { + NSException *error; SOGoAppointmentFolder *aptFolder; SOGoWebAppointmentFolder *webCalendar; NSString *name; + NSURL *url; - if ([self newFolderWithName: folderDisplayName - nameInContainer: &name]) - webCalendar = nil; - else + webCalendar = nil; + + if ([folderDisplayName length] > 0 && [urlString length] > 0) { - aptFolder = [subFolders objectForKey: name]; - [aptFolder setFolderPropertyValue: url - inCategory: @"WebCalendars"]; - - webCalendar = [SOGoWebAppointmentFolder objectWithName: name - inContainer: self]; - [webCalendar setOCSPath: [aptFolder ocsPath]]; - [subFolders setObject: webCalendar forKey: name]; + url = [NSURL URLWithString: urlString]; + if ([[url scheme] hasPrefix: @"http"]) + { + error = [self newFolderWithName: folderDisplayName + nameInContainer: &name]; + if (!error) + { + aptFolder = [subFolders objectForKey: name]; + [aptFolder setFolderPropertyValue: urlString + inCategory: @"WebCalendars"]; + + webCalendar = [SOGoWebAppointmentFolder objectWithName: name + inContainer: self]; + [webCalendar setOCSPath: [aptFolder ocsPath]]; + [subFolders setObject: webCalendar forKey: name]; + } + } } return webCalendar; @@ -542,6 +553,7 @@ static SoSecurityManager *sm = nil; calSettings = [us objectForKey: @"Calendar"]; refs = [[calSettings objectForKey: @"WebCalendars"] allKeys]; max = [refs count]; + for (count = 0; count < max; count++) { ref = [refs objectAtIndex: count]; diff --git a/SoObjects/Appointments/SOGoWebAppointmentFolder.h b/SoObjects/Appointments/SOGoWebAppointmentFolder.h index b20ab1ce1..1b8ac33b2 100644 --- a/SoObjects/Appointments/SOGoWebAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoWebAppointmentFolder.h @@ -27,7 +27,10 @@ @interface SOGoWebAppointmentFolder : SOGoAppointmentFolder -- (int) loadWebCalendar; +- (void) setUsername: (NSString *) username + andPassword: (NSString *) password; + +- (NSDictionary *) loadWebCalendar; - (void) deleteAllContent; - (void) setReloadOnLogin: (BOOL) newReloadOnLogin; diff --git a/SoObjects/Appointments/SOGoWebAppointmentFolder.m b/SoObjects/Appointments/SOGoWebAppointmentFolder.m index c27c868e1..76c5f21ab 100644 --- a/SoObjects/Appointments/SOGoWebAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoWebAppointmentFolder.m @@ -21,12 +21,26 @@ * Boston, MA 02111-1307, USA. */ -#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import +#import + #import #import -#import -#import -#import +#import +#import +#import +#import +#import #import "SOGoWebAppointmentFolder.h" @@ -39,45 +53,154 @@ [[self ocsFolder] deleteAllContent]; } -- (int) loadWebCalendar +- (NSDictionary *) _loadAuthData { - NSString *location, *contents; - WOHTTPURLHandle *handle; - iCalCalendar *calendar; - NSData *data; + NSDictionary *authData; + NSString *authValue, *userPassword; + NSArray *parts, *keys; + + userPassword = [[self authenticatorInContext: context] + passwordInContext: context]; + if ([userPassword length] == 0) + { + authData = nil; + } + else + { + authValue + = [[self folderPropertyValueInCategory: @"WebCalendarsAuthentication"] + decryptWithKey: userPassword]; + parts = [authValue componentsSeparatedByString: @":"]; + if ([parts count] == 2) + { + keys = [NSArray arrayWithObjects: @"username", @"password", nil]; + authData = [NSDictionary dictionaryWithObjects: parts + forKeys: keys]; + } + else + authData = nil; + } + + return authData; +} + +- (void) setUsername: (NSString *) username + andPassword: (NSString *) password +{ + NSString *authValue, *userPassword; + + userPassword = [[self authenticatorInContext: context] + passwordInContext: context]; + if ([userPassword length] > 0) + { + if (!username) + username = @""; + if (!password) + password = @""; + authValue = [NSString stringWithFormat: @"%@:%@", username, password]; + [self setFolderPropertyValue: [authValue encryptWithKey: userPassword] + inCategory: @"WebCalendarsAuthentication"]; + } +} + +- (NSDictionary *) loadWebCalendar +{ + NSString *location, *httpauth; + NSDictionary *authInfos; + NSMutableData *bodyData; NSURL *url; + CURL *curl; + CURLcode rc; + char error[CURL_ERROR_SIZE]; + NSMutableDictionary *result; + NSString *content, *newDisplayName; + iCalCalendar *calendar; + NSUInteger imported, status; - int imported = 0; + result = [NSMutableDictionary dictionary]; + // Prepare HTTPS post using libcurl location = [self folderPropertyValueInCategory: @"WebCalendars"]; + [result setObject: location forKey: @"url"]; + url = [NSURL URLWithString: location]; if (url) { - handle = AUTORELEASE([[WOHTTPURLHandle alloc] initWithURL: url cached: NO]); - data = [handle resourceData]; - - if (!data && [[location lowercaseString] hasPrefix: @"https"]) - { - NSLog(@"WARNING: Your GNUstep/SOPE installation might not have SSL support."); - return -1; - } - - contents = [[NSString alloc] initWithData: data - encoding: NSUTF8StringEncoding]; - [contents autorelease]; - calendar = [iCalCalendar parseSingleFromSource: contents]; - if (calendar) + curl_global_init (CURL_GLOBAL_SSL); + curl = curl_easy_init (); + if (curl) { - [self deleteAllContent]; - imported = [self importCalendar: calendar]; - } - else - { - imported = -1; + curl_easy_setopt (curl, CURLOPT_URL, [location UTF8String]); + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L); + + authInfos = [self _loadAuthData]; + if (authInfos) + { + httpauth = [authInfos keysWithFormat: @"%{username}:%{password}"]; + curl_easy_setopt (curl, CURLOPT_USERPWD, [httpauth UTF8String]); + curl_easy_setopt (curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + } + + bodyData = [NSMutableData data]; + size_t curlBodyFunction (void *ptr, size_t size, size_t nmemb, void *inSelf) + { + size_t total; + + total = size * nmemb; + [bodyData appendBytes: ptr length: total]; + + return total; + } + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, curlBodyFunction); + + error[0] = 0; + curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, &error); + + // Perform SOAP request + rc = curl_easy_perform (curl); + if (rc == 0) + { + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); + [result setObject: [NSNumber numberWithUnsignedInt: status] + forKey: @"status"]; + + if (status == 200) + { + content = [[NSString alloc] initWithData: bodyData + encoding: NSUTF8StringEncoding]; + if (!content) + content = [[NSString alloc] initWithData: bodyData + encoding: NSISOLatin1StringEncoding]; + [content autorelease]; + calendar = [iCalCalendar parseSingleFromSource: content]; + if (calendar) + { + newDisplayName = [[calendar + firstChildWithTag: @"x-wr-calname"] + flattenedValuesForKey: @""]; + if ([newDisplayName length] > 0) + [self setDisplayName: newDisplayName]; + [self deleteAllContent]; + imported = [self importCalendar: calendar]; + [result setObject: [NSNumber numberWithInt: imported] + forKey: @"imported"]; + } + else + [result setObject: @"invalid-calendar-content" forKey: @"error"]; + } + else + [result setObject: @"http-error" forKey: @"error"]; + } + else + [result setObject: @"bad-url" forKey: @"error"]; + curl_easy_cleanup (curl); } } - - return imported; + else + [result setObject: @"invalid-url" forKey: @"error"]; + + return result; } - (void) setReloadOnLogin: (BOOL) newReloadOnLogin @@ -98,7 +221,10 @@ error = [super delete]; if (!error) - [self setFolderPropertyValue: nil inCategory: @"WebCalendars"]; + { + [self setFolderPropertyValue: nil inCategory: @"WebCalendars"]; + [self setFolderPropertyValue: nil inCategory: @"WebCalendarsAuthentication"]; + } return error; } diff --git a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings index 7e8668f9f..764fcb981 100644 --- a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Recarregar Calendários Remotos"; "Properties" = "Propriedades"; "Done" = "Feito"; -"An error occured while importing calendar." = "Um erro ocorreu na importação do calendário."; +"An error occurred while importing calendar." = "Um erro ocorreu na importação do calendário."; "No event was imported." = "Nenhum evento importado."; "A total of %{0} events were imported in the calendar." = "Um total de %{0} eventos foram importados no calendário."; diff --git a/UI/Scheduler/Catalan.lproj/Localizable.strings b/UI/Scheduler/Catalan.lproj/Localizable.strings index a546456a9..f8bda1b69 100644 --- a/UI/Scheduler/Catalan.lproj/Localizable.strings +++ b/UI/Scheduler/Catalan.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Actualitzar calendaris remots"; "Properties" = "Propietats"; "Done" = "Fet"; -"An error occured while importing calendar." = "Hi ha hagut un error en importar el calendari."; +"An error occurred while importing calendar." = "Hi ha hagut un error en importar el calendari."; "No event was imported." = "No s'ha importat cap esdeveniment."; "A total of %{0} events were imported in the calendar." = "Han estat importats %{0} esdeveniments al calendari."; diff --git a/UI/Scheduler/Czech.lproj/Localizable.strings b/UI/Scheduler/Czech.lproj/Localizable.strings index e2ebdd3ff..424ab9acd 100644 --- a/UI/Scheduler/Czech.lproj/Localizable.strings +++ b/UI/Scheduler/Czech.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Aktualizovat vzdálené kalendáře"; "Properties" = "Vlastnosti"; "Done" = "Hotovo"; -"An error occured while importing calendar." = "Při importu událostí došlo k chybě."; +"An error occurred while importing calendar." = "Při importu událostí došlo k chybě."; "No event was imported." = "Nebyla importována žádná událost."; "A total of %{0} events were imported in the calendar." = "Do kalendáře bylo importováno %{0} událostí."; diff --git a/UI/Scheduler/Danish.lproj/Localizable.strings b/UI/Scheduler/Danish.lproj/Localizable.strings index 2e95d3047..0730e8917 100644 --- a/UI/Scheduler/Danish.lproj/Localizable.strings +++ b/UI/Scheduler/Danish.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Opdatér kalendere"; "Properties" = "Egenskaber"; "Done" = "Udført"; -"An error occured while importing calendar." = "Der opstod en fejl under importering af kalender."; +"An error occurred while importing calendar." = "Der opstod en fejl under importering af kalender."; "No event was imported." = "Ingen begivenhed blev importeret."; "A total of %{0} events were imported in the calendar." = "I alt %{0} begivenheder blev importeret i kalenderen."; diff --git a/UI/Scheduler/Dutch.lproj/Localizable.strings b/UI/Scheduler/Dutch.lproj/Localizable.strings index 398fb694b..dae19fe4b 100644 --- a/UI/Scheduler/Dutch.lproj/Localizable.strings +++ b/UI/Scheduler/Dutch.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Externe agenda herladen"; "Properties" = "Instellingen"; "Done" = "Klaar"; -"An error occured while importing calendar." = "Er is een fout opgetreden bij het importeren van de kalender."; +"An error occurred while importing calendar." = "Er is een fout opgetreden bij het importeren van de kalender."; "No event was imported." = "Geen enkele gebeurtenis werd geïmporteerd."; "A total of %{0} events were imported in the calendar." = "Een totaal van %{0} gebeurtenissen werd geïmporteerd in de kalender."; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 6f63b5361..fb704adc3 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Reload Remote Calendars"; "Properties" = "Properties"; "Done" = "Done"; -"An error occured while importing calendar." = "An error occured while importing calendar."; +"An error occurred while importing calendar." = "An error occurred while importing calendar."; "No event was imported." = "No event was imported."; "A total of %{0} events were imported in the calendar." = "A total of %{0} events were imported in the calendar."; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 274c5cfe4..8b4132bc2 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Recharger les agendas distants"; "Properties" = "Propriétés"; "Done" = "Terminer"; -"An error occured while importing calendar." = "Une erreur s'est produite lors de l'importation."; +"An error occurred while importing calendar." = "Une erreur s'est produite lors de l'importation."; "No event was imported." = "Aucun événement n'a été importé."; "A total of %{0} events were imported in the calendar." = "Un total de %{0} événements ont été importés dans le calendrier."; diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 0ecf1c9cc..077e53eb7 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Externe Kalender neu laden"; "Properties" = "Einstellungen"; "Done" = "Fertig"; -"An error occured while importing calendar." = "Fehler während des Importierens von Terminen."; +"An error occurred while importing calendar." = "Fehler während des Importierens von Terminen."; "No event was imported." = "Es wurden keine Termine importiert."; "A total of %{0} events were imported in the calendar." = "%{0} Termine wurden in den Kalender importiert."; diff --git a/UI/Scheduler/Hungarian.lproj/Localizable.strings b/UI/Scheduler/Hungarian.lproj/Localizable.strings index 448f483ac..78d6ddab3 100644 --- a/UI/Scheduler/Hungarian.lproj/Localizable.strings +++ b/UI/Scheduler/Hungarian.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Távoli naptárak frissítése"; "Properties" = "Tulajdonságok"; "Done" = "Kész"; -"An error occured while importing calendar." = "Hiba történt a naptár importálásakor."; +"An error occurred while importing calendar." = "Hiba történt a naptár importálásakor."; "No event was imported." = "Nem volt importált esemény."; "A total of %{0} events were imported in the calendar." = "Összesen %{0} esemény lett importálva a naptárba."; diff --git a/UI/Scheduler/Icelandic.lproj/Localizable.strings b/UI/Scheduler/Icelandic.lproj/Localizable.strings index 6dc96f29f..f99e7ccac 100644 --- a/UI/Scheduler/Icelandic.lproj/Localizable.strings +++ b/UI/Scheduler/Icelandic.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Reload Remote Calendars"; "Properties" = "Eiginleikar"; "Done" = "Ljúka"; -"An error occured while importing calendar." = "Villa kom upp þegar dagatalið var flutt inn."; +"An error occurred while importing calendar." = "Villa kom upp þegar dagatalið var flutt inn."; "No event was imported." = "Enginn viðburður var fluttur inn."; "A total of %{0} events were imported in the calendar." = "Alls voru %{0} viðburðir fluttir inn í dagatalið."; diff --git a/UI/Scheduler/Italian.lproj/Localizable.strings b/UI/Scheduler/Italian.lproj/Localizable.strings index 091d5f725..2355c9b42 100644 --- a/UI/Scheduler/Italian.lproj/Localizable.strings +++ b/UI/Scheduler/Italian.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Aggiorna calendari remoti"; "Properties" = "Proprietà"; "Done" = "Fatto"; -"An error occured while importing calendar." = "Si è verificato un errore durante l'importazione del calendario."; +"An error occurred while importing calendar." = "Si è verificato un errore durante l'importazione del calendario."; "No event was imported." = "Nessun evento è stato importato."; "A total of %{0} events were imported in the calendar." = "Un totale di %{0} sono stati importati nel calendario."; diff --git a/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings b/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings index b4ff3dedf..792c26fe6 100644 --- a/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings +++ b/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Last på nytt fjernkalendre"; "Properties" = "Egenskaper"; "Done" = "Klar"; -"An error occured while importing calendar." = "En feil har oppstått under kalenderimporten."; +"An error occurred while importing calendar." = "En feil har oppstått under kalenderimporten."; "No event was imported." = "Ingen hendelser ble importert."; "A total of %{0} events were imported in the calendar." = "Totalt %{0} hendelser ble importert til kalenderen."; diff --git a/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings b/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings index 565c6c85d..29710aeff 100644 --- a/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Last på nytt fjernkalendre"; "Properties" = "Egenskaper"; "Done" = "Klar"; -"An error occured while importing calendar." = "En feil har oppstått under kalenderimporten."; +"An error occurred while importing calendar." = "En feil har oppstått under kalenderimporten."; "No event was imported." = "Ingen hendelser ble importert."; "A total of %{0} events were imported in the calendar." = "Totalt %{0} hendelser ble importert til kalenderen."; diff --git a/UI/Scheduler/Polish.lproj/Localizable.strings b/UI/Scheduler/Polish.lproj/Localizable.strings index 3b8ced6ba..bf224b00d 100644 --- a/UI/Scheduler/Polish.lproj/Localizable.strings +++ b/UI/Scheduler/Polish.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Przeładuj zdalne kalendarze"; "Properties" = "Właściwości"; "Done" = "Zrobione"; -"An error occured while importing calendar." = "Błąd w trakcie importu kalendarza."; +"An error occurred while importing calendar." = "Błąd w trakcie importu kalendarza."; "No event was imported." = "Nie zaimportowano żadnego wydarzenia."; "A total of %{0} events were imported in the calendar." = "Do kalendarza zaimportowano razem %{0} wydarzeń(nia)."; diff --git a/UI/Scheduler/Russian.lproj/Localizable.strings b/UI/Scheduler/Russian.lproj/Localizable.strings index 203da149b..210b9af8e 100644 --- a/UI/Scheduler/Russian.lproj/Localizable.strings +++ b/UI/Scheduler/Russian.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Обновить удаленные календари"; "Properties" = "Свойства"; "Done" = "Готово"; -"An error occured while importing calendar." = "Ошибка при импорте календаря."; +"An error occurred while importing calendar." = "Ошибка при импорте календаря."; "No event was imported." = "События не были импортированы."; "A total of %{0} events were imported in the calendar." = " Всего %{0} событий было импортировано в календарь."; diff --git a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings index c98af4f09..9cd1306d5 100644 --- a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Recargar calendarios remotos"; "Properties" = "Propiedades"; "Done" = "Hecho"; -"An error occured while importing calendar." = "Ha ocurrido un error mientras importaba el calendario."; +"An error occurred while importing calendar." = "Ha ocurrido un error mientras importaba el calendario."; "No event was imported." = "El evento no fue importado."; "A total of %{0} events were imported in the calendar." = "Un total de %{0} eventos fueron importados al calendario."; diff --git a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings index 818207d1b..e196c4d55 100644 --- a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Recargar calendarios remotos"; "Properties" = "Propiedades"; "Done" = "Hecho"; -"An error occured while importing calendar." = "Ha ocurido un error mientras importaba el calendario."; +"An error occurred while importing calendar." = "Ha ocurido un error mientras importaba el calendario."; "No event was imported." = "El evento no fue importado."; "A total of %{0} events were imported in the calendar." = "Un total de %{0} eventos fueron importados al calendario."; diff --git a/UI/Scheduler/Swedish.lproj/Localizable.strings b/UI/Scheduler/Swedish.lproj/Localizable.strings index faa76dcc2..db43bc72f 100644 --- a/UI/Scheduler/Swedish.lproj/Localizable.strings +++ b/UI/Scheduler/Swedish.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Ladda om fjärrkalendrar"; "Properties" = "Egenskaper"; "Done" = "Klar"; -"An error occured while importing calendar." = "Ett fel har inträffat under kalenderimporten."; +"An error occurred while importing calendar." = "Ett fel har inträffat under kalenderimporten."; "No event was imported." = "Inga händeler importerades."; "A total of %{0} events were imported in the calendar." = "Totalt %{0} händelser importerades till kalendern."; 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 0312ac579..07c16e7d2 100644 --- a/UI/Scheduler/UIxCalMainActions.m +++ b/UI/Scheduler/UIxCalMainActions.m @@ -22,6 +22,7 @@ #import +#import #import #import #import @@ -29,6 +30,7 @@ #import #import +#import #import #import #import @@ -60,68 +62,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]; -} - -// -// The method below is different than the -reloadWebCalendarsAction as it -// won't force the reload of all calendars and automatically redirect the -// user to the /SOGo/so page upon completion. -// -// This is particularly useful for WebAuth users and they won't have a -// precise "entry point" in SOGo - so calendars reload upon login -// isn't possible for them. -// -- (WOResponse *) reloadWebCalendarsAndRedirectAction -{ - [[self clientObject] reloadWebCalendars: NO]; - - return [self redirectToLocation: @"/SOGo/so"]; -} - - @end diff --git a/UI/Scheduler/Ukrainian.lproj/Localizable.strings b/UI/Scheduler/Ukrainian.lproj/Localizable.strings index 4d83bc42a..467f117f3 100644 --- a/UI/Scheduler/Ukrainian.lproj/Localizable.strings +++ b/UI/Scheduler/Ukrainian.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Перевантажити віддалені календарі"; "Properties" = "Властивості"; "Done" = "Зроблено"; -"An error occured while importing calendar." = "Виникла помилка під час імпорту календаря."; +"An error occurred while importing calendar." = "Виникла помилка під час імпорту календаря."; "No event was imported." = "Жодну подію не було імпортовано."; "A total of %{0} events were imported in the calendar." = "Разом %{0} подій було імпортовано в цьому календарі."; diff --git a/UI/Scheduler/Welsh.lproj/Localizable.strings b/UI/Scheduler/Welsh.lproj/Localizable.strings index 72e685540..2f13952a3 100644 --- a/UI/Scheduler/Welsh.lproj/Localizable.strings +++ b/UI/Scheduler/Welsh.lproj/Localizable.strings @@ -114,7 +114,7 @@ "Reload Remote Calendars" = "Ail-lwytho Calendrau Anghysbell"; "Properties" = "Dewisiadau"; "Done" = "Done"; -"An error occured while importing calendar." = "An error occured while importing calendar."; +"An error occurred while importing calendar." = "An error occurred while importing calendar."; "No event was imported." = "No event was imported."; "A total of %{0} events were imported in the calendar." = "A total of %{0} events were imported in the calendar."; diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 11cabb44e..fa3f719e1 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -45,16 +45,6 @@ actionClass = "UIxCalMainActions"; actionName = "addWebCalendar"; }; - reloadWebCalendars = { - protectedBy = "View"; - actionClass = "UIxCalMainActions"; - actionName = "reloadWebCalendars"; - }; - reloadWebCalendarsAndRedirect = { - protectedBy = "View"; - actionClass = "UIxCalMainActions"; - actionName = "reloadWebCalendarsAndRedirect"; - }; saveDragHandleState = { protectedBy = "View"; pageName = "UIxCalMainView"; @@ -181,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 d6c2a0ef9..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) { @@ -2420,7 +2575,7 @@ function onMenuSharing(event) { var folders = $("calendarList"); var selected = folders.getSelectedNodes()[0]; /* FIXME: activation of the context menu should preferably select the entry - above which the event has occured */ + above which the event has occurred */ if (selected) { var folderID = selected.getAttribute("id"); var urlstr = URLForFolderID(folderID) + "/acls"; @@ -2473,14 +2628,14 @@ function initCalendarSelector() { var items = list.childNodesWithTag("li"); for (var i = 0; i < items.length; i++) { var input = items[i].childNodesWithTag("input")[0]; - $(input).observe("click", updateCalendarStatus); + $(input).observe("click", clickEventWrapper(updateCalendarStatus)); } var links = $("calendarSelectorButtons").childNodesWithTag("a"); - $(links[0]).observe("click", onCalendarNew); - $(links[1]).observe("click", onCalendarWebAdd); - $(links[2]).observe("click", onCalendarAdd); - $(links[3]).observe("click", onCalendarRemove); + $(links[0]).observe("click", clickEventWrapper(onCalendarNew)); + $(links[1]).observe("click", clickEventWrapper(onCalendarWebAdd)); + $(links[2]).observe("click", clickEventWrapper(onCalendarAdd)); + $(links[3]).observe("click", clickEventWrapper(onCalendarRemove)); } function onCalendarSelectionChange(event) { @@ -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 occured while importing calendar.")); + 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) { @@ -2637,7 +2814,7 @@ function uploadCompleted(response) { var div = $("uploadResults"); if (data.imported < 0) - $("uploadResultsContent").update(_("An error occured while importing calendar.")); + $("uploadResultsContent").update(_("An error occurred while importing calendar.")); else if (data.imported == 0) $("uploadResultsContent").update(_("No event was imported.")); else { @@ -2699,7 +2876,7 @@ function appendCalendar(folderName, folderPath) { li.getElementsByTagName("input")[0].checked = true; // Register event on checkbox - $(checkBox).on("click", updateCalendarStatus); + $(checkBox).on("click", clickEventWrapper(updateCalendarStatus)); var url = URLForFolderID(folderPath) + "/canAccessContent"; triggerAjaxRequest(url, calendarEntryCallback, folderPath); diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 91817c758..88ff066ec 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -47,6 +47,15 @@ var removeFolderRequestCount = 0; // Email validation regexp var emailRE = /^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i; +function clickEventWrapper(functionRef) { + function button_clickEventWrapper(event) { + preventDefault(event); + return functionRef(event); + } + + return button_clickEventWrapper; +} + function createElement(tagName, id, classes, attributes, htmlAttributes, @@ -1257,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; } @@ -1927,8 +1943,9 @@ function createButton(id, caption, action) { var span = createElement("span", null, null, null, null, newButton); span.appendChild(document.createTextNode(caption)); } - if (action) - newButton.on("click", action); + if (action) { + newButton.on("click", clickEventWrapper(action)); + } return newButton; } @@ -2086,6 +2103,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)