1 /* UIxComponentEditor.m - this file is part of SOGo
3 * Copyright (C) 2006 Inverse groupe conseil
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
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)
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.
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.
23 #import <Foundation/NSArray.h>
24 #import <Foundation/NSBundle.h>
25 #import <Foundation/NSException.h>
26 #import <Foundation/NSCalendarDate.h>
27 #import <Foundation/NSKeyValueCoding.h>
28 #import <Foundation/NSString.h>
29 #import <Foundation/NSUserDefaults.h>
30 #import <Foundation/NSURL.h>
32 #import <NGCards/iCalPerson.h>
33 #import <NGCards/iCalRepeatableEntityObject.h>
34 #import <NGCards/iCalRecurrenceRule.h>
35 #import <NGCards/NSString+NGCards.h>
36 #import <NGCards/NSCalendarDate+NGCards.h>
37 #import <NGObjWeb/SoSecurityManager.h>
38 #import <NGObjWeb/NSException+HTTP.h>
39 #import <NGObjWeb/WORequest.h>
40 #import <NGExtensions/NSCalendarDate+misc.h>
41 #import <NGExtensions/NSObject+Logs.h>
42 #import <NGExtensions/NSString+misc.h>
44 #import <UI/SOGoUI/SOGoDateFormatter.h>
45 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
46 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
47 #import <SoObjects/Appointments/SOGoTaskObject.h>
48 #import <SoObjects/SOGo/NSString+Utilities.h>
49 #import <SoObjects/SOGo/SOGoUser.h>
50 #import <SoObjects/SOGo/SOGoPermissions.h>
52 #import "UIxComponent+Scheduler.h"
54 #import "UIxComponentEditor.h"
56 @implementation UIxComponentEditor
60 if ((self = [super init]))
63 [self setPrivacy: @"PUBLIC"];
64 [self setIsCycleEndNever];
68 attendeesEmails = nil;
78 [cycleUntilDate release];
88 [attendeesNames release];
89 [attendeesEmails release];
90 [calendarList release];
95 - (void) _loadAttendees
97 NSEnumerator *attendees;
98 iCalPerson *currentAttendee;
99 NSMutableString *names, *emails;
101 names = [NSMutableString new];
102 emails = [NSMutableString new];
104 attendees = [[component attendees] objectEnumerator];
105 currentAttendee = [attendees nextObject];
106 while (currentAttendee)
108 NSLog (@"currentCN: %@", [currentAttendee cn]);
109 [names appendFormat: @"%@,", [currentAttendee cn]];
110 [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
111 currentAttendee = [attendees nextObject];
114 if ([names length] > 0)
116 ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
117 ASSIGN (attendeesEmails,
118 [emails substringToIndex: [emails length] - 1]);
125 /* warning: we use this method which will be triggered by the template system
126 when the page is instantiated, but we should find another and cleaner way of
127 doing this... for example, when the clientObject is set */
128 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
130 // iCalRecurrenceRule *rrule;
135 component = newComponent;
137 co = [self clientObject];
138 componentOwner = [co ownerInContext: nil];
140 ASSIGN (title, [component summary]);
141 ASSIGN (location, [component location]);
142 ASSIGN (comment, [component comment]);
143 ASSIGN (url, [[component url] absoluteString]);
144 ASSIGN (privacy, [component accessClass]);
145 ASSIGN (priority, [component priority]);
146 ASSIGN (status, [component status]);
147 ASSIGN (categories, [[component categories] commaSeparatedValues]);
148 ASSIGN (organizer, [component organizer]);
149 [self _loadAttendees];
152 // if ([component isRecurrent])
154 // rrule = [[component recurrenceRules] objectAtIndex: 0];
155 // [self adjustCycleControlsForRRule: rrule];
159 - (void) setSaveURL: (NSString *) newSaveURL
161 saveURL = newSaveURL;
164 - (NSString *) saveURL
171 - (void) setItem: (id) _item
173 ASSIGN (item, _item);
181 - (NSString *) itemPriorityText
183 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
186 - (NSString *) itemPrivacyText
190 tag = [[self clientObject] componentTag];
192 return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]];
195 - (NSString *) itemStatusText
197 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
200 - (void) setTitle: (NSString *) _value
202 ASSIGN (title, _value);
210 - (void) setUrl: (NSString *) _url
220 - (void) setAttendeesNames: (NSString *) newAttendeesNames
222 ASSIGN (attendeesNames, newAttendeesNames);
225 - (NSString *) attendeesNames
227 return attendeesNames;
230 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
232 ASSIGN (attendeesEmails, newAttendeesEmails);
235 - (NSString *) attendeesEmails
237 return attendeesEmails;
240 - (void) setLocation: (NSString *) _value
242 ASSIGN (location, _value);
245 - (NSString *) location
250 - (void) setComment: (NSString *) _value
252 ASSIGN (comment, _value);
255 - (NSString *) comment
260 - (NSArray *) categoryList
262 static NSArray *categoryItems = nil;
266 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
288 [categoryItems retain];
291 return categoryItems;
294 - (void) setCategories: (NSArray *) _categories
296 ASSIGN (categories, _categories);
299 - (NSArray *) categories
304 - (NSString *) itemCategoryText
306 return [self labelForKey:
307 [NSString stringWithFormat: @"category_%@", item]];
310 - (NSArray *) calendarList
312 SOGoAppointmentFolder *folder;
313 NSEnumerator *allCalendars;
314 NSDictionary *currentCalendar;
318 calendarList = [NSMutableArray new];
319 folder = [[self clientObject] container];
320 allCalendars = [[folder calendarFolders] objectEnumerator];
321 currentCalendar = [allCalendars nextObject];
322 while (currentCalendar)
324 if ([[currentCalendar objectForKey: @"active"] boolValue])
325 [calendarList addObject: currentCalendar];
326 currentCalendar = [allCalendars nextObject];
333 - (NSString *) calendarsFoldersList
337 calendars = [[self calendarList] valueForKey: @"folder"];
339 return [calendars componentsJoinedByString: @","];
342 - (NSString *) componentCalendar
349 - (NSArray *) priorities
355 static NSArray *priorities = nil;
359 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
366 - (void) setPriority: (NSString *) _priority
368 ASSIGN (priority, _priority);
371 - (NSString *) priority
376 - (NSArray *) privacyClasses
378 static NSArray *priorities = nil;
382 priorities = [NSArray arrayWithObjects: @"PUBLIC",
383 @"CONFIDENTIAL", @"PRIVATE", nil];
390 - (void) setPrivacy: (NSString *) _privacy
392 ASSIGN (privacy, _privacy);
395 - (NSString *) privacy
400 - (NSArray *) statusTypes
402 static NSArray *priorities = nil;
406 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
413 - (void) setStatus: (NSString *) _status
415 ASSIGN (status, _status);
418 - (NSString *) status
427 static NSArray *cycles = nil;
431 bundle = [NSBundle bundleForClass:[self class]];
432 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
433 NSAssert(path != nil, @"Cannot find cycles.plist!");
434 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
435 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
441 - (void) setCycle: (NSDictionary *) _cycle
443 ASSIGN (cycle, _cycle);
446 - (NSDictionary *) cycle
453 return ([cycle objectForKey: @"rule"] != nil);
456 - (NSString *) cycleLabel
460 key = [(NSDictionary *)item objectForKey: @"label"];
462 return [self labelForKey:key];
465 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
467 // NSCalendarDate *until;
469 // /* copy hour/minute/second from startDate */
470 // until = [_cycleUntilDate hour: [startDate hourOfDay]
471 // minute: [startDate minuteOfHour]
472 // second: [startDate secondOfMinute]];
473 // [until setTimeZone: [startDate timeZone]];
474 // ASSIGN (cycleUntilDate, until);
477 - (NSCalendarDate *) cycleUntilDate
479 return cycleUntilDate;
482 - (iCalRecurrenceRule *) rrule
485 iCalRecurrenceRule *rule;
487 if (![self hasCycle])
489 ruleRep = [cycle objectForKey: @"rule"];
490 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
492 if (cycleUntilDate && [self isCycleEndUntil])
493 [rule setUntilDate:cycleUntilDate];
498 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
501 // NSCalendarDate *until;
503 // c = [self cycleMatchingRRule:_rrule];
504 // [self setCycle:c];
506 // until = [[[_rrule untilDate] copy] autorelease];
508 // until = startDate;
510 // [self setIsCycleEndUntil];
512 // [until setTimeZone:[[self clientObject] userTimeZone]];
513 // [self setCycleUntilDate:until];
517 This method is necessary, because we have a fixed sets of cycles in the UI.
518 The model is able to represent arbitrary rules, however.
519 There SHOULD be a different UI, similar to iCal.app, to allow modelling
520 of more complex rules.
522 This method obviously cannot map all existing rules back to the fixed list
523 in cycles.plist. This should be fixed in a future version when interop
524 becomes more important.
526 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
533 return [[self cycles] objectAtIndex:0];
535 cycleRep = [_rrule versitString];
536 cycles = [self cycles];
537 count = [cycles count];
538 for (i = 1; i < count; i++) {
542 c = [cycles objectAtIndex:i];
543 cr = [c objectForKey: @"rule"];
544 if ([cr isEqualToString:cycleRep])
547 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
551 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
552 - (NSArray *) cycleEnds
554 static NSArray *ends = nil;
558 ends = [NSArray arrayWithObjects: @"cycle_end_never",
559 @"cycle_end_until", nil];
566 - (void) setCycleEnd: (NSString *) _cycleEnd
568 ASSIGN (cycleEnd, _cycleEnd);
571 - (NSString *) cycleEnd
576 - (BOOL) isCycleEndUntil
578 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
581 - (void) setIsCycleEndUntil
583 [self setCycleEnd: @"cycle_end_until"];
586 - (void) setIsCycleEndNever
588 [self setCycleEnd: @"cycle_end_never"];
592 - (NSFormatter *) titleDateFormatter
594 SOGoDateFormatter *fmt;
596 fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
598 [fmt setFullWeekdayNameAndDetails];
603 - (NSString *) completeURIForMethod: (NSString *) _method
608 uri = [[[self context] request] uri];
610 /* first: identify query parameters */
611 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
613 uri = [uri substringToIndex:r.location];
615 /* next: append trailing slash */
616 if (![uri hasSuffix: @"/"])
617 uri = [uri stringByAppendingString: @"/"];
619 /* next: append method */
620 uri = [uri stringByAppendingString:_method];
622 /* next: append query parameters */
623 return [self completeHrefForMethod:uri];
626 - (BOOL) isWriteableClientObject
628 return [[self clientObject]
629 respondsToSelector: @selector(saveContentString:)];
632 - (BOOL) containsConflict: (id) _component
634 [self subclassResponsibility: _cmd];
642 - (iCalPerson *) getOrganizer
647 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
648 p = [[[iCalPerson alloc] init] autorelease];
649 [p setEmail:emailProp];
650 [p setCn:[self cnForUser]];
655 - (BOOL) isMyComponent
657 return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
660 - (BOOL) canEditComponent
662 return [self isMyComponent];
665 /* response generation */
667 - (NSString *) initialCycleVisibility
669 return ([self hasCycle]
670 ? @"visibility: visible;"
671 : @"visibility: hidden;");
674 - (NSString *) initialCycleEndUntilVisibility {
675 return ([self isCycleEndUntil]
676 ? @"visibility: visible;"
677 : @"visibility: hidden;");
680 // - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
684 // s = [self iCalParticipantsStringFromQueryParameters];
685 // return [s stringByAppendingString:
686 // [self iCalResourcesStringFromQueryParameters]];
689 // - (NSString *) iCalParticipantsStringFromQueryParameters
691 // static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
693 // return [self iCalStringFromQueryParameter: @"ps"
694 // format: iCalParticipantString];
697 // - (NSString *) iCalResourcesStringFromQueryParameters
699 // static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
701 // return [self iCalStringFromQueryParameter: @"rs"
702 // format: iCalResourceString];
705 // - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
706 // format: (NSString *) _format
708 // AgenorUserManager *um;
709 // NSMutableString *iCalRep;
712 // um = [AgenorUserManager sharedUserManager];
713 // iCalRep = (NSMutableString *)[NSMutableString string];
714 // s = [self queryParameterForKey:_qp];
715 // if(s && [s length] > 0) {
717 // unsigned i, count;
719 // es = [s componentsSeparatedByString: @","];
720 // count = [es count];
721 // for(i = 0; i < count; i++) {
722 // NSString *email, *cn;
724 // email = [es objectAtIndex:i];
725 // cn = [um getCNForUID:[um getUIDForEmail:email]];
726 // [iCalRep appendFormat:_format, cn, email];
732 - (NSException *) validateObjectForStatusChange
736 co = [self clientObject];
737 if (![co respondsToSelector: @selector(changeParticipationStatus:)])
738 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
740 @"method cannot be invoked on the specified object"];
745 /* contact editor compatibility */
747 - (NSString *) urlButtonClasses
754 classes = @"button _disabled";
759 - (void) _handleAttendeesEdition
761 NSArray *names, *emails;
762 NSMutableArray *newAttendees;
763 unsigned int count, max;
764 NSString *currentEmail;
765 iCalPerson *currentAttendee;
767 newAttendees = [NSMutableArray new];
768 if ([attendeesNames length] > 0)
770 names = [attendeesNames componentsSeparatedByString: @","];
771 emails = [attendeesEmails componentsSeparatedByString: @","];
772 max = [emails count];
773 for (count = 0; count < max; count++)
775 currentEmail = [emails objectAtIndex: count];
776 currentAttendee = [component findParticipantWithEmail: currentEmail];
777 if (!currentAttendee)
779 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
780 [currentAttendee setCn: [names objectAtIndex: count]];
781 [currentAttendee setEmail: currentEmail];
782 [currentAttendee setRole: @"REQ-PARTICIPANT"];
784 setParticipationStatus: iCalPersonPartStatNeedsAction];
786 [newAttendees addObject: currentAttendee];
790 [component setAttendees: newAttendees];
791 [newAttendees release];
794 - (void) _handleOrganizer
796 NSString *organizerEmail;
797 SOGoUser *activeUser;
799 organizerEmail = [[component organizer] email];
800 if ([organizerEmail length] == 0)
802 if ([[component attendees] count] > 0)
804 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
805 activeUser = [context activeUser];
806 [organizer setCn: [activeUser cn]];
807 [organizer setEmail: [activeUser primaryEmail]];
808 [component setOrganizer: organizer];
813 if ([[component attendees] count] == 0)
815 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
816 [component setOrganizer: organizer];
821 - (void) takeValuesFromRequest: (WORequest *) _rq
822 inContext: (WOContext *) _ctx
825 SOGoCalendarComponent *clientObject;
827 [super takeValuesFromRequest: _rq inContext: _ctx];
829 now = [NSCalendarDate calendarDate];
830 [component setSummary: title];
831 [component setLocation: location];
832 [component setComment: comment];
833 [component setUrl: url];
834 [component setAccessClass: privacy];
835 [self _handleAttendeesEdition];
836 [self _handleOrganizer];
837 clientObject = [self clientObject];
838 if ([clientObject isNew])
840 [component setUid: [clientObject nameInContainer]];
841 [component setCreated: now];
842 [component setTimeStampAsDate: now];
843 [component setPriority: @"0"];
845 [component setLastModified: now];
848 - (NSString *) toolbar
850 SOGoCalendarComponent *clientObject;
851 NSString *toolbarFilename;
852 iCalPerson *participant;
853 iCalPersonPartStat participationStatus;
854 SoSecurityManager *sm;
857 sm = [SoSecurityManager sharedSecurityManager];
858 clientObject = [self clientObject];
860 owner = [clientObject ownerInContext: context];
861 participant = [clientObject findParticipantWithUID: owner];
864 && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
865 onObject: clientObject
868 participationStatus = [participant participationStatus];
869 /* Lightning does not manage participation status within tasks */
870 if (participationStatus == iCalPersonPartStatAccepted)
871 toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
872 else if (participationStatus == iCalPersonPartStatDeclined)
873 toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
875 toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
877 else if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
878 onObject: clientObject
881 if ([[clientObject componentTag] isEqualToString: @"vevent"])
882 toolbarFilename = @"SOGoAppointmentObject.toolbar";
884 toolbarFilename = @"SOGoTaskObject.toolbar";
887 toolbarFilename = @"SOGoComponentClose.toolbar";
889 return toolbarFilename;