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