]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Appointments/SOGoCalendarComponent.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1045 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Appointments / SOGoCalendarComponent.m
1 /* SOGoCalendarComponent.m - this file is part of SOGo
2  *
3  * Copyright (C) 2006 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
7  * This file is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This file is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #import <Foundation/NSString.h>
24
25 #import <NGCards/iCalCalendar.h>
26 #import <NGCards/iCalPerson.h>
27 #import <NGCards/iCalRepeatableEntityObject.h>
28 #import <NGMime/NGMime.h>
29 #import <NGMail/NGMail.h>
30 #import <NGMail/NGSendMail.h>
31
32 #import <SoObjects/SOGo/AgenorUserManager.h>
33 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import <SoObjects/SOGo/SOGoUser.h>
35
36 #import "common.h"
37
38 #import "SOGoAptMailNotification.h"
39 #import "SOGoCalendarComponent.h"
40
41 static NSString *mailTemplateDefaultLanguage = nil;
42 static BOOL sendEMailNotifications = NO;
43
44 @implementation SOGoCalendarComponent
45
46 + (void) initialize
47 {
48   NSUserDefaults      *ud;
49   static BOOL         didInit = NO;
50   
51   if (!didInit)
52     {
53       didInit = YES;
54   
55       ud = [NSUserDefaults standardUserDefaults];
56       mailTemplateDefaultLanguage = [[ud stringForKey:@"SOGoDefaultLanguage"]
57                                       retain];
58       if (!mailTemplateDefaultLanguage)
59         mailTemplateDefaultLanguage = @"French";
60
61       sendEMailNotifications
62         = [ud boolForKey: @"SOGoAppointmentSendEMailNotifications"];
63     }
64 }
65
66 - (id) init
67 {
68   if ((self = [super init]))
69     {
70       calendar = nil;
71       calContent = nil;
72       isNew = NO;
73     }
74
75   return self;
76 }
77
78 - (void) dealloc
79 {
80   if (calendar)
81     [calendar release];
82   if (calContent)
83     [calContent release];
84   [super dealloc];
85 }
86
87 - (NSString *) davContentType
88 {
89   return @"text/calendar";
90 }
91
92 - (NSString *) componentTag
93 {
94   [self subclassResponsibility: _cmd];
95
96   return nil;
97 }
98
99 - (void) _filterPrivateComponent: (iCalEntityObject *) component
100 {
101   [component setSummary: @""];
102   [component setComment: @""];
103   [component setUserComment: @""];
104   [component setLocation: @""];
105   [component setCategories: @""];
106   [component setUrl: @""];
107   [component removeAllAttendees];
108   [component removeAllAlarms];
109 }
110
111 - (NSString *) contentAsString
112 {
113   NSString *tmpContent, *email;
114   iCalCalendar *tmpCalendar;
115   iCalRepeatableEntityObject *tmpComponent;
116
117   if (!calContent)
118     {
119       tmpContent = [super contentAsString];
120       calContent = tmpContent;
121       if ([tmpContent length] > 0)
122         {
123           tmpCalendar = [iCalCalendar parseSingleFromSource: tmpContent];
124           tmpComponent = (iCalRepeatableEntityObject *) [tmpCalendar firstChildWithTag: [self componentTag]];
125           if (![tmpComponent isPublic])
126             {
127               email = [[context activeUser] email];
128               if (!([tmpComponent isOrganizer: email]
129                     || [tmpComponent isParticipant: email]))
130                 {
131                   //             content = tmpContent;
132                   [self _filterPrivateComponent: tmpComponent];
133                   calContent = [tmpCalendar versitString];
134                 }
135             }
136         }
137
138       [calContent retain];
139     }
140
141   return calContent;
142 }
143
144 - (NSException *) saveContentString: (NSString *) contentString
145                         baseVersion: (unsigned int) baseVersion
146 {
147   NSException *result;
148
149   result = [super saveContentString: contentString
150                   baseVersion: baseVersion];
151   if (!result && calContent)
152     {
153       [calContent release];
154       calContent = nil;
155     }
156
157   return result;
158 }
159
160 - (iCalCalendar *) calendar: (BOOL) create
161 {
162   NSString *iCalString, *componentTag;
163   CardGroup *newComponent;
164
165   if (!calendar)
166     {
167       iCalString = [self contentAsString];
168       if ([iCalString length] > 0)
169         calendar = [iCalCalendar parseSingleFromSource: iCalString];
170       else
171         {
172           if (create)
173             {
174               calendar = [iCalCalendar groupWithTag: @"vcalendar"];
175               [calendar setVersion: @"2.0"];
176               [calendar setProdID: @"-//Inverse groupe conseil//SOGo 0.9//EN"];
177               componentTag = [[self componentTag] uppercaseString];
178               newComponent = [[calendar classForTag: componentTag]
179                                groupWithTag: componentTag];
180               [calendar addChild: newComponent];
181               isNew = YES;
182             }
183         }
184       if (calendar)
185         [calendar retain];
186     }
187
188   return calendar;
189 }
190
191 - (iCalRepeatableEntityObject *) component: (BOOL) create
192 {
193   return (iCalRepeatableEntityObject *)
194     [[self calendar: create]
195       firstChildWithTag: [self componentTag]];
196 }
197
198 - (BOOL) isNew
199 {
200   return isNew;
201 }
202
203 /* raw saving */
204
205 - (NSException *) primarySaveContentString: (NSString *) _iCalString
206 {
207   return [super saveContentString: _iCalString];
208 }
209
210 - (NSException *) primaryDelete
211 {
212   return [super delete];
213 }
214
215 - (NSException *) deleteWithBaseSequence: (int) a
216 {
217   [self subclassResponsibility: _cmd];
218
219   return nil;
220 }
221
222 - (NSException *) delete
223 {
224   return [self deleteWithBaseSequence:0];
225 }
226
227 /* EMail Notifications */
228 - (NSString *) homePageURLForPerson: (iCalPerson *) _person
229 {
230   NSString *baseURL;
231   NSString *uid;
232   NSArray *traversalObjects;
233
234   /* generate URL from traversal stack */
235   traversalObjects = [context objectTraversalStack];
236   if ([traversalObjects count] > 0)
237     baseURL = [[traversalObjects objectAtIndex:0] baseURLInContext: context];
238   else
239     {
240       baseURL = @"http://localhost/";
241       [self warnWithFormat:@"Unable to create baseURL from context!"];
242     }
243   uid = [[AgenorUserManager sharedUserManager]
244           getUIDForEmail: [_person rfc822Email]];
245
246   return ((uid)
247           ? [NSString stringWithFormat:@"%@%@", baseURL, uid]
248           : nil);
249 }
250
251 - (NSException *) changeParticipationStatus: (NSString *) _status
252 {
253   iCalRepeatableEntityObject *component;
254   iCalPerson *p;
255   NSString *newContent;
256   NSException *ex;
257   NSString *myEMail;
258   
259   ex = nil;
260
261   component = [self component: NO];
262   if (component)
263     {
264       myEMail = [[context activeUser] email];
265       p = [component findParticipantWithEmail: myEMail];
266       if (p)
267         {
268           // TODO: send iMIP reply mails?
269           [p setPartStat: _status];
270           newContent = [[component parent] versitString];
271           if (newContent)
272             {
273               ex = [self saveContentString:newContent];
274               if (ex)
275                 // TODO: why is the exception wrapped?
276                 /* Server Error */
277                 ex = [NSException exceptionWithHTTPStatus: 500
278                                   reason: [ex reason]];
279             }
280           else
281             ex
282               = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
283                              reason: @"Could not generate iCalendar data ..."];
284         }
285       else
286         ex = [NSException exceptionWithHTTPStatus: 404 /* Not Found */
287                           reason: @"user does not participate in this "
288                           @"calendar component"];
289     }
290   else
291     ex = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
292                       reason: @"unable to parse component record"];
293
294   return ex;
295 }
296
297 - (BOOL) sendEMailNotifications
298 {
299   return sendEMailNotifications;
300 }
301
302 - (NSTimeZone *) timeZoneForUser: (NSString *) email
303 {
304   NSString *uid;
305
306   uid = [[AgenorUserManager sharedUserManager] getUIDForEmail: email];
307
308   return [[SOGoUser userWithLogin: uid andRoles: nil] timeZone];
309 }
310
311 - (void) sendEMailUsingTemplateNamed: (NSString *) _pageName
312                         forOldObject: (iCalRepeatableEntityObject *) _oldObject
313                         andNewObject: (iCalRepeatableEntityObject *) _newObject
314                          toAttendees: (NSArray *) _attendees
315 {
316   NSString *pageName;
317   iCalPerson *organizer;
318   NSString *cn, *email, *sender, *iCalString;
319   NGSendMail *sendmail;
320   WOApplication *app;
321   unsigned i, count;
322   iCalPerson *attendee;
323   NSString *recipient;
324   SOGoAptMailNotification *p;
325   NSString *subject, *text, *header;
326   NGMutableHashMap *headerMap;
327   NGMimeMessage *msg;
328   NGMimeBodyPart *bodyPart;
329   NGMimeMultipartBody *body;
330
331   if ([_attendees count])
332     {
333       /* sender */
334
335       organizer = [_newObject organizer];
336       cn = [organizer cnWithoutQuotes];
337       if (cn)
338         sender = [NSString stringWithFormat:@"%@ <%@>",
339                            cn,
340                            [organizer rfc822Email]];
341       else
342         sender = [organizer rfc822Email];
343
344       /* generate iCalString once */
345       iCalString = [[_newObject parent] versitString];
346   
347       /* get sendmail object */
348       sendmail = [NGSendMail sharedSendMail];
349
350       /* get WOApplication instance */
351       app = [WOApplication application];
352
353       /* generate dynamic message content */
354
355       count = [_attendees count];
356       for (i = 0; i < count; i++)
357         {
358           attendee = [_attendees objectAtIndex:i];
359
360           /* construct recipient */
361           cn = [attendee cn];
362           email = [attendee rfc822Email];
363           if (cn)
364             recipient = [NSString stringWithFormat: @"%@ <%@>",
365                                   cn, email];
366           else
367             recipient = email;
368
369           /* create page name */
370           // TODO: select user's default language?
371           pageName = [NSString stringWithFormat: @"SOGoAptMail%@%@",
372                                mailTemplateDefaultLanguage,
373                                _pageName];
374           /* construct message content */
375           p = [app pageWithName: pageName inContext: context];
376           [p setNewApt: _newObject];
377           [p setOldApt: _oldObject];
378           [p setHomePageURL: [self homePageURLForPerson: attendee]];
379           [p setViewTZ: [self timeZoneForUser: email]];
380           subject = [p getSubject];
381           text = [p getBody];
382
383           /* construct message */
384           headerMap = [NGMutableHashMap hashMapWithCapacity: 5];
385           
386           /* NOTE: multipart/alternative seems like the correct choice but
387            * unfortunately Thunderbird doesn't offer the rich content alternative
388            * at all. Mail.app shows the rich content alternative _only_
389            * so we'll stick with multipart/mixed for the time being.
390            */
391           [headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
392           [headerMap setObject: sender forKey: @"From"];
393           [headerMap setObject: recipient forKey: @"To"];
394           [headerMap setObject: [NSCalendarDate date] forKey: @"date"];
395           [headerMap setObject: subject forKey: @"Subject"];
396           msg = [NGMimeMessage messageWithHeader: headerMap];
397
398           /* multipart body */
399           body = [[NGMimeMultipartBody alloc] initWithPart: msg];
400     
401           /* text part */
402           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
403           [headerMap setObject: @"text/plain; charset=utf-8"
404                      forKey: @"content-type"];
405           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
406           [bodyPart setBody: [text dataUsingEncoding: NSUTF8StringEncoding]];
407
408           /* attach text part to multipart body */
409           [body addBodyPart: bodyPart];
410     
411           /* calendar part */
412           header = [NSString stringWithFormat: @"text/calendar; method=%@;"
413                              @" charset=utf-8",
414                              [(iCalCalendar *) [_newObject parent] method]];
415           headerMap = [NGMutableHashMap hashMapWithCapacity: 1];
416           [headerMap setObject:header forKey: @"content-type"];
417           bodyPart = [NGMimeBodyPart bodyPartWithHeader: headerMap];
418           [bodyPart setBody: [iCalString dataUsingEncoding: NSUTF8StringEncoding]];
419
420           /* attach calendar part to multipart body */
421           [body addBodyPart: bodyPart];
422     
423           /* attach multipart body to message */
424           [msg setBody: body];
425           [body release];
426
427           /* send the damn thing */
428           [sendmail sendMimePart: msg
429                     toRecipients: [NSArray arrayWithObject: email]
430                     sender: [organizer rfc822Email]];
431         }
432     }
433 }
434
435 - (NSArray *) rolesOfUser: (NSString *) login
436 {
437   AgenorUserManager *um;
438   iCalRepeatableEntityObject *component;
439   NSMutableArray *sogoRoles;
440   NSString *email;
441   SOGoUser *user;
442
443   sogoRoles = [NSMutableArray new];
444   [sogoRoles autorelease];
445
446   um = [AgenorUserManager sharedUserManager];
447   email = [um getEmailForUID: login];
448
449   component = [self component: NO];
450   if (component)
451     {
452       if ([component isOrganizer: email])
453         [sogoRoles addObject: SOGoRole_Organizer];
454       else if ([component isParticipant: email])
455         [sogoRoles addObject: SOGoRole_Participant];
456       else if ([[container ownerInContext: context] isEqualToString: login])
457         [sogoRoles addObject: SoRole_Owner];
458     }
459   else
460     {
461       user = [SOGoUser userWithLogin: login andRoles: nil];
462       [sogoRoles addObjectsFromArray: [user rolesForObject: container
463                                             inContext: context]];
464     }
465
466   return sogoRoles;
467 }
468
469 - (BOOL) isOrganizer: (NSString *) email
470              orOwner: (NSString *) login
471 {
472   BOOL isOrganizerOrOwner;
473   iCalRepeatableEntityObject *component;
474   NSString *organizerEmail;
475
476   component = [self component: NO];
477   organizerEmail = [[component organizer] rfc822Email];
478   if (component && [organizerEmail length] > 0)
479     isOrganizerOrOwner
480       = ([organizerEmail caseInsensitiveCompare: email] == NSOrderedSame);
481   else
482     isOrganizerOrOwner
483       = [[container ownerInContext: context] isEqualToString: login];
484
485   return isOrganizerOrOwner;
486 }
487
488 - (BOOL) isParticipant: (NSString *) email
489 {
490   BOOL isParticipant;
491   iCalRepeatableEntityObject *component;
492
493   component = [self component: NO];
494   if (component)
495     isParticipant = [component isParticipant: email];
496   else
497     isParticipant = NO;
498
499   return isParticipant;
500 }
501
502 @end