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/SOGoAppointmentFolders.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 [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 *calendar, *currentCalendar;
336 SOGoAppointmentFolders *calendarParent;
337 NSEnumerator *allCalendars;
341 calendarList = [NSMutableArray new];
342 calendar = [[self clientObject] container];
343 calendarParent = [calendar container];
344 allCalendars = [[calendarParent subFolders] objectEnumerator];
345 currentCalendar = [allCalendars nextObject];
346 while (currentCalendar)
348 if ([currentCalendar isActive])
349 [calendarList addObject: currentCalendar];
350 currentCalendar = [allCalendars nextObject];
357 - (NSString *) calendarsFoldersList
361 calendars = [[self calendarList] valueForKey: @"nameInContainer"];
363 return [calendars componentsJoinedByString: @","];
366 - (SOGoAppointmentFolder *) componentCalendar
368 SOGoAppointmentFolder *calendar;
370 calendar = [[self clientObject] container];
377 - (NSArray *) priorities
384 static NSArray *priorities = nil;
388 priorities = [NSArray arrayWithObjects: @"9", @"5", @"1", nil];
395 - (void) setPriority: (NSString *) _priority
397 ASSIGN (priority, _priority);
400 - (NSString *) priority
405 - (NSArray *) privacyClasses
407 static NSArray *priorities = nil;
411 priorities = [NSArray arrayWithObjects: @"PUBLIC",
412 @"CONFIDENTIAL", @"PRIVATE", nil];
419 - (void) setPrivacy: (NSString *) _privacy
421 ASSIGN (privacy, _privacy);
424 - (NSString *) privacy
429 - (NSArray *) statusTypes
431 static NSArray *priorities = nil;
435 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
442 - (void) setStatus: (NSString *) _status
444 ASSIGN (status, _status);
447 - (NSString *) status
456 static NSArray *cycles = nil;
460 bundle = [NSBundle bundleForClass:[self class]];
461 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
462 NSAssert(path != nil, @"Cannot find cycles.plist!");
463 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
464 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
470 - (void) setCycle: (NSDictionary *) _cycle
472 ASSIGN (cycle, _cycle);
475 - (NSDictionary *) cycle
482 return ([cycle objectForKey: @"rule"] != nil);
485 - (NSString *) cycleLabel
489 key = [(NSDictionary *)item objectForKey: @"label"];
491 return [self labelForKey:key];
494 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
496 // NSCalendarDate *until;
498 // /* copy hour/minute/second from startDate */
499 // until = [_cycleUntilDate hour: [startDate hourOfDay]
500 // minute: [startDate minuteOfHour]
501 // second: [startDate secondOfMinute]];
502 // [until setTimeZone: [startDate timeZone]];
503 // ASSIGN (cycleUntilDate, until);
506 - (NSCalendarDate *) cycleUntilDate
508 return cycleUntilDate;
511 - (iCalRecurrenceRule *) rrule
514 iCalRecurrenceRule *rule;
516 if (![self hasCycle])
518 ruleRep = [cycle objectForKey: @"rule"];
519 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
521 if (cycleUntilDate && [self isCycleEndUntil])
522 [rule setUntilDate:cycleUntilDate];
527 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
530 // NSCalendarDate *until;
532 // c = [self cycleMatchingRRule:_rrule];
533 // [self setCycle:c];
535 // until = [[[_rrule untilDate] copy] autorelease];
537 // until = startDate;
539 // [self setIsCycleEndUntil];
541 // [until setTimeZone:[[self clientObject] userTimeZone]];
542 // [self setCycleUntilDate:until];
546 This method is necessary, because we have a fixed sets of cycles in the UI.
547 The model is able to represent arbitrary rules, however.
548 There SHOULD be a different UI, similar to iCal.app, to allow modelling
549 of more complex rules.
551 This method obviously cannot map all existing rules back to the fixed list
552 in cycles.plist. This should be fixed in a future version when interop
553 becomes more important.
555 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
562 return [[self cycles] objectAtIndex:0];
564 cycleRep = [_rrule versitString];
565 cycles = [self cycles];
566 count = [cycles count];
567 for (i = 1; i < count; i++) {
571 c = [cycles objectAtIndex:i];
572 cr = [c objectForKey: @"rule"];
573 if ([cr isEqualToString:cycleRep])
576 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
580 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
581 - (NSArray *) cycleEnds
583 static NSArray *ends = nil;
587 ends = [NSArray arrayWithObjects: @"cycle_end_never",
588 @"cycle_end_until", nil];
595 - (void) setCycleEnd: (NSString *) _cycleEnd
597 ASSIGN (cycleEnd, _cycleEnd);
600 - (NSString *) cycleEnd
605 - (BOOL) isCycleEndUntil
607 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
610 - (void) setIsCycleEndUntil
612 [self setCycleEnd: @"cycle_end_until"];
615 - (void) setIsCycleEndNever
617 [self setCycleEnd: @"cycle_end_never"];
621 - (NSString *) completeURIForMethod: (NSString *) _method
626 uri = [[[self context] request] uri];
628 /* first: identify query parameters */
629 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
631 uri = [uri substringToIndex:r.location];
633 /* next: append trailing slash */
634 if (![uri hasSuffix: @"/"])
635 uri = [uri stringByAppendingString: @"/"];
637 /* next: append method */
638 uri = [uri stringByAppendingString:_method];
640 /* next: append query parameters */
641 return [self completeHrefForMethod:uri];
644 - (BOOL) isWriteableClientObject
646 return [[self clientObject]
647 respondsToSelector: @selector(saveContentString:)];
650 - (BOOL) containsConflict: (id) _component
652 [self subclassResponsibility: _cmd];
660 - (iCalPerson *) getOrganizer
665 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
666 p = [[[iCalPerson alloc] init] autorelease];
667 [p setEmail:emailProp];
668 [p setCn:[self cnForUser]];
673 - (BOOL) isMyComponent
675 return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
678 - (BOOL) canEditComponent
680 return [self isMyComponent];
683 /* response generation */
685 - (NSString *) initialCycleVisibility
687 return ([self hasCycle]
688 ? @"visibility: visible;"
689 : @"visibility: hidden;");
692 - (NSString *) initialCycleEndUntilVisibility {
693 return ([self isCycleEndUntil]
694 ? @"visibility: visible;"
695 : @"visibility: hidden;");
698 // - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
702 // s = [self iCalParticipantsStringFromQueryParameters];
703 // return [s stringByAppendingString:
704 // [self iCalResourcesStringFromQueryParameters]];
707 // - (NSString *) iCalParticipantsStringFromQueryParameters
709 // static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
711 // return [self iCalStringFromQueryParameter: @"ps"
712 // format: iCalParticipantString];
715 // - (NSString *) iCalResourcesStringFromQueryParameters
717 // static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
719 // return [self iCalStringFromQueryParameter: @"rs"
720 // format: iCalResourceString];
723 // - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
724 // format: (NSString *) _format
726 // AgenorUserManager *um;
727 // NSMutableString *iCalRep;
730 // um = [AgenorUserManager sharedUserManager];
731 // iCalRep = (NSMutableString *)[NSMutableString string];
732 // s = [self queryParameterForKey:_qp];
733 // if(s && [s length] > 0) {
735 // unsigned i, count;
737 // es = [s componentsSeparatedByString: @","];
738 // count = [es count];
739 // for(i = 0; i < count; i++) {
740 // NSString *email, *cn;
742 // email = [es objectAtIndex:i];
743 // cn = [um getCNForUID:[um getUIDForEmail:email]];
744 // [iCalRep appendFormat:_format, cn, email];
750 - (NSException *) validateObjectForStatusChange
754 co = [self clientObject];
755 if (![co respondsToSelector: @selector(changeParticipationStatus:)])
756 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
758 @"method cannot be invoked on the specified object"];
763 /* contact editor compatibility */
765 - (NSString *) urlButtonClasses
772 classes = @"button _disabled";
777 - (void) _handleAttendeesEdition
779 NSArray *names, *emails;
780 NSMutableArray *newAttendees;
781 unsigned int count, max;
782 NSString *currentEmail;
783 iCalPerson *currentAttendee;
785 newAttendees = [NSMutableArray new];
786 if ([attendeesNames length] > 0)
788 names = [attendeesNames componentsSeparatedByString: @","];
789 emails = [attendeesEmails componentsSeparatedByString: @","];
790 max = [emails count];
791 for (count = 0; count < max; count++)
793 currentEmail = [emails objectAtIndex: count];
794 currentAttendee = [component findParticipantWithEmail: currentEmail];
795 if (!currentAttendee)
797 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
798 [currentAttendee setCn: [names objectAtIndex: count]];
799 [currentAttendee setEmail: currentEmail];
800 [currentAttendee setRole: @"REQ-PARTICIPANT"];
802 setParticipationStatus: iCalPersonPartStatNeedsAction];
804 [newAttendees addObject: currentAttendee];
808 [component setAttendees: newAttendees];
809 [newAttendees release];
812 - (void) _handleOrganizer
814 NSString *organizerEmail;
815 SOGoUser *activeUser;
816 NSDictionary *primaryIdentity;
818 organizerEmail = [[component organizer] email];
819 if ([organizerEmail length] == 0)
821 if ([[component attendees] count] > 0)
823 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
824 activeUser = [context activeUser];
825 primaryIdentity = [activeUser primaryIdentity];
826 [organizer setCn: [activeUser cn]];
827 [organizer setEmail: [primaryIdentity objectForKey: @"email"]];
828 [component setOrganizer: organizer];
833 if ([[component attendees] count] == 0)
835 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
836 [component setOrganizer: organizer];
841 - (void) takeValuesFromRequest: (WORequest *) _rq
842 inContext: (WOContext *) _ctx
845 SOGoCalendarComponent *clientObject;
847 [super takeValuesFromRequest: _rq inContext: _ctx];
849 now = [NSCalendarDate calendarDate];
850 [component setSummary: title];
851 [component setLocation: location];
852 [component setComment: comment];
853 [component setUrl: url];
854 [component setAccessClass: privacy];
855 [component setCategories: [category capitalizedString]];
856 [self _handleAttendeesEdition];
857 [self _handleOrganizer];
858 clientObject = [self clientObject];
859 if ([clientObject isNew])
861 [component setUid: [clientObject nameInContainer]];
862 [component setCreated: now];
863 [component setTimeStampAsDate: now];
865 [component setPriority: priority];
866 [component setLastModified: now];
869 - (NSString *) toolbar
871 SOGoCalendarComponent *clientObject;
872 NSString *toolbarFilename;
873 iCalPerson *participant;
874 iCalPersonPartStat participationStatus;
875 SoSecurityManager *sm;
878 sm = [SoSecurityManager sharedSecurityManager];
879 clientObject = [self clientObject];
881 owner = [clientObject ownerInContext: context];
882 participant = [clientObject findParticipantWithUID: owner];
885 && ![sm validatePermission: SOGoCalendarPerm_RespondToComponent
886 onObject: clientObject
889 participationStatus = [participant participationStatus];
890 /* Lightning does not manage participation status within tasks */
891 if (participationStatus == iCalPersonPartStatAccepted)
892 toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
893 else if (participationStatus == iCalPersonPartStatDeclined)
894 toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
896 toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
898 else if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
899 onObject: clientObject
902 if ([[clientObject componentTag] isEqualToString: @"vevent"])
903 toolbarFilename = @"SOGoAppointmentObject.toolbar";
905 toolbarFilename = @"SOGoTaskObject.toolbar";
908 toolbarFilename = @"SOGoComponentClose.toolbar";
910 return toolbarFilename;