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