1/* UIxMailPartICalActions.m - this file is part of SOGo 2 * 3 * Copyright (C) 2007-2016 Inverse inc. 4 * 5 * This file is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2, or (at your option) 8 * any later version. 9 * 10 * This file is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; see the file COPYING. If not, write to 17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21#import <Foundation/NSCalendarDate.h> 22#import <Foundation/NSDictionary.h> 23#import <Foundation/NSValue.h> 24 25#import <NGObjWeb/NSException+HTTP.h> 26#import <NGObjWeb/WOContext+SoObjects.h> 27#import <NGObjWeb/WORequest.h> 28#import <NGObjWeb/WOResponse.h> 29 30#import <NGExtensions/NSNull+misc.h> 31#import <NGExtensions/NSObject+Logs.h> 32 33#import <NGCards/iCalCalendar.h> 34 35#import <NGImap4/NGImap4EnvelopeAddress.h> 36 37#import <Appointments/iCalEvent+SOGo.h> 38#import <Appointments/iCalEntityObject+SOGo.h> 39#import <Appointments/iCalPerson+SOGo.h> 40#import <Appointments/SOGoAppointmentObject.h> 41#import <Appointments/SOGoAppointmentFolder.h> 42#import <Appointments/SOGoAppointmentFolders.h> 43#import <Mailer/SOGoMailObject.h> 44#import <SOGo/SOGoUser.h> 45#import <SOGo/SOGoUserManager.h> 46#import <SOGo/NSString+Utilities.h> 47#import <Mailer/SOGoMailBodyPart.h> 48 49#import "UIxMailPartICalActions.h" 50 51@implementation UIxMailPartICalActions 52 53- (iCalEvent *) _emailEvent 54{ 55 iCalCalendar *emailCalendar; 56 NSString *eventString; 57 NSData *content; 58 59 content = [[self clientObject] fetchBLOB]; 60 eventString = [[NSString alloc] initWithData: content 61 encoding: NSUTF8StringEncoding]; 62 if (!eventString) 63 eventString = [[NSString alloc] initWithData: content 64 encoding: NSISOLatin1StringEncoding]; 65 66 emailCalendar = [iCalCalendar parseSingleFromSource: eventString]; 67 [eventString release]; 68 69 return (iCalEvent *) [emailCalendar firstChildWithTag: @"vevent"]; 70} 71 72// 73// 74// 75- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid 76 forUser: (SOGoUser *) user 77{ 78 SOGoAppointmentFolder *folder; 79 SOGoAppointmentObject *eventObject; 80 NSArray *folders; 81 NSEnumerator *e; 82 NSString *cname, *userLogin; 83 84 eventObject = nil; 85 86 userLogin = [user login]; 87 88 folders = [[user calendarsFolderInContext: context] subFolders]; 89 e = [folders objectEnumerator]; 90 while (eventObject == nil && (folder = [e nextObject])) 91 { 92 if ([[folder ownerInContext: nil] isEqualToString: userLogin]) 93 { 94 cname = [folder resourceNameForEventUID: uid]; 95 if (cname) 96 { 97 eventObject = [folder lookupName: cname inContext: context 98 acquire: NO]; 99 if ([eventObject isKindOfClass: [NSException class]]) 100 eventObject = nil; 101 } 102 } 103 } 104 105 if (!eventObject) 106 { 107 folder = [user personalCalendarFolderInContext: context]; 108 eventObject = [SOGoAppointmentObject objectWithName: uid 109 inContainer: folder]; 110 [eventObject setIsNew: YES]; 111 } 112 113 return eventObject; 114} 115 116// 117// 118// 119- (SOGoAppointmentObject *) _eventObjectWithUID: (NSString *) uid 120{ 121#warning this will not work if Bob reads emails of Alice and accepts events for her 122 return [self _eventObjectWithUID: uid forUser: [context activeUser]]; 123} 124 125// 126// 127// 128- (void) _fixOrganizerInEvent: (iCalEvent *) brokenEvent 129{ 130 iCalPerson *organizer; 131 SOGoMailObject *mail; 132 NSArray *addresses; 133 NGImap4EnvelopeAddress *from; 134 id value; 135 136 organizer = nil; 137 138 mail = [[self clientObject] mailObject]; 139 140 addresses = [mail replyToEnvelopeAddresses]; 141 if (![addresses count]) 142 addresses = [mail fromEnvelopeAddresses]; 143 if ([addresses count] > 0) 144 { 145 from = [addresses objectAtIndex: 0]; 146 value = [from baseEMail]; 147 if ([value isNotNull]) 148 { 149 organizer = [iCalPerson elementWithTag: @"organizer"]; 150 [organizer setEmail: value]; 151 value = [from personalName]; 152 if ([value isNotNull]) 153 [organizer setCn: value]; 154 [brokenEvent setOrganizer: organizer]; 155 } 156 } 157 158 if (!organizer) 159 [self errorWithFormat: @"no organizer could be found, SOGo will crash" 160 @" if the user replies"]; 161} 162 163// 164// 165// 166- (iCalEvent *) _setupChosenEventAndEventObject: (SOGoAppointmentObject **) eventObject 167{ 168 iCalEvent *emailEvent, *calendarEvent, *chosenEvent; 169 iCalPerson *organizer; 170 171 emailEvent = [self _emailEvent]; 172 if (emailEvent) 173 { 174 *eventObject = [self _eventObjectWithUID: [emailEvent uid]]; 175 if ([*eventObject isNew]) 176 { 177 chosenEvent = emailEvent; 178 [*eventObject saveCalendar: [emailEvent parent]]; 179 } 180 else 181 { 182 if ([emailEvent recurrenceId]) 183 { 184 // Event attached to email is not complete -- retrieve it 185 // from the database. 186 NSString *recurrenceTime; 187 188 recurrenceTime = [NSString stringWithFormat: @"%f", 189 [[emailEvent recurrenceId] timeIntervalSince1970]]; 190 calendarEvent = (iCalEvent *)[*eventObject lookupOccurrence: recurrenceTime]; 191 } 192 else 193 calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO]; 194 195 if (calendarEvent != nil) 196 { 197 // Calendar event still exists -- verify which of the calendar 198 // and email events is the most recent. We must also update 199 // the event (or recurrence-id) with the email's content, otherwise 200 // we would never get major properties updates 201 if ([calendarEvent compare: emailEvent] == NSOrderedAscending) 202 { 203 iCalCalendar *parent; 204 205 parent = [calendarEvent parent]; 206 [parent removeChild: calendarEvent]; 207 [parent addChild: emailEvent]; 208 [*eventObject saveCalendar: parent]; 209 [*eventObject flush]; 210 chosenEvent = emailEvent; 211 } 212 else 213 { 214 chosenEvent = calendarEvent; 215 if (![[[chosenEvent parent] method] length]) 216 [[chosenEvent parent] setMethod: [[emailEvent parent] method]]; 217 } 218 } 219 else 220 chosenEvent = emailEvent; 221 } 222 223 organizer = [chosenEvent organizer]; 224 if (![[organizer rfc822Email] length]) 225 [self _fixOrganizerInEvent: chosenEvent]; 226 } 227 else 228 chosenEvent = nil; 229 230 return chosenEvent; 231} 232 233// 234// 235// 236- (WOResponse *) _changePartStatusAction: (NSString *) newStatus 237 withDelegate: (iCalPerson *) delegate 238{ 239 SOGoAppointmentObject *eventObject; 240 WOResponse *response; 241 iCalEvent *chosenEvent; 242 iCalAlarm *alarm; 243 244 chosenEvent = [self _setupChosenEventAndEventObject: &eventObject]; 245 if (chosenEvent) 246 { 247 // For invitations, we take the organizers's alarm to start with 248 alarm = [[chosenEvent alarms] lastObject]; 249 response = (WOResponse*)[eventObject changeParticipationStatus: newStatus 250 withDelegate: delegate 251 alarm: alarm 252 forRecurrenceId: [chosenEvent recurrenceId]]; 253 if (!response) 254 response = [self responseWith204]; 255 } 256 else 257 { 258 response = [context response]; 259 [response setStatus: 409]; 260 } 261 262 return response; 263} 264 265- (WOResponse *) acceptAction 266{ 267 return [self _changePartStatusAction: @"ACCEPTED" 268 withDelegate: nil]; 269} 270 271- (WOResponse *) declineAction 272{ 273 return [self _changePartStatusAction: @"DECLINED" 274 withDelegate: nil]; 275} 276 277- (WOResponse *) tentativeAction 278{ 279 return [self _changePartStatusAction: @"TENTATIVE" 280 withDelegate: nil]; 281} 282 283- (WOResponse *) delegateAction 284{ 285// BOOL receiveUpdates; 286 NSString *delegatedEmail, *delegatedUid; 287 iCalPerson *delegatedAttendee; 288 NSDictionary *content; 289 SOGoUser *user; 290 WORequest *request; 291 WOResponse *response; 292 293 request = [context request]; 294 content = [[request contentAsString] objectFromJSONString]; 295 delegatedEmail = [content objectForKey: @"delegatedTo"]; 296 297 if ([delegatedEmail length]) 298 { 299 user = [context activeUser]; 300 delegatedAttendee = [iCalPerson new]; 301 [delegatedAttendee autorelease]; 302 [delegatedAttendee setEmail: delegatedEmail]; 303 delegatedUid = [delegatedAttendee uidInDomain: [user domain]]; 304 if (delegatedUid) 305 { 306 SOGoUser *delegatedUser; 307 delegatedUser = [SOGoUser userWithLogin: delegatedUid]; 308 [delegatedAttendee setCn: [delegatedUser cn]]; 309 } 310 311 [delegatedAttendee setRole: @"REQ-PARTICIPANT"]; 312 [delegatedAttendee setRsvp: @"TRUE"]; 313 [delegatedAttendee setParticipationStatus: iCalPersonPartStatNeedsAction]; 314 [delegatedAttendee setDelegatedFrom: 315 [NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]]; 316 317// receiveUpdates = [[content objectForKey: @"receiveUpdates"] boolValue]; 318// if (receiveUpdates) 319// [delegatedAttendee setRole: @"NON-PARTICIPANT"]; 320 321 response = [self _changePartStatusAction: @"DELEGATED" 322 withDelegate: delegatedAttendee]; 323 } 324 else 325 response = [NSException exceptionWithHTTPStatus: 400 326 reason: @"missing 'to' parameter"]; 327 328 return response; 329} 330 331- (WOResponse *) addToCalendarAction 332{ 333 iCalEvent *emailEvent; 334 SOGoAppointmentObject *eventObject; 335 SOGoUser *user; 336 WOResponse *response; 337 338 emailEvent = [self _emailEvent]; 339 if (emailEvent) 340 { 341 eventObject = [self _eventObjectWithUID: [emailEvent uid]]; 342 if ([eventObject isNew]) 343 { 344 user = [context activeUser]; 345 if (![emailEvent userIsOrganizer: user] && ![emailEvent userIsAttendee: user]) 346 { 347 [emailEvent setOrganizer: nil]; 348 [emailEvent removeAllAttendees]; 349 } 350 [eventObject saveCalendar: [emailEvent parent]]; 351 response = [self responseWith204]; 352 } 353 else 354 { 355 response = [self responseWithStatus: 409]; 356 } 357 } 358 else 359 { 360 response = [context response]; 361 [response setStatus: 409]; 362 response = [self responseWithStatus: 404]; 363 } 364 365 return response; 366} 367 368- (WOResponse *) deleteFromCalendarAction 369{ 370 iCalEvent *emailEvent; 371 SOGoAppointmentObject *eventObject; 372 WOResponse *response; 373 374 emailEvent = [self _emailEvent]; 375 if (emailEvent) 376 { 377 eventObject = [self _eventObjectWithUID: [emailEvent uid]]; 378 [eventObject delete]; 379 response = [self responseWith204]; 380 } 381 else 382 { 383 response = [context response]; 384 [response setStatus: 409]; 385 } 386 387 return response; 388} 389 390- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event 391{ 392 NGImap4EnvelopeAddress *address; 393 SOGoMailObject *mailObject; 394 NSString *emailFrom; 395 iCalPerson *p; 396 397 mailObject = [[self clientObject] mailObject]; 398 address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0]; 399 emailFrom = [address baseEMail]; 400 p = [event findAttendeeWithEmail: emailFrom]; 401 402 // We haven't found it yet, let's look in the identities 403 // associated to this user 404 if (!p) 405 { 406 SOGoUserManager *sm; 407 NSString *uid; 408 409 sm = [SOGoUserManager sharedUserManager]; 410 uid = [sm getUIDForEmail: emailFrom]; 411 412 if (uid) 413 { 414 NSArray *allEmails; 415 NSString *email; 416 SOGoUser *u; 417 int i; 418 419 u = [SOGoUser userWithLogin: uid]; 420 allEmails = [u allEmails]; 421 for (i = 0; i < [allEmails count]; i++) 422 { 423 email = [allEmails objectAtIndex: i]; 424 if ([email caseInsensitiveCompare: emailFrom] == NSOrderedSame) 425 continue; 426 427 p = [event findAttendeeWithEmail: email]; 428 429 if (p) 430 break; 431 } 432 } 433 } 434 435 return p; 436} 437 438- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent 439 fromEvent: (iCalEvent *) emailEvent 440 inObject: (SOGoAppointmentObject *) eventObject 441{ 442 iCalPerson *calendarParticipant, *mailParticipant; 443 NSString *partStat; 444 BOOL result; 445 446 calendarParticipant = [self _emailParticipantWithEvent: calendarEvent]; 447 mailParticipant = [self _emailParticipantWithEvent: emailEvent]; 448 if (calendarParticipant && mailParticipant) 449 { 450 result = YES; 451 partStat = [mailParticipant partStat]; 452 if ([partStat caseInsensitiveCompare: [calendarParticipant partStat]] 453 != NSOrderedSame) 454 { 455 [calendarParticipant setPartStat: [partStat uppercaseString]]; 456 [eventObject saveComponent: calendarEvent]; 457 } 458 } 459 else 460 result = NO; 461 462 return result; 463} 464 465- (WOResponse *) updateUserStatusAction 466{ 467 iCalEvent *emailEvent, *calendarEvent; 468 SOGoAppointmentObject *eventObject; 469 WOResponse *response; 470 471 response = nil; 472 473 emailEvent = [self _emailEvent]; 474 if (emailEvent) 475 { 476 eventObject = [self _eventObjectWithUID: [emailEvent uid]]; 477 calendarEvent = [eventObject component: NO secure: NO]; 478 if (calendarEvent) 479 { 480 if (([[emailEvent sequence] compare: [calendarEvent sequence]] 481 != NSOrderedAscending) 482 && ([self _updateParticipantStatusInEvent: calendarEvent 483 fromEvent: emailEvent 484 inObject: eventObject])) 485 response = [self responseWith204]; 486 } 487 else 488 response = [self responseWithStatus: 404 489 andString: @"Local event not found."]; 490 } 491 492 if (!response) 493 { 494 response = [context response]; 495 [response setStatus: 409]; 496 } 497 498 return response; 499} 500 501@end 502