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 <SoObjects/Appointments/SOGoAppointmentFolder.h>
45 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
46 #import <SoObjects/Appointments/SOGoTaskObject.h>
47 #import <SoObjects/SOGo/NSString+Utilities.h>
48 #import <SoObjects/SOGo/SOGoUser.h>
49 #import <SoObjects/SOGo/SOGoPermissions.h>
51 #import "UIxComponent+Scheduler.h"
53 #import "UIxComponentEditor.h"
55 @implementation UIxComponentEditor
59 if ((self = [super init]))
62 [self setPrivacy: @"PUBLIC"];
63 [self setIsCycleEndNever];
67 attendeesEmails = nil;
77 [cycleUntilDate release];
87 [attendeesNames release];
88 [attendeesEmails release];
89 [calendarList release];
94 - (void) _loadAttendees
96 NSEnumerator *attendees;
97 iCalPerson *currentAttendee;
98 NSMutableString *names, *emails;
100 names = [NSMutableString new];
101 emails = [NSMutableString new];
103 attendees = [[component attendees] objectEnumerator];
104 currentAttendee = [attendees nextObject];
105 while (currentAttendee)
107 NSLog (@"currentCN: %@", [currentAttendee cn]);
108 [names appendFormat: @"%@,", [currentAttendee cn]];
109 [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
110 currentAttendee = [attendees nextObject];
113 if ([names length] > 0)
115 ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
116 ASSIGN (attendeesEmails,
117 [emails substringToIndex: [emails length] - 1]);
124 - (void) _loadCategories
126 NSString *compCategories, *simpleCategory;
128 compCategories = [component categories];
129 if ([compCategories length] > 0)
131 simpleCategory = [[compCategories componentsSeparatedByString: @","]
133 ASSIGN (category, [simpleCategory uppercaseString]);
137 /* warning: we use this method which will be triggered by the template system
138 when the page is instantiated, but we should find another and cleaner way of
139 doing this... for example, when the clientObject is set */
140 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
142 // iCalRecurrenceRule *rrule;
147 component = newComponent;
149 co = [self clientObject];
150 componentOwner = [co ownerInContext: nil];
152 ASSIGN (title, [component summary]);
153 ASSIGN (location, [component location]);
154 ASSIGN (comment, [component comment]);
155 ASSIGN (url, [[component url] absoluteString]);
156 ASSIGN (privacy, [component accessClass]);
157 ASSIGN (priority, [component priority]);
158 ASSIGN (status, [component status]);
159 ASSIGN (categories, [[component categories] commaSeparatedValues]);
160 ASSIGN (organizer, [component organizer]);
161 [self _loadCategories];
162 [self _loadAttendees];
165 // if ([component isRecurrent])
167 // rrule = [[component recurrenceRules] objectAtIndex: 0];
168 // [self adjustCycleControlsForRRule: rrule];
172 - (void) setSaveURL: (NSString *) newSaveURL
174 saveURL = newSaveURL;
177 - (NSString *) saveURL
184 - (void) setItem: (id) _item
186 ASSIGN (item, _item);
194 - (NSString *) itemPriorityText
196 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
199 - (NSString *) itemPrivacyText
203 tag = [[self clientObject] componentTag];
205 return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]];
208 - (NSString *) itemStatusText
210 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
213 - (void) setTitle: (NSString *) _value
215 ASSIGN (title, _value);
223 - (void) setUrl: (NSString *) _url
233 - (void) setAttendeesNames: (NSString *) newAttendeesNames
235 ASSIGN (attendeesNames, newAttendeesNames);
238 - (NSString *) attendeesNames
240 return attendeesNames;
243 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
245 ASSIGN (attendeesEmails, newAttendeesEmails);
248 - (NSString *) attendeesEmails
250 return attendeesEmails;
253 - (void) setLocation: (NSString *) _value
255 ASSIGN (location, _value);
258 - (NSString *) location
263 - (void) setComment: (NSString *) _value
265 ASSIGN (comment, _value);
268 - (NSString *) comment
273 - (NSArray *) categoryList
275 static NSArray *categoryItems = nil;
279 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
301 [categoryItems retain];
304 return categoryItems;
307 - (void) setCategories: (NSArray *) _categories
309 ASSIGN (categories, _categories);
312 - (NSArray *) categories
317 - (void) setCategory: (NSArray *) newCategory
319 ASSIGN (category, newCategory);
322 - (NSString *) category
327 - (NSString *) itemCategoryText
329 return [self labelForKey:
330 [NSString stringWithFormat: @"category_%@", item]];
333 - (NSArray *) calendarList
335 SOGoAppointmentFolder *folder;
336 NSEnumerator *allCalendars;
337 NSDictionary *currentCalendar;
341 calendarList = [NSMutableArray new];
342 folder = [[self clientObject] container];
343 allCalendars = [[folder calendarFolders] objectEnumerator];
344 currentCalendar = [allCalendars nextObject];
345 while (currentCalendar)
347 if ([[currentCalendar objectForKey: @"active"] boolValue])
348 [calendarList addObject: currentCalendar];
349 currentCalendar = [allCalendars nextObject];
356 - (NSString *) calendarsFoldersList
360 calendars = [[self calendarList] valueForKey: @"folder"];
362 return [calendars componentsJoinedByString: @","];
365 - (NSString *) componentCalendar
372 - (NSArray *) priorities
378 static NSArray *priorities = nil;
382 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
389 - (void) setPriority: (NSString *) _priority
391 ASSIGN (priority, _priority);
394 - (NSString *) priority
399 - (NSArray *) privacyClasses
401 static NSArray *priorities = nil;
405 priorities = [NSArray arrayWithObjects: @"PUBLIC",
406 @"CONFIDENTIAL", @"PRIVATE", nil];
413 - (void) setPrivacy: (NSString *) _privacy
415 ASSIGN (privacy, _privacy);
418 - (NSString *) privacy
423 - (NSArray *) statusTypes
425 static NSArray *priorities = nil;
429 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
436 - (void) setStatus: (NSString *) _status
438 ASSIGN (status, _status);
441 - (NSString *) status
450 static NSArray *cycles = nil;
454 bundle = [NSBundle bundleForClass:[self class]];
455 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
456 NSAssert(path != nil, @"Cannot find cycles.plist!");
457 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
458 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
464 - (void) setCycle: (NSDictionary *) _cycle
466 ASSIGN (cycle, _cycle);
469 - (NSDictionary *) cycle
476 return ([cycle objectForKey: @"rule"] != nil);
479 - (NSString *) cycleLabel
483 key = [(NSDictionary *)item objectForKey: @"label"];
485 return [self labelForKey:key];
488 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
490 // NSCalendarDate *until;
492 // /* copy hour/minute/second from startDate */
493 // until = [_cycleUntilDate hour: [startDate hourOfDay]
494 // minute: [startDate minuteOfHour]
495 // second: [startDate secondOfMinute]];
496 // [until setTimeZone: [startDate timeZone]];
497 // ASSIGN (cycleUntilDate, until);
500 - (NSCalendarDate *) cycleUntilDate
502 return cycleUntilDate;
505 - (iCalRecurrenceRule *) rrule
508 iCalRecurrenceRule *rule;
510 if (![self hasCycle])
512 ruleRep = [cycle objectForKey: @"rule"];
513 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
515 if (cycleUntilDate && [self isCycleEndUntil])
516 [rule setUntilDate:cycleUntilDate];
521 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
524 // NSCalendarDate *until;
526 // c = [self cycleMatchingRRule:_rrule];
527 // [self setCycle:c];
529 // until = [[[_rrule untilDate] copy] autorelease];
531 // until = startDate;
533 // [self setIsCycleEndUntil];
535 // [until setTimeZone:[[self clientObject] userTimeZone]];
536 // [self setCycleUntilDate:until];
540 This method is necessary, because we have a fixed sets of cycles in the UI.
541 The model is able to represent arbitrary rules, however.
542 There SHOULD be a different UI, similar to iCal.app, to allow modelling
543 of more complex rules.
545 This method obviously cannot map all existing rules back to the fixed list
546 in cycles.plist. This should be fixed in a future version when interop
547 becomes more important.
549 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
556 return [[self cycles] objectAtIndex:0];
558 cycleRep = [_rrule versitString];
559 cycles = [self cycles];
560 count = [cycles count];
561 for (i = 1; i < count; i++) {
565 c = [cycles objectAtIndex:i];
566 cr = [c objectForKey: @"rule"];
567 if ([cr isEqualToString:cycleRep])
570 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
574 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
575 - (NSArray *) cycleEnds
577 static NSArray *ends = nil;
581 ends = [NSArray arrayWithObjects: @"cycle_end_never",
582 @"cycle_end_until", nil];
589 - (void) setCycleEnd: (NSString *) _cycleEnd
591 ASSIGN (cycleEnd, _cycleEnd);
594 - (NSString *) cycleEnd
599 - (BOOL) isCycleEndUntil
601 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
604 - (void) setIsCycleEndUntil
606 [self setCycleEnd: @"cycle_end_until"];
609 - (void) setIsCycleEndNever
611 [self setCycleEnd: @"cycle_end_never"];
615 - (NSString *) completeURIForMethod: (NSString *) _method
620 uri = [[[self context] request] uri];
622 /* first: identify query parameters */
623 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
625 uri = [uri substringToIndex:r.location];
627 /* next: append trailing slash */
628 if (![uri hasSuffix: @"/"])
629 uri = [uri stringByAppendingString: @"/"];
631 /* next: append method */
632 uri = [uri stringByAppendingString:_method];
634 /* next: append query parameters */
635 return [self completeHrefForMethod:uri];
638 - (BOOL) isWriteableClientObject
640 return [[self clientObject]
641 respondsToSelector: @selector(saveContentString:)];
644 - (BOOL) containsConflict: (id) _component
646 [self subclassResponsibility: _cmd];
654 - (iCalPerson *) getOrganizer
659 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
660 p = [[[iCalPerson alloc] init] autorelease];
661 [p setEmail:emailProp];
662 [p setCn:[self cnForUser]];
667 - (BOOL) isMyComponent
669 return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
672 - (BOOL) canEditComponent
674 return [self isMyComponent];
677 /* response generation */
679 - (NSString *) initialCycleVisibility
681 return ([self hasCycle]
682 ? @"visibility: visible;"
683 : @"visibility: hidden;");
686 - (NSString *) initialCycleEndUntilVisibility {
687 return ([self isCycleEndUntil]
688 ? @"visibility: visible;"
689 : @"visibility: hidden;");
692 // - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
696 // s = [self iCalParticipantsStringFromQueryParameters];
697 // return [s stringByAppendingString:
698 // [self iCalResourcesStringFromQueryParameters]];
701 // - (NSString *) iCalParticipantsStringFromQueryParameters
703 // static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
705 // return [self iCalStringFromQueryParameter: @"ps"
706 // format: iCalParticipantString];
709 // - (NSString *) iCalResourcesStringFromQueryParameters
711 // static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
713 // return [self iCalStringFromQueryParameter: @"rs"
714 // format: iCalResourceString];
717 // - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
718 // format: (NSString *) _format
720 // AgenorUserManager *um;
721 // NSMutableString *iCalRep;
724 // um = [AgenorUserManager sharedUserManager];
725 // iCalRep = (NSMutableString *)[NSMutableString string];
726 // s = [self queryParameterForKey:_qp];
727 // if(s && [s length] > 0) {
729 // unsigned i, count;
731 // es = [s componentsSeparatedByString: @","];
732 // count = [es count];
733 // for(i = 0; i < count; i++) {
734 // NSString *email, *cn;
736 // email = [es objectAtIndex:i];
737 // cn = [um getCNForUID:[um getUIDForEmail:email]];
738 // [iCalRep appendFormat:_format, cn, email];
744 - (NSException *) validateObjectForStatusChange
748 co = [self clientObject];
749 if (![co respondsToSelector: @selector(changeParticipationStatus:)])
750 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
752 @"method cannot be invoked on the specified object"];
757 /* contact editor compatibility */
759 - (NSString *) urlButtonClasses
766 classes = @"button _disabled";
771 - (void) _handleAttendeesEdition
773 NSArray *names, *emails;
774 NSMutableArray *newAttendees;
775 unsigned int count, max;
776 NSString *currentEmail;
777 iCalPerson *currentAttendee;
779 newAttendees = [NSMutableArray new];
780 if ([attendeesNames length] > 0)
782 names = [attendeesNames componentsSeparatedByString: @","];
783 emails = [attendeesEmails componentsSeparatedByString: @","];
784 max = [emails count];
785 for (count = 0; count < max; count++)
787 currentEmail = [emails objectAtIndex: count];
788 currentAttendee = [component findParticipantWithEmail: currentEmail];
789 if (!currentAttendee)
791 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
792 [currentAttendee setCn: [names objectAtIndex: count]];
793 [currentAttendee setEmail: currentEmail];
794 [currentAttendee setRole: @"REQ-PARTICIPANT"];
796 setParticipationStatus: iCalPersonPartStatNeedsAction];
798 [newAttendees addObject: currentAttendee];
802 [component setAttendees: newAttendees];
803 [newAttendees release];
806 - (void) _handleOrganizer
808 NSString *organizerEmail;
809 SOGoUser *activeUser;
810 NSDictionary *primaryIdentity;
812 organizerEmail = [[component organizer] email];
813 if ([organizerEmail length] == 0)
815 if ([[component attendees] count] > 0)
817 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
818 activeUser = [context activeUser];
819 primaryIdentity = [activeUser primaryIdentity];
820 [organizer setCn: [activeUser cn]];
821 [organizer setEmail: [primaryIdentity objectForKey: @"email"]];
822 [component setOrganizer: organizer];
827 if ([[component attendees] count] == 0)
829 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
830 [component setOrganizer: organizer];
835 - (void) takeValuesFromRequest: (WORequest *) _rq
836 inContext: (WOContext *) _ctx
839 SOGoCalendarComponent *clientObject;
841 [super takeValuesFromRequest: _rq inContext: _ctx];
843 now = [NSCalendarDate calendarDate];
844 [component setSummary: title];
845 [component setLocation: location];
846 [component setComment: comment];
847 [component setUrl: url];
848 [component setAccessClass: privacy];
849 [component setCategories: [category capitalizedString]];
850 [self _handleAttendeesEdition];
851 [self _handleOrganizer];
852 clientObject = [self clientObject];
853 if ([clientObject isNew])
855 [component setUid: [clientObject nameInContainer]];
856 [component setCreated: now];
857 [component setTimeStampAsDate: now];
858 [component setPriority: @"0"];
860 [component setLastModified: now];
863 - (NSString *) toolbar
865 SOGoCalendarComponent *clientObject;
866 NSString *toolbarFilename;
867 iCalPerson *participant;
868 iCalPersonPartStat participationStatus;
869 SoSecurityManager *sm;
872 sm = [SoSecurityManager sharedSecurityManager];
873 clientObject = [self clientObject];
875 owner = [clientObject ownerInContext: context];
876 participant = [clientObject findParticipantWithUID: owner];
879 && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
880 onObject: clientObject
883 participationStatus = [participant participationStatus];
884 /* Lightning does not manage participation status within tasks */
885 if (participationStatus == iCalPersonPartStatAccepted)
886 toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
887 else if (participationStatus == iCalPersonPartStatDeclined)
888 toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
890 toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
892 else if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
893 onObject: clientObject
896 if ([[clientObject componentTag] isEqualToString: @"vevent"])
897 toolbarFilename = @"SOGoAppointmentObject.toolbar";
899 toolbarFilename = @"SOGoTaskObject.toolbar";
902 toolbarFilename = @"SOGoComponentClose.toolbar";
904 return toolbarFilename;