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 <NGExtensions/NSCalendarDate+misc.h>
38 #import <NGExtensions/NSObject+Logs.h>
39 #import <NGExtensions/NSString+misc.h>
40 #import <NGObjWeb/NSException+HTTP.h>
41 #import <NGObjWeb/WORequest.h>
43 #import <SOGo/SOGoUser.h>
44 #import <SOGoUI/SOGoDateFormatter.h>
45 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
46 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
47 #import <SoObjects/Appointments/SOGoTaskObject.h>
48 #import <SoObjects/SOGo/NSString+Utilities.h>
50 #import "UIxComponent+Scheduler.h"
52 #import "UIxComponentEditor.h"
54 @implementation UIxComponentEditor
58 if ((self = [super init]))
61 [self setPrivacy: @"PUBLIC"];
62 [self setIsCycleEndNever];
66 attendeesEmails = nil;
76 [cycleUntilDate release];
86 [attendeesNames release];
87 [attendeesEmails release];
88 [calendarList release];
93 - (void) _loadAttendees
95 NSEnumerator *attendees;
96 iCalPerson *currentAttendee;
97 NSMutableString *names, *emails;
99 names = [NSMutableString new];
100 emails = [NSMutableString new];
102 attendees = [[component attendees] objectEnumerator];
103 currentAttendee = [attendees nextObject];
104 while (currentAttendee)
106 NSLog (@"currentCN: %@", [currentAttendee cn]);
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 /* warning: we use this method which will be triggered by the template system
124 when the page is instantiated, but we should find another and cleaner way of
125 doing this... for example, when the clientObject is set */
126 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
128 // iCalRecurrenceRule *rrule;
133 component = newComponent;
135 co = [self clientObject];
136 componentOwner = [co ownerInContext: nil];
138 ASSIGN (title, [component summary]);
139 ASSIGN (location, [component location]);
140 ASSIGN (comment, [component comment]);
141 ASSIGN (url, [[component url] absoluteString]);
142 ASSIGN (privacy, [component accessClass]);
143 ASSIGN (priority, [component priority]);
144 ASSIGN (status, [component status]);
145 ASSIGN (categories, [[component categories] commaSeparatedValues]);
146 ASSIGN (organizer, [component organizer]);
147 [self _loadAttendees];
150 // if ([component isRecurrent])
152 // rrule = [[component recurrenceRules] objectAtIndex: 0];
153 // [self adjustCycleControlsForRRule: rrule];
157 - (void) setSaveURL: (NSString *) newSaveURL
159 saveURL = newSaveURL;
162 - (NSString *) saveURL
169 - (void) setItem: (id) _item
171 ASSIGN (item, _item);
179 - (NSString *) itemPriorityText
181 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
184 - (NSString *) itemPrivacyText
188 tag = [[self clientObject] componentTag];
190 return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]];
193 - (NSString *) itemStatusText
195 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
198 - (void) setTitle: (NSString *) _value
200 ASSIGN (title, _value);
208 - (void) setUrl: (NSString *) _url
218 - (void) setAttendeesNames: (NSString *) newAttendeesNames
220 ASSIGN (attendeesNames, newAttendeesNames);
223 - (NSString *) attendeesNames
225 return attendeesNames;
228 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
230 ASSIGN (attendeesEmails, newAttendeesEmails);
233 - (NSString *) attendeesEmails
235 return attendeesEmails;
238 - (void) setLocation: (NSString *) _value
240 ASSIGN (location, _value);
243 - (NSString *) location
248 - (void) setComment: (NSString *) _value
250 ASSIGN (comment, _value);
253 - (NSString *) comment
258 - (NSArray *) categoryList
260 static NSArray *categoryItems = nil;
264 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
286 [categoryItems retain];
289 return categoryItems;
292 - (void) setCategories: (NSArray *) _categories
294 ASSIGN (categories, _categories);
297 - (NSArray *) categories
302 - (NSString *) itemCategoryText
304 return [self labelForKey:
305 [NSString stringWithFormat: @"category_%@", item]];
308 - (NSArray *) calendarList
310 SOGoAppointmentFolder *folder;
311 NSEnumerator *allCalendars;
312 NSDictionary *currentCalendar;
316 calendarList = [NSMutableArray new];
317 folder = [[self clientObject] container];
318 allCalendars = [[folder calendarFolders] objectEnumerator];
319 currentCalendar = [allCalendars nextObject];
320 while (currentCalendar)
322 if ([[currentCalendar objectForKey: @"active"] boolValue])
323 [calendarList addObject: currentCalendar];
324 currentCalendar = [allCalendars nextObject];
331 - (NSString *) calendarsFoldersList
335 calendars = [[self calendarList] valueForKey: @"folder"];
337 return [calendars componentsJoinedByString: @","];
340 - (NSString *) componentCalendar
347 - (NSArray *) priorities
353 static NSArray *priorities = nil;
357 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
364 - (void) setPriority: (NSString *) _priority
366 ASSIGN (priority, _priority);
369 - (NSString *) priority
374 - (NSArray *) privacyClasses
376 static NSArray *priorities = nil;
380 priorities = [NSArray arrayWithObjects: @"PUBLIC",
381 @"CONFIDENTIAL", @"PRIVATE", nil];
388 - (void) setPrivacy: (NSString *) _privacy
390 ASSIGN (privacy, _privacy);
393 - (NSString *) privacy
398 - (NSArray *) statusTypes
400 static NSArray *priorities = nil;
404 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
411 - (void) setStatus: (NSString *) _status
413 ASSIGN (status, _status);
416 - (NSString *) status
425 static NSArray *cycles = nil;
429 bundle = [NSBundle bundleForClass:[self class]];
430 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
431 NSAssert(path != nil, @"Cannot find cycles.plist!");
432 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
433 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
439 - (void) setCycle: (NSDictionary *) _cycle
441 ASSIGN (cycle, _cycle);
444 - (NSDictionary *) cycle
451 return ([cycle objectForKey: @"rule"] != nil);
454 - (NSString *) cycleLabel
458 key = [(NSDictionary *)item objectForKey: @"label"];
460 return [self labelForKey:key];
463 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
465 // NSCalendarDate *until;
467 // /* copy hour/minute/second from startDate */
468 // until = [_cycleUntilDate hour: [startDate hourOfDay]
469 // minute: [startDate minuteOfHour]
470 // second: [startDate secondOfMinute]];
471 // [until setTimeZone: [startDate timeZone]];
472 // ASSIGN (cycleUntilDate, until);
475 - (NSCalendarDate *) cycleUntilDate
477 return cycleUntilDate;
480 - (iCalRecurrenceRule *) rrule
483 iCalRecurrenceRule *rule;
485 if (![self hasCycle])
487 ruleRep = [cycle objectForKey: @"rule"];
488 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
490 if (cycleUntilDate && [self isCycleEndUntil])
491 [rule setUntilDate:cycleUntilDate];
496 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
499 // NSCalendarDate *until;
501 // c = [self cycleMatchingRRule:_rrule];
502 // [self setCycle:c];
504 // until = [[[_rrule untilDate] copy] autorelease];
506 // until = startDate;
508 // [self setIsCycleEndUntil];
510 // [until setTimeZone:[[self clientObject] userTimeZone]];
511 // [self setCycleUntilDate:until];
515 This method is necessary, because we have a fixed sets of cycles in the UI.
516 The model is able to represent arbitrary rules, however.
517 There SHOULD be a different UI, similar to iCal.app, to allow modelling
518 of more complex rules.
520 This method obviously cannot map all existing rules back to the fixed list
521 in cycles.plist. This should be fixed in a future version when interop
522 becomes more important.
524 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
531 return [[self cycles] objectAtIndex:0];
533 cycleRep = [_rrule versitString];
534 cycles = [self cycles];
535 count = [cycles count];
536 for (i = 1; i < count; i++) {
540 c = [cycles objectAtIndex:i];
541 cr = [c objectForKey: @"rule"];
542 if ([cr isEqualToString:cycleRep])
545 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
549 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
550 - (NSArray *) cycleEnds
552 static NSArray *ends = nil;
556 ends = [NSArray arrayWithObjects: @"cycle_end_never",
557 @"cycle_end_until", nil];
564 - (void) setCycleEnd: (NSString *) _cycleEnd
566 ASSIGN (cycleEnd, _cycleEnd);
569 - (NSString *) cycleEnd
574 - (BOOL) isCycleEndUntil
576 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
579 - (void) setIsCycleEndUntil
581 [self setCycleEnd: @"cycle_end_until"];
584 - (void) setIsCycleEndNever
586 [self setCycleEnd: @"cycle_end_never"];
590 - (NSFormatter *) titleDateFormatter
592 SOGoDateFormatter *fmt;
594 fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
596 [fmt setFullWeekdayNameAndDetails];
601 - (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];
624 - (BOOL) isWriteableClientObject
626 return [[self clientObject]
627 respondsToSelector: @selector(saveContentString:)];
630 - (BOOL) containsConflict: (id) _component
632 [self subclassResponsibility: _cmd];
640 - (iCalPerson *) getOrganizer
645 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
646 p = [[[iCalPerson alloc] init] autorelease];
647 [p setEmail:emailProp];
648 [p setCn:[self cnForUser]];
653 - (BOOL) isMyComponent
655 return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
658 - (BOOL) canEditComponent
660 return [self isMyComponent];
663 /* response generation */
665 - (NSString *) initialCycleVisibility
667 return ([self hasCycle]
668 ? @"visibility: visible;"
669 : @"visibility: hidden;");
672 - (NSString *) initialCycleEndUntilVisibility {
673 return ([self isCycleEndUntil]
674 ? @"visibility: visible;"
675 : @"visibility: hidden;");
678 // - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
682 // s = [self iCalParticipantsStringFromQueryParameters];
683 // return [s stringByAppendingString:
684 // [self iCalResourcesStringFromQueryParameters]];
687 // - (NSString *) iCalParticipantsStringFromQueryParameters
689 // static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
691 // return [self iCalStringFromQueryParameter: @"ps"
692 // format: iCalParticipantString];
695 // - (NSString *) iCalResourcesStringFromQueryParameters
697 // static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
699 // return [self iCalStringFromQueryParameter: @"rs"
700 // format: iCalResourceString];
703 // - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
704 // format: (NSString *) _format
706 // AgenorUserManager *um;
707 // NSMutableString *iCalRep;
710 // um = [AgenorUserManager sharedUserManager];
711 // iCalRep = (NSMutableString *)[NSMutableString string];
712 // s = [self queryParameterForKey:_qp];
713 // if(s && [s length] > 0) {
715 // unsigned i, count;
717 // es = [s componentsSeparatedByString: @","];
718 // count = [es count];
719 // for(i = 0; i < count; i++) {
720 // NSString *email, *cn;
722 // email = [es objectAtIndex:i];
723 // cn = [um getCNForUID:[um getUIDForEmail:email]];
724 // [iCalRep appendFormat:_format, cn, email];
730 - (NSException *) validateObjectForStatusChange
734 co = [self clientObject];
735 if (![co respondsToSelector: @selector(changeParticipationStatus:)])
736 return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
738 @"method cannot be invoked on the specified object"];
743 /* contact editor compatibility */
745 - (NSString *) urlButtonClasses
752 classes = @"button _disabled";
757 - (void) _handleAttendeesEdition
759 NSArray *names, *emails;
760 NSMutableArray *newAttendees;
761 unsigned int count, max;
762 NSString *currentEmail;
763 iCalPerson *currentAttendee;
765 newAttendees = [NSMutableArray new];
766 if ([attendeesNames length] > 0)
768 names = [attendeesNames componentsSeparatedByString: @","];
769 emails = [attendeesEmails componentsSeparatedByString: @","];
770 max = [emails count];
771 for (count = 0; count < max; count++)
773 currentEmail = [emails objectAtIndex: count];
774 currentAttendee = [component findParticipantWithEmail: currentEmail];
775 if (!currentAttendee)
777 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
778 [currentAttendee setCn: [names objectAtIndex: count]];
779 [currentAttendee setEmail: currentEmail];
780 [currentAttendee setRole: @"REQ-PARTICIPANT"];
782 setParticipationStatus: iCalPersonPartStatNeedsAction];
784 [newAttendees addObject: currentAttendee];
788 [component setAttendees: newAttendees];
789 [newAttendees release];
792 - (void) _handleOrganizer
794 NSString *organizerEmail;
795 SOGoUser *activeUser;
797 organizerEmail = [[component organizer] email];
798 if ([organizerEmail length] == 0)
800 if ([[component attendees] count] > 0)
802 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
803 activeUser = [context activeUser];
804 [organizer setCn: [activeUser cn]];
805 [organizer setEmail: [activeUser primaryEmail]];
806 [component setOrganizer: organizer];
811 if ([[component attendees] count] == 0)
813 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
814 [component setOrganizer: organizer];
819 - (void) takeValuesFromRequest: (WORequest *) _rq
820 inContext: (WOContext *) _ctx
823 SOGoCalendarComponent *clientObject;
825 [super takeValuesFromRequest: _rq inContext: _ctx];
827 now = [NSCalendarDate calendarDate];
828 [component setSummary: title];
829 [component setLocation: location];
830 [component setComment: comment];
831 [component setUrl: url];
832 [component setAccessClass: privacy];
833 [self _handleAttendeesEdition];
834 [self _handleOrganizer];
835 clientObject = [self clientObject];
836 if ([clientObject isNew])
838 [component setUid: [clientObject nameInContainer]];
839 [component setCreated: now];
840 [component setTimeStampAsDate: now];
841 [component setPriority: @"0"];
843 [component setLastModified: now];
846 - (NSString *) toolbar
848 SOGoUser *currentUser;
849 SOGoCalendarComponent *clientObject;
850 NSString *toolbarFilename;
852 iCalPersonPartStat participationStatus;
854 clientObject = [self clientObject];
855 currentUser = [[self context] activeUser];
856 if ([clientObject isOrganizerOrOwner: currentUser])
858 if ([[clientObject componentTag] isEqualToString: @"vevent"])
859 toolbarFilename = @"SOGoAppointmentObject.toolbar";
861 toolbarFilename = @"SOGoTaskObject.toolbar";
865 /* Lightning does not manage participation status within tasks */
866 person = [clientObject participant: currentUser];
869 participationStatus = [person participationStatus];
870 if (participationStatus == iCalPersonPartStatAccepted)
871 toolbarFilename = @"SOGoAppointmentObjectDecline.toolbar";
872 else if (participationStatus == iCalPersonPartStatDeclined)
873 toolbarFilename = @"SOGoAppointmentObjectAccept.toolbar";
875 toolbarFilename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
878 toolbarFilename = @"SOGoComponentClose.toolbar";
881 return toolbarFilename;