diff --git a/ActiveSync/NGVCard+ActiveSync.m b/ActiveSync/NGVCard+ActiveSync.m index 0cf624e2c..7e418b6b3 100644 --- a/ActiveSync/NGVCard+ActiveSync.m +++ b/ActiveSync/NGVCard+ActiveSync.m @@ -40,6 +40,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "NSDate+ActiveSync.h" #include "NSString+ActiveSync.h" +#import + +#import +#import + @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: @"%@", [o activeSyncRepresentationInContext: context]]; + { + NSTimeZone *userTimeZone; + userTimeZone = [[[context activeUser] userDefaults] timeZone]; + + [s appendFormat: @"%@", + [[o dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: ([userTimeZone secondsFromGMTForDate: o])*-1] + activeSyncRepresentationInContext: context]]; + } if ((o = [self note])) { diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 8761b4fdd..858532f46 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -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: @""]; [s appendString: @"1"]; - [s appendFormat: @"%@", fileReference]; + [s appendFormat: @"mail/%@/%@/%@", [folderName stringByEscapingURL], messageName, pathToPart]; [s appendString: @""]; [s appendFormat: @"%@/%@", [[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; } diff --git a/NEWS b/NEWS index 591d70832..01d8b630b 100644 --- a/NEWS +++ b/NEWS @@ -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) ------------------ diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 8aa840d99..d3d80b869 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2014 Inverse inc. + Copyright (C) 2007-2016 Inverse inc. This file is part of SOGo diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 0e77633e8..0b45df89b 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -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 #import #import +#import #import #import #import @@ -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; } // diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index e1e2bc7bd..1595a1c75 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -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 diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 4261ba60b..32a2cb390 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -675,6 +675,12 @@ return [self saveCalendar: [newObject parent]]; } +- (NSException *) saveComponent: (iCalRepeatableEntityObject *) newEvent + force: (BOOL) forceSave +{ + return [self saveComponent: newEvent]; +} + /* raw saving */ /* EMail Notifications */ diff --git a/SoObjects/Appointments/SOGoTaskObject.h b/SoObjects/Appointments/SOGoTaskObject.h index 5d15c856a..15512a21b 100644 --- a/SoObjects/Appointments/SOGoTaskObject.h +++ b/SoObjects/Appointments/SOGoTaskObject.h @@ -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 diff --git a/SoObjects/Appointments/SOGoTaskObject.m b/SoObjects/Appointments/SOGoTaskObject.m index 3d8a99bb2..c3efc4e5c 100644 --- a/SoObjects/Appointments/SOGoTaskObject.m +++ b/SoObjects/Appointments/SOGoTaskObject.m @@ -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. diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 90ebf6eef..8fdbc5f71 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -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 diff --git a/SoObjects/SOGo/SOGoDateFormatter.h b/SoObjects/SOGo/SOGoDateFormatter.h index 3889bff51..6eeec8c97 100644 --- a/SoObjects/SOGo/SOGoDateFormatter.h +++ b/SoObjects/SOGo/SOGoDateFormatter.h @@ -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_ */ diff --git a/SoObjects/SOGo/SOGoDateFormatter.m b/SoObjects/SOGo/SOGoDateFormatter.m index a31af79ee..b16420acd 100644 --- a/SoObjects/SOGo/SOGoDateFormatter.m +++ b/SoObjects/SOGo/SOGoDateFormatter.m @@ -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 #import -#import /* for NSXXXFormatString, ... */ +#import #import "SOGoDateFormatter.h" diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index de3c5480d..fbb962ddb 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -72,6 +72,7 @@ SOGoTrashFolderName = "Trash"; SOGoJunkFolderName = "Junk"; SOGoMailComposeMessageType = "html"; + SOGoMailComposeFontSize = 0; SOGoMailDisplayRemoteInlineImages = "never"; SOGoMailAutoSave = "5"; diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index 69ebb8d15..078bc4ca4 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -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; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 50208d41c..c7a0ff9f0 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -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"]; diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index 56f3070c7..b98565fb2 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -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"; diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index 74f32775b..7263b4ce6 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -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 #import +#import #import #import #import #import +#import +#import #import #import #import @@ -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 diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 7d42e4d99..dcce99e27 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -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: @"%@", 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: @"%@", + fontSize, text]; + else + content = [NSString stringWithFormat: @"%@", text]; + } + else + { + content = text; + } + [co setText: content]; error = [co storeInfo]; } diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index 6b2b02130..ee9a004be 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -278,6 +278,11 @@ actionClass = "UIxMailActions"; actionName = "forward"; }; + viewplain = { + protectedBy = "View"; + actionClass = "UIxMailActions"; + actionName = "viewPlain"; + }; markMessageUncollapse = { protectedBy = "View"; actionClass = "UIxMailActions"; diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index 69d577e20..c6a80de0b 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -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"; diff --git a/UI/PreferencesUI/UIxJSONPreferences.m b/UI/PreferencesUI/UIxJSONPreferences.m index 74519cacb..cf8291dca 100644 --- a/UI/PreferencesUI/UIxJSONPreferences.m +++ b/UI/PreferencesUI/UIxJSONPreferences.m @@ -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"]; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 7155c4b3d..fc7d36cdc 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -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 // diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 12d24630e..e26b95849 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -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 "; diff --git a/UI/Scheduler/UIxAppointmentActions.h b/UI/Scheduler/UIxAppointmentActions.h index c7554918e..a50346d9c 100644 --- a/UI/Scheduler/UIxAppointmentActions.h +++ b/UI/Scheduler/UIxAppointmentActions.h @@ -1,8 +1,6 @@ /* UIxAppointmentActions.h - this file is part of SOGo * - * Copyright (C) 2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * 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 diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index 0ac9c9c11..231edab81 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -1,8 +1,6 @@ /* UIxAppointmentActions.m - this file is part of SOGo * - * Copyright (C) 2011-2014 Inverse inc. - * - * Author: Wolfgang Sourdeau + * 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 diff --git a/UI/Scheduler/UIxAppointmentEditor.h b/UI/Scheduler/UIxAppointmentEditor.h index 5fabc80dd..52df2377a 100644 --- a/UI/Scheduler/UIxAppointmentEditor.h +++ b/UI/Scheduler/UIxAppointmentEditor.h @@ -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 diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index ce0125e80..438dc5f23 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -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 diff --git a/UI/Templates/ContactsUI/UIxContactViewTemplate.wox b/UI/Templates/ContactsUI/UIxContactViewTemplate.wox index 75cc90919..939422cb8 100644 --- a/UI/Templates/ContactsUI/UIxContactViewTemplate.wox +++ b/UI/Templates/ContactsUI/UIxContactViewTemplate.wox @@ -101,7 +101,8 @@ {{ ref.$fullname() }}

- {{ ref.email }} +

diff --git a/UI/Templates/MailerUI/UIxMailMainFrame.wox b/UI/Templates/MailerUI/UIxMailMainFrame.wox index 3d61ae5d0..ef8ecac4a 100644 --- a/UI/Templates/MailerUI/UIxMailMainFrame.wox +++ b/UI/Templates/MailerUI/UIxMailMainFrame.wox @@ -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">