]> err.no Git - scalable-opengroupware.org/blob - UI/Scheduler/UIxComponentEditor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1034 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / Scheduler / UIxComponentEditor.m
1 /* UIxComponentEditor.m - this file is part of SOGo
2  *
3  * Copyright (C) 2006 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
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)
10  * any later version.
11  *
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.
16  *
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.
21  */
22
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>
30
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>
41
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>
47
48 #import "UIxComponent+Agenor.h"
49
50 #import "UIxComponentEditor.h"
51
52 @implementation UIxComponentEditor
53
54 - (id) init
55 {
56   if ((self = [super init]))
57     {
58       component = nil;
59       [self setPrivacy: @"PUBLIC"];
60       [self setIsCycleEndNever];
61       componentOwner = @"";
62       organizer = nil;
63       attendeesNames = nil;
64       attendeesEmails = nil;
65     }
66
67   return self;
68 }
69
70 - (void) dealloc
71 {
72   [item release];
73   [cycleUntilDate release];
74   [title release];
75   [location release];
76   [organizer release];
77   [comment release];
78   [priority release];
79   [categories release];
80   [cycle release];
81   [cycleEnd release];
82   [url release];
83   [attendeesNames release];
84   [attendeesEmails release];
85
86   [super dealloc];
87 }
88
89 - (void) _loadAttendees
90 {
91   NSEnumerator *attendees;
92   iCalPerson *currentAttendee;
93   NSMutableString *names, *emails;
94
95   names = [NSMutableString new];
96   emails = [NSMutableString new];
97
98   attendees = [[component attendees] objectEnumerator];
99   currentAttendee = [attendees nextObject];
100   while (currentAttendee)
101     {
102       NSLog (@"currentCN: %@", [currentAttendee cn]);
103       [names appendFormat: @"%@,", [currentAttendee cn]];
104       [emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
105       currentAttendee = [attendees nextObject];
106     }
107
108   if ([names length] > 0)
109     {
110       ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
111       ASSIGN (attendeesEmails,
112               [emails substringToIndex: [emails length] - 1]);
113     }
114
115   [names release];
116   [emails release];
117 }
118
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
123 {
124 //   iCalRecurrenceRule *rrule;
125   SOGoObject *co;
126
127   if (!component)
128     {
129       component = newComponent;
130
131       co = [self clientObject];
132       componentOwner = [co ownerInContext: nil];
133
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];
144     }
145 //   /* cycles */
146 //   if ([component isRecurrent])
147 //     {
148 //       rrule = [[component recurrenceRules] objectAtIndex: 0];
149 //       [self adjustCycleControlsForRRule: rrule];
150 //     }
151 }
152
153 - (void) setSaveURL: (NSString *) newSaveURL
154 {
155   saveURL = newSaveURL;
156 }
157
158 - (NSString *) saveURL
159 {
160   return saveURL;
161 }
162
163 /* accessors */
164
165 - (void) setItem: (id) _item
166 {
167   ASSIGN (item, _item);
168 }
169
170 - (id) item
171 {
172   return item;
173 }
174
175 - (NSString *) itemPriorityText
176 {
177   return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
178 }
179
180 - (NSString *) itemPrivacyText
181 {
182   return [self labelForKey: [NSString stringWithFormat: @"privacy_%@", item]];
183 }
184
185 - (NSString *) itemStatusText
186 {
187   return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
188 }
189
190 - (void) setTitle: (NSString *) _value
191 {
192   ASSIGN (title, _value);
193 }
194
195 - (NSString *) title
196 {
197   return title;
198 }
199
200 - (void) setUrl: (NSString *) _url
201 {
202   ASSIGN (url, _url);
203 }
204
205 - (NSString *) url
206 {
207   return url;
208 }
209
210 - (void) setAttendeesNames: (NSString *) newAttendeesNames
211 {
212   ASSIGN (attendeesNames, newAttendeesNames);
213 }
214
215 - (NSString *) attendeesNames
216 {
217   return attendeesNames;
218 }
219
220 - (void) setAttendeesEmails: (NSString *) newAttendeesEmails
221 {
222   ASSIGN (attendeesEmails, newAttendeesEmails);
223 }
224
225 - (NSString *) attendeesEmails
226 {
227   return attendeesEmails;
228 }
229
230 - (void) setLocation: (NSString *) _value
231 {
232   ASSIGN (location, _value);
233 }
234
235 - (NSString *) location
236 {
237   return location;
238 }
239
240 - (void) setComment: (NSString *) _value
241 {
242   ASSIGN (comment, _value);
243 }
244
245 - (NSString *) comment
246 {
247   return comment;
248 }
249
250 - (NSArray *) categoryList
251 {
252   static NSArray *categoryItems = nil;
253
254   if (!categoryItems)
255     {
256       categoryItems = [NSArray arrayWithObjects: @"ANNIVERSARY",
257                                @"BIRTHDAY",
258                                @"BUSINESS",
259                                @"CALLS", 
260                                @"CLIENTS",
261                                @"COMPETITION",
262                                @"CUSTOMER",
263                                @"FAVORITES",
264                                @"FOLLOW UP",
265                                @"GIFTS",
266                                @"HOLIDAYS",
267                                @"IDEAS",
268                                @"ISSUES",
269                                @"MISCELLANEOUS",
270                                @"PERSONAL",
271                                @"PROJECTS",
272                                @"PUBLIC HOLIDAY",
273                                @"STATUS",
274                                @"SUPPLIERS",
275                                @"TRAVEL",
276                                @"VACATION",
277                               nil];
278       [categoryItems retain];
279     }
280
281   return categoryItems;
282 }
283
284 - (void) setCategories: (NSArray *) _categories
285 {
286   ASSIGN (categories, _categories);
287 }
288
289 - (NSArray *) categories
290 {
291   return categories;
292 }
293
294 - (NSString *) itemCategoryText
295 {
296   return [self labelForKey: [NSString stringWithFormat: @"category_%@", item]];
297 }
298
299 /* priorities */
300
301 - (NSArray *) priorities
302 {
303   /* 0 == undefined
304      5 == normal
305      1 == high
306   */
307   static NSArray *priorities = nil;
308
309   if (!priorities)
310     {
311       priorities = [NSArray arrayWithObjects: @"0", @"5", @"1", nil];
312       [priorities retain];
313     }
314
315   return priorities;
316 }
317
318 - (void) setPriority: (NSString *) _priority
319 {
320   ASSIGN (priority, _priority);
321 }
322
323 - (NSString *) priority
324 {
325   return priority;
326 }
327
328 - (NSArray *) privacyClasses
329 {
330   static NSArray *priorities = nil;
331
332   if (!priorities)
333     {
334       priorities = [NSArray arrayWithObjects: @"PUBLIC",
335                             @"CONFIDENTIAL", @"PRIVATE", nil];
336       [priorities retain];
337     }
338
339   return priorities;
340 }
341
342 - (void) setPrivacy: (NSString *) _privacy
343 {
344   ASSIGN (privacy, _privacy);
345 }
346
347 - (NSString *) privacy
348 {
349   return privacy;
350 }
351
352 - (NSArray *) statusTypes
353 {
354   static NSArray *priorities = nil;
355
356   if (!priorities)
357     {
358       priorities = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
359       [priorities retain];
360     }
361
362   return priorities;
363 }
364
365 - (void) setStatus: (NSString *) _status
366 {
367   ASSIGN (status, _status);
368 }
369
370 - (NSString *) status
371 {
372   return status;
373 }
374
375 - (NSArray *) cycles
376 {
377   NSBundle *bundle;
378   NSString *path;
379   static NSArray *cycles = nil;
380   
381   if (!cycles)
382     {
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!");
388     }
389
390   return cycles;
391 }
392
393 - (void) setCycle: (NSDictionary *) _cycle
394 {
395   ASSIGN (cycle, _cycle);
396 }
397
398 - (NSDictionary *) cycle
399 {
400   return cycle;
401 }
402
403 - (BOOL) hasCycle
404 {
405   return ([cycle objectForKey: @"rule"] != nil);
406 }
407
408 - (NSString *) cycleLabel
409 {
410   NSString *key;
411   
412   key = [(NSDictionary *)item objectForKey: @"label"];
413
414   return [self labelForKey:key];
415 }
416
417 - (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
418 {
419 //   NSCalendarDate *until;
420
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);
427 }
428
429 - (NSCalendarDate *) cycleUntilDate
430 {
431   return cycleUntilDate;
432 }
433
434 - (iCalRecurrenceRule *) rrule
435 {
436   NSString *ruleRep;
437   iCalRecurrenceRule *rule;
438
439   if (![self hasCycle])
440     return nil;
441   ruleRep = [cycle objectForKey: @"rule"];
442   rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
443
444   if (cycleUntilDate && [self isCycleEndUntil])
445     [rule setUntilDate:cycleUntilDate];
446
447   return rule;
448 }
449
450 - (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
451 {
452 //   NSDictionary *c;
453 //   NSCalendarDate *until;
454   
455 //   c = [self cycleMatchingRRule:_rrule];
456 //   [self setCycle:c];
457
458 //   until = [[[_rrule untilDate] copy] autorelease];
459 //   if (!until)
460 //     until = startDate;
461 //   else
462 //     [self setIsCycleEndUntil];
463
464 //   [until setTimeZone:[[self clientObject] userTimeZone]];
465 //   [self setCycleUntilDate:until];
466 }
467
468 /*
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.
473  
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.
477  */
478 - (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
479 {
480   NSString *cycleRep;
481   NSArray *cycles;
482   unsigned i, count;
483
484   if (!_rrule)
485     return [[self cycles] objectAtIndex:0];
486
487   cycleRep = [_rrule versitString];
488   cycles   = [self cycles];
489   count    = [cycles count];
490   for (i = 1; i < count; i++) {
491     NSDictionary *c;
492     NSString *cr;
493
494     c  = [cycles objectAtIndex:i];
495     cr = [c objectForKey: @"rule"];
496     if ([cr isEqualToString:cycleRep])
497       return c;
498   }
499   [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
500   return nil;
501 }
502
503 /* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
504 - (NSArray *) cycleEnds
505 {
506   static NSArray *ends = nil;
507   
508   if (!ends)
509     {
510       ends = [NSArray arrayWithObjects: @"cycle_end_never",
511                       @"cycle_end_until", nil];
512       [ends retain];
513     }
514
515   return ends;
516 }
517
518 - (void) setCycleEnd: (NSString *) _cycleEnd
519 {
520   ASSIGN (cycleEnd, _cycleEnd);
521 }
522
523 - (NSString *) cycleEnd
524 {
525   return cycleEnd;
526 }
527
528 - (BOOL) isCycleEndUntil
529 {
530   return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
531 }
532
533 - (void) setIsCycleEndUntil
534 {
535   [self setCycleEnd: @"cycle_end_until"];
536 }
537
538 - (void) setIsCycleEndNever
539 {
540   [self setCycleEnd: @"cycle_end_never"];
541 }
542
543 /* helpers */
544 - (NSFormatter *) titleDateFormatter
545 {
546   SOGoDateFormatter *fmt;
547   
548   fmt = [[SOGoDateFormatter alloc] initWithLocale: [self locale]];
549   [fmt autorelease];
550   [fmt setFullWeekdayNameAndDetails];
551
552   return fmt;
553 }
554
555 - (NSString *) completeURIForMethod: (NSString *) _method
556 {
557   NSString *uri;
558   NSRange r;
559     
560   uri = [[[self context] request] uri];
561     
562   /* first: identify query parameters */
563   r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
564   if (r.length > 0)
565     uri = [uri substringToIndex:r.location];
566     
567   /* next: append trailing slash */
568   if (![uri hasSuffix: @"/"])
569     uri = [uri stringByAppendingString: @"/"];
570   
571   /* next: append method */
572   uri = [uri stringByAppendingString:_method];
573     
574   /* next: append query parameters */
575   return [self completeHrefForMethod:uri];
576 }
577
578 - (BOOL) isWriteableClientObject
579 {
580   return [[self clientObject] 
581                 respondsToSelector: @selector(saveContentString:)];
582 }
583
584 - (BOOL) containsConflict: (id) _component
585 {
586   [self subclassResponsibility: _cmd];
587
588   return NO;
589 }
590
591 /* access */
592
593 #if 0
594 - (iCalPerson *) getOrganizer
595 {
596   iCalPerson *p;
597   NSString *emailProp;
598   
599   emailProp = [@"MAILTO:" stringByAppendingString:[self emailForUser]];
600   p = [[[iCalPerson alloc] init] autorelease];
601   [p setEmail:emailProp];
602   [p setCn:[self cnForUser]];
603   return p;
604 }
605 #endif
606
607 - (BOOL) isMyComponent
608 {
609   // TODO: this should check a set of emails against the SoUser
610   return ([[organizer rfc822Email] isEqualToString: [self emailForUser]]);
611 }
612
613 - (BOOL) canEditComponent
614 {
615   return [self isMyComponent];
616 }
617
618 /* response generation */
619
620 - (NSString *) initialCycleVisibility
621 {
622   return ([self hasCycle]
623           ? @"visibility: visible;"
624           : @"visibility: hidden;");
625 }
626
627 - (NSString *) initialCycleEndUntilVisibility {
628   return ([self isCycleEndUntil]
629           ? @"visibility: visible;"
630           : @"visibility: hidden;");
631 }
632
633 - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
634 {
635   NSString *s;
636   
637   s = [self iCalParticipantsStringFromQueryParameters];
638   return [s stringByAppendingString:
639               [self iCalResourcesStringFromQueryParameters]];
640 }
641
642 - (NSString *) iCalParticipantsStringFromQueryParameters
643 {
644   static NSString *iCalParticipantString = \
645     @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
646   
647   return [self iCalStringFromQueryParameter: @"ps"
648                format: iCalParticipantString];
649 }
650
651 - (NSString *) iCalResourcesStringFromQueryParameters
652 {
653   static NSString *iCalResourceString = \
654     @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
655
656   return [self iCalStringFromQueryParameter: @"rs"
657                format: iCalResourceString];
658 }
659
660 - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
661                                      format: (NSString *) _format
662 {
663   AgenorUserManager *um;
664   NSMutableString *iCalRep;
665   NSString *s;
666
667   um = [AgenorUserManager sharedUserManager];
668   iCalRep = (NSMutableString *)[NSMutableString string];
669   s = [self queryParameterForKey:_qp];
670   if(s && [s length] > 0) {
671     NSArray *es;
672     unsigned i, count;
673     
674     es = [s componentsSeparatedByString: @","];
675     count = [es count];
676     for(i = 0; i < count; i++) {
677       NSString *email, *cn;
678       
679       email = [es objectAtIndex:i];
680       cn = [um getCNForUID:[um getUIDForEmail:email]];
681       [iCalRep appendFormat:_format, cn, email];
682     }
683   }
684   return iCalRep;
685 }
686
687 - (NSString *) iCalOrganizerString
688 {
689   return [NSString stringWithFormat: @"ORGANIZER;CN=\"%@\":MAILTO:%@\r\n",
690                    [self cnForUser], [self emailForUser]];
691 }
692
693 - (NSException *) validateObjectForStatusChange
694 {
695   id co;
696
697   co = [self clientObject];
698   if (![co
699          respondsToSelector: @selector(changeParticipationStatus:inContext:)])
700     return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
701                         reason:
702                           @"method cannot be invoked on the specified object"];
703
704   return nil;
705 }
706
707 /* contact editor compatibility */
708
709 - (NSArray *) availableCalendars
710 {
711   NSEnumerator *rawContacts;
712   NSString *list, *currentId;
713   NSMutableArray *calendars;
714   SOGoUser *user;
715
716   calendars = [NSMutableArray array];
717
718   user = [context activeUser];
719   list = [[user userDefaults] stringForKey: @"calendaruids"];
720   if ([list length] == 0)
721     list = [self shortUserNameForDisplay];
722
723   rawContacts
724     = [[list componentsSeparatedByString: @","] objectEnumerator];
725   currentId = [rawContacts nextObject];
726   while (currentId)
727     {
728       if ([currentId hasPrefix: @"-"])
729         [calendars addObject: [currentId substringFromIndex: 1]];
730       else
731         [calendars addObject: currentId];
732       currentId = [rawContacts nextObject];
733     }
734
735   return calendars;
736 }
737
738 - (NSString *) componentOwner
739 {
740   return componentOwner;
741 }
742
743 - (NSString *) urlButtonClasses
744 {
745   NSString *classes;
746
747   if ([url length])
748     classes = @"button";
749   else
750     classes = @"button _disabled";
751
752   return classes;
753 }
754
755 - (void) _handleAttendeesEdition
756 {
757   NSArray *names, *emails;
758   NSMutableArray *newAttendees;
759   unsigned int count, max;
760   NSString *currentEmail;
761   iCalPerson *currentAttendee;
762
763   newAttendees = [NSMutableArray new];
764   if ([attendeesNames length] > 0)
765     {
766       names = [attendeesNames componentsSeparatedByString: @","];
767       emails = [attendeesEmails componentsSeparatedByString: @","];
768       max = [emails count];
769       for (count = 0; count < max; count++)
770         {
771           currentEmail = [emails objectAtIndex: count];
772           currentAttendee = [component findParticipantWithEmail: currentEmail];
773           if (!currentAttendee)
774             {
775               currentAttendee = [iCalPerson elementWithTag: @"attendee"];
776               [currentAttendee setCn: [names objectAtIndex: count]];
777               [currentAttendee setEmail: currentEmail];
778               [currentAttendee setRole: @"REQ-PARTICIPANT"];
779               [currentAttendee
780                 setParticipationStatus: iCalPersonPartStatNeedsAction];
781             }
782           [newAttendees addObject: currentAttendee];
783         }
784     }
785
786   [component setAttendees: newAttendees];
787   [newAttendees release];
788 }
789
790 - (void) _handleOrganizer
791 {
792   NSString *organizerEmail;
793
794   organizerEmail = [[component organizer] email];
795   if ([organizerEmail length] == 0)
796     {
797       if ([[component attendees] count] > 0)
798         {
799           ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
800           [organizer setCn: [self cnForUser]];
801           [organizer setEmail: [self emailForUser]];
802           [component setOrganizer: organizer];
803         }
804     }
805   else
806     {
807       if ([[component attendees] count] == 0)
808         {
809           ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
810           [component setOrganizer: organizer];
811         }
812     }
813 }
814
815 - (void) takeValuesFromRequest: (WORequest *) _rq
816                      inContext: (WOContext *) _ctx
817 {
818   NSCalendarDate *now;
819   SOGoCalendarComponent *clientObject;
820
821   [super takeValuesFromRequest: _rq inContext: _ctx];
822
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])
832     {
833       [component setUid: [clientObject nameInContainer]];
834       [component setCreated: now];
835       [component setTimeStampAsDate: now];
836       [component setPriority: @"0"];
837     }
838   [component setLastModified: now];
839 }
840
841 @end