2 Copyright (C) 2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include <SOGoUI/UIxComponent.h>
29 @class iCalRecurrenceRule;
30 @class SOGoAppointment;
32 @interface UIxAppointmentEditor : UIxComponent
38 /* individual values */
39 NSCalendarDate *startDate;
40 NSCalendarDate *endDate;
41 NSCalendarDate *cycleUntilDate;
45 iCalPerson *organizer;
46 NSArray *participants; /* array of iCalPerson's */
47 NSArray *resources; /* array of iCalPerson's */
50 NSString *accessClass;
51 BOOL isPrivate; /* default: NO */
52 BOOL checkForConflicts; /* default: NO */
57 - (NSString *)iCalStringTemplate;
58 - (NSString *)iCalString;
60 - (void)setIsPrivate:(BOOL)_yn;
61 - (void)setAccessClass:(NSString *)_class;
63 - (void)setCheckForConflicts:(BOOL)_checkForConflicts;
64 - (BOOL)checkForConflicts;
67 - (iCalRecurrenceRule *)rrule;
68 - (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule;
69 - (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule;
71 - (BOOL)isCycleEndUntil;
72 - (void)setIsCycleEndUntil;
73 - (void)setIsCycleEndNever;
75 - (NSString *)_completeURIForMethod:(NSString *)_method;
77 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
78 treatAsResource:(BOOL)_isResource;
80 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters;
81 - (NSString *)iCalParticipantsStringFromQueryParameters;
82 - (NSString *)iCalResourcesStringFromQueryParameters;
83 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
84 format:(NSString *)_format;
85 - (NSString *)iCalOrganizerString;
87 - (id)acceptOrDeclineAction:(BOOL)_accept;
92 #include <NGiCal/NGiCal.h>
93 #include <NGExtensions/NGCalendarDateRange.h>
94 #include <SOGoUI/SOGoDateFormatter.h>
95 #include <SOGo/SOGoAppointment.h>
96 #include <SOGo/AgenorUserManager.h>
97 #include <Appointments/SOGoAppointmentFolder.h>
98 #include <Appointments/SOGoAppointmentObject.h>
99 #include "iCalPerson+UIx.h"
100 #include "UIxComponent+Agenor.h"
102 @interface iCalRecurrenceRule (SOGoExtensions)
103 - (NSString *)cycleRepresentationForSOGo;
106 @interface NSDate(UsedPrivates)
107 - (NSString *)icalString; // TODO: this is in NGiCal
110 @implementation UIxAppointmentEditor
115 [self setIsPrivate:NO];
116 [self setCheckForConflicts:NO];
117 [self setIsCycleEndNever];
123 [self->iCalString release];
124 [self->errorText release];
125 [self->item release];
127 [self->startDate release];
128 [self->endDate release];
129 [self->cycleUntilDate release];
130 [self->title release];
131 [self->location release];
132 [self->organizer release];
133 [self->comment release];
134 [self->participants release];
135 [self->resources release];
136 [self->priority release];
137 [self->categories release];
138 [self->accessClass release];
139 [self->cycle release];
140 [self->cycleEnd release];
146 - (void)setItem:(id)_item {
147 ASSIGN(self->item, _item);
153 - (void)setErrorText:(NSString *)_txt {
154 ASSIGNCOPY(self->errorText, _txt);
156 - (NSString *)errorText {
157 return self->errorText;
159 - (BOOL)hasErrorText {
160 return [self->errorText length] > 0 ? YES : NO;
163 - (NSFormatter *)titleDateFormatter {
164 SOGoDateFormatter *fmt;
166 fmt = [[[SOGoDateFormatter alloc] initWithLocale:[self locale]] autorelease];
167 [fmt setFullWeekdayNameAndDetails];
171 - (void)setAptStartDate:(NSCalendarDate *)_date {
172 ASSIGN(self->startDate, _date);
174 - (NSCalendarDate *)aptStartDate {
175 return self->startDate;
177 - (void)setAptEndDate:(NSCalendarDate *)_date {
178 ASSIGN(self->endDate, _date);
180 - (NSCalendarDate *)aptEndDate {
181 return self->endDate;
184 - (void)setTitle:(NSString *)_value {
185 ASSIGNCOPY(self->title, _value);
187 - (NSString *)title {
190 - (void)setLocation:(NSString *)_value {
191 ASSIGNCOPY(self->location, _value);
193 - (NSString *)location {
194 return self->location;
196 - (void)setComment:(NSString *)_value {
197 ASSIGNCOPY(self->comment, _value);
199 - (NSString *)comment {
200 return self->comment;
203 - (void)setParticipants:(NSArray *)_parts {
204 ASSIGN(self->participants, _parts);
206 - (NSArray *)participants {
207 return self->participants;
209 - (void)setResources:(NSArray *)_res {
210 ASSIGN(self->resources, _res);
212 - (NSArray *)resources {
213 return self->resources;
218 - (NSArray *)priorities {
223 static NSArray *priorities = nil;
226 priorities = [[NSArray arrayWithObjects:@"0", @"5", @"1", nil] retain];
230 - (NSString *)itemPriorityText {
233 key = [NSString stringWithFormat:@"prio_%@", self->item];
234 return [self labelForKey:key];
237 - (void)setPriority:(NSString *)_priority {
238 ASSIGN(self->priority, _priority);
240 - (NSString *)priority {
241 return self->priority;
247 - (NSArray *)categoryItems {
248 // TODO: make this configurable?
250 Tasks categories will be modified as follow :
251 – by default (a simple logo or no logo at all),
258 static NSArray *categoryItems = nil;
260 if (!categoryItems) {
261 categoryItems = [[NSArray arrayWithObjects:@"APPOINTMENT",
268 return categoryItems;
271 - (NSString *)itemCategoryText {
272 return [self labelForKey:self->item];
275 - (void)setCategories:(NSArray *)_categories {
276 ASSIGN(self->categories, _categories);
278 - (NSArray *)categories {
279 return self->categories;
285 - (NSArray *)accessClassItems {
286 static NSArray classItems = nil;
289 return [[NSArray arrayWithObjects:@"PUBLIC", @"PRIVATE", nil] retain];
295 - (void)setAccessClass:(NSString *)_class {
296 ASSIGN(self->accessClass, _class);
298 - (NSString *)accessClass {
299 return self->accessClass;
302 - (void)setIsPrivate:(BOOL)_yn {
304 [self setAccessClass:@"PRIVATE"];
306 [self setAccessClass:@"PUBLIC"];
307 self->isPrivate = _yn;
310 return self->isPrivate;
313 - (void)setCheckForConflicts:(BOOL)_checkForConflicts {
314 self->checkForConflicts = _checkForConflicts;
316 - (BOOL)checkForConflicts {
317 return self->checkForConflicts;
320 - (NSArray *)cycles {
321 static NSArray *cycles = nil;
327 bundle = [NSBundle bundleForClass:[self class]];
328 path = [bundle pathForResource:@"cycles" ofType:@"plist"];
329 NSAssert(path != nil, @"Cannot find cycles.plist!");
330 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
331 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
336 - (void)setCycle:(NSDictionary *)_cycle {
337 ASSIGN(self->cycle, _cycle);
339 - (NSDictionary *)cycle {
343 [self debugWithFormat:@"cycle: %@", self->cycle];
344 if (![self->cycle objectForKey:@"rule"])
348 - (NSString *)cycleLabel {
351 key = [self->item objectForKey:@"label"];
352 return [self labelForKey:key];
356 - (void)setCycleUntilDate:(NSCalendarDate *)_cycleUntilDate {
357 NSCalendarDate *until;
359 /* copy hour/minute/second from startDate */
360 until = [_cycleUntilDate hour:[self->startDate hourOfDay]
361 minute:[self->startDate minuteOfHour]
362 second:[self->startDate secondOfMinute]];
363 [until setTimeZone:[self->startDate timeZone]];
364 ASSIGN(self->cycleUntilDate, until);
366 - (NSCalendarDate *)cycleUntilDate {
367 return self->cycleUntilDate;
370 - (iCalRecurrenceRule *)rrule {
372 iCalRecurrenceRule *rule;
374 if (![self hasCycle])
376 ruleRep = [self->cycle objectForKey:@"rule"];
377 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
379 if (self->cycleUntilDate && [self isCycleEndUntil])
380 [rule setUntilDate:self->cycleUntilDate];
384 - (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule {
386 NSCalendarDate *until;
388 c = [self cycleMatchingRRule:_rrule];
391 until = [[[_rrule untilDate] copy] autorelease];
393 until = self->startDate;
395 [self setIsCycleEndUntil];
397 [until setTimeZone:[self viewTimeZone]];
398 [self setCycleUntilDate:until];
402 This method is necessary, because we have a fixed sets of cycles in the UI.
403 The model is able to represent arbitrary rules, however.
404 There SHOULD be a different UI, similar to iCal.app, to allow modelling
405 of more complex rules.
407 This method obviously cannot map all existing rules back to the fixed list
408 in cycles.plist. This should be fixed in a future version when interop
409 becomes more important.
411 - (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule {
417 return [[self cycles] objectAtIndex:0];
419 cycleRep = [_rrule cycleRepresentationForSOGo];
420 cycles = [self cycles];
421 count = [cycles count];
422 for (i = 1; i < count; i++) {
426 c = [cycles objectAtIndex:i];
427 cr = [c objectForKey:@"rule"];
428 if ([cr isEqualToString:cycleRep])
431 [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
435 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
436 - (NSArray *)cycleEnds {
437 static NSArray *ends = nil;
440 ends = [[NSArray alloc] initWithObjects:@"cycle_end_never",
447 - (void)setCycleEnd:(NSString *)_cycleEnd {
448 ASSIGNCOPY(self->cycleEnd, _cycleEnd);
450 - (NSString *)cycleEnd {
451 return self->cycleEnd;
453 - (BOOL)isCycleEndUntil {
454 return (self->cycleEnd &&
455 [self->cycleEnd isEqualToString:@"cycle_end_until"]);
457 - (void)setIsCycleEndUntil {
458 [self setCycleEnd:@"cycle_end_until"];
460 - (void)setIsCycleEndNever {
461 [self setCycleEnd:@"cycle_end_never"];
466 - (NSString *)transparency {
467 return @"TRANSPARENT";
473 - (void)setICalString:(NSString *)_s {
474 ASSIGNCOPY(self->iCalString, _s);
476 - (NSString *)iCalString {
477 return self->iCalString;
480 - (NSString *)iCalStringTemplate {
481 static NSString *iCalStringTemplate = \
482 @"BEGIN:VCALENDAR\r\n"
483 @"METHOD:REQUEST\r\n"
484 @"PRODID:OpenGroupware.org SOGo 0.9\r\n"
489 @"STATUS:CONFIRMED\r\n" /* confirmed by default */
496 @"%@" /* organizer */
497 @"%@" /* participants and resources */
501 NSCalendarDate *lStartDate, *lEndDate;
502 NSString *template, *s;
505 s = [self queryParameterForKey:@"dur"];
506 if(s && [s length] > 0) {
507 minutes = [s intValue];
512 lStartDate = [self selectedDate];
513 lEndDate = [lStartDate dateByAddingYears:0 months:0 days:0
514 hours:0 minutes:minutes seconds:0];
516 s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
517 template = [NSString stringWithFormat:iCalStringTemplate,
518 [[self clientObject] nameInContainer],
519 [[NSCalendarDate date] icalString],
520 [lStartDate icalString],
521 [lEndDate icalString],
523 [self iCalOrganizerString],
528 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters {
531 s = [self iCalParticipantsStringFromQueryParameters];
532 return [s stringByAppendingString:
533 [self iCalResourcesStringFromQueryParameters]];
536 - (NSString *)iCalParticipantsStringFromQueryParameters {
537 static NSString *iCalParticipantString = \
538 @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
540 return [self iCalStringFromQueryParameter:@"ps"
541 format:iCalParticipantString];
544 - (NSString *)iCalResourcesStringFromQueryParameters {
545 static NSString *iCalResourceString = \
546 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
548 return [self iCalStringFromQueryParameter:@"rs"
549 format:iCalResourceString];
552 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
553 format:(NSString *)_format
555 AgenorUserManager *um;
556 NSMutableString *iCalRep;
559 um = [AgenorUserManager sharedUserManager];
560 iCalRep = (NSMutableString *)[NSMutableString string];
561 s = [self queryParameterForKey:_qp];
562 if(s && [s length] > 0) {
566 es = [s componentsSeparatedByString:@","];
568 for(i = 0; i < count; i++) {
569 NSString *email, *cn;
571 email = [es objectAtIndex:i];
572 cn = [um getCNForUID:[um getUIDForEmail:email]];
573 [iCalRep appendFormat:_format, cn, email];
579 - (NSString *)iCalOrganizerString {
580 static NSString *fmt = @"ORGANIZER;CN=\"%@\":mailto:%@\r\n";
581 return [NSString stringWithFormat:fmt,
583 [self emailForUser]];
587 - (iCalPerson *)getOrganizer {
591 emailProp = [@"mailto:" stringByAppendingString:[self emailForUser]];
592 p = [[[iCalPerson alloc] init] autorelease];
593 [p setEmail:emailProp];
594 [p setCn:[self cnForUser]];
602 - (NSString *)_completeURIForMethod:(NSString *)_method {
606 uri = [[[self context] request] uri];
608 /* first: identify query parameters */
609 r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
611 uri = [uri substringToIndex:r.location];
613 /* next: append trailing slash */
614 if (![uri hasSuffix:@"/"])
615 uri = [uri stringByAppendingString:@"/"];
617 /* next: append method */
618 uri = [uri stringByAppendingString:_method];
620 /* next: append query parameters */
621 return [self completeHrefForMethod:uri];
628 This method creates a unique ID and redirects to the "edit" method on the
630 It is actually a folder method and should be defined on the folder.
632 Note: 'clientObject' is the SOGoAppointmentFolder!
633 Update: remember that there are group folders as well.
635 NSString *uri, *objectId, *method;
637 objectId = [NSClassFromString(@"SOGoAppointmentFolder")
638 globallyUniqueObjectId];
639 if ([objectId length] == 0) {
640 return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
641 reason:@"could not create a unique ID"];
644 method = [NSString stringWithFormat:@"Calendar/%@/edit", objectId];
645 method = [[self userFolderPath] stringByAppendingPathComponent:method];
647 /* add all current calendarUIDs as default participants */
648 if ([[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
649 AgenorUserManager *um;
651 NSMutableArray *emails;
655 um = [AgenorUserManager sharedUserManager];
656 uids = [[self clientObject] calendarUIDs];
657 count = [uids count];
658 emails = [NSMutableArray arrayWithCapacity:count];
660 for (i = 0; i < count; i++) {
663 email = [um getEmailForUID:[uids objectAtIndex:i]];
665 [emails addObject:email];
667 ps = [emails componentsJoinedByString:@","];
668 [self setQueryParameter:ps forKey:@"ps"];
670 uri = [self completeHrefForMethod:method];
671 return [self redirectToLocation:uri];
676 /* returned dates are in GMT */
677 - (NSCalendarDate *)_dateFromString:(NSString *)_str {
678 NSCalendarDate *date;
680 date = [NSCalendarDate dateWithString:_str
681 calendarFormat:@"%Y-%m-%d %H:%M %Z"];
682 [date setTimeZone:[self backendTimeZone]];
686 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
687 treatAsResource:(BOOL)_isResource
690 NSMutableArray *result;
692 count = [_values count];
693 result = [[NSMutableArray alloc] initWithCapacity:count];
694 for (i = 0; i < count; i++) {
695 NSString *pString, *email, *cn;
699 pString = [_values objectAtIndex:i];
700 if ([pString length] == 0)
703 /* delimiter between email and cn */
704 r = [pString rangeOfString:@";"];
706 email = [pString substringToIndex:r.location];
707 cn = (r.location + 1 < [pString length])
708 ? [pString substringFromIndex:r.location + 1]
717 AgenorUserManager *um = [AgenorUserManager sharedUserManager];
718 cn = [um getCNForUID:[um getUIDForEmail:email]];
721 p = [[iCalPerson alloc] init];
722 [p setEmail:[@"mailto:" stringByAppendingString:email]];
723 if ([cn isNotNull]) [p setCn:cn];
725 /* see RFC2445, sect. 4.2.16 for details */
726 [p setRole:_isResource ? @"NON-PARTICIPANT" : @"REQ-PARTICIPANT"];
727 [result addObject:p];
730 return [result autorelease];
733 - (BOOL)isWriteableClientObject {
734 return [[self clientObject]
735 respondsToSelector:@selector(saveContentString:)];
738 - (void)loadValuesFromAppointment:(SOGoAppointment *)_appointment {
740 iCalRecurrenceRule *rrule;
742 if ((self->startDate = [[_appointment startDate] copy]) == nil)
743 self->startDate = [[[NSCalendarDate date] hour:11 minute:0] copy];
744 if ((self->endDate = [[_appointment endDate] copy]) == nil) {
746 [[self->startDate hour:[self->startDate hourOfDay] + 1 minute:0] copy];
748 [self->startDate setTimeZone:[self viewTimeZone]];
749 [self->endDate setTimeZone:[self viewTimeZone]];
751 self->title = [[_appointment summary] copy];
752 self->location = [[_appointment location] copy];
753 self->comment = [[_appointment comment] copy];
754 self->priority = [[_appointment priority] copy];
755 self->categories = [[_appointment categories] retain];
756 self->organizer = [[_appointment organizer] retain];
757 self->participants = [[_appointment participants] retain];
758 self->resources = [[_appointment resources] retain];
760 s = [_appointment accessClass];
761 if(!s || [s isEqualToString:@"PUBLIC"])
762 [self setIsPrivate:NO];
764 [self setIsPrivate:YES]; /* we're possibly loosing information here */
767 rrule = [_appointment recurrenceRule];
768 [self adjustCycleControlsForRRule:rrule];
771 - (void)saveValuesIntoAppointment:(SOGoAppointment *)_appointment {
772 /* merge in form values */
773 NSArray *attendees, *lResources;
775 [_appointment setStartDate:[self aptStartDate]];
776 [_appointment setEndDate:[self aptEndDate]];
778 [_appointment setSummary:[self title]];
779 [_appointment setLocation:[self location]];
780 [_appointment setComment:[self comment]];
781 [_appointment setPriority:[self priority]];
782 [_appointment setCategories:[self categories]];
784 [_appointment setAccessClass:[self accessClass]];
785 [_appointment setTransparency:[self transparency]];
789 * Organizer is no form value, thus we MUST NOT change it
791 [_appointment setOrganizer:self->organizer];
793 attendees = [self participants];
794 lResources = [self resources];
795 if ([lResources count] > 0) {
796 attendees = ([attendees count] > 0)
797 ? [attendees arrayByAddingObjectsFromArray:lResources]
800 [_appointment setAttendees:attendees];
803 [_appointment setRecurrenceRule:[self rrule]];
806 - (void)loadValuesFromICalString:(NSString *)_ical {
807 SOGoAppointment *apt;
809 apt = [[SOGoAppointment alloc] initWithICalString:_ical];
810 [self loadValuesFromAppointment:apt];
814 /* contact editor compatibility */
816 - (void)setContentString:(NSString *)_s {
817 [self setICalString:_s];
819 - (NSString *)contentStringTemplate {
820 return [self iCalStringTemplate];
823 - (void)loadValuesFromContentString:(NSString *)_s {
824 [self loadValuesFromICalString:_s];
831 if (!self->organizer) return YES; // assume this is safe to do, right?
832 return [[self->organizer rfc822Email] isEqualToString:[self emailForUser]];
835 - (BOOL)canAccessApt {
836 return [self isMyApt];
840 return [self isMyApt];
844 /* conflict management */
846 - (BOOL)containsConflict:(SOGoAppointment *)_apt {
847 NSArray *attendees, *uids;
848 SOGoAppointmentFolder *groupCalendar;
853 [self logWithFormat:@"search from %@ to %@",
854 [_apt startDate], [_apt endDate]];
856 folder = [[self clientObject] container];
857 attendees = [_apt attendees];
858 uids = [folder uidsFromICalPersons:attendees];
859 if ([uids count] == 0) {
860 [self logWithFormat:@"Note: no UIDs selected."];
864 groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
865 inContext:[self context]];
866 [self debugWithFormat:@"group calendar: %@", groupCalendar];
868 if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
869 [self errorWithFormat:@"invalid folder to run freebusy query on!"];
873 infos = [groupCalendar fetchFreebusyInfosFrom:[_apt startDate]
875 [self debugWithFormat:@" process: %d events", [infos count]];
877 ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
878 andEndDateKey:@"endDate"];
879 ranges = [ranges arrayByCompactingContainedDateRanges];
880 [self debugWithFormat:@" blocked ranges: %@", ranges];
882 return [ranges count] != 0 ? YES : NO;
885 /* response generation */
887 - (NSString *)jsCode {
888 static NSString *script = \
889 @"function showElement(e, show) {\n"
890 @" e.style.visibility = show ? 'visible' : 'hidden';\n"
893 @"function selectHasCycle(sender) {\n"
894 @" var value = sender.selectedIndex;\n"
895 @" var show = (value != 0);\n"
896 @" var sel = document.getElementById('cycle_end_mode_selection');"
897 @" this.showElement(document.getElementById('cycle_end_label'), show);\n"
898 @" this.showElement(document.getElementById('cycle_end_mode'), show);\n"
899 @" this.selectCycleEnd(sel);\n"
901 @"function selectCycleEnd(sender) {\n"
902 @" var cycleEndUntil = document.getElementById('cycle_end_until');\n"
903 @" var value = sender.options[sender.selectedIndex].value;\n"
904 @" var show = (value == 'cycle_end_until');\n"
905 @" this.showElement(cycleEndUntil, show);\n"
912 - (NSString *)initialCycleVisibility {
913 if (![self hasCycle])
914 return @"visibility: hidden;";
915 return @"visibility: visible;";
918 - (NSString *)initialCycleEndUntilVisibility {
919 if ([self isCycleEndUntil])
920 return @"visibility: visible;";
921 return @"visibility: hidden;";
927 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
932 /* for testing only */
934 SOGoAppointment *apt;
937 req = [[self context] request];
938 apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
939 [self saveValuesIntoAppointment:apt];
940 content = [apt iCalString];
941 [self logWithFormat:@"%s -- iCal:\n%@",
948 - (id)defaultAction {
951 /* load iCalendar file */
953 // TODO: can't we use [clientObject contentAsString]?
954 ical = [[self clientObject] valueForKey:@"iCalString"];
955 if ([ical length] == 0) /* a new appointment */
956 ical = [self contentStringTemplate];
958 [self setContentString:ical];
959 [self loadValuesFromContentString:ical];
961 if (![self canEditApt]) {
962 /* TODO: we need proper ACLs */
963 return [self redirectToLocation:[self _completeURIForMethod:@"../view"]];
969 SOGoAppointment *apt;
974 if (![self isWriteableClientObject]) {
975 /* return 400 == Bad Request */
976 return [NSException exceptionWithHTTPStatus:400
977 reason:@"method cannot be invoked on "
978 @"the specified object"];
981 apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
985 s = [self labelForKey:@"Invalid iCal data!"];
986 [self setErrorText:s];
990 [self saveValuesIntoAppointment:apt];
991 p = [apt findParticipantWithEmail:[self emailForUser]];
993 [p setParticipationStatus:iCalPersonPartStatAccepted];
996 if ([self checkForConflicts]) {
997 if ([self containsConflict:apt]) {
1000 s = [self labelForKey:@"Conflicts found!"];
1001 [self setErrorText:s];
1006 content = [apt iCalString];
1007 [apt release]; apt = nil;
1009 if (content == nil) {
1012 s = [self labelForKey:@"Could not create iCal data!"];
1013 [self setErrorText:s];
1017 ex = [[self clientObject] saveContentString:content];
1019 [self setErrorText:[ex reason]];
1023 return [self redirectToLocation:[self _completeURIForMethod:@".."]];
1026 - (id)acceptAction {
1027 return [self acceptOrDeclineAction:YES];
1030 - (id)declineAction {
1031 return [self acceptOrDeclineAction:NO];
1034 - (id)acceptOrDeclineAction:(BOOL)_accept {
1035 SOGoAppointment *apt;
1037 NSString *iCal, *content;
1040 if (![self isWriteableClientObject]) {
1041 /* 400 == Bad Request */
1042 return [NSException exceptionWithHTTPStatus:400
1043 reason:@"method cannot be invoked on "
1044 @"the specified object"];
1046 iCal = [[self clientObject] valueForKey:@"iCalString"];
1047 apt = [[SOGoAppointment alloc] initWithICalString:iCal];
1049 /* 500 == Internal Server Error */
1050 return [NSException exceptionWithHTTPStatus:500
1051 reason:@"unable to parse appointment"];
1054 p = [apt findParticipantWithEmail:[self emailForUser]];
1056 /* 404 == Not found */
1057 return [NSException exceptionWithHTTPStatus:404
1058 reason:@"user does not participate in this "
1062 [p setParticipationStatus:iCalPersonPartStatAccepted];
1064 [p setParticipationStatus:iCalPersonPartStatDeclined];
1066 content = [apt iCalString];
1069 if (content == nil) {
1070 /* 500 == Internal Server Error */
1071 return [NSException exceptionWithHTTPStatus:500
1072 reason:@"Could not create iCalendar data ..."];
1075 ex = [[self clientObject] saveContentString:content];
1077 /* 500 == Internal Server Error */
1078 return [NSException exceptionWithHTTPStatus:500
1079 reason:[ex reason]];
1082 return [self redirectToLocation:[self _completeURIForMethod:@".."]];
1085 @end /* UIxAppointmentEditor */
1089 This is a pretty ugly (unfortunately necessary) hack to map our limited
1090 set of recurrence rules back to the popup list
1092 @interface iCalRecurrenceRule (UsedPrivates)
1094 - (NSString *)byDayList;
1095 @end /* iCalRecurrenceRule (UsedPrivates) */
1097 @implementation iCalRecurrenceRule (SOGoExtensions)
1099 - (NSString *)cycleRepresentationForSOGo {
1102 s = [NSMutableString stringWithCapacity:20];
1103 [s appendString:@"FREQ="];
1104 [s appendString:[self freq]];
1105 if ([self repeatInterval] != 1) {
1106 [s appendFormat:@";INTERVAL=%d", [self repeatInterval]];
1108 if (self->byDay.mask != 0) {
1109 [s appendString:@";BYDAY="];
1110 [s appendString:[self byDayList]];
1115 @end /* iCalRecurrenceRule (SOGoExtensions) */