2 Copyright (C) 2004-2005 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
22 #include <SOGoUI/UIxComponent.h>
28 @class iCalRecurrenceRule;
29 @class SOGoAppointment;
31 @interface UIxAppointmentEditor : UIxComponent
37 /* individual values */
38 NSCalendarDate *startDate;
39 NSCalendarDate *endDate;
40 NSCalendarDate *cycleUntilDate;
44 iCalPerson *organizer;
45 NSArray *participants; /* array of iCalPerson's */
46 NSArray *resources; /* array of iCalPerson's */
49 NSString *accessClass;
50 BOOL isPrivate; /* default: NO */
51 BOOL checkForConflicts; /* default: NO */
56 - (NSString *)iCalStringTemplate;
57 - (NSString *)iCalString;
59 - (void)setIsPrivate:(BOOL)_yn;
60 - (void)setAccessClass:(NSString *)_class;
62 - (void)setCheckForConflicts:(BOOL)_checkForConflicts;
63 - (BOOL)checkForConflicts;
66 - (iCalRecurrenceRule *)rrule;
67 - (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule;
68 - (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule;
70 - (BOOL)isCycleEndUntil;
71 - (void)setIsCycleEndUntil;
72 - (void)setIsCycleEndNever;
74 - (NSString *)_completeURIForMethod:(NSString *)_method;
76 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
77 treatAsResource:(BOOL)_isResource;
79 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters;
80 - (NSString *)iCalParticipantsStringFromQueryParameters;
81 - (NSString *)iCalResourcesStringFromQueryParameters;
82 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
83 format:(NSString *)_format;
84 - (NSString *)iCalOrganizerString;
86 - (id)acceptOrDeclineAction:(BOOL)_accept;
91 #include <NGiCal/NGiCal.h>
92 #include <NGExtensions/NGCalendarDateRange.h>
93 #include <SOGoUI/SOGoDateFormatter.h>
94 #include <SOGo/SOGoAppointment.h>
95 #include <SOGo/AgenorUserManager.h>
96 #include <Appointments/SOGoAppointmentFolder.h>
97 #include <Appointments/SOGoAppointmentObject.h>
98 #include "iCalPerson+UIx.h"
99 #include "UIxComponent+Agenor.h"
101 @interface iCalRecurrenceRule (SOGoExtensions)
102 - (NSString *)cycleRepresentationForSOGo;
105 @interface NSDate(UsedPrivates)
106 - (NSString *)icalString; // TODO: this is in NGiCal
109 @implementation UIxAppointmentEditor
112 return [super version] + 0 /* v2 */;
116 NSAssert2([super version] == 2,
117 @"invalid superclass (%@) version %i !",
118 NSStringFromClass([self superclass]), [super version]);
124 [self setIsPrivate:NO];
125 [self setCheckForConflicts:NO];
126 [self setIsCycleEndNever];
132 [self->iCalString release];
133 [self->errorText release];
134 [self->item release];
136 [self->startDate release];
137 [self->endDate release];
138 [self->cycleUntilDate release];
139 [self->title release];
140 [self->location release];
141 [self->organizer release];
142 [self->comment release];
143 [self->participants release];
144 [self->resources release];
145 [self->priority release];
146 [self->categories release];
147 [self->accessClass release];
148 [self->cycle release];
149 [self->cycleEnd release];
155 - (void)setItem:(id)_item {
156 ASSIGN(self->item, _item);
162 - (void)setErrorText:(NSString *)_txt {
163 ASSIGNCOPY(self->errorText, _txt);
165 - (NSString *)errorText {
166 return self->errorText;
168 - (BOOL)hasErrorText {
169 return [self->errorText length] > 0 ? YES : NO;
172 - (NSFormatter *)titleDateFormatter {
173 SOGoDateFormatter *fmt;
175 fmt = [[[SOGoDateFormatter alloc] initWithLocale:[self locale]] autorelease];
176 [fmt setFullWeekdayNameAndDetails];
180 - (void)setAptStartDate:(NSCalendarDate *)_date {
181 ASSIGN(self->startDate, _date);
183 - (NSCalendarDate *)aptStartDate {
184 return self->startDate;
186 - (void)setAptEndDate:(NSCalendarDate *)_date {
187 ASSIGN(self->endDate, _date);
189 - (NSCalendarDate *)aptEndDate {
190 return self->endDate;
193 - (void)setTitle:(NSString *)_value {
194 ASSIGNCOPY(self->title, _value);
196 - (NSString *)title {
199 - (void)setLocation:(NSString *)_value {
200 ASSIGNCOPY(self->location, _value);
202 - (NSString *)location {
203 return self->location;
205 - (void)setComment:(NSString *)_value {
206 ASSIGNCOPY(self->comment, _value);
208 - (NSString *)comment {
209 return self->comment;
212 - (void)setParticipants:(NSArray *)_parts {
213 ASSIGN(self->participants, _parts);
215 - (NSArray *)participants {
216 return self->participants;
218 - (void)setResources:(NSArray *)_res {
219 ASSIGN(self->resources, _res);
221 - (NSArray *)resources {
222 return self->resources;
227 - (NSArray *)priorities {
232 static NSArray *priorities = nil;
235 priorities = [[NSArray arrayWithObjects:@"0", @"5", @"1", nil] retain];
239 - (NSString *)itemPriorityText {
242 key = [NSString stringWithFormat:@"prio_%@", self->item];
243 return [self labelForKey:key];
246 - (void)setPriority:(NSString *)_priority {
247 ASSIGN(self->priority, _priority);
249 - (NSString *)priority {
250 return self->priority;
256 - (NSArray *)categoryItems {
257 // TODO: make this configurable?
259 Tasks categories will be modified as follow :
260 – by default (a simple logo or no logo at all),
267 static NSArray *categoryItems = nil;
269 if (!categoryItems) {
270 categoryItems = [[NSArray arrayWithObjects:@"APPOINTMENT",
277 return categoryItems;
280 - (NSString *)itemCategoryText {
281 return [self labelForKey:self->item];
284 - (void)setCategories:(NSArray *)_categories {
285 ASSIGN(self->categories, _categories);
287 - (NSArray *)categories {
288 return self->categories;
294 - (NSArray *)accessClassItems {
295 static NSArray classItems = nil;
298 return [[NSArray arrayWithObjects:@"PUBLIC", @"PRIVATE", nil] retain];
304 - (void)setAccessClass:(NSString *)_class {
305 ASSIGN(self->accessClass, _class);
307 - (NSString *)accessClass {
308 return self->accessClass;
311 - (void)setIsPrivate:(BOOL)_yn {
313 [self setAccessClass:@"PRIVATE"];
315 [self setAccessClass:@"PUBLIC"];
316 self->isPrivate = _yn;
319 return self->isPrivate;
322 - (void)setCheckForConflicts:(BOOL)_checkForConflicts {
323 self->checkForConflicts = _checkForConflicts;
325 - (BOOL)checkForConflicts {
326 return self->checkForConflicts;
329 - (NSArray *)cycles {
330 static NSArray *cycles = nil;
336 bundle = [NSBundle bundleForClass:[self class]];
337 path = [bundle pathForResource:@"cycles" ofType:@"plist"];
338 NSAssert(path != nil, @"Cannot find cycles.plist!");
339 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
340 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
345 - (void)setCycle:(NSDictionary *)_cycle {
346 ASSIGN(self->cycle, _cycle);
348 - (NSDictionary *)cycle {
352 [self debugWithFormat:@"cycle: %@", self->cycle];
353 if (![self->cycle objectForKey:@"rule"])
357 - (NSString *)cycleLabel {
360 key = [(NSDictionary *)self->item objectForKey:@"label"];
361 return [self labelForKey:key];
365 - (void)setCycleUntilDate:(NSCalendarDate *)_cycleUntilDate {
366 NSCalendarDate *until;
368 /* copy hour/minute/second from startDate */
369 until = [_cycleUntilDate hour:[self->startDate hourOfDay]
370 minute:[self->startDate minuteOfHour]
371 second:[self->startDate secondOfMinute]];
372 [until setTimeZone:[self->startDate timeZone]];
373 ASSIGN(self->cycleUntilDate, until);
375 - (NSCalendarDate *)cycleUntilDate {
376 return self->cycleUntilDate;
379 - (iCalRecurrenceRule *)rrule {
381 iCalRecurrenceRule *rule;
383 if (![self hasCycle])
385 ruleRep = [self->cycle objectForKey:@"rule"];
386 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
388 if (self->cycleUntilDate && [self isCycleEndUntil])
389 [rule setUntilDate:self->cycleUntilDate];
393 - (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule {
395 NSCalendarDate *until;
397 c = [self cycleMatchingRRule:_rrule];
400 until = [[[_rrule untilDate] copy] autorelease];
402 until = self->startDate;
404 [self setIsCycleEndUntil];
406 [until setTimeZone:[self viewTimeZone]];
407 [self setCycleUntilDate:until];
411 This method is necessary, because we have a fixed sets of cycles in the UI.
412 The model is able to represent arbitrary rules, however.
413 There SHOULD be a different UI, similar to iCal.app, to allow modelling
414 of more complex rules.
416 This method obviously cannot map all existing rules back to the fixed list
417 in cycles.plist. This should be fixed in a future version when interop
418 becomes more important.
420 - (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule {
426 return [[self cycles] objectAtIndex:0];
428 cycleRep = [_rrule cycleRepresentationForSOGo];
429 cycles = [self cycles];
430 count = [cycles count];
431 for (i = 1; i < count; i++) {
435 c = [cycles objectAtIndex:i];
436 cr = [c objectForKey:@"rule"];
437 if ([cr isEqualToString:cycleRep])
440 [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
444 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
445 - (NSArray *)cycleEnds {
446 static NSArray *ends = nil;
449 ends = [[NSArray alloc] initWithObjects:@"cycle_end_never",
456 - (void)setCycleEnd:(NSString *)_cycleEnd {
457 ASSIGNCOPY(self->cycleEnd, _cycleEnd);
459 - (NSString *)cycleEnd {
460 return self->cycleEnd;
462 - (BOOL)isCycleEndUntil {
463 return (self->cycleEnd &&
464 [self->cycleEnd isEqualToString:@"cycle_end_until"]);
466 - (void)setIsCycleEndUntil {
467 [self setCycleEnd:@"cycle_end_until"];
469 - (void)setIsCycleEndNever {
470 [self setCycleEnd:@"cycle_end_never"];
475 - (NSString *)transparency {
476 return @"TRANSPARENT";
482 - (void)setICalString:(NSString *)_s {
483 ASSIGNCOPY(self->iCalString, _s);
485 - (NSString *)iCalString {
486 return self->iCalString;
489 - (NSString *)iCalStringTemplate {
490 static NSString *iCalStringTemplate = \
491 @"BEGIN:VCALENDAR\r\n"
492 @"METHOD:REQUEST\r\n"
493 @"PRODID:OpenGroupware.org SOGo 0.9\r\n"
498 @"STATUS:CONFIRMED\r\n" /* confirmed by default */
505 @"%@" /* organizer */
506 @"%@" /* participants and resources */
510 NSCalendarDate *lStartDate, *lEndDate;
511 NSString *template, *s;
514 s = [self queryParameterForKey:@"dur"];
515 if(s && [s length] > 0) {
516 minutes = [s intValue];
521 lStartDate = [self selectedDate];
522 lEndDate = [lStartDate dateByAddingYears:0 months:0 days:0
523 hours:0 minutes:minutes seconds:0];
525 s = [self iCalParticipantsAndResourcesStringFromQueryParameters];
526 template = [NSString stringWithFormat:iCalStringTemplate,
527 [[self clientObject] nameInContainer],
528 [[NSCalendarDate date] icalString],
529 [lStartDate icalString],
530 [lEndDate icalString],
532 [self iCalOrganizerString],
537 - (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters {
540 s = [self iCalParticipantsStringFromQueryParameters];
541 return [s stringByAppendingString:
542 [self iCalResourcesStringFromQueryParameters]];
545 - (NSString *)iCalParticipantsStringFromQueryParameters {
546 static NSString *iCalParticipantString = \
547 @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
549 return [self iCalStringFromQueryParameter:@"ps"
550 format:iCalParticipantString];
553 - (NSString *)iCalResourcesStringFromQueryParameters {
554 static NSString *iCalResourceString = \
555 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
557 return [self iCalStringFromQueryParameter:@"rs"
558 format:iCalResourceString];
561 - (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
562 format:(NSString *)_format
564 AgenorUserManager *um;
565 NSMutableString *iCalRep;
568 um = [AgenorUserManager sharedUserManager];
569 iCalRep = (NSMutableString *)[NSMutableString string];
570 s = [self queryParameterForKey:_qp];
571 if(s && [s length] > 0) {
575 es = [s componentsSeparatedByString:@","];
577 for(i = 0; i < count; i++) {
578 NSString *email, *cn;
580 email = [es objectAtIndex:i];
581 cn = [um getCNForUID:[um getUIDForEmail:email]];
582 [iCalRep appendFormat:_format, cn, email];
588 - (NSString *)iCalOrganizerString {
589 static NSString *fmt = @"ORGANIZER;CN=\"%@\":mailto:%@\r\n";
590 return [NSString stringWithFormat:fmt,
592 [self emailForUser]];
596 - (iCalPerson *)getOrganizer {
600 emailProp = [@"mailto:" stringByAppendingString:[self emailForUser]];
601 p = [[[iCalPerson alloc] init] autorelease];
602 [p setEmail:emailProp];
603 [p setCn:[self cnForUser]];
611 - (NSString *)_completeURIForMethod:(NSString *)_method {
615 uri = [[[self context] request] uri];
617 /* first: identify query parameters */
618 r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
620 uri = [uri substringToIndex:r.location];
622 /* next: append trailing slash */
623 if (![uri hasSuffix:@"/"])
624 uri = [uri stringByAppendingString:@"/"];
626 /* next: append method */
627 uri = [uri stringByAppendingString:_method];
629 /* next: append query parameters */
630 return [self completeHrefForMethod:uri];
637 This method creates a unique ID and redirects to the "edit" method on the
639 It is actually a folder method and should be defined on the folder.
641 Note: 'clientObject' is the SOGoAppointmentFolder!
642 Update: remember that there are group folders as well.
644 NSString *uri, *objectId, *method, *ps;
646 objectId = [NSClassFromString(@"SOGoAppointmentFolder")
647 globallyUniqueObjectId];
648 if ([objectId length] == 0) {
649 return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
650 reason:@"could not create a unique ID"];
653 method = [NSString stringWithFormat:@"Calendar/%@/edit", objectId];
654 method = [[self userFolderPath] stringByAppendingPathComponent:method];
656 /* check if participants have already been provided */
657 ps = [[[self context] request] formValueForKey:@"ps"];
659 [self setQueryParameter:ps forKey:@"ps"];
661 else if ([[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
662 AgenorUserManager *um;
664 NSMutableArray *emails;
667 /* add all current calendarUIDs as default participants */
669 um = [AgenorUserManager sharedUserManager];
670 uids = [[self clientObject] calendarUIDs];
671 count = [uids count];
672 emails = [NSMutableArray arrayWithCapacity:count];
674 for (i = 0; i < count; i++) {
677 email = [um getEmailForUID:[uids objectAtIndex:i]];
679 [emails addObject:email];
681 ps = [emails componentsJoinedByString:@","];
682 [self setQueryParameter:ps forKey:@"ps"];
684 uri = [self completeHrefForMethod:method];
685 return [self redirectToLocation:uri];
690 /* returned dates are in GMT */
691 - (NSCalendarDate *)_dateFromString:(NSString *)_str {
692 NSCalendarDate *date;
694 date = [NSCalendarDate dateWithString:_str
695 calendarFormat:@"%Y-%m-%d %H:%M %Z"];
696 [date setTimeZone:[self backendTimeZone]];
700 - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
701 treatAsResource:(BOOL)_isResource
704 NSMutableArray *result;
706 count = [_values count];
707 result = [[NSMutableArray alloc] initWithCapacity:count];
708 for (i = 0; i < count; i++) {
709 NSString *pString, *email, *cn;
713 pString = [_values objectAtIndex:i];
714 if ([pString length] == 0)
717 /* delimiter between email and cn */
718 r = [pString rangeOfString:@";"];
720 email = [pString substringToIndex:r.location];
721 cn = (r.location + 1 < [pString length])
722 ? [pString substringFromIndex:r.location + 1]
731 AgenorUserManager *um = [AgenorUserManager sharedUserManager];
732 cn = [um getCNForUID:[um getUIDForEmail:email]];
735 p = [[iCalPerson alloc] init];
736 [p setEmail:[@"mailto:" stringByAppendingString:email]];
737 if ([cn isNotNull]) [p setCn:cn];
739 /* see RFC2445, sect. 4.2.16 for details */
740 [p setRole:_isResource ? @"NON-PARTICIPANT" : @"REQ-PARTICIPANT"];
741 [result addObject:p];
744 return [result autorelease];
747 - (BOOL)isWriteableClientObject {
748 return [[self clientObject]
749 respondsToSelector:@selector(saveContentString:)];
752 - (NSException *)validateObjectForStatusChange {
756 co = [self clientObject];
757 ok = [co respondsToSelector:@selector(changeParticipationStatus:inContext:)];
759 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
761 @"method cannot be invoked on the specified object"];
766 - (void)loadValuesFromAppointment:(SOGoAppointment *)_appointment {
768 iCalRecurrenceRule *rrule;
770 if ((self->startDate = [[_appointment startDate] copy]) == nil)
771 self->startDate = [[[NSCalendarDate date] hour:11 minute:0] copy];
772 if ((self->endDate = [[_appointment endDate] copy]) == nil) {
774 [[self->startDate hour:[self->startDate hourOfDay] + 1 minute:0] copy];
776 [self->startDate setTimeZone:[self viewTimeZone]];
777 [self->endDate setTimeZone:[self viewTimeZone]];
779 self->title = [[_appointment summary] copy];
780 self->location = [[_appointment location] copy];
781 self->comment = [[_appointment comment] copy];
782 self->priority = [[_appointment priority] copy];
783 self->categories = [[_appointment categories] retain];
784 self->organizer = [[_appointment organizer] retain];
785 self->participants = [[_appointment participants] retain];
786 self->resources = [[_appointment resources] retain];
788 s = [_appointment accessClass];
789 if(!s || [s isEqualToString:@"PUBLIC"])
790 [self setIsPrivate:NO];
792 [self setIsPrivate:YES]; /* we're possibly loosing information here */
795 rrule = [_appointment recurrenceRule];
796 [self adjustCycleControlsForRRule:rrule];
799 - (void)saveValuesIntoAppointment:(SOGoAppointment *)_appointment {
800 /* merge in form values */
801 NSArray *attendees, *lResources;
803 [_appointment setStartDate:[self aptStartDate]];
804 [_appointment setEndDate:[self aptEndDate]];
806 [_appointment setSummary:[self title]];
807 [_appointment setLocation:[self location]];
808 [_appointment setComment:[self comment]];
809 [_appointment setPriority:[self priority]];
810 [_appointment setCategories:[self categories]];
812 [_appointment setAccessClass:[self accessClass]];
813 [_appointment setTransparency:[self transparency]];
818 Organizer is no form value, thus we MUST NOT change it
820 [_appointment setOrganizer:self->organizer];
822 attendees = [self participants];
823 lResources = [self resources];
824 if ([lResources count] > 0) {
825 attendees = ([attendees count] > 0)
826 ? [attendees arrayByAddingObjectsFromArray:lResources]
829 [_appointment setAttendees:attendees];
832 [_appointment setRecurrenceRule:[self rrule]];
835 - (void)loadValuesFromICalString:(NSString *)_ical {
836 SOGoAppointment *apt;
838 apt = [[SOGoAppointment alloc] initWithICalString:_ical];
839 [self loadValuesFromAppointment:apt];
843 /* contact editor compatibility */
845 - (void)setContentString:(NSString *)_s {
846 [self setICalString:_s];
848 - (NSString *)contentStringTemplate {
849 return [self iCalStringTemplate];
852 - (void)loadValuesFromContentString:(NSString *)_s {
853 [self loadValuesFromICalString:_s];
860 if (self->organizer == nil)
861 return YES; // assume this is safe to do, right?
863 // TODO: this should check a set of emails against the SoUser
864 return [[self->organizer rfc822Email] isEqualToString:[self emailForUser]];
867 - (BOOL)canAccessApt {
868 return [self isMyApt];
872 return [self isMyApt];
876 /* conflict management */
878 - (BOOL)containsConflict:(SOGoAppointment *)_apt {
879 NSArray *attendees, *uids;
880 SOGoAppointmentFolder *groupCalendar;
885 [self logWithFormat:@"search from %@ to %@",
886 [_apt startDate], [_apt endDate]];
888 folder = [[self clientObject] container];
889 attendees = [_apt attendees];
890 uids = [folder uidsFromICalPersons:attendees];
891 if ([uids count] == 0) {
892 [self logWithFormat:@"Note: no UIDs selected."];
896 groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
897 inContext:[self context]];
898 [self debugWithFormat:@"group calendar: %@", groupCalendar];
900 if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
901 [self errorWithFormat:@"invalid folder to run freebusy query on!"];
905 infos = [groupCalendar fetchFreebusyInfosFrom:[_apt startDate]
907 [self debugWithFormat:@" process: %d events", [infos count]];
909 ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
910 andEndDateKey:@"endDate"];
911 ranges = [ranges arrayByCompactingContainedDateRanges];
912 [self debugWithFormat:@" blocked ranges: %@", ranges];
914 return [ranges count] != 0 ? YES : NO;
917 /* response generation */
919 - (NSString *)jsCode {
920 static NSString *script = \
921 @"function showElement(e, show) {\n"
922 @" e.style.visibility = show ? 'visible' : 'hidden';\n"
925 @"function selectHasCycle(sender) {\n"
926 @" var value = sender.selectedIndex;\n"
927 @" var show = (value != 0);\n"
928 @" var sel = document.getElementById('cycle_end_mode_selection');"
929 @" this.showElement(document.getElementById('cycle_end_label'), show);\n"
930 @" this.showElement(document.getElementById('cycle_end_mode'), show);\n"
931 @" this.selectCycleEnd(sel);\n"
933 @"function selectCycleEnd(sender) {\n"
934 @" var cycleEndUntil = document.getElementById('cycle_end_until');\n"
935 @" var value = sender.options[sender.selectedIndex].value;\n"
936 @" var show = (value == 'cycle_end_until');\n"
937 @" this.showElement(cycleEndUntil, show);\n"
944 - (NSString *)initialCycleVisibility {
945 if (![self hasCycle])
946 return @"visibility: hidden;";
947 return @"visibility: visible;";
950 - (NSString *)initialCycleEndUntilVisibility {
951 if ([self isCycleEndUntil])
952 return @"visibility: visible;";
953 return @"visibility: hidden;";
959 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
964 /* for testing only */
966 SOGoAppointment *apt;
969 req = [[self context] request];
970 apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
971 [self saveValuesIntoAppointment:apt];
972 content = [apt iCalString];
973 [self logWithFormat:@"%s -- iCal:\n%@",
980 - (id<WOActionResults>)defaultAction {
983 /* load iCalendar file */
985 // TODO: can't we use [clientObject contentAsString]?
986 ical = [[self clientObject] valueForKey:@"iCalString"];
987 if ([ical length] == 0) /* a new appointment */
988 ical = [self contentStringTemplate];
990 [self setContentString:ical];
991 [self loadValuesFromContentString:ical];
993 if (![self canEditApt]) {
994 /* TODO: we need proper ACLs */
995 return [self redirectToLocation:[self _completeURIForMethod:@"../view"]];
1001 SOGoAppointment *apt;
1006 if (![self isWriteableClientObject]) {
1007 /* return 400 == Bad Request */
1008 return [NSException exceptionWithHTTPStatus:400
1009 reason:@"method cannot be invoked on "
1010 @"the specified object"];
1013 apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
1017 s = [self labelForKey:@"Invalid iCal data!"];
1018 [self setErrorText:s];
1022 [self saveValuesIntoAppointment:apt];
1023 p = [apt findParticipantWithEmail:[self emailForUser]];
1025 [p setParticipationStatus:iCalPersonPartStatAccepted];
1028 if ([self checkForConflicts]) {
1029 if ([self containsConflict:apt]) {
1032 s = [self labelForKey:@"Conflicts found!"];
1033 [self setErrorText:s];
1038 content = [apt iCalString];
1039 [apt release]; apt = nil;
1041 if (content == nil) {
1044 s = [self labelForKey:@"Could not create iCal data!"];
1045 [self setErrorText:s];
1049 ex = [[self clientObject] saveContentString:content];
1051 [self setErrorText:[ex reason]];
1055 return [self redirectToLocation:[self _completeURIForMethod:@".."]];
1058 - (id)acceptAction {
1059 return [self acceptOrDeclineAction:YES];
1062 - (id)declineAction {
1063 return [self acceptOrDeclineAction:NO];
1066 // TODO: add tentatively
1068 - (id)acceptOrDeclineAction:(BOOL)_accept {
1069 // TODO: this should live in the SoObjects
1072 if ((ex = [self validateObjectForStatusChange]) != nil)
1075 ex = [[self clientObject] changeParticipationStatus:
1076 _accept ? @"ACCEPTED" : @"DECLINED"
1077 inContext:[self context]];
1078 if (ex != nil) return ex;
1080 return [self redirectToLocation:[self _completeURIForMethod:@"../view"]];
1083 @end /* UIxAppointmentEditor */