Added invitation delegation support in Web interface. See ChangeLog.

Monotone-Parent: 4d711e074341810486c1842c6a345777cc3664bb
Monotone-Revision: 2fc8598099b516f243935e73039200f1c004784e

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2009-09-10T18:31:20
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Francis Lachapelle 2009-09-10 18:31:20 +00:00
parent b63b53fecd
commit 9a9ecb1811
45 changed files with 1296 additions and 836 deletions

View File

@ -1,3 +1,43 @@
2009-09-10 Francis Lachapelle <flachapelle@inverse.ca>
* UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor
-_handleAttendeesEdition]): the attendees list is now submited as
a JSON string.
([UIxComponentEditor -_loadAttendees]): constructs a dictionary
with the attendees' information that will be passed as a JSON
string to the template.
* UI/MailPartViewers/UIxMailPartICalViewer.m
([UIxMailPartICalViewer -currentAttendeeClass]): new method that
returns the attendee's partstat as the CSS class and adds
"attendeeUser" if the attendee is the active user.
* UI/MailPartViewers/UIxMailPartICalActions.m (-): added delegateAction.
* UI/Contacts/UIxContactFoldersView.m ([UIxContactFoldersView
-allContactSearchAction]): added the possibility to pass the form
parameter "excludeLists" in order to avoid returning groups.
* SoObjects/SOGo/LDAPSource.m ([LDAPSource
_convertLDAPEntryToContact:]): added a key named "isList" to the
data representation of the LDAP entry if it matches specific objectClasses.
* SoObjects/Appointments/SOGoAptMailICalReply.m
([SOGoAptMailICalReply -getBody]): trim spaces before returning
the body.
* SoObjects/Appointments/SOGoAppointmentObject.m
([SOGoAppointmentObject
-_handleAttendee:withDelegate:ownerUser:statusChange:inEvent:]):
added support for invitation delegation.
([SOGoAppointementObject
-_updateAttendee:withDelegate:ownerUser:forEventUID:withRecurrenceId:withSequence:forUID:shouldAddSentBy:]):
fixed the case of chained delegates.
([SOGoAppointmentObject
-changeParticipationStatus:withDelegate:forRecurrenceId:]): when
delegating an invitation, verify that the delegated is not already
a participant nor a group of users.
2009-09-10 Cyril Robert <crobert@inverse.ca>
* SoObjects/Appointments/SOGoWebAppointmentFolder.m: New

View File

@ -1,3 +1,11 @@
2009-09-10 Francis Lachapelle <flachapelle@inverse.ca>
* iCalPerson.m ([iCalPerson -_valueOfMailtoAttribute:]): fixed the
string range that was removing valid characters from a quoted string.
* NSString+NGCards.m ([NSString -rfc822Email]): new method that
returns the string without the "mailto:" prefix.
2009-09-09 Cyril Robert <crobert@inverse.ca>
* NGVCard.m: Made use of NSDictionary+Utilities' userRecordAsLDIFEntry.

View File

@ -1,6 +1,6 @@
/* NSString+NGCards.h - this file is part of SOPE
*
* Copyright (C) 2006 Inverse inc.
* Copyright (C) 2006-2009 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@ -35,6 +35,7 @@
- (NSArray *) asCardAttributeValues;
- (NSString *) escapedForCards;
- (NSString *) unescapedFromCard;
- (NSString *) rfc822Email;
- (NSTimeInterval) durationAsTimeInterval;
- (NSCalendarDate *) asCalendarDate;

View File

@ -329,4 +329,17 @@ static NSString *commaSeparator = nil;
return components;
}
- (NSString *) rfc822Email
{
unsigned idx;
idx = NSMaxRange([self rangeOfString:@":"]);
if ((idx > 0) && ([self length] > idx))
return [self substringFromIndex: idx];
return self;
}
@end

View File

@ -205,7 +205,7 @@
mailTo = [self value: 0 ofAttribute: name];
if ([mailTo hasPrefix: @"\""])
mailTo
= [mailTo substringWithRange: NSMakeRange (0, [mailTo length] - 2)];
= [mailTo substringWithRange: NSMakeRange (1, [mailTo length] - 2)];
return mailTo;
}

View File

@ -60,7 +60,6 @@ Appointments_COMPONENTS += \
SOGoAptMailDutchDeletion.wo \
SOGoAptMailDutchUpdate.wo \
SOGoAptMailEnglishInvitation.wo \
SOGoAptMailEnglishICalReply.wo \
SOGoAptMailEnglishDeletion.wo \
SOGoAptMailEnglishUpdate.wo \
SOGoAptMailFrenchInvitation.wo \

View File

@ -35,6 +35,8 @@
#import <NGCards/NSCalendarDate+NGCards.h>
#import <SaxObjC/XMLNamespaces.h>
#import <SOPE/NGCards/NSString+NGCards.h>
#import <SoObjects/SOGo/iCalEntityObject+Utilities.h>
#import <SoObjects/SOGo/LDAPUserManager.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
@ -400,8 +402,8 @@
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
owner: owner];
forUID: currentUID
owner: owner];
}
[self sendEMailUsingTemplateNamed: @"Update"
@ -425,8 +427,8 @@
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
owner: owner];
forUID: currentUID
owner: owner];
}
}
@ -483,8 +485,8 @@
currentUID = [currentAttendee uid];
if (currentUID)
[self _addOrUpdateEvent: newEvent
forUID: currentUID
owner: owner];
forUID: currentUID
owner: owner];
}
[self sendReceiptEmailUsingTemplateNamed: @"Update"
@ -635,7 +637,7 @@
delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length])
delegateEmail = [delegateEmail substringFromIndex: 7];
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
else
@ -664,7 +666,22 @@
}
if (removeDelegate)
[event removeFromAttendees: otherDelegate];
{
while (otherDelegate)
{
[event removeFromAttendees: otherDelegate];
// Verify if the delegate was already delegated
delegateEmail = [otherDelegate delegatedTo];
if ([delegateEmail length])
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
else
otherDelegate = NO;
}
}
if (addDelegate)
[event addToAttendees: delegate];
@ -721,7 +738,50 @@
ex = nil;
currentStatus = [attendee partStat];
if ([currentStatus caseInsensitiveCompare: newStatus]
iCalPerson *otherAttendee, *otherDelegate;
NSString *delegateEmail;
BOOL addDelegate, removeDelegate;
otherAttendee = attendee;
delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length])
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
else
otherDelegate = NO;
/* We handle the addition/deletion of delegated users */
addDelegate = NO;
removeDelegate = NO;
if (delegate)
{
if (otherDelegate)
{
// There was already a delegated
if (![delegate hasSameEmailAddress: otherDelegate])
{
// The delegated has changed
removeDelegate = YES;
addDelegate = YES;
}
}
else
// There was no previous delegated
addDelegate = YES;
}
else
{
if (otherDelegate)
// The user has removed the delegated
removeDelegate = YES;
}
if (addDelegate || removeDelegate
|| [currentStatus caseInsensitiveCompare: newStatus]
!= NSOrderedSame)
{
[attendee setPartStat: newStatus];
@ -745,7 +805,70 @@
// we don't want to keep the previous SENT-BY attribute there.
[(NSMutableDictionary *)[attendee attributes] removeObjectForKey: @"SENT-BY"];
}
[attendee setDelegatedTo: [delegate email]];
NSString *delegatedUID;
NSMutableArray *delegates;
if (removeDelegate)
{
delegates = [NSMutableArray new];
while (otherDelegate)
{
[delegates addObject: otherDelegate];
delegatedUID = [otherDelegate uid];
if (delegatedUID)
// Delegated attendee is a local user; remove event from his calendar
[self _removeEventFromUID: delegatedUID
owner: [theOwnerUser login]
withRecurrenceId: [event recurrenceId]];
[event removeFromAttendees: otherDelegate];
// Verify if the delegate was already delegated
delegateEmail = [otherDelegate delegatedTo];
if ([delegateEmail length])
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
else
otherDelegate = NO;
}
[self sendEMailUsingTemplateNamed: @"Deletion"
forObject: [event itipEntryWithMethod: @"cancel"]
previousObject: nil
toAttendees: delegates];
[self sendReceiptEmailUsingTemplateNamed: @"Deletion"
forObject: event
to: delegates];
[delegates release];
}
if (addDelegate)
{
delegatedUID = [delegate uid];
delegates = [NSArray arrayWithObject: delegate];
[event addToAttendees: delegate];
if (delegatedUID)
// Delegated attendee is a local user; add event to his calendar
[self _addOrUpdateEvent: event
forUID: delegatedUID
owner: [theOwnerUser login]];
[self sendEMailUsingTemplateNamed: @"Invitation"
forObject: [event itipEntryWithMethod: @"request"]
previousObject: nil
toAttendees: delegates];
[self sendReceiptEmailUsingTemplateNamed: @"Invitation"
forObject: event to: delegates];
}
// We generate the updated iCalendar file and we save it
// in the database.
newContent = [[event parent] versitString];
@ -794,7 +917,9 @@
{
att = [attendees objectAtIndex: i];
uid = [att uid];
if (uid && att != attendee)
if (uid
&& att != attendee
&& ![uid isEqualToString: delegatedUID])
[self _updateAttendee: attendee
withDelegate: delegate
ownerUser: theOwnerUser
@ -1097,7 +1222,6 @@
int i;
/* We update the copy of the organizer, only if it's a local user. */
#warning add a check for only local users
uid = [[event organizer] uid];
if (uid)
[self _updateAttendee: attendee
@ -1200,7 +1324,8 @@
- (NSException *) changeParticipationStatus: (NSString *) status
withDelegate: (iCalPerson *) delegate
{
return [self changeParticipationStatus: status withDelegate: delegate
return [self changeParticipationStatus: status
withDelegate: delegate
forRecurrenceId: nil];
}
@ -1240,27 +1365,37 @@
if (event)
{
// ownerUser will actually be the owner of the calendar
// where the participation change on the event has
// actually occured. The particpation change will of
// course be on the attendee that is the owner of the
// calendar where the participation change has occured.
// where the participation change on the event occurs. The particpation
// change will be on the attendee corresponding to the ownerUser.
ownerUser = [SOGoUser userWithLogin: owner];
attendee = [event findParticipant: ownerUser];
if (attendee)
ex = [self _handleAttendee: attendee
withDelegate: delegate
ownerUser: ownerUser
statusChange: _status
inEvent: event];
{
if (delegate
&& ![[delegate email] isEqualToString: [attendee delegatedTo]])
{
if ([event isParticipant: [[delegate email] rfc822Email]])
ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is a participant"];
else if ([SOGoGroup groupWithEmail: [[delegate email] rfc822Email]])
ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is a group"];
}
if (ex == nil)
ex = [self _handleAttendee: attendee
withDelegate: delegate
ownerUser: ownerUser
statusChange: _status
inEvent: event];
}
else
ex = [NSException exceptionWithHTTPStatus: 404 // Not Found
reason: @"user does not participate in this "
@"calendar event"];
reason: @"user does not participate in this calendar event"];
}
else
ex = [NSException exceptionWithHTTPStatus: 500 // Server Error
reason: @"unable to parse event record"];
reason: @"unable to parse event record"];
return ex;
}

View File

@ -1,2 +0,0 @@
<#IsSubject>Event Invitation Reply: <#summary/></#IsSubject>
<#IsBody><#attendee/><#HasSentBy> (sent by <#sentBy/>)</#HasSentBy> has <#HasAccepted>accepted</#HasAccepted><#HasDeclined>declined</#HasDeclined><#HasNotAcceptedNotDeclined>not yet decided upon</#HasNotAcceptedNotDeclined> your event invitation.</#IsBody>

View File

@ -1,39 +0,0 @@
IsSubject: WOConditional {
condition = isSubject;
}
IsBody: WOConditional {
condition = isSubject;
negate = YES;
}
summary: WOString {
value = summary;
escapeHTML = NO;
}
attendee: WOString {
value = attendeeName;
escapeHTML = NO;
}
HasAccepted: WOConditional {
condition = hasAccepted;
}
HasDeclined: WOConditional {
condition = hasDeclined;
}
HasNotAcceptedNotDeclined: WOConditional {
condition = hasNotAcceptedNotDeclined;
}
HasSentBy: WOConditional {
condition = hasSentBy;
}
sentBy: WOString {
value = sentBy;
escapeHTML = NO;
}

View File

@ -186,8 +186,13 @@ static NSCharacterSet *wsSet = nil;
- (NSString *) getBody
{
NSString *body;
isSubject = NO;
return [[self generateResponse] contentAsString];
body = [[self generateResponse] contentAsString];
return [body stringByTrimmingCharactersInSet: wsSet];
}
@end

View File

@ -761,7 +761,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence, NSStr
ownerUser = from;
language = [ownerUser language];
/* create page name */
pageName
pageName
= [NSString stringWithFormat: @"SOGoAptMail%@ICalReply", language];
/* construct message content */
p = [app pageWithName: pageName inContext: context];
@ -778,13 +778,13 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence, NSStr
* at all. Mail.app shows the rich content alternative _only_
* so we'll stick with multipart/mixed for the time being.
*/
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
[headerMap setObject: [attendee mailAddress] forKey: @"from"];
[headerMap setObject: [recipient mailAddress] forKey: @"to"];
mailDate = [[NSCalendarDate date] rfc822DateString];
[headerMap setObject: mailDate forKey: @"date"];
[headerMap setObject: [p getSubject] forKey: @"subject"];
[headerMap setObject: @"1.0" forKey: @"MIME-Version"];
[headerMap setObject: @"multipart/mixed" forKey: @"content-type"];
msg = [NGMimeMessage messageWithHeader: headerMap];
NSLog (@"sending 'REPLY' from %@ to %@",

View File

@ -251,6 +251,10 @@
data = @"";
[newRecord setObject: data forKey: @"c_telephonenumber"];
data = [oldRecord objectForKey: @"isGroup"];
if (data)
[newRecord setObject: data forKey: @"isGroup"];
contactInfo = [ud stringForKey: @"SOGoLDAPContactInfoAttribute"];
if ([contactInfo length] > 0) {
data = [oldRecord objectForKey: contactInfo];

View File

@ -658,14 +658,27 @@ static NSLock *lock;
NSMutableDictionary *contactEntry;
NSEnumerator *attributes;
NSString *currentAttribute, *value;
NSArray *classes;
contactEntry = [NSMutableDictionary dictionary];
[contactEntry setObject: [ldapEntry dn] forKey: @"dn"];
classes = [ldapEntry objectClasses];
if ([ldapEntry objectClasses])
[contactEntry setObject: [ldapEntry objectClasses]
forKey: @"objectClasses"];
attributes = [[self _searchAttributes] objectEnumerator];
if (classes)
{
[contactEntry setObject: classes
forKey: @"objectClasses"];
attributes = [[self _searchAttributes] objectEnumerator];
if ([classes containsObject: @"group"] ||
[classes containsObject: @"groupOfNames"] ||
[classes containsObject: @"groupOfUniqueNames"] ||
[classes containsObject: @"posixGroup"])
{
[contactEntry setObject: [NSNumber numberWithInt: 1]
forKey: @"isGroup"];
}
}
while ((currentAttribute = [attributes nextObject]))
{
value = [[ldapEntry attributeWithName: currentAttribute]
@ -887,10 +900,12 @@ static NSLock *lock;
- (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail
{
#warning We should support MailFieldNames
return [self lookupGroupEntryByAttribute: @"mail"
andValue: theEmail];
}
// This method should accept multiple attributes
- (NGLdapEntry *) lookupGroupEntryByAttribute: (NSString *) theAttribute
andValue: (NSString *) theValue
{
@ -911,9 +926,6 @@ static NSLock *lock;
EOQualifier *qualifier;
NSString *s;
// FIXME
// we should support MailFieldNames?
s = [NSString stringWithFormat: @"(%@='%@')",
theAttribute, SafeLDAPCriteria (theValue)];
qualifier = [EOQualifier qualifierWithQualifierFormat: s];

View File

@ -204,7 +204,7 @@
dn = [dns objectAtIndex: i];
login = [um getLoginForDN: [dn lowercaseString]];
//NSLog(@"member = %@", login);
user = [SOGoUser userWithLogin: login roles: nil];
user = [SOGoUser userWithLogin: login roles: nil];
if (user)
[array addObject: user];
}
@ -213,7 +213,7 @@
for (i = 0; i < [uids count]; i++)
{
login = [uids objectAtIndex: i];
NSLog(@"member = %@", login);
//NSLog(@"member = %@", login);
user = [SOGoUser userWithLogin: login roles: nil];
if (user)

View File

@ -50,6 +50,10 @@
"You don't have the required privileges to perform the operation."
= "You don't have the required privileges to perform the operation.";
"noEmailForDelegation" = "You must specify the address to which you want to delegate your invitation.";
"delegate is a participant" = "The delegate is already a participant.";
"delegate is a group" = "The specified address corresponds to a group. You can only delegate to a unique person.";
/* alarms */
"Reminder:" = "Reminder:";
"Start:" = "Start:";

View File

@ -195,11 +195,13 @@
NSMutableDictionary *uniqueContacts;
unsigned int i, j;
NSSortDescriptor *commonNameDescriptor;
BOOL excludeGroups;
searchText = [self queryParameterForKey: @"search"];
if ([searchText length] > 0)
{
NSLog(@"Search all contacts: %@", searchText);
excludeGroups = [[self queryParameterForKey: @"excludeGroups"] boolValue];
NS_DURING
folders = [[self clientObject] subFolders];
NS_HANDLER
@ -218,7 +220,7 @@
{
folder = [folders objectAtIndex: i];
if ([folder isKindOfClass: [SOGoContactLDAPFolder class]])
[sortedFolders insertObject: folder atIndex: 0];
[sortedFolders insertObject: folder atIndex: 0];
else
[sortedFolders addObject: folder];
}
@ -234,8 +236,10 @@
contact = [contacts objectAtIndex: j];
mail = [contact objectForKey: @"c_mail"];
//NSLog(@" found %@ (%@)", [contact objectForKey: @"displayName"], mail);
if ([mail isNotNull] && [uniqueContacts objectForKey: mail] == nil)
[uniqueContacts setObject: contact forKey: mail];
if ([mail isNotNull]
&& [uniqueContacts objectForKey: mail] == nil
&& !(excludeGroups && [contact objectForKey: @"isGroup"]))
[uniqueContacts setObject: contact forKey: mail];
}
}
if ([uniqueContacts count] > 0)

View File

@ -23,7 +23,9 @@
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSEnumerator.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NSNull+misc.h>
@ -199,183 +201,95 @@
return chosenEvent;
}
#warning this is code copied from SOGoAppointmentObject...
- (void) _updateAttendee: (iCalPerson *) attendee
ownerUser: (SOGoUser *) theOwnerUser
forEventUID: (NSString *) eventUID
withRecurrenceId: (NSCalendarDate *) recurrenceId
withSequence: (NSNumber *) sequence
forUID: (NSString *) uid
shouldAddSentBy: (BOOL) b
{
SOGoAppointmentObject *eventObject;
iCalCalendar *calendar;
iCalEvent *event;
iCalPerson *otherAttendee;
NSArray *events;
NSString *iCalString, *recurrenceTime;
eventObject = [self _eventObjectWithUID: eventUID
forUser: [SOGoUser userWithLogin: uid roles: nil]];
if (![eventObject isNew])
{
if (recurrenceId == nil)
{
// We must update main event and all its occurences (if any).
calendar = [eventObject calendar: NO secure: NO];
event = (iCalEvent *)[calendar firstChildWithTag: [eventObject componentTag]];
events = [calendar allObjects];
}
else
{
// If recurrenceId is defined, find the specified occurence
// within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
event = (iCalEvent *)[eventObject lookupOccurence: recurrenceTime];
if (event == nil)
// If no occurence found, create one
event = (iCalEvent *)[eventObject newOccurenceWithID: recurrenceTime];
events = [NSArray arrayWithObject: event];
}
if ([[event sequence] compare: sequence]
== NSOrderedSame)
{
SOGoUser *currentUser;
int i;
currentUser = [context activeUser];
for (i = 0; i < [events count]; i++)
{
event = [events objectAtIndex: i];
otherAttendee = [event findParticipant: theOwnerUser];
[otherAttendee setPartStat: [attendee partStat]];
// If one has accepted / declined an invitation on behalf of
// the attendee, we add the user to the SENT-BY attribute.
if (b && ![[currentUser login] isEqualToString: [theOwnerUser login]])
{
NSString *currentEmail;
currentEmail = [[currentUser allEmails] objectAtIndex: 0];
[otherAttendee addAttribute: @"SENT-BY"
value: [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail]];
}
else
{
// We must REMOVE any SENT-BY here. This is important since if A accepted
// the event for B and then, B changes by himself his participation status,
// we don't want to keep the previous SENT-BY attribute there.
[(NSMutableDictionary *)[otherAttendee attributes] removeObjectForKey: @"SENT-BY"];
}
}
iCalString = [[event parent] versitString];
[eventObject saveContentString: iCalString];
}
}
}
- (WOResponse *) _changePartStatusAction: (NSString *) newStatus
withDelegate: (iCalPerson *) delegate
{
WOResponse *response;
SOGoAppointmentObject *eventObject;
iCalEvent *chosenEvent;
iCalPerson *user;
iCalCalendar *emailCalendar, *calendar;
NSString *rsvp, *method, *organizerUID;
//NSException *ex;
chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
if (chosenEvent)
{
user = [chosenEvent findParticipant: [context activeUser]];
[user setPartStat: newStatus];
calendar = [chosenEvent parent];
emailCalendar = [[self _emailEvent] parent];
method = [[emailCalendar method] lowercaseString];
if ([method isEqualToString: @"request"])
{
[calendar setMethod: @""];
rsvp = [[user rsvp] lowercaseString];
}
else
rsvp = nil;
// We generate the updated iCalendar file and we save it
// in the database.
[eventObject saveContentString: [calendar versitString]];
// Send a notification to the organizer if necessary
if ([rsvp isEqualToString: @"true"] &&
[chosenEvent isStillRelevant])
[eventObject sendResponseToOrganizer: chosenEvent
from: [context activeUser]];
organizerUID = [[chosenEvent organizer] uid];
if (organizerUID)
// Update the event in the organizer's calendar
[self _updateAttendee: user
ownerUser: [context activeUser]
forEventUID: [chosenEvent uid]
withRecurrenceId: [chosenEvent recurrenceId]
withSequence: [chosenEvent sequence]
forUID: organizerUID
shouldAddSentBy: YES];
// We update the calendar of all participants that are
// local to the system. This is useful in case user A accepts
// invitation from organizer B and users C, D, E who are also
// attendees need to verify if A has accepted.
NSArray *attendees;
iCalPerson *att;
NSString *uid;
int i;
attendees = [chosenEvent attendees];
for (i = 0; i < [attendees count]; i++)
{
att = [attendees objectAtIndex: i];
if (att == user) continue;
uid = [[LDAPUserManager sharedUserManager]
getUIDForEmail: [att rfc822Email]];
if (uid)
{
[self _updateAttendee: user
ownerUser: [context activeUser]
forEventUID: [chosenEvent uid]
withRecurrenceId: [chosenEvent recurrenceId]
withSequence: [chosenEvent sequence]
forUID: uid
shouldAddSentBy: YES];
}
}
response = [self responseWith204];
response = (WOResponse*)[eventObject changeParticipationStatus: newStatus
withDelegate: delegate
forRecurrenceId: [chosenEvent recurrenceId]];
// if (ex)
// response = ex; //[self responseWithStatus: 500];
// else
if (!response)
response = [self responseWith204];
}
else
{
response = [context response];
[response setStatus: 409];
}
return response;
}
//- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
// inContext: (WOContext*) localContext
//{
// return YES;
//}
- (WOResponse *) acceptAction
{
return [self _changePartStatusAction: @"ACCEPTED"];
return [self _changePartStatusAction: @"ACCEPTED"
withDelegate: nil];
}
- (WOResponse *) declineAction
{
return [self _changePartStatusAction: @"DECLINED"];
return [self _changePartStatusAction: @"DECLINED"
withDelegate: nil];
}
- (WOResponse *) delegateAction
{
// BOOL receiveUpdates;
NSString *delegatedEmail, *delegatedUid;
iCalPerson *delegatedAttendee;
SOGoUser *user;
WORequest *request;
WOResponse *response;
request = [context request];
delegatedEmail = [request formValueForKey: @"to"];
if ([delegatedEmail length])
{
user = [context activeUser];
delegatedAttendee = [iCalPerson new];
[delegatedAttendee setEmail: delegatedEmail];
delegatedUid = [delegatedAttendee uid];
if (delegatedUid)
{
SOGoUser *delegatedUser;
delegatedUser = [SOGoUser userWithLogin: delegatedUid];
[delegatedAttendee setCn: [delegatedUser cn]];
}
[delegatedAttendee setRole: @"REQ-PARTICIPANT"];
[delegatedAttendee setRsvp: @"TRUE"];
[delegatedAttendee setParticipationStatus: iCalPersonPartStatNeedsAction];
[delegatedAttendee setDelegatedFrom:
[NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]];
// receiveUpdates = [[request formValueForKey: @"receiveUpdates"] boolValue];
// if (receiveUpdates)
// [delegatedAttendee setRole: @"NON-PARTICIPANT"];
response = [self _changePartStatusAction: @"DELEGATED"
withDelegate: delegatedAttendee];
}
else
response = [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'to' parameter"];
return response;
}
- (WOResponse *) addToCalendarAction

View File

@ -376,6 +376,18 @@
return [[self authorativeEvent] userIsParticipant: [context activeUser]];
}
- (NSString *) currentAttendeeClass
{
NSString *cssClass;
cssClass = [[attendee partStatWithDefault] lowercaseString];
if ([[attendee rfc822Email] isEqualToString: [self loggedInUserEMail]])
cssClass = [cssClass stringByAppendingString: @" attendeeUser"];
return cssClass;
}
/* derived fields */
- (NSString *) organizerDisplayName

View File

@ -28,6 +28,11 @@
actionClass = "UIxMailPartICalActions";
actionName = "decline";
};
delegate = {
protectedBy = "View";
actionClass = "UIxMailPartICalActions";
actionName = "delegate";
};
updateUserStatus = {
protectedBy = "View";
actionClass = "UIxMailPartICalActions";

View File

@ -246,7 +246,7 @@
"partStat_ACCEPTED" = "Accepted";
"partStat_DECLINED" = "Declined";
"partStat_TENTATIVE" = "Tentative";
"partStat_DELEGATED" = "Delegated";
"partStat_DELEGATED" = "Delegated to";
"partStat_OTHER" = "Other";
/* Appointments (error messages) */

View File

@ -46,6 +46,7 @@
#import <SoObjects/SOGo/SOGoDateFormatter.h>
#import <SoObjects/SOGo/SOGoContentObject.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/Appointments/iCalPerson+SOGo.h>
#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
#import <SoObjects/Appointments/SOGoAppointmentObject.h>
#import <SoObjects/Appointments/SOGoAppointmentOccurence.h>
@ -530,4 +531,52 @@
return self;
}
- (id) delegateAction
{
// BOOL receiveUpdates;
NSString *delegatedEmail, *delegatedUid;
iCalPerson *delegatedAttendee;
SOGoUser *user;
WORequest *request;
WOResponse *response;
response = nil;
request = [context request];
delegatedEmail = [request formValueForKey: @"to"];
if ([delegatedEmail length])
{
user = [context activeUser];
delegatedAttendee = [iCalPerson new];
[delegatedAttendee setEmail: delegatedEmail];
delegatedUid = [delegatedAttendee uid];
if (delegatedUid)
{
SOGoUser *delegatedUser;
delegatedUser = [SOGoUser userWithLogin: delegatedUid];
[delegatedAttendee setCn: [delegatedUser cn]];
}
[delegatedAttendee setRole: @"REQ-PARTICIPANT"];
[delegatedAttendee setRsvp: @"TRUE"];
[delegatedAttendee setParticipationStatus: iCalPersonPartStatNeedsAction];
[delegatedAttendee setDelegatedFrom:
[NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]];
// receiveUpdates = [[request formValueForKey: @"receiveUpdates"] boolValue];
// if (receiveUpdates)
// [delegatedAttendee setRole: @"NON-PARTICIPANT"];
response = (WOResponse*)[[self clientObject] changeParticipationStatus: @"DELEGATED"
withDelegate: delegatedAttendee];
}
else
response = [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'to' parameter"];
if (!response)
response = [self responseWithStatus: 200];
return response;
}
@end

View File

@ -38,6 +38,7 @@
{
iCalRepeatableEntityObject *component;
id item;
id attendee;
NSString *saveURL;
NSMutableArray *calendarList;
@ -59,13 +60,11 @@
NSDictionary *cycle;
NSString *cycleEnd;
iCalPerson *organizer;
iCalPerson *ownerAsAttendee;
NSString *componentOwner;
NSString *dateFormat;
NSString *attendeesNames;
NSString *attendeesUIDs;
NSString *attendeesEmails;
NSString *attendeesStates;
NSMutableDictionary *jsonAttendees;
NSString *reminder;
NSString *reminderQuantity;
@ -121,7 +120,6 @@
- (NSString *) privacy;
- (NSString *) itemPrivacyText;
- (NSArray *) statusTypes;
- (void) setStatus: (NSString *) _status;
- (NSString *) status;
- (NSString *) itemStatusText;
@ -142,14 +140,7 @@
- (BOOL) hasAttendees;
- (void) setAttendeesNames: (NSString *) newAttendeesNames;
- (NSString *) attendeesNames;
- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs;
- (NSString *) attendeesUIDs;
- (void) setAttendeesEmails: (NSString *) newAttendeesEmails;
- (NSString *) attendeesEmails;
- (NSString *) jsonAttendees;
- (NSString *) repeat;
- (void) setRepeat: (NSString *) newRepeat;

View File

@ -58,6 +58,7 @@
#import <SoObjects/SOGo/LDAPUserManager.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import <SoObjects/SOGo/NSScanner+BSJSONAdditions.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
@ -168,10 +169,9 @@ iRANGE(2);
componentOwner = @"";
organizer = nil;
//organizerIdentity = nil;
attendeesNames = nil;
attendeesUIDs = nil;
attendeesEmails = nil;
attendeesStates = nil;
ownerAsAttendee = nil;
attendee = nil;
jsonAttendees = nil;
calendarList = nil;
repeat = nil;
reminder = nil;
@ -202,16 +202,15 @@ iRANGE(2);
[location release];
[organizer release];
//[organizerIdentity release];
[ownerAsAttendee release];
[comment release];
[priority release];
[categories release];
[cycle release];
[cycleEnd release];
[attachUrl release];
[attendeesNames release];
[attendeesUIDs release];
[attendeesEmails release];
[attendeesStates release];
[attendee release];
[jsonAttendees release];
[calendarList release];
[reminder release];
@ -241,42 +240,44 @@ iRANGE(2);
- (void) _loadAttendees
{
NSEnumerator *attendees;
NSMutableDictionary *currentAttendeeData;
iCalPerson *currentAttendee;
NSMutableString *names, *uids, *emails, *states;
NSString *uid;
LDAPUserManager *um;
names = [NSMutableString string];
uids = [NSMutableString string];
emails = [NSMutableString string];
states = [NSMutableString string];
jsonAttendees = [NSMutableDictionary new];
um = [LDAPUserManager sharedUserManager];
attendees = [[component attendees] objectEnumerator];
while ((currentAttendee = [attendees nextObject]))
{
if ([[currentAttendee cn] length])
[names appendFormat: @"%@,", [currentAttendee cn]];
else
[names appendFormat: @"%@,", [currentAttendee rfc822Email]];
currentAttendeeData = [NSMutableDictionary dictionary];
[emails appendFormat: @"%@,", [currentAttendee rfc822Email]];
if ([[currentAttendee cn] length])
[currentAttendeeData setObject: [currentAttendee cn]
forKey: @"name"];
[currentAttendeeData setObject: [currentAttendee rfc822Email]
forKey: @"email"];
uid = [um getUIDForEmail: [currentAttendee rfc822Email]];
if (uid != nil)
[uids appendFormat: @"%@,", uid];
else
[uids appendString: @","];
[states appendFormat: @"%@,",
[[currentAttendee partStat] lowercaseString]];
}
[currentAttendeeData setObject: uid
forKey: @"uid"];
if ([names length] > 0)
{
ASSIGN (attendeesNames, [names substringToIndex: [names length] - 1]);
ASSIGN (attendeesUIDs, [uids substringToIndex: [uids length] - 1]);
ASSIGN (attendeesEmails,
[emails substringToIndex: [emails length] - 1]);
ASSIGN (attendeesStates, [states substringToIndex: [states length] - 1]);
[currentAttendeeData setObject: [[currentAttendee partStat] lowercaseString]
forKey: @"partstat"];
if ([[currentAttendee delegatedTo] length])
[currentAttendeeData setObject: [[currentAttendee delegatedTo] rfc822Email]
forKey: @"delegated-to"];
if ([[currentAttendee delegatedFrom] length])
[currentAttendeeData setObject: [[currentAttendee delegatedFrom] rfc822Email]
forKey: @"delegated-from"];
[jsonAttendees setObject: currentAttendeeData
forKey: [currentAttendee rfc822Email]];
}
}
@ -526,6 +527,8 @@ iRANGE(2);
{
// iCalRecurrenceRule *rrule;
SOGoObject *co;
LDAPUserManager *um;
NSString *owner, *ownerEmail;
if (!component)
{
@ -545,6 +548,7 @@ iRANGE(2);
ASSIGN (categories,
[[component categories] componentsWithSafeSeparator: ',']);
ASSIGN (organizer, [component organizer]);
[self _loadCategories];
[self _loadAttendees];
[self _loadRRules];
@ -555,6 +559,11 @@ iRANGE(2);
if ([componentCalendar isKindOfClass: [SOGoCalendarComponent class]])
componentCalendar = [componentCalendar container];
[componentCalendar retain];
um = [LDAPUserManager sharedUserManager];
owner = [componentCalendar ownerInContext: context];
ownerEmail = [um getEmailForUID: owner];
ASSIGN (ownerAsAttendee, [component findParticipantWithEmail: (id)ownerEmail]);
}
}
// /* cycles */
@ -750,44 +759,32 @@ iRANGE(2);
return ([[component attendees] count] > 0);
}
- (void) setAttendeesNames: (NSString *) newAttendeesNames
- (void) setAttendee: (id) _attendee
{
ASSIGN (attendeesNames, newAttendeesNames);
ASSIGN (attendee, _attendee);
}
- (NSString *) attendeesNames
- (id) attendee
{
return attendeesNames;
return attendee;
}
- (void) setAttendeesUIDs: (NSString *) newAttendeesUIDs
- (NSString *) attendeeForDisplay
{
ASSIGN (attendeesUIDs, newAttendeesUIDs);
NSString *fn, *result;
fn = [attendee cnWithoutQuotes];
if ([fn length])
result = fn;
else
result = [attendee rfc822Email];
return result;
}
- (NSString *) attendeesUIDs
- (NSString *) jsonAttendees
{
return attendeesUIDs;
}
- (void) setAttendeesEmails: (NSString *) newAttendeesEmails
{
ASSIGN (attendeesEmails, newAttendeesEmails);
}
- (NSString *) attendeesEmails
{
return attendeesEmails;
}
- (void) setAttendeesStates: (NSString *) newAttendeesStates
{
ASSIGN (attendeesStates, newAttendeesStates);
}
- (NSString *) attendeesStates
{
return attendeesStates;
return [jsonAttendees jsonRepresentation];
}
- (void) setLocation: (NSString *) _value
@ -971,6 +968,10 @@ iRANGE(2);
word = @"ACCEPTED";
else if ([item intValue] == iCalPersonPartStatDeclined)
word = @"DECLINED";
else if ([item intValue] == iCalPersonPartStatDelegated)
word = @"DELEGATED";
else
word = @"UNKNOWN";
return [self labelForKey: [NSString stringWithFormat: @"partStat_%@", word]];
}
@ -978,21 +979,18 @@ iRANGE(2);
- (NSArray *) replyList
{
return [NSArray arrayWithObjects:
[NSNumber numberWithInt: iCalPersonPartStatAccepted],
[NSNumber numberWithInt: iCalPersonPartStatDeclined], nil];
[NSNumber numberWithInt: iCalPersonPartStatAccepted],
[NSNumber numberWithInt: iCalPersonPartStatDeclined],
[NSNumber numberWithInt: iCalPersonPartStatDelegated],
nil];
}
- (NSNumber *) reply
{
iCalPersonPartStat participationStatus;
LDAPUserManager *um;
NSString *owner, *ownerEmail;
um = [LDAPUserManager sharedUserManager];
owner = [componentCalendar ownerInContext: context];
ownerEmail = [um getEmailForUID: owner];
// We assume the owner is part of the participants
participationStatus = [[component findParticipantWithEmail: (id)ownerEmail] participationStatus];
participationStatus = [ownerAsAttendee participationStatus];
return [NSNumber numberWithInt: participationStatus];
}
@ -1143,19 +1141,6 @@ iRANGE(2);
return privacy;
}
- (NSArray *) statusTypes
{
static NSArray *statusTypes = nil;
if (!statusTypes)
{
statusTypes = [NSArray arrayWithObjects: @"", @"TENTATIVE", @"CONFIRMED", @"CANCELLED", nil];
[statusTypes retain];
}
return statusTypes;
}
- (void) setStatus: (NSString *) _status
{
ASSIGN (status, _status);
@ -1499,26 +1484,35 @@ RANGE(2);
- (void) _handleAttendeesEdition
{
NSArray *names, *emails;
NSMutableArray *newAttendees;
unsigned int count, max;
NSString *currentEmail;
iCalPerson *currentAttendee;
NSString *json;
NSDictionary *attendeesData;
NSArray *attendees;
NSDictionary *currentData;
NSScanner *jsonScanner;
WORequest *request;
newAttendees = [NSMutableArray new];
if ([attendeesNames length] > 0)
request = [context request];
json = [request formValueForKey: @"attendees"];
attendees = [NSArray array];
jsonScanner = [NSScanner scannerWithString: json];
if ([jsonScanner scanJSONObject: &attendeesData])
{
names = [attendeesNames componentsSeparatedByString: @","];
emails = [attendeesEmails componentsSeparatedByString: @","];
max = [emails count];
newAttendees = [NSMutableArray new];
attendees = [attendeesData allValues];
max = [attendees count];
for (count = 0; count < max; count++)
{
currentEmail = [emails objectAtIndex: count];
currentData = [attendees objectAtIndex: count];
currentEmail = [currentData objectForKey: @"email"];
currentAttendee = [component findParticipantWithEmail: currentEmail];
if (!currentAttendee)
{
currentAttendee = [iCalPerson elementWithTag: @"attendee"];
[currentAttendee setCn: [names objectAtIndex: count]];
[currentAttendee setCn: [currentData objectForKey: @"name"]];
[currentAttendee setEmail: currentEmail];
[currentAttendee setRole: @"REQ-PARTICIPANT"];
[currentAttendee setRsvp: @"TRUE"];
@ -1527,10 +1521,11 @@ RANGE(2);
}
[newAttendees addObject: currentAttendee];
}
[component setAttendees: newAttendees];
[newAttendees release];
}
[component setAttendees: newAttendees];
[newAttendees release];
else
NSLog(@"Error scanning following JSON:\n%@", json);
}
- (void) _handleOrganizer

View File

@ -230,6 +230,11 @@
pageName = "UIxAppointmentEditor";
actionName = "decline";
};
delegate = {
protectedBy = "RespondToComponent";
actionClass = "UIxAppointmentEditor";
actionName = "delegate";
};
};
};

View File

@ -0,0 +1,17 @@
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE container>
<container
xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label">
<var:if condition="isSubject">
[Event Invitation Reply] <var:string value="summary" const:escapeHTML="NO" />
</var:if>
<var:if condition="isSubject" const:negate="YES">
<var:string value="attendeeName" /><var:if condition="hasSentBy"> (sent by <var:string value="sentBy" />)</var:if> has <var:if condition="attendee.partStatWithDefault" const:value="DELEGATED">delegated the invitation to <var:string value="attendee.delegatedTo.rfc822Email" />.</var:if><var:if condition="attendee.partStatWithDefault" const:value="DELEGATED" const:negate="YES"><var:if condition="hasAccepted">accepted</var:if><var:if condition="hasDeclined">declined</var:if><var:if condition="hasNotAcceptedNotDeclined">not yet decided upon</var:if> your event invitation.</var:if>
</var:if>
</container>

View File

@ -10,7 +10,7 @@
<!-- TODO: add iMIP actions -->
<input id="iCalendarAttachment" type="hidden"
var:value="pathToAttachment"/>
var:value="pathToAttachment"/>
<var:if condition="couldParseCalendar" const:negate="1">
<fieldset>
@ -23,10 +23,13 @@
</var:if>
<var:if condition="couldParseCalendar">
<div class="popupMenu" id="contactsMenu">
<ul></ul>
</div>
<fieldset>
<legend>
<var:string label:value="Appointment"/>:
<var:string value="inEvent.summary" /> <!-- TODO: shorted title -->
<strong><var:string value="inEvent.summary" /></strong> <!-- TODO: shorted title -->
<var:if condition="isLoggedInUserTheOrganizer">
(<var:string label:value="organized_by_you"/>)
@ -51,6 +54,25 @@
<input id="iCalendarDecline" class="button"
type="button" label:value="Decline"/>
</var:if>
<var:if condition="currentUserAttendee.partStatWithDefault"
const:value="DELEGATED" const:negate="YES">
<input id="editDelegate" class="button"
type="button" label:value="Delegate ..." />
<span id="delegateEditor" style="display: none;">
<var:string label:value="Delegate to" />
<input name="delegatedTo" id="delegatedTo" type="text" />
<input class="button" type="button" id="iCalendarDelegate" name="delegateOK" value="OK" />
</span>
</var:if>
<var:if condition="currentUserAttendee.partStatWithDefault"
const:value="DELEGATED">
<span id="delegateEditor">
<var:string label:value="Delegate to" />
<input name="delegatedTo" id="delegatedTo" type="text" var:value="currentUserAttendee.delegatedTo.rfc822Email" var:uid="currentUserAttendee.delegatedTo.rfc822Email" style="display: none;" />
<a var:href="currentUserAttendee.delegatedTo" name="delegatedToLink" id="delegatedToLink"><var:string value="currentUserAttendee.delegatedTo.rfc822Email" /></a>
<input class="button" type="button" id="iCalendarDelegate" name="delegateOK" value="OK" />
</span>
</var:if>
<!-- <input id="iCalendarTentative" class="button"
type="button" label:value="Tentative"/> -->
<var:if condition="isEventStoredInCalendar" const:negate="YES">
@ -197,11 +219,12 @@
<tr>
<td valign="top"><var:string label:value="Attendees"/>:</td>
<td>
<td id="iCalAttendees">
<var:foreach list="authorativeEvent.participants" item="attendee">
<a var:href="attendee.email"><var:string value="attendeeForDisplay"/></a>
(<var:string label:value="$attendee.partStatWithDefault" />)
<br />
<var:if condition="attendee.delegatedTo" const:negate="YES"><!-- don't show attendees that delegated the invitation --><span var:class="currentAttendeeClass"><a var:href="attendee.email">
<var:string value="attendeeForDisplay"/></a>
(<var:string label:value="$attendee.partStatWithDefault" /><var:if condition="attendee.delegatedTo"> <var:string label:value="to" /> <var:string value="attendee.delegatedTo.rfc822Email" /></var:if><var:if condition="attendee.delegatedFrom">, <var:string label:value="delegated from" /> <var:string value="attendee.delegatedFrom.rfc822Email" /></var:if>)</span>
<br /></var:if>
</var:foreach>
</td>
</tr>

View File

@ -55,8 +55,8 @@
<div><table id="freeBusyAttendees" cellspacing="0" cellpadding="0">
<tbody>
<tr class="futureAttendee"
><td class="attendees"><input type="text" class="textField"
readonly="readonly" label:value="newAttendee" /></td
><td class="attendees"><a href="#" class="button"
readonly="readonly"><var:string label:value="newAttendee" /></a></td
></tr>
<tr class="attendeeModel"
><td class="attendees"><input type="text" class="textField" /></td

View File

@ -12,13 +12,14 @@
title="name"
var:toolbar="toolbar"
const:cssFiles="UIxComponentEditor.css"
const:jsFiles="skycalendar.js,UIxComponentEditor.js">
const:jsFiles="skycalendar.js,UIxComponentEditor.js,SOGoAutoCompletion.js">
<script type="text/javascript">
var activeCalendar = '<var:string value="clientObject.container.nameInContainer"/>';
var activeComponent = '<var:string value="clientObject.nameInContainer"/>';
var readOnly = <var:if condition="eventIsReadOnly">true</var:if><var:if condition="eventIsReadOnly"
const:negate="YES">false</var:if>;
var attendees = <var:string value="jsonAttendees" const:escapeHTML="NO" />;
</script>
<var:if condition="eventIsReadOnly" const:negate="YES">
@ -106,14 +107,7 @@
<input type="hidden" name="attach" id="attach" var:value="attach"/>
<input type="hidden" name="privacy" id="privacy"
var:value="privacy"/>
<input type="hidden" name="attendeesNames" id="attendeesNames"
var:value="attendeesNames"/>
<input type="hidden" name="attendeesUIDs" id="attendeesUIDs"
var:value="attendeesUIDs"/>
<input type="hidden" name="attendeesEmails" id="attendeesEmails"
var:value="attendeesEmails"/>
<input type="hidden" name="attendeesStates" id="attendeesStates"
var:value="attendeesStates"/>
<input type="hidden" name="attendees" id="attendees" />
<input type="hidden" name="calendarFoldersList"
id="calendarFoldersList"
var:value="calendarsFoldersList"/>
@ -166,6 +160,9 @@
</form>
</var:if>
<var:if condition="eventIsReadOnly">
<div class="popupMenu" id="contactsMenu">
<ul></ul>
</div>
<label class="calendarName"><var:string var:value="componentCalendarName" />
</label>
<div id="eventView">
@ -215,6 +212,23 @@
string="itemReplyText"
var:selection="reply" /></span>
</label>
<var:if condition="ownerAsAttendee.delegatedTo" const:negate="YES">
<label id="delegateEditor" style="display: none;">
<span class="content">
<input name="delegatedTo" id="delegatedTo" type="text" var:value="ownerAsAttendee.delegatedTo" var:uid="delegatedTo" />
<br />
<input type="checkbox" name="delegateReceiveUpdates" /> <var:string label:value="Keep sending me updates" />
</span>
</label>
</var:if><var:if condition="ownerAsAttendee.delegatedTo">
<label id="delegateEditor">
<span class="content">
<input name="delegatedTo" id="delegatedTo" type="text" var:value="ownerAsAttendee.delegatedTo.rfc822Email" var:uid="ownerAsAttendee.delegatedTo.rfc822Email" />
<br />
<input type="checkbox" name="delegateReceiveUpdates" /> <var:string label:value="Keep sending me updates" />
</span>
</label>
</var:if>
<!--TODO: I'm not sure how to send this
<label><var:string label:value="Reminder:" />
<span class="content"><var:popup list="reminderList" item="item"
@ -235,6 +249,9 @@
</label>
<label id="attendeesLabel">
<span class="content"><div id="attendeesMenu" class="fakeTextArea">
<var:foreach list="component.participants" item="attendee">
<var:if condition="attendee.delegatedTo" const:negate="YES"><div var:class="attendee.partStatWithDefault.lowercaseString"><a var:href="attendee.email" var:email="attendee.email.rfc822Email"><var:string value="attendeeForDisplay" /></a><var:if condition="attendee.delegatedFrom"> (<var:string label:value="delegated from" /> <var:string value="attendee.delegatedFrom.rfc822Email" />)</var:if></div></var:if>
</var:foreach>
</div></span>
</label>
</div>

View File

@ -50,6 +50,6 @@
/></span
></var:if>
</var:foreach>
<img id="progressIndicator" rsrc:src="busy.gif" />
<img id="progressIndicator" rsrc:src="busy.gif" />
</div>
</var:if>

View File

@ -310,44 +310,6 @@ tr.mailer_listcell_regular td a
text-decoration: none;
}
IMG.mailerReadIcon
{
/* TODO
*/
}
DIV.mailer_readicon
{
/* TODO: use Thunderbird icon */
background-image: url(icon_read.gif);
background-repeat: no-repeat;
background-position: 0px 4px;
}
DIV.mailer_readicon a
{
width: 17px;
height: 17px;
margin: 0px auto;
display: block;
}
DIV.mailerUnreadIcon
{
/* TODO: use Thunderbird icon */
background-image: url(icon_unread.gif);
background-repeat: no-repeat;
background-position: 0px 4px;
}
DIV.mailer_unreadicon a
{
width: 17px;
height: 17px;
margin: 0px auto;
display: block;
}
/* fields (key/value UI), eg used in mail viewer */
INPUT#editDraftButton
{
@ -814,3 +776,40 @@ DIV.copy
background-image: url(message-copy.gif) !important;
background-position: 1px -2px !important;
}
/* UIxMailPartICalViewer */
#iCalAttendees SPAN
{ background-repeat: no-repeat;
background-position: 5px center;
padding-left: 22px;
padding-right: 4px; }
#iCalAttendees .accepted
{ background-image: url("accepted.png"); }
#iCalAttendees .needs-action
{ background-image: url("needs-action.png"); }
#iCalAttendees .declined
{ background-image: url("declined.png"); }
#iCalAttendees .delegated
{ background-image: url("delegated.png"); }
#iCalAttendees .attendeeUser,
#iCalAttendees .attendeeUser A
{ font-weight: bold; }
#delegateEditor
{ padding-left: 5px; }
#delegatedTo
{ width: 220px; }
#delegatedTo
{ background-image: url("abcard.gif");
background-repeat: no-repeat;
background-position: 4px center;
padding: 2px 2px 2px 24px;
width: 220px; }

View File

@ -511,9 +511,15 @@ function onMailboxMenuMove(event) {
rows[i].hide();
uids.push(uid);
paths.push(path);
// Remove references to closed popups
for (var j = Mailer.popups.length - 1; j > -1; j--)
if (!Mailer.popups[j].open || Mailer.popups[j].closed)
Mailer.popups.splice(j,1);
// Close message popup if opened
for (var j = 0; j < Mailer.popups.length; j++)
if (Mailer.popups[j].messageUID == path) {
Mailer.popups[j].close();
Mailer.popups.splice(j,1);
break;
}
}
@ -670,7 +676,7 @@ function messageListCallback(http) {
addressHeaderCell.setAttribute("id", newRows[0].cells[addrIndex].getAttribute("id"));
table.replaceChild(tmp.firstChild.tBodies[0], tbody);
configureMessageListEvents(table);
}
}
else {
// Add table
div.update(http.responseText);
@ -1012,6 +1018,46 @@ function configureiCalLinksInMessage() {
onICalendarButtonClick.bindAsEventListener(button));
}
}
var button = $("iCalendarDelegate");
if (button) {
button.observe("click", onICalendarDelegate);
var delegatedTo = $("delegatedTo");
delegatedTo.addInterface(SOGoAutoCompletionInterface);
delegatedTo.uidField = "c_mail";
delegatedTo.excludeGroups = true;
var editDelegate = $("editDelegate");
if (editDelegate)
// The user delegates the invitation
editDelegate.observe("click", function(event) {
$("delegateEditor").show();
$("delegatedTo").focus();
this.hide();
});
var delegatedToLink = $("delegatedToLink");
if (delegatedToLink) {
// The user already delegated the invitation and wants
// to change the delegated attendee
delegatedToLink.stopObserving("click");
delegatedToLink.observe("click", function(event) {
$("delegatedTo").show();
$("delegatedTo").focus();
this.hide();
Event.stop(event);
});
}
}
}
function onICalendarDelegate(event) {
var link = $("iCalendarAttachment").value;
if (link) {
var currentMsg = Mailer.currentMailbox + "/"
+ Mailer.currentMessages[Mailer.currentMailbox];
delegateInvitation(link, ICalendarButtonCallback, currentMsg);
}
}
function onICalendarButtonClick(event) {
@ -1041,8 +1087,13 @@ function ICalendarButtonCallback(http) {
break;
}
}
else if (http.status == 403) {
var data = http.responseText;
var msg = data.replace(/^(.*\n)*.*<p>((.*\n)*.*)<\/p>(.*\n)*.*$/, "$2");
window.alert(clabels[msg]?clabels[msg]:msg);
}
else
window.alert("received code: " + http.status);
window.alert("received code: " + http.status + "\nerror: " + http.responseText);
}
function resizeMailContent() {

View File

@ -4,6 +4,7 @@
// using this interface.
var SOGoAutoCompletionInterface = {
uidField: "c_name",
excludeGroups: false,
animationParent: null,
selectedIndex: -1,
delay: 0.750,
@ -90,7 +91,9 @@ var SOGoAutoCompletionInterface = {
if (input.value.trim().length > 2) {
var urlstr = (UserFolderURL + "Contacts/allContactSearch?search="
+ encodeURIComponent(input.value));
if (input.animationParent)
if (input.excludeGroups)
urlstr += "&excludeGroups=1";
if (input.animationParent)
startAnimation(input.animationParent);
document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, input.performSearchCallback.bind(input), input);

View File

@ -183,15 +183,17 @@ function deleteEvent() {
return false;
}
function modifyEvent(sender, modification) {
function modifyEvent(sender, modification, parameters) {
var currentLocation = '' + window.location;
var arr = currentLocation.split("/");
arr[arr.length-1] = modification;
document.modifyEventAjaxRequest = triggerAjaxRequest(arr.join("/"),
modifyEventCallback,
modification);
modification,
parameters,
{ "Content-type": "application/x-www-form-urlencoded" });
return false;
}
@ -225,6 +227,11 @@ function modifyEventCallback(http) {
window.setTimeout("window.close();", 100);
}
}
else if (http.status == 403) {
var data = http.responseText;
var msg = data.replace(/^(.*\n)*.*<p>((.*\n)*.*)<\/p>(.*\n)*.*$/, "$2");
window.alert(clabels[msg]?clabels[msg]:msg);
}
else {
// log("showing alert...");
window.alert(labels["eventPartStatModificationError"]);

View File

@ -124,7 +124,8 @@ A#attendeesHref
DIV#attendeesMenu LI
{ padding-left: 10px; }
DIV#attendeesMenu .attendee
DIV#attendeesMenu .attendee,
DIV#attendeesMenu DIV
{ background-repeat: no-repeat;
background-position: 5px center;
padding-left: 22px; }
@ -138,10 +139,12 @@ DIV#attendeesMenu .needs-action
DIV#attendeesMenu .declined
{ background-image: url("declined.png"); }
DIV#attendeesMenu .delegated
{ background-image: url("delegated.png"); }
DIV#attendeesMenu .delegate
{ margin-left: 10px; }
/* read-only view */
DIV#attendeesMenu DIV
{ cursor: pointer;
padding-left: 20px; }
DIV#attendeesMenu DIV:hover
{ text-decoration: underline; }
{ padding-left: 20px; }

View File

@ -99,44 +99,6 @@ function validateAptEditor() {
return true;
}
function toggleDetails() {
var div = $("details");
var buttons = $("buttons");
var buttonsHeight = buttons.clientHeight * 3;
if (div.style.visibility) {
div.style.visibility = null;
window.resizeBy(0, -(div.clientHeight + buttonsHeight));
$("detailsButton").innerHTML = labels["Show Details"];
} else {
div.style.visibility = 'visible;';
window.resizeBy(0, (div.clientHeight + buttonsHeight));
$("detailsButton").innerHTML = labels["Hide Details"];
}
return false;
}
function toggleCycleVisibility(node, nodeName, hiddenValue) {
var spanNode = $(nodeName);
var newVisibility = ((node.value == hiddenValue) ? null : 'visible;');
spanNode.style.visibility = newVisibility;
if (nodeName == 'cycleSelectionFirstLevel') {
var otherSpanNode = $('cycleSelectionSecondLevel');
if (!newVisibility)
{
otherSpanNode.superVisibility = otherSpanNode.style.visibility;
otherSpanNode.style.visibility = null;
}
else
{
otherSpanNode.style.visibility = otherSpanNode.superVisibility;
otherSpanNode.superVisibility = null;
}
}
}
function onAttendeesMenuPrepareVisibility()
{
var composeToUndecidedAttendees = $('composeToUndecidedAttendees');
@ -201,8 +163,10 @@ function addContact(tag, fullContactName, contactId, contactName, contactEmail)
}
function saveEvent(sender) {
if (validateAptEditor())
if (validateAptEditor()) {
document.forms['editform'].attendees.value = attendees.toJSON();
document.forms['editform'].submit();
}
return false;
}
@ -335,51 +299,22 @@ function initTimeWidgets(widgets) {
}
function refreshAttendeesRO () {
var attendeesNames = $("attendeesNames").value;
var attendeesEmails = $("attendeesEmails").value.split(",");
var attendeesStates = $("attendeesStates").value.split(",");
var attendeesMenu = $("attendeesMenu");
var attendeesLabel = $("attendeesLabel");
if (attendeesMenu) {
for (var i = 0; i < attendeesMenu.childNodes.length; i++)
attendeesMenu.removeChild (attendeesMenu.childNodes[i]);
}
if (attendeesNames.length > 0) {
// Update attendees link and show label
if (attendeesLabel)
attendeesLabel.setStyle({ display: "block" });
if ($("attendeesDiv"))
$("attendeesDiv").setStyle({display: "block"});
// Update attendees in menu
attendeesNames = attendeesNames.split(",");
for (var i = 0; i < attendeesEmails.length; i++) {
var node = document.createElement("div");
if (attendeesMenu)
attendeesMenu.appendChild(node);
$(node).writeAttribute("email", attendeesEmails[i]);
$(node).addClassName("attendee");
$(node).addClassName(attendeesStates[i]);
node.appendChild(document.createTextNode(attendeesNames[i]));
$(node).observe("click", onMailTo);
}
}
else {
// Hide link of attendees
attendeesLabel.setStyle({ display: "none" });
if ($("attendeesDiv"))
$("attendeesDiv").setStyle({display: "none"});
}
var attendeesDiv = $("attendeesDiv");
attendeesLabel.setStyle({ display: "block" });
attendeesDiv.setStyle({display: "block"});
// Register "click" event on each attendee's email
var attendees = attendeesMenu.getElementsByTagName('a');
$A(attendees).each(function(attendee) {
$(attendee).observe("click", onMailTo);
});
}
function refreshAttendees() {
function refreshAttendees(newAttendees) {
var attendeesLabel = $("attendeesLabel");
var attendeesNames = $("attendeesNames").value;
var attendeesEmails = $("attendeesEmails").value.split(",");
var attendeesStates = $("attendeesStates").value.split(",");
var attendeesHref = $("attendeesHref");
var attendeesMenu = $("attendeesMenu");
@ -398,24 +333,36 @@ function refreshAttendees() {
if (menuItems && attendeesMenu)
for (var i = 0; i < menuItems.length; i++)
attendeesMenu.removeChild(menuItems[i]);
if (attendeesNames.length > 0) {
// Update attendees link and show label
attendeesHref.appendChild(document.createTextNode(attendeesNames));
attendeesLabel.setStyle({ display: "block" });
if (newAttendees)
attendees = $H(newAttendees.evalJSON(true));
// Update attendees in menu
attendeesNames = attendeesNames.split(",");
for (var i = 0; i < attendeesEmails.length; i++) {
var node = document.createElement("li");
if (attendeesMenu)
attendeesMenu.appendChild(node);
$(node).writeAttribute("email", attendeesEmails[i]);
$(node).addClassName("attendee");
$(node).addClassName(attendeesStates[i]);
node.appendChild(document.createTextNode(attendeesNames[i]));
$(node).observe("click", onMailTo);
}
if (attendees.keys().length > 0) {
// Update attendees link and show label
var names = new Array();
attendees.values().each(function(attendee) {
attendee = $H(attendee);
var name = attendee.get('name') || attendee.get('email');
if (!attendee.get('delegated-to'))
names.push(name);
if (attendeesMenu) {
var delegatedTo = attendee.get('delegated-to');
if (!attendee.get('delegated-from') || delegatedTo) {
var node = document.createElement("li");
attendeesMenu.appendChild(node);
setupAttendeeNode(node, attendee);
}
if (delegatedTo) {
var delegate = attendees.get(delegatedTo);
var node = document.createElement("li");
attendeesMenu.appendChild(node);
setupAttendeeNode(node, $H(delegate), true);
}
}
});
attendeesHref.appendChild(document.createTextNode(names.join(", ")));
attendeesLabel.setStyle({ display: "block" });
}
else {
// Hide link of attendees
@ -423,6 +370,25 @@ function refreshAttendees() {
}
}
function setupAttendeeNode(aNode, aAttendee, isDelegate) {
// Construct the display string from common name and/or email address.
var name = aAttendee.get('name');
var email = aAttendee.get('email');
// if (name)
// name += ' <' + email + '>';
// else
// name = email;
name = name || email;
$(aNode).writeAttribute("email", email);
$(aNode).addClassName("attendee");
$(aNode).addClassName(aAttendee.get('partstat'));
if (isDelegate)
$(aNode).addClassName("delegate");
aNode.appendChild(document.createTextNode(name));
$(aNode).observe("click", onMailTo);
}
function initializeAttendeesHref() {
var attendeesHref = $("attendeesHref");
var attendeesLabel = $("attendeesLabel");
@ -443,6 +409,8 @@ function onMailTo(event) {
var target = getTarget(event);
var address = target.firstChild.nodeValue.trim() + " <" + target.readAttribute("email") + ">";
openMailTo(address);
Event.stop(event);
return false;
}
function getMenus() {
@ -473,6 +441,10 @@ function onAppointmentEditorLoad() {
'minute': $("endTime_time_minute")}};
initTimeWidgets(widgets);
}
// Extend JSON representation of attendees
attendees = $H(attendees);
initializeAttendeesHref();
}

View File

@ -121,6 +121,14 @@ TABLE#freeBusyAttendees TR.futureAttendee INPUT
{ background-image: none;
color: #aaa; }
TABLE#freeBusyAttendees TR.futureAttendee TD,
TABLE#freeBusyData TR.futureData TD
{ border: 0;
line-height: 3em; }
TABLE#freeBusyAttendees TR.futureAttendee TD A
{ margin-left: 24px; }
SPAN.freeBusyZoneElement
{ display: block;
float: left;

View File

@ -13,11 +13,7 @@ var attendeesEditor = {
delay: 500,
delayedSearch: false,
currentField: null,
selectedIndex: -1,
names: null,
UIDs: null,
emails: null,
states: null
selectedIndex: -1
};
@ -48,12 +44,12 @@ function onContactKeydown(event) {
var row = $(this).up("tr").next();
this.blur(); // triggers checkAttendee function call
var input = row.down("input");
if (input.readOnly)
newAttendee(null);
else {
if (input) {
input.focussed = true;
input.activate();
}
else
newAttendee(null);
}
else if (event.keyCode == 0
|| event.keyCode == 8 // Backspace
@ -579,15 +575,12 @@ function onNextSlotClick (event) {
function onEditorOkClick(event) {
preventDefault(event);
attendeesEditor.names = new Array();
attendeesEditor.UIDs = new Array();
attendeesEditor.emails = new Array();
attendeesEditor.states = new Array();
var attendees = window.opener.attendees;
var newAttendees = new Hash();
var table = $("freeBusy");
var inputs = table.getElementsByTagName("input");
for (var i = 0; i < inputs.length - 2; i++) {
for (var i = 0; i < inputs.length - 1; i++) {
var row = $(inputs[i]).up("tr");
var name = extractEmailName(inputs[i].value);
var email = extractEmailAddress(inputs[i].value);
@ -599,24 +592,15 @@ function onEditorOkClick(event) {
name = inputs[i].uid;
else
name = email;
var state = "needs-action";
if (row.hasClassName("accepted"))
state = "accepted";
else if (row.hasClassName("declined"))
state = "declined";
var pos = attendeesEditor.emails.indexOf(email);
if (pos == -1)
pos = attendeesEditor.emails.length;
attendeesEditor.names[pos] = name;
attendeesEditor.UIDs[pos] = uid;
attendeesEditor.emails[pos] = email;
attendeesEditor.states[pos] = state;
var attendee = attendees.get(email);
if (!attendee)
attendee = new Hash({'email': email,
'name': name,
'uid': uid,
'partstat': 'needs-action'});
newAttendees.set(email, attendee);
}
parent$("attendeesNames").value = attendeesEditor.names.join(",");
parent$("attendeesUIDs").value = attendeesEditor.UIDs.join(",");
parent$("attendeesEmails").value = attendeesEditor.emails.join(",");
parent$("attendeesStates").value = attendeesEditor.states.join(",");
window.opener.refreshAttendees();
window.opener.refreshAttendees(newAttendees.toJSON());
updateParentDateFields("startTime", "startTime");
updateParentDateFields("endTime", "endTime");
@ -746,15 +730,11 @@ function prepareTableRows() {
}
function prepareAttendees() {
var value = parent$("attendeesNames").value;
var tableAttendees = $("freeBusyAttendees");
var tableData = $("freeBusyData");
if (value.length > 0) {
attendeesEditor.names = parent$("attendeesNames").value.split(",");
attendeesEditor.UIDs = parent$("attendeesUIDs").value.split(",");
attendeesEditor.emails = parent$("attendeesEmails").value.split(",");
attendeesEditor.states = parent$("attendeesStates").value.split(",");
var attendees = window.opener.attendees;
if (attendees && attendees.keys()) {
var tbodyAttendees = tableAttendees.tBodies[0];
var modelAttendee = tbodyAttendees.rows[tbodyAttendees.rows.length - 1];
var newAttendeeRow = tbodyAttendees.rows[tbodyAttendees.rows.length - 2];
@ -763,42 +743,38 @@ function prepareAttendees() {
var modelData = tbodyData.rows[tbodyData.rows.length - 1];
var newDataRow = tbodyData.rows[tbodyData.rows.length - 2];
for (var i = 0; i < attendeesEditor.names.length; i++) {
var row = modelAttendee.cloneNode(true);
tbodyAttendees.insertBefore(row, newAttendeeRow);
$(row).removeClassName("attendeeModel");
$(row).addClassName(attendeesEditor.states[i]);
var input = $(row).down("input");
var value = "";
if (attendeesEditor.names[i].length > 0
&& attendeesEditor.names[i] != attendeesEditor.emails[i])
value += attendeesEditor.names[i] + " ";
value += "<" + attendeesEditor.emails[i] + ">";
input.value = value;
if (attendeesEditor.UIDs[i].length > 0)
input.uid = attendeesEditor.UIDs[i];
input.setAttribute("name", "");
input.setAttribute("modified", "0");
input.observe("blur", checkAttendee);
input.observe("keydown", onContactKeydown);
attendees.values().each(function(attendee) {
attendee = $H(attendee);
var row = modelAttendee.cloneNode(true);
tbodyAttendees.insertBefore(row, newAttendeeRow);
$(row).removeClassName("attendeeModel");
$(row).addClassName(attendee.get('partstat'));
var input = $(row).down("input");
var value = attendee.get('name');
if (value)
value += " ";
else
value = "";
value += "<" + attendee.get('email') + ">";
input.value = value;
if (attendee.get('uid'))
input.uid = attendee.get('uid');
input.setAttribute("name", "");
input.setAttribute("modified", "0");
input.observe("blur", checkAttendee);
input.observe("keydown", onContactKeydown);
row = modelData.cloneNode(true);
tbodyData.insertBefore(row, newDataRow);
$(row).removeClassName("dataModel");
row = modelData.cloneNode(true);
tbodyData.insertBefore(row, newDataRow);
$(row).removeClassName("dataModel");
displayFreeBusyForNode(input);
}
}
else {
attendeesEditor.names = new Array();
attendeesEditor.UIDs = new Array();
attendeesEditor.emails = new Array();
//newAttendee(null);
displayFreeBusyForNode(input);
});
}
var inputs = tableAttendees.select("input");
inputs[inputs.length - 2].setAttribute("autocomplete", "off");
inputs[inputs.length - 2].observe("click", newAttendee);
// Activate "Add attendee" button
var links = tableAttendees.select("TR.futureAttendee TD A");
links.first().observe("click", newAttendee);
}
function onWindowResize(event) {

View File

@ -85,9 +85,29 @@ DIV.fakeTextArea
border-width: 2px;
border-style: inset;
padding: 2px;
height: 100px;
white-space: pre-wrap; }
height: 100px; }
DIV#descriptionDiv,
DIV#attendeesDiv
{ height: 120px; }
DIV#descriptionDiv DIV.fakeTextArea
{ line-height: 1.5em;
padding: 2px 4px;
white-space: pre-wrap; }
#delegateEditor
{ padding-bottom: 1em; }
#delegatedTo
{ background-image: url("abcard.gif");
background-repeat: no-repeat;
background-position: 4px center;
padding: 2px 2px 2px 24px;
width: 260px; }
IMG#progressIndicator
{ float: none;
position: absolute;
right: 1em;
margin: 0 5px; }

View File

@ -130,15 +130,25 @@ function onComponentEditorLoad(event) {
list.fire("mousedown");
}
if ($("itemPrivacyList")) {
var menuItems = $("itemPrivacyList").childNodesWithTag("li");
var tmp = $("itemPrivacyList");
if (tmp) {
var menuItems = tmp.childNodesWithTag("li");
for (var i = 0; i < menuItems.length; i++)
menuItems[i].observe("mousedown",
onMenuSetClassification.bindAsEventListener(menuItems[i]),
false);
}
var tmp = $("repeatHref");
tmp = $("replyList");
if (tmp) {
tmp.observe("change", onReplyChange);
tmp = $("delegatedTo");
tmp.addInterface(SOGoAutoCompletionInterface);
tmp.uidField = "c_mail";
tmp.excludeGroups = true;
tmp.animationParent=$("delegateEditor");
}
tmp = $("repeatHref");
if (tmp)
tmp.observe("click", onPopupRecurrenceWindow);
tmp = $("repeatList");
@ -182,6 +192,21 @@ function onSummaryChange (e) {
document.title = $("summary").value;
}
function onReplyChange(event) {
var delegateEditor = $("delegateEditor");
if (this.value == 2) {
// Delegated
delegateEditor.show();
$("delegatedTo").focus();
}
else {
delegateEditor.hide();
}
onWindowResize(null);
return true;
}
function onWindowResize(event) {
var comment = $("commentArea");
if (comment) {
@ -269,14 +294,19 @@ function onOkButtonClick (e) {
var item = $("replyList");
var value = parseInt(item.options[item.selectedIndex].value);
var action = "";
var parameters = "";
if (value == 0)
action = 'accept';
else if (value == 1)
action = 'decline';
else if (value == 2) {
var url = ApplicationBaseURL + activeCalendar + '/' + activeComponent;
document.modifyEventAjaxRequest = delegateInvitation(url, modifyEventCallback);
}
if (action != "")
modifyEvent (item, action);
modifyEvent (item, action, parameters);
}
function onCancelButtonClick (e) {

View File

@ -1,4 +1,4 @@
HTML, BODY
{ background-color: #fff;
font-size: normal;
font-family: sans-serif; }
font-family: sans-serif; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

View File

@ -759,7 +759,7 @@ DIV.resize-handle
position: absolute;
top: 0;
left: 0;
max-height: 2em; }
max-height: 2em; } /* will be set in JavaScript when setting drag handles */
@media print
{
@ -779,8 +779,8 @@ DIV.resize-handle
.genericHoverClass
{
background-color: #f0f0f0 !important;
color: #000000 !important;
background-color: #0033cc !important;
color: #fff !important;
}
DIV#uploadDialog,DIV#uploadResults

View File

@ -1602,6 +1602,26 @@ function createFolderCallback(http) {
}
}
/* invitation delegation */
function delegateInvitation(componentUrl, callbackFunction, callbackData) {
var input = $("delegatedTo");
var delegatedTo = null;
if (input.uid != null)
delegatedTo = input.uid;
else if (input.value.blank())
alert(clabels["noEmailForDelegation"]);
else
delegatedTo = input.value;
if (delegatedTo) {
var receiveUpdates = false; //confirm("Do you want to keep receiving updates on the event?");
var urlstr = componentUrl + "/delegate";
var parameters = "to=" + delegatedTo + "&receiveUpdates=" + (receiveUpdates?"YES":"NO");
return triggerAjaxRequest(urlstr, callbackFunction, callbackData, parameters,
{ "Content-type": "application/x-www-form-urlencoded" });
}
}
function onFinalLoadHandler(event) {
var safetyNet = $("javascriptSafetyNet");
if (safetyNet)
@ -1623,7 +1643,6 @@ function getMenus() {
}
function onHeaderClick(event) {
window.alert("generic headerClick");
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -204,8 +204,10 @@ sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/create-accou
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/day-view-multicolumn.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/day-view.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/declined.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/delegated.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/dialog-left.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/dialog-right.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/dot.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/edit.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/empty.gif
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/event-gradient.png
@ -217,7 +219,6 @@ sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon-forward
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon-forwarded.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon-new.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon-replied.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon_read.gif
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/icon_unread.gif
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/important.png
sogo: image-file-in-usr-lib usr/lib/GNUstep/SOGo/WebServerResources/inverse.png