Merge branch 'master' into fix-some-warnings

Conflicts:
	SoObjects/Appointments/SOGoAppointmentObject.m
pull/199/head
Patrice Levesque 2016-02-15 11:23:27 -05:00
commit a5cc2bb5d5
66 changed files with 676 additions and 343 deletions

View File

@ -40,6 +40,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "NSDate+ActiveSync.h"
#include "NSString+ActiveSync.h"
#import <NGObjWeb/WOContext+SoObjects.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
@implementation NGVCard (ActiveSync)
//
@ -227,7 +232,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Other, less important fields
if ((o = [self birthday]))
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>", [o activeSyncRepresentationInContext: context]];
{
NSTimeZone *userTimeZone;
userTimeZone = [[[context activeUser] userDefaults] timeZone];
[s appendFormat: @"<Birthday xmlns=\"Contacts:\">%@</Birthday>",
[[o dateByAddingYears: 0 months: 0 days: 0
hours: 0 minutes: 0
seconds: ([userTimeZone secondsFromGMTForDate: o])*-1]
activeSyncRepresentationInContext: context]];
}
if ((o = [self note]))
{

View File

@ -1081,17 +1081,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
SOGoMailObject *mailObject;
NSMutableArray *a;
NSArray *partKeys;
int p;
NSRange r1, r2;
r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
[a autorelease];
pathToPart = [a lastObject];
[a removeLastObject];
messageName = [a lastObject];
[a removeLastObject];
folderName = [a componentsJoinedByString: @"/"];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
@ -1288,20 +1289,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoMailAccounts *accountsFolder;
SOGoUserFolder *userFolder;
SOGoMailObject *mailObject;
NSMutableArray *a;
if ([fileReference length])
{
// fetch attachment
NSRange r1, r2;
NSArray *partKeys;
int p;
r1 = [realCollectionId rangeOfString: @"/"];
r2 = [realCollectionId rangeOfString: @"/" options: 0 range: NSMakeRange(NSMaxRange(r1)+1, [realCollectionId length]-NSMaxRange(r1)-1)];
folderName = [realCollectionId substringToIndex: r1.location];
messageName = [realCollectionId substringWithRange: NSMakeRange(NSMaxRange(r1), r2.location-r1.location-1)];
pathToPart = [realCollectionId substringFromIndex: r2.location+1];
a = [[realCollectionId componentsSeparatedByString: @"/"] mutableCopy];
[a autorelease];
pathToPart = [a lastObject];
[a removeLastObject];
messageName = [a lastObject];
[a removeLastObject];
folderName = [a componentsJoinedByString: @"/"];
userFolder = [[context activeUser] homeFolderInContext: context];
accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO];
@ -1317,13 +1319,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
currentBodyPart = [mailObject lookupImap4BodyPartKey: [partKeys objectAtIndex:0] inContext: context];
for (p = 1; p < [partKeys count]; p++)
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
{
currentBodyPart = [currentBodyPart lookupImap4BodyPartKey: [partKeys objectAtIndex:p] inContext: context];
}
[s appendString: @"<Fetch>"];
[s appendString: @"<Status>1</Status>"];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">%@</FileReference>", fileReference];
[s appendFormat: @"<FileReference xmlns=\"AirSyncBase:\">mail/%@/%@/%@</FileReference>", [folderName stringByEscapingURL], messageName, pathToPart];
[s appendString: @"<Properties>"];
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
@ -1946,6 +1948,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
}
else
{
if (heartbeatInterval < internalInterval)
heartbeatInterval = internalInterval;
status = 1;
}

12
NEWS
View File

@ -1,6 +1,11 @@
3.0.2 (2016-02-DD)
------------------
New features
- [web] show all/only this calendar
- [web] convert a message to an appointment or a task (#1722)
- [web] customizable base font size for HTML messages
Enhancements
- [web] added Junk handling feature from v2
- [web] updated Material Icons font to version 2.1.3
@ -8,6 +13,8 @@ Enhancements
- [web] mail filters are now sortable
- [web] now supports RFC6154 and NoInferiors IMAP flag
- [web] improved confirm dialogs for deletions
- [web] allow resources to prevent invitations (#3410)
- [web] warn when double-booking attendees and offer force save option
Bug fixes
- [web] handle birthday dates before 1970
@ -16,6 +23,11 @@ Bug fixes
- [web] fixed virtual repeater when moving up in messages list
- [web] really delete mailboxes being deleted from the Trash folder (#595, #1189, #641)
- [web] fixed address autocompletion of mail editor affecting cards list of active addressbook
- [web] fixed batched delete of components (#3516)
- [web] fixed mail draft autosave in preferences (#3519)
- [web] fixed password change (#3496)
- [eas] allow EAS attachments get on 2nd-level mailboxes (#3505)
- [eas] fix EAS bday shift (#3518)
3.0.1 (2016-02-05)
------------------

View File

@ -1,5 +1,5 @@
/*
Copyright (C) 2007-2014 Inverse inc.
Copyright (C) 2007-2016 Inverse inc.
This file is part of SOGo

View File

@ -1,6 +1,5 @@
/*
Copyright (C) 2007-2015 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2007-2016 Inverse inc.
This file is part of SOGo
@ -46,6 +45,7 @@
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoDateFormatter.h>
#import <SOGo/SOGoGroup.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserSettings.h>
@ -402,8 +402,8 @@
}
// This method scans the list of attendees.
- (NSException *) _handleAttendeeAvailability: (NSArray *) theAttendees
forEvent: (iCalEvent *) theEvent
- (NSException *) _handleAttendeesAvailability: (NSArray *) theAttendees
forEvent: (iCalEvent *) theEvent
{
iCalPerson *currentAttendee;
SOGoUser *user;
@ -420,7 +420,7 @@
i = count = 0;
// Build list of the attendees uids without ressources
// Build list of the attendees uids
unavailableAttendees = [[NSMutableArray alloc] init];
enumerator = [theAttendees objectEnumerator];
ownerUID = [[[self context] activeUser] login];
@ -436,7 +436,7 @@
moduleSettings = [us objectForKey:@"Calendar"];
// Check if the user prevented their account from beeing invited to events
if (![user isResource] && [[moduleSettings objectForKey:@"PreventInvitations"] boolValue])
if ([[moduleSettings objectForKey:@"PreventInvitations"] boolValue])
{
// Check if the user have a whiteList
whiteList = [moduleSettings objectForKey:@"PreventInvitationsWhitelist"];
@ -483,20 +483,20 @@
//
// This methods scans the list of attendees. If they are
// considered as resource, it checks for conflicting
// dates for the event.
// dates for the event and potentially auto-accept/decline
// the invitation.
//
// For normal attendees, it'll return an exception with
// conflicting dates, unless we force the save.//
// We check for between startDate + 1 second and
// endDate - 1 second
//
//
// It also CHANGES the participation status of resources
// depending on constraints defined on them.
//
// Note that it doesn't matter if it changes the participation
// status since in case of an error, nothing will get saved.
//
- (NSException *) _handleResourcesConflicts: (NSArray *) theAttendees
- (NSException *) _handleAttendeesConflicts: (NSArray *) theAttendees
forEvent: (iCalEvent *) theEvent
force: (BOOL) forceSave
{
iCalPerson *currentAttendee;
NSMutableArray *attendees;
@ -527,110 +527,115 @@
enumerator = [attendees objectEnumerator];
while ((currentUID = [enumerator nextObject]))
{
NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate;
SOGoAppointmentFolder *folder;
NGCalendarDateRange *range;
NSMutableArray *fbInfo;
NSArray *allOccurences;
BOOL must_delete;
int i, j, delta;
user = [SOGoUser userWithLogin: currentUID];
if ([user isResource])
// We get the start/end date for our conflict range. If the event to be added is recurring, we
// check for at least a year to start with.
start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1];
end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1];
folder = [user personalCalendarFolderInContext: context];
// Deny access to the resource if the ACLs don't allow the user
if ([user isResource] && ![folder aclSQLListingFilter])
{
NSCalendarDate *start, *end, *rangeStartDate, *rangeEndDate;
SOGoAppointmentFolder *folder;
NGCalendarDateRange *range;
NSMutableArray *fbInfo;
NSArray *allOccurences;
BOOL must_delete;
int i, j, delta;
// We get the start/end date for our conflict range. If the event to be added is recurring, we
// check for at least a year to start with.
start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1];
end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1];
folder = [user personalCalendarFolderInContext: context];
// Deny access to the resource if the ACLs don't allow the user
if (![folder aclSQLListingFilter])
{
NSDictionary *values;
NSString *reason;
NSDictionary *values;
NSString *reason;
values = [NSDictionary dictionaryWithObjectsAndKeys:
[user cn], @"Cn",
[user systemEmail], @"SystemEmail"];
reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]];
return [NSException exceptionWithHTTPStatus:403 reason: reason];
}
values = [NSDictionary dictionaryWithObjectsAndKeys:
[user cn], @"Cn",
[user systemEmail], @"SystemEmail"];
reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]];
return [NSException exceptionWithHTTPStatus:403 reason: reason];
}
fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start
fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start
to: end]];
// We first remove any occurences in the freebusy that corresponds to the
// current event. We do this to avoid raising a conflict if we move a 1 hour
// meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise.
//
// We must also check here for repetitive events that don't overlap our event.
// We remove all events that don't overlap. The events here are already
// decomposed.
//
if ([theEvent isRecurrent])
allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end]
firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate]
endDate: [theEvent endDate]]];
else
allOccurences = nil;
for (i = [fbInfo count]-1; i >= 0; i--)
// We first remove any occurences in the freebusy that corresponds to the
// current event. We do this to avoid raising a conflict if we move a 1 hour
// meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise.
//
// We must also check here for repetitive events that don't overlap our event.
// We remove all events that don't overlap. The events here are already
// decomposed.
//
if ([theEvent isRecurrent])
allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start
endDate: end]
firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate]
endDate: [theEvent endDate]]];
else
allOccurences = nil;
for (i = [fbInfo count]-1; i >= 0; i--)
{
// We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been
// modified in SOGoAppointmentFolder: -fixupCycleRecord: ....
rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"];
delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT];
rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"];
delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent endDate]] secondsOffsetFromGMT];
rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate
endDate: rangeEndDate];
// We remove the freebusy entries corresponding to the actual event being modified
if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame)
{
// We MUST use the -uniqueChildWithTag method here because the event has been flattened, so its timezone has been
// modified in SOGoAppointmentFolder: -fixupCycleRecord: ....
rangeStartDate = [[fbInfo objectAtIndex: i] objectForKey: @"startDate"];
delta = [[rangeStartDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtstart"] timeZone] periodForDate: [theEvent startDate]] secondsOffsetFromGMT];
rangeStartDate = [rangeStartDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
rangeEndDate = [[fbInfo objectAtIndex: i] objectForKey: @"endDate"];
delta = [[rangeEndDate timeZoneDetail] timeZoneSecondsFromGMT] - [[[(iCalDateTime *)[theEvent uniqueChildWithTag: @"dtend"] timeZone] periodForDate: [theEvent endDate]] secondsOffsetFromGMT];
rangeEndDate = [rangeEndDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: delta];
range = [NGCalendarDateRange calendarDateRangeWithStartDate: rangeStartDate
endDate: rangeEndDate];
[fbInfo removeObjectAtIndex: i];
continue;
}
if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame)
// No need to check if the event isn't recurrent here as it's handled correctly
// when we compute the "end" date.
if ([allOccurences count])
{
must_delete = YES;
for (j = 0; j < [allOccurences count]; j++)
{
[fbInfo removeObjectAtIndex: i];
continue;
}
// No need to check if the event isn't recurrent here as it's handled correctly
// when we compute the "end" date.
if ([allOccurences count])
{
must_delete = YES;
for (j = 0; j < [allOccurences count]; j++)
if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]])
{
if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]])
{
must_delete = NO;
break;
}
must_delete = NO;
break;
}
if (must_delete)
[fbInfo removeObjectAtIndex: i];
}
if (must_delete)
[fbInfo removeObjectAtIndex: i];
}
}
// Find the attendee associated to the current UID
for (i = 0; i < [theAttendees count]; i++)
{
currentAttendee = [theAttendees objectAtIndex: i];
if ([[currentAttendee uidInContext: context] isEqualToString: currentUID])
break;
else
currentAttendee = nil;
}
if ([fbInfo count])
{
SOGoDateFormatter *formatter;
formatter = [[context activeUser] dateFormatterInContext: context];
// Find the attendee associated to the current UID
for (i = 0; i < [theAttendees count]; i++)
{
currentAttendee = [theAttendees objectAtIndex: i];
if ([[currentAttendee uidInContext: context] isEqualToString: currentUID])
break;
else
currentAttendee = nil;
}
if ([fbInfo count])
if ([user isResource])
{
// If we always force the auto-accept if numberOfSimultaneousBookings <= 0 (ie., no limit
// is imposed) or if numberOfSimultaneousBookings is greater than the number of
@ -650,35 +655,63 @@
NSDictionary *values;
NSString *reason;
iCalEvent *event;
calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]];
event = [[calendar events] lastObject];
values = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
[user cn], @"Cn",
[user systemEmail], @"SystemEmail",
([event summary] ? [event summary] : @""), @"EventTitle",
[[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate",
nil];
[NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings",
[user cn], @"Cn",
[user systemEmail], @"SystemEmail",
([event summary] ? [event summary] : @""), @"EventTitle",
[formatter formattedDateAndTime: [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"]], @"StartDate",
nil];
reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]];
return [NSException exceptionWithHTTPStatus: 403
reason: reason];
}
}
else if (currentAttendee)
//
// We are dealing with a normal attendee. Lets check if we have conflicts, unless
// we are being asked to force the save anyway
//
else if (!forceSave)
{
// No conflict, we auto-accept. We do this for resources automatically if no
// double-booking is observed. If it's not the desired behavior, just don't
// set the resource as one!
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
NSMutableDictionary *info;
NSMutableArray *conflicts;
id o;
info = [NSMutableDictionary dictionary];
conflicts = [NSMutableArray array];
[info setObject: [currentAttendee cn] forKey: @"attendee_name"];
[info setObject: [currentAttendee rfc822Email] forKey: @"attendee_email"];
for (i = 0; i < [fbInfo count]; i++)
{
o = [fbInfo objectAtIndex: i];
[conflicts addObject: [NSDictionary dictionaryWithObjectsAndKeys: [formatter formattedDateAndTime: [o objectForKey: @"startDate"]], @"startDate",
[formatter formattedDateAndTime: [o objectForKey: @"endDate"]], @"endDate", nil]];
}
[info setObject: conflicts forKey: @"conflicts"];
return [NSException exceptionWithHTTPStatus: 403
reason: [info jsonRepresentation]];
}
} // if ([fbInfo count]) ...
else if (currentAttendee && [user isResource])
{
// No conflict, we auto-accept. We do this for resources automatically if no
// double-booking is observed. If it's not the desired behavior, just don't
// set the resource as one!
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
}
}
} // if ([user isResource]) ...
return nil;
}
@ -687,6 +720,7 @@
//
- (NSException *) _handleAddedUsers: (NSArray *) attendees
fromEvent: (iCalEvent *) newEvent
force: (BOOL) forceSave
{
iCalPerson *currentAttendee;
NSEnumerator *enumerator;
@ -694,9 +728,9 @@
NSException *e;
// We check for conflicts
if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent]))
if ((e = [self _handleAttendeesConflicts: attendees forEvent: newEvent force: forceSave]))
return e;
if ((e = [self _handleAttendeeAvailability: attendees forEvent: newEvent]))
if ((e = [self _handleAttendeesAvailability: attendees forEvent: newEvent]))
return e;
enumerator = [attendees objectEnumerator];
@ -752,6 +786,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
- (NSException *) _handleUpdatedEvent: (iCalEvent *) newEvent
fromOldEvent: (iCalEvent *) oldEvent
force: (BOOL) forceSave
{
NSArray *addedAttendees, *deletedAttendees, *updatedAttendees;
iCalEventChanges *changes;
@ -791,9 +826,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
withType: @"calendar:cancellation"];
}
if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent]))
if ((ex = [self _handleAttendeesConflicts: [newEvent attendees] forEvent: newEvent force: forceSave]))
return ex;
if ((ex = [self _handleAttendeeAvailability: [newEvent attendees] forEvent: newEvent]))
if ((ex = [self _handleAttendeesAvailability: [newEvent attendees] forEvent: newEvent]))
return ex;
addedAttendees = [changes insertedAttendees];
@ -842,7 +877,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if ([addedAttendees count])
{
// Send an invitation to new attendees
if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent]))
if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent force: forceSave]))
return ex;
[self sendEMailUsingTemplateNamed: @"Invitation"
@ -887,6 +922,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
//
//
- (NSException *) saveComponent: (iCalEvent *) newEvent
{
return [self saveComponent: newEvent force: NO];
}
- (NSException *) saveComponent: (iCalEvent *) newEvent
force: (BOOL) forceSave
{
iCalEvent *oldEvent, *oldMasterEvent;
NSCalendarDate *recurrenceId;
@ -911,7 +952,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
// We catch conflicts and abort the save process immediately
// in case of one with resources
if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent]))
if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent force: forceSave]))
return ex;
if ([attendees count])
@ -954,7 +995,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if (!hasOrganizer || [oldMasterEvent userIsOrganizer: ownerUser])
// The owner is the organizer of the event; handle the modifications. We aslo
// catch conflicts just like when the events are created
if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]))
if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent force: forceSave]))
return ex;
}
@ -1591,7 +1632,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
currentUser = [context activeUser];
attendees = [occurence attendeesWithoutUser: currentUser];
#warning Make sure this is correct ..
if (![attendees count] && event != occurence)
attendees = [event attendeesWithoutUser: currentUser];
@ -1994,7 +2034,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
attendees = [event attendeesWithoutUser: ownerUser];
if ([attendees count])
{
if ((ex = [self _handleAddedUsers: attendees fromEvent: event]))
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
return ex;
else
{
@ -2146,7 +2186,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
if (!newEvent && oldEvent)
[self prepareDeleteOccurence: oldEvent];
// The master event was changed, A RECCURENCE-ID was added or modified
else if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent]))
else if ((ex = [self _handleUpdatedEvent: newEvent fromOldEvent: oldEvent force: YES]))
return ex;
}
//

View File

@ -61,7 +61,8 @@
- (void) updateComponent: (iCalRepeatableEntityObject *) newObject;
- (NSException *) saveCalendar: (iCalCalendar *) newCalendar;
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newObject;
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newEvent
force: (BOOL) forceSave;
/* mail notifications */
- (void) sendEMailUsingTemplateNamed: (NSString *) pageName
forObject: (iCalRepeatableEntityObject *) object

View File

@ -675,6 +675,12 @@
return [self saveCalendar: [newObject parent]];
}
- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newEvent
force: (BOOL) forceSave
{
return [self saveComponent: newEvent];
}
/* raw saving */
/* EMail Notifications */

View File

@ -1,7 +1,5 @@
/*
Copyright (C) 2006-2014 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2014-2016 Inverse inc.
This file is part of SOGo.
@ -26,24 +24,6 @@
#import "SOGoCalendarComponent.h"
/*
SOGoTaskObject
Represents a single task. This SOPE controller object manages all the
attendee storages (that is, it might store into multiple folders for meeting
tasks!).
Note: SOGoTaskObject do not need to exist yet. They can also be "new"
tasks with an externally generated unique key.
*/
@class NSArray;
@class NSException;
@class NSString;
@class iCalToDo;
@class iCalCalendar;
@interface SOGoTaskObject : SOGoCalendarComponent
@end

View File

@ -1,6 +1,5 @@
/*
Copyright (C) 2006-2014 Inverse inc.
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2006-2014-2016 Inverse inc.
This file is part of SOGo.

View File

@ -164,8 +164,7 @@
NSMutableArray *keys;
NSArray *acceptedTypes;
acceptedTypes
= [NSArray arrayWithObjects: @"text/plain", @"text/html", nil];
acceptedTypes = [NSArray arrayWithObjects: @"text/plain", @"text/html", nil];
keys = [NSMutableArray array];
[self addRequiredKeysOfStructure: [self bodyStructure]
path: @"" toArray: keys acceptedTypes: acceptedTypes

View File

@ -1,14 +1,14 @@
/*
Copyright (C) 2004 SKYRIX Software AG
Copyright (C) 2005-2016 Inverse inc.
This file is part of OpenGroupware.org.
This file is part of SOGo.
OGo is free software; you can redistribute it and/or modify it under
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
@ -48,21 +48,6 @@
- (NSString *) stringForObjectValue: (id) date;
// - (void) setFullWeekdayNameAndDetails;
// - (NSString *) date: (NSCalendarDate *) date
// withFormat: (unsigned int) format;
// - (NSString *) date: (NSCalendarDate *) date
// withNSFormat: (NSNumber *) format;
// - (NSString *) shortDayOfWeek: (int)_day;
// - (NSString *) fullDayOfWeek: (int)_day;
// - (NSString *) shortMonthOfYear: (int)_month;
// - (NSString *) fullMonthOfYear: (int)_month;
// - (NSString *) fullWeekdayNameAndDetailsForDate: (NSCalendarDate *)_date;
@end
#endif /* __SOGoDateFormatter_H_ */

View File

@ -1,14 +1,14 @@
/*
Copyright (C) 2004 SKYRIX Software AG
Copyright (C) 2005-2016 Inverse inc.
This file is part of OpenGroupware.org.
This file is part of SOGo.
OGo is free software; you can redistribute it and/or modify it under
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
@ -21,7 +21,7 @@
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSUserDefaults.h> /* for NSXXXFormatString, ... */
#import <Foundation/NSUserDefaults.h>
#import "SOGoDateFormatter.h"

View File

@ -72,6 +72,7 @@
SOGoTrashFolderName = "Trash";
SOGoJunkFolderName = "Junk";
SOGoMailComposeMessageType = "html";
SOGoMailComposeFontSize = 0;
SOGoMailDisplayRemoteInlineImages = "never";
SOGoMailAutoSave = "5";

View File

@ -132,6 +132,9 @@ extern NSString *SOGoWeekStartFirstFullWeek;
- (void) setMailComposeMessageType: (NSString *) newValue;
- (NSString *) mailComposeMessageType;
- (void) setMailComposeFontSize: (NSString *) newValue;
- (NSString *) mailComposeFontSize;
- (void) setMailDisplayRemoteInlineImages: (NSString *) newValue;
- (NSString *) mailDisplayRemoteInlineImages;

View File

@ -534,6 +534,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek";
return [self stringForKey: @"SOGoMailComposeMessageType"];
}
- (void) setMailComposeFontSize: (NSString *) newValue
{
[self setObject: newValue forKey: @"SOGoMailComposeFontSize"];
}
- (NSString *) mailComposeFontSize
{
return [self stringForKey: @"SOGoMailComposeFontSize"];
}
- (void) setMailDisplayRemoteInlineImages: (NSString *) newValue
{
[self setObject: newValue forKey: @"SOGoMailDisplayRemoteInlineImages"];

View File

@ -187,6 +187,13 @@
"Save As..." = "Save As...";
"Print Preview" = "Print Preview";
"View Message Source" = "View Message Source";
/* Message view "more" menu: create an event from message */
"Convert To Event" = "Convert To Event";
/* Message view "more" menu: create a task from message */
"Convert To Task" = "Convert To Task";
"Print..." = "Print...";
"Delete Message" = "Delete Message";
"Delete Selected Messages" = "Delete Selected Messages";

View File

@ -1,6 +1,6 @@
/* UIxMailActions.m - this file is part of SOGo
*
* Copyright (C) 2007-2014 Inverse inc.
* Copyright (C) 2007-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -24,10 +24,13 @@
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <SoObjects/Mailer/NSString+Mail.h>
#import <SoObjects/Mailer/SOGoDraftObject.h>
#import <SoObjects/Mailer/SOGoDraftsFolder.h>
#import <SoObjects/Mailer/SOGoMailAccount.h>
#import <SoObjects/Mailer/SOGoMailObject.h>
#import <SoObjects/Mailer/SOGoMailObject+Draft.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
@ -116,6 +119,61 @@
andString: [data jsonRepresentation]];
}
- (WOResponse *) viewPlainAction
{
BOOL htmlContent;
NSArray *acceptedTypes, *types;
NSDictionary *parts;
NSMutableArray *keys;
NSMutableDictionary *data;
NSString *rawPart, *contentKey, *subject, *content;
NSUInteger index;
SOGoMailObject *co;
co = [self clientObject];
subject = [co decodedSubject];
htmlContent = NO;
data = [NSMutableDictionary dictionary];
if (subject)
[data setObject: subject
forKey: @"subject"];
// Fetch the text parts of the message body structure
acceptedTypes = [NSArray arrayWithObjects: @"text/plain", @"text/html", nil];
keys = [NSMutableArray array];
[co addRequiredKeysOfStructure: [co bodyStructure]
path: @"" toArray: keys acceptedTypes: acceptedTypes
withPeek: NO];
// Use plain part if available, otherwise use the HTML part
types = [keys objectsForKey: @"mimeType" notFoundMarker: @""];
index = [types indexOfObject: @"text/plain"];
if (index == NSNotFound)
{
index = [types indexOfObject: @"text/html"];
htmlContent = YES;
}
// Fetch part and convert HTML if necessary
contentKey = [keys objectAtIndex: index];
parts = [co fetchPlainTextStrings: [NSArray arrayWithObject: contentKey]];
if ([parts count] > 0)
{
rawPart = [[parts allValues] objectAtIndex: 0];
if (htmlContent)
content = [rawPart htmlToText];
else
content = rawPart;
if (content)
[data setObject: [content stringByTrimmingSpaces]
forKey: @"content"];
}
return [self responseWithStatus: 201
andString: [data jsonRepresentation]];
}
/* active message */
- (id) markMessageUnflaggedAction

View File

@ -612,9 +612,11 @@ static NSArray *infoKeys = nil;
{
NSDictionary *info;
NSException *error;
NSString *fontSize, *content;
NGMimeType *mimeType;
WORequest *request;
SOGoDraftObject *co;
SOGoUserDefaults *ud;
error = nil;
request = [context request];
@ -632,7 +634,22 @@ static NSArray *infoKeys = nil;
info = [self infoFromRequest];
[co setHeaders: info];
[co setIsHTML: isHTML];
[co setText: (isHTML ? [NSString stringWithFormat: @"<html>%@</html>", text] : text)];;
if (isHTML)
{
// Set a base font size if mail is HTML and user has set a default font-size
ud = [[context activeUser] userDefaults];
fontSize = [ud mailComposeFontSize];
if ([fontSize intValue] > 0)
content = [NSString stringWithFormat: @"<html><span style=\"font-size: %@px;\">%@</span></html>",
fontSize, text];
else
content = [NSString stringWithFormat: @"<html>%@</html>", text];
}
else
{
content = text;
}
[co setText: content];
error = [co storeInfo];
}

View File

@ -278,6 +278,11 @@
actionClass = "UIxMailActions";
actionName = "forward";
};
viewplain = {
protectedBy = "View";
actionClass = "UIxMailActions";
actionName = "viewPlain";
};
markMessageUncollapse = {
protectedBy = "View";
actionClass = "UIxMailActions";

View File

@ -142,6 +142,10 @@
"Compose messages in" = "Compose messages in";
"composemessagestype_html" = "HTML";
"composemessagestype_text" = "Plain text";
/* Base font size for messages composed in HTML */
"Default font size" = "Default font size";
"Display remote inline images" = "Display remote inline images";
"displayremoteinlineimages_never" = "Never";
"displayremoteinlineimages_always" = "Always";

View File

@ -171,6 +171,9 @@ static SoProduct *preferencesProduct = nil;
if (![[defaults source] objectForKey: @"SOGoMailComposeMessageType"])
[[defaults source] setObject: [defaults mailComposeMessageType] forKey: @"SOGoMailComposeMessageType"];
if (![[defaults source] objectForKey: @"SOGoMailComposeFontSize"])
[[defaults source] setObject: [defaults mailComposeFontSize] forKey: @"SOGoMailComposeFontSize"];
if (![[defaults source] objectForKey: @"SOGoMailDisplayRemoteInlineImages"])
[[defaults source] setObject: [defaults mailDisplayRemoteInlineImages] forKey: @"SOGoMailDisplayRemoteInlineImages"];

View File

@ -1163,6 +1163,27 @@ static NSArray *reminderValues = nil;
: [self _defaultEmailAddresses]);
}
//
// Used by templates
//
- (NSArray *) fontSizesList
{
static NSArray *fontSizes = nil;
if (!fontSizes)
{
fontSizes = [NSArray arrayWithObjects: @"8", @"9", @"10", @"11", @"12", @"13", @"14", @"16", @"18",
@"20", @"22", @"24", @"26", @"28",
@"36",
@"48",
@"72",
nil];
[fontSizes retain];
}
return fontSizes;
}
//
// Used by templates
//

View File

@ -437,6 +437,13 @@ vtodo_class2 = "(Confidential task)";
"When I modify my calendar, send a mail to" = "When I modify my calendar, send a mail to";
"Email Address" = "Email Address";
"Export" = "Export";
/* Show only the calendar for which the menu is displayed */
"Show Only This Calendar" = "Show Only This Calendar";
/* Show all calendar (personal, subscriptions and web) */
"Show All Calendars" = "Show All Calendars";
"Links to this Calendar" = "Links to this Calendar";
"Authenticated User Access" = "Authenticated User Access";
"CalDAV URL" = "CalDAV URL ";

View File

@ -1,8 +1,6 @@
/* UIxAppointmentActions.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2010-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,8 +1,6 @@
/* UIxAppointmentActions.m - this file is part of SOGo
*
* Copyright (C) 2011-2014 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Copyright (C) 2011-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -65,6 +63,7 @@
NSException *ex;
SOGoAppointmentFolder *targetCalendar, *sourceCalendar;
SOGoAppointmentFolders *folders;
BOOL forceSave;
rq = [context request];
params = [[rq contentAsString] objectFromJSONString];
@ -73,6 +72,7 @@
startDelta = [params objectForKey: @"start"];
durationDelta = [params objectForKey: @"duration"];
destionationCalendar = [params objectForKey: @"destination"];
forceSave = NO;
if (daysDelta || startDelta || durationDelta)
{
@ -116,7 +116,8 @@
[event updateRecurrenceRulesUntilDate: end];
[event setLastModified: [NSCalendarDate calendarDate]];
ex = [co saveComponent: event];
ex = [co saveComponent: event force: forceSave];
// This condition will be executed only if the event is moved from a calendar to another. If destionationCalendar == 0; there is no calendar change
if ([destionationCalendar length] > 0)
{
@ -135,12 +136,21 @@
ex = [co moveToFolder: targetCalendar];
}
}
if (ex)
{
unsigned int httpStatus;
httpStatus = 500;
if ([ex respondsToSelector: @selector(httpStatus)])
httpStatus = [ex httpStatus];
jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys:
[ex reason], @"message",
[ex reason], @"message",
nil];
response = [self responseWithStatus: 403
response = [self responseWithStatus: httpStatus
andJSONRepresentation: jsonResponse];
}
else

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.h - this file is part of SOGo
*
* Copyright (C) 2007-2015 Inverse inc.
* Copyright (C) 2007-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
/* UIxAppointmentEditor.m - this file is part of SOGo
*
* Copyright (C) 2007-2015 Inverse inc.
* Copyright (C) 2007-2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -454,7 +454,9 @@
SOGoAppointmentObject *co;
SoSecurityManager *sm;
WORequest *request;
unsigned int httpStatus;
BOOL forceSave;
event = [self event];
co = [self clientObject];
@ -475,6 +477,7 @@
else
{
[self setAttributes: params];
forceSave = NO;
if ([event hasRecurrenceRules])
[self _adjustRecurrentRules];
@ -498,12 +501,12 @@
}
// Save the event.
ex = [co saveComponent: event];
ex = [co saveComponent: event force: forceSave];
}
else
{
// The event was modified -- save it.
ex = [co saveComponent: event];
ex = [co saveComponent: event force: forceSave];
if (componentCalendar
&& ![[componentCalendar ocsPath]
@ -526,9 +529,12 @@
if (ex)
{
httpStatus = 500;
if ([ex respondsToSelector: @selector(httpStatus)])
httpStatus = [ex httpStatus];
jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys:
@"failure", @"status",
[ex reason], @"message",
[ex reason], @"message",
nil];
}
else

View File

@ -101,7 +101,8 @@
<a ui-sref="app.addressbook.card.view({addressbookId: editor.currentFolder.id, cardId: ref.reference})">{{ ref.$fullname() }}</a>
</h3>
<h4 ng-show="ref.email">
<a ui-sref="mailto:{{ref.email}}" ng-class="ng-scope">{{ ref.email }}</a>
<a href="#" ng-bind="ref.email"
ng-click="addressbook.newMessageWithRecipient($event, ref.email, ref.$fullname())"><!-- contact email --></a>
</h4>
</div>
</md-list-item>

View File

@ -8,7 +8,7 @@
xmlns:label="OGo:label"
className="UIxPageFrame"
title="title"
const:jsFiles="Common.js, Preferences.services.js, Contacts.services.js, Mailer.js, Mailer.services.js, vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js">
const:jsFiles="Common.js, Preferences.services.js, Contacts.services.js, Scheduler.services.js, Mailer.js, Mailer.services.js, vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js">
<script type="text/javascript">
var mailAccounts = <var:string value="mailAccounts" const:escapeHTML="NO" />;
var userNames = <var:string value="userNames" const:escapeHTML="NO" />;
@ -116,7 +116,8 @@
md-menu-origin="md-menu-origin">more_vert</md-icon>
<md-menu-content width="3">
<md-menu-item>
<md-button type="button" md-menu-align-target="md-menu-align-target" ng-click="app.markFolderRead(folder)">
<md-button type="button" md-menu-align-target="md-menu-align-target"
ng-click="app.markFolderRead(folder)">
<var:string label:value="Mark Folder Read"/>
</md-button>
</md-menu-item>

View File

@ -87,6 +87,19 @@
<var:string label:value="View Message Source"/>
</md-button>
</md-menu-item>
<md-menu-divider><!-- divider --></md-menu-divider>
<md-menu-item>
<md-button label:aria-label="Convert To Event"
ng-click="viewer.convertToEvent($event)">
<var:string label:value="Convert To Event"/>
</md-button>
</md-menu-item>
<md-menu-item>
<md-button label:aria-label="Convert To Task"
ng-click="viewer.convertToTask($event)">
<var:string label:value="Convert To Task"/>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</md-card-actions>

View File

@ -14,7 +14,11 @@
<md-icon class="material-icons sg-icon-toolbar-bg">filter_list</md-icon>
<md-input-container class="md-block md-flex">
<label><var:string label:value="Filter name"/></label>
<input class="md-title" type="text" ng-model="filterEditor.filter.name" required="required"/>
<input class="md-title"
type="text"
md-autofocus="true"
ng-model="filterEditor.filter.name"
required="required"/>
</md-input-container>
<md-button type="button" class="sg-icon-button " ng-click="filterEditor.cancel()">
<md-icon>close</md-icon>

View File

@ -511,19 +511,18 @@
</md-checkbox>
</div>
<div>
<div layout="row" layout-align="start center" flex="50" flex-xs="100">
<md-checkbox
class="md-align-top-left" ng-model="app.preferences.defaults.SOGoMailAddOutgoingAddresses"
ng-true-value="1"
ng-false-value="0"
label:arial-label="When sending mail, add unknown recipients to my">
<var:string label:value="When sending mail, add unknown recipients to my"/>
</md-checkbox>
<md-input-container>
<label><var:string label:aria-label="Address Book"/></label>
<md-input-container class="md-block md-flex">
<label><var:string label:value="When sending mail, add unknown recipients to my"/></label>
<md-select
ng-disabled="app.preferences.defaults.SOGoMailAddOutgoingAddresses == 0"
ng-disabled="app.preferences.defaults.SOGoMailAddOutgoingAddresses != 1"
ng-model="app.preferences.defaults.SOGoSelectedAddressBook">
<var:foreach list="addressBookList" item="item">
<md-option var:value="item.id">
@ -534,7 +533,7 @@
</md-input-container>
</div>
<md-input-container class="md-block">
<md-input-container class="md-block" flex="50" flex-xs="100">
<label><var:string label:value="Forward messages"/></label>
<md-select ng-model="app.preferences.defaults.SOGoMailMessageForwarding">
<var:foreach list="messageForwardingList" item="item">
@ -569,18 +568,42 @@
</md-input-container>
</div>
<md-input-container class="md-block">
<label><var:string label:value="Compose messages in"/></label>
<md-select ng-model="app.preferences.defaults.SOGoMailComposeMessageType">
<var:foreach list="composeMessagesType" item="item">
<md-option var:value="item">
<var:string value="itemComposeMessagesText"/>
</md-option>
</var:foreach>
</md-select>
</md-input-container>
<div layout="row" layout-wrap="layout-wrap">
<md-input-container class="md-block" flex="50">
<label><var:string label:value="Compose messages in"/></label>
<md-select ng-model="app.preferences.defaults.SOGoMailComposeMessageType">
<var:foreach list="composeMessagesType" item="item">
<md-option var:value="item">
<var:string value="itemComposeMessagesText"/>
</md-option>
</var:foreach>
</md-select>
</md-input-container>
<md-input-container class="md-block">
<div layout="row" flex="50"
ng-show="app.preferences.defaults.SOGoMailComposeMessageType == 'html'">
<md-checkbox
ng-model="app.preferences.defaults.SOGoMailComposeFontSizeEnabled"
label:aria-label="Default font size">
</md-checkbox>
<md-input-container class="md-block md-flex">
<label><var:string label:value="Default font size"/></label>
<md-select label:aria-label="Default font size"
ng-disabled="!app.preferences.defaults.SOGoMailComposeFontSizeEnabled"
ng-required="app.preferences.defaults.SOGoMailComposeFontSizeEnabled"
ng-model="app.preferences.defaults.SOGoMailComposeFontSize">
<var:foreach list="fontSizesList" item="item">
<md-option var:value="item">
<var:string value="item"/>
</md-option>
</var:foreach>
</md-select>
<div class="md-char-counter">px</div>
</md-input-container>
</div>
</div>
<md-input-container class="md-block" flex="50" flex-xs="100">
<label><var:string label:value="Display remote inline images"/></label>
<md-select ng-model="app.preferences.defaults.SOGoMailDisplayRemoteInlineImages">
<var:foreach list="displayRemoteInlineImages" item="item">
@ -594,7 +617,8 @@
<div layout="row" layout-align="start center">
<p><var:string label:value="Auto save every"/></p>
<md-input-container class="md-input-number" md-no-float="md-no-float">
<input type="number" min="0" label:aria-label="minutes" ng-model="preferences.defaults.SOGoMailAutoSave"/>
<input type="number" min="0" label:aria-label="minutes"
ng-model="app.preferences.defaults.SOGoMailAutoSave"/>
</md-input-container>
<var:string label:value="minutes"/>
</div>

View File

@ -8,6 +8,7 @@
<form name="eventForm" ng-submit="editor.save(eventForm)">
<md-toolbar ng-class="editor.component.getClassName('bg')">
<div class="md-toolbar-tools">
<md-icon class="material-icons sg-icon-toolbar-bg">event</md-icon>
<!-- summary -->
<md-icon ng-if="editor.component.classification == 'confidential'">visibility_off</md-icon>
<md-icon ng-if="editor.component.classification == 'private'">vpn_key</md-icon>

View File

@ -188,6 +188,17 @@
</md-button>
</md-menu-item>
<md-menu-divider><!-- divider --></md-menu-divider>
<md-menu-item>
<md-button ng-click="app.showOnly(calendar)">
<var:string label:value="Show Only This Calendar"/>
</md-button>
</md-menu-item>
<md-menu-item>
<md-button ng-click="app.showAll()">
<var:string label:value="Show All Calendars"/>
</md-button>
</md-menu-item>
<md-menu-divider><!-- divider --></md-menu-divider>
<md-menu-item>
<md-button ng-click="app.showLinks(calendar)">
<var:string label:value="Links to this Calendar"/>

View File

@ -8,6 +8,7 @@
<form name="eventForm" ng-submit="editor.save(eventForm)">
<md-toolbar ng-class="editor.component.getClassName('bg')">
<div class="md-toolbar-tools">
<md-icon class="material-icons sg-icon-toolbar-bg">assignment_turned_in</md-icon>
<!-- summary -->
<md-icon ng-if="editor.component.classification == 'confidential'">visibility_off</md-icon>
<md-icon ng-if="editor.component.classification == 'private'">vpn_key</md-icon>

View File

@ -4,7 +4,7 @@ include ../common.make
WEBSERVER_RESOURCE_DIRS = css fonts img js
JS_FILES = js/Administration.* js/Common.* js/Contacts.* js/Mailer.* js/Main.* js/Preferences.* js/Scheduler.* css/styles.*
JS_LIB_FILES = js/vendor/angular-animate.* js/vendor/angular-aria.* js/vendor/angular-file-upload.min.js js/vendor/angular-material.* js/vendor/angular-sanitize.* js/vendor/angular-ui-router.* js/vendor/angular.* js/vendor/lodash.*
JS_LIB_FILES = js/vendor/angular-animate.* js/vendor/angular-aria.* js/vendor/angular-file-upload.min.js js/vendor/ng-sortable.* js/vendor/angular-material.* js/vendor/angular-sanitize.* js/vendor/angular-ui-router.* js/vendor/angular.* js/vendor/lodash.*
CSS_FILES = css/styles.css css/styles.css.map
.DEFAULT_GOAL := all
@ -26,7 +26,11 @@ prod:
grunt --stack build
git update-index --no-assume-unchanged $(CSS_FILES) $(JS_FILES) $(JS_LIB_FILES)
git add -f $(CSS_FILES) $(JS_FILES) $(JS_LIB_FILES)
git commit -m "(js/css) Update generated files"
@if ! git diff --quiet --exit-code; then \
git commit -m "(js/css) Update generated files"; \
else \
echo "Nothing to commit; skipping git-commit"; \
fi
git update-index --assume-unchanged $(CSS_FILES) $(JS_FILES) $(JS_LIB_FILES)
all:

View File

@ -2,8 +2,8 @@ module.exports = function(grunt) {
var js_files = {
'js/Common.js': ['js/Common/*.app.js', 'js/Common/*.filter.js', 'js/Common/*Controller.js', 'js/Common/*.service.js', 'js/Common/*.directive.js', 'js/Common/utils.js'],
'js/Main.js': ['js/Main/Main.app.js'],
'js/Scheduler.services.js': ['js/Scheduler/*.service.js'],
'js/Scheduler.js': ['js/Scheduler/Scheduler.app.js', 'js/Scheduler/*Controller.js', 'js/Scheduler/*.directive.js'],
'js/Scheduler.services.js': ['js/Scheduler/*.service.js', 'js/Scheduler/*Controller.js', 'js/Scheduler/*.directive.js'],
'js/Scheduler.js': ['js/Scheduler/Scheduler.app.js'],
'js/Contacts.services.js': ['js/Contacts/*.service.js'],
'js/Contacts.js': ['js/Contacts/Contacts.app.js', 'js/Contacts/*Controller.js', 'js/Contacts/*.directive.js'],
'js/Mailer.services.js': ['js/Mailer/*.service.js', 'js/Mailer/*Controller.js', 'js/Mailer/*.directive.js'],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -93,7 +93,7 @@
*/
getService.$inject = ['$q', '$http', 'passwordPolicyConfig'];
function getService($q, $http, passwordPolicyConfig) {
var _this = this, service;
var service;
service = {
login: function(data) {
@ -171,7 +171,7 @@
changePassword: function(newPassword) {
var d = $q.defer(),
loginCookie = _this.readLoginCookie();
loginCookie = readLoginCookie();
$http({
method: 'POST',

View File

@ -328,7 +328,7 @@ Date.prototype.beginOfDay = function() {
return beginOfDay;
};
Date.prototype.beginOfWeek = function() {
Date.prototype.beginOfWeek = function(firstDayOfWeek) {
var offset = firstDayOfWeek - this.getDay();
if (offset > 0)
offset -= 7;
@ -340,8 +340,8 @@ Date.prototype.beginOfWeek = function() {
return beginOfWeek;
};
Date.prototype.endOfWeek = function() {
var endOfWeek = this.beginOfWeek();
Date.prototype.endOfWeek = function(firstDayOfWeek) {
var endOfWeek = this.beginOfWeek(firstDayOfWeek);
endOfWeek.addDays(6);
endOfWeek.setHours(23);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
!function(){"use strict";function a(a,l){a.state("mail",{url:"/Mail",views:{mailboxes:{templateUrl:"UIxMailMainFrame",controller:"MailboxesController",controllerAs:"app"}},resolve:{stateAccounts:b}}).state("mail.account",{url:"/:accountId","abstract":!0,views:{mailbox:{template:"<ui-view/>"}},resolve:{stateAccount:c}}).state("mail.account.virtualMailbox",{url:"/virtual",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:g}}).state("mail.account.virtualMailbox.message",{url:"/:mailboxId/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},resolve:{stateMailbox:h,stateMessages:f,stateMessage:i}}).state("mail.account.inbox",{url:"/inbox",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:e,stateMessages:f}}).state("mail.account.mailbox",{url:"/:mailboxId",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:d,stateMessages:f}}).state("mail.account.mailbox.message",{url:"/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},onEnter:j,onExit:k,resolve:{stateMessage:i}}),l.otherwise("/Mail/0/inbox")}function b(a,b){var c=b.$findAll(window.mailAccounts),d=[];return angular.forEach(c,function(a,b){var c=a.$getMailboxes();d.push(c.then(function(b){return a}))}),a.all(d)}function c(a,b){return _.find(b,function(b){return b.id==a.accountId})}function d(a,b,c,d,e,f){var g,h,i=e(c.mailboxId);return h=function(a){var b=_.find(a,function(a){return a.path==i});return b||angular.forEach(a,function(a){!b&&a.children&&a.children.length>0&&(b=h(a.children))}),b},f.selectedFolder&&(f.selectedFolder.$isLoading=!0),g=h(d.$mailboxes),g?(g.$topIndex=0,g):b.go("mail.account.inbox")}function e(a,b){return b.selectedFolder&&(b.selectedFolder.$isLoading=!0),a.$mailboxes[0]}function f(a,b){return a.$virtualMode?[]:b.$filter()}function g(a,b){return b.$virtualMode?b.selectedFolder:a.reject("No virtual mailbox defined")}function h(a,b,c,d){var e=c(d.mailboxId);return b.$virtualMode?(b.selectedFolder.resetSelectedMessage(),_.find(b.selectedFolder.$mailboxes,function(a){return a.path==e})):a.reject("No virtual mailbox defined for message")}function i(a,b,c,d,e,f){var g;return(g=_.find(e.$messages,function(a){return a.uid==parseInt(c.messageId)}))?g.$reload():void d.go("mail.account.mailbox",{accountId:e.$account.id,mailboxId:b(e.path)})}function j(a,b){b.selectedMessage=parseInt(a.messageId)}function k(a){a.selectedMessage=-1}function l(a,b,c){a.$on("$stateChangeError",function(a,d,e,f,g,h){b.error(h),a.preventDefault(),"mail.account.inbox"!=d.name?c.go("mail.account.inbox"):c.go("mail")}),a.$on("$routeChangeError",function(a,c,d,e){b.error(a,c,d,e)})}angular.module("SOGo.MailerUI",["ui.router","ck","angularFileUpload","SOGo.Common","SOGo.ContactsUI","ngAnimate","SOGo.PreferencesUI"]).config(a).run(l),a.$inject=["$stateProvider","$urlRouterProvider"],b.$inject=["$q","Account"],c.$inject=["$stateParams","stateAccounts"],d.$inject=["$q","$state","$stateParams","stateAccount","decodeUriFilter","Mailbox"],e.$inject=["stateAccount","Mailbox"],f.$inject=["Mailbox","stateMailbox"],g.$inject=["$q","Mailbox"],h.$inject=["$q","Mailbox","decodeUriFilter","$stateParams"],i.$inject=["Mailbox","encodeUriFilter","$stateParams","$state","stateMailbox","stateMessages"],j.$inject=["$stateParams","stateMailbox"],k.$inject=["stateMailbox"],l.$inject=["$rootScope","$log","$state"]}();
!function(){"use strict";function a(a,l){a.state("mail",{url:"/Mail",views:{mailboxes:{templateUrl:"UIxMailMainFrame",controller:"MailboxesController",controllerAs:"app"}},resolve:{stateAccounts:b}}).state("mail.account",{url:"/:accountId","abstract":!0,views:{mailbox:{template:"<ui-view/>"}},resolve:{stateAccount:c}}).state("mail.account.virtualMailbox",{url:"/virtual",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:g}}).state("mail.account.virtualMailbox.message",{url:"/:mailboxId/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},resolve:{stateMailbox:h,stateMessages:f,stateMessage:i}}).state("mail.account.inbox",{url:"/inbox",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:e,stateMessages:f}}).state("mail.account.mailbox",{url:"/:mailboxId",views:{"mailbox@mail":{templateUrl:"UIxMailFolderTemplate",controller:"MailboxController",controllerAs:"mailbox"}},resolve:{stateMailbox:d,stateMessages:f}}).state("mail.account.mailbox.message",{url:"/:messageId",views:{message:{templateUrl:"UIxMailViewTemplate",controller:"MessageController",controllerAs:"viewer"}},onEnter:j,onExit:k,resolve:{stateMessage:i}}),l.otherwise("/Mail/0/inbox")}function b(a,b){var c=b.$findAll(window.mailAccounts),d=[];return angular.forEach(c,function(a,b){var c=a.$getMailboxes();d.push(c.then(function(b){return a}))}),a.all(d)}function c(a,b){return _.find(b,function(b){return b.id==a.accountId})}function d(a,b,c,d,e,f){var g,h,i=e(c.mailboxId);return h=function(a){var b=_.find(a,function(a){return a.path==i});return b||angular.forEach(a,function(a){!b&&a.children&&a.children.length>0&&(b=h(a.children))}),b},f.selectedFolder&&(f.selectedFolder.$isLoading=!0),g=h(d.$mailboxes),g?(g.$topIndex=0,g):b.go("mail.account.inbox")}function e(a,b){return b.selectedFolder&&(b.selectedFolder.$isLoading=!0),a.$mailboxes[0]}function f(a,b){return a.$virtualMode?[]:b.$filter()}function g(a,b){return b.$virtualMode?b.selectedFolder:a.reject("No virtual mailbox defined")}function h(a,b,c,d){var e=c(d.mailboxId);return b.$virtualMode?(b.selectedFolder.resetSelectedMessage(),_.find(b.selectedFolder.$mailboxes,function(a){return a.path==e})):a.reject("No virtual mailbox defined for message")}function i(a,b,c,d,e,f){var g;return(g=_.find(e.$messages,function(a){return a.uid==parseInt(c.messageId)}))?g.$reload():void d.go("mail.account.mailbox",{accountId:e.$account.id,mailboxId:b(e.path)})}function j(a,b){b.selectedMessage=parseInt(a.messageId)}function k(a){a.selectedMessage=-1}function l(a,b,c){a.$on("$stateChangeError",function(a,d,e,f,g,h){b.error(h),a.preventDefault(),"mail.account.inbox"!=d.name?c.go("mail.account.inbox"):c.go("mail")}),a.$on("$routeChangeError",function(a,c,d,e){b.error(a,c,d,e)})}angular.module("SOGo.MailerUI",["ui.router","ck","angularFileUpload","SOGo.Common","SOGo.ContactsUI","SOGo.SchedulerUI","ngAnimate","SOGo.PreferencesUI"]).config(a).run(l),a.$inject=["$stateProvider","$urlRouterProvider"],b.$inject=["$q","Account"],c.$inject=["$stateParams","stateAccounts"],d.$inject=["$q","$state","$stateParams","stateAccount","decodeUriFilter","Mailbox"],e.$inject=["stateAccount","Mailbox"],f.$inject=["Mailbox","stateMailbox"],g.$inject=["$q","Mailbox"],h.$inject=["$q","Mailbox","decodeUriFilter","$stateParams"],i.$inject=["Mailbox","encodeUriFilter","$stateParams","$state","stateMailbox","stateMessages"],j.$inject=["$stateParams","stateMailbox"],k.$inject=["stateMailbox"],l.$inject=["$rootScope","$log","$state"]}();
//# sourceMappingURL=Mailer.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
(function() {
'use strict';
angular.module('SOGo.MailerUI', ['ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'ngAnimate', 'SOGo.PreferencesUI'])
angular.module('SOGo.MailerUI', ['ui.router', 'ck', 'angularFileUpload', 'SOGo.Common', 'SOGo.ContactsUI', 'SOGo.SchedulerUI', 'ngAnimate', 'SOGo.PreferencesUI'])
.config(configure)
.run(runBlock);

View File

@ -362,6 +362,15 @@
});
};
/**
* @function $plainContent
* @memberof Message.prototype
* @returns the a plain text representation of the subject and body
*/
Message.prototype.$plainContent = function() {
return Message.$$resource.fetch(this.$absolutePath(), 'viewplain');
};
/**
* @function addTag
* @memberof Message.prototype
@ -576,27 +585,24 @@
*/
Message.prototype.$send = function() {
var _this = this,
data = angular.copy(this.editable),
deferred = Message.$q.defer();
data = angular.copy(this.editable);
Message.$log.debug('send = ' + JSON.stringify(data, undefined, 2));
Message.$$resource.post(this.$absolutePath({asDraft: true}), 'send', data).then(function(data) {
return Message.$$resource.post(this.$absolutePath({asDraft: true}), 'send', data).then(function(data) {
if (data.status == 'success') {
deferred.resolve(data);
if (angular.isDefined(_this.origin)) {
if (_this.origin.action.startsWith('reply'))
_this.origin.message.isanswered = true;
else if (_this.origin.action == 'forward')
_this.origin.message.isforwarded = true;
}
return data;
}
else {
deferred.reject(data);
return Message.$q.reject(data);
}
});
return deferred.promise;
};
/**

View File

@ -6,8 +6,8 @@
/**
* @ngInject
*/
MessageController.$inject = ['$window', '$scope', '$state', '$mdDialog', 'stateAccounts', 'stateAccount', 'stateMailbox', 'stateMessage', 'encodeUriFilter', 'sgSettings', 'sgFocus', 'Dialog', 'Account', 'Mailbox', 'Message'];
function MessageController($window, $scope, $state, $mdDialog, stateAccounts, stateAccount, stateMailbox, stateMessage, encodeUriFilter, sgSettings, focus, Dialog, Account, Mailbox, Message) {
MessageController.$inject = ['$window', '$scope', '$state', '$mdDialog', 'stateAccounts', 'stateAccount', 'stateMailbox', 'stateMessage', 'encodeUriFilter', 'sgSettings', 'sgFocus', 'Dialog', 'Calendar', 'Component', 'Account', 'Mailbox', 'Message'];
function MessageController($window, $scope, $state, $mdDialog, stateAccounts, stateAccount, stateMailbox, stateMessage, encodeUriFilter, sgSettings, focus, Dialog, Calendar, Component, Account, Mailbox, Message) {
var vm = this, messageDialog = null, popupWindow = null;
// Expose controller
@ -35,6 +35,8 @@
vm.saveMessage = saveMessage;
vm.toggleRawSource = toggleRawSource;
vm.showRawSource = false;
vm.convertToEvent = convertToEvent;
vm.convertToTask = convertToTask;
// One-way refresh of the parent window when modifying the message from a popup window.
if ($window.opener) {
@ -254,6 +256,45 @@
vm.showRawSource = !vm.showRawSource;
}
}
function convertToEvent($event) {
return convertToComponent($event, 'appointment');
}
function convertToTask($event) {
return convertToComponent($event, 'task');
}
function convertToComponent($event, type) {
vm.message.$plainContent().then(function(data) {
var componentData = {
pid: Calendar.$defaultCalendar(),
type: type,
summary: data.subject,
comment: data.content
};
var component = new Component(componentData);
// UI/Templates/SchedulerUI/UIxAppointmentEditorTemplate.wox or
// UI/Templates/SchedulerUI/UIxTaskEditorTemplate.wox
var templateUrl = [
sgSettings.activeUser('folderURL'),
'Calendar',
'UIx' + type.capitalize() + 'EditorTemplate'
].join('/');
return $mdDialog.show({
parent: angular.element(document.body),
targetEvent: $event,
clickOutsideToClose: true,
escapeToClose: true,
templateUrl: templateUrl,
controller: 'ComponentEditorController',
controllerAs: 'editor',
locals: {
stateComponent: component
}
});
});
}
}
angular

View File

@ -1,2 +1,2 @@
!function(){"use strict";function a(){var b=this;this.defaults={},this.settings={},this.defaultsPromise=a.$$resource.fetch("jsonDefaults").then(function(c){var d=_.object(_.map(c.SOGoMailLabelsColors,function(a,b){return"$"==b.charAt(0)?["_"+b,a]:[b,a]}));return c.SOGoMailLabelsColors=d,c.Vacation?(c.Vacation.endDate?c.Vacation.endDate=new Date(1e3*parseInt(c.Vacation.endDate)):(c.Vacation.endDateEnabled=0,c.Vacation.endDate=new Date),c.Vacation.autoReplyEmailAddresses&&c.Vacation.autoReplyEmailAddresses.length?c.Vacation.autoReplyEmailAddresses=c.Vacation.autoReplyEmailAddresses.join(","):delete c.Vacation.autoReplyEmailAddresses):c.Vacation={},angular.isUndefined(c.Vacation.autoReplyEmailAddresses)&&angular.isDefined(window.defaultEmailAddresses)&&(c.Vacation.autoReplyEmailAddresses=window.defaultEmailAddresses),angular.isUndefined(c.Vacation.daysBetweenResponse)&&(c.Vacation.daysBetweenResponse=7),angular.isUndefined(c.Vacation.endDate)&&(c.Vacation.endDateEnabled=0,c.Vacation.endDate=new Date),c.Forward&&c.Forward.forwardAddress&&(c.Forward.forwardAddress=c.Forward.forwardAddress.join(",")),angular.isUndefined(c.SOGoCalendarCategoriesColors)&&(c.SOGoCalendarCategoriesColors={},c.SOGoCalendarCategories=[]),angular.isUndefined(c.SOGoContactsCategories)&&(c.SOGoContactsCategories=[]),angular.extend(b.defaults,c),angular.extend(a.$mdDateLocaleProvider,c.locale),a.$mdDateLocaleProvider.firstDayOfWeek=parseInt(c.SOGoFirstDayOfWeek),a.$mdDateLocaleProvider.weekNumberFormatter=function(a){return l("Week %d",a)},a.$mdDateLocaleProvider.msgCalendar=l("Calender"),a.$mdDateLocaleProvider.msgOpenCalendar=l("Open Calendar"),a.$mdDateLocaleProvider.parseDate=function(b){return b?b.parseDate(a.$mdDateLocaleProvider,c.SOGoShortDateFormat):new Date(NaN)},a.$mdDateLocaleProvider.formatDate=function(b){return b?b.format(a.$mdDateLocaleProvider,c.SOGoShortDateFormat):""},a.$mdDateLocaleProvider.formatTime=function(b){return b?b.format(a.$mdDateLocaleProvider,c.SOGoTimeFormat):""},b.defaults}),this.settingsPromise=a.$$resource.fetch("jsonSettings").then(function(c){return c.Calendar&&(c.Calendar.PreventInvitationsWhitelist?c.Calendar.PreventInvitationsWhitelist=_.map(c.Calendar.PreventInvitationsWhitelist,function(b,c){var d=/^(.+)\s<(\S+)>$/.exec(b);return new a.$User({uid:c,cn:d[1],c_email:d[2]})}):c.Calendar.PreventInvitationsWhitelist=[]),angular.extend(b.settings,c),b.settings})}a.$factory=["$q","$timeout","$log","$mdDateLocale","sgSettings","Resource","User",function(b,c,d,e,f,g,h){return angular.extend(a,{$q:b,$timeout:c,$log:d,$mdDateLocaleProvider:e,$$resource:new g(f.activeUser("folderURL"),f.activeUser()),activeUser:f.activeUser(),$User:h}),new a}];try{angular.module("SOGo.PreferencesUI")}catch(b){angular.module("SOGo.PreferencesUI",["SOGo.Common"])}angular.module("SOGo.PreferencesUI").factory("Preferences",a.$factory),a.prototype.ready=function(){return a.$q.all([this.defaultsPromise,this.settingsPromise])},a.prototype.$save=function(){return a.$$resource.save("Preferences",this.$omit(!0)).then(function(a){return a})},a.prototype.$omit=function(a){var b,c,d;return b={},d={},angular.forEach(this,function(c,d){"constructor"!=d&&"$"!=d[0]&&(a?b[d]=angular.copy(c):b[d]=c)}),c=_.object(_.map(b.defaults.SOGoMailLabelsColors,function(a,b){return"_"==b.charAt(0)&&"$"==b.charAt(1)?b.length>2&&"$"==b.charAt(2)?[a[0].toLowerCase().replace(/[ \(\)\/\{%\*<>\\\"]/g,"_"),a]:[b.substring(1),a]:[b,a]})),b.defaults.SOGoMailLabelsColors=c,b.defaults.Vacation&&(b.defaults.Vacation.endDateEnabled?b.defaults.Vacation.endDate=b.defaults.Vacation.endDate.getTime()/1e3:b.defaults.Vacation.endDate=0,b.defaults.Vacation.autoReplyEmailAddresses?b.defaults.Vacation.autoReplyEmailAddresses=_.filter(b.defaults.Vacation.autoReplyEmailAddresses.split(","),function(a){return a.length}):b.defaults.Vacation.autoReplyEmailAddresses=[]),b.defaults.Forward&&b.defaults.Forward.forwardAddress&&(b.defaults.Forward.forwardAddress=b.defaults.Forward.forwardAddress.split(",")),b.settings.Calendar&&b.settings.Calendar.PreventInvitationsWhitelist&&(_.each(b.settings.Calendar.PreventInvitationsWhitelist,function(a){d[a.uid]=a.$shortFormat()}),b.settings.Calendar.PreventInvitationsWhitelist=d),b}}();
!function(){"use strict";function a(){var b=this;this.defaults={},this.settings={},this.defaultsPromise=a.$$resource.fetch("jsonDefaults").then(function(c){var d=_.object(_.map(c.SOGoMailLabelsColors,function(a,b){return"$"==b.charAt(0)?["_"+b,a]:[b,a]}));return c.SOGoMailLabelsColors=d,c.SOGoMailAutoSave=parseInt(c.SOGoMailAutoSave)||0,c.SOGoMailComposeFontSizeEnabled=parseInt(c.SOGoMailComposeFontSize)>0,window.CKEDITOR&&c.SOGoMailComposeFontSizeEnabled&&(window.CKEDITOR.config.fontSize_defaultLabel=c.SOGoMailComposeFontSize,window.CKEDITOR.addCss(".cke_editable { font-size: "+c.SOGoMailComposeFontSize+"px; }")),c.Vacation?(c.Vacation.endDate?c.Vacation.endDate=new Date(1e3*parseInt(c.Vacation.endDate)):(c.Vacation.endDateEnabled=0,c.Vacation.endDate=new Date),c.Vacation.autoReplyEmailAddresses&&c.Vacation.autoReplyEmailAddresses.length?c.Vacation.autoReplyEmailAddresses=c.Vacation.autoReplyEmailAddresses.join(","):delete c.Vacation.autoReplyEmailAddresses):c.Vacation={},angular.isUndefined(c.Vacation.autoReplyEmailAddresses)&&angular.isDefined(window.defaultEmailAddresses)&&(c.Vacation.autoReplyEmailAddresses=window.defaultEmailAddresses),angular.isUndefined(c.Vacation.daysBetweenResponse)&&(c.Vacation.daysBetweenResponse=7),angular.isUndefined(c.Vacation.endDate)&&(c.Vacation.endDateEnabled=0,c.Vacation.endDate=new Date),c.Forward&&c.Forward.forwardAddress&&(c.Forward.forwardAddress=c.Forward.forwardAddress.join(",")),angular.isUndefined(c.SOGoCalendarCategoriesColors)&&(c.SOGoCalendarCategoriesColors={},c.SOGoCalendarCategories=[]),angular.isUndefined(c.SOGoContactsCategories)&&(c.SOGoContactsCategories=[]),angular.extend(b.defaults,c),b.$mdDateLocaleProvider=a.$mdDateLocaleProvider,angular.extend(b.$mdDateLocaleProvider,c.locale),b.$mdDateLocaleProvider.firstDayOfWeek=parseInt(c.SOGoFirstDayOfWeek),b.$mdDateLocaleProvider.weekNumberFormatter=function(a){return l("Week %d",a)},b.$mdDateLocaleProvider.msgCalendar=l("Calender"),b.$mdDateLocaleProvider.msgOpenCalendar=l("Open Calendar"),b.$mdDateLocaleProvider.parseDate=function(a){return a?a.parseDate(b.$mdDateLocaleProvider,c.SOGoShortDateFormat):new Date(NaN)},b.$mdDateLocaleProvider.formatDate=function(a){return a?a.format(b.$mdDateLocaleProvider,c.SOGoShortDateFormat):""},b.$mdDateLocaleProvider.formatTime=function(a){return a?a.format(b.$mdDateLocaleProvider,c.SOGoTimeFormat):""},b.defaults}),this.settingsPromise=a.$$resource.fetch("jsonSettings").then(function(c){return c.Calendar&&(c.Calendar.PreventInvitationsWhitelist?c.Calendar.PreventInvitationsWhitelist=_.map(c.Calendar.PreventInvitationsWhitelist,function(b,c){var d=/^(.+)\s<(\S+)>$/.exec(b);return new a.$User({uid:c,cn:d[1],c_email:d[2]})}):c.Calendar.PreventInvitationsWhitelist=[]),angular.extend(b.settings,c),b.settings})}a.$factory=["$q","$timeout","$log","$mdDateLocale","sgSettings","Resource","User",function(b,c,d,e,f,g,h){return angular.extend(a,{$q:b,$timeout:c,$log:d,$mdDateLocaleProvider:e,$$resource:new g(f.activeUser("folderURL"),f.activeUser()),activeUser:f.activeUser(),$User:h}),new a}];try{angular.module("SOGo.PreferencesUI")}catch(b){angular.module("SOGo.PreferencesUI",["SOGo.Common"])}angular.module("SOGo.PreferencesUI").factory("Preferences",a.$factory),a.prototype.ready=function(){return a.$q.all([this.defaultsPromise,this.settingsPromise])},a.prototype.$save=function(){return a.$$resource.save("Preferences",this.$omit(!0)).then(function(a){return a})},a.prototype.$omit=function(a){var b,c,d;return b={},d={},angular.forEach(this,function(c,d){"constructor"!=d&&"$"!=d[0]&&(a?b[d]=angular.copy(c):b[d]=c)}),c=_.object(_.map(b.defaults.SOGoMailLabelsColors,function(a,b){return"_"==b.charAt(0)&&"$"==b.charAt(1)?b.length>2&&"$"==b.charAt(2)?[a[0].toLowerCase().replace(/[ \(\)\/\{%\*<>\\\"]/g,"_"),a]:[b.substring(1),a]:[b,a]})),b.defaults.SOGoMailLabelsColors=c,b.defaults.SOGoMailComposeFontSizeEnabled||(b.defaults.SOGoMailComposeFontSize=0),delete b.defaults.SOGoMailComposeFontSizeEnabled,b.defaults.Vacation&&(b.defaults.Vacation.endDateEnabled?b.defaults.Vacation.endDate=b.defaults.Vacation.endDate.getTime()/1e3:b.defaults.Vacation.endDate=0,b.defaults.Vacation.autoReplyEmailAddresses?b.defaults.Vacation.autoReplyEmailAddresses=_.filter(b.defaults.Vacation.autoReplyEmailAddresses.split(","),function(a){return a.length}):b.defaults.Vacation.autoReplyEmailAddresses=[]),b.defaults.Forward&&b.defaults.Forward.forwardAddress&&(b.defaults.Forward.forwardAddress=b.defaults.Forward.forwardAddress.split(",")),b.settings.Calendar&&b.settings.Calendar.PreventInvitationsWhitelist&&(_.each(b.settings.Calendar.PreventInvitationsWhitelist,function(a){d[a.uid]=a.$shortFormat()}),b.settings.Calendar.PreventInvitationsWhitelist=d),b}}();
//# sourceMappingURL=Preferences.services.js.map

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,18 @@
data.SOGoMailLabelsColors = labels;
// Mail editor autosave is a number of minutes or 0 if disabled
data.SOGoMailAutoSave = parseInt(data.SOGoMailAutoSave) || 0;
// Specify a base font size for HTML messages when SOGoMailComposeFontSize is not zero
data.SOGoMailComposeFontSizeEnabled = parseInt(data.SOGoMailComposeFontSize) > 0;
if (window.CKEDITOR && data.SOGoMailComposeFontSizeEnabled) {
// HTML editor is enabled; set user's preferred font size
window.CKEDITOR.config.fontSize_defaultLabel = data.SOGoMailComposeFontSize;
window.CKEDITOR.addCss('.cke_editable { font-size: ' + data.SOGoMailComposeFontSize + 'px; }');
}
// We convert our list of autoReplyEmailAddresses/forwardAddress into a string.
// We also convert our date objects into real date, otherwise we'll have strings
// or undefined values and the md-datepicker does NOT like this.
@ -66,21 +78,22 @@
angular.extend(_this.defaults, data);
// Configure date locale
angular.extend(Preferences.$mdDateLocaleProvider, data.locale);
Preferences.$mdDateLocaleProvider.firstDayOfWeek = parseInt(data.SOGoFirstDayOfWeek);
Preferences.$mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
_this.$mdDateLocaleProvider = Preferences.$mdDateLocaleProvider;
angular.extend(_this.$mdDateLocaleProvider, data.locale);
_this.$mdDateLocaleProvider.firstDayOfWeek = parseInt(data.SOGoFirstDayOfWeek);
_this.$mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
return l('Week %d', weekNumber);
};
Preferences.$mdDateLocaleProvider.msgCalendar = l('Calender');
Preferences.$mdDateLocaleProvider.msgOpenCalendar = l('Open Calendar');
Preferences.$mdDateLocaleProvider.parseDate = function(dateString) {
return dateString? dateString.parseDate(Preferences.$mdDateLocaleProvider, data.SOGoShortDateFormat) : new Date(NaN);
_this.$mdDateLocaleProvider.msgCalendar = l('Calender');
_this.$mdDateLocaleProvider.msgOpenCalendar = l('Open Calendar');
_this.$mdDateLocaleProvider.parseDate = function(dateString) {
return dateString? dateString.parseDate(_this.$mdDateLocaleProvider, data.SOGoShortDateFormat) : new Date(NaN);
};
Preferences.$mdDateLocaleProvider.formatDate = function(date) {
return date? date.format(Preferences.$mdDateLocaleProvider, data.SOGoShortDateFormat) : '';
_this.$mdDateLocaleProvider.formatDate = function(date) {
return date? date.format(_this.$mdDateLocaleProvider, data.SOGoShortDateFormat) : '';
};
Preferences.$mdDateLocaleProvider.formatTime = function(date) {
return date? date.format(Preferences.$mdDateLocaleProvider, data.SOGoTimeFormat) : '';
_this.$mdDateLocaleProvider.formatTime = function(date) {
return date? date.format(_this.$mdDateLocaleProvider, data.SOGoTimeFormat) : '';
};
return _this.defaults;
@ -197,6 +210,10 @@
preferences.defaults.SOGoMailLabelsColors = labels;
if (!preferences.defaults.SOGoMailComposeFontSizeEnabled)
preferences.defaults.SOGoMailComposeFontSize = 0;
delete preferences.defaults.SOGoMailComposeFontSizeEnabled;
if (preferences.defaults.Vacation) {
if (preferences.defaults.Vacation.endDateEnabled)
preferences.defaults.Vacation.endDate = preferences.defaults.Vacation.endDate.getTime()/1000;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -124,6 +124,14 @@
_this.$calendars.push(calendar);
});
}
else if (angular.isUndefined(this.$calendars)) {
this.$calendars = [];
this.$subscriptions = [];
this.$webcalendars = [];
Calendar.$$resource.fetch('calendarslist').then(function(data) {
Calendar.$findAll(data.calendars, writable);
});
}
if (writable) {
return _.union(this.$calendars, _.filter(this.$subscriptions, function(calendar) { return calendar.acls.objectCreator; }));
@ -235,25 +243,19 @@
* @return a promise of the HTTP operation
*/
Calendar.$deleteComponents = function(components) {
// We create a c_folder -> event hash
var calendars = {}, _this = this;
var _this = this, calendars = {}, promises = [];
_.forEach(components, function(component) {
if (!angular.isDefined(calendars[component.c_folder]))
calendars[component.c_folder] = [];
calendars[component.c_folder].push(component.c_name);
if (!angular.isDefined(calendars[component.pid]))
calendars[component.pid] = [];
calendars[component.pid].push(component.id);
});
_.forEach(calendars, function(uids, c_folder) {
Calendar.$$resource.post(c_folder, 'batchDelete', {uids: uids});
_.forEach(calendars, function(uids, pid) {
promises.push(Calendar.$$resource.post(pid, 'batchDelete', {uids: uids}));
});
// We slice both arrays - might be useful if in the future, we can delete
// events and tasks at the same time.
_this.$Component.$events = _.difference(_this.$Component.$events, components);
_this.$Component.$tasks = _.difference(_this.$Component.$tasks, components);
return Calendar.$q.all(promises);
};
/**

View File

@ -80,8 +80,12 @@
{ ok: l('Delete') })
.then(function() {
// User confirmed the deletion
var components = _.filter(Component['$' + vm.componentType], function(component) { return component.selected; });
Calendar.$deleteComponents(components);
var components = _.filter(Component['$' + vm.componentType], function(component) {
return component.selected;
});
Calendar.$deleteComponents(components).then(function() {
$rootScope.$emit('calendars:list');
});
});
}

View File

@ -21,6 +21,8 @@
vm.share = share;
vm.importCalendar = importCalendar;
vm.exportCalendar = exportCalendar;
vm.showOnly = showOnly;
vm.showAll = showAll;
vm.showLinks = showLinks;
vm.showProperties = showProperties;
vm.subscribeToFolder = subscribeToFolder;
@ -202,6 +204,19 @@
window.location.href = ApplicationBaseURL + '/' + calendar.id + '.ics' + '/export';
}
function showOnly(calendar) {
_.forEach(Calendar.$findAll(), function(o) {
if (calendar.id == o.id)
o.active = 1;
else
o.active = 0;
});
}
function showAll() {
_.forEach(Calendar.$findAll(), function(o) { o.active = 1; });
}
function showLinks(calendar) {
$mdDialog.show({
parent: angular.element(document.body),

View File

@ -39,7 +39,7 @@
$Preferences: Preferences,
$Card: Card,
$gravatar: Gravatar,
$$resource: new Resource(Settings.baseURL(), Settings.activeUser()),
$$resource: new Resource(Settings.activeUser('folderURL') + 'Calendar', Settings.activeUser()),
timeFormat: "%H:%M",
// Filter parameters common to events and tasks
$query: { value: '', search: 'title_Category_Location' },
@ -236,35 +236,40 @@
* @returns a promise of a collection of objects describing the events blocks
*/
Component.$eventsBlocksForView = function(view, date) {
var viewAction, startDate, endDate, params;
var _this = this;
if (view == 'day') {
viewAction = 'dayView';
startDate = endDate = date;
}
else if (view == 'multicolumnday') {
viewAction = 'multicolumndayView';
startDate = endDate = date;
}
else if (view == 'week') {
viewAction = 'weekView';
startDate = date.beginOfWeek();
endDate = new Date();
endDate.setTime(startDate.getTime());
endDate.addDays(6);
}
else if (view == 'month') {
viewAction = 'monthView';
startDate = date;
startDate.setDate(1);
startDate = startDate.beginOfWeek();
endDate = new Date();
endDate.setTime(startDate.getTime());
endDate.setMonth(endDate.getMonth() + 1);
endDate.addDays(-1);
endDate = endDate.endOfWeek();
}
return this.$eventsBlocks(viewAction, startDate, endDate);
return Component.$Preferences.ready().then(function(data) {
var firstDayOfWeek, viewAction, startDate, endDate, params;
firstDayOfWeek = Component.$Preferences.defaults.SOGoFirstDayOfWeek;
if (view == 'day') {
viewAction = 'dayView';
startDate = endDate = date;
}
else if (view == 'multicolumnday') {
viewAction = 'multicolumndayView';
startDate = endDate = date;
}
else if (view == 'week') {
viewAction = 'weekView';
startDate = date.beginOfWeek(firstDayOfWeek);
endDate = new Date();
endDate.setTime(startDate.getTime());
endDate.addDays(6);
}
else if (view == 'month') {
viewAction = 'monthView';
startDate = date;
startDate.setDate(1);
startDate = startDate.beginOfWeek(firstDayOfWeek);
endDate = new Date();
endDate.setTime(startDate.getTime());
endDate.setMonth(endDate.getMonth() + 1);
endDate.addDays(-1);
endDate = endDate.endOfWeek(firstDayOfWeek);
}
return _this.$eventsBlocks(viewAction, startDate, endDate);
});
};
/**

View File

@ -23,17 +23,6 @@ CKEDITOR.editorConfig = function( config ) {
config.tabSpaces = 4;
config.allowedContent = true; // don't filter tags
// The list of fonts size to be displayed in the Font Size combo in the toolbar.
config.fontSize_sizes = '8/8px;9/9px;10/10px;11/11px;12/12px;13/13px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px';
// Explicitly show the default site font size to the end user (as defined in contents.css)
config.fontSize_defaultLabel = '13px';
// The CSS file(s) to be used to apply style to editor content.
// For example, the following ck.css could overwrite the font-size of .cke_editable
//config.contentsCss = ['/SOGo.woa/WebServerResources/js/vendor/ckeditor/contents.css', // default CSS
// '/css/ck.css']; // custom CSS
// Disables the built-in words spell checker if browser provides one. Defaults to true.
// http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-disableNativeSpellChecker
//config.disableNativeSpellChecker = false;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long