1/* 2 Copyright (C) 2007-2014 Inverse inc. 3 4 This file is part of SOGo 5 6 SOGo is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOGo is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with OGo; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22 23#import <NGObjWeb/WOContext+SoObjects.h> 24#import <NGObjWeb/WOResponse.h> 25#import <NGExtensions/NSCalendarDate+misc.h> 26#import <NGCards/iCalCalendar.h> 27#import <NGCards/iCalFreeBusy.h> 28#import <NGCards/iCalPerson.h> 29 30#import <SOGo/SOGoBuild.h> 31#import <SOGo/SOGoDomainDefaults.h> 32#import <SOGo/SOGoSource.h> 33#import <SOGo/SOGoUser.h> 34#import <SOGo/SOGoUserManager.h> 35 36#import "SOGoAppointmentFolder.h" 37#import "SOGoAppointmentFolders.h" 38 39#import "MSExchangeFreeBusy.h" 40 41#import "SOGoFreeBusyObject.h" 42 43@interface SOGoFreeBusyObject (PrivateAPI) 44- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos 45 from: (NSCalendarDate *) _startDate 46 to: (NSCalendarDate *) _endDate; 47@end 48 49@implementation SOGoFreeBusyObject 50 51- (iCalPerson *) iCalPersonWithUID: (NSString *) uid 52{ 53 iCalPerson *person; 54 SOGoUserManager *um; 55 NSString *domain; 56 NSDictionary *contactInfos; 57 58 um = [SOGoUserManager sharedUserManager]; 59 contactInfos = [um contactInfosForUserWithUIDorEmail: uid]; 60 if (contactInfos == nil) 61 { 62 // Search among global addressbooks 63 domain = [[context activeUser] domain]; 64 contactInfos = [um fetchContactWithUID: uid inDomain: domain]; 65 } 66 67 /* iCal.app compatibility: 68 - don't add "cn"; */ 69 person = [iCalPerson new]; 70 [person autorelease]; 71 [person setEmail: [contactInfos objectForKey: @"c_email"]]; 72 73 return person; 74} 75 76/* Private API */ 77- (iCalFreeBusyType) _fbTypeForEventStatus: (int) eventStatus 78{ 79 //unsigned int status; 80 iCalFreeBusyType fbType; 81 82 //status = [eventStatus unsignedIntValue]; 83 if (eventStatus == 0) 84 fbType = iCalFBBusyTentative; 85 else if (eventStatus == 1) 86 fbType = iCalFBBusy; 87 else 88 fbType = iCalFBFree; 89 90 return fbType; 91} 92 93- (NSString *) iCalStringForFreeBusyInfos: (NSArray *) _infos 94 withMethod: (NSString *) method 95 andUID: (NSString *) uid 96 andOrganizer: (iCalPerson *) organizer 97 andContact: (NSString *) contactID 98 from: (NSCalendarDate *) _startDate 99 to: (NSCalendarDate *) _endDate 100{ 101 NSArray *emails, *partstates; 102 NSEnumerator *events; 103 iCalCalendar *calendar; 104 iCalFreeBusy *freebusy; 105 NSDictionary *info; 106 iCalFreeBusyType type; 107 SOGoUser *user; 108 NSString *login; 109 int i; 110 111 login = [container ownerInContext: context]; 112 user = [SOGoUser userWithLogin: login]; 113 114 calendar = [iCalCalendar groupWithTag: @"vcalendar"]; 115 [calendar setProdID: [NSString stringWithFormat: 116 @"-//Inverse inc./SOGo %@//EN", 117 SOGoVersion]]; 118 [calendar setVersion: @"2.0"]; 119 if (method) 120 [calendar setMethod: method]; 121 122 freebusy = [iCalFreeBusy groupWithTag: @"vfreebusy"]; 123 if (uid) 124 [freebusy setUid: uid]; 125 if (organizer) 126 [freebusy setOrganizer: organizer]; 127 if (contactID) 128 [freebusy addToAttendees: [self iCalPersonWithUID: contactID]]; 129 else 130 [freebusy addToAttendees: [self iCalPersonWithUID: login]]; 131 [freebusy setTimeStampAsDate: [NSCalendarDate calendarDate]]; 132 [freebusy setStartDate: _startDate]; 133 [freebusy setEndDate: _endDate]; 134 135 /* ORGANIZER - strictly required but missing for now */ 136 137 /* ATTENDEE */ 138// person = [self iCalPersonWithUid: login]; 139// [person setTag: @"ATTENDEE"]; 140// [ms appendString: [person versitString]]; 141 142 /* FREEBUSY */ 143 events = [_infos objectEnumerator]; 144 while ((info = [events nextObject])) 145 if ([[info objectForKey: @"c_isopaque"] boolValue]) 146 { 147 type = iCalFBFree; 148 149 // If the event has NO organizer (which means it's the user that has created it) OR 150 // If we are the organizer of the event THEN we are automatically busy 151 if ([[info objectForKey: @"c_orgmail"] length] == 0 || 152 [user hasEmail: [info objectForKey: @"c_orgmail"]]) 153 { 154 type = iCalFBBusy; 155 } 156 else 157 { 158 // We check if the user has accepted/declined or needs action 159 // on the current event. 160 emails = [[info objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"]; 161 162 for (i = 0; i < [emails count]; i++) 163 { 164 if ([user hasEmail: [emails objectAtIndex: i]]) 165 { 166 // We now fetch the c_partstates array and get the participation 167 // status of the user for the event 168 partstates = [[info objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"]; 169 170 if (i < [partstates count]) 171 { 172 type = [self _fbTypeForEventStatus: [[partstates objectAtIndex: i] intValue]]; 173 } 174 break; 175 } 176 } 177 } 178 179 if (type == iCalFBBusy 180 || type == iCalFBBusyTentative 181 || type == iCalFBBusyUnavailable) 182 [freebusy addFreeBusyFrom: [info objectForKey: @"startDate"] 183 to: [info objectForKey: @"endDate"] 184 type: type]; 185 } 186 187 [calendar setUniqueChild: freebusy]; 188 189 return [calendar versitString]; 190} 191 192- (NSString *) contentAsString 193{ 194 NSCalendarDate *today, *startDate, *endDate; 195 SOGoUserDefaults *ud; 196 SOGoDomainDefaults *dd; 197 NSArray *interval; 198 int start, end; 199 200 today = [[NSCalendarDate calendarDate] beginOfDay]; 201 ud = [[context activeUser] userDefaults]; 202 [today setTimeZone: [ud timeZone]]; 203 204 dd = [[context activeUser] domainDefaults]; 205 interval = [dd freeBusyDefaultInterval]; 206 if ([interval count] > 1) 207 { 208 start = [[interval objectAtIndex: 0] unsignedIntValue]; 209 end = [[interval objectAtIndex: 1] unsignedIntValue]; 210 } 211 else 212 { 213 start = 7; 214 end = 7; 215 } 216 217 startDate = [today dateByAddingYears: 0 months: 0 days: -start 218 hours: 0 minutes: 0 seconds: 0]; 219 endDate = [today dateByAddingYears: 0 months: 0 days: end 220 hours: 0 minutes: 0 seconds: 0]; 221 222 return [self contentAsStringFrom: startDate to: endDate]; 223} 224 225- (NSString *) contentAsStringWithMethod: (NSString *) method 226 andUID: (NSString *) UID 227 andOrganizer: (iCalPerson *) organizer 228 andContact: (NSString *) contactID 229 from: (NSCalendarDate *) _startDate 230 to: (NSCalendarDate *) _endDate 231{ 232 NSArray *infos; 233 234 infos = [self fetchFreeBusyInfosFrom: _startDate to: _endDate 235 forContact: contactID]; 236 237 return [self iCalStringForFreeBusyInfos: infos 238 withMethod: method 239 andUID: UID andOrganizer: organizer 240 andContact: contactID 241 from: _startDate to: _endDate]; 242} 243 244- (NSString *) contentAsStringFrom: (NSCalendarDate *) _startDate 245 to: (NSCalendarDate *) _endDate 246{ 247 return [self contentAsStringWithMethod: nil andUID: nil 248 andOrganizer: nil 249 andContact: nil 250 from: _startDate 251 to: _endDate]; 252} 253 254/** 255 * Fetch freebusy information for a user that exists in a contact source 256 * (not an authentication source) for which freebusy information is available 257 * (currently limited to a Microsoft Exchange server with Web Services enabled). 258 * @param startDate the beginning of the covered period 259 * @param endDate the ending of the covered period 260 * @param uid the ID of the contact within the current domain 261 * @return an array of dictionaries containing the start and end dates of each busy period 262 * @see MSExchangeFreeBusy.m 263 */ 264- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate 265 to: (NSCalendarDate *) endDate 266 forContact: (NSString *) uid 267{ 268 if ([uid length]) 269 { 270 SOGoUserManager *um; 271 NSString *domain, *email; 272 NSDictionary *contact; 273 MSExchangeFreeBusy *exchangeFreeBusy; 274 NSObject <SOGoDNSource> *source; 275 276 um = [SOGoUserManager sharedUserManager]; 277 domain = [[context activeUser] domain]; 278 contact = [um fetchContactWithUID: uid inDomain: domain]; 279 if (contact) 280 { 281 email = [contact valueForKey: @"c_email"]; 282 source = [contact objectForKey: @"source"]; 283 if ([email length] 284 && [source conformsToProtocol: @protocol (SOGoDNSource)] 285 && [source MSExchangeHostname]) 286 { 287 exchangeFreeBusy = [[MSExchangeFreeBusy alloc] init]; 288 [exchangeFreeBusy autorelease]; 289 290 return [exchangeFreeBusy fetchFreeBusyInfosFrom: startDate 291 to: endDate 292 forEmail: email 293 inSource: source 294 inContext: context]; 295 } 296 } 297 } 298 else 299 { 300 return [self fetchFreeBusyInfosFrom: startDate to: endDate]; 301 } 302 303 // No freebusy information found 304 return nil; 305} 306 307 308- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) startDate 309 to: (NSCalendarDate *) endDate 310{ 311 SOGoAppointmentFolder *calFolder; 312 SOGoAppointmentFolders *calFolders; 313 SOGoUser *user; 314 SOGoUserDefaults *ud; 315 NSArray *folders; 316 NSMutableArray *infos; 317 NSString *login; 318 unsigned int count, max; 319 320 infos = [NSMutableArray array]; 321 322 calFolders = [container lookupName: @"Calendar" 323 inContext: context 324 acquire: NO]; 325 [calFolders appendSubscribedSources]; 326 folders = [calFolders subFolders]; 327 328 max = [folders count]; 329 for (count = 0; count < max; count++) 330 { 331 calFolder = [folders objectAtIndex: count]; 332 if ([calFolder includeInFreeBusy]) 333 [infos addObjectsFromArray: [calFolder fetchFreeBusyInfosFrom: startDate 334 to: endDate]]; 335 } 336 337 login = [container ownerInContext: context]; 338 user = [SOGoUser userWithLogin: login]; 339 ud = [user userDefaults]; 340 341 if ([ud busyOffHours]) 342 { 343 NSCalendarDate *currentStartDate, *currentEndDate, *weekendStartDate, *weekendEndDate; 344 NSTimeZone *timeZone; 345 unsigned int dayStartHour, dayEndHour, intervalHours; 346 BOOL firstRange; 347 348 dayStartHour = [ud dayStartHour]; 349 dayEndHour = [ud dayEndHour]; 350 intervalHours = dayStartHour + 24 - dayEndHour; 351 timeZone = [ud timeZone]; 352 firstRange = YES; 353 354 currentStartDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra] 355 month: [startDate monthOfYear] 356 day: [startDate dayOfMonth] 357 hour: 0 358 minute: 0 359 second: 0 360 timeZone: timeZone]; 361 currentEndDate = [NSCalendarDate dateWithYear: [startDate yearOfCommonEra] 362 month: [startDate monthOfYear] 363 day: [startDate dayOfMonth] 364 hour: dayStartHour 365 minute: 0 366 second: 0 367 timeZone: timeZone]; 368 369 while ([currentStartDate compare: endDate] == NSOrderedAscending || 370 [currentStartDate compare: endDate] == NSOrderedSame) 371 { 372 if ([endDate compare: currentEndDate] == NSOrderedAscending) 373 currentEndDate = endDate; 374 375 if ([currentStartDate compare: startDate] == NSOrderedAscending) 376 { 377 if ([startDate compare: currentEndDate] == NSOrderedAscending) 378 { 379 [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: 380 [NSNumber numberWithBool: YES], @"c_isopaque", 381 startDate, @"startDate", 382 currentEndDate, @"endDate", nil]]; 383 } 384 } 385 else 386 { 387 [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: 388 [NSNumber numberWithBool: YES], @"c_isopaque", 389 currentStartDate, @"startDate", 390 currentEndDate, @"endDate", nil]]; 391 } 392 393 if (currentEndDate != endDate 394 && ([currentEndDate dayOfWeek] == 6 || [currentEndDate dayOfWeek] == 0)) 395 { 396 // Fill weekend days 397 weekendStartDate = currentEndDate; 398 weekendEndDate = [weekendStartDate addYear:0 month:0 day:0 hour:(-[weekendStartDate hourOfDay] + dayEndHour) minute:0 second:0]; 399 [infos addObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: YES], @"c_isopaque", 400 weekendStartDate, @"startDate", 401 weekendEndDate, @"endDate", nil]]; 402 } 403 404 // Compute next range 405 if (firstRange) 406 { 407 currentStartDate = [currentStartDate addYear:0 month:0 day:0 hour:dayEndHour minute:0 second:0]; 408 firstRange = NO; 409 } 410 else 411 { 412 currentStartDate = [currentStartDate addYear:0 month:0 day:1 hour:0 minute:0 second:0]; 413 } 414 currentEndDate = [currentStartDate addYear:0 month:0 day:0 hour:intervalHours minute:0 second:0]; 415 } 416 } 417 418 return infos; 419} 420 421- (NSString *) iCalString 422{ 423 // for UI-X appointment viewer 424 return [self contentAsString]; 425} 426 427/* deliver content without need for view method */ 428 429- (id) GETAction: (id)_ctx 430{ 431 WOResponse *r; 432 NSData *contentData; 433 434 contentData = [[self contentAsString] 435 dataUsingEncoding: NSUTF8StringEncoding]; 436 437 r = [(WOContext *) _ctx response]; 438 [r setHeader: @"text/calendar" forKey: @"content-type"]; 439 [r setContent: contentData]; 440 [r setStatus: 200]; 441 442 return r; 443} 444 445- (BOOL) isFolderish 446{ 447 return NO; 448} 449 450- (NSString *) davContentType 451{ 452 return @"text/calendar"; 453} 454 455- (NSArray *) aclsForUser: (NSString *) uid 456{ 457 return nil; 458} 459 460@end 461