Merge pull request #45 from alexcloutier/feature/PreventInvitations

New user preference to prevent invitations
This commit is contained in:
Francis Lachapelle 2014-07-21 09:38:42 -04:00
commit f3ded6ce2a
8 changed files with 1867 additions and 1432 deletions

View file

@ -1,3 +1,4 @@
"This or these persons cannot be invited:" = "This or these persons cannot be invited:";
"Personal Calendar" = "Personal Calendar"; "Personal Calendar" = "Personal Calendar";
vevent_class0 = "(Public event)"; vevent_class0 = "(Public event)";
vevent_class1 = "(Private event)"; vevent_class1 = "(Private event)";

View file

@ -25,6 +25,7 @@
#import <Foundation/NSEnumerator.h> #import <Foundation/NSEnumerator.h>
#import <Foundation/NSTimeZone.h> #import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h> #import <Foundation/NSValue.h>
#import <Foundation/NSPredicate.h>
#import <NGObjWeb/NSException+HTTP.h> #import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h> #import <NGObjWeb/WOContext+SoObjects.h>
@ -52,6 +53,7 @@
#import <SOGo/SOGoPermissions.h> #import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoGroup.h> #import <SOGo/SOGoGroup.h>
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoDomainDefaults.h> #import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoWebDAVValue.h> #import <SOGo/SOGoWebDAVValue.h>
#import <SOGo/WORequest+SOGo.h> #import <SOGo/WORequest+SOGo.h>
@ -126,7 +128,6 @@
} }
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID - (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
{ {
return [[self calendar: NO secure: NO] eventWithRecurrenceID: recID]; return [[self calendar: NO secure: NO] eventWithRecurrenceID: recID];
} }
@ -343,9 +344,7 @@
[event removeFromAttendees: delegate]; [event removeFromAttendees: delegate];
} }
else else
[self errorWithFormat: [self errorWithFormat:@"broken chain: delegate with email '%@' was not found", mailTo];
@"broken chain: delegate with email '%@' was not found",
mailTo];
} }
} }
@ -412,6 +411,73 @@
withType: @"calendar:invitation-update"]; withType: @"calendar:invitation-update"];
} }
// This method scans the list of attendees.
- (NSException *) _handleAttendeeAvailability: (NSArray *) theAttendees
forEvent: (iCalEvent *) theEvent
{
iCalPerson *currentAttendee;
NSMutableArray *attendees, *unavailableAttendees, *whiteList;
NSEnumerator *enumerator;
NSPredicate *predicate;
NSString *currentUID, *ownerUID;
NSMutableString *reason;
NSDictionary *values;
NSMutableDictionary *value, *moduleSettings;
SOGoUser *user;
SOGoUserSettings *us;
int count = 0, i = 0;
// Build list of the attendees uids without ressources
attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]];
unavailableAttendees = [[NSMutableArray alloc] init];
enumerator = [theAttendees objectEnumerator];
ownerUID = [[[self context] activeUser] login];
while ((currentAttendee = [enumerator nextObject]))
{
currentUID = [currentAttendee uid];
if (currentUID)
{
user = [SOGoUser userWithLogin: currentUID];
us = [user userSettings];
moduleSettings = [us objectForKey:@"Calendar"];
// Check if the user prevented his account from beeing invited to events
if (![user isResource] && [[moduleSettings objectForKey:@"PreventInvitations"] boolValue])
{
// Check if the user have a whiteList
whiteList = [NSMutableArray arrayWithObject:[moduleSettings objectForKey:@"PreventInvitationsWhitelist"]];
predicate = [NSPredicate predicateWithFormat:@"SELF CONTAINS[cd] %@", ownerUID];
[whiteList filterUsingPredicate:predicate];
// If the filter have a hit, do not add the currentUID to the unavailableAttendees array
if ([whiteList count] == 0)
{
values = [NSDictionary dictionaryWithObject:[user cn] forKey:@"Cn"];
[unavailableAttendees addObject:values];
}
}
}
}
count = [unavailableAttendees count];
if (count > 0)
{
reason = [NSMutableString stringWithString:[self labelForKey: @"This or these persons cannot be invited:"]];
// Add all the unavailable users in the warning message
for (i = 0; i < count; i++)
{
value = [unavailableAttendees objectAtIndex:i];
[reason appendString:[value keysWithFormat: @"\n %{Cn}"]];
if (i < count-2)
[reason appendString:@", "];
}
[unavailableAttendees release];
return [NSException exceptionWithHTTPStatus:409 reason: reason];
}
[unavailableAttendees release];
return nil;
}
// //
// This methods scans the list of attendees. If they are // This methods scans the list of attendees. If they are
// considered as resource, it checks for conflicting // considered as resource, it checks for conflicting
@ -618,6 +684,8 @@
// We check for conflicts // We check for conflicts
if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent])) if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent]))
return e; return e;
if ((e = [self _handleAttendeeAvailability: attendees forEvent: newEvent]))
return e;
enumerator = [attendees objectEnumerator]; enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject])) while ((currentAttendee = [enumerator nextObject]))
@ -661,7 +729,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[e addToAttendees: [theAttendees objectAtIndex: j]]; [e addToAttendees: [theAttendees objectAtIndex: j]];
else else
[e removeFromAttendees: [theAttendees objectAtIndex: j]]; [e removeFromAttendees: [theAttendees objectAtIndex: j]];
} }
} }
@ -709,8 +776,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
withType: @"calendar:cancellation"]; withType: @"calendar:cancellation"];
} }
if ((ex = [self _handleResourcesConflicts: [newEvent attendees] if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent]))
forEvent: newEvent])) return ex;
if ((ex = [self _handleAttendeeAvailability: [newEvent attendees] forEvent: newEvent]))
return ex; return ex;
addedAttendees = [changes insertedAttendees]; addedAttendees = [changes insertedAttendees];
@ -858,8 +926,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// within the repeating vEvent. // within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]]; recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime]; oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
if (oldEvent == nil) if (oldEvent == nil) // If no occurence found, create one
// If no occurence found, create one
oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime]; oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime];
} }
@ -1002,8 +1069,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
NSString *currentEmail, *quotedEmail; NSString *currentEmail, *quotedEmail;
currentEmail = [[currentUser allEmails] objectAtIndex: 0]; currentEmail = [[currentUser allEmails] objectAtIndex: 0];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail];
currentEmail];
[otherAttendee setValue: 0 ofAttribute: @"SENT-BY" [otherAttendee setValue: 0 ofAttribute: @"SENT-BY"
to: quotedEmail]; to: quotedEmail];
} }
@ -1088,8 +1154,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
} }
if (addDelegate || removeDelegate if (addDelegate || removeDelegate
|| [currentStatus caseInsensitiveCompare: newStatus] || [currentStatus caseInsensitiveCompare: newStatus] != NSOrderedSame)
!= NSOrderedSame)
{ {
NSMutableArray *delegates; NSMutableArray *delegates;
NSString *delegatedUID; NSString *delegatedUID;
@ -1104,8 +1169,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
NSString *currentEmail, *quotedEmail; NSString *currentEmail, *quotedEmail;
currentEmail = [[currentUser allEmails] objectAtIndex: 0]; currentEmail = [[currentUser allEmails] objectAtIndex: 0];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail];
currentEmail];
[attendee setValue: 0 ofAttribute: @"SENT-BY" [attendee setValue: 0 ofAttribute: @"SENT-BY"
to: quotedEmail]; to: quotedEmail];
} }
@ -1215,9 +1279,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
att = [attendees objectAtIndex: i]; att = [attendees objectAtIndex: i];
uid = [att uid]; uid = [att uid];
if (uid if (uid && att != attendee && ![uid isEqualToString: delegatedUID])
&& att != attendee
&& ![uid isEqualToString: delegatedUID])
[self _updateAttendee: attendee [self _updateAttendee: attendee
withDelegate: delegate withDelegate: delegate
ownerUser: theOwnerUser ownerUser: theOwnerUser
@ -1238,13 +1300,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
NSDictionary *code; NSDictionary *code;
element = [NSMutableArray array]; element = [NSMutableArray array];
[element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, recipient)];
recipient)]; [element addObject: davElementWithContent (@"request-status", XMLNS_CALDAV, @"2.0;Success")];
[element addObject: davElementWithContent (@"request-status", code = davElementWithContent (@"response", XMLNS_CALDAV, element);
XMLNS_CALDAV,
@"2.0;Success")];
code = davElementWithContent (@"response", XMLNS_CALDAV,
element);
return code; return code;
} }
@ -1371,8 +1429,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
attendee = [event userAsAttendee: ownerUser]; attendee = [event userAsAttendee: ownerUser];
if (attendee) if (attendee)
{ {
if (delegate if (delegate && ![[delegate email] isEqualToString: [attendee delegatedTo]])
&& ![[delegate email] isEqualToString: [attendee delegatedTo]])
{ {
delegatedUid = [delegate uid]; delegatedUid = [delegate uid];
if (delegatedUid) if (delegatedUid)
@ -1405,8 +1462,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// the database. We do this ONLY when using SOGo from the // the database. We do this ONLY when using SOGo from the
// Web interface or over ActiveSync. // Web interface or over ActiveSync.
// Over DAV, it'll be handled directly in PUTAction: // Over DAV, it'll be handled directly in PUTAction:
if (![context request] if (![context request] || [[context request] handledByDefaultHandler]
|| [[context request] handledByDefaultHandler]
|| [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) || [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"])
ex = [self saveContentString: [[event parent] versitString]]; ex = [self saveContentString: [[event parent] versitString]];
} }
@ -1616,11 +1672,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{ {
event = [allEvents objectAtIndex: i]; event = [allEvents objectAtIndex: i];
if ([event isAllDay] && [event isOpaque]) if ([event isAllDay] && [event isOpaque])
{
[event setTransparency: @"TRANSPARENT"]; [event setTransparency: @"TRANSPARENT"];
} }
} }
}
/** /**
* Verify vCalendar for any inconsistency or missing attributes. * Verify vCalendar for any inconsistency or missing attributes.
@ -1744,12 +1798,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
NSArray *roles; NSArray *roles;
SOGoUser *ownerUser; SOGoUser *ownerUser;
if (calendar == fullCalendar if (calendar == fullCalendar || calendar == safeCalendar
|| calendar == safeCalendar
|| calendar == originalCalendar) || calendar == originalCalendar)
[NSException raise: NSInvalidArgumentException [NSException raise: NSInvalidArgumentException format: @"the 'calendar' argument must be a distinct instance" @" from the original object"];
format: @"the 'calendar' argument must be a distinct instance"
@" from the original object"];
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
@ -1760,17 +1811,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// responding to one of our invitation. In this case, _setupResponseCalendarInRequest // responding to one of our invitation. In this case, _setupResponseCalendarInRequest
// will only take the new attendee status and actually discard any other modifications. // will only take the new attendee status and actually discard any other modifications.
// //
if ([roles containsObject: @"ComponentResponder"] if ([roles containsObject: @"ComponentResponder"] && ![roles containsObject: @"ComponentModifier"])
&& ![roles containsObject: @"ComponentModifier"])
calendar = [self _setupResponseInRequestCalendar: calendar]; calendar = [self _setupResponseInRequestCalendar: calendar];
else else
{ {
if (![[rq headersForKey: @"X-SOGo"] if (![[rq headersForKey: @"X-SOGo"] containsObject: @"NoGroupsDecomposition"])
containsObject: @"NoGroupsDecomposition"])
[self _decomposeGroupsInRequestCalendar: calendar]; [self _decomposeGroupsInRequestCalendar: calendar];
if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency] if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency] && [rq isIPhone])
&& [rq isIPhone])
{ {
[self _adjustTransparencyInRequestCalendar: calendar]; [self _adjustTransparencyInRequestCalendar: calendar];
} }
@ -1834,11 +1882,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// //
else if (scheduling && [event userIsAttendee: ownerUser]) else if (scheduling && [event userIsAttendee: ownerUser])
{ {
[self sendResponseToOrganizer: event [self sendIMIPReplyForEvent: event
from: ownerUser]; from: ownerUser
to: [event organizer]];
} }
[self sendReceiptEmailForObject: event [self sendReceiptEmailForObject: event
addedAttendees: attendees addedAttendees: attendees
deletedAttendees: nil deletedAttendees: nil
@ -1960,8 +2008,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// recurrence-id and not in the master event. We must fix this, otherwise // recurrence-id and not in the master event. We must fix this, otherwise
// SOGo will break. // SOGo will break.
if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid]) if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid])
[[[[newEvent parent] events] objectAtIndex: 0] [[[[newEvent parent] events] objectAtIndex: 0] setOrganizer: [newEvent organizer]];
setOrganizer: [newEvent organizer]];
if (newEvent == oldEvent) if (newEvent == oldEvent)
newEvent = nil; newEvent = nil;

View file

@ -19,6 +19,7 @@
*/ */
#import <Foundation/NSCalendarDate.h> #import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSPropertyList.h> #import <Foundation/NSPropertyList.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h> #import <Foundation/NSTimeZone.h>
@ -41,6 +42,7 @@
#import <SOGo/NSString+Utilities.h> #import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h> #import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoDomainDefaults.h> #import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSieveManager.h> #import <SOGo/SOGoSieveManager.h>
#import <SOGo/SOGoSystemDefaults.h> #import <SOGo/SOGoSystemDefaults.h>
@ -638,6 +640,47 @@ static NSArray *reminderValues = nil;
return [userDefaults busyOffHours]; return [userDefaults busyOffHours];
} }
- (NSArray *) whiteList
{
SOGoUserSettings *us;
NSMutableDictionary *moduleSettings;
NSArray *whiteList;
us = [user userSettings];
moduleSettings = [us objectForKey: @"Calendar"];
whiteList = [moduleSettings objectForKey:@"PreventInvitationsWhitelist"];
return whiteList;
}
- (void) setWhiteList: (NSArray *) whiteList
{
SOGoUserSettings *us;
NSMutableDictionary *moduleSettings;
us = [user userSettings];
moduleSettings = [us objectForKey: @"Calendar"];
[moduleSettings setObject: whiteList forKey: @"PreventInvitationsWhitelist"];
[us synchronize];
}
- (void) setPreventInvitations: (BOOL) preventInvitations
{
SOGoUserSettings *us;
NSMutableDictionary *moduleSettings;
us = [user userSettings];
moduleSettings = [us objectForKey: @"Calendar"];
[moduleSettings setObject: [NSNumber numberWithBool: preventInvitations] forKey: @"PreventInvitations"];
[us synchronize];
}
- (BOOL) preventInvitations
{
SOGoUserSettings *us;
NSMutableDictionary *moduleSettings;
us = [user userSettings];
moduleSettings = [us objectForKey: @"Calendar"];
return [[moduleSettings objectForKey: @"PreventInvitations"] boolValue];
}
- (NSArray *) firstWeekList - (NSArray *) firstWeekList
{ {
return [NSArray arrayWithObjects: return [NSArray arrayWithObjects:

View file

@ -11,11 +11,14 @@
title="title" title="title"
const:popup="YES" const:popup="YES"
const:cssFiles="datepicker.css" const:cssFiles="datepicker.css"
const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js" const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js, SOGoAutoCompletion.js"
> >
<script type="text/javascript"> <script type="text/javascript">
var localeCode = '<var:string value="localeCode"/>'; var localeCode = '<var:string value="localeCode"/>';
</script> </script>
<div class="popupMenu" id="contactsMenu">
<ul></ul>
</div>
<div id="colorPickerDialog" style="display: none;" class="dialog right bottom"> <div id="colorPickerDialog" style="display: none;" class="dialog right bottom">
<div> <div>
@ -214,41 +217,85 @@
const:id="reminderList" const:id="reminderList"
string="itemReminderText" var:selection="reminder"/></dd> string="itemReminderText" var:selection="reminder"/></dd>
</dl> </dl>
<div class="tabsContainer" id="calendarOptionsTabs">
<label><var:string label:value="Categories"/></label> <ul>
<div id="calendarCategoriesListWrapper" class="listWrapper" <li target="calendarCategoriesView">
><table class="categoriesList" cellspacing="0"> <span><var:string label:value="Categories"/></span></li>
<li target="calendarAppointmentsInvitationsView">
<span><var:string label:value="Appointments invitations"/></span></li>
</ul>
<div class="tabs">
<div id="calendarCategoriesView" class="tab">
<div id="calendarCategoriesListWrapper" class="listWrapper">
<table class="categoriesList" cellspacing="0">
<thead> <thead>
<tr class="tableview" <tr class="tableview">
><th const:class="tbtv_headercell" const:id="nameTableHeader" <th const:class="tbtv_headercell" const:id="nameTableHeader">
><var:string label:value="Name"/></th <var:string label:value="Name"/></th>
><th const:class="tbtv_headercell" const:id="colorTableHeader" <th const:class="tbtv_headercell" const:id="colorTableHeader">
><var:string label:value="Color"/></th <var:string label:value="Color"/></th>
></tr </tr>
></thead> </thead>
<tbody> <tbody>
<var:foreach list="calendarCategoryList" item="category"> <var:foreach list="calendarCategoryList" item="category">
<tr const:class="categoryListRow" <tr const:class="categoryListRow">
><td const:class="categoryListCell" <td const:class="categoryListCell">
><var:string var:value="category"/></td <var:string var:value="category"/></td>
><td const:class="categoryListCell" <td const:class="categoryListCell">
><div const:class="colorBox" var:data-color="categoryColor"><entity name="nbsp"/></div></td <div const:class="colorBox" var:data-color="categoryColor"><entity name="nbsp"/></div></td>
></tr> </tr>
</var:foreach> </var:foreach>
</tbody> </tbody>
</table> </table>
</div> </div><!-- #calendarCategoriesListWrapper -->
<div class="bottomToolbar"> <div class="bottomToolbar">
<a const:id="calendarCategoryAdd" class="bottomButton" href="#"> <a const:id="calendarCategoryAdd" class="bottomButton" href="#">
<span><img rsrc:src="add-icon.png" label:title="Add" /> <span><img rsrc:src="add-icon.png" label:title="Add" /></span></a>
</span></a>
<a const:id="calendarCategoryDelete" class="bottomButton" href="#"> <a const:id="calendarCategoryDelete" class="bottomButton" href="#">
<span><img rsrc:src="remove-icon.png" label:title="Delete" /> <span><img rsrc:src="remove-icon.png" label:title="Delete" /> </span></a>
</span></a> </div><!-- .bottomToolbar -->
</div>
<input type="hidden" const:id="calendarCategoriesValue" <input type="hidden" const:id="calendarCategoriesValue"
const:name="calendarCategoriesValue" var:value="calendarCategoriesValue"/> const:name="calendarCategoriesValue" var:value="calendarCategoriesValue"/>
</div> </div><!-- #calendarCategoriesView -->
<div id="calendarAppointmentsInvitationsView" class="tab">
<div><input type="checkbox"
const:name="preventInvitations"
const:id="preventInvitations"
var:checked="preventInvitations" />
<var:string label:value="Prevent from being invited to appointments"/></div>
<hr />
<var:string label:value="White list for appointments invitations:"/>
<div id="appointmentsWhiteListWrapper" class="listWrapper">
<table id="tableViewWhiteList" cellspacing="0">
<thead>
<tr class="tableview">
<th const:class="tbtv_headercell" const:id="whiteListTableHeader">
<var:string label:value="Contacts names"/></th>
</tr>
</thead>
<tbody>
<var:foreach list="appointmentsWhiteList" item="contact">
<tr const:class="whiteListRow">
<td const:class="whiteListCell">
<var:string var:value="contact"/></td>
</tr>
</var:foreach>
</tbody>
</table>
</div><!-- #appointmentsWhiteListWrapper -->
<div class="bottomToolbar">
<a const:id="appointmentsWhiteListAdd" class="bottomButton" href="#">
<span><img rsrc:src="add-icon.png" label:title="Add" /></span></a>
<a const:id="appointmentsWhiteListDelete" class="bottomButton" href="#">
<span><img rsrc:src="remove-icon.png" label:title="Delete" /></span></a>
</div><!-- .bottomToolbar -->
<input type="hidden" const:id="whiteList"
const:name="whiteList" var:value="whiteList"/>
</div><!-- #calendarAppointmentsInvitationsView -->
</div><!-- .tabs -->
</div><!-- #calendarOptionsTabs -->
</div><!-- #calendarOptionsView -->
</var:if> </var:if>
<div id="contactsOptionsView" class="tab"> <div id="contactsOptionsView" class="tab">
<label><var:string label:value="Categories"/></label> <label><var:string label:value="Categories"/></label>

View file

@ -13,6 +13,7 @@ var SOGoAutoCompletionInterface = {
// inheriting the inteface // inheriting the inteface
uidField: "c_name", uidField: "c_name",
addressBook: null, addressBook: null,
SOGoUsersSearch: false,
excludeGroups: false, excludeGroups: false,
excludeLists: false, excludeLists: false,
@ -126,6 +127,12 @@ var SOGoAutoCompletionInterface = {
document.contactLookupAjaxRequest.abort(); document.contactLookupAjaxRequest.abort();
} }
if (input.value.trim().length > minimumSearchLength) { if (input.value.trim().length > minimumSearchLength) {
if (input.SOGoUsersSearch) {
var urlstr = UserFolderURL + "usersSearch?search=" + encodeURIComponent(input.value);
document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, input.performUsersSearchCallback.bind(input), input);
}
else {
var urlstr = UserFolderURL + "Contacts/"; var urlstr = UserFolderURL + "Contacts/";
if (input.addressBook) if (input.addressBook)
urlstr += input.addressBook + "/contact"; urlstr += input.addressBook + "/contact";
@ -141,6 +148,7 @@ var SOGoAutoCompletionInterface = {
document.contactLookupAjaxRequest = document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, input.performSearchCallback.bind(input), input); triggerAjaxRequest(urlstr, input.performSearchCallback.bind(input), input);
} }
}
else { else {
if (document.currentPopupMenu) if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu); hideMenu(document.currentPopupMenu);
@ -259,6 +267,105 @@ var SOGoAutoCompletionInterface = {
} }
}, },
performUsersSearchCallback: function (http) {
if (http.readyState == 4) {
var list = this.menu.down("ul");
var input = http.callbackData;
if (http.status == 200) {
var response = http.responseText.evalJSON();
if (response.length > 1) {
list.select("li").each(function(item) {
item.stopObserving("mousedown");
item.remove();
});
// Populate popup menu
for (var i = 0; i < response.length; i++) {
var c_name = response[i][1];
var completeEmail = c_name;
var c_mail = response[i][2];
var uid = response[i][3];
if (c_mail)
completeEmail += " <" + c_mail + ">";
var node = new Element('li', { 'address': completeEmail,
'uid': uid });
var matchPosition = completeEmail.toLowerCase().indexOf(input.getValue().toLowerCase());
if (matchPosition > -1) {
var matchBefore = completeEmail.substring(0, matchPosition);
var matchText = completeEmail.substring(matchPosition, matchPosition + input.getValue().length);
var matchAfter = completeEmail.substring(matchPosition + input.getValue().length);
node.appendChild(document.createTextNode(matchBefore));
node.appendChild(new Element('strong').update(matchText));
node.appendChild(document.createTextNode(matchAfter));
}
else {
node.appendChild(document.createTextNode(completeEmail));
}
list.appendChild(node);
$(node).observe("mousedown", this.onAddressResultClick.bindAsEventListener(this));
}
// Show popup menu
var offsetScroll = Element.cumulativeScrollOffset(input);
var offset = Element.positionedOffset(input);
if ($(document.body).hasClassName("popup") && typeof initPopupMailer == 'undefined')
// Hack for some situations where the offset must be computed differently
offset = Element.cumulativeOffset(input);
var top = offset.top - offsetScroll.top + node.offsetHeight + 3;
var height = 'auto';
var heightDiff = window.height() - offset[1];
var nodeHeight = node.getHeight();
if ((response.length * nodeHeight) > heightDiff)
// Limit the size of the popup to the window height, minus 12 pixels
height = parseInt(heightDiff/nodeHeight) * nodeHeight - 12 + 'px';
this.menu.setStyle({ top: top + "px",
left: offset[0] + "px",
height: height,
maxWidth: (window.width() - offset[0] - 12) + "px",
visibility: "visible" });
this.menu.scrollTop = 0;
document.currentPopupMenu = this.menu;
$(document.body).stopObserving("click");
$(document.body).observe("click", onBodyClickMenuHandler);
}
else {
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
if (response.length == 1) {
// Single result
var c_name = response[0][1];
var completeEmail = c_name;
var c_mail = response[0][2];
var c_uid = response[0][0];
input.writeAttribute("uid", c_uid);
if (c_mail)
completeEmail += " <" + c_mail + ">";
if (c_uid.substring(0, input.getValue().length).toUpperCase() == input.getValue().toUpperCase())
input.value = completeEmail;
else
// The result matches email address, not user name
input.value += ' >> ' + completeEmail;
input.confirmedValue = completeEmail;
var end = input.getValue().length;
$(input).selectText(start, end);
this.selectedIndex = -1;
}
}
}
else
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
document.contactLookupAjaxRequest = null;
}
},
onAddressResultClick: function(event) { onAddressResultClick: function(event) {
var e = Event.element(event); var e = Event.element(event);
if (e.tagName != 'LI') if (e.tagName != 'LI')

View file

@ -14,6 +14,12 @@ DIV.bottomToolbar
right: 2em; right: 2em;
bottom: 8px; } bottom: 8px; }
#WhiteListAdd, #WhiteListDelete
{
border-bottom: 1px solid #9B9B9B;
border-right: 1px solid #9B9B9B;
}
#mailAccountsToolbar #mailAccountsToolbar
{ left: 5px; { left: 5px;
bottom: 9px; bottom: 9px;
@ -47,17 +53,35 @@ DIV.listWrapper
padding: 0px; padding: 0px;
margin-top: 2px; margin-top: 2px;
border-left: 1px solid #9b9b9b; border-left: 1px solid #9b9b9b;
border-right: 1px solid #9b9b9b;
background: #ccddec;} background: #ccddec;}
.listWrapper TABLE TD .listWrapper TABLE TD
{ height: 22px; } { height: 22px; }
#calendarCategoriesListWrapper #calendarCategoriesListWrapper
{ top: 232px; { top:1em;
bottom: 30px; bottom: 30px;
right: 2em; right: 2em;
left: 2em; } left: 2em; }
#appointmentsWhiteListWrapper
{ top:4.5em;
bottom: 30px;
right: 2em;
left: 2em; }
#tableViewWhiteList
{ width:100%; }
DIV#calendarOptionsTabs
{ position: absolute;
top: 225px;
left: 0px;
right: 0px;
bottom: 0px;
}
#contactsCategoriesListWrapper #contactsCategoriesListWrapper
{ overflow: auto; { overflow: auto;
position: absolute; position: absolute;

View file

@ -9,6 +9,9 @@ function savePreferences(sender) {
if (sigList) if (sigList)
sigList.disabled = false; sigList.disabled = false;
if ($("appointmentsWhiteListWrapper"))
serializeAppointmentsWhiteList();
if ($("calendarCategoriesListWrapper")) if ($("calendarCategoriesListWrapper"))
serializeCalendarCategories(); serializeCalendarCategories();
@ -212,6 +215,13 @@ function initPreferences() {
mailController.attachToTabsContainer(tabsContainer); mailController.attachToTabsContainer(tabsContainer);
} }
// Inner tabs on the calendar module tab
tabsContainer = $('calendarOptionsTabs');
if (tabsContainer) {
var mailController = new SOGoTabsController();
mailController.attachToTabsContainer(tabsContainer);
}
_setupEvents(); _setupEvents();
// Optional function called when initializing the preferences // Optional function called when initializing the preferences
@ -223,6 +233,50 @@ function initPreferences() {
$('colorPickerDialog').on('click', 'span', onColorPickerChoice); $('colorPickerDialog').on('click', 'span', onColorPickerChoice);
$(document.body).on("click", onBodyClickHandler); $(document.body).on("click", onBodyClickHandler);
// Calendar whiteList
var whiteList = $("appointmentsWhiteListWrapper");
if(whiteList) {
var whiteListValue = $("whiteList").getValue();
if (whiteListValue != "") {
whiteListValue = whiteListValue.split(",");
var tablebody = $("appointmentsWhiteListWrapper").childNodesWithTag("table")[0].tBodies[0];
for (i = 0; i < whiteListValue.length; i++)
{
var elements = whiteListValue[i].split("=");
var row = new Element("tr");
var td = new Element("td").update("");
var textField = new Element("input");
var span = new Element("span");
row.addClassName("whiteListRow");
row.observe("mousedown", onRowClick);
td.addClassName ("whiteListCell");
td.observe("mousedown", endAllEditables);
td.observe("dblclick", onNameEdit);
textField.addInterface(SOGoAutoCompletionInterface);
textField.SOGoUsersSearch = true;
textField.observe("autocompletion:changed", endEditable);
textField.addClassName("textField");
textField.value = elements[1];
textField.setAttribute("uid", elements[0]);
textField.hide();
span.innerText = elements[1];
td.appendChild(textField);
td.appendChild(span);
row.appendChild (td);
tablebody.appendChild(row);
$(tablebody).deselectAll();
}
}
var table = whiteList.childNodesWithTag("table")[0];
table.multiselect = true;
$("appointmentsWhiteListAdd").observe("click", onAppointmentsWhiteListAdd);
$("appointmentsWhiteListDelete").observe("click", onAppointmentsWhiteListDelete);
}
// Calender categories // Calender categories
var wrapper = $("calendarCategoriesListWrapper"); var wrapper = $("calendarCategoriesListWrapper");
if (wrapper) { if (wrapper) {
@ -282,8 +336,7 @@ function initPreferences() {
button = $("enableVacationEndDate"); button = $("enableVacationEndDate");
if (button) { if (button) {
jQuery("#vacationEndDate_date").closest(".date").datepicker( jQuery("#vacationEndDate_date").closest(".date").datepicker({ autoclose: true, position: 'above', weekStart: $('weekStartDay').getValue() });
{ autoclose: true, position: 'above', weekStart: $('weekStartDay').getValue() });
button.on("click", function(event) { button.on("click", function(event) {
if (this.checked) if (this.checked)
$("vacationEndDate_date").enable(); $("vacationEndDate_date").enable();
@ -977,6 +1030,119 @@ function onCalendarColorEdit(e) {
onCCE(e, "calendarCategoriesListWrapper"); onCCE(e, "calendarCategoriesListWrapper");
} }
function makeEditable (element) {
element.addClassName("editing");
element.removeClassName("whiteListCell");
var span = element.down("SPAN");
span.update();
var textField = element.down("INPUT");
textField.show();
textField.focus();
textField.select();
return true;
}
function endAllEditables (e) {
var r = $$("TABLE#tableViewWhiteList TBODY TR TD");
for (var i = 0; i < r.length; i++) {
var element = $(r[i]);
if (r[i] != this && element.hasClassName("editing"))
endEditable(null, element.down("INPUT"));
}
}
function onNameEdit (e) {
endAllEditables();
if (!this.hasClassName("editing")) {
makeEditable (this);
}
}
function endEditable(event, textField) {
if (!textField)
textField = this;
var uid = textField.readAttribute("uid");
var cell = textField.up("TD");
var textSpan = cell.down("SPAN");
cell.removeClassName("editing");
cell.addClassName("whiteListCell");
textField.hide();
var tmp = textField.value;
tmp = tmp.replace (/</, "&lt;");
tmp = tmp.replace (/>/, "&gt;");
if (!uid)
cell.up("TR").addClassName("notfound");
if (tmp)
textSpan.update(tmp);
else
cell.up("TR").remove();
if (event)
Event.stop(event);
return false;
}
function onAppointmentsWhiteListAdd(e) {
var tablebody = $("appointmentsWhiteListWrapper").childNodesWithTag("table")[0].tBodies[0];
var row = new Element("tr");
var td = new Element("td").update("");
var textField = new Element("input");
var span = new Element("span");
row.addClassName("whiteListRow");
row.observe("mousedown", onRowClick);
td.addClassName ("whiteListCell");
td.observe("mousedown", endAllEditables);
td.observe("dblclick", onNameEdit);
textField.addInterface(SOGoAutoCompletionInterface);
textField.SOGoUsersSearch = true;
textField.observe("autocompletion:changed", endEditable);
textField.addClassName("textField");
td.appendChild(textField);
td.appendChild(span);
row.appendChild (td);
tablebody.appendChild(row);
$(tablebody).deselectAll();
row.selectElement();
makeEditable(td);
}
function onAppointmentsWhiteListDelete(e) {
var list = $('appointmentsWhiteListWrapper').down("TABLE").down("TBODY");
var rows = list.getSelectedNodes();
var count = rows.length;
for (var i=0; i < count; i++) {
rows[i].editionController = null;
rows[i].remove();
}
}
function serializeAppointmentsWhiteList() {
var r = $$("#appointmentsWhiteListWrapper TBODY TR");
var values = [];
for (var i = 0; i < r.length; i++) {
var tds = r[i].childElements().first().down("INPUT");
var uid = tds.getAttribute("uid");
var value = tds.getValue();
var user = uid + "=" + value;
if (uid != null)
values.push(user);
}
$("whiteList").value = values;
}
function onCalendarCategoryAdd(e) { function onCalendarCategoryAdd(e) {
var row = new Element("tr"); var row = new Element("tr");
var nametd = new Element("td").update(""); var nametd = new Element("td").update("");
@ -1016,7 +1182,7 @@ function serializeCalendarCategories() {
var values = []; var values = [];
for (var i = 0; i < r.length; i++) { for (var i = 0; i < r.length; i++) {
var tds = r[i].childElements(); var tds = r[i].childElements();
var name = $(tds.first()).innerHTML; var name = $(tds.first()).innerHTML.trim();
var color = $(tds.last().childElements().first()).readAttribute('data-color'); var color = $(tds.last().childElements().first()).readAttribute('data-color');
values.push("\"" + name + "\": \"" + color + "\""); values.push("\"" + name + "\": \"" + color + "\"");
} }
@ -1179,7 +1345,6 @@ function onAddOutgoingAddressesCheck(checkBox) {
checkBox = $("addOutgoingAddresses"); checkBox = $("addOutgoingAddresses");
} }
$("addressBookList").disabled = !checkBox.checked; $("addressBookList").disabled = !checkBox.checked;
} }
function onReplyPlacementListChange() { function onReplyPlacementListChange() {

View file

@ -410,7 +410,8 @@ TH.tbtv_headercell IMG.tbtv_sortcell
text-align: right; text-align: right;
border: 0px; border: 0px;
width: 12px; width: 12px;
height: 12px; } height: 12px;
top:0;}
.tableview .tableview
{ cursor: default; { cursor: default;