diff --git a/ChangeLog b/ChangeLog index 2eb727274..cfa955fa6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,36 @@ +2012-01-12 Francis Lachapelle + + * SoObjects/SOGo/SOGoSource.h (-bindDN, -bindPassword) + (-MSExchangeHostname): new accessors to the SOGoDNSource protocol. + + * SoObjects/SOGo/LDAPSource.m (-_convertLDAPEntryToContact:): + added a reference to the instance source in the returned dictionary. + + * SoObjects/SOGo/SOGoUserManager.m + (-_compactAndCompleteContacts:): keep the source instance in the + returned dictionary. + + * UI/MainUI/SOGoUserHomePage.m (-readFreeBusyAction): the http + post can now include an optional "uid" parameter to perform the + freebusy lookup on the user corresponding to the specified + uid. This covers the special case where we want to query a user + from a contact source (not an authentication source) and for which + external freebusy information is available (currently limited to a + Microsoft Exchange server with Web Services enabled). + + * SoObjects/Appointments/SOGoFreeBusyObject.m + (-fetchFreeBusyInfosFrom:to:forUser:): new method currently + limited perform a SOAP request to a MS Exhange server to retrieve + the freebusy information of a user. + + * UI/WebServerResources/UIxAttendeesEditor.js + (performSearchCallback): if a matching contact has the + "isMSExchange" attribute, prefix the uid with the login uid. + (_performAjaxRequest): query the freebusy information from the + login user (/SOGo/so//freebusy.ifb/ajaxRead?uid=) + when a contact uid is specified. Otherwise, perform the query on + the user instance as usual (/SOGo/so//freebusy.ifb/ajaxRead). + 2012-01-10 Wolfgang Sourdeau * SoObjects/Appointments/SOGoAppointmentObject.m diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index dd11b0435..cf823d7c3 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -42,10 +42,15 @@ Appointments_OBJC_FILES = \ SOGoAptMailUpdate.m \ SOGoAptMailReceipt.m \ \ - SOGoEMailAlarmsManager.m + SOGoEMailAlarmsManager.m \ + \ + MSExchangeFreeBusySOAPRequest.m \ + MSExchangeFreeBusy.m Appointments_RESOURCE_FILES += \ product.plist \ + \ + MSExchangeFreeBusySOAPRequest.wo Appointments_LANGUAGES = BrazilianPortuguese Catalan Czech Danish Dutch English French German Hungarian Icelandic Italian NorwegianBokmal NorwegianNynorsk Polish Russian SpanishSpain SpanishArgentina Swedish Ukrainian Welsh @@ -53,6 +58,7 @@ Appointments_LOCALIZED_RESOURCE_FILES = Localizable.strings ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/ ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/ +ADDITIONAL_LDFLAGS += -lcurl -lgnutls -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/wobundle.make diff --git a/SoObjects/Appointments/MSExchangeFreeBusy.h b/SoObjects/Appointments/MSExchangeFreeBusy.h new file mode 100644 index 000000000..3d9c49c04 --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusy.h @@ -0,0 +1,69 @@ +/* MSExchangeFreeBusy.h - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc. + * + * Author: Francis Lachapelle + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef MSEXCHANGEFREEBUSY_H +#define MSEXCHANGEFREEBUSY_H + +#include + +@class MSExchangeFreeBusyResponse; +@class MSExchangeFreeBusyView; + +@interface MSExchangeFreeBusy : NSObject +{ + NSMutableData *curlBody; + MSExchangeFreeBusyResponse *response; +} + +- (size_t) curlWritePtr: (void *) inPtr + size: (size_t) inSize + number: (size_t) inNumber; +- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate + forEmail: (NSString *) email + inSource: (NSObject *) source +// fromServer: (NSString *) hostname + inContext: (WOContext *) context; + +@end + +@interface MSExchangeFreeBusyResponse : NSObject +{ + MSExchangeFreeBusyView *view; +} + +- (MSExchangeFreeBusyView *) view; + +@end + +@interface MSExchangeFreeBusyView : NSObject +{ + NSString *freeBusyViewType; + NSString *mergedFreeBusy; +} + +- (NSArray *) infosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate; + +@end + +#endif /* MSEXCHANGEFREEBUSY_H */ diff --git a/SoObjects/Appointments/MSExchangeFreeBusy.m b/SoObjects/Appointments/MSExchangeFreeBusy.m new file mode 100644 index 000000000..05be8c37c --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusy.m @@ -0,0 +1,350 @@ +/* MSExchangeFreeBusy.m - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc. + * + * Author: Francis Lachapelle + * + * 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 + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import + +#import +#import +#import + +#import + +#import +#import +#import +#import + +#import + +#import + +#import "MSExchangeFreeBusySOAPRequest.h" +#import "MSExchangeFreeBusy.h" + +size_t curlBodyFunction(void *ptr, size_t size, size_t nmemb, void *inSelf) +{ + return [(MSExchangeFreeBusy *)inSelf curlWritePtr:ptr size:size number:nmemb]; +} + +@implementation MSExchangeFreeBusy + +- (id) init +{ + if ((self = [super init])) + { + curlBody = [[NSMutableData alloc] init]; + } + + return self; +} + +- (void) dealloc +{ + [curlBody release]; + [super dealloc]; +} + +- (size_t) curlWritePtr:(void *)inPtr + size:(size_t)inSize + number:(size_t)inNumber +{ + size_t written = inSize*inNumber; + NSData *data = [NSData dataWithBytes:inPtr length:written]; + [curlBody appendData: data]; + + return written; +} + +/** + * Fetch the user availability by sending a SOAP request to a MS Exchange server (EWS). + * @param startDate the beginning of the covered period + * @param endDate the ending of the covered period + * @param email the address of the user to query + * @param source the SOGo source of the user + * @param context the current WO context + * @return an array of dictionaries containing the start and end dates of each busy period + * @see + */ +- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate + forEmail: (NSString *) email + inSource: (NSObject *) source + inContext: (WOContext *) context +{ + static id parser = nil; + static SaxObjectDecoder *sax = nil; + + MSExchangeFreeBusySOAPRequest *soapRequest; + MSExchangeFreeBusyResponse *freeBusyResponse; + NSString *rawRequest, *url, *body, *hostname, *httpauth, *authname, *password; + NSArray *infos = nil; + NSDictionary *root; + + CURL *curl; + struct curl_slist *headerlist=NULL; + CURLcode rc; + char error[CURL_ERROR_SIZE]; + + // Construct SOAP GetUserAvailabilityRequest from .wo template + soapRequest = [[WOApplication application] pageWithName: @"MSExchangeFreeBusySOAPRequest" + inContext: context]; + [soapRequest setAddress: email + from: startDate + to: endDate]; + rawRequest = [[soapRequest generateResponse] contentAsString]; + + if ([rawRequest length]) + { + // Prepare HTTPS post using libcurl + curl_global_init(CURL_GLOBAL_SSL); + curl = curl_easy_init(); + headerlist = curl_slist_append(headerlist, "Content-Type: text/xml; charset=utf-8"); + if (curl) + { + hostname = [source MSExchangeHostname]; + authname = [source lookupLoginByDN: [source bindDN]]; + password = [source bindPassword]; + error[0] = 0; + if ([authname length] && [password length]) + { + httpauth = [NSString stringWithFormat: @"%@:%@", authname, password]; + curl_easy_setopt(curl, CURLOPT_USERPWD, [httpauth UTF8String]); + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); + } + url = [NSString stringWithFormat: @"https://%@/ews/Exchange.asmx", hostname]; + curl_easy_setopt(curl, CURLOPT_URL, [url UTF8String]); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, [rawRequest UTF8String]); + //curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction); + //curl_easy_setopt(curl, CURLOPT_HEADER, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlBodyFunction); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, self); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error); + + // Perform SOAP request + rc = curl_easy_perform(curl); + if (rc != 0) + [self errorWithFormat: @"CURL error while accessing %@ (%d): ", url, rc, [NSString stringWithCString: error]]; + curl_easy_cleanup(curl); + curl_slist_free_all(headerlist); + + if ([curlBody length]) + { + // Parse SOAP response + if (parser == nil) + { + parser = [[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType:@"text/xml"]; + [parser retain]; + } + if (sax == nil && parser != nil) + { + sax = [[SaxObjectDecoder alloc] initWithMappingAtPath:@"./MSExchangeFreeBusySOAPResponseMap.plist"]; + [parser setContentHandler:sax]; + //[parser setErrorHandler:sax]; + } + + body = [[NSString alloc] initWithData:curlBody encoding:NSASCIIStringEncoding]; + [body autorelease]; + + [parser parseFromSource: body]; + root = [sax rootObject]; + freeBusyResponse = [[root objectForKey: @"Body"] objectForKey: @"GetUserAvailabilityResponse"]; + + // Extract busy periods + infos = [[freeBusyResponse view] infosFrom: startDate to: endDate]; + } + } + } + + return infos; +} + +@end + + +@implementation MSExchangeFreeBusyResponse + +- (id) init +{ + if ((self = [super init])) + { + view = nil; + } + + return self; +} + +- (void) dealloc +{ + [view release]; + [super dealloc]; +} + +- (MSExchangeFreeBusyView *) view +{ + return self->view; +} + +- (void) setFreeBusyResponseArray: (NSDictionary *) _value +{ + NSString *responseCode; + NSArray *responses; + NSDictionary *response; + + view = nil; + responses = (NSArray *) [_value objectForKey: @"responses"]; + + if ([responses count] != 1) + { + [self errorWithFormat: @"unexpected number of responses (%i) from SOAP request", [responses count]]; + } + else + { + response = [responses objectAtIndex: 0]; + responseCode = [[response objectForKey: @"ResponseMessage"] objectForKey: @"ResponseCode"]; + if ([responseCode compare: @"NoError"] == NSOrderedSame) + { + view = [response objectForKey: @"FreeBusyView"]; + [view retain]; + } + } + + [self logWithFormat: @"SOAP Response: %@", self->view]; +} + +@end + +@implementation MSExchangeFreeBusyView + +- (id) init +{ + if ((self = [super init])) + { + freeBusyViewType = nil; + mergedFreeBusy = nil; + } + + return self; +} + +- (void) dealloc +{ + [freeBusyViewType release]; + [mergedFreeBusy release]; + [super dealloc]; +} + +- (void) setFreeBusyViewType: (NSString *) _value +{ + ASSIGN(freeBusyViewType, _value); +} + +- (void) setMergedFreeBusy: (NSString *) _value +{ + ASSIGN(mergedFreeBusy, _value); +} + +/** + * Parse the "DetailedMerged" representation of the freebusy information and + * extract the busy periods. + * @param startDate the beginning of the covered period + * @param endDate the ending of the covered period + * @return an array of dictionaries containing the start and end dates of each busy period + * @see + */ +- (NSArray *) infosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate +{ + NSMutableArray *infos; + NSCalendarDate *currentDate, *currentStartDate, *currentEndDate; + unsigned int count; + + infos = [NSMutableArray array]; + currentStartDate = nil; + currentDate = startDate; + count = 0; + + while (([currentDate compare: endDate] == NSOrderedAscending || + [currentDate compare: endDate] == NSOrderedSame) && + [mergedFreeBusy length] > count) + { + switch ([mergedFreeBusy characterAtIndex: count]) + { + case '0': // Free + if (currentStartDate) + { + currentEndDate = currentDate; + [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool: YES], @"c_isopaque", + currentStartDate, @"startDate", + currentEndDate, @"endDate", nil]]; + [self debugWithFormat: @"Busy period from %@ to %@", currentStartDate, currentEndDate]; + currentStartDate = nil; + } + break; + + case '1': // Tentative + case '2': // Busy + case '3': // Out of Office + if (currentStartDate == nil) + currentStartDate = currentDate; + break; + } + + count++; + currentDate = [currentDate dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 15 seconds: 0]; + } + + if (currentStartDate) + { + currentEndDate = currentDate; + [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool: YES], @"c_isopaque", + currentStartDate, @"startDate", + currentEndDate, @"endDate", nil]]; + [self debugWithFormat: @"Busy period from %@ to %@", currentStartDate, currentEndDate]; + } + + return infos; +} + +- (NSString *) description +{ + NSMutableString *s; + + s = [NSMutableString stringWithCapacity: 64]; + [s appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; + if (freeBusyViewType) + [s appendFormat:@" freeBusyViewType='%@'", freeBusyViewType]; + if (mergedFreeBusy) + [s appendFormat:@" mergedFreeBusy='%@'", mergedFreeBusy]; + [s appendString:@">"]; + + return s; +} + +@end diff --git a/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.h b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.h new file mode 100644 index 000000000..3553ef949 --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2012 Inverse inc. + + This file is part of SOGo. + + SOGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + SOGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + 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. +*/ + +#ifndef __Appointments_MSExchangeFreeBusySOAPRequest_H_ +#define __Appointments_MSExchangeFreeBusySOAPRequest_H_ + +#include + +@class NSCalendarDate; +@class NSMutableDictionary; +@class NSString; +@class NSTimeZone; +@class iCalEvent; + +/* + * NOTE: We inherit from SoComponent in order to get the correct + * resourceManager required for this product + */ +@interface MSExchangeFreeBusySOAPRequest : SoComponent +{ + NSString *address; + NSTimeZone *timeZone; + NSCalendarDate *startDate; + NSCalendarDate *endDate; + int interval; +} + +- (void) setAddress: (NSString *) _address + from: (NSCalendarDate *) _startDate + to: (NSCalendarDate *) _endDate; + +@end + +#endif /* __Appointments_MSExchangeFreeBusySOAPRequest_H_ */ diff --git a/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.m b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.m new file mode 100644 index 000000000..01c9e8aea --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.m @@ -0,0 +1,105 @@ +/* + Copyright (C) 2012 Inverse inc. + + This file is part of SOGo. + + SOGo is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + SOGo is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + 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. +*/ + +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import "MSExchangeFreeBusySOAPRequest.h" + +@implementation MSExchangeFreeBusySOAPRequest + +- (id) init +{ + if ((self = [super init])) + { + address = nil; + timeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"]; + [timeZone retain]; + startDate = nil; + endDate = nil; + interval = 15; + } + + return self; +} + +- (void) dealloc +{ + [address release]; + [timeZone release]; + [startDate release]; + [endDate release]; + [super dealloc]; +} + +- (void) setAddress: (NSString *) newAddress + from: (NSCalendarDate *) newStartDate + to: (NSCalendarDate *) newEndDate +{ + ASSIGN(address, newAddress); + ASSIGN(startDate, newStartDate); + ASSIGN(endDate, newEndDate); + + [startDate setTimeZone: timeZone]; + [endDate setTimeZone: timeZone]; +} + +- (NSString *) serverVersion +{ + return @"Exchange2007_SP1"; +} + +- (NSString *) address +{ + return address; +} + +- (NSString *) startTime +{ + return [startDate descriptionWithCalendarFormat: @"%Y-%m-%dT%H:%M:%S"]; +} + +- (NSString *) endTime +{ + return [endDate descriptionWithCalendarFormat: @"%Y-%m-%dT%H:%M:%S"]; +} + +- (NSString *) interval +{ + return [NSString stringWithFormat: @"%i", interval]; +} + +@end diff --git a/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.html b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.html new file mode 100644 index 000000000..33627d9db --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.html @@ -0,0 +1,45 @@ + + + + + + + + + 0 + + 0 + + 1 + 1 + Sunday + + + 0 + + 1 + 1 + Sunday + + + + + + <#address/> + + Required + false + + + + + <#startTime/> + <#endTime/> + + 15 + DetailedMerged + + + + diff --git a/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.wod b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.wod new file mode 100644 index 000000000..c11d5fd63 --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusySOAPRequest.wo/MSExchangeFreeBusySOAPRequest.wod @@ -0,0 +1,24 @@ +serverVersion: WOString { + value = serverVersion; + escapeHTML = NO; +} + +address: WOString { + value = address; + escapeHTML = NO; +} + +startTime: WOString { + value = startTime; + escapeHTML = NO; +} + +endTime: WOString { + value = endTime; + escapeHTML = NO; +} + +interval: WOString { + value = interval; + escapeHTML = NO; +} \ No newline at end of file diff --git a/SoObjects/Appointments/MSExchangeFreeBusySOAPResponseMap.plist b/SoObjects/Appointments/MSExchangeFreeBusySOAPResponseMap.plist new file mode 100644 index 000000000..e8f78bb23 --- /dev/null +++ b/SoObjects/Appointments/MSExchangeFreeBusySOAPResponseMap.plist @@ -0,0 +1,63 @@ +{ /* -*-java-*- */ + "http://schemas.xmlsoap.org/soap/envelope/" = { + Envelope = { + class = NSMutableDictionary; + }; + + Header = { + class = NSMutableDictionary; + attributes = { + serverVersionInfo = ServerVersionInfo; + }; + }; + + Body = { + class = NSMutableDictionary; + }; + }; + + "http://schemas.microsoft.com/exchange/services/2006/messages" = { + GetUserAvailabilityResponse = { + class = MSExchangeFreeBusyResponse; + attributes = { + FreeBusyResponseArray = FreeBusyResponseArray; + }; + }; + + FreeBusyResponseArray = { + class = NSMutableDictionary; + ToManyRelationships = { + responses = ( FreeBusyResponse ); + }; + }; + + ResponseMessage = { + class = NSMutableDictionary; + }; + + ResponseCode = { + class = NSString; + }; + + FreeBusyResponse = { + class = NSMutableDictionary; + }; + + FreeBusyView = { + class = MSExchangeFreeBusyView; + attributes = { + FreeBusyViewType = freeBusyViewType; + MergedFreeBusy = mergedFreeBusy; + }; + }; + }; + + "http://schemas.microsoft.com/exchange/services/2006/types" = { + FreeBusyViewType = { + class = NSString; + }; + MergedFreeBusy = { + class = NSString; + }; + }; +} \ No newline at end of file diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.h b/SoObjects/Appointments/SOGoFreeBusyObject.h index 1a09a38f9..e3644a54c 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.h +++ b/SoObjects/Appointments/SOGoFreeBusyObject.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2011 Inverse inc. + Copyright (C) 2007-2012 Inverse inc. Copyright (C) 2000-2004 SKYRIX Software AG This file is part of SOGo @@ -38,8 +38,6 @@ @class iCalPerson; @interface SOGoFreeBusyObject : SOGoObject -{ -} /* accessors */ @@ -55,7 +53,9 @@ - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate; - +- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate + forUser: (NSString *) uid; @end #endif /* __Appointments_SOGoFreeBusyObject_H_ */ diff --git a/SoObjects/Appointments/SOGoFreeBusyObject.m b/SoObjects/Appointments/SOGoFreeBusyObject.m index f66d00c58..af02c2ea1 100644 --- a/SoObjects/Appointments/SOGoFreeBusyObject.m +++ b/SoObjects/Appointments/SOGoFreeBusyObject.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2011 Inverse inc. + Copyright (C) 2007-2012 Inverse inc. Copyright (C) 2000-2004 SKYRIX Software AG This file is part of SOGo @@ -21,10 +21,12 @@ */ #import +#import #import #import #import +#import #import #import #import @@ -34,6 +36,7 @@ #import #import +#import #import #import #import @@ -42,6 +45,8 @@ #import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolders.h" +#import "MSExchangeFreeBusy.h" + #import "SOGoFreeBusyObject.h" @interface SOGoFreeBusyObject (PrivateAPI) @@ -240,6 +245,60 @@ to: _endDate]; } +/** + * Fetch freebusy information for a user that exists in a contact source + * (not an authentication source) for which freebusy information is available + * (currently limited to a Microsoft Exchange server with Web Services enabled). + * @param startDate the beginning of the covered period + * @param endDate the ending of the covered period + * @param uid the ID of the user within the current domain + * @return an array of dictionaries containing the start and end dates of each busy period + * @see MSExchangeFreeBusy.m + */ +- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate + to: (NSCalendarDate *) endDate + forUser: (NSString *) uid +{ + if ([uid length]) + { + SOGoUserManager *um; + NSArray *users; + NSString *domain, *email; + NSDictionary *user; + MSExchangeFreeBusy *exchangeFreeBusy; + NSObject *source; + + um = [SOGoUserManager sharedUserManager]; + domain = [[context activeUser] domain]; + users = [um fetchContactsMatching: uid inDomain: domain]; + if ([users count] == 1) + { + user = [users lastObject]; + email = [user valueForKey: @"c_email"]; + source = [user objectForKey: @"source"]; + if ([email length]) + { + exchangeFreeBusy = [[MSExchangeFreeBusy alloc] init]; + [exchangeFreeBusy autorelease]; + + return [exchangeFreeBusy fetchFreeBusyInfosFrom: startDate + to: endDate + forEmail: email + inSource: source + inContext: context]; + } + } + } + else + { + return [self fetchFreeBusyInfosFrom: startDate to: endDate]; + } + + // No freebusy information found + return nil; +} + + - (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate to: (NSCalendarDate *) endDate { diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 0f7929527..0a302f0f6 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -74,6 +74,8 @@ /* resources handling */ NSString *kindField; NSString *multipleBookingsField; + + NSString *MSExchangeHostname; } - (void) setBindDN: (NSString *) newBindDN diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index abb45190e..b14362515 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -1,6 +1,6 @@ /* LDAPSource.m - this file is part of SOGo * - * Copyright (C) 2007-2011 Inverse inc. + * Copyright (C) 2007-2012 Inverse inc. * * Author: Wolfgang Sourdeau * Ludovic Marcotte @@ -124,6 +124,8 @@ static NSArray *commonSearchFields; @"serialnumber", @"calfburl", @"proxyaddresses", + // MS Exchange + @"msExchHomeServerName", nil]; [commonSearchFields retain]; } @@ -181,6 +183,8 @@ static NSArray *commonSearchFields; kindField = nil; multipleBookingsField = nil; + MSExchangeHostname = nil; + _dnCache = [[NSMutableDictionary alloc] init]; } @@ -216,6 +220,7 @@ static NSArray *commonSearchFields; [_dnCache release]; [kindField release]; [multipleBookingsField release]; + [MSExchangeHostname release]; [super dealloc]; } @@ -249,7 +254,7 @@ static NSArray *commonSearchFields; IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"] bindFields: [udSource objectForKey: @"bindFields"] kindField: [udSource objectForKey: @"KindFieldName"] - andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; + andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]]; if ([sourceDomain length]) { @@ -286,6 +291,8 @@ static NSArray *commonSearchFields; if ([udSource objectForKey: @"passwordPolicy"]) passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue]; + + ASSIGN(MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]); } return self; @@ -297,11 +304,21 @@ static NSArray *commonSearchFields; ASSIGN(bindDN, theDN); } +- (NSString *) bindDN +{ + return bindDN; +} + - (void) setBindPassword: (NSString *) thePassword { ASSIGN (password, thePassword); } +- (NSString *) bindPassword +{ + return password; +} + - (BOOL) bindAsCurrentUser { return _bindAsCurrentUser; @@ -888,6 +905,7 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField id o; contactEntry = [NSMutableDictionary dictionary]; + [contactEntry setObject: self forKey: @"source"]; [contactEntry setObject: [ldapEntry dn] forKey: @"dn"]; attributes = [[self _searchAttributes] objectEnumerator]; @@ -1225,4 +1243,9 @@ andMultipleBookingsField: (NSString *) newMultipleBookingsField return baseDN; } +- (NSString *) MSExchangeHostname +{ + return MSExchangeHostname; +} + @end diff --git a/SoObjects/SOGo/SOGoSource.h b/SoObjects/SOGo/SOGoSource.h index 7fd04fd46..d7cc7db04 100644 --- a/SoObjects/SOGo/SOGoSource.h +++ b/SoObjects/SOGo/SOGoSource.h @@ -1,6 +1,6 @@ /* SOGoSource.h - this file is part of SOGo * - * Copyright (C) 2009-2010 Inverse inc. + * Copyright (C) 2009-2012 Inverse inc. * * Author: Ludovic Marcotte * @@ -63,13 +63,16 @@ @protocol SOGoDNSource - (void) setBindDN: (NSString *) theDN; +- (NSString *) bindDN; - (void) setBindPassword: (NSString *) thePassword; +- (NSString *) bindPassword; - (BOOL) bindAsCurrentUser; - (NSString *) lookupLoginByDN: (NSString *) theDN; - (NSString *) lookupDNByLogin: (NSString *) theLogin; - (NSString *) baseDN; +- (NSString *) MSExchangeHostname; @end diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index 7400021a9..7682f283f 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -60,7 +60,7 @@ SOGoUserFolder *homeFolder; NSString *currentPassword; NSString *loginInDomain; - NSString *language; + //NSString *language; NSArray *allEmails; NSMutableArray *mailAccounts; NSString *cn; diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 4d7aa6c67..68678e519 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -234,7 +234,7 @@ [currentPassword release]; [cn release]; [loginInDomain release]; - [language release]; + //[language release]; [super dealloc]; } diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index eda34babf..91915716a 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -91,15 +91,16 @@ return sharedUserManager; } -- (void) _registerSource: (NSDictionary *) udSource +- (BOOL) _registerSource: (NSDictionary *) udSource inDomain: (NSString *) domain { NSString *sourceID, *value, *type; NSMutableDictionary *metadata; NSObject *sogoSource; - BOOL isAddressBook; + BOOL isAddressBook, result; Class c; + result = NO; sourceID = [udSource objectForKey: @"id"]; if ([sourceID length] > 0) { @@ -147,17 +148,20 @@ [metadata setObject: value forKey: @"SearchFieldNames"]; [_sourcesMetadata setObject: metadata forKey: sourceID]; + result = YES; } } else [self errorWithFormat: @"attempted to register a contact/user source" @" without id (skipped)"]; + + return result; } - (int) _registerSourcesInDomain: (NSString *) domain { NSArray *userSources; - unsigned int count, max; + unsigned int count, max, total; SOGoDomainDefaults *dd; if (domain) @@ -167,11 +171,13 @@ userSources = [dd userSources]; max = [userSources count]; + total = 0; for (count = 0; count < max; count++) - [self _registerSource: [userSources objectAtIndex: count] - inDomain: domain]; + if ([self _registerSource: [userSources objectAtIndex: count] + inDomain: domain]) + total++; - return max; + return total; } - (void) _prepareSources @@ -634,8 +640,7 @@ if ([userEntry objectForKey: @"numberOfSimultaneousBookings"]) [currentUser setObject: [userEntry objectForKey: @"numberOfSimultaneousBookings"] forKey: @"numberOfSimultaneousBookings"]; - - } + } } if (!cn) @@ -824,8 +829,9 @@ { returnContact = [NSMutableDictionary dictionary]; [returnContact setObject: uid forKey: @"c_uid"]; - [compactContacts setObject: returnContact forKey: uid]; - } + [returnContact setObject: [userEntry objectForKey: @"source"] forKey: @"source"]; + [compactContacts setObject: returnContact forKey: uid]; + } if (![[returnContact objectForKey: @"c_name"] length]) [returnContact setObject: [userEntry objectForKey: @"c_name"] forKey: @"c_name"]; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 4af7bbbc8..128e0d63a 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -158,6 +158,7 @@ - (NSString *) _freeBusyFromStartDate: (NSCalendarDate *) startDate toEndDate: (NSCalendarDate *) endDate forFreeBusy: (SOGoFreeBusyObject *) fb + andUser: (NSString *) user { NSMutableArray *freeBusy; unsigned int *freeBusyItems; @@ -169,7 +170,7 @@ freeBusyItems = NSZoneCalloc (NULL, intervals, sizeof (int)); [self _fillFreeBusyItems: freeBusyItems count: intervals - withRecords: [fb fetchFreeBusyInfosFrom: startDate to: endDate] + withRecords: [fb fetchFreeBusyInfosFrom: startDate to: endDate forUser: user] fromStartDate: startDate toEndDate: endDate]; freeBusy = [NSMutableArray arrayWithCapacity: intervals]; @@ -184,15 +185,16 @@ - (id ) readFreeBusyAction { WOResponse *response; - SOGoFreeBusyObject *co; + SOGoFreeBusyObject *freebusy; NSCalendarDate *startDate, *endDate; - NSString *queryDay; + NSString *queryDay, *uid; NSTimeZone *uTZ; SOGoUser *user; user = [context activeUser]; uTZ = [[user userDefaults] timeZone]; + uid = [self queryParameterForKey: @"uid"]; queryDay = [self queryParameterForKey: @"sday"]; if ([queryDay length] == 8) { @@ -211,13 +213,13 @@ andString: @"Start date is later than end date."]; else { - co = [self clientObject]; + freebusy = [self clientObject]; response = [self responseWithStatus: 200 - andString: [self - _freeBusyFromStartDate: startDate - toEndDate: endDate - forFreeBusy: co]]; + andString: [self _freeBusyFromStartDate: startDate + toEndDate: endDate + forFreeBusy: freebusy + andUser: uid]]; } } else diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index af7faf682..bdf494de3 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -219,7 +219,7 @@ function performSearchCallback(http) { list.appendChild(node); node.address = completeEmail; // log("node.address: " + node.address); - node.uid = contact["c_uid"]; + node.uid = (contact["isMSExchange"]? UserLogin + ":" : "") + contact["c_uid"]; node.isList = isList; if (isList) { node.cname = contact["c_name"]; @@ -272,7 +272,7 @@ function performSearchCallback(http) { if (data.contacts.length == 1) { // Single result var contact = data.contacts[0]; - input.uid = contact["c_uid"]; + input.uid = (contact["isMSExchange"]? UserLogin + ":" : "") + contact["c_uid"]; var isList = (contact["c_component"] && contact["c_component"] == "vlist"); if (isList) { @@ -997,11 +997,19 @@ freeBusyRequest.prototype = { }, _performAjaxRequest: function fBR__performAjaxRequest(rqStart, rqEnd) { - var urlstr = (UserFolderURL + "../" + this.mUid - + "/freebusy.ifb/ajaxRead?" - + "sday=" + rqStart.getDayString() - + "&eday=" + rqEnd.getDayString()); - + var urlstr = UserFolderURL + "../"; + var uids = this.mUid.split(":"); + if (uids.length > 1) + urlstr += (uids[0] + + "/freebusy.ifb/ajaxRead?" + + "uid=" + uids[1] + + "&"); + else + urlstr += (this.mUid + + "/freebusy.ifb/ajaxRead?"); + urlstr += ("sday=" + rqStart.getDayString() + + "&eday=" + rqEnd.getDayString()); + var thisRequest = this; var callback = function fBR__performAjaxRequest_cb(http) { if (http.readyState == 4) {