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/NSString.h>
28 #import <Foundation/NSUserDefaults.h>
29 #import <Foundation/NSURL.h>
31 #import <NGCards/iCalPerson.h>
32 #import <NGCards/iCalRepeatableEntityObject.h>
33 #import <NGCards/iCalRecurrenceRule.h>
34 #import <NGCards/NSString+NGCards.h>
35 #import <NGCards/NSCalendarDate+NGCards.h>
36 #import <NGExtensions/NSCalendarDate+misc.h>
37 #import <NGExtensions/NSObject+Logs.h>
38 #import <NGExtensions/NSString+misc.h>
39 #import <NGObjWeb/NSException+HTTP.h>
40 #import <NGObjWeb/WORequest.h>
42 #import <SOGo/AgenorUserManager.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>
49 #import "UIxComponent+Agenor.h"
51 #import "UIxComponentEditor.h"
53 @implementation UIxComponentEditor
57 if ((self = [super init]))
60 [self setPrivacy: @"PUBLIC"];
61 [self setIsCycleEndNever];
65 attendeesEmails = nil;
75 [cycleUntilDate release];
85 [attendeesNames release];
86 [attendeesEmails release];
87 [calendarList release];
92 - (void) _loadAttendees
94 NSEnumerator *attendees;
95 iCalPerson *currentAttendee;
96 NSMutableString *names, *emails;
98 names = [NSMutableString new];
99 emails = [NSMutableString new];
101 attendees = [[component attendees] objectEnumerator];
102 currentAttendee = [attendees nextObject];
103 while (currentAttendee)
105 NSLog (@"currentCN: %@", [currentAttendee cn]);
106 [names appendFormat: @"%@,", [currentAttendee cn]];
107 [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
108 currentAttendee = [attendees nextObject];
111 if ([names length] > 0)
113 ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
114 ASSIGN (attendeesEmails,
115 [emails substringToIndex: [emails length] - 1]);
122 /* warning: we use this method which will be triggered by the template system
123 when the page is instantiated, but we should find another and cleaner way of
124 doing this... for example, when the clientObject is set */
125 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
127 // iCalRecurrenceRule *rrule;
132 component = newComponent;
134 co = [self clientObject];
135 componentOwner = [co ownerInContext: nil];
137 ASSIGN (title, [component summary]);
138 ASSIGN (location, [component location]);
139 ASSIGN (comment, [component comment]);
140 ASSIGN (url, [[component url] absoluteString]);
141 ASSIGN (privacy, [component accessClass]);
142 ASSIGN (priority, [component priority]);
143 ASSIGN (status, [component status]);
144 ASSIGN (categories, [[component categories] commaSeparatedValues]);
145 ASSIGN (organizer, [component organizer]);
146 [self _loadAttendees];
149 // if ([component isRecurrent])
151 // rrule = [[component recurrenceRules] objectAtIndex: 0];
152 // [self adjustCycleControlsForRRule: rrule];
156 - (void) setSaveURL: (NSString *) newSaveURL
158 saveURL = newSaveURL;
161 - (NSString *) saveURL
168 - (void) setItem: (id) _item
170 ASSIGN (item, _item);
178 - (NSString *) itemPriorityText
180 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
183 - (NSString *) itemPrivacyText
185 return [self labelForKey: [NSString stringWithFormat: @"privacy_%@", item]];
188 - (NSString *) itemStatusText
190 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
193 - (void) setTitle: (NSString *) _value
195 ASSIGN (title, _value);
203 - (void) setUrl: (NSString *) _url
213 - (void) setAttendeesNames: (NSString *) newAttendeesNames
215 ASSIGN (attendeesNames, newAttendeesNames);
218 - (NSString *) attendeesNames
220 return attendeesNames;
223 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
225 ASSIGN (attendeesEmails, newAttendeesEmails);
228 - (NSString *) attendeesEmails
230 return attendeesEmails;
233 - (void) setLocation: (NSString *) _value
235 ASSIGN (location, _value);
238 - (NSString *) location
243 - (void) setComment: (NSString *) _value
245 ASSIGN (comment, _value);
248 - (NSString *) comment
253 - (NSArray *) categoryList
255 static NSArray *categoryItems = nil;
259 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
281 [categoryItems retain];
284 return categoryItems;
287 - (void) setCategories: (NSArray *) _categories
289 ASSIGN (categories, _categories);
292 - (NSArray *) categories
297 - (NSString *) itemCategoryText
299 return [self labelForKey:
300 [NSString stringWithFormat: @"category_%@", item]];
303 - (NSArray *) calendarList
305 SOGoAppointmentFolder *folder;
306 NSEnumerator *allCalendars;
307 NSDictionary *currentCalendar;
311 calendarList = [NSMutableArray new];
312 folder = [[self clientObject] container];
314 = [[folder calendarFoldersInContext: context] objectEnumerator];
315 currentCalendar = [allCalendars nextObject];
316 while (currentCalendar)
318 if ([[currentCalendar objectForKey: @"active"] boolValue])
319 [calendarList addObject: currentCalendar];
320 currentCalendar = [allCalendars nextObject];
327 - (NSString *) itemCalendarText
332 - (NSString *) calendarsFoldersList
336 calendars = [[self calendarList] valueForKey: @"folder"];
338 return [calendars componentsJoinedByString: @","];
341 - (NSString *) componentCalendar
348 - (NSArray *) priorities
354 static NSArray *priorities = nil;
358 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
365 - (void) setPriority: (NSString *) _priority
367 ASSIGN (priority, _priority);
370 - (NSString *) priority
375 - (NSArray *) privacyClasses
377 static NSArray *priorities = nil;
381 priorities = [NSArray arrayWithObjects: @"PUBLIC",
382 @"CONFIDENTIAL", @"PRIVATE", nil];
389 - (void) setPrivacy: (NSString *) _privacy
391 ASSIGN (privacy, _privacy);
394 - (NSString *) privacy
399 - (NSArray *) statusTypes
401 static NSArray *priorities = nil;
405 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
412 - (void) setStatus: (NSString *) _status
414 ASSIGN (status, _status);
417 - (NSString *) status
426 static NSArray *cycles = nil;
430 bundle = [NSBundle bundleForClass:[self class]];
431 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
432 NSAssert(path != nil, @"Cannot find cycles.plist!");
433 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
434 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
440 - (void) setCycle: (NSDictionary *) _cycle
442 ASSIGN (cycle, _cycle);
445 - (NSDictionary *) cycle
452 return ([cycle objectForKey: @"rule"] != nil);
455 - (NSString *) cycleLabel
459 key = [(NSDictionary *)item objectForKey: @"label"];
461 return [self labelForKey:key];
464 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
466 // NSCalendarDate *until;
468 // /* copy hour/minute/second from startDate */
469 // until = [_cycleUntilDate hour: [startDate hourOfDay]
470 // minute: [startDate minuteOfHour]
471 // second: [startDate secondOfMinute]];
472 // [until setTimeZone: [startDate timeZone]];
473 // ASSIGN (cycleUntilDate, until);
476 - (NSCalendarDate *) cycleUntilDate
478 return cycleUntilDate;
481 - (iCalRecurrenceRule *) rrule
484 iCalRecurrenceRule *rule;
486 if (![self hasCycle])
488 ruleRep = [cycle objectForKey: @"rule"];
489 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
491 if (cycleUntilDate && [self isCycleEndUntil])
492 [rule setUntilDate:cycleUntilDate];
497 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
500 // NSCalendarDate *until;
502 // c = [self cycleMatchingRRule:_rrule];
503 // [self setCycle:c];
505 // until = [[[_rrule untilDate] copy] autorelease];
507 // until = startDate;
509 // [self setIsCycleEndUntil];
511 // [until setTimeZone:[[self clientObject] userTimeZone]];
512 // [self setCycleUntilDate:until];
516 This method is necessary, because we have a fixed sets of cycles in the UI.
517 The model is able to represent arbitrary rules, however.
518 There SHOULD be a different UI, similar to iCal.app, to allow modelling
519 of more complex rules.
521 This method obviously cannot map all existing rules back to the fixed list
522 in cycles.plist. This should be fixed in a future version when interop
523 becomes more important.
525 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
532 return [[self cycles] objectAtIndex:0];
534 cycleRep = [_rrule versitString];
535 cycles = [self cycles];
536 count = [cycles count];
537 for (i = 1; i < count; i++) {
541 c = [cycles objectAtIndex:i];
542 cr = [c objectForKey: @"rule"];
543 if ([cr isEqualToString:cycleRep])
546 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
550 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
551 - (NSArray *) cycleEnds
553 static NSArray *ends = nil;
557 ends = [NSArray arrayWithObjects: @"cycle_end_never",
558 @"cycle_end_until", nil];
565 - (void) setCycleEnd: (NSString *) _cycleEnd
567 ASSIGN (cycleEnd, _cycleEnd);
570 - (NSString *) cycleEnd
575 - (BOOL) isCycleEndUntil
577 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
580 - (void) setIsCycleEndUntil
582 [self setCycleEnd: @"cycle_end_until"];
585 - (void) setIsCycleEndNever
587 [self setCycleEnd: @"cycle_end_never"];
591 - (NSFormatter *) titleDateFormatter
593 SOGoDateFormatter *fmt;
595 fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
597 [fmt setFullWeekdayNameAndDetails];
602 - (NSString *) completeURIForMethod: (NSString *) _method
607 uri = [[[self context] request] uri];
609 /* first: identify query parameters */
610 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
612 uri = [uri substringToIndex:r.location];
614 /* next: append trailing slash */
615 if (![uri hasSuffix: @"/"])
616 uri = [uri stringByAppendingString: @"/"];
618 /* next: append method */
619 uri = [uri stringByAppendingString:_method];
621 /* next: append query parameters */
622 return [self completeHrefForMethod:uri];
625 - (BOOL) isWriteableClientObject
627 return [[self clientObject]
628 respondsToSelector: @selector(saveContentString:)];
631 - (BOOL) containsConflict: (id) _component
633 [self subclassResponsibility: _cmd];
641 - (iCalPerson *) getOrganizer
646 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
647 p = [[[iCalPerson alloc] init] autorelease];
648 [p setEmail:emailProp];
649 [p setCn:[self cnForUser]];
654 - (BOOL) isMyComponent
656 // TODO: this should check a set of emails against the SoUser
657 return ([[organizer rfc822Email] isEqualToString: [self emailForUser]]);
660 - (BOOL) canEditComponent
662 return [self isMyComponent];
665 /* response generation */
667 - (NSString *) initialCycleVisibility
669 return ([self hasCycle]
670 ? @"visibility: visible;"
671 : @"visibility: hidden;");
674 - (NSString *) initialCycleEndUntilVisibility {
675 return ([self isCycleEndUntil]
676 ? @"visibility: visible;"
677 : @"visibility: hidden;");
680 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
684 s = [self iCalParticipantsStringFromQueryParameters];
685 return [s stringByAppendingString:
686 [self iCalResourcesStringFromQueryParameters]];
689 - (NSString *) iCalParticipantsStringFromQueryParameters
691 static NSString *iCalParticipantString = \
692 @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
694 return [self iCalStringFromQueryParameter: @"ps"
695 format: iCalParticipantString];
698 - (NSString *) iCalResourcesStringFromQueryParameters
700 static NSString *iCalResourceString = \
701 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
703 return [self iCalStringFromQueryParameter: @"rs"
704 format: iCalResourceString];
707 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
708 format: (NSString *) _format
710 AgenorUserManager *um;
711 NSMutableString *iCalRep;
714 um = [AgenorUserManager sharedUserManager];
715 iCalRep = (NSMutableString *)[NSMutableString string];
716 s = [self queryParameterForKey:_qp];
717 if(s && [s length] > 0) {
721 es = [s componentsSeparatedByString: @","];
723 for(i = 0; i < count; i++) {
724 NSString *email, *cn;
726 email = [es objectAtIndex:i];
727 cn = [um getCNForUID:[um getUIDForEmail:email]];
728 [iCalRep appendFormat:_format, cn, email];
734 - (NSString *) iCalOrganizerString
736 return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
737 [self cnForUser], [self emailForUser]];
740 - (NSException *) validateObjectForStatusChange
744 co = [self clientObject];
746 respondsToSelector: @selector(changeParticipationStatus:inContext:)])
747 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
749 @"method cannot be invoked on the specified object"];
754 /* contact editor compatibility */
756 - (NSString *) urlButtonClasses
763 classes = @"button _disabled";
768 - (void) _handleAttendeesEdition
770 NSArray *names, *emails;
771 NSMutableArray *newAttendees;
772 unsigned int count, max;
773 NSString *currentEmail;
774 iCalPerson *currentAttendee;
776 newAttendees = [NSMutableArray new];
777 if ([attendeesNames length] > 0)
779 names = [attendeesNames componentsSeparatedByString: @","];
780 emails = [attendeesEmails componentsSeparatedByString: @","];
781 max = [emails count];
782 for (count = 0; count < max; count++)
784 currentEmail = [emails objectAtIndex: count];
785 currentAttendee = [component findParticipantWithEmail: currentEmail];
786 if (!currentAttendee)
788 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
789 [currentAttendee setCn: [names objectAtIndex: count]];
790 [currentAttendee setEmail: currentEmail];
791 [currentAttendee setRole: @"REQ-PARTICIPANT"];
793 setParticipationStatus: iCalPersonPartStatNeedsAction];
795 [newAttendees addObject: currentAttendee];
799 [component setAttendees: newAttendees];
800 [newAttendees release];
803 - (void) _handleOrganizer
805 NSString *organizerEmail;
807 organizerEmail = [[component organizer] email];
808 if ([organizerEmail length] == 0)
810 if ([[component attendees] count] > 0)
812 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
813 [organizer setCn: [self cnForUser]];
814 [organizer setEmail: [self emailForUser]];
815 [component setOrganizer: organizer];
820 if ([[component attendees] count] == 0)
822 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
823 [component setOrganizer: organizer];
828 - (void) takeValuesFromRequest: (WORequest *) _rq
829 inContext: (WOContext *) _ctx
832 SOGoCalendarComponent *clientObject;
834 [super takeValuesFromRequest: _rq inContext: _ctx];
836 now = [NSCalendarDate calendarDate];
837 [component setSummary: title];
838 [component setLocation: location];
839 [component setComment: comment];
840 [component setUrl: url];
841 [self _handleAttendeesEdition];
842 [self _handleOrganizer];
843 clientObject = [self clientObject];
844 if ([clientObject isNew])
846 [component setUid: [clientObject nameInContainer]];
847 [component setCreated: now];
848 [component setTimeStampAsDate: now];
849 [component setPriority: @"0"];
851 [component setLastModified: now];