Merge pull request #45 from alexcloutier/feature/PreventInvitations

New user preference to prevent invitations
pull/47/head
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";
vevent_class0 = "(Public event)";
vevent_class1 = "(Private event)";

View File

@ -25,6 +25,7 @@
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSPredicate.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
@ -52,6 +53,7 @@
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoGroup.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoWebDAVValue.h>
#import <SOGo/WORequest+SOGo.h>
@ -126,7 +128,6 @@
}
- (iCalRepeatableEntityObject *) lookupOccurrence: (NSString *) recID
{
return [[self calendar: NO secure: NO] eventWithRecurrenceID: recID];
}
@ -343,9 +344,7 @@
[event removeFromAttendees: delegate];
}
else
[self errorWithFormat:
@"broken chain: delegate with email '%@' was not found",
mailTo];
[self errorWithFormat:@"broken chain: delegate with email '%@' was not found", mailTo];
}
}
@ -412,6 +411,73 @@
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
// considered as resource, it checks for conflicting
@ -618,6 +684,8 @@
// We check for conflicts
if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent]))
return e;
if ((e = [self _handleAttendeeAvailability: attendees forEvent: newEvent]))
return e;
enumerator = [attendees objectEnumerator];
while ((currentAttendee = [enumerator nextObject]))
@ -661,7 +729,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
[e addToAttendees: [theAttendees objectAtIndex: j]];
else
[e removeFromAttendees: [theAttendees objectAtIndex: j]];
}
}
@ -709,8 +776,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
withType: @"calendar:cancellation"];
}
if ((ex = [self _handleResourcesConflicts: [newEvent attendees]
forEvent: newEvent]))
if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent]))
return ex;
if ((ex = [self _handleAttendeeAvailability: [newEvent attendees] forEvent: newEvent]))
return ex;
addedAttendees = [changes insertedAttendees];
@ -858,8 +926,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// within the repeating vEvent.
recurrenceTime = [NSString stringWithFormat: @"%f", [recurrenceId timeIntervalSince1970]];
oldEvent = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
if (oldEvent == nil)
// If no occurence found, create one
if (oldEvent == nil) // If no occurence found, create one
oldEvent = (iCalEvent *)[self newOccurenceWithID: recurrenceTime];
}
@ -1002,8 +1069,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
NSString *currentEmail, *quotedEmail;
currentEmail = [[currentUser allEmails] objectAtIndex: 0];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"",
currentEmail];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail];
[otherAttendee setValue: 0 ofAttribute: @"SENT-BY"
to: quotedEmail];
}
@ -1088,8 +1154,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
}
if (addDelegate || removeDelegate
|| [currentStatus caseInsensitiveCompare: newStatus]
!= NSOrderedSame)
|| [currentStatus caseInsensitiveCompare: newStatus] != NSOrderedSame)
{
NSMutableArray *delegates;
NSString *delegatedUID;
@ -1104,8 +1169,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
NSString *currentEmail, *quotedEmail;
currentEmail = [[currentUser allEmails] objectAtIndex: 0];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"",
currentEmail];
quotedEmail = [NSString stringWithFormat: @"\"MAILTO:%@\"", currentEmail];
[attendee setValue: 0 ofAttribute: @"SENT-BY"
to: quotedEmail];
}
@ -1215,9 +1279,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
att = [attendees objectAtIndex: i];
uid = [att uid];
if (uid
&& att != attendee
&& ![uid isEqualToString: delegatedUID])
if (uid && att != attendee && ![uid isEqualToString: delegatedUID])
[self _updateAttendee: attendee
withDelegate: delegate
ownerUser: theOwnerUser
@ -1238,13 +1300,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
NSDictionary *code;
element = [NSMutableArray array];
[element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV,
recipient)];
[element addObject: davElementWithContent (@"request-status",
XMLNS_CALDAV,
@"2.0;Success")];
code = davElementWithContent (@"response", XMLNS_CALDAV,
element);
[element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, recipient)];
[element addObject: davElementWithContent (@"request-status", XMLNS_CALDAV, @"2.0;Success")];
code = davElementWithContent (@"response", XMLNS_CALDAV, element);
return code;
}
@ -1371,8 +1429,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
attendee = [event userAsAttendee: ownerUser];
if (attendee)
{
if (delegate
&& ![[delegate email] isEqualToString: [attendee delegatedTo]])
if (delegate && ![[delegate email] isEqualToString: [attendee delegatedTo]])
{
delegatedUid = [delegate uid];
if (delegatedUid)
@ -1405,8 +1462,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// the database. We do this ONLY when using SOGo from the
// Web interface or over ActiveSync.
// Over DAV, it'll be handled directly in PUTAction:
if (![context request]
|| [[context request] handledByDefaultHandler]
if (![context request] || [[context request] handledByDefaultHandler]
|| [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"])
ex = [self saveContentString: [[event parent] versitString]];
}
@ -1616,11 +1672,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
{
event = [allEvents objectAtIndex: i];
if ([event isAllDay] && [event isOpaque])
{
[event setTransparency: @"TRANSPARENT"];
}
}
}
/**
* Verify vCalendar for any inconsistency or missing attributes.
@ -1744,12 +1798,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
NSArray *roles;
SOGoUser *ownerUser;
if (calendar == fullCalendar
|| calendar == safeCalendar
if (calendar == fullCalendar || calendar == safeCalendar
|| calendar == originalCalendar)
[NSException raise: NSInvalidArgumentException
format: @"the 'calendar' argument must be a distinct instance"
@" from the original object"];
[NSException raise: NSInvalidArgumentException format: @"the 'calendar' argument must be a distinct instance" @" from the original object"];
ownerUser = [SOGoUser userWithLogin: owner];
@ -1760,17 +1811,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// responding to one of our invitation. In this case, _setupResponseCalendarInRequest
// will only take the new attendee status and actually discard any other modifications.
//
if ([roles containsObject: @"ComponentResponder"]
&& ![roles containsObject: @"ComponentModifier"])
if ([roles containsObject: @"ComponentResponder"] && ![roles containsObject: @"ComponentModifier"])
calendar = [self _setupResponseInRequestCalendar: calendar];
else
{
if (![[rq headersForKey: @"X-SOGo"]
containsObject: @"NoGroupsDecomposition"])
if (![[rq headersForKey: @"X-SOGo"] containsObject: @"NoGroupsDecomposition"])
[self _decomposeGroupsInRequestCalendar: calendar];
if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency]
&& [rq isIPhone])
if ([[ownerUser domainDefaults] iPhoneForceAllDayTransparency] && [rq isIPhone])
{
[self _adjustTransparencyInRequestCalendar: calendar];
}
@ -1834,11 +1882,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
else if (scheduling && [event userIsAttendee: ownerUser])
{
[self sendResponseToOrganizer: event
from: ownerUser];
[self sendIMIPReplyForEvent: event
from: ownerUser
to: [event organizer]];
}
[self sendReceiptEmailForObject: event
addedAttendees: attendees
deletedAttendees: nil
@ -1960,8 +2008,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// recurrence-id and not in the master event. We must fix this, otherwise
// SOGo will break.
if (!recurrenceId && ![[[[[newEvent parent] events] objectAtIndex: 0] organizer] uid])
[[[[newEvent parent] events] objectAtIndex: 0]
setOrganizer: [newEvent organizer]];
[[[[newEvent parent] events] objectAtIndex: 0] setOrganizer: [newEvent organizer]];
if (newEvent == oldEvent)
newEvent = nil;

View File

@ -19,6 +19,7 @@
*/
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSPropertyList.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimeZone.h>
@ -41,6 +42,7 @@
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSieveManager.h>
#import <SOGo/SOGoSystemDefaults.h>
@ -638,6 +640,47 @@ static NSArray *reminderValues = nil;
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
{
return [NSArray arrayWithObjects:

View File

@ -11,11 +11,14 @@
title="title"
const:popup="YES"
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">
var localeCode = '<var:string value="localeCode"/>';
</script>
<div class="popupMenu" id="contactsMenu">
<ul></ul>
</div>
<div id="colorPickerDialog" style="display: none;" class="dialog right bottom">
<div>
@ -214,41 +217,85 @@
const:id="reminderList"
string="itemReminderText" var:selection="reminder"/></dd>
</dl>
<label><var:string label:value="Categories"/></label>
<div id="calendarCategoriesListWrapper" class="listWrapper"
><table class="categoriesList" cellspacing="0">
<div class="tabsContainer" id="calendarOptionsTabs">
<ul>
<li target="calendarCategoriesView">
<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>
<tr class="tableview"
><th const:class="tbtv_headercell" const:id="nameTableHeader"
><var:string label:value="Name"/></th
><th const:class="tbtv_headercell" const:id="colorTableHeader"
><var:string label:value="Color"/></th
></tr
></thead>
<tr class="tableview">
<th const:class="tbtv_headercell" const:id="nameTableHeader">
<var:string label:value="Name"/></th>
<th const:class="tbtv_headercell" const:id="colorTableHeader">
<var:string label:value="Color"/></th>
</tr>
</thead>
<tbody>
<var:foreach list="calendarCategoryList" item="category">
<tr const:class="categoryListRow"
><td const:class="categoryListCell"
><var:string var:value="category"/></td
><td const:class="categoryListCell"
><div const:class="colorBox" var:data-color="categoryColor"><entity name="nbsp"/></div></td
></tr>
<tr const:class="categoryListRow">
<td const:class="categoryListCell">
<var:string var:value="category"/></td>
<td const:class="categoryListCell">
<div const:class="colorBox" var:data-color="categoryColor"><entity name="nbsp"/></div></td>
</tr>
</var:foreach>
</tbody>
</table>
</div>
</div><!-- #calendarCategoriesListWrapper -->
<div class="bottomToolbar">
<a const:id="calendarCategoryAdd" class="bottomButton" href="#">
<span><img rsrc:src="add-icon.png" label:title="Add" />
</span></a>
<span><img rsrc:src="add-icon.png" label:title="Add" /></span></a>
<a const:id="calendarCategoryDelete" class="bottomButton" href="#">
<span><img rsrc:src="remove-icon.png" label:title="Delete" />
</span></a>
</div>
<span><img rsrc:src="remove-icon.png" label:title="Delete" /> </span></a>
</div><!-- .bottomToolbar -->
<input type="hidden" const:id="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>
<div id="contactsOptionsView" class="tab">
<label><var:string label:value="Categories"/></label>

View File

@ -13,6 +13,7 @@ var SOGoAutoCompletionInterface = {
// inheriting the inteface
uidField: "c_name",
addressBook: null,
SOGoUsersSearch: false,
excludeGroups: false,
excludeLists: false,
@ -126,6 +127,12 @@ var SOGoAutoCompletionInterface = {
document.contactLookupAjaxRequest.abort();
}
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/";
if (input.addressBook)
urlstr += input.addressBook + "/contact";
@ -141,6 +148,7 @@ var SOGoAutoCompletionInterface = {
document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, input.performSearchCallback.bind(input), input);
}
}
else {
if (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) {
var e = Event.element(event);
if (e.tagName != 'LI')

View File

@ -14,6 +14,12 @@ DIV.bottomToolbar
right: 2em;
bottom: 8px; }
#WhiteListAdd, #WhiteListDelete
{
border-bottom: 1px solid #9B9B9B;
border-right: 1px solid #9B9B9B;
}
#mailAccountsToolbar
{ left: 5px;
bottom: 9px;
@ -47,17 +53,35 @@ DIV.listWrapper
padding: 0px;
margin-top: 2px;
border-left: 1px solid #9b9b9b;
border-right: 1px solid #9b9b9b;
background: #ccddec;}
.listWrapper TABLE TD
{ height: 22px; }
#calendarCategoriesListWrapper
{ top: 232px;
{ top:1em;
bottom: 30px;
right: 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
{ overflow: auto;
position: absolute;

View File

@ -9,6 +9,9 @@ function savePreferences(sender) {
if (sigList)
sigList.disabled = false;
if ($("appointmentsWhiteListWrapper"))
serializeAppointmentsWhiteList();
if ($("calendarCategoriesListWrapper"))
serializeCalendarCategories();
@ -212,6 +215,13 @@ function initPreferences() {
mailController.attachToTabsContainer(tabsContainer);
}
// Inner tabs on the calendar module tab
tabsContainer = $('calendarOptionsTabs');
if (tabsContainer) {
var mailController = new SOGoTabsController();
mailController.attachToTabsContainer(tabsContainer);
}
_setupEvents();
// Optional function called when initializing the preferences
@ -223,6 +233,50 @@ function initPreferences() {
$('colorPickerDialog').on('click', 'span', onColorPickerChoice);
$(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
var wrapper = $("calendarCategoriesListWrapper");
if (wrapper) {
@ -282,8 +336,7 @@ function initPreferences() {
button = $("enableVacationEndDate");
if (button) {
jQuery("#vacationEndDate_date").closest(".date").datepicker(
{ autoclose: true, position: 'above', weekStart: $('weekStartDay').getValue() });
jQuery("#vacationEndDate_date").closest(".date").datepicker({ autoclose: true, position: 'above', weekStart: $('weekStartDay').getValue() });
button.on("click", function(event) {
if (this.checked)
$("vacationEndDate_date").enable();
@ -977,6 +1030,119 @@ function onCalendarColorEdit(e) {
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) {
var row = new Element("tr");
var nametd = new Element("td").update("");
@ -1016,7 +1182,7 @@ function serializeCalendarCategories() {
var values = [];
for (var i = 0; i < r.length; i++) {
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');
values.push("\"" + name + "\": \"" + color + "\"");
}
@ -1179,7 +1345,6 @@ function onAddOutgoingAddressesCheck(checkBox) {
checkBox = $("addOutgoingAddresses");
}
$("addressBookList").disabled = !checkBox.checked;
}
function onReplyPlacementListChange() {

View File

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