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 hour: 8 minute: 0] earlierDate: newStartDate] == newStartDate))
609 hour = [now hourOfDay];
611 newStartDate = [now hour: 8 minute: 0];
613 newStartDate = [[now tomorrow] hour: 8 minute: 0];
622 - (void) loadValuesFromComponent: (iCalRepeatableEntityObject *) component
624 iCalRecurrenceRule *rrule;
628 co = [self clientObject];
629 componentOwner = [co ownerInContext: nil];
630 componentLoaded = YES;
632 startDate = [component startDate];
633 // if ((startDate = [component startDate]) == nil)
634 // startDate = [[NSCalendarDate date] hour:11 minute:0];
635 uTZ = [co userTimeZone];
638 [startDate setTimeZone: uTZ];
642 title = [[component summary] copy];
643 location = [[component location] copy];
644 comment = [[component comment] copy];
645 url = [[[component url] absoluteString] copy];
646 privacy = [[component accessClass] copy];
647 priority = [[component priority] copy];
648 status = [[component status] copy];
649 categories = [[[component categories] commaSeparatedValues] retain];
650 organizer = [[component organizer] retain];
651 participants = [[component participants] retain];
652 resources = [[component resources] retain];
655 if ([component isRecurrent])
657 rrule = [[component recurrenceRules] objectAtIndex: 0];
658 [self adjustCycleControlsForRRule: rrule];
662 - (NSString *) iCalStringTemplate
664 [self subclassResponsibility: _cmd];
669 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
673 s = [self iCalParticipantsStringFromQueryParameters];
674 return [s stringByAppendingString:
675 [self iCalResourcesStringFromQueryParameters]];
678 - (NSString *) iCalParticipantsStringFromQueryParameters
680 static NSString *iCalParticipantString = \
681 @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
683 return [self iCalStringFromQueryParameter: @"ps"
684 format: iCalParticipantString];
687 - (NSString *) iCalResourcesStringFromQueryParameters
689 static NSString *iCalResourceString = \
690 @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
692 return [self iCalStringFromQueryParameter: @"rs"
693 format: iCalResourceString];
696 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
697 format: (NSString *) _format
699 AgenorUserManager *um;
700 NSMutableString *iCalRep;
703 um = [AgenorUserManager sharedUserManager];
704 iCalRep = (NSMutableString *)[NSMutableString string];
705 s = [self queryParameterForKey:_qp];
706 if(s && [s length] > 0) {
710 es = [s componentsSeparatedByString:@","];
712 for(i = 0; i < count; i++) {
713 NSString *email, *cn;
715 email = [es objectAtIndex:i];
716 cn = [um getCNForUID:[um getUIDForEmail:email]];
717 [iCalRep appendFormat:_format, cn, email];
723 - (NSString *) iCalOrganizerString
725 return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
726 [self cnForUser], [self emailForUser]];
729 - (NSString *) saveUrl
731 [self subclassResponsibility: _cmd];
736 - (NSException *) validateObjectForStatusChange
740 co = [self clientObject];
742 respondsToSelector: @selector(changeParticipationStatus:inContext:)])
743 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
745 @"method cannot be invoked on the specified object"];
750 /* contact editor compatibility */
752 - (void) setICalString: (NSString *) _s
754 ASSIGNCOPY(iCalString, _s);
757 - (NSString *) iCalString
763 - (NSArray *) availableCalendars
765 NSEnumerator *rawContacts;
766 NSString *list, *currentId;
767 NSMutableArray *calendars;
770 calendars = [NSMutableArray array];
772 user = [context activeUser];
773 list = [[user userDefaults] stringForKey: @"calendaruids"];
774 if ([list length] == 0)
775 list = [self shortUserNameForDisplay];
778 = [[list componentsSeparatedByString: @","] objectEnumerator];
779 currentId = [rawContacts nextObject];
782 if ([currentId hasPrefix: @"-"])
783 [calendars addObject: [currentId substringFromIndex: 1]];
785 [calendars addObject: currentId];
786 currentId = [rawContacts nextObject];
792 - (NSString *) componentOwner
794 return componentOwner;
797 - (NSString *) urlButtonClasses
804 classes = @"button _disabled";
809 - (NSString *) _toolbarForCalObject: (iCalEntityObject *) calObject
811 NSString *filename, *myEmail;
813 NSEnumerator *persons;
814 iCalPersonPartStat myParticipationStatus;
817 myEmail = [[[self context] activeUser] email];
818 if ([[organizer rfc822Email] isEqualToString: myEmail])
819 filename = @"SOGoAppointmentObject.toolbar";
824 persons = [participants objectEnumerator];
825 person = [persons nextObject];
826 while (person && !found)
827 if ([[person rfc822Email] isEqualToString: myEmail])
830 myParticipationStatus = [person participationStatus];
831 if (myParticipationStatus == iCalPersonPartStatAccepted)
832 filename = @"SOGoAppointmentObjectDecline.toolbar";
833 else if (myParticipationStatus == iCalPersonPartStatDeclined)
834 filename = @"SOGoAppointmentObjectAccept.toolbar";
836 filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
839 person = [persons nextObject];
845 - (NSString *) toolbar
848 iCalEntityObject *calObject;
853 co = [self clientObject];
854 if ([co isKindOfClass: [SOGoAppointmentObject class]])
856 calObject = (iCalEntityObject *) [co event];
857 filename = [self _toolbarForCalObject: calObject];
859 else if ([co isKindOfClass: [SOGoTaskObject class]])
861 calObject = (iCalEntityObject *) [co task];
862 filename = [self _toolbarForCalObject: calObject];