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 [names appendFormat: @"%@,", [currentAttendee cn]];
108 [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
109 currentAttendee = [attendees nextObject];
112 if ([names length] > 0)
114 ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
115 ASSIGN (attendeesEmails,
116 [emails substringToIndex: [emails length] - 1]);
123 - (void) _loadCategories
125 NSString *compCategories, *simpleCategory;
127 compCategories = [component categories];
128 if ([compCategories length] > 0)
130 simpleCategory = [[compCategories componentsSeparatedByString: @","]
132 ASSIGN (category, [simpleCategory uppercaseString]);
136 /* warning: we use this method which will be triggered by the template system
137 when the page is instantiated, but we should find another and cleaner way of
138 doing this... for example, when the clientObject is set */
139 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
141 // iCalRecurrenceRule *rrule;
146 component = newComponent;
148 co = [self clientObject];
149 componentOwner = [co ownerInContext: nil];
151 ASSIGN (title, [component summary]);
152 ASSIGN (location, [component location]);
153 ASSIGN (comment, [component comment]);
154 ASSIGN (url, [[component url] absoluteString]);
155 ASSIGN (privacy, [component accessClass]);
156 ASSIGN (priority, [component priority]);
157 ASSIGN (status, [component status]);
158 ASSIGN (categories, [[component categories] commaSeparatedValues]);
159 ASSIGN (organizer, [component organizer]);
160 [self _loadCategories];
161 [self _loadAttendees];
164 // if ([component isRecurrent])
166 // rrule = [[component recurrenceRules] objectAtIndex: 0];
167 // [self adjustCycleControlsForRRule: rrule];
171 - (void) setSaveURL: (NSString *) newSaveURL
173 saveURL = newSaveURL;
176 - (NSString *) saveURL
183 - (void) setItem: (id) _item
185 ASSIGN (item, _item);
193 - (NSString *) itemPriorityText
195 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
198 - (NSString *) itemPrivacyText
202 tag = [[self clientObject] componentTag];
204 return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]];
207 - (NSString *) itemStatusText
209 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
212 - (void) setTitle: (NSString *) _value
214 ASSIGN (title, _value);
222 - (void) setUrl: (NSString *) _url
232 - (void) setAttendeesNames: (NSString *) newAttendeesNames
234 ASSIGN (attendeesNames, newAttendeesNames);
237 - (NSString *) attendeesNames
239 return attendeesNames;
242 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
244 ASSIGN (attendeesEmails, newAttendeesEmails);
247 - (NSString *) attendeesEmails
249 return attendeesEmails;
252 - (void) setLocation: (NSString *) _value
254 ASSIGN (location, _value);
257 - (NSString *) location
262 - (void) setComment: (NSString *) _value
264 ASSIGN (comment, _value);
267 - (NSString *) comment
272 - (NSArray *) categoryList
274 static NSArray *categoryItems = nil;
278 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
300 [categoryItems retain];
303 return categoryItems;
306 - (void) setCategories: (NSArray *) _categories
308 ASSIGN (categories, _categories);
311 - (NSArray *) categories
316 - (void) setCategory: (NSArray *) newCategory
318 ASSIGN (category, newCategory);
321 - (NSString *) category
326 - (NSString *) itemCategoryText
328 return [self labelForKey:
329 [NSString stringWithFormat: @"category_%@", item]];
332 - (NSArray *) calendarList
334 SOGoAppointmentFolder *folder;
335 NSEnumerator *allCalendars;
336 NSDictionary *currentCalendar;
340 calendarList = [NSMutableArray new];
341 folder = [[self clientObject] container];
342 allCalendars = [[folder calendarFolders] objectEnumerator];
343 currentCalendar = [allCalendars nextObject];
344 while (currentCalendar)
346 if ([[currentCalendar objectForKey: @"active"] boolValue])
347 [calendarList addObject: currentCalendar];
348 currentCalendar = [allCalendars nextObject];
355 - (NSString *) calendarsFoldersList
359 calendars = [[self calendarList] valueForKey: @"folder"];
361 return [calendars componentsJoinedByString: @","];
364 - (NSString *) componentCalendar
371 - (NSArray *) priorities
377 static NSArray *priorities = nil;
381 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
388 - (void) setPriority: (NSString *) _priority
390 ASSIGN (priority, _priority);
393 - (NSString *) priority
398 - (NSArray *) privacyClasses
400 static NSArray *priorities = nil;
404 priorities = [NSArray arrayWithObjects: @"PUBLIC",
405 @"CONFIDENTIAL", @"PRIVATE", nil];
412 - (void) setPrivacy: (NSString *) _privacy
414 ASSIGN (privacy, _privacy);
417 - (NSString *) privacy
422 - (NSArray *) statusTypes
424 static NSArray *priorities = nil;
428 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
435 - (void) setStatus: (NSString *) _status
437 ASSIGN (status, _status);
440 - (NSString *) status
449 static NSArray *cycles = nil;
453 bundle = [NSBundle bundleForClass:[self class]];
454 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
455 NSAssert(path != nil, @"Cannot find cycles.plist!");
456 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
457 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
463 - (void) setCycle: (NSDictionary *) _cycle
465 ASSIGN (cycle, _cycle);
468 - (NSDictionary *) cycle
475 return ([cycle objectForKey: @"rule"] != nil);
478 - (NSString *) cycleLabel
482 key = [(NSDictionary *)item objectForKey: @"label"];
484 return [self labelForKey:key];
487 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
489 // NSCalendarDate *until;
491 // /* copy hour/minute/second from startDate */
492 // until = [_cycleUntilDate hour: [startDate hourOfDay]
493 // minute: [startDate minuteOfHour]
494 // second: [startDate secondOfMinute]];
495 // [until setTimeZone: [startDate timeZone]];
496 // ASSIGN (cycleUntilDate, until);
499 - (NSCalendarDate *) cycleUntilDate
501 return cycleUntilDate;
504 - (iCalRecurrenceRule *) rrule
507 iCalRecurrenceRule *rule;
509 if (![self hasCycle])
511 ruleRep = [cycle objectForKey: @"rule"];
512 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
514 if (cycleUntilDate && [self isCycleEndUntil])
515 [rule setUntilDate:cycleUntilDate];
520 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
523 // NSCalendarDate *until;
525 // c = [self cycleMatchingRRule:_rrule];
526 // [self setCycle:c];
528 // until = [[[_rrule untilDate] copy] autorelease];
530 // until = startDate;
532 // [self setIsCycleEndUntil];
534 // [until setTimeZone:[[self clientObject] userTimeZone]];
535 // [self setCycleUntilDate:until];
539 This method is necessary, because we have a fixed sets of cycles in the UI.
540 The model is able to represent arbitrary rules, however.
541 There SHOULD be a different UI, similar to iCal.app, to allow modelling
542 of more complex rules.
544 This method obviously cannot map all existing rules back to the fixed list
545 in cycles.plist. This should be fixed in a future version when interop
546 becomes more important.
548 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
555 return [[self cycles] objectAtIndex:0];
557 cycleRep = [_rrule versitString];
558 cycles = [self cycles];
559 count = [cycles count];
560 for (i = 1; i < count; i++) {
564 c = [cycles objectAtIndex:i];
565 cr = [c objectForKey: @"rule"];
566 if ([cr isEqualToString:cycleRep])
569 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
573 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
574 - (NSArray *) cycleEnds
576 static NSArray *ends = nil;
580 ends = [NSArray arrayWithObjects: @"cycle_end_never",
581 @"cycle_end_until", nil];
588 - (void) setCycleEnd: (NSString *) _cycleEnd
590 ASSIGN (cycleEnd, _cycleEnd);
593 - (NSString *) cycleEnd
598 - (BOOL) isCycleEndUntil
600 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
603 - (void) setIsCycleEndUntil
605 [self setCycleEnd: @"cycle_end_until"];
608 - (void) setIsCycleEndNever
610 [self setCycleEnd: @"cycle_end_never"];
614 - (NSString *) completeURIForMethod: (NSString *) _method
619 uri = [[[self context] request] uri];
621 /* first: identify query parameters */
622 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
624 uri = [uri substringToIndex:r.location];
626 /* next: append trailing slash */
627 if (![uri hasSuffix: @"/"])
628 uri = [uri stringByAppendingString: @"/"];
630 /* next: append method */
631 uri = [uri stringByAppendingString:_method];
633 /* next: append query parameters */
634 return [self completeHrefForMethod:uri];
637 - (BOOL) isWriteableClientObject
639 return [[self clientObject]
640 respondsToSelector: @selector(saveContentString:)];
643 - (BOOL) containsConflict: (id) _component
645 [self subclassResponsibility: _cmd];
653 - (iCalPerson *) getOrganizer
658 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
659 p = [[[iCalPerson alloc] init] autorelease];
660 [p setEmail:emailProp];
661 [p setCn:[self cnForUser]];
666 - (BOOL) isMyComponent
668 return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
671 - (BOOL) canEditComponent
673 return [self isMyComponent];
676 /* response generation */
678 - (NSString *) initialCycleVisibility
680 return ([self hasCycle]
681 ? @"visibility: visible;"
682 : @"visibility: hidden;");
685 - (NSString *) initialCycleEndUntilVisibility {
686 return ([self isCycleEndUntil]
687 ? @"visibility: visible;"
688 : @"visibility: hidden;");
691 // - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
695 // s = [self iCalParticipantsStringFromQueryParameters];
696 // return [s stringByAppendingString:
697 // [self iCalResourcesStringFromQueryParameters]];
700 // - (NSString *) iCalParticipantsStringFromQueryParameters
702 // static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
704 // return [self iCalStringFromQueryParameter: @"ps"
705 // format: iCalParticipantString];
708 // - (NSString *) iCalResourcesStringFromQueryParameters
710 // static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
712 // return [self iCalStringFromQueryParameter: @"rs"
713 // format: iCalResourceString];
716 // - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
717 // format: (NSString *) _format
719 // AgenorUserManager *um;
720 // NSMutableString *iCalRep;
723 // um = [AgenorUserManager sharedUserManager];
724 // iCalRep = (NSMutableString *)[NSMutableString string];
725 // s = [self queryParameterForKey:_qp];
726 // if(s && [s length] > 0) {
728 // unsigned i, count;
730 // es = [s componentsSeparatedByString: @","];
731 // count = [es count];
732 // for(i = 0; i < count; i++) {
733 // NSString *email, *cn;
735 // email = [es objectAtIndex:i];
736 // cn = [um getCNForUID:[um getUIDForEmail:email]];
737 // [iCalRep appendFormat:_format, cn, email];
743 - (NSException *) validateObjectForStatusChange
747 co = [self clientObject];
748 if (![co respondsToSelector: @selector(changeParticipationStatus:)])
749 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
751 @"method cannot be invoked on the specified object"];
756 /* contact editor compatibility */
758 - (NSString *) urlButtonClasses
765 classes = @"button _disabled";
770 - (void) _handleAttendeesEdition
772 NSArray *names, *emails;
773 NSMutableArray *newAttendees;
774 unsigned int count, max;
775 NSString *currentEmail;
776 iCalPerson *currentAttendee;
778 newAttendees = [NSMutableArray new];
779 if ([attendeesNames length] > 0)
781 names = [attendeesNames componentsSeparatedByString: @","];
782 emails = [attendeesEmails componentsSeparatedByString: @","];
783 max = [emails count];
784 for (count = 0; count < max; count++)
786 currentEmail = [emails objectAtIndex: count];
787 currentAttendee = [component findParticipantWithEmail: currentEmail];
788 if (!currentAttendee)
790 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
791 [currentAttendee setCn: [names objectAtIndex: count]];
792 [currentAttendee setEmail: currentEmail];
793 [currentAttendee setRole: @"REQ-PARTICIPANT"];
795 setParticipationStatus: iCalPersonPartStatNeedsAction];
797 [newAttendees addObject: currentAttendee];
801 [component setAttendees: newAttendees];
802 [newAttendees release];
805 - (void) _handleOrganizer
807 NSString *organizerEmail;
808 SOGoUser *activeUser;
809 NSDictionary *primaryIdentity;
811 organizerEmail = [[component organizer] email];
812 if ([organizerEmail length] == 0)
814 if ([[component attendees] count] > 0)
816 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
817 activeUser = [context activeUser];
818 primaryIdentity = [activeUser primaryIdentity];
819 [organizer setCn: [activeUser cn]];
820 [organizer setEmail: [primaryIdentity objectForKey: @"email"]];
821 [component setOrganizer: organizer];
826 if ([[component attendees] count] == 0)
828 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
829 [component setOrganizer: organizer];
834 - (void) takeValuesFromRequest: (WORequest *) _rq
835 inContext: (WOContext *) _ctx
838 SOGoCalendarComponent *clientObject;
840 [super takeValuesFromRequest: _rq inContext: _ctx];
842 now = [NSCalendarDate calendarDate];
843 [component setSummary: title];
844 [component setLocation: location];
845 [component setComment: comment];
846 [component setUrl: url];
847 [component setAccessClass: privacy];
848 [component setCategories: [category capitalizedString]];
849 [self _handleAttendeesEdition];
850 [self _handleOrganizer];
851 clientObject = [self clientObject];
852 if ([clientObject isNew])
854 [component setUid: [clientObject nameInContainer]];
855 [component setCreated: now];
856 [component setTimeStampAsDate: now];
857 [component setPriority: @"0"];
859 [component setLastModified: now];
862 - (NSString *) toolbar
864 SOGoCalendarComponent *clientObject;
865 NSString *toolbarFilename;
866 iCalPerson *participant;
867 iCalPersonPartStat participationStatus;
868 SoSecurityManager *sm;
871 sm = [SoSecurityManager sharedSecurityManager];
872 clientObject = [self clientObject];
874 owner = [clientObject ownerInContext: context];
875 participant = [clientObject findParticipantWithUID: owner];
878 && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
879 onObject: clientObject
882 participationStatus = [participant participationStatus];
883 /* Lightning does not manage participation status within tasks */
884 if (participationStatus == iCalPersonPartStatAccepted)
885 toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
886 else if (participationStatus == iCalPersonPartStatDeclined)
887 toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
889 toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
891 else if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
892 onObject: clientObject
895 if ([[clientObject componentTag] isEqualToString: @"vevent"])
896 toolbarFilename = @"SOGoAppointmentObject.toolbar";
898 toolbarFilename = @"SOGoTaskObject.toolbar";
901 toolbarFilename = @"SOGoComponentClose.toolbar";
903 return toolbarFilename;