Merge pull request #45 from alexcloutier/feature/PreventInvitations
New user preference to prevent invitationspull/47/head
commit
f3ded6ce2a
|
@ -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)";
|
||||
|
|
|
@ -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,10 +1672,8 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
|||
{
|
||||
event = [allEvents objectAtIndex: i];
|
||||
if ([event isAllDay] && [event isOpaque])
|
||||
{
|
||||
[event setTransparency: @"TRANSPARENT"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -11,28 +11,29 @@ var SOGoAutoCompletionInterface = {
|
|||
|
||||
// Attributes that could be changed from the object
|
||||
// inheriting the inteface
|
||||
uidField: "c_name",
|
||||
addressBook: null,
|
||||
excludeGroups: false,
|
||||
excludeLists: false,
|
||||
uidField: "c_name",
|
||||
addressBook: null,
|
||||
SOGoUsersSearch: false,
|
||||
excludeGroups: false,
|
||||
excludeLists: false,
|
||||
|
||||
// Internal attributes
|
||||
animationParent: null,
|
||||
selectedIndex: -1,
|
||||
delay: 0.750,
|
||||
delayedSearch: false,
|
||||
menu: null,
|
||||
animationParent: null,
|
||||
selectedIndex: -1,
|
||||
delay: 0.750,
|
||||
delayedSearch: false,
|
||||
menu: null,
|
||||
|
||||
bind: function () {
|
||||
bind: function () {
|
||||
this.menu = $('contactsMenu');
|
||||
this.writeAttribute("autocomplete", "off");
|
||||
this.writeAttribute("container", null);
|
||||
this.confirmedValue = null;
|
||||
this.observe("keydown", this.onKeydown.bindAsEventListener(this));
|
||||
this.observe("blur", this.onBlur.bindAsEventListener(this));
|
||||
},
|
||||
},
|
||||
|
||||
onKeydown: function (event) {
|
||||
onKeydown: function (event) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
this.focussed = true;
|
||||
return;
|
||||
|
@ -102,9 +103,9 @@ var SOGoAutoCompletionInterface = {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
onBlur: function (event) {
|
||||
onBlur: function (event) {
|
||||
if (this.delayedSearch)
|
||||
window.clearTimeout(this.delayedSearch);
|
||||
if (this.confirmedValue) {
|
||||
|
@ -116,9 +117,9 @@ var SOGoAutoCompletionInterface = {
|
|||
}
|
||||
else
|
||||
this.writeAttribute("uid", null);
|
||||
},
|
||||
},
|
||||
|
||||
performSearch: function (input) {
|
||||
performSearch: function (input) {
|
||||
// Perform address completion
|
||||
if (document.contactLookupAjaxRequest) {
|
||||
// Abort any pending request
|
||||
|
@ -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,13 +148,14 @@ var SOGoAutoCompletionInterface = {
|
|||
document.contactLookupAjaxRequest =
|
||||
triggerAjaxRequest(urlstr, input.performSearchCallback.bind(input), input);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (document.currentPopupMenu)
|
||||
hideMenu(document.currentPopupMenu);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
performSearchCallback: function (http) {
|
||||
performSearchCallback: function (http) {
|
||||
if (http.readyState == 4) {
|
||||
var list = this.menu.down("ul");
|
||||
|
||||
|
@ -257,9 +265,108 @@ var SOGoAutoCompletionInterface = {
|
|||
hideMenu(document.currentPopupMenu);
|
||||
document.contactLookupAjaxRequest = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
onAddressResultClick: function(event) {
|
||||
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')
|
||||
e = e.up('LI');
|
||||
|
@ -274,5 +381,5 @@ var SOGoAutoCompletionInterface = {
|
|||
this.fire("autocompletion:changed", Event.KEY_RETURN);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (/</, "<");
|
||||
tmp = tmp.replace (/>/, ">");
|
||||
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 + "\"");
|
||||
}
|
||||
|
@ -1144,7 +1310,7 @@ function onContactsCategoryAdd(e) {
|
|||
var list = $('contactsCategoriesListWrapper').down("TABLE").down("TBODY");
|
||||
list.appendChild(row);
|
||||
|
||||
resetContactsTableActions ();
|
||||
resetContactsTableActions();
|
||||
nametd.editionController.startEditing();
|
||||
}
|
||||
|
||||
|
@ -1179,7 +1345,6 @@ function onAddOutgoingAddressesCheck(checkBox) {
|
|||
checkBox = $("addOutgoingAddresses");
|
||||
}
|
||||
$("addressBookList").disabled = !checkBox.checked;
|
||||
|
||||
}
|
||||
|
||||
function onReplyPlacementListChange() {
|
||||
|
|
|
@ -374,7 +374,7 @@ TH.tbtv_navcell
|
|||
text-align: left;
|
||||
font-weight: normal;
|
||||
background-color: #E7E7E7;
|
||||
height: 20px; }
|
||||
height: 20px;}
|
||||
|
||||
TD.sortableTableHeader:active,
|
||||
TH.sortableTableHeader:active
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue