1/* UIxComponentEditor.m - this file is part of SOGo
2 *
3 * Copyright (C) 2006-2015 Inverse inc.
4 *
5 * This file is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; see the file COPYING.  If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21#import <Foundation/NSArray.h>
22#import <Foundation/NSBundle.h>
23#import <Foundation/NSEnumerator.h>
24#import <Foundation/NSException.h>
25#import <Foundation/NSCalendarDate.h>
26#import <Foundation/NSKeyValueCoding.h>
27#import <Foundation/NSString.h>
28#import <Foundation/NSValue.h>
29#import <Foundation/NSURL.h>
30
31#import <NGCards/iCalAlarm.h>
32#import <NGCards/iCalByDayMask.h>
33#import <NGCards/iCalDateTime.h>
34#import <NGCards/iCalEvent.h>
35#import <NGCards/iCalPerson.h>
36#import <NGCards/iCalRepeatableEntityObject.h>
37#import <NGCards/iCalRecurrenceRule.h>
38#import <NGCards/iCalToDo.h>
39#import <NGCards/iCalTrigger.h>
40
41
42#import <NGCards/NSString+NGCards.h>
43#import <NGCards/NSCalendarDate+NGCards.h>
44#import <NGObjWeb/SoSecurityManager.h>
45#import <NGObjWeb/NSException+HTTP.h>
46#import <NGObjWeb/WOApplication.h>
47#import <NGObjWeb/WORequest.h>
48#import <NGObjWeb/WOResponse.h>
49#import <NGExtensions/NSCalendarDate+misc.h>
50#import <NGExtensions/NSObject+Logs.h>
51#import <NGExtensions/NSNull+misc.h>
52#import <NGExtensions/NSString+misc.h>
53
54#import <Appointments/iCalAlarm+SOGo.h>
55#import <Appointments/iCalEntityObject+SOGo.h>
56#import <Appointments/iCalPerson+SOGo.h>
57#import <Appointments/SOGoAppointmentFolder.h>
58#import <Appointments/SOGoWebAppointmentFolder.h>
59#import <Appointments/SOGoAppointmentFolders.h>
60#import <Appointments/SOGoAppointmentObject.h>
61#import <Appointments/SOGoAppointmentOccurence.h>
62#import <Appointments/SOGoTaskObject.h>
63#import <SOGo/NSArray+Utilities.h>
64#import <SOGo/NSDictionary+Utilities.h>
65#import <SOGo/NSString+Utilities.h>
66#import <SOGo/SOGoUser.h>
67#import <SOGo/SOGoUserDefaults.h>
68#import <SOGo/SOGoUserManager.h>
69#import <SOGo/SOGoSource.h>
70#import <SOGo/SOGoPermissions.h>
71#import <SOGo/SOGoSystemDefaults.h>
72#import <SOGo/WOResourceManager+SOGo.h>
73
74#import "../../Main/SOGo.h"
75
76#import "UIxComponentEditor.h"
77#import "UIxDatePicker.h"
78
79static NSArray *reminderItems = nil;
80static NSArray *reminderValues = nil;
81
82#define iREPEAT(X) \
83- (NSString *) repeat##X; \
84- (void) setRepeat##X: (NSString *) theValue
85
86#define iRANGE(X) \
87- (NSString *) range##X; \
88- (void) setRange##X: (NSString *) theValue
89
90
91@interface UIxComponentEditor (Private)
92
93iREPEAT(1);
94iREPEAT(2);
95iREPEAT(3);
96iREPEAT(4);
97iREPEAT(5);
98iREPEAT(6);
99iREPEAT(7);
100iRANGE(1);
101iRANGE(2);
102
103@end
104
105#define REPEAT(X) \
106- (NSString *) repeat##X { return repeat##X; } \
107- (void) setRepeat##X: (NSString *) theValue { ASSIGN(repeat##X, theValue); } \
108
109#define RANGE(X) \
110- (NSString *) range##X { return range##X; } \
111- (void) setRange##X: (NSString *) theValue { ASSIGN(range##X, theValue);  }
112
113@implementation UIxComponentEditor
114
115+ (void) initialize
116{
117  if (!reminderItems && !reminderValues)
118    {
119      reminderItems = [NSArray arrayWithObjects:
120			       @"5_MINUTES_BEFORE",
121			       @"10_MINUTES_BEFORE",
122			       @"15_MINUTES_BEFORE",
123			       @"30_MINUTES_BEFORE",
124			       @"45_MINUTES_BEFORE",
125			       @"-",
126			       @"1_HOUR_BEFORE",
127			       @"2_HOURS_BEFORE",
128			       @"5_HOURS_BEFORE",
129			       @"15_HOURS_BEFORE",
130			       @"-",
131			       @"1_DAY_BEFORE",
132			       @"2_DAYS_BEFORE",
133			       @"1_WEEK_BEFORE",
134			       @"-",
135			       @"CUSTOM",
136			       nil];
137      reminderValues = [NSArray arrayWithObjects:
138				@"-PT5M",
139				@"-PT10M",
140				@"-PT15M",
141				@"-PT30M",
142				@"-PT45M",
143				@"",
144				@"-PT1H",
145				@"-PT2H",
146				@"-PT5H",
147				@"-PT15H",
148				@"",
149				@"-P1D",
150				@"-P2D",
151				@"-P1W",
152				@"",
153				@"",
154				nil];
155
156      [reminderItems retain];
157      [reminderValues retain];
158    }
159}
160
161- (id) init
162{
163  UIxDatePicker *datePicker;
164
165  if ((self = [super init]))
166    {
167      // We must instanciate a UIxDatePicker object to retrieve
168      // the proper date format to use.
169      datePicker = [[UIxDatePicker alloc] initWithContext: context];
170      dateFormat = [datePicker dateFormat];
171      [datePicker release];
172
173      component = nil;
174      componentCalendar = nil;
175      classification = nil;
176      [self setIsCycleEndNever];
177      componentOwner = @"";
178      organizer = nil;
179      organizerProfile = nil;
180      ownerAsAttendee = nil;
181      attendee = nil;
182      jsonAttendees = nil;
183      calendarList = nil;
184      repeat = nil;
185      reminder = nil;
186      reminderQuantity = nil;
187      reminderUnit = nil;
188      reminderRelation = nil;
189      reminderReference = nil;
190      reminderAction = nil;
191      reminderEmailOrganizer = NO;
192      reminderEmailAttendees = NO;
193      repeatType = nil;
194      repeat1 = nil;
195      repeat2 = nil;
196      repeat3 = nil;
197      repeat4 = nil;
198      repeat5 = nil;
199      repeat6 = nil;
200      repeat7 = nil;
201      range1 = nil;
202      range2 = nil;
203    }
204
205  return self;
206}
207
208- (void) dealloc
209{
210  [item release];
211  [cycleUntilDate release];
212  [title release];
213  [location release];
214  [organizer release];
215  [organizerProfile release];
216  [ownerAsAttendee release];
217  [comment release];
218  [priority release];
219  [classification release];
220  [categories release];
221  [cycle release];
222  [cycleEnd release];
223  [attachUrl release];
224  [attendee release];
225  [jsonAttendees release];
226  [calendarList release];
227
228  [reminder release];
229  [reminderQuantity release];
230  [reminderUnit release];
231  [reminderRelation release];
232  [reminderReference release];
233
234  [repeat release];
235  [repeatType release];
236  [repeat1 release];
237  [repeat2 release];
238  [repeat3 release];
239  [repeat4 release];
240  [repeat5 release];
241  [repeat6 release];
242  [repeat7 release];
243  [range1 release];
244  [range2 release];
245
246  [component release];
247  [componentCalendar release];
248
249  [super dealloc];
250}
251
252- (void) _loadAttendees
253{
254  NSEnumerator *attendees;
255  NSMutableDictionary *currentAttendeeData;
256  NSString *uid, *domain;
257  NSArray *contacts;
258  NSDictionary *contact;
259  iCalPerson *currentAttendee;
260  SOGoUserManager *um;
261  NSObject <SOGoSource> *source;
262
263  jsonAttendees = [NSMutableDictionary new];
264  um = [SOGoUserManager sharedUserManager];
265
266  attendees = [[component attendees] objectEnumerator];
267  while ((currentAttendee = [attendees nextObject]))
268    {
269      currentAttendeeData = [NSMutableDictionary dictionary];
270
271      if ([[currentAttendee cn] length])
272	[currentAttendeeData setObject: [currentAttendee cn]
273				forKey: @"name"];
274
275      [currentAttendeeData setObject: [currentAttendee rfc822Email]
276			      forKey: @"email"];
277
278      uid = [um getUIDForEmail: [currentAttendee rfc822Email]];
279      if (uid != nil)
280	[currentAttendeeData setObject: uid
281				forKey: @"uid"];
282      else
283        {
284          domain = [[context activeUser] domain];
285          contacts = [um fetchContactsMatching: [currentAttendee rfc822Email] inDomain: domain];
286          if ([contacts count] == 1)
287            {
288              contact = [contacts lastObject];
289              source = [contact objectForKey: @"source"];
290              if ([source conformsToProtocol: @protocol (SOGoDNSource)] &&
291                  [[(NSObject <SOGoDNSource>*) source MSExchangeHostname] length])
292                {
293                  uid = [NSString stringWithFormat: @"%@:%@", [[context activeUser] login],
294                              [contact valueForKey: @"c_uid"]];
295                  [currentAttendeeData setObject: uid forKey: @"uid"];
296                }
297            }
298        }
299
300      [currentAttendeeData setObject: [[currentAttendee partStat] lowercaseString]
301			      forKey: @"partstat"];
302      [currentAttendeeData setObject: [[currentAttendee role] lowercaseString]
303			      forKey: @"role"];
304
305      if ([[currentAttendee delegatedTo] length])
306	[currentAttendeeData setObject: [[currentAttendee delegatedTo] rfc822Email]
307				forKey: @"delegated-to"];
308
309      if ([[currentAttendee delegatedFrom] length])
310	[currentAttendeeData setObject: [[currentAttendee delegatedFrom] rfc822Email]
311				forKey: @"delegated-from"];
312
313      [jsonAttendees setObject: currentAttendeeData
314			forKey: [currentAttendee rfc822Email]];
315    }
316}
317
318- (void) _loadCategories
319{
320  NSString *simpleCategory;
321  NSArray *compCategories;
322
323  compCategories = [component categories];
324  if ([compCategories count] > 0)
325    {
326      simpleCategory = [compCategories objectAtIndex: 0];
327      ASSIGN (category, simpleCategory);
328    }
329}
330
331- (void) _loadRRules
332{
333  SOGoUserDefaults *ud;
334
335  // We initialize our repeat ivars
336  if ([component hasRecurrenceRules])
337    {
338      iCalRecurrenceRule *rule;
339
340      [self setRepeat: @"CUSTOM"];
341
342      rule = [[component recurrenceRules] lastObject];
343
344      /* DAILY */
345      if ([rule frequency] == iCalRecurrenceFrequenceDaily)
346	{
347	  repeatType = @"0";
348
349	  if ([[rule byDayMask] isWeekDays])
350	    {
351	      if ([rule isInfinite])
352		repeat = @"EVERY WEEKDAY";
353	      repeat1 = @"1";
354	    }
355	  else
356	    {
357	      repeat1 = @"0";
358
359	      if ([rule repeatInterval] == 1 && [rule isInfinite])
360		repeat = @"DAILY";
361
362	      [self setRepeat2: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
363	    }
364	}
365
366      /* WEEKLY */
367      else if ([rule frequency] == iCalRecurrenceFrequenceWeekly)
368	{
369	  repeatType = @"1";
370	  [self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
371
372	  if (![[rule byDay] length])
373	    {
374	      if ([rule repeatInterval] == 1)
375		repeat = @"WEEKLY";
376	      else if ([rule repeatInterval] == 2)
377		repeat = @"BI-WEEKLY";
378	    }
379	  else
380	    {
381	      [self setRepeat2: [[rule byDayMask] asRuleStringWithIntegers]];
382	    }
383	}
384
385      /* MONTHLY */
386      else if ([rule frequency] == iCalRecurrenceFrequenceMonthly)
387	{
388	  repeatType = @"2";
389
390	  if ([[rule byDay] length])
391	    {
392	      int firstOccurrence;
393	      iCalByDayMask *dayMask;
394
395	      dayMask = [rule byDayMask];
396	      firstOccurrence = [dayMask firstOccurrence] - 1;
397	      if (firstOccurrence < 0)
398		firstOccurrence = 5;
399
400	      [self setRepeat2: @"0"];
401	      [self setRepeat3: [NSString stringWithFormat: @"%d", firstOccurrence]];
402	      [self setRepeat4: [NSString stringWithFormat: @"%d", [dayMask firstDay]]];
403	    }
404	  else if ([[rule byMonthDay] count])
405	    {
406	      NSArray *days;
407
408	      days = [rule byMonthDay];
409	      if ([days count] > 0 && [[days objectAtIndex: 0] intValue] < 0)
410		{
411		  // BYMONTHDAY=-1
412		  [self setRepeat2: @"0"];
413		  [self setRepeat3: @"5"]; // last ..
414		  [self setRepeat4: @"7"]; //      .. day of the month
415		}
416	      else
417		{
418		  [self setRepeat2: @"1"];
419		  [self setRepeat5: [[rule byMonthDay] componentsJoinedByString: @","]];
420		}
421	    }
422	  else if ([rule repeatInterval] == 1)
423	    repeat = @"MONTHLY";
424
425	  [self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
426	}
427
428      /* YEARLY */
429      else
430	{
431	  repeatType = @"3";
432
433	  if ([[rule flattenedValuesForKey: @"bymonth"] length])
434	    {
435	      if ([[rule byDay] length])
436		{
437		  int firstOccurrence;
438		  iCalByDayMask *dayMask;
439
440		  dayMask = [rule byDayMask];
441		  firstOccurrence = [dayMask firstOccurrence] - 1;
442		  if (firstOccurrence < 0)
443		    firstOccurrence = 5;
444
445		  [self setRepeat2: @"1"];
446		  [self setRepeat5: [NSString stringWithFormat: @"%d", firstOccurrence]];
447		  [self setRepeat6: [NSString stringWithFormat: @"%d", [dayMask firstDay]]];
448		  [self setRepeat7: [NSString stringWithFormat: @"%d", [[rule flattenedValuesForKey: @"bymonth"] intValue]-1]];
449		}
450	      else
451		{
452		  [self setRepeat2: @"0"];
453		  [self setRepeat3: [rule flattenedValuesForKey: @"bymonthday"]];
454		  [self setRepeat4: [NSString stringWithFormat: @"%d", [[rule flattenedValuesForKey: @"bymonth"] intValue]-1]];
455		}
456	    }
457	  else if ([rule repeatInterval] == 1)
458	    repeat = @"YEARLY";
459
460	  [self setRepeat1: [NSString stringWithFormat: @"%d", [rule repeatInterval]]];
461	}
462
463      /* We decode the proper end date, recurrences count, etc. */
464      if ([rule repeatCount])
465	{
466	  repeat = @"CUSTOM";
467	  [self setRange1: @"1"];
468	  [self setRange2: [rule flattenedValuesForKey: @"count"]];
469	}
470      else if ([rule untilDate])
471	{
472	  NSCalendarDate *date;
473
474	  repeat = @"CUSTOM";
475	  date = [[rule untilDate] copy];
476
477          ud = [[context activeUser] userDefaults];
478	  [date setTimeZone: [ud timeZone]];
479	  [self setRange1: @"2"];
480	  [self setRange2: [date descriptionWithCalendarFormat: dateFormat]];
481	  [date release];
482	}
483      else
484	[self setRange1: @"0"];
485    }
486  else
487    {
488      DESTROY(repeat);
489      repeatType = @"0";
490      repeat1 = @"0";
491      repeat2 = @"1";
492    }
493}
494
495- (void) _loadEMailAlarm: (iCalAlarm *) anAlarm
496{
497  NSArray *attendees;
498  iCalPerson *aAttendee;
499  SOGoUser *owner;
500  NSString *ownerId, *email;
501  int count, max;
502
503  attendees = [anAlarm attendees];
504  reminderEmailOrganizer = NO;
505  reminderEmailAttendees = NO;
506
507  ownerId = [[self clientObject] ownerInContext: nil];
508  owner = [SOGoUser userWithLogin: ownerId];
509  email = [[owner defaultIdentity] objectForKey: @"email"];
510
511  max = [attendees count];
512  for (count = 0;
513       !(reminderEmailOrganizer && reminderEmailAttendees)
514         && count < max;
515       count++)
516    {
517      aAttendee = [attendees objectAtIndex: count];
518      if ([[aAttendee rfc822Email] isEqualToString: email])
519        reminderEmailOrganizer = YES;
520      else
521        reminderEmailAttendees = YES;
522    }
523}
524
525- (void) _loadAlarms
526{
527  iCalAlarm *anAlarm;
528  iCalTrigger *aTrigger;
529  NSString *duration, *quantity;
530  unichar c;
531  NSUInteger i;
532
533  if ([component hasAlarms])
534    {
535      // We currently have the following limitations for alarms:
536      // - the alarm's action must be of type DISPLAY or AUDIO (considered as DISPLAY)
537      // - the alarm's trigger value type must be DURATION.
538
539      anAlarm = [component firstSupportedAlarm];
540      aTrigger = [anAlarm trigger];
541      ASSIGN (reminderAction, [[anAlarm action] lowercaseString]);
542
543      //  The default value type is DURATION. See http://tools.ietf.org/html/rfc5545#section-3.8.6.3
544      if (![[aTrigger valueType] length] ||
545          [[aTrigger valueType] caseInsensitiveCompare: @"DURATION"] == NSOrderedSame)
546	{
547	  duration = [aTrigger flattenedValuesForKey: @""];
548	  i = [reminderValues indexOfObject: duration];
549
550	  if (i == NSNotFound || [reminderAction isEqualToString: @"email"])
551	    {
552	      // Custom alarm
553	      ASSIGN (reminder, @"CUSTOM");
554	      ASSIGN (reminderRelation, [aTrigger relationType]);
555
556	      i = 0;
557	      c = [duration characterAtIndex: i];
558	      if (c == '-')
559		{
560		  ASSIGN (reminderReference, @"BEFORE");
561		  i++;
562		}
563	      else
564		{
565		  ASSIGN (reminderReference, @"AFTER");
566		}
567
568	      c = [duration characterAtIndex: i];
569	      if (c == 'P')
570		{
571		  quantity = @"";
572		  // Parse duration -- ignore first character (P)
573		  for (i++; i < [duration length]; i++)
574		    {
575		      c = [duration characterAtIndex: i];
576		      if (c == 't' || c == 'T')
577			// time -- ignore character
578			continue;
579		      else if (isdigit (c))
580			quantity = [quantity stringByAppendingFormat: @"%c", c];
581		      else
582			{
583			  switch (c)
584			    {
585			    case 'D': /* day  */
586			      ASSIGN (reminderUnit, @"DAYS");
587			      break;
588			    case 'H': /* hour */
589			      ASSIGN (reminderUnit, @"HOURS");
590			      break;
591			    case 'M': /* min  */
592			      ASSIGN (reminderUnit, @"MINUTES");
593			      break;
594			    default:
595			      //NSLog(@"Cannot process duration unit: '%c'", c);
596			      break;
597			    }
598			}
599		    }
600		  if ([quantity length])
601		    ASSIGN (reminderQuantity, quantity);
602
603                  if ([reminderAction isEqualToString: @"email"])
604                    [self _loadEMailAlarm: anAlarm];
605		}
606	    }
607	  else
608	    // Matches one of the predefined alarms
609	    ASSIGN (reminder, [reminderItems objectAtIndex: i]);
610	}
611    }
612}
613
614/* warning: we use this method which will be triggered by the template system
615   when the page is instantiated, but we should find another and cleaner way of
616   doing this... for example, when the clientObject is set */
617- (void) setComponent: (iCalRepeatableEntityObject *) newComponent
618{
619  SOGoCalendarComponent *co;
620  SOGoUserManager *um;
621  NSString *owner, *ownerEmail;
622  iCalRepeatableEntityObject *masterComponent;
623  SOGoUserDefaults *defaults;
624  NSString *tag;
625
626  if (!component)
627    {
628      ASSIGN (component, newComponent);
629
630      co = [self clientObject];
631      componentOwner = [co ownerInContext: nil];
632      if (component)
633	{
634	  ASSIGN (title, [component summary]);
635	  ASSIGN (location, [component location]);
636	  ASSIGN (comment, [component comment]);
637	  ASSIGN (attachUrl, [[component attach] absoluteString]);
638	  ASSIGN (classification, [component accessClass]);
639          if ([co isNew] && [classification length] == 0)
640            {
641              defaults = [[context activeUser] userDefaults];
642              tag = [co componentTag];
643              [classification release];
644              if ([tag isEqualToString: @"vevent"])
645                classification = [defaults calendarEventsDefaultClassification];
646              else
647                classification = [defaults calendarTasksDefaultClassification];
648
649              if ([classification length] == 0)
650                classification = @"PUBLIC";
651              [classification retain];
652            }
653
654	  ASSIGN (priority, [component priority]);
655	  ASSIGN (status, [component status]);
656          ASSIGN (categories, [component categories]);
657	  if ([[[component organizer] rfc822Email] length])
658	    {
659	      ASSIGN (organizer, [component organizer]);
660	    }
661	  else
662	    {
663	      masterComponent = [[[component parent] allObjects] objectAtIndex: 0];
664	      ASSIGN (organizer, [masterComponent organizer]);
665	    }
666
667	  [self _loadCategories];
668          if (!jsonAttendees)
669            [self _loadAttendees];
670	  [self _loadRRules];
671	  [self _loadAlarms];
672
673 	  [componentCalendar release];
674	  componentCalendar = [co container];
675	  if ([componentCalendar isKindOfClass: [SOGoCalendarComponent class]])
676	    componentCalendar = [componentCalendar container];
677	  [componentCalendar retain];
678
679	  um = [SOGoUserManager sharedUserManager];
680	  owner = [componentCalendar ownerInContext: context];
681	  ownerEmail = [um getEmailForUID: owner];
682	  ASSIGN (ownerAsAttendee, [component findAttendeeWithEmail: (id)ownerEmail]);
683	}
684    }
685}
686
687- (void) setRSVPURL: (NSString *) theURL
688{
689  rsvpURL = theURL;
690}
691
692- (NSString *) rsvpURL
693{
694  return rsvpURL;
695}
696
697- (void) setSaveURL: (NSString *) newSaveURL
698{
699  saveURL = newSaveURL;
700}
701
702- (NSString *) saveURL
703{
704  return saveURL;
705}
706
707/* accessors */
708
709- (BOOL) isChildOccurence
710{
711  return [[self clientObject] isKindOfClass: [SOGoComponentOccurence class]];
712}
713
714- (void) setItem: (id) _item
715{
716  ASSIGN (item, _item);
717}
718
719- (id) item
720{
721  return item;
722}
723
724- (NSString *) itemPriorityText
725{
726  return [self labelForKey: [NSString stringWithFormat: @"prio_%@", item]];
727}
728
729- (NSString *) itemClassificationText
730{
731  NSString *tag;
732
733  tag = [[component tag] lowercaseString];
734
735  return [self labelForKey: [NSString stringWithFormat: @"%@_%@", item, tag]];
736}
737
738- (NSString *) itemStatusText
739{
740  return [self labelForKey: [NSString stringWithFormat: @"status_%@", item]];
741}
742
743- (void) setTitle: (NSString *) _value
744{
745  ASSIGN (title, _value);
746}
747
748- (NSString *) title
749{
750  SOGoCalendarComponent *co;
751  NSString *tag;
752
753  co = [self clientObject];
754  if ([co isNew] && [co isKindOfClass: [SOGoCalendarComponent class]])
755    {
756      tag = [co componentTag];
757      if ([tag isEqualToString: @"vevent"])
758        [self setTitle: [self labelForKey: @"New Event"]];
759      else if ([tag isEqualToString: @"vtodo"])
760        [self setTitle: [self labelForKey: @"New Task"]];
761    }
762
763  return title;
764}
765
766- (void) setAttach: (NSString *) _attachUrl
767{
768  ASSIGN (attachUrl, _attachUrl);
769}
770
771- (NSString *) attach
772{
773  return attachUrl;
774}
775
776- (NSDictionary *) organizerProfile
777{
778  NSMutableDictionary *profile;
779  NSDictionary *ownerIdentity;
780  NSString *uid, *name, *email, *partstat, *role;
781  SOGoUserManager *um;
782  SOGoCalendarComponent *co;
783  SOGoUser *ownerUser;
784
785  if (organizerProfile == nil)
786    {
787      profile = [NSMutableDictionary dictionary];
788      email = [organizer rfc822Email];
789      role = nil;
790      partstat = nil;
791
792      if ([email length])
793	{
794	  um = [SOGoUserManager sharedUserManager];
795
796	  name = [organizer cn];
797	  uid = [um getUIDForEmail: email];
798
799	  partstat = [[organizer partStat] lowercaseString];
800	  role = [[organizer role] lowercaseString];
801	}
802      else
803	{
804	  // No organizer defined in vEvent; use calendar owner
805	  co = [self clientObject];
806	  uid = [[co container] ownerInContext: context];
807	  ownerUser = [SOGoUser userWithLogin: uid roles: nil];
808	  ownerIdentity = [ownerUser defaultIdentity];
809
810	  name = [ownerIdentity objectForKey: @"fullName"];
811	  email = [ownerIdentity  objectForKey: @"email"];
812	}
813
814      if (uid != nil)
815	[profile setObject: uid
816		    forKey: @"uid"];
817      else
818	uid = email;
819
820      [profile setObject: name
821                  forKey: @"name"];
822
823      [profile setObject: email
824                  forKey: @"email"];
825
826      if (partstat == nil || ![partstat length])
827	partstat = @"accepted";
828
829      [profile setObject: partstat
830                  forKey: @"partstat"];
831
832      if (role == nil || ![role length])
833	role = @"chair";
834
835      [profile setObject: role
836                  forKey: @"role"];
837
838      organizerProfile = [NSDictionary dictionaryWithObject: profile forKey: uid];
839      [organizerProfile retain];
840    }
841
842  return organizerProfile;
843}
844
845
846- (BOOL) hasCreatedBy
847{
848  return ([[[component firstChildWithTag: @"X-SOGo-Component-Created-By"] flattenedValuesForKey: @""] length] > 0);
849}
850
851- (NSString *) createdBy
852{
853  return [[component firstChildWithTag: @"X-SOGo-Component-Created-By"] flattenedValuesForKey: @""];
854}
855
856- (NSString *) createdByLink
857{
858  return [NSString stringWithFormat: @"mailto:%@",
859		   [[component firstChildWithTag: @"X-SOGo-Component-Created-By"] flattenedValuesForKey: @""]];
860}
861
862- (NSString *) createdByName
863{
864  NSString *login;
865  SOGoUser *user;
866
867  login = [[SOGoUserManager sharedUserManager] getUIDForEmail: [self createdBy]];
868
869  if (login)
870    {
871      user = [SOGoUser userWithLogin: login];
872
873      if (user)
874	return [user cn];
875    }
876
877  return @"";
878}
879
880- (NSString *) organizerName
881{
882  NSDictionary *profile;
883  NSString *s;
884
885  profile = [[[self organizerProfile] allValues] lastObject];
886
887  s = [profile objectForKey: @"name"];
888
889  if ([s length] == 0)
890    s = [profile objectForKey: @"email"];
891
892  return s;
893}
894
895- (NSString *) jsonOrganizer
896{
897  return [[[[self organizerProfile] allValues] lastObject] jsonRepresentation];
898}
899
900- (BOOL) hasOrganizer
901{
902  // We check if there's an organizer and if it's not ourself
903  NSString *value;
904
905  value = [organizer rfc822Email];
906
907  if ([value length])
908    {
909      NSDictionary *currentIdentity;
910      NSEnumerator *identities;
911      NSArray *allIdentities;
912
913      allIdentities = [[context activeUser] allIdentities];
914      identities = [allIdentities objectEnumerator];
915      currentIdentity = nil;
916
917      while ((currentIdentity = [identities nextObject]))
918	if ([[currentIdentity objectForKey: @"email"]
919	      caseInsensitiveCompare: value]
920	    == NSOrderedSame)
921	  return NO;
922
923      return YES;
924    }
925
926  return NO;
927}
928
929- (BOOL) hasAttendees
930{
931  return ([[component attendees] count] > 0);
932}
933
934- (void) setAttendee: (id) _attendee
935{
936  ASSIGN (attendee, _attendee);
937}
938
939- (id) attendee
940{
941  return attendee;
942}
943
944- (NSString *) attendeeForDisplay
945{
946  NSString *fn, *result;
947
948  fn = [attendee cnWithoutQuotes];
949  if ([fn length])
950    result = fn;
951  else
952    result = [attendee rfc822Email];
953
954  return result;
955}
956
957- (NSString *) jsonAttendees
958{
959  return [jsonAttendees jsonRepresentation];
960}
961
962- (void) setLocation: (NSString *) _value
963{
964  ASSIGN (location, _value);
965}
966
967- (NSString *) location
968{
969  return location;
970}
971
972- (BOOL) hasLocation
973{
974  return [location length] > 0;
975}
976
977- (void) setComment: (NSString *) _value
978{
979#warning should we do the same for "location" and "summary"? What about ContactsUI?
980  ASSIGN (comment, [_value stringByReplacingString: @"\r\n" withString: @"\n"]);
981}
982
983- (NSString *) comment
984{
985  return [comment stringByReplacingString: @"\n" withString: @"\r\n"];
986}
987
988- (BOOL) hasComment
989{
990  return [comment length] > 0;
991}
992
993- (NSArray *) categoryList
994{
995  NSMutableArray *categoryList;
996  NSArray *categoryLabels;
997  SOGoUserDefaults *defaults;
998
999  defaults = [[context activeUser] userDefaults];
1000  categoryLabels = [defaults calendarCategories];
1001  if (!categoryLabels)
1002    categoryLabels = [[self labelForKey: @"category_labels"]
1003                       componentsSeparatedByString: @","];
1004  categoryList
1005    = [NSMutableArray arrayWithCapacity: [categoryLabels count] + 1];
1006  if ([category length] && ![categoryLabels containsObject: category])
1007    [categoryList addObject: category];
1008  [categoryList addObjectsFromArray:
1009                  [categoryLabels sortedArrayUsingSelector:
1010                                    @selector (localizedCaseInsensitiveCompare:)]];
1011
1012  return categoryList;
1013}
1014
1015- (void) setCategories: (NSArray *) _categories
1016{
1017  ASSIGN (categories, _categories);
1018}
1019
1020- (NSArray *) categories
1021{
1022  return categories;
1023}
1024
1025- (void) setCategory: (NSString *) newCategory
1026{
1027  if (newCategory)
1028    ASSIGN (categories, [NSArray arrayWithObject: newCategory]);
1029  else
1030    {
1031      [categories release];
1032      categories = nil;
1033    }
1034}
1035
1036- (NSString *) category
1037{
1038  return category;
1039}
1040
1041- (BOOL) hasCategory
1042{
1043  return [category length] > 0;
1044}
1045
1046- (NSArray *) repeatList
1047{
1048  static NSArray *repeatItems = nil;
1049
1050  if (!repeatItems)
1051    {
1052      repeatItems = [NSArray arrayWithObjects: @"DAILY",
1053                             @"WEEKLY",
1054                             @"BI-WEEKLY",
1055                             @"EVERY WEEKDAY",
1056                             @"MONTHLY",
1057                             @"YEARLY",
1058                             @"-",
1059                             @"CUSTOM",
1060                             nil];
1061      [repeatItems retain];
1062    }
1063
1064  return repeatItems;
1065}
1066
1067- (NSString *) itemRepeatText
1068{
1069  NSString *text;
1070
1071  if ([item isEqualToString: @"-"])
1072    text = item;
1073  else
1074    text = [self labelForKey: [NSString stringWithFormat: @"repeat_%@", item]];
1075
1076  return text;
1077}
1078
1079- (NSString *) repeatLabel
1080{
1081  NSString *rc;
1082
1083  if ([self repeat])
1084    rc = [self labelForKey: [NSString stringWithFormat: @"repeat_%@", [self repeat]]];
1085  else
1086    rc = [self labelForKey: @"repeat_NEVER"];
1087
1088  return rc;
1089}
1090
1091- (NSArray *) reminderList
1092{
1093  return reminderItems;
1094}
1095
1096- (void) setReminder: (NSString *) theReminder
1097{
1098  ASSIGN(reminder, theReminder);
1099}
1100
1101- (NSString *) reminder
1102{
1103  if ([[self clientObject] isNew])
1104    {
1105      NSString *value;
1106      NSUInteger index;
1107
1108      value = [userDefaults calendarDefaultReminder];
1109      index = [reminderValues indexOfObject: value];
1110
1111      if (index != NSNotFound)
1112        return [reminderItems objectAtIndex: index];
1113
1114      return @"NONE";
1115    }
1116
1117  return reminder;
1118}
1119
1120- (void) setReminderQuantity: (NSString *) theReminderQuantity
1121{
1122  ASSIGN(reminderQuantity, theReminderQuantity);
1123}
1124
1125- (NSString *) reminderQuantity
1126{
1127  return reminderQuantity;
1128}
1129
1130- (NSString *) itemReminderText
1131{
1132  NSString *text;
1133
1134  if ([item isEqualToString: @"-"])
1135    text = item;
1136  else
1137    text = [self labelForKey: [NSString stringWithFormat: @"reminder_%@", item]];
1138
1139  return text;
1140}
1141
1142- (void) setReminderAction: (NSString *) newValue
1143{
1144  ASSIGN (reminderAction, newValue);
1145}
1146
1147- (NSString *) reminderAction
1148{
1149  return reminderAction;
1150}
1151
1152- (void) setReminderEmailOrganizer: (NSString *) newValue
1153{
1154  reminderEmailOrganizer = [newValue isEqualToString: @"true"];
1155}
1156
1157- (NSString *) reminderEmailOrganizer
1158{
1159  return (reminderEmailOrganizer ? @"true" : @"false");
1160}
1161
1162- (void) setReminderEmailAttendees: (NSString *) newValue
1163{
1164  reminderEmailAttendees = [newValue isEqualToString: @"true"];
1165}
1166
1167- (NSString *) reminderEmailAttendees
1168{
1169  return (reminderEmailAttendees ? @"true" : @"false");
1170}
1171
1172- (NSString *) repeat
1173{
1174  return repeat;
1175}
1176
1177- (void) setRepeat: (NSString *) newRepeat
1178{
1179  ASSIGN(repeat, newRepeat);
1180}
1181
1182- (BOOL) hasRepeat
1183{
1184  return [repeat length] > 0;
1185}
1186
1187- (NSString *) itemReplyText
1188{
1189  NSString *word;
1190
1191  switch ([item intValue])
1192    {
1193    case iCalPersonPartStatAccepted:
1194      word = @"ACCEPTED";
1195      break;
1196    case iCalPersonPartStatDeclined:
1197      word = @"DECLINED";
1198      break;
1199    case iCalPersonPartStatNeedsAction:
1200      word = @"NEEDS-ACTION";
1201      break;
1202    case iCalPersonPartStatTentative:
1203      word = @"TENTATIVE";
1204      break;
1205    case iCalPersonPartStatDelegated:
1206      word = @"DELEGATED";
1207      break;
1208    default:
1209      word = @"UNKNOWN";
1210    }
1211
1212  return [self labelForKey: [NSString stringWithFormat: @"partStat_%@", word]];
1213}
1214
1215- (NSArray *) replyList
1216{
1217  return [NSArray arrayWithObjects:
1218                    [NSNumber numberWithInt: iCalPersonPartStatAccepted],
1219	   [NSNumber numberWithInt: iCalPersonPartStatDeclined],
1220	   [NSNumber numberWithInt: iCalPersonPartStatNeedsAction],
1221	   [NSNumber numberWithInt: iCalPersonPartStatTentative],
1222	   [NSNumber numberWithInt: iCalPersonPartStatDelegated],
1223		  nil];
1224}
1225
1226- (NSNumber *) reply
1227{
1228  iCalPersonPartStat participationStatus;
1229
1230  participationStatus = [ownerAsAttendee participationStatus];
1231
1232  return [NSNumber numberWithInt: participationStatus];
1233}
1234
1235- (NSArray *) calendarList
1236{
1237  SOGoAppointmentFolder *calendar, *currentCalendar;
1238  SOGoAppointmentFolders *calendarParent;
1239  NSEnumerator *allCalendars;
1240  SoSecurityManager *sm;
1241  NSString *perm;
1242
1243  if (!calendarList)
1244    {
1245      calendarList = [NSMutableArray new];
1246      calendar = [self componentCalendar];
1247      sm = [SoSecurityManager sharedSecurityManager];
1248
1249      perm = SoPerm_DeleteObjects;
1250      if (![[self clientObject] isNew] &&
1251          [sm validatePermission: perm
1252                        onObject: calendar
1253                       inContext: context])
1254        {
1255	  // User can't delete components from this calendar;
1256	  // don't add any calendar other than the current one
1257          // unless it's a new component
1258	  [calendarList addObject: calendar];
1259	}
1260      else
1261	{
1262	  // Find which calendars user has creation rights
1263	  perm = SoPerm_AddDocumentsImagesAndFiles;
1264	  calendarParent
1265	    = [[context activeUser] calendarsFolderInContext: context];
1266	  allCalendars = [[calendarParent subFolders] objectEnumerator];
1267	  while ((currentCalendar = [allCalendars nextObject]))
1268	    if ([calendar isEqual: currentCalendar] ||
1269		![sm validatePermission: perm
1270			       onObject: currentCalendar
1271			      inContext: context])
1272	      [calendarList addObject: currentCalendar];
1273	}
1274    }
1275
1276  return calendarList;
1277}
1278
1279/**
1280 * This method is called from the wox template and uses to display the event
1281 * organizer in the edition window of the attendees.
1282 * Returns an array of the two elements :
1283 *  - array of calendar owners
1284 *  - dictionary of owners profiles
1285 */
1286- (NSArray *) calendarOwnerList
1287{
1288  NSArray *calendars;
1289  NSMutableArray *owners;
1290  NSDictionary *currentOwnerIdentity;
1291  NSMutableDictionary *profiles, *currentOwnerProfile;
1292  NSString *currentOwner;
1293  SOGoAppointmentFolder *currentCalendar;
1294  SOGoUser *currentUser;
1295  NSUInteger i;
1296
1297  calendars = [self calendarList];
1298  owners = [NSMutableArray arrayWithCapacity: [calendars count]];
1299  profiles = [NSMutableDictionary dictionaryWithDictionary: [self organizerProfile]];
1300
1301  for (i = 0; i < [calendars count]; i++)
1302    {
1303      currentCalendar = [calendars objectAtIndex: i];
1304      currentOwner = [currentCalendar ownerInContext: context];
1305      [owners addObject: currentOwner];
1306
1307      if ([profiles objectForKey: currentOwner] == nil)
1308	{
1309	  currentUser = [SOGoUser userWithLogin: currentOwner roles: nil];
1310	  currentOwnerIdentity = [currentUser defaultIdentity];
1311
1312	  currentOwnerProfile = [NSMutableDictionary dictionary];
1313	  [currentOwnerProfile setObject: ([currentOwnerIdentity objectForKey: @"fullName"] == nil ? @"" : [currentOwnerIdentity objectForKey: @"fullName"])
1314				  forKey: @"name"];
1315	  [currentOwnerProfile setObject: ([currentOwnerIdentity objectForKey: @"email"] == nil ? @"" : [currentOwnerIdentity objectForKey: @"email"])
1316				  forKey: @"email"];
1317	  [currentOwnerProfile setObject: @"accepted"
1318				  forKey: @"partstat"];
1319	  [currentOwnerProfile setObject: @"chair"
1320				  forKey: @"role"];
1321
1322	  [profiles setObject: currentOwnerProfile forKey: currentOwner];
1323	}
1324    }
1325
1326  return [NSArray arrayWithObjects: owners, profiles, nil];
1327}
1328
1329- (NSString *) calendarDisplayName
1330{
1331  NSString *fDisplayName;
1332  SOGoAppointmentFolder *folder;
1333  SOGoAppointmentFolders *parentFolder;
1334
1335  fDisplayName = [item displayName];
1336  folder = [self componentCalendar];
1337  parentFolder = [folder container];
1338  if ([fDisplayName isEqualToString: [parentFolder defaultFolderName]])
1339    fDisplayName = [self labelForKey: fDisplayName];
1340
1341  return fDisplayName;
1342}
1343
1344- (NSString *) calendarsFoldersList
1345{
1346  NSArray *calendars;
1347
1348  calendars = [[self calendarList] valueForKey: @"nameInContainer"];
1349
1350  return [calendars componentsJoinedByString: @","];
1351}
1352
1353
1354- (SOGoAppointmentFolder *) componentCalendar
1355{
1356  return componentCalendar;
1357}
1358
1359- (NSString *) componentCalendarName
1360{
1361  return [componentCalendar displayName];
1362}
1363
1364- (void) setComponentCalendar: (SOGoAppointmentFolder *) _componentCalendar
1365{
1366  if (_componentCalendar)
1367    ASSIGN(componentCalendar, _componentCalendar);
1368}
1369
1370/* priorities */
1371
1372- (NSArray *) priorities
1373{
1374  /* 0 == undefined
1375     9 == low
1376     5 == medium
1377     1 == high
1378  */
1379  static NSArray *priorities = nil;
1380
1381  if (!priorities)
1382    {
1383      priorities = [NSArray arrayWithObjects: @"9", @"5", @"1", nil];
1384      [priorities retain];
1385    }
1386
1387  return priorities;
1388}
1389
1390- (void) setPriority: (NSString *) _priority
1391{
1392  ASSIGN (priority, _priority);
1393}
1394
1395- (NSString *) priority
1396{
1397  return priority;
1398}
1399
1400- (BOOL) hasPriority
1401{
1402  return [priority length] > 0;
1403}
1404
1405- (NSArray *) classificationClasses
1406{
1407  static NSArray *classes = nil;
1408
1409  if (!classes)
1410    {
1411      classes = [NSArray arrayWithObjects: @"PUBLIC",
1412                         @"CONFIDENTIAL", @"PRIVATE", nil];
1413      [classes retain];
1414    }
1415
1416  return classes;
1417}
1418
1419- (void) setClassification: (NSString *) _classification
1420{
1421  ASSIGN (classification, _classification);
1422}
1423
1424- (NSString *) classification
1425{
1426  return classification;
1427}
1428
1429- (void) setStatus: (NSString *) _status
1430{
1431  ASSIGN (status, _status);
1432}
1433
1434- (NSString *) status
1435{
1436  return status;
1437}
1438
1439- (void) setRepeatType: (NSString *) theValue
1440{
1441  ASSIGN (repeatType, theValue);
1442}
1443
1444- (NSString *) repeatType
1445{
1446  return repeatType;
1447}
1448
1449REPEAT(1);
1450REPEAT(2);
1451REPEAT(3);
1452REPEAT(4);
1453REPEAT(5);
1454REPEAT(6);
1455REPEAT(7);
1456RANGE(1);
1457RANGE(2);
1458
1459////////////////////////////////// JUNK ////////////////////////////////////////
1460////////////////////////////////// JUNK ////////////////////////////////////////
1461////////////////////////////////// JUNK ////////////////////////////////////////
1462- (NSArray *) cycles
1463{
1464  NSString *path;
1465  static NSArray *cycles = nil;
1466
1467  if (!cycles)
1468    {
1469      path = [[self componentBundle] pathForResource: @"cycles" ofType: @"plist"];
1470      NSAssert(path != nil, @"Cannot find cycles.plist!");
1471      cycles = [[NSArray arrayWithContentsOfFile:path] retain];
1472      NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
1473    }
1474
1475  return cycles;
1476}
1477
1478- (void) setCycle: (NSDictionary *) _cycle
1479{
1480  ASSIGN (cycle, _cycle);
1481}
1482
1483- (NSDictionary *) cycle
1484{
1485  return cycle;
1486}
1487
1488- (BOOL) hasCycle
1489{
1490  return ([cycle objectForKey: @"rule"] != nil);
1491}
1492
1493- (NSString *) cycleLabel
1494{
1495  NSString *key;
1496
1497  key = [(NSDictionary *)item objectForKey: @"label"];
1498
1499  return [self labelForKey:key];
1500}
1501
1502- (void) setCycleUntilDate: (NSCalendarDate *) _cycleUntilDate
1503{
1504//   NSCalendarDate *until;
1505
1506//   /* copy hour/minute/second from startDate */
1507//   until = [_cycleUntilDate hour: [startDate hourOfDay]
1508//                            minute: [startDate minuteOfHour]
1509//                            second: [startDate secondOfMinute]];
1510//   [until setTimeZone: [startDate timeZone]];
1511//   ASSIGN (cycleUntilDate, until);
1512}
1513
1514- (NSCalendarDate *) cycleUntilDate
1515{
1516  return cycleUntilDate;
1517}
1518
1519- (iCalRecurrenceRule *) rrule
1520{
1521  NSString *ruleRep;
1522  iCalRecurrenceRule *rule;
1523
1524  if (![self hasCycle])
1525    return nil;
1526  ruleRep = [cycle objectForKey: @"rule"];
1527  rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
1528
1529  if (cycleUntilDate && [self isCycleEndUntil])
1530    [rule setUntilDate:cycleUntilDate];
1531
1532  return rule;
1533}
1534
1535- (void) adjustCycleControlsForRRule: (iCalRecurrenceRule *) _rrule
1536{
1537//   NSDictionary *c;
1538//   NSCalendarDate *until;
1539
1540//   c = [self cycleMatchingRRule:_rrule];
1541//   [self setCycle:c];
1542
1543//   until = [[[_rrule untilDate] copy] autorelease];
1544//   if (!until)
1545//     until = startDate;
1546//   else
1547//     [self setIsCycleEndUntil];
1548
1549//   [until setTimeZone:[[self clientObject] userTimeZone]];
1550//   [self setCycleUntilDate:until];
1551}
1552
1553/*
1554 This method is necessary, because we have a fixed sets of cycles in the UI.
1555 The model is able to represent arbitrary rules, however.
1556 There SHOULD be a different UI, similar to iCal.app, to allow modelling
1557 of more complex rules.
1558
1559 This method obviously cannot map all existing rules back to the fixed list
1560 in cycles.plist. This should be fixed in a future version when interop
1561 becomes more important.
1562 */
1563- (NSDictionary *) cycleMatchingRRule: (iCalRecurrenceRule *) _rrule
1564{
1565  NSString *cycleRep;
1566  NSArray *cycles;
1567  NSUInteger i, count;
1568
1569  if (!_rrule)
1570    return [[self cycles] objectAtIndex:0];
1571
1572  cycleRep = [_rrule versitString];
1573  cycles   = [self cycles];
1574  count    = [cycles count];
1575  for (i = 1; i < count; i++) {
1576    NSDictionary *c;
1577    NSString *cr;
1578
1579    c  = [cycles objectAtIndex:i];
1580    cr = [c objectForKey: @"rule"];
1581    if ([cr isEqualToString:cycleRep])
1582      return c;
1583  }
1584  [self warnWithFormat: @"No default cycle for rrule found! -> %@", _rrule];
1585  return nil;
1586}
1587
1588/* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
1589- (NSArray *) cycleEnds
1590{
1591  static NSArray *ends = nil;
1592
1593  if (!ends)
1594    {
1595      ends = [NSArray arrayWithObjects: @"cycle_end_never",
1596                      @"cycle_end_until", nil];
1597      [ends retain];
1598    }
1599
1600  return ends;
1601}
1602
1603- (void) setCycleEnd: (NSString *) _cycleEnd
1604{
1605  ASSIGN (cycleEnd, _cycleEnd);
1606}
1607
1608- (NSString *) cycleEnd
1609{
1610  return cycleEnd;
1611}
1612
1613- (BOOL) isCycleEndUntil
1614{
1615  return (cycleEnd && [cycleEnd isEqualToString: @"cycle_end_until"]);
1616}
1617
1618- (void) setIsCycleEndUntil
1619{
1620  [self setCycleEnd: @"cycle_end_until"];
1621}
1622
1623- (void) setIsCycleEndNever
1624{
1625  [self setCycleEnd: @"cycle_end_never"];
1626}
1627////////////////////////////////// JUNK ////////////////////////////////////////
1628////////////////////////////////// JUNK ////////////////////////////////////////
1629////////////////////////////////// JUNK ////////////////////////////////////////
1630
1631
1632/* helpers */
1633- (NSString *) completeURIForMethod: (NSString *) _method
1634{
1635  NSString *uri;
1636  NSRange r;
1637
1638  uri = [[[self context] request] uri];
1639
1640  /* first: identify query parameters */
1641  r = [uri rangeOfString: @"?" options:NSBackwardsSearch];
1642  if (r.length > 0)
1643    uri = [uri substringToIndex:r.location];
1644
1645  /* next: append trailing slash */
1646  if (![uri hasSuffix: @"/"])
1647    uri = [uri stringByAppendingString: @"/"];
1648
1649  /* next: append method */
1650  uri = [uri stringByAppendingString:_method];
1651
1652  /* next: append query parameters */
1653  return [self completeHrefForMethod:uri];
1654}
1655
1656- (BOOL) isWriteableClientObject
1657{
1658  return [[self clientObject]
1659	   respondsToSelector: @selector(saveCompontent:)];
1660}
1661
1662/* access */
1663
1664- (BOOL) canEditComponent
1665{
1666  return ([[context activeUser] hasEmail: [organizer rfc822Email]]);
1667}
1668
1669/* response generation */
1670
1671- (NSString *) initialCycleVisibility
1672{
1673  return ([self hasCycle]
1674          ? @"visibility: visible;"
1675          : @"visibility: hidden;");
1676}
1677
1678- (NSString *) initialCycleEndUntilVisibility {
1679  return ([self isCycleEndUntil]
1680          ? @"visibility: visible;"
1681          : @"visibility: hidden;");
1682}
1683
1684// - (NSString *) iCalParticipantsAndResourcesStringFromQueryParameters
1685// {
1686//   NSString *s;
1687
1688//   s = [self iCalParticipantsStringFromQueryParameters];
1689//   return [s stringByAppendingString:
1690//               [self iCalResourcesStringFromQueryParameters]];
1691// }
1692
1693// - (NSString *) iCalParticipantsStringFromQueryParameters
1694// {
1695//   static NSString *iCalParticipantString = @"ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=\"%@\":MAILTO:%@\r\n";
1696
1697//   return [self iCalStringFromQueryParameter: @"ps"
1698//                format: iCalParticipantString];
1699// }
1700
1701// - (NSString *) iCalResourcesStringFromQueryParameters
1702// {
1703//   static NSString *iCalResourceString = @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":MAILTO:%@\r\n";
1704
1705//   return [self iCalStringFromQueryParameter: @"rs"
1706//                format: iCalResourceString];
1707// }
1708
1709// - (NSString *) iCalStringFromQueryParameter: (NSString *) _qp
1710//                                      format: (NSString *) _format
1711// {
1712//   LDAPUserManager *um;
1713//   NSMutableString *iCalRep;
1714//   NSString *s;
1715
1716//   um = [LDAPUserManager sharedUserManager];
1717//   iCalRep = (NSMutableString *)[NSMutableString string];
1718//   s = [self queryParameterForKey:_qp];
1719//   if(s && [s length] > 0) {
1720//     NSArray *es;
1721//     NSUInteger i, count;
1722
1723//     es = [s componentsSeparatedByString: @","];
1724//     count = [es count];
1725//     for(i = 0; i < count; i++) {
1726//       NSString *email, *cn;
1727
1728//       email = [es objectAtIndex:i];
1729//       cn = [um getCNForUID:[um getUIDForEmail:email]];
1730//       [iCalRep appendFormat:_format, cn, email];
1731//     }
1732//   }
1733//   return iCalRep;
1734// }
1735
1736- (NSException *) validateObjectForStatusChange
1737{
1738  id co;
1739
1740  co = [self clientObject];
1741  if (![co respondsToSelector: @selector(changeParticipationStatus:)])
1742    return [NSException exceptionWithHTTPStatus: 400 /* Bad Request */
1743                        reason:
1744                          @"method cannot be invoked on the specified object"];
1745
1746  return nil;
1747}
1748
1749/* contact editor compatibility */
1750
1751/*- (NSString *) urlButtonClasses
1752{
1753  NSString *classes;
1754
1755  if ([url length])
1756    classes = @"button";
1757  else
1758    classes = @"button _disabled";
1759
1760  return classes;
1761  }*/
1762
1763- (void) _handleAttendeesEdition
1764{
1765  NSMutableArray *newAttendees;
1766  NSUInteger count, max;
1767  NSString *currentEmail;
1768  iCalPerson *currentAttendee;
1769  NSString *json, *role, *partstat;
1770  NSDictionary *attendeesData;
1771  NSArray *attendees;
1772  NSDictionary *currentData;
1773  WORequest *request;
1774
1775  request = [context request];
1776  json = [request formValueForKey: @"attendees"];
1777  if ([json length])
1778    {
1779      attendees = [NSArray array];
1780      attendeesData = [json objectFromJSONString];
1781      if (attendeesData && [attendeesData isKindOfClass: [NSDictionary class]])
1782	{
1783	  newAttendees = [NSMutableArray array];
1784	  attendees = [attendeesData allValues];
1785	  max = [attendees count];
1786	  for (count = 0; count < max; count++)
1787	    {
1788	      currentData = [attendees objectAtIndex: count];
1789	      currentEmail = [currentData objectForKey: @"email"];
1790              if ([currentEmail length] > 0)
1791                {
1792                  role = [[currentData objectForKey: @"role"] uppercaseString];
1793                  if (!role)
1794                    role = @"REQ-PARTICIPANT";
1795                  if ([role isEqualToString: @"NON-PARTICIPANT"])
1796                    partstat = @"";
1797                  else
1798                    {
1799                      partstat = [[currentData objectForKey: @"partstat"]
1800                                   uppercaseString];
1801                      if (!partstat)
1802                        partstat = @"NEEDS-ACTION";
1803                    }
1804                  currentAttendee = [component findAttendeeWithEmail: currentEmail];
1805                  if (!currentAttendee)
1806                    {
1807                      currentAttendee = [iCalPerson elementWithTag: @"attendee"];
1808                      [currentAttendee setCn: [currentData objectForKey: @"name"]];
1809                      [currentAttendee setEmail: currentEmail];
1810                      // [currentAttendee
1811                      //   setParticipationStatus: iCalPersonPartStatNeedsAction];
1812                    }
1813                  [currentAttendee
1814                    setRsvp: ([role isEqualToString: @"NON-PARTICIPANT"]
1815                              ? @"FALSE"
1816                              : @"TRUE")];
1817                  [currentAttendee setRole: role];
1818                  [currentAttendee setPartStat: partstat];
1819                  [newAttendees addObject: currentAttendee];
1820                }
1821	    }
1822	  [component setAttendees: newAttendees];
1823	}
1824      else
1825        {
1826	  //NSLog(@"Error scanning following JSON:\n%@", json);
1827        }
1828    }
1829}
1830
1831- (void) _handleOrganizer
1832{
1833  NSString *owner, *login, *currentEmail;
1834  BOOL isOwner, hasAttendees;
1835
1836  //owner = [[self clientObject] ownerInContext: context];
1837  owner = [componentCalendar ownerInContext: context];
1838  login = [[context activeUser] login];
1839  isOwner = [owner isEqualToString: login];
1840  hasAttendees = [self hasAttendees];
1841  currentEmail = [[[context activeUser] allEmails] objectAtIndex: 0];
1842
1843  if (hasAttendees)
1844    {
1845      SOGoUser *user;
1846      id identity;
1847
1848      ASSIGN (organizer, [iCalPerson elementWithTag: @"organizer"]);
1849      [component setOrganizer: organizer];
1850
1851      user = [SOGoUser userWithLogin: owner roles: nil];
1852      identity = [user defaultIdentity];
1853      [organizer setCn: [identity objectForKey: @"fullName"]];
1854      [organizer setEmail: [identity  objectForKey: @"email"]];
1855
1856      if (!isOwner)
1857	{
1858	  NSString *quotedEmail;
1859
1860          quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"",
1861                                  currentEmail];
1862          [organizer setValue: 0 ofAttribute: @"SENT-BY"
1863                           to: quotedEmail];
1864	}
1865    }
1866  else
1867    {
1868      organizer = nil;
1869    }
1870  [component setOrganizer: organizer];
1871
1872  // In case of a new component, if the current user isn't the owner of the calendar, we
1873  // add the "X-SOGo-Component-Created-By: <email address>" attribute
1874  if ([[self clientObject] isNew] &&
1875      !isOwner &&
1876      [currentEmail length])
1877    {
1878      [component addChild: [CardElement simpleElementWithTag: @"X-SOGo-Component-Created-By"
1879						       value: currentEmail]];
1880    }
1881
1882}
1883
1884- (void) _handleCustomRRule: (iCalRecurrenceRule *) theRule
1885
1886{
1887  int type, range;
1888  NSMutableArray *values;
1889
1890  // We decode the range
1891  range = [[self range1] intValue];
1892
1893  // Create X appointments
1894  if (range == 1)
1895    {
1896      [theRule setRepeatCount: [[self range2] intValue]];
1897    }
1898  // Repeat until date
1899  else if (range == 2)
1900    {
1901      NSCalendarDate *date;
1902      SOGoUserDefaults *ud;
1903
1904      date = [NSCalendarDate dateWithString: [self range2]
1905			     calendarFormat: dateFormat
1906                                     locale: locale];
1907
1908      // Adjust timezone
1909      ud = [[context activeUser] userDefaults];
1910      date = [NSCalendarDate dateWithYear: [date yearOfCommonEra]
1911                                    month: [date monthOfYear]
1912                                      day: [date dayOfMonth]
1913                                     hour: 0 minute: 0 second: 0
1914                                 timeZone: [ud timeZone]];
1915
1916      [theRule setUntilDate: date];
1917    }
1918  // No end date.
1919  else
1920    {
1921      // Do nothing?
1922    }
1923
1924
1925  // We decode the type and the rest accordingly.
1926  type = [[self repeatType] intValue];
1927
1928  switch (type)
1929    {
1930      // DAILY (0)
1931      //
1932      // repeat1 holds the value of the frequency radio button:
1933      //   0 -> Every X days
1934      //   1 -> Every weekday
1935      //
1936      // repeat2 holds the value of X when repeat1 equals 0
1937      //
1938    case 0:
1939      {
1940	[theRule setFrequency: iCalRecurrenceFrequenceDaily];
1941
1942	if ([[self repeat1] intValue] > 0)
1943	  {
1944	    [theRule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
1945	  }
1946	else
1947	  {
1948	    // Make sure we haven't received any junk....
1949	    if ([[self repeat2] intValue] < 1)
1950	      [self setRepeat2: @"1"];
1951
1952	    [theRule setInterval: [self repeat2]];
1953	  }
1954      }
1955      break;
1956
1957      // WEEKLY (1)
1958      //
1959      // repeat1 holds the value of "Every X week(s)"
1960      //
1961      // repeat2 holds which days are part of the recurrence rule
1962      //  1 -> Monday
1963      //  2 -> Tuesday .. and so on.
1964      //  The list is separated by commas, like: 1,3,4
1965    case 1:
1966      {
1967	if ([[self repeat1] intValue] > 0)
1968	  {
1969	    NSArray *v;
1970	    int c, day;
1971	    iCalWeekOccurrences days;
1972
1973	    [theRule setFrequency: iCalRecurrenceFrequenceWeekly];
1974	    [theRule setInterval: [self repeat1]];
1975
1976	    if ([[self repeat2] length])
1977	      {
1978		v = [[self repeat2] componentsSeparatedByString: @","];
1979		c = [v count];
1980		memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
1981
1982		while (c--)
1983		  {
1984		    day = [[v objectAtIndex: c] intValue];
1985		    if (day >= 0 && day <= 7)
1986		      days[day] = iCalWeekOccurrenceAll;
1987		  }
1988		[theRule setByDayMask: [iCalByDayMask byDayMaskWithDays: days]];
1989	      }
1990	  }
1991      }
1992      break;
1993
1994      // MONTHLY (2)
1995      //
1996      // repeat1 holds the value of "Every X month(s)"
1997      //
1998      // repeat2 holds the value of the radio-button "The" / "Recur on day(s)"
1999      //  0 -> The
2000      //  1 -> Recur on day(s)
2001      //
2002      // repeat3 holds the value of the first popup
2003      //  0 -> First
2004      //  1 -> Second ... and so on.
2005      //
2006      // repeat4 holds the value of the second popop
2007      //  0 -> Sunday
2008      //  1 -> Monday ... and so on.
2009      //  7 -> Day of the month
2010      //
2011      // repeat5 holds the selected days when "Recur on day(s)"
2012      // is chosen. The value starts at 1.
2013      //
2014    case 2:
2015      {
2016	if ([[self repeat1] intValue] > 0)
2017	  {
2018	    [theRule setFrequency: iCalRecurrenceFrequenceMonthly];
2019	    [theRule setInterval: [self repeat1]];
2020
2021	    // We recur on specific days...
2022	    if ([[self repeat2] intValue] == 0)
2023              {
2024                NSString *day;
2025                int occurence;
2026
2027                day = [theRule iCalRepresentationForWeekDay: [[self repeat4] intValue]];
2028                occurence = [[self repeat3] intValue] + 1;
2029		if (occurence > 5)  // the first/second/third/fourth/fifth ..
2030		  occurence = -1;   // the last ..
2031
2032                // Day of the month (last, fourth, etc.)
2033                if ([[self repeat4] intValue] == 7)
2034                  {
2035                    [theRule setSingleValue: [NSString stringWithFormat: @"%d",occurence]
2036                                     forKey: @"bymonthday"];
2037                  }
2038                else
2039                  [theRule setSingleValue: [NSString stringWithFormat: @"%d%@",
2040                                                     occurence, day]
2041                                   forKey: @"byday"];
2042              }
2043            else
2044              {
2045                if ([[self repeat5] intValue] > 0)
2046                  {
2047                    values = [[[self repeat5]
2048                                componentsSeparatedByString: @","]
2049                               mutableCopy];
2050                    [theRule setValues: values
2051                             atIndex: 0 forKey: @"bymonthday"];
2052                    [values release];
2053                  }
2054 	      }
2055	  }
2056      }
2057      break;
2058
2059      // YEARLY (3)
2060      //
2061      // repeat1 holds the value of "Every X year(s)"
2062      //
2063      // repeat2 holds the value of the radio-button "Every" / "Every .. of .."
2064      //  0 -> Every
2065      //  1 -> Every .. of ..
2066      //
2067      // repeat3 holds the value of the DAY parameter
2068      // repeat4 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
2069      //  ex: 3 February
2070      //
2071      // repeat5 holds the value of the OCCURENCE parameter (0 -> First, 1 -> Second .., 5 -> Last)
2072      // repeat6 holds the value of the DAY parameter (0 -> Sunday, 1 -> Monday, etc..)
2073      // repeat7 holds the value of the MONTH parameter (0 -> January, 1 -> February ... )
2074      //
2075    case 3:
2076    default:
2077      {
2078	if ([[self repeat1] intValue] > 0)
2079	  {
2080	    [theRule setFrequency: iCalRecurrenceFrequenceYearly];
2081	    [theRule setInterval: [self repeat1]];
2082
2083	    // We recur Every .. of ..
2084	    if ([[self repeat2] intValue] == 1)
2085	      {
2086                NSString *day;
2087                int occurence;
2088
2089                day = [theRule iCalRepresentationForWeekDay: [[self repeat6] intValue]];
2090                occurence = [[self repeat5] intValue] + 1;
2091		if (occurence > 5)  // the first/second/third/fourth/fifth ..
2092		  occurence = -1;   // the last ..
2093                [theRule setSingleValue: [NSString stringWithFormat: @"%d%@",
2094                                                  occurence, day]
2095                                 forKey: @"byday"];
2096                [theRule setSingleValue: [NSString stringWithFormat: @"%d",
2097                                                   [[self repeat7] intValue] + 1]
2098                                 forKey: @"bymonth"];
2099	      }
2100	    else
2101	      {
2102		if ([[self repeat3] intValue] > 0
2103		    && [[self repeat4] intValue] > 0)
2104		  {
2105                    values = [[[self repeat3]
2106                                componentsSeparatedByString: @","]
2107                               mutableCopy];
2108		    [theRule setValues: values atIndex: 0
2109                                forKey: @"bymonthday"];
2110                    [values release];
2111                    [theRule setSingleValue: [NSString stringWithFormat: @"%d",
2112                                                       [[self repeat4] intValue] + 1]
2113                                     forKey: @"bymonth"];
2114		  }
2115	      }
2116	  }
2117      }
2118      break;
2119    }
2120}
2121
2122
2123
2124- (void) takeValuesFromRequest: (WORequest *) _rq
2125                     inContext: (WOContext *) _ctx
2126{
2127  SOGoCalendarComponent *clientObject;
2128  iCalRecurrenceRule *rule;
2129  NSCalendarDate *now;
2130
2131  [super takeValuesFromRequest: _rq inContext: _ctx];
2132
2133  now = [NSCalendarDate calendarDate];
2134  [component setSummary: title];
2135  [component setLocation: location];
2136  [component setComment: comment];
2137  [component setAttach: attachUrl];
2138  [component setAccessClass: classification];
2139  [component setCategories: categories];
2140  [self _handleAttendeesEdition];
2141  [self _handleOrganizer];
2142  clientObject = [self clientObject];
2143  if ([clientObject isNew])
2144    {
2145      [component setCreated: now];
2146      [component setTimeStampAsDate: now];
2147    }
2148  [component setPriority: priority];
2149  [component setLastModified: now];
2150
2151  if (!reminder || [reminder caseInsensitiveCompare: @"-"] == NSOrderedSame)
2152    // No alarm selected -- if there was an unsupported alarm defined in
2153    // the event, it will be deleted.
2154    [component removeAllAlarms];
2155  else
2156    {
2157      iCalAlarm *anAlarm;
2158      NSString *aValue;
2159      NSUInteger index;
2160
2161      index = [reminderItems indexOfObject: reminder];
2162      aValue = [reminderValues objectAtIndex: index];
2163
2164      // Predefined alarm
2165      if ([aValue length])
2166        {
2167          iCalTrigger *aTrigger;
2168
2169          anAlarm = [[[iCalAlarm alloc] init] autorelease];
2170          aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"];
2171          [aTrigger setValueType: @"DURATION"];
2172          [anAlarm setTrigger: aTrigger];
2173          [anAlarm setAction: @"DISPLAY"];
2174          [aTrigger setSingleValue: aValue forKey: @""];
2175        }
2176      else
2177        {
2178          // Custom alarm
2179          anAlarm = [iCalAlarm alarmForEvent: component
2180                                       owner: [[self clientObject] ownerInContext: context]
2181                                      action: reminderAction
2182                                        unit: reminderUnit
2183                                    quantity: reminderQuantity
2184                                   reference: reminderReference
2185                            reminderRelation: reminderRelation
2186                              emailAttendees: reminderEmailAttendees
2187                              emailOrganizer: reminderEmailOrganizer];
2188        }
2189
2190      if (anAlarm)
2191        {
2192          [component removeAllAlarms];
2193          [component addToAlarms: anAlarm];
2194        }
2195    }
2196
2197  if (![self isChildOccurence])
2198    {
2199      // We remove any repeat rules
2200      if (!repeat && [component hasRecurrenceRules])
2201	[component removeAllRecurrenceRules];
2202      else if ([repeat caseInsensitiveCompare: @"-"] != NSOrderedSame)
2203	{
2204	  rule = [iCalRecurrenceRule new];
2205
2206	  [rule setInterval: @"1"];
2207
2208	  if ([repeat caseInsensitiveCompare: @"BI-WEEKLY"] == NSOrderedSame)
2209	    {
2210	      [rule setFrequency: iCalRecurrenceFrequenceWeekly];
2211	      [rule setInterval: @"2"];
2212	    }
2213	  else if ([repeat caseInsensitiveCompare: @"EVERY WEEKDAY"] == NSOrderedSame)
2214	    {
2215	      [rule setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
2216	      [rule setFrequency: iCalRecurrenceFrequenceDaily];
2217	    }
2218	  else if ([repeat caseInsensitiveCompare: @"MONTHLY"] == NSOrderedSame
2219		   || [repeat caseInsensitiveCompare: @"DAILY"] == NSOrderedSame
2220		   || [repeat caseInsensitiveCompare: @"WEEKLY"] == NSOrderedSame
2221		   || [repeat caseInsensitiveCompare: @"YEARLY"] == NSOrderedSame)
2222	    {
2223	      [rule setInterval: @"1"];
2224	      [rule setFrequency:
2225		      (iCalRecurrenceFrequency) [rule valueForFrequency: repeat]];
2226	    }
2227	  else
2228	    {
2229	      // We have a CUSTOM recurrence. Let's decode what kind of custome recurrence
2230	      // we have and set that.
2231	      [self _handleCustomRRule: rule];
2232	    }
2233
2234	  [component setRecurrenceRules: [NSArray arrayWithObject: rule]];
2235	  [rule release];
2236	}
2237    }
2238}
2239
2240#warning the following methods probably share some code...
2241- (NSString *) _toolbarForOwner: (SOGoUser *) ownerUser
2242		andClientObject: (SOGoContentObject
2243				  <SOGoComponentOccurence> *) clientObject
2244{
2245  NSString *toolbarFilename;
2246  BOOL isOrganizer;
2247
2248  // We determine if we're the organizer of the component beeing modified.
2249  // If we created an event on behalf of someone else -userIsOrganizer will
2250  // return us YES. This is OK because we're in the SENT-BY. But, Alice
2251  // should be able to accept/decline an invitation if she created the event
2252  // in Bob's calendar and added herself in the attendee list.
2253  isOrganizer = [component userIsOrganizer: ownerUser];
2254
2255  if (isOrganizer)
2256    isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]];
2257
2258  if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]]
2259      || ([component userIsAttendee: ownerUser]
2260	  && !isOrganizer
2261	  // Lightning does not manage participation status within tasks,
2262	  // so we also ignore the participation status of tasks in the
2263	  // web interface.
2264	  && ![[component tag] isEqualToString: @"VTODO"]))
2265    toolbarFilename = @"SOGoEmpty.toolbar";
2266  else
2267    {
2268      if ([clientObject isKindOfClass: [SOGoAppointmentObject class]]
2269	  || [clientObject isKindOfClass: [SOGoAppointmentOccurence class]])
2270        toolbarFilename = @"SOGoAppointmentObject.toolbar";
2271      else
2272        toolbarFilename = @"SOGoTaskObject.toolbar";
2273    }
2274
2275  return toolbarFilename;
2276}
2277
2278- (NSString *) _toolbarForDelegate: (SOGoUser *) ownerUser
2279		   andClientObject: (SOGoContentObject
2280				     <SOGoComponentOccurence> *) clientObject
2281{
2282  SoSecurityManager *sm;
2283  NSString *toolbarFilename;
2284
2285  sm = [SoSecurityManager sharedSecurityManager];
2286
2287  if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
2288                     onObject: clientObject
2289                    inContext: context])
2290    toolbarFilename = [self _toolbarForOwner: ownerUser
2291                             andClientObject: clientObject];
2292  else
2293    toolbarFilename = @"SOGoEmpty.toolbar";
2294
2295  return toolbarFilename;
2296}
2297
2298- (NSString *) toolbar
2299{
2300  SOGoContentObject <SOGoComponentOccurence> *clientObject;
2301  NSString *toolbarFilename;
2302  SOGoUser *ownerUser;
2303
2304  clientObject = [self clientObject];
2305  ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]
2306			roles: nil];
2307
2308  if ([ownerUser isEqual: [context activeUser]])
2309    toolbarFilename = [self _toolbarForOwner: ownerUser
2310			    andClientObject: clientObject];
2311  else
2312    toolbarFilename = [self _toolbarForDelegate: ownerUser
2313			    andClientObject: clientObject];
2314
2315
2316  return toolbarFilename;
2317}
2318
2319
2320- (int) ownerIsAttendee: (SOGoUser *) ownerUser
2321        andClientObject: (SOGoContentObject
2322                          <SOGoComponentOccurence> *) clientObject
2323{
2324  BOOL isOrganizer;
2325  iCalPerson *ownerAttendee;
2326  int rc;
2327
2328  rc = 0;
2329
2330  isOrganizer = [component userIsOrganizer: ownerUser];
2331  if (isOrganizer)
2332    isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]];
2333
2334  if (!isOrganizer && ![[component tag] isEqualToString: @"VTODO"])
2335    {
2336      ownerAttendee = [component userAsAttendee: ownerUser];
2337      if (ownerAttendee)
2338        rc = 1;
2339    }
2340
2341  return rc;
2342}
2343
2344- (int) delegateIsAttendee: (SOGoUser *) ownerUser
2345           andClientObject: (SOGoContentObject
2346                             <SOGoComponentOccurence> *) clientObject
2347{
2348  SoSecurityManager *sm;
2349  iCalPerson *ownerAttendee;
2350  int rc;
2351
2352  rc = 0;
2353
2354  sm = [SoSecurityManager sharedSecurityManager];
2355  if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
2356                     onObject: clientObject
2357                    inContext: context])
2358    rc = [self ownerIsAttendee: ownerUser
2359               andClientObject: clientObject];
2360  else if (![sm validatePermission: SOGoCalendarPerm_RespondToComponent
2361                          onObject: clientObject
2362                         inContext: context])
2363    {
2364      ownerAttendee = [component userAsAttendee: ownerUser];
2365      if ([[ownerAttendee rsvp] isEqualToString: @"true"]
2366          && ![component userIsOrganizer: ownerUser])
2367        rc = 1;
2368      else
2369        rc = 2;
2370    }
2371  else
2372    rc = 2; // not invited, just RO
2373
2374  return rc;
2375}
2376
2377- (int) getEventRWType
2378{
2379  SOGoContentObject <SOGoComponentOccurence> *clientObject;
2380  SOGoUser *ownerUser;
2381  int rc;
2382
2383  clientObject = [self clientObject];
2384  ownerUser
2385    = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
2386  if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]])
2387    rc = 2;
2388  else
2389    {
2390      if ([ownerUser isEqual: [context activeUser]])
2391        rc = [self ownerIsAttendee: ownerUser
2392                   andClientObject: clientObject];
2393      else
2394        rc = [self delegateIsAttendee: ownerUser
2395                      andClientObject: clientObject];
2396    }
2397
2398  return rc;
2399}
2400
2401- (BOOL) eventIsReadOnly
2402{
2403  return [self getEventRWType] != 0;
2404}
2405
2406- (NSString *) emailAlarmsEnabled
2407{
2408  SOGoSystemDefaults *sd;
2409
2410  sd = [SOGoSystemDefaults sharedSystemDefaults];
2411
2412  return ([sd enableEMailAlarms]
2413          ? @"true"
2414          : @"false");
2415}
2416
2417- (BOOL) userHasRSVP
2418{
2419  return ([self getEventRWType] == 1);
2420}
2421
2422- (NSString *) currentAttendeeClasses
2423{
2424  NSMutableArray *classes;
2425  iCalPerson *ownerAttendee;
2426  SOGoUser *ownerUser;
2427  NSString *role, *partStat;
2428  SOGoCalendarComponent *co;
2429
2430  classes = [NSMutableArray arrayWithCapacity: 5];
2431
2432  /* rsvp class */
2433  if (![[attendee rsvp] isEqualToString: @"true"])
2434    [classes addObject: @"not-rsvp"];
2435
2436  /* partstat class */
2437  partStat = [[attendee partStat] lowercaseString];
2438  if (![partStat length])
2439    partStat = @"no-partstat";
2440  [classes addObject: partStat];
2441
2442  /* role class */
2443  role = [[attendee role] lowercaseString];
2444  if (![partStat length])
2445    role = @"no-role";
2446  [classes addObject: role];
2447
2448  /* attendee class */
2449  if ([[attendee delegatedFrom] length] > 0)
2450    [classes addObject: @"delegate"];
2451
2452  /* current attendee class */
2453  co = [self clientObject];
2454  ownerUser = [SOGoUser userWithLogin: [co ownerInContext: context]];
2455  ownerAttendee = [component userAsAttendee: ownerUser];
2456  if (attendee == ownerAttendee)
2457    [classes addObject: @"attendeeUser"];
2458
2459  return [classes componentsJoinedByString: @" "];
2460}
2461
2462- (NSString *) ownerLogin
2463{
2464  return [[self clientObject] ownerInContext: context];
2465}
2466
2467- (unsigned int) firstDayOfWeek
2468{
2469  SOGoUserDefaults *ud;
2470
2471  ud = [[context activeUser] userDefaults];
2472
2473  return [ud firstDayOfWeek];
2474}
2475
2476// returns the raw content of the object
2477- (WOResponse *) rawAction
2478{
2479  NSMutableString *content;
2480  WOResponse *response;
2481
2482  content = [NSMutableString string];
2483  response = [context response];
2484
2485  [content appendFormat: @"%@", [[self clientObject] contentAsString]];
2486  [response setHeader: @"text/plain; charset=utf-8"
2487            forKey: @"content-type"];
2488  [response appendContentString: content];
2489
2490  return response;
2491}
2492
2493+ (NSArray *) reminderValues
2494{
2495  return reminderValues;
2496}
2497
2498@end
2499