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]))
58 [self setPrivacy: @"PUBLIC"];
59 [self setCheckForConflicts: NO];
60 [self setIsCycleEndNever];
74 [cycleUntilDate release];
79 [participants release];
92 - (void) setItem: (id) _item
102 - (NSString *) itemPriorityText
104 return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
107 - (NSString *) itemPrivacyText
109 return [self labelForKey: [NSString stringWithFormat: @"privacy_%@", item]];
112 - (NSString *) itemStatusText
114 return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
117 - (void) setErrorText: (NSString *) _txt
119 ASSIGNCOPY(errorText, _txt);
122 - (NSString *) errorText
127 - (BOOL) hasErrorText
129 return [errorText length] > 0 ? YES : NO;
132 - (void) setStartDate: (NSCalendarDate *) _date
134 ASSIGN(startDate, _date);
137 - (NSCalendarDate *) startDate
142 - (void) setTitle: (NSString *) _value
144 ASSIGNCOPY(title, _value);
152 - (void) setUrl: (NSString *) _url
154 ASSIGNCOPY(url, _url);
162 - (void) setLocation: (NSString *) _value
164 ASSIGNCOPY(location, _value);
167 - (NSString *) location
172 - (void) setComment: (NSString *) _value
174 ASSIGNCOPY(comment, _value);
177 - (NSString *) comment
182 - (NSArray *) categoryItems
184 // TODO: make this configurable?
186 Tasks categories will be modified as follow :
187 – by default (a simple logo or no logo at all),
194 static NSArray *categoryItems = nil;
198 categoryItems = [NSArray arrayWithObjects: @"APPOINTMENT",
204 [categoryItems retain];
207 return categoryItems;
210 - (void) setCategories: (NSArray *) _categories
212 ASSIGN(categories, _categories);
215 - (NSArray *) categories
220 - (NSString *) itemCategoryText
222 return [[self labelForKey: item] stringByEscapingHTMLString];
227 - (NSArray *) priorities
233 static NSArray *priorities = nil;
237 priorities = [NSArray arrayWithObjects:@"0", @"5", @"1", nil];
244 - (void) setPriority: (NSString *) _priority
246 ASSIGN(priority, _priority);
249 - (NSString *) priority
254 - (NSArray *) privacyClasses
256 static NSArray *priorities = nil;
260 priorities = [NSArray arrayWithObjects: @"PUBLIC",
261 @"CONFIDENTIAL", @"PRIVATE", nil];
268 - (void) setPrivacy: (NSString *) _privacy
270 ASSIGN(privacy, _privacy);
273 - (NSString *) privacy
278 - (NSArray *) statusTypes
280 static NSArray *priorities = nil;
284 priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
291 - (void) setStatus: (NSString *) _status
293 ASSIGN(status, _status);
296 - (NSString *) status
301 - (void) setParticipants: (NSArray *) _parts
303 ASSIGN(participants, _parts);
306 - (NSArray *) participants
311 - (void) setResources: (NSArray *) _res
313 ASSIGN(resources, _res);
316 - (NSArray *) resources
321 - (void) setCheckForConflicts: (BOOL) _checkForConflicts
323 checkForConflicts = _checkForConflicts;
326 - (BOOL) checkForConflicts
328 return checkForConflicts;
335 static NSArray *cycles = nil;
339 bundle = [NSBundle bundleForClass:[self class]];
340 path = [bundle pathForResource:@"cycles" ofType:@"plist"];
341 NSAssert(path != nil, @"Cannot find cycles.plist!");
342 cycles = [[NSArray arrayWithContentsOfFile:path] retain];
343 NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
349 - (void) setCycle: (NSDictionary *) _cycle
351 ASSIGN(cycle, _cycle);
354 - (NSDictionary *) cycle
361 return ([cycle objectForKey: @"rule"] != nil);
364 - (NSString *) cycleLabel
368 key = [(NSDictionary *)item objectForKey:@"label"];
370 return [self labelForKey:key];
373 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
375 NSCalendarDate *until;
377 /* copy hour/minute/second from startDate */
378 until = [_cycleUntilDate hour: [startDate hourOfDay]
379 minute: [startDate minuteOfHour]
380 second: [startDate secondOfMinute]];
381 [until setTimeZone: [startDate timeZone]];
382 ASSIGN(cycleUntilDate, until);
385 - (NSCalendarDate *) cycleUntilDate
387 return cycleUntilDate;
390 - (iCalRecurrenceRule *) rrule
393 iCalRecurrenceRule *rule;
395 if (![self hasCycle])
397 ruleRep = [cycle objectForKey:@"rule"];
398 rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
400 if (cycleUntilDate && [self isCycleEndUntil])
401 [rule setUntilDate:cycleUntilDate];
406 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
409 NSCalendarDate *until;
411 c = [self cycleMatchingRRule:_rrule];
414 until = [[[_rrule untilDate] copy] autorelease];
418 [self setIsCycleEndUntil];
420 [until setTimeZone:[[self clientObject] userTimeZone]];
421 [self setCycleUntilDate:until];
425 This method is necessary, because we have a fixed sets of cycles in the UI.
426 The model is able to represent arbitrary rules, however.
427 There SHOULD be a different UI, similar to iCal.app, to allow modelling
428 of more complex rules.
430 This method obviously cannot map all existing rules back to the fixed list
431 in cycles.plist. This should be fixed in a future version when interop
432 becomes more important.
434 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
441 return [[self cycles] objectAtIndex:0];
443 cycleRep = [_rrule versitString];
444 cycles = [self cycles];
445 count = [cycles count];
446 for (i = 1; i < count; i++) {
450 c = [cycles objectAtIndex:i];
451 cr = [c objectForKey:@"rule"];
452 if ([cr isEqualToString:cycleRep])
455 [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
459 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
460 - (NSArray *) cycleEnds
462 static NSArray *ends = nil;
466 ends = [NSArray arrayWithObjects: @"cycle_end_never",
467 @"cycle_end_until", nil];
474 - (void) setCycleEnd: (NSString *) _cycleEnd
476 ASSIGNCOPY(cycleEnd, _cycleEnd);
479 - (NSString *) cycleEnd
484 - (BOOL) isCycleEndUntil
487 [cycleEnd isEqualToString:@"cycle_end_until"]);
490 - (void) setIsCycleEndUntil
492 [self setCycleEnd:@"cycle_end_until"];
495 - (void) setIsCycleEndNever
497 [self setCycleEnd:@"cycle_end_never"];
501 - (NSFormatter *) titleDateFormatter
503 SOGoDateFormatter *fmt;
505 fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
507 [fmt setFullWeekdayNameAndDetails];
512 - (NSString *) completeURIForMethod: (NSString *) _method
517 uri = [[[self context] request] uri];
519 /* first: identify query parameters */
520 r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
522 uri = [uri substringToIndex:r.location];
524 /* next: append trailing slash */
525 if (![uri hasSuffix:@"/"])
526 uri = [uri stringByAppendingString:@"/"];
528 /* next: append method */
529 uri = [uri stringByAppendingString:_method];
531 /* next: append query parameters */
532 return [self completeHrefForMethod:uri];
535 - (BOOL) isWriteableClientObject
537 return [[self clientObject]
538 respondsToSelector:@selector(saveContentString:)];
541 - (BOOL) shouldTakeValuesFromRequest: (WORequest *) _rq
542 inContext: (WOContext*) _c
547 - (BOOL) containsConflict: (id) _component
549 [self subclassResponsibility: _cmd];
557 - (iCalPerson *) getOrganizer
562 emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
563 p = [[[iCalPerson alloc] init] autorelease];
564 [p setEmail:emailProp];
565 [p setCn:[self cnForUser]];
570 - (BOOL) isMyComponent
572 // TODO: this should check a set of emails against the SoUser
573 return ([[organizer rfc822Email] isEqualToString: [self emailForUser]]);
576 - (BOOL) canEditComponent
578 return [self isMyComponent];
581 /* response generation */
583 - (NSString *) initialCycleVisibility
585 return ([self hasCycle]
586 ? @"visibility: visible;"
587 : @"visibility: hidden;");
590 - (NSString *) initialCycleEndUntilVisibility {
591 return ([self isCycleEndUntil]
592 ? @"visibility: visible;"
593 : @"visibility: hidden;");
597 - (NSCalendarDate *) newStartDate
599 NSCalendarDate *newStartDate, *now;
602 newStartDate = [self selectedDate];
603 if ([[self queryParameterForKey: @"hm"] length] == 0)
605 now = [NSCalendarDate calendarDate];
606 [now setTimeZone: [[self clientObject] userTimeZone]];
607 if ([now isDateOnSameDay: newStartDate])
609 hour = [now hourOfDay];
611 newStartDate = [now hour: 8 minute: 0];
613 newStartDate = [[now tomorrow] hour: 8 minute: 0];
618 newStartDate = [newStartDate hour: 8 minute: 0];
624 - (void) loadValuesFromComponent: (iCalRepeatableEntityObject *) component
626 iCalRecurrenceRule *rrule;
630 co = [self clientObject];
631 componentOwner = [co ownerInContext: nil];
632 componentLoaded = YES;
634 startDate = [component startDate];
635 // if ((startDate = [component startDate]) == nil)
636 // startDate = [[NSCalendarDate date] hour:11 minute:0];
637 uTZ = [co userTimeZone];
640 [startDate setTimeZone: uTZ];
644 title = [[component summary] copy];
645 location = [[component location] copy];
646 comment = [[component comment] copy];
647 url = [[[component url] absoluteString] copy];
648 privacy = [[component accessClass] copy];
649 priority = [[component priority] copy];
650 status = [[component status] copy];
651 categories = [[[component categories] commaSeparatedValues] retain];
652 organizer = [[component organizer] retain];
653 participants = [[component participants] retain];
654 resources = [[component resources] retain];
657 if ([component isRecurrent])
659 rrule = [[component recurrenceRules] objectAtIndex: 0];
660 [self adjustCycleControlsForRRule: rrule];
664 - (NSString *) iCalStringTemplate
666 [self subclassResponsibility: _cmd];
671 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
675 s = [self iCalParticipantsStringFromQueryParameters];
676 return [s stringByAppendingString:
677 [self iCalResourcesStringFromQueryParameters]];
680 - (NSString *) iCalParticipantsStringFromQueryParameters
682 static NSString *iCalParticipantString = \
683 @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
685 return [self iCalStringFromQueryParameter: @"ps"
686 format: iCalParticipantString];
689 - (NSString *) iCalResourcesStringFromQueryParameters
691 static NSString *iCalResourceString = \
692 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
694 return [self iCalStringFromQueryParameter: @"rs"
695 format: iCalResourceString];
698 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
699 format: (NSString *) _format
701 AgenorUserManager *um;
702 NSMutableString *iCalRep;
705 um = [AgenorUserManager sharedUserManager];
706 iCalRep = (NSMutableString *)[NSMutableString string];
707 s = [self queryParameterForKey:_qp];
708 if(s && [s length] > 0) {
712 es = [s componentsSeparatedByString:@","];
714 for(i = 0; i < count; i++) {
715 NSString *email, *cn;
717 email = [es objectAtIndex:i];
718 cn = [um getCNForUID:[um getUIDForEmail:email]];
719 [iCalRep appendFormat:_format, cn, email];
725 - (NSString *) iCalOrganizerString
727 return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
728 [self cnForUser], [self emailForUser]];
731 - (NSString *) saveUrl
733 [self subclassResponsibility: _cmd];
738 - (NSException *) validateObjectForStatusChange
742 co = [self clientObject];
744 respondsToSelector: @selector(changeParticipationStatus:inContext:)])
745 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
747 @"method cannot be invoked on the specified object"];
752 /* contact editor compatibility */
754 - (void) setICalString: (NSString *) _s
756 ASSIGNCOPY(iCalString, _s);
759 - (NSString *) iCalString
765 - (NSArray *) availableCalendars
767 NSEnumerator *rawContacts;
768 NSString *list, *currentId;
769 NSMutableArray *calendars;
772 calendars = [NSMutableArray array];
774 user = [context activeUser];
775 list = [[user userDefaults] stringForKey: @"calendaruids"];
776 if ([list length] == 0)
777 list = [self shortUserNameForDisplay];
780 = [[list componentsSeparatedByString: @","] objectEnumerator];
781 currentId = [rawContacts nextObject];
784 if ([currentId hasPrefix: @"-"])
785 [calendars addObject: [currentId substringFromIndex: 1]];
787 [calendars addObject: currentId];
788 currentId = [rawContacts nextObject];
794 - (NSString *) componentOwner
796 return componentOwner;
799 - (NSString *) urlButtonClasses
806 classes = @"button _disabled";
811 - (NSString *) _toolbarForCalObject: (iCalEntityObject *) calObject
813 NSString *filename, *myEmail;
815 NSEnumerator *persons;
816 iCalPersonPartStat myParticipationStatus;
819 myEmail = [[[self context] activeUser] email];
820 if ([[organizer rfc822Email] isEqualToString: myEmail])
821 filename = @"SOGoAppointmentObject.toolbar";
826 persons = [participants objectEnumerator];
827 person = [persons nextObject];
828 while (person && !found)
829 if ([[person rfc822Email] isEqualToString: myEmail])
832 myParticipationStatus = [person participationStatus];
833 if (myParticipationStatus == iCalPersonPartStatAccepted)
834 filename = @"SOGoAppointmentObjectDecline.toolbar";
835 else if (myParticipationStatus == iCalPersonPartStatDeclined)
836 filename = @"SOGoAppointmentObjectAccept.toolbar";
838 filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
841 person = [persons nextObject];
847 - (NSString *) toolbar
850 iCalEntityObject *calObject;
855 co = [self clientObject];
856 if ([co isKindOfClass: [SOGoAppointmentObject class]])
858 calObject = (iCalEntityObject *) [co event];
859 filename = [self _toolbarForCalObject: calObject];
861 else if ([co isKindOfClass: [SOGoTaskObject class]])
863 calObject = (iCalEntityObject *) [co task];
864 filename = [self _toolbarForCalObject: calObject];