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/SOGoAppointmentObject.h>
46 #import <SoObjects/Appointments/SOGoTaskObject.h>
48 #import "UIxComponent+Agenor.h"
50 #import "UIxComponentEditor.h"
52 @implementation UIxComponentEditor
56 if ((self = [super init]))
59 [self setPrivacy: @"PUBLIC"];
60 [self setIsCycleEndNever];
64 attendeesEmails = nil;
73 [cycleUntilDate release];
83 [attendeesNames release];
84 [attendeesEmails release];
89 - (void) _loadAttendees
91 NSEnumerator *attendees;
92 iCalPerson *currentAttendee;
93 NSMutableString *names, *emails;
95 names = [NSMutableString new];
96 emails = [NSMutableString new];
98 attendees = [[component attendees] objectEnumerator];
99 currentAttendee = [attendees nextObject];
100 while (currentAttendee)
102 NSLog (@"currentCN: %@", [currentAttendee cn]);
103 [names appendFormat: @"%@,", [currentAttendee cn]];
104 [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
105 currentAttendee = [attendees nextObject];
108 if ([names length] > 0)
110 ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
111 ASSIGN (attendeesEmails,
112 [emails substringToIndex: [emails length] - 1]);
119 /* warning: we use this method which will be triggered by the template system
120 when the page is instantiated, but we should find another and cleaner way of
121 doing this... for example, when the clientObject is set */
122 - (void) setComponent: (iCalRepeatableEntityObject *) newComponent
124 // iCalRecurrenceRule *rrule;
129 component = newComponent;
131 co = [self clientObject];
132 componentOwner = [co ownerInContext: nil];
134 ASSIGN (title, [component summary]);
135 ASSIGN (location, [component location]);
136 ASSIGN (comment, [component comment]);
137 ASSIGN (url, [[component url] absoluteString]);
138 ASSIGN (privacy, [component accessClass]);
139 ASSIGN (priority, [component priority]);
140 ASSIGN (status, [component status]);
141 ASSIGN (categories, [[component categories] commaSeparatedValues]);
142 ASSIGN (organizer, [component organizer]);
143 [self _loadAttendees];
146 // if ([component isRecurrent])
148 // rrule = [[component recurrenceRules] objectAtIndex: 0];
149 // [self adjustCycleControlsForRRule: rrule];
153 - (void) setSaveURL: (NSString *) newSaveURL
155 saveURL = newSaveURL;
158 - (NSString *) saveURL
165 - (void) setItem: (id) _item
167 ASSIGN (item, _item);
175 - (NSString *) itemPriorityText
177 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
180 - (NSString *) itemPrivacyText
182 return [self labelForKey: [NSString stringWithFormat: @"privacy_%@", item]];
185 - (NSString *) itemStatusText
187 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
190 - (void) setTitle: (NSString *) _value
192 ASSIGN (title, _value);
200 - (void) setUrl: (NSString *) _url
210 - (void) setAttendeesNames: (NSString *) newAttendeesNames
212 ASSIGN (attendeesNames, newAttendeesNames);
215 - (NSString *) attendeesNames
217 return attendeesNames;
220 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
222 ASSIGN (attendeesEmails, newAttendeesEmails);
225 - (NSString *) attendeesEmails
227 return attendeesEmails;
230 - (void) setLocation: (NSString *) _value
232 ASSIGN (location, _value);
235 - (NSString *) location
240 - (void) setComment: (NSString *) _value
242 ASSIGN (comment, _value);
245 - (NSString *) comment
250 - (NSArray *) categoryList
252 static NSArray *categoryItems = nil;
256 categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
278 [categoryItems retain];
281 return categoryItems;
284 - (void) setCategories: (NSArray *) _categories
286 ASSIGN (categories, _categories);
289 - (NSArray *) categories
294 - (NSString *) itemCategoryText
296 return [self labelForKey: [NSString stringWithFormat: @"category_%@", item]];
301 - (NSArray *) priorities
307 static NSArray *priorities = nil;
311 priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
318 - (void) setPriority: (NSString *) _priority
320 ASSIGN (priority, _priority);
323 - (NSString *) priority
328 - (NSArray *) privacyClasses
330 static NSArray *priorities = nil;
334 priorities = [NSArray arrayWithObjects: @"PUBLIC",
335 @"CONFIDENTIAL", @"PRIVATE", nil];
342 - (void) setPrivacy: (NSString *) _privacy
344 ASSIGN (privacy, _privacy);
347 - (NSString *) privacy
352 - (NSArray *) statusTypes
354 static NSArray *priorities = nil;
358 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
365 - (void) setStatus: (NSString *) _status
367 ASSIGN (status, _status);
370 - (NSString *) status
379 static NSArray *cycles = nil;
383 bundle = [NSBundle bundleForClass:[self class]];
384 path = [bundle pathForResource: @"cycles" ofType: @"plist"];
385 NSAssert(path != nil, @"Cannot find cycles.plist!");
386 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
387 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
393 - (void) setCycle: (NSDictionary *) _cycle
395 ASSIGN (cycle, _cycle);
398 - (NSDictionary *) cycle
405 return ([cycle objectForKey: @"rule"] != nil);
408 - (NSString *) cycleLabel
412 key = [(NSDictionary *)item objectForKey: @"label"];
414 return [self labelForKey:key];
417 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
419 // NSCalendarDate *until;
421 // /* copy hour/minute/second from startDate */
422 // until = [_cycleUntilDate hour: [startDate hourOfDay]
423 // minute: [startDate minuteOfHour]
424 // second: [startDate secondOfMinute]];
425 // [until setTimeZone: [startDate timeZone]];
426 // ASSIGN (cycleUntilDate, until);
429 - (NSCalendarDate *) cycleUntilDate
431 return cycleUntilDate;
434 - (iCalRecurrenceRule *) rrule
437 iCalRecurrenceRule *rule;
439 if (![self hasCycle])
441 ruleRep = [cycle objectForKey: @"rule"];
442 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
444 if (cycleUntilDate && [self isCycleEndUntil])
445 [rule setUntilDate:cycleUntilDate];
450 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
453 // NSCalendarDate *until;
455 // c = [self cycleMatchingRRule:_rrule];
456 // [self setCycle:c];
458 // until = [[[_rrule untilDate] copy] autorelease];
460 // until = startDate;
462 // [self setIsCycleEndUntil];
464 // [until setTimeZone:[[self clientObject] userTimeZone]];
465 // [self setCycleUntilDate:until];
469 This method is necessary, because we have a fixed sets of cycles in the UI.
470 The model is able to represent arbitrary rules, however.
471 There SHOULD be a different UI, similar to iCal.app, to allow modelling
472 of more complex rules.
474 This method obviously cannot map all existing rules back to the fixed list
475 in cycles.plist. This should be fixed in a future version when interop
476 becomes more important.
478 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
485 return [[self cycles] objectAtIndex:0];
487 cycleRep = [_rrule versitString];
488 cycles = [self cycles];
489 count = [cycles count];
490 for (i = 1; i < count; i++) {
494 c = [cycles objectAtIndex:i];
495 cr = [c objectForKey: @"rule"];
496 if ([cr isEqualToString:cycleRep])
499 [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
503 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
504 - (NSArray *) cycleEnds
506 static NSArray *ends = nil;
510 ends = [NSArray arrayWithObjects: @"cycle_end_never",
511 @"cycle_end_until", nil];
518 - (void) setCycleEnd: (NSString *) _cycleEnd
520 ASSIGN (cycleEnd, _cycleEnd);
523 - (NSString *) cycleEnd
528 - (BOOL) isCycleEndUntil
530 return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
533 - (void) setIsCycleEndUntil
535 [self setCycleEnd: @"cycle_end_until"];
538 - (void) setIsCycleEndNever
540 [self setCycleEnd: @"cycle_end_never"];
544 - (NSFormatter *) titleDateFormatter
546 SOGoDateFormatter *fmt;
548 fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
550 [fmt setFullWeekdayNameAndDetails];
555 - (NSString *) completeURIForMethod: (NSString *) _method
560 uri = [[[self context] request] uri];
562 /* first: identify query parameters */
563 r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
565 uri = [uri substringToIndex:r.location];
567 /* next: append trailing slash */
568 if (![uri hasSuffix: @"/"])
569 uri = [uri stringByAppendingString: @"/"];
571 /* next: append method */
572 uri = [uri stringByAppendingString:_method];
574 /* next: append query parameters */
575 return [self completeHrefForMethod:uri];
578 - (BOOL) isWriteableClientObject
580 return [[self clientObject]
581 respondsToSelector: @selector(saveContentString:)];
584 - (BOOL) containsConflict: (id) _component
586 [self subclassResponsibility: _cmd];
594 - (iCalPerson *) getOrganizer
599 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
600 p = [[[iCalPerson alloc] init] autorelease];
601 [p setEmail:emailProp];
602 [p setCn:[self cnForUser]];
607 - (BOOL) isMyComponent
609 // TODO: this should check a set of emails against the SoUser
610 return ([[organizer rfc822Email] isEqualToString: [self emailForUser]]);
613 - (BOOL) canEditComponent
615 return [self isMyComponent];
618 /* response generation */
620 - (NSString *) initialCycleVisibility
622 return ([self hasCycle]
623 ? @"visibility: visible;"
624 : @"visibility: hidden;");
627 - (NSString *) initialCycleEndUntilVisibility {
628 return ([self isCycleEndUntil]
629 ? @"visibility: visible;"
630 : @"visibility: hidden;");
633 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
637 s = [self iCalParticipantsStringFromQueryParameters];
638 return [s stringByAppendingString:
639 [self iCalResourcesStringFromQueryParameters]];
642 - (NSString *) iCalParticipantsStringFromQueryParameters
644 static NSString *iCalParticipantString = \
645 @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
647 return [self iCalStringFromQueryParameter: @"ps"
648 format: iCalParticipantString];
651 - (NSString *) iCalResourcesStringFromQueryParameters
653 static NSString *iCalResourceString = \
654 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
656 return [self iCalStringFromQueryParameter: @"rs"
657 format: iCalResourceString];
660 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
661 format: (NSString *) _format
663 AgenorUserManager *um;
664 NSMutableString *iCalRep;
667 um = [AgenorUserManager sharedUserManager];
668 iCalRep = (NSMutableString *)[NSMutableString string];
669 s = [self queryParameterForKey:_qp];
670 if(s && [s length] > 0) {
674 es = [s componentsSeparatedByString: @","];
676 for(i = 0; i < count; i++) {
677 NSString *email, *cn;
679 email = [es objectAtIndex:i];
680 cn = [um getCNForUID:[um getUIDForEmail:email]];
681 [iCalRep appendFormat:_format, cn, email];
687 - (NSString *) iCalOrganizerString
689 return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
690 [self cnForUser], [self emailForUser]];
693 - (NSException *) validateObjectForStatusChange
697 co = [self clientObject];
699 respondsToSelector: @selector(changeParticipationStatus:inContext:)])
700 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
702 @"method cannot be invoked on the specified object"];
707 /* contact editor compatibility */
709 - (NSArray *) availableCalendars
711 NSEnumerator *rawContacts;
712 NSString *list, *currentId;
713 NSMutableArray *calendars;
716 calendars = [NSMutableArray array];
718 user = [context activeUser];
719 list = [[user userDefaults] stringForKey: @"calendaruids"];
720 if ([list length] == 0)
721 list = [self shortUserNameForDisplay];
724 = [[list componentsSeparatedByString: @","] objectEnumerator];
725 currentId = [rawContacts nextObject];
728 if ([currentId hasPrefix: @"-"])
729 [calendars addObject: [currentId substringFromIndex: 1]];
731 [calendars addObject: currentId];
732 currentId = [rawContacts nextObject];
738 - (NSString *) componentOwner
740 return componentOwner;
743 - (NSString *) urlButtonClasses
750 classes = @"button _disabled";
755 - (void) _handleAttendeesEdition
757 NSArray *names, *emails;
758 NSMutableArray *newAttendees;
759 unsigned int count, max;
760 NSString *currentEmail;
761 iCalPerson *currentAttendee;
763 newAttendees = [NSMutableArray new];
764 if ([attendeesNames length] > 0)
766 names = [attendeesNames componentsSeparatedByString: @","];
767 emails = [attendeesEmails componentsSeparatedByString: @","];
768 max = [emails count];
769 for (count = 0; count < max; count++)
771 currentEmail = [emails objectAtIndex: count];
772 currentAttendee = [component findParticipantWithEmail: currentEmail];
773 if (!currentAttendee)
775 currentAttendee = [iCalPerson elementWithTag: @"attendee"];
776 [currentAttendee setCn: [names objectAtIndex: count]];
777 [currentAttendee setEmail: currentEmail];
778 [currentAttendee setRole: @"REQ-PARTICIPANT"];
780 setParticipationStatus: iCalPersonPartStatNeedsAction];
782 [newAttendees addObject: currentAttendee];
786 [component setAttendees: newAttendees];
787 [newAttendees release];
790 - (void) _handleOrganizer
792 NSString *organizerEmail;
794 organizerEmail = [[component organizer] email];
795 if ([organizerEmail length] == 0)
797 if ([[component attendees] count] > 0)
799 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
800 [organizer setCn: [self cnForUser]];
801 [organizer setEmail: [self emailForUser]];
802 [component setOrganizer: organizer];
807 if ([[component attendees] count] == 0)
809 ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
810 [component setOrganizer: organizer];
815 - (void) takeValuesFromRequest: (WORequest *) _rq
816 inContext: (WOContext *) _ctx
819 SOGoCalendarComponent *clientObject;
821 [super takeValuesFromRequest: _rq inContext: _ctx];
823 now = [NSCalendarDate calendarDate];
824 [component setSummary: title];
825 [component setLocation: location];
826 [component setComment: comment];
827 [component setUrl: url];
828 [self _handleAttendeesEdition];
829 [self _handleOrganizer];
830 clientObject = [self clientObject];
831 if ([clientObject isNew])
833 [component setUid: [clientObject nameInContainer]];
834 [component setCreated: now];
835 [component setTimeStampAsDate: now];
836 [component setPriority: @"0"];
838 [component setLastModified: now];