diff --git a/ActiveSync/ActiveSyncProduct.m b/ActiveSync/ActiveSyncProduct.m new file mode 100644 index 000000000..6dd3c4ca4 --- /dev/null +++ b/ActiveSync/ActiveSyncProduct.m @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#import + +@interface ActiveSyncProduct : NSObject +{ +} + +@end + +@implementation ActiveSyncProduct +@end /* ActiveSyncProduct */ diff --git a/ActiveSync/GNUmakefile b/ActiveSync/GNUmakefile new file mode 100644 index 000000000..2ea1c36fe --- /dev/null +++ b/ActiveSync/GNUmakefile @@ -0,0 +1,39 @@ + +# GNUstep makefile + +include common.make + +BUNDLE_NAME = ActiveSync + +ActiveSync_PRINCIPAL_CLASS = ActiveSyncProduct + +ActiveSync_OBJC_FILES = \ + ActiveSyncProduct.m \ + iCalEvent+ActiveSync.m \ + iCalRecurrenceRule+ActiveSync.m \ + iCalTimeZone+ActiveSync.m \ + iCalToDo+ActiveSync.m \ + NSCalendarDate+ActiveSync.m \ + NSData+ActiveSync.m \ + NSDate+ActiveSync.m \ + NGDOMElement+ActiveSync.m \ + NGMimeMessage+ActiveSync.m \ + NGVCard+ActiveSync.m \ + NSString+ActiveSync.m \ + SOGoActiveSyncDispatcher.m \ + SOGoActiveSyncDispatcher+Sync.m \ + SOGoMailObject+ActiveSync.m \ + SoObjectWebDAVDispatcher+ActiveSync.m + +ActiveSync_RESOURCE_FILES += \ + product.plist + +ADDITIONAL_OBJCFLAGS += -Wno-deprecated-declarations +ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/ -I../SoObjects/ +ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/ +ADDITIONAL_INCLUDE_DIRS += -I/usr/include/libwbxml-1.0/ +ADDITIONAL_LDFLAGS += -Wl,--no-as-needed -lwbxml2 + +-include GNUmakefile.preamble +include $(GNUSTEP_MAKEFILES)/bundle.make +-include GNUmakefile.postamble diff --git a/ActiveSync/GNUmakefile.preamble b/ActiveSync/GNUmakefile.preamble new file mode 100644 index 000000000..c302ad8be --- /dev/null +++ b/ActiveSync/GNUmakefile.preamble @@ -0,0 +1 @@ +# compilation settings diff --git a/ActiveSync/LICENSE b/ActiveSync/LICENSE new file mode 100644 index 000000000..86c70cafe --- /dev/null +++ b/ActiveSync/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ActiveSync/NGDOMElement+ActiveSync.h b/ActiveSync/NGDOMElement+ActiveSync.h new file mode 100644 index 000000000..037b0bcc6 --- /dev/null +++ b/ActiveSync/NGDOMElement+ActiveSync.h @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NGDOMELEMENTACTIVESYNC_H__ +#define __NGDOMELEMENTACTIVESYNC_H__ + +#import +#import + +@class NSDictionary; + +@interface NGDOMElement (ActiveSync) + +- (NSDictionary *) applicationData; + +@end + +#endif // NGDOMELEMENTACTIVESYNC diff --git a/ActiveSync/NGDOMElement+ActiveSync.m b/ActiveSync/NGDOMElement+ActiveSync.m new file mode 100644 index 000000000..527486605 --- /dev/null +++ b/ActiveSync/NGDOMElement+ActiveSync.m @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "NGDOMElement+ActiveSync.h" + +#import +#import +#import + +static NSArray *asElementArray = nil; + +@implementation NGDOMElement (ActiveSync) + +// +// We must handle "inner data" like this: +// +// +// +// 2 +// Flag for follow up +// +// +// +// and stuff like that: +// +// +// +// sogo1@example.com +// John Doe +// 5 +// 1 +// +// +// sogo2@example.com +// Balthazar César +// 5 +// 1 +// +// +// sogo3@example.com +// Wolfgang Fritz +// 5 +// 1 +// +// +// + +- (NSDictionary *) applicationData +{ + NSMutableDictionary *data; + id children; + id element; + int i, count; + + if (!asElementArray) + asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", nil]; + + data = [NSMutableDictionary dictionary]; + + children = [self childNodes]; + + for (i = 0; i < [children length]; i++) + { + element = [children objectAtIndex: i]; + + if ([element nodeType] == DOM_ELEMENT_NODE) + { + NSString *tag; + id value; + + tag = [element tagName]; + count = [(NSArray *)[element childNodes] count]; + + // Handle inner data - see above for samples + if (count > 2) + { + NSMutableArray *innerElements; + id innerElement; + NSArray *childNodes; + NSString *innerTag; + BOOL same; + int j; + + childNodes = (NSArray *)[element childNodes]; + innerElements = [NSMutableArray array]; + innerTag = nil; + same = YES; + + for (j = 1; j < count; j++) + { + innerElement = [childNodes objectAtIndex: j]; + + if ([innerElement nodeType] == DOM_ELEMENT_NODE) + { + if (!innerTag) + innerTag = [innerElement tagName]; + + if ([innerTag isEqualToString: [innerElement tagName]]) + { + [innerElements addObject: [(NGDOMElement *)innerElement applicationData]]; + } + else + { + same = NO; + break; + } + } + } + + if (same && [asElementArray containsObject: innerTag]) + value = innerElements; + else + { + value = [(NGDOMElement *)element applicationData]; + + // Don't set empty values like Foo = {} + if (![value count]) + value = nil; + } + } + else + value = [[element firstChild] textValue]; + + if (value && tag) + [data setObject: value forKey: tag]; + } + } + + return data; +} + +@end diff --git a/ActiveSync/NGMimeMessage+ActiveSync.h b/ActiveSync/NGMimeMessage+ActiveSync.h new file mode 100644 index 000000000..4a319da0f --- /dev/null +++ b/ActiveSync/NGMimeMessage+ActiveSync.h @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NGMIMEMESSAGEACTIVESYNC_H__ +#define __NGMIMEMESSAGEACTIVESYNC_H__ + +#import + +@class NSArray; + +@interface NGMimeMessage (ActiveSync) + +- (NSArray *) allRecipients; + +@end + +#endif diff --git a/ActiveSync/NGMimeMessage+ActiveSync.m b/ActiveSync/NGMimeMessage+ActiveSync.m new file mode 100644 index 000000000..45643bee6 --- /dev/null +++ b/ActiveSync/NGMimeMessage+ActiveSync.m @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NGMimeMessage+ActiveSync.h" + +#import +#import +#import + +#import + +@implementation NGMimeMessage (ActiveSync) + +- (NSArray *) allRecipients +{ + NSMutableArray *recipients; + NSEnumerator *enumerator; + NSString *s; + + recipients = [NSMutableArray array]; + + enumerator = [[self headersForKey: @"to"] objectEnumerator]; + while ((s = [enumerator nextObject])) + { + [recipients addObject: [s pureEMailAddress]]; + } + + enumerator = [[self headersForKey: @"cc"] objectEnumerator]; + while ((s = [enumerator nextObject])) + { + [recipients addObject: [s pureEMailAddress]]; + } + + return recipients; +} +@end diff --git a/ActiveSync/NGVCard+ActiveSync.h b/ActiveSync/NGVCard+ActiveSync.h new file mode 100644 index 000000000..8c3d5d08a --- /dev/null +++ b/ActiveSync/NGVCard+ActiveSync.h @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NGVCARDACTIVESYNC_H__ +#define __NGVCARDACTIVESYNC_H__ + +#import + +@class NSDictionary; +@class NSString; +@class WOContext; + +@interface NGVCard (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/NGVCard+ActiveSync.m b/ActiveSync/NGVCard+ActiveSync.m new file mode 100644 index 000000000..8c9a421a0 --- /dev/null +++ b/ActiveSync/NGVCard+ActiveSync.m @@ -0,0 +1,308 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NGVCard+ActiveSync.h" + +#import +#import +#import +#import + +#import + +#import + +#import + +#include "NSDate+ActiveSync.h" +#include "NSString+ActiveSync.h" + +@implementation NGVCard (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + CardElement *n, *homeAdr, *workAdr; + NSArray *emails, *addresses; + NSMutableString *s; + id o; + + int i; + + s = [NSMutableString string]; + n = [self n]; + + if ((o = [n flattenedValueAtIndex: 0 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [n flattenedValueAtIndex: 1 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self workCompany])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self title])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self preferredEMail])) + [s appendFormat: @"%@", o]; + + + // Secondary email addresses (2 and 3) + emails = [self secondaryEmails]; + + for (i = 0; i < [emails count]; i++) + { + o = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; + + [s appendFormat: @"%@", i+2, o, i+2]; + + if (i == 1) + break; + } + + // Telephone numbers + if ((o = [self workPhone]) && [o length]) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self homePhone]) && [o length]) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self fax]) && [o length]) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self mobile]) && [o length]) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [self pager]) && [o length]) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + // Home Address + addresses = [self childrenWithTag: @"adr" + andAttribute: @"type" + havingValue: @"home"]; + + if ([addresses count]) + { + homeAdr = [addresses objectAtIndex: 0]; + + if ((o = [homeAdr flattenedValueAtIndex: 2 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [homeAdr flattenedValueAtIndex: 4 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [homeAdr flattenedValueAtIndex: 5 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [homeAdr flattenedValueAtIndex: 6 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + } + + // Work Address + addresses = [self childrenWithTag: @"adr" + andAttribute: @"type" + havingValue: @"work"]; + + if ([addresses count]) + { + workAdr = [addresses objectAtIndex: 0]; + + if ((o = [workAdr flattenedValueAtIndex: 2 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [workAdr flattenedValueAtIndex: 4 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [workAdr flattenedValueAtIndex: 5 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + if ((o = [workAdr flattenedValueAtIndex: 6 forKey: @""])) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + } + + // Other, less important fields + if ((o = [self birthday])) + [s appendFormat: @"%@", [o activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + if ((o = [self note])) + { + o = [o activeSyncRepresentationInContext: context]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [o length]]; + [s appendFormat: @"%d", 0]; + [s appendFormat: @"%@", o]; + [s appendString: @""]; + } + + return s; +} + +// +// +// +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context +{ + CardElement *element; + id o; + + // Contact's note + if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) + [self setNote: o]; + + // Birthday + if ((o = [theValues objectForKey: @"Birthday"])) + { + o = [o calendarDate]; + [self setBday: [o descriptionWithCalendarFormat: @"%Y-%m-%d"]]; + } + + // + // Business address information + // + // BusinessStreet + // BusinessCity + // BusinessPostalCode + // BusinessState + // BusinessCountry + // + element = [self elementWithTag: @"adr" ofType: @"work"]; + [element setSingleValue: @"" + atIndex: 1 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"BusinessStreet"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"BusinessCity"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"BusinessState"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"BusinessPostalCode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"BusinessCountry"] + atIndex: 6 forKey: @""]; + + // + // Home address information + // + // HomeStreet + // HomeCity + // HomePostalCode + // HomeState + // HomeCountry + // + element = [self elementWithTag: @"adr" ofType: @"home"]; + [element setSingleValue: @"" + atIndex: 1 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"HomeStreet"] + atIndex: 2 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"HomeCity"] + atIndex: 3 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"HomeState"] + atIndex: 4 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"HomePostalCode"] + atIndex: 5 forKey: @""]; + [element setSingleValue: [theValues objectForKey: @"HomeCountry"] + atIndex: 6 forKey: @""]; + + // Company's name + if ((o = [theValues objectForKey: @"CompanyName"])) + { + [self setOrg: o units: nil]; + } + + // Email addresses + if ((o = [theValues objectForKey: @"Email1Address"])) + { + [self addEmail: o types: [NSArray arrayWithObject: @"pref"]]; + } + + if ((o = [theValues objectForKey: @"Email2Address"])) + { + [self addEmail: o types: nil]; + } + + if ((o = [theValues objectForKey: @"Email3Address"])) + { + [self addEmail: o types: nil]; + } + + // Formatted name + // MiddleName + // Suffix (II) + // Title (Mr.) + [self setFn: [theValues objectForKey: @"FileAs"]]; + + [self setNWithFamily: [theValues objectForKey: @"LastName"] + given: [theValues objectForKey: @"FirstName"] + additional: nil prefixes: nil suffixes: nil]; + + // IM information + [[self uniqueChildWithTag: @"x-aim"] + setSingleValue: [theValues objectForKey: @"IMAddress"] + forKey: @""]; + + // + // Phone numbrrs + // + element = [self elementWithTag: @"tel" ofType: @"work"]; + [element setSingleValue: [theValues objectForKey: @"BusinessPhoneNumber"] forKey: @""]; + + element = [self elementWithTag: @"tel" ofType: @"home"]; + [element setSingleValue: [theValues objectForKey: @"HomePhoneNumber"] forKey: @""]; + + element = [self elementWithTag: @"tel" ofType: @"cell"]; + [element setSingleValue: [theValues objectForKey: @"MobilePhoneNumber"] forKey: @""]; + + element = [self elementWithTag: @"tel" ofType: @"fax"]; + [element setSingleValue: [theValues objectForKey: @"BusinessFaxNumber"] forKey: @""]; + + element = [self elementWithTag: @"tel" ofType: @"pager"]; + [element setSingleValue: [theValues objectForKey: @"PagerNumber"] forKey: @""]; + + // Job's title + if ((o = [theValues objectForKey: @"JobTitle"])) + { + [self setTitle: o]; + } + + // WebPage (work) + if ((o = [theValues objectForKey: @"WebPage"])) + { + [[self elementWithTag: @"url" ofType: @"work"] + setSingleValue: o forKey: @""]; + } +} + +@end diff --git a/ActiveSync/NSCalendarDate+ActiveSync.h b/ActiveSync/NSCalendarDate+ActiveSync.h new file mode 100644 index 000000000..b8deed527 --- /dev/null +++ b/ActiveSync/NSCalendarDate+ActiveSync.h @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSCALENDARDATEACTIVESYNC_H__ +#define __NSCALENDARDATEACTIVESYNC_H__ + +#import + +@class NSString; + +@interface NSCalendarDate (ActiveSync) + ++ (NSCalendarDate *) dateFromFilterType: (NSString *) theFilterType; + +@end + +#endif diff --git a/ActiveSync/NSCalendarDate+ActiveSync.m b/ActiveSync/NSCalendarDate+ActiveSync.m new file mode 100644 index 000000000..e28a95c57 --- /dev/null +++ b/ActiveSync/NSCalendarDate+ActiveSync.m @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NSCalendarDate+ActiveSync.h" + +#import +#import + +#define ONE_DAY 86400 + +@implementation NSCalendarDate (ActiveSync) + +// +// See http://msdn.microsoft.com/en-us/library/gg709713(v=exchg.80).aspx for available types +// ++ (NSCalendarDate *) dateFromFilterType: (NSString *) theFilterType +{ + NSCalendarDate *d; + + d = [NSCalendarDate calendarDate]; + + if (d) + { + int value; + + switch ([theFilterType intValue]) + { + case 1: + value = ONE_DAY; + break; + case 2: + value = 3 * ONE_DAY; + break; + case 3: + value = 7 * ONE_DAY; + break; + case 4: + value = 14 * ONE_DAY; + break; + case 5: + value = 30 * ONE_DAY; + break; + case 6: + value = 90 * ONE_DAY; + break; + case 7: + value = 180 * ONE_DAY; + break; + case 0: + case 8: + default: + return nil; + } + + return [d initWithTimeIntervalSinceNow: -value]; + } + + return d; +} + +@end diff --git a/ActiveSync/NSData+ActiveSync.h b/ActiveSync/NSData+ActiveSync.h new file mode 100644 index 000000000..b0a994886 --- /dev/null +++ b/ActiveSync/NSData+ActiveSync.h @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSDATAACTIVESYNC_H__ +#define __NSDATAACTIVESYNC_H__ + +#import + +@class NSString; +@class WOContext; + +@interface NSData (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (NSData *) wbxml2xml; +- (NSData *) xml2wbxml; + +@end + +#endif diff --git a/ActiveSync/NSData+ActiveSync.m b/ActiveSync/NSData+ActiveSync.m new file mode 100644 index 000000000..abd91995a --- /dev/null +++ b/ActiveSync/NSData+ActiveSync.m @@ -0,0 +1,152 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NSData+ActiveSync.h" + +#import +#import + +#import + +#include +#include +#include + +#define WBXMLDEBUG 0 + +@implementation NSData (ActiveSync) + +- (void) _dumpToFile +{ + NSString *path; + + path = [NSString stringWithFormat: @"/tmp/%@.data", [[NSProcessInfo processInfo] globallyUniqueString]]; + [self writeToFile: path atomically: YES]; + NSLog(@"Original data written to: %@", path); +} + +// +// Encodes the data in base64 and strip newline characters +// +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + return [[self stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]; +} + +- (NSData *) wbxml2xml +{ + WBXMLGenXMLParams params; + NSData *data; + + unsigned int wbxml_len, xml_len, ret; + unsigned char *wbxml, *xml; + + wbxml = (unsigned char*)[self bytes]; + wbxml_len = [self length]; + xml = NULL; + xml_len = 0; + + params.lang = WBXML_LANG_ACTIVESYNC; + params.gen_type = WBXML_GEN_XML_INDENT; + params.indent = 1; + params.keep_ignorable_ws = FALSE; + + ret = wbxml_conv_wbxml2xml_withlen(wbxml, wbxml_len, &xml, &xml_len, ¶ms); + + if (ret != WBXML_OK) + { + NSLog(@"wbxml2xmlFromContent: failed: %s\n", wbxml_errors_string(ret)); + [self _dumpToFile]; + return nil; + } + + data = [[NSData alloc] initWithBytes: xml length: xml_len]; + +#if WBXMLDEBUG + [data writeToFile: @"/tmp/protocol.decoded" atomically: YES]; +#endif + + free(xml); + + return AUTORELEASE(data); +} + + +- (NSData *) xml2wbxml +{ + WBXMLConvXML2WBXML *conv; + NSData *data; + + unsigned int wbxml_len, xml_len, ret; + unsigned char *wbxml, *xml; + + xml = (unsigned char*)[self bytes]; + xml_len = [self length]; + wbxml = NULL; + wbxml_len = 0; + conv = NULL; + + ret = wbxml_conv_xml2wbxml_create(&conv); + + if (ret != WBXML_OK) + { + NSLog(@"xml2wbxmlFromContent: failed: %s\n", wbxml_errors_string(ret)); + [self _dumpToFile]; + return nil; + } + + wbxml_conv_xml2wbxml_enable_preserve_whitespaces(conv); + + // From libwbxml's changelog in v0.11.0: "The public ID is set to unknown and the DTD is not included. This is required for Microsoft ActiveSync." + wbxml_conv_xml2wbxml_disable_public_id(conv); + wbxml_conv_xml2wbxml_disable_string_table(conv); + + ret = wbxml_conv_xml2wbxml_run(conv, xml, xml_len, &wbxml, &wbxml_len); + + if (ret != WBXML_OK) + { + NSLog(@"xml2wbxmlFromContent: failed: %s\n", wbxml_errors_string(ret)); + [self _dumpToFile]; + free(wbxml); + wbxml_conv_xml2wbxml_destroy(conv); + return nil; + } + + data = [[NSData alloc] initWithBytes: wbxml length: wbxml_len]; + +#if WBXMLDEBUG + [data writeToFile: @"/tmp/protocol.encoded" atomically: YES]; +#endif + + free(wbxml); + wbxml_conv_xml2wbxml_destroy(conv); + + return AUTORELEASE(data); +} +@end diff --git a/ActiveSync/NSDate+ActiveSync.h b/ActiveSync/NSDate+ActiveSync.h new file mode 100644 index 000000000..49b6a94fe --- /dev/null +++ b/ActiveSync/NSDate+ActiveSync.h @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSDATEACTIVESYNC_H__ +#define __NSDATEACTIVESYNC_H__ + +#import + +@class NSString; +@class WOContext; + +@interface NSDate (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (NSString *) activeSyncRepresentationWithoutSeparatorsInContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/NSDate+ActiveSync.m b/ActiveSync/NSDate+ActiveSync.m new file mode 100644 index 000000000..ed3e98b3a --- /dev/null +++ b/ActiveSync/NSDate+ActiveSync.m @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NSDate+ActiveSync.h" + +#import +#import + +@implementation NSDate (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + return [self descriptionWithCalendarFormat: @"%Y-%m-%d-T%H:%M:%S.%FZ" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil]; +} + + +// +// From [MS-ASDTYPE].pdf - section 2.3 "Dates and times in calendar items MUST NOT include punctuation separators." +// +- (NSString *) activeSyncRepresentationWithoutSeparatorsInContext: (WOContext *) context +{ + return [self descriptionWithCalendarFormat: @"%Y%m%dT%H%M%SZ" timeZone: [NSTimeZone timeZoneWithName: @"GMT"] locale: nil]; +} + + +@end diff --git a/ActiveSync/NSString+ActiveSync.h b/ActiveSync/NSString+ActiveSync.h new file mode 100644 index 000000000..6f0433de0 --- /dev/null +++ b/ActiveSync/NSString+ActiveSync.h @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSSTRINGACTIVESYNC_H__ +#define __NSSTRINGACTIVESYNC_H__ + +#import + +#include "SOGoActiveSyncConstants.h" + +@class NSCalendarDate; +@class NSData; +@class WOContext; + +@interface NSString (ActiveSync) + +- (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType; +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (int) activeSyncFolderType; +- (NSString *) realCollectionIdWithFolderType: (SOGoMicrosoftActiveSyncFolderType *) folderType; +- (NSCalendarDate *) calendarDate; +- (NSString *) deviceId; +- (NSString *) command; +- (NSData *) convertHexStringToBytes; + +@end + +#endif diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m new file mode 100644 index 000000000..8b09c75ad --- /dev/null +++ b/ActiveSync/NSString+ActiveSync.m @@ -0,0 +1,283 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "NSString+ActiveSync.h" + +#include +#include +#include +#include + +#include + +#include + +@implementation NSString (ActiveSync) + +- (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType +{ + if (folderType == ActiveSyncEventFolder) + { + int len; + + len = [self length]; + + if (len > 4 && [self hasSuffix: @".ics"]) + return [self substringToIndex: len-4]; + else + return [NSString stringWithFormat: @"%@.ics", self]; + } + + return self; +} + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + NSString *s; + + s = [self stringByEscapingHTMLString]; + + return [[s componentsSeparatedByCharactersInSet: [self safeCharacterSet]] + componentsJoinedByString: @""]; +} + +- (int) activeSyncFolderType +{ + if ([self isEqualToString: @"inbox"]) + return 2; + else if ([self isEqualToString: @"draft"]) + return 3; + else if ([self isEqualToString: @"sent"]) + return 5; + else if ([self isEqualToString: @"trash"]) + return 4; + + return 12; +} + +- (NSString *) realCollectionIdWithFolderType: (SOGoMicrosoftActiveSyncFolderType *) folderType; +{ + NSString *realCollectionId, *v; + + *folderType = ActiveSyncGenericFolder; + v = [self stringByUnescapingURL]; + + if ([v hasPrefix: @"vevent/"]) + { + realCollectionId = [v substringFromIndex: 7]; + *folderType = ActiveSyncEventFolder; + } + else if ([v hasPrefix: @"vtodo/"]) + { + realCollectionId = [v substringFromIndex: 6]; + *folderType = ActiveSyncTaskFolder; + } + else if ([v hasPrefix: @"vcard/"]) + { + realCollectionId = [v substringFromIndex: 6]; + *folderType = ActiveSyncContactFolder; + } + else if ([v hasPrefix: @"mail/"]) + { + realCollectionId = [[v stringByUnescapingURL] substringFromIndex: 5]; + *folderType = ActiveSyncMailFolder; + } + else + { + realCollectionId = nil; + } + + return realCollectionId; +} + +// +// 2014-01-16T05:00:00.000Z +// +// See http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSCalendarDate.html#method$NSCalendarDate-initWithString$calendarFormat$ for the format details. +// +- (NSCalendarDate *) calendarDate +{ + id o; + + o = [NSCalendarDate dateWithString: self calendarFormat: @"%Y%m%dT%H%M%SZ"]; + + if (!o) + o = [NSCalendarDate dateWithString: self calendarFormat: @"%Y-%m-%dT%H:%M:%S.%FZ"]; + + return o; +} + +- (NSString *) _valueForParameter: (NSString *) theParameter +{ + NSArray *components; + NSString *s; + int i; + + components = [[[self componentsSeparatedByString: @"/"] lastObject] componentsSeparatedByString: @"&"]; + + for (i = 0; i < [components count]; i++) + { + s = [components objectAtIndex: i]; + + if ([[s uppercaseString] hasPrefix: theParameter]) + return [s substringFromIndex: [theParameter length]]; + } + + return nil; +} + +// +// This method extracts the "DeviceId" from a URI: +// +// /SOGo/Microsoft-Server-ActiveSync?Cmd=FolderSync&User=sogo10&DeviceId=SEC17CD1A3E9E3F2&DeviceType=SAMSUNGSGHI317M +// +- (NSString *) deviceId +{ + NSString *s; + + s = [self _valueForParameter: @"DEVICEID="]; + + if (!s) + s = @"Unknown"; + + return s; +} + +// +// +// +- (NSString *) command +{ + NSString *s; + + s = [self _valueForParameter: @"CMD="]; + + if (!s) + s = @"Unknown"; + + return s; +} + +// +// FIXME: combine with our OpenChange code. +// +- (char) _decodeHexByte: (char) byteChar +{ + char newByte; + + if (byteChar >= 48 && byteChar <= 57) + newByte = (uint8_t) byteChar - 48; + else if (byteChar >= 65 && byteChar <= 70) + newByte = (uint8_t) byteChar - 55; + else if (byteChar >= 97 && byteChar <= 102) + newByte = (uint8_t) byteChar - 87; + else + newByte = -1; + + return newByte; +} + +// +// FIXME: combine with our OpenChange code. +// +- (BOOL) _decodeHexByte: (uint8_t *) byte + atPos: (NSUInteger) pos +{ + BOOL error = NO; + char newByte; + unichar byteChar; + + byteChar = [self characterAtIndex: pos]; + if (byteChar < 256) + { + newByte = [self _decodeHexByte: (char) byteChar]; + if (newByte == -1) + error = YES; + else + *byte = newByte; + } + else + error = YES; + + return error; +} + +// +// FIXME: combine with our OpenChange code. +// +- (BOOL) _decodeHexPair: (uint8_t *) byte + atPos: (NSUInteger) pos +{ + BOOL error; + uint8_t lowValue, highValue; + + error = [self _decodeHexByte: &highValue atPos: pos]; + if (!error) + { + error = [self _decodeHexByte: &lowValue atPos: pos + 1]; + if (!error) + *byte = highValue << 4 | lowValue; + } + + return error; +} + +// +// FIXME: combine with our OpenChange code. +// +- (NSData *) convertHexStringToBytes +{ + NSUInteger count, strLen, bytesLen; + uint8_t *bytes, *currentByte; + NSData *decoded = nil; + BOOL error = NO; + + strLen = [self length]; + if ((strLen % 2) == 0) + { + bytesLen = strLen / 2; + bytes = NSZoneCalloc (NULL, bytesLen, sizeof (uint8_t)); + currentByte = bytes; + for (count = 0; !error && count < strLen; count += 2) + { + error = [self _decodeHexPair: currentByte atPos: count]; + currentByte++; + } + if (error) + NSZoneFree (NULL, bytes); + else + decoded = [NSData dataWithBytesNoCopy: bytes + length: bytesLen + freeWhenDone: YES]; + } + + return decoded; +} + +@end diff --git a/ActiveSync/README b/ActiveSync/README new file mode 100644 index 000000000..0bdb38085 --- /dev/null +++ b/ActiveSync/README @@ -0,0 +1,12 @@ +In order to use this software in production environments, you need to +get a proper usage license from Microsoft. Please contact them directly +to negotiate the fees associated to your user base. + +To contact Microsoft, please visit: + +http://www.microsoft.com/en-us/legal/intellectualproperty/IPLicensing/Programs/exchangeactivesyncprotocol.aspx + +and send an email to iplicreq@microsoft.com + +Inverse inc. provides this software for free, but is not responsible +for anything related to its usage. diff --git a/ActiveSync/SOGoActiveSyncConstants.h b/ActiveSync/SOGoActiveSyncConstants.h new file mode 100644 index 000000000..1fd7229b8 --- /dev/null +++ b/ActiveSync/SOGoActiveSyncConstants.h @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __SOGOACTIVESYNCCONSTANTS_H__ +#define __SOGOACTIVESYNCCONSTANTS_H__ + +typedef enum +{ + ActiveSyncGenericFolder = 0, + ActiveSyncMailFolder = 1, + ActiveSyncContactFolder = 2, + ActiveSyncEventFolder = 3, + ActiveSyncTaskFolder = 4, +} SOGoMicrosoftActiveSyncFolderType; + +#endif diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.h b/ActiveSync/SOGoActiveSyncDispatcher+Sync.h new file mode 100644 index 000000000..5508b4d89 --- /dev/null +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.h @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __SOGOACTIVESYNCDISPATCHERSYNC_H__ +#define __SOGOACTIVESYNCDISPATCHERSYNC_H__ + +#import "SOGoActiveSyncDispatcher.h" + +#import +#import + +@class WOResponse; + +@interface SOGoActiveSyncDispatcher (Sync) + +- (void) processSync: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse; + +@end + +#endif // SOGOACTIVESYNCDISPATCHERSYNC diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m new file mode 100644 index 000000000..dfa0b3110 --- /dev/null +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -0,0 +1,1022 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "SOGoActiveSyncDispatcher+Sync.h" + + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import +#import + +#import + +#import +#import +#import +#import +#import + +#import +#import + +#import + +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import + +#import +#import + +#include "iCalEvent+ActiveSync.h" +#include "iCalToDo+ActiveSync.h" +#include "NGDOMElement+ActiveSync.h" +#include "NGVCard+ActiveSync.h" +#include "NSCalendarDate+ActiveSync.h" +#include "NSDate+ActiveSync.h" +#include "NSData+ActiveSync.h" +#include "NSString+ActiveSync.h" +#include "SOGoActiveSyncConstants.h" +#include "SOGoMailObject+ActiveSync.h" + +#include + +@implementation SOGoActiveSyncDispatcher (Sync) + +// +// +// +// +// +// +// 1388757902 +// vcard/personal +// +// 25 +// +// +// 1 +// 32768 +// +// +// +// +// 16 +// +// +// 1 +// +// +// Goo Inc. +// annie@broccoli.com +// Broccoli, Annie +// Annie +// Broccoli +// +// +// +// +// +// +// +// +- (void) processSyncAddCommand: (id ) theDocumentElement + inCollection: (id) theCollection + withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + inBuffer: (NSMutableString *) theBuffer +{ + NSMutableDictionary *allValues; + NSString *clientId, *serverId; + NSArray *additions; + + id anAddition, sogoObject, o; + BOOL is_new; + int i; + + additions = (id)[theDocumentElement getElementsByTagName: @"Add"]; + if ([additions count]) + { + for (i = 0; i < [additions count]; i++) + { + anAddition = [additions objectAtIndex: i]; + is_new = YES; + + clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue]; + allValues = [NSMutableDictionary dictionaryWithDictionary: [[(id)[anAddition getElementsByTagName: @"ApplicationData"] lastObject] applicationData]]; + + switch (theFolderType) + { + case ActiveSyncContactFolder: + { + serverId = [NSString stringWithFormat: @"%@.vcf", [theCollection globallyUniqueObjectId]]; + sogoObject = [[SOGoContactGCSEntry alloc] initWithName: serverId + inContainer: theCollection]; + o = [sogoObject vCard]; + } + break; + case ActiveSyncEventFolder: + { + // Before adding a new appointment, we check if one is already present with the same UID. If that's + // the case, let's just update it. This can happen if for example, an iOS based device receives the + // invitation email and choses "Add to calendar" BEFORE actually syncing the calendar. That would + // create a duplicate on the server. + if ([allValues objectForKey: @"UID"]) + serverId = [allValues objectForKey: @"UID"]; + else + serverId = [theCollection globallyUniqueObjectId]; + + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: NO]; + + // If object isn't found, we 'create' a new one + if ([sogoObject isKindOfClass: [NSException class]]) + { + sogoObject = [[SOGoAppointmentObject alloc] initWithName: [serverId sanitizedServerIdWithType: theFolderType] + inContainer: theCollection]; + o = [sogoObject component: YES secure: NO]; + } + else + { + o = [sogoObject component: NO secure: NO]; + is_new = NO; + } + } + break; + case ActiveSyncTaskFolder: + { + serverId = [NSString stringWithFormat: @"%@.ics", [theCollection globallyUniqueObjectId]]; + sogoObject = [[SOGoTaskObject alloc] initWithName: serverId + inContainer: theCollection]; + o = [sogoObject component: YES secure: NO]; + } + break; + case ActiveSyncMailFolder: + default: + { + // FIXME + //continue; + NSLog(@"BLARG!"); + abort(); + } + } + + [o takeActiveSyncValues: allValues inContext: context]; + [sogoObject setIsNew: is_new]; + [sogoObject saveComponent: o]; + + // Everything is fine, lets generate our response + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", clientId]; + [theBuffer appendFormat: @"%@", serverId]; + [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: @""]; + } + } +} + +// +// +// +// +// +// +// 1387546048 +// vtodo/personal +// +// 25 +// +// +// 1 +// 32768 +// +// +// +// +// 36C5-52B36280-1-27B38F40.ics +// +// +// 1 +// +// +// foobar1 +// 1 +// 0 +// 0 +// 0 +// +// +// +// +// +// +// +- (void) processSyncChangeCommand: (id ) theDocumentElement + inCollection: (id) theCollection + withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + inBuffer: (NSMutableString *) theBuffer +{ + NSDictionary *allChanges; + NSString *serverId; + NSArray *changes; + id aChange, o, sogoObject; + + int i; + + changes = (id)[theDocumentElement getElementsByTagName: @"Change"]; + + if ([changes count]) + { + for (i = 0; i < [changes count]; i++) + { + aChange = [changes objectAtIndex: i]; + + serverId = [[(id)[aChange getElementsByTagName: @"ServerId"] lastObject] textValue]; + allChanges = [[(id)[aChange getElementsByTagName: @"ApplicationData"] lastObject] applicationData]; + + // Fetch the object and apply the changes + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: NO]; + + // Object was removed inbetween sync/commands? + if ([sogoObject isKindOfClass: [NSException class]]) + { + // FIXME - return status == 8 + continue; + } + + switch (theFolderType) + { + case ActiveSyncContactFolder: + { + o = [sogoObject vCard]; + [o takeActiveSyncValues: allChanges inContext: context]; + [sogoObject saveComponent: o]; + } + break; + case ActiveSyncEventFolder: + case ActiveSyncTaskFolder: + { + o = [sogoObject component: NO secure: NO]; + [o takeActiveSyncValues: allChanges inContext: context]; + [sogoObject saveComponent: o]; + } + break; + case ActiveSyncMailFolder: + default: + { + [sogoObject takeActiveSyncValues: allChanges inContext: context]; + } + } + + } + } +} + +// +// +// +// +// +// +// 1388764784 +// vtodo/personal +// +// 25 +// +// +// 1 +// 32768 +// +// +// +// +// 2CB5-52B36080-1-1C1D0240.ics +// +// +// +// +// +// +- (void) processSyncDeleteCommand: (id ) theDocumentElement + inCollection: (id) theCollection + withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + inBuffer: (NSMutableString *) theBuffer +{ + NSArray *deletions; + NSString *serverId; + + id aDelete, sogoObject; + int i; + + deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"]; + + if ([deletions count]) + { + for (i = 0; i < [deletions count]; i++) + { + aDelete = [deletions objectAtIndex: i]; + + serverId = [[(id)[aDelete getElementsByTagName: @"ServerId"] lastObject] textValue]; + + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: NO]; + + [sogoObject delete]; + } + } +} + + +// +// +// 91 +// +// +- (void) processSyncFetchCommand: (id ) theDocumentElement + inCollection: (id) theCollection + withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + inBuffer: (NSMutableString *) theBuffer +{ + NSString *serverId; + id o; + + serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue]; + + o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: NO]; + + // FIXME - error handling + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", serverId]; + [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: @""]; + [theBuffer appendString: [o activeSyncRepresentationInContext: context]]; + [theBuffer appendString: @""]; + [theBuffer appendString: @""]; +} + + +// +// The method handles +// +- (void) processSyncGetChanges: (id ) theDocumentElement + inCollection: (id) theCollection + withWindowSize: (unsigned int) theWindowSize + withSyncKey: (NSString *) theSyncKey + withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + withFilterType: (NSCalendarDate *) theFilterType + inBuffer: (NSMutableString *) theBuffer + lastServerKey: (NSString **) theLastServerKey + +{ + NSMutableString *s; + + BOOL more_available; + int i, max; + + // + // No changes in the collection - 2.2.2.19.1.1 Empty Sync Request. + // We check this and we don't generate any commands if we don't have to. + // + if ([theSyncKey isEqualToString: [theCollection davCollectionTag]]) + return; + + s = [NSMutableString string]; + more_available = NO; + + switch (theFolderType) + { + // Handle all the GCS components + case ActiveSyncContactFolder: + case ActiveSyncEventFolder: + case ActiveSyncTaskFolder: + { + id sogoObject, componentObject; + NSString *uid, *component_name; + NSDictionary *component; + NSArray *allComponents; + + BOOL updated; + int deleted; + + if (theFolderType == ActiveSyncContactFolder) + component_name = @"vcard"; + else if (theFolderType == ActiveSyncEventFolder) + component_name = @"vevent"; + else + component_name = @"vtodo"; + + allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; + + // Check for the WindowSize + max = [allComponents count]; + + // Disabled for now for GCS folders. + // if (max > theWindowSize) + // { + // max = theWindowSize; + // more_available = YES; + // } + + for (i = 0; i < max; i++) + { + component = [allComponents objectAtIndex: i]; + deleted = [[component objectForKey: @"c_deleted"] intValue]; + + if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name]) + continue; + + uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType]; + + if (deleted) + { + [s appendString: @""]; + [s appendFormat: @"%@", uid]; + [s appendString: @""]; + } + else + { + updated = YES; + + if ([[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue]) + updated = NO; + + sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType] + inContext: context + acquire: 0]; + + if (theFolderType == ActiveSyncContactFolder) + componentObject = [sogoObject vCard]; + else + componentObject = [sogoObject component: NO secure: NO]; + + + // + // We do NOT synchronize NEW events that are in fact, invitations + // to events. This is due to the fact that Outlook 2013 creates + // "phantom" events in the calendar that are mapped to invitations mails. + // If we synchronize these events too, it'll interfere with the whole thing + // and prevent Outlook from properly calling MeetingResponse. + // + if (!updated && theFolderType == ActiveSyncEventFolder) + { + iCalPersonPartStat partstat; + iCalPerson *attendee; + NSString *email; + + email = [[[context activeUser] allEmails] objectAtIndex: 0]; + attendee = [componentObject findAttendeeWithEmail: email]; + + if (attendee) + { + partstat = [attendee participationStatus]; + + if (partstat == iCalPersonPartStatNeedsAction) + continue; + } + } + + if (updated) + [s appendString: @""]; + else + [s appendString: @""]; + + [s appendFormat: @"%@", uid]; + [s appendString: @""]; + + [s appendString: [componentObject activeSyncRepresentationInContext: context]]; + + [s appendString: @""]; + + if (updated) + [s appendString: @""]; + else + [s appendString: @""]; + } + } // for ... + } + break; + case ActiveSyncMailFolder: + default: + { + SOGoMailObject *mailObject; + NSString *uid, *command; + NSDictionary *aMessage; + NSArray *allMessages; + + allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; + + // Check for the WindowSize. + // FIXME: we should eventually check for modseq and slice the maximum + // amount of messages returned to ensure we don't have the same + // modseq accross contiguous boundaries + max = [allMessages count]; + if (max > theWindowSize) + { + max = theWindowSize; + more_available = YES; + } + + for (i = 0; i < max; i++) + { + aMessage = [allMessages objectAtIndex: i]; + + uid = [[[aMessage allKeys] lastObject] stringValue]; + command = [[aMessage allValues] lastObject]; + + if ([command isEqualToString: @"deleted"]) + { + [s appendString: @""]; + [s appendFormat: @"%@", uid]; + [s appendString: @""]; + } + else + { + if ([command isEqualToString: @"added"]) + [s appendString: @""]; + else + [s appendString: @""]; + + mailObject = [theCollection lookupName: uid + inContext: context + acquire: 0]; + + [s appendFormat: @"%@", uid]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendString: @""]; + + if ([command isEqualToString: @"added"]) + [s appendString: @""]; + else + [s appendString: @""]; + + } + } + + // + if (more_available) + { + *theLastServerKey = uid; + } + } + break; + } // switch (folderType) ... + + if ([s length]) + { + [theBuffer appendString: @""]; + [theBuffer appendString: s]; + [theBuffer appendString: @""]; + + if (more_available) + [theBuffer appendString: @""]; + } +} + +// +// We have something like this: +// +// +// +// 91 +// +// +// +- (void) processSyncCommands: (id ) theDocumentElement + inCollection: (id) theCollection + withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType + inBuffer: (NSMutableString *) theBuffer + processed: (BOOL *) processed +{ + id aCommandDetails; + id aCommand, element; + NSArray *allCommands; + int i, j; + + allCommands = (id)[theDocumentElement getElementsByTagName: @"Commands"]; + + for (i = 0; i < [allCommands count]; i++) + { + aCommand = [allCommands objectAtIndex: i]; + aCommandDetails = [aCommand childNodes]; + + for (j = 0; j < [(id)aCommandDetails count]; j++) + { + element = [aCommandDetails objectAtIndex: j]; + + if ([element nodeType] == DOM_ELEMENT_NODE) + { + if ([[element tagName] isEqualToString: @"Add"]) + { + // Add + [self processSyncAddCommand: aCommand + inCollection: theCollection + withType: theFolderType + inBuffer: theBuffer]; + *processed = YES; + } + else if ([[element tagName] isEqualToString: @"Change"]) + { + // Change + [self processSyncChangeCommand: aCommand + inCollection: theCollection + withType: theFolderType + inBuffer: theBuffer]; + *processed = YES; + } + else if ([[element tagName] isEqualToString: @"Delete"]) + { + // Delete + [self processSyncDeleteCommand: aCommand + inCollection: theCollection + withType: theFolderType + inBuffer: theBuffer]; + } + else if ([[element tagName] isEqualToString: @"Fetch"]) + { + // Fetch + [self processSyncFetchCommand: aCommand + inCollection: theCollection + withType: theFolderType + inBuffer: theBuffer]; + *processed = YES; + } + } + } + } +} + +// +// +// +- (void) processSyncCollection: (id ) theDocumentElement + inBuffer: (NSMutableString *) theBuffer + changeDetected: (BOOL *) changeDetected +{ + NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *lastServerKey; + SOGoMicrosoftActiveSyncFolderType folderType; + id collection, value; + + NSMutableString *changeBuffer, *commandsBuffer; + BOOL getChanges, first_sync; + unsigned int windowSize; + + changeBuffer = [NSMutableString string]; + commandsBuffer = [NSMutableString string]; + + collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + collection = [self collectionFromId: realCollectionId type: folderType]; + + syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; + + // We check for a window size, default to 100 if not specfied or out of bounds + windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue]; + + if (windowSize == 0 || windowSize > 512) + windowSize = 100; + + lastServerKey = nil; + + // From the documention, if GetChanges is missing, we must assume it's a YES. + // See http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx for all details. + value = [theDocumentElement getElementsByTagName: @"GetChanges"]; + getChanges = YES; + + if ([value count] && [[[value lastObject] textValue] length]) + getChanges = [[[value lastObject] textValue] boolValue]; + + first_sync = NO; + + if ([syncKey isEqualToString: @"0"]) + { + davCollectionTag = @"-1"; + first_sync = YES; + *changeDetected = YES; + } + + // We check our sync preferences and we stash them + bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue]; + + if (!bodyPreferenceType) + bodyPreferenceType = @"1"; + + [context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"]; + + + // We generate the commands, if any, for the response. We might also have + // generated some in processSyncCommand:inResponse: as we could have + // received a Fetch command + if (getChanges && !first_sync) + { + [self processSyncGetChanges: theDocumentElement + inCollection: collection + withWindowSize: windowSize + withSyncKey: syncKey + withFolderType: folderType + withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] + inBuffer: changeBuffer + lastServerKey: &lastServerKey]; + } + + // + // We process the commands from the request + // + if (!first_sync) + { + NSMutableString *s; + BOOL processed; + + s = [NSMutableString string]; + processed = NO; + + [self processSyncCommands: theDocumentElement + inCollection: collection + withType: folderType + inBuffer: s + processed: &processed]; + + if (processed) + [commandsBuffer appendFormat: @"%@", s]; + else + [commandsBuffer appendString: s]; + } + + // If we got any changes or if we have applied any commands + // let's regenerate our SyncKey based on the collection tag. + if ([changeBuffer length] || [commandsBuffer length]) + { + if (lastServerKey) + davCollectionTag = [collection davCollectionTagFromId: lastServerKey]; + else + davCollectionTag = [collection davCollectionTag]; + + *changeDetected = YES; + } + + // Generate the response buffer + [theBuffer appendString: @""]; + + if (folderType == ActiveSyncMailFolder) + [theBuffer appendString: @"Email"]; + else if (folderType == ActiveSyncContactFolder) + [theBuffer appendString: @"Contacts"]; + else if (folderType == ActiveSyncEventFolder) + [theBuffer appendString: @"Calendar"]; + else if (folderType == ActiveSyncTaskFolder) + [theBuffer appendString: @"Tasks"]; + + [theBuffer appendFormat: @"%@", davCollectionTag]; + [theBuffer appendFormat: @"%@", collectionId]; + [theBuffer appendFormat: @"%d", 1]; + + [theBuffer appendString: changeBuffer]; + [theBuffer appendString: commandsBuffer]; + + [theBuffer appendString: @""]; +} + +// +// Initial folder sync: +// +// +// +// +// +// +// 0 +// folderINBOX +// +// +// +// +// +// Following this will be a GetItemEstimate call. Following our response to the GetItemEstimate, we'll +// have a new Sync call like this: +// +// +// +// +// +// +// 1 +// folderINBOX +// 1 +// +// 50 +// +// 5 -- http://msdn.microsoft.com/en-us/library/gg709713(v=exchg.80).aspx +// -- http://msdn.microsoft.com/en-us/library/ee218197(v=exchg.80).aspx +// 2 -- +// 51200 +// +// +// 4 +// +// +// +// +// +// +// +// +// When adding a new task, we might have something like this: +// +// +// +// +// +// +// 1 +// personal +// +// -- http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx +// 5 -- http://msdn.microsoft.com/en-us/library/gg650865(v=exchg.80).aspx +// +// -- http://msdn.microsoft.com/en-us/library/ee218197(v=exchg.80).aspx +// 1 +// 400000 +// +// +// +// +// new_task_1386614771261 +// +// +// 1 +// 6 +// tomate +// +// test 1 +// 1 +// 2013-12-09T19:00:00.000Z +// 0 +// 0 +// 2013-12-09T19:00:00.000Z +// +// +// +// +// +// +// +// The algorithm here is pretty simple: +// +// 1. extract the list of collections +// 2. for each collection +// 2.1. extract the metadata (id, synckey, etc.) +// 2.2. extract the list of commands +// 2.3. for each command +// 2.3.1 process the command (add/change/delete/fetch) +// 2.3.2 build a response during the processsing +// +// +- (void) processSync: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + SOGoSystemDefaults *defaults; + id aCollection; + NSMutableString *output, *s; + NSArray *allCollections; + NSData *d; + + int i, j, defaultInterval, heartbeatInterval, internalInterval; + BOOL changeDetected; + + // We initialize our output buffer + output = [NSMutableString string]; + + [output appendString: @""]; + [output appendString: @""]; + [output appendString: @""]; + + defaults = [SOGoSystemDefaults sharedSystemDefaults]; + heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue]; + defaultInterval = [defaults maximumSyncInterval]; + internalInterval = [defaults internalSyncInterval]; + + // We check to see if our heartbeat interval falls into the supported ranges. + if (heartbeatInterval > defaultInterval || heartbeatInterval < 1) + { + // Interval is too long, inform the client. + heartbeatInterval = defaultInterval; + + // Outlook doesn't like this... + //[output appendFormat: @"%d", defaultInterval]; + //[output appendFormat: @"%d", 14]; + } + + [output appendString: @""]; + + allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"]; + + // We enter our loop detection change + for (i = 0; i < (defaultInterval/internalInterval); i++) + { + s = [NSMutableString string]; + + for (j = 0; j < [allCollections count]; j++) + { + aCollection = [allCollections objectAtIndex: j]; + + [self processSyncCollection: aCollection inBuffer: s changeDetected: &changeDetected]; + } + + if (changeDetected) + { + NSLog(@"Change detected, we push the content."); + break; + } + else + { + NSLog(@"Sleeping %d seconds while detecting changes...", internalInterval); + sleep(internalInterval); + } + } + + // We always return the last generated response. + // If we only return , + // iOS powered devices will simply crash. + [output appendString: s]; + + [output appendString: @""]; + + d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +@end diff --git a/ActiveSync/SOGoActiveSyncDispatcher.h b/ActiveSync/SOGoActiveSyncDispatcher.h new file mode 100644 index 000000000..0c28ebced --- /dev/null +++ b/ActiveSync/SOGoActiveSyncDispatcher.h @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import + +#include "SOGoActiveSyncConstants.h" + +@class NSException; + +@interface SOGoActiveSyncDispatcher : NSObject +{ + id context; +} + +- (id) collectionFromId: (NSString *) theCollectionId + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; + +- (NSException *) dispatchRequest: (id) theRequest + inResponse: (id) theResponse + context: (id) theContext; + +@end diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m new file mode 100644 index 000000000..efb201367 --- /dev/null +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -0,0 +1,1572 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "SOGoActiveSyncDispatcher.h" + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#import +#import +#import + +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import +#import + +#include "iCalEvent+ActiveSync.h" +#include "iCalToDo+ActiveSync.h" +#include "NGMimeMessage+ActiveSync.h" +#include "NGVCard+ActiveSync.h" +#include "NSCalendarDate+ActiveSync.h" +#include "NSData+ActiveSync.h" +#include "NSDate+ActiveSync.h" +#include "NSString+ActiveSync.h" +#include "SOGoActiveSyncConstants.h" +#include "SOGoMailObject+ActiveSync.h" + +#include + +@implementation SOGoActiveSyncDispatcher + +- (void) _setFolderSyncKey: (NSString *) theSyncKey +{ + NSMutableDictionary *metadata; + + metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; + + [metadata setObject: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"SyncKey"] forKey: @"FolderSync"]; + + [[[context activeUser] userSettings] setMicrosoftActiveSyncMetadata: metadata + forDevice: [context objectForKey: @"DeviceId"]]; + + [[[context activeUser] userSettings] synchronize]; +} + +// +// +// +- (id) collectionFromId: (NSString *) theCollectionId + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType +{ + id collection; + + collection = nil; + + switch (theFolderType) + { + case ActiveSyncContactFolder: + { + collection = [[context activeUser] personalContactsFolderInContext: context]; + } + break; + case ActiveSyncEventFolder: + case ActiveSyncTaskFolder: + { + collection = [[context activeUser] personalCalendarFolderInContext: context]; + } + break; + case ActiveSyncMailFolder: + default: + { + SOGoMailAccounts *accountsFolder; + SOGoMailFolder *currentFolder; + SOGoUserFolder *userFolder; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId] + inContext: context + acquire: NO]; + } + } + + return collection; +} + +// +// +// +- (void) processFolderCreate: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *parentId, *displayName, *nameInContainer, *syncKey; + SOGoUserFolder *userFolder; + NSMutableString *s; + NSData *d; + + int type; + + parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue]; + displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue]; + type = [[[(id)[theDocumentElement getElementsByTagName: @"Type"] lastObject] textValue] intValue]; + userFolder = [[context activeUser] homeFolderInContext: context]; + + // See 2.2.3.170.2 Type (FolderCreate) - http://msdn.microsoft.com/en-us/library/gg675445(v=exchg.80).aspx + // We support the following types: + // + // 12 User-created mail folder + // 13 User-created Calendar folder + // 14 User-created Contacts folder + // 15 User-created Tasks folder + // + switch (type) + { + case 12: + { + SOGoMailAccounts *accountsFolder; + SOGoMailFolder *newFolder; + id currentFolder; + + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", [displayName stringByEncodingImap4FolderName]] + inContext: context + acquire: NO]; + + // FIXME + // handle exists (status == 2) + // handle right synckey + if ([newFolder create]) + { + nameInContainer = [newFolder nameInContainer]; + + // We strip the "folder" prefix + nameInContainer = [nameInContainer substringFromIndex: 6]; + nameInContainer = [[NSString stringWithFormat: @"mail/%@", nameInContainer] stringByEscapingURL]; + } + else + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unable to create folder."]; + return; + } + } + break; + case 13: + case 15: + { + SOGoAppointmentFolders *appointmentFolders; + + appointmentFolders = [userFolder privateCalendars: @"Calendar" inContext: context]; + [appointmentFolders newFolderWithName: displayName + nameInContainer: &nameInContainer]; + if (type == 13) + nameInContainer = [NSString stringWithFormat: @"vevent/%@", nameInContainer]; + else + nameInContainer = [NSString stringWithFormat: @"vtodo/%@", nameInContainer]; + } + break; + case 14: + { + SOGoContactFolders *contactFolders; + + contactFolders = [userFolder privateContacts: @"Contacts" inContext: context]; + [contactFolders newFolderWithName: displayName + nameInContainer: &nameInContainer]; + nameInContainer = [NSString stringWithFormat: @"vcard/%@", nameInContainer]; + } + break; + default: + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unsupported folder type during creation."]; + return; + } + } // switch (type) ... + + // + // We update the FolderSync's synckey + // + syncKey = [[NSProcessInfo processInfo] globallyUniqueString]; + + [self _setFolderSyncKey: syncKey]; + + // All good, we send our response. The format is documented here: + // 6.7 FolderCreate Response Schema - http://msdn.microsoft.com/en-us/library/dn338950(v=exchg.80).aspx + // + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%@", syncKey]; + [s appendFormat: @"%@", nameInContainer]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +// +// +// +- (void) processFolderDelete: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + SOGoMailAccounts *accountsFolder; + SOGoMailFolder *folderToDelete; + SOGoUserFolder *userFolder; + id currentFolder; + NSException *error; + NSString *serverId; + + SOGoMicrosoftActiveSyncFolderType folderType; + + + serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + folderToDelete = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", serverId] + inContext: context + acquire: NO]; + + error = [folderToDelete delete]; + + if (!error) + { + NSMutableString *s; + NSString *syncKey; + NSData *d; + + // + // We update the FolderSync's synckey + // + syncKey = [[NSProcessInfo processInfo] globallyUniqueString]; + + [self _setFolderSyncKey: syncKey]; + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%@", syncKey]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; + } + else + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unable to delete folder."]; + } +} + +// +// +// +- (void) processFolderUpdate: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *serverId, *parentId, *displayName; + SOGoMailAccounts *accountsFolder; + SOGoUserFolder *userFolder; + SOGoMailFolder *folderToUpdate; + id currentFolder; + NSException *error; + + SOGoMicrosoftActiveSyncFolderType folderType; + int status; + + serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue]; + displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue]; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + folderToUpdate = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", serverId] + inContext: context + acquire: NO]; + + error = [folderToUpdate renameTo: displayName]; + + // Handle new name exist + if (!error) + { + NSMutableString *s; + NSString *syncKey; + NSData *d; + + // + // We update the FolderSync's synckey + // + syncKey = [[NSProcessInfo processInfo] globallyUniqueString]; + + // See http://msdn.microsoft.com/en-us/library/gg675615(v=exchg.80).aspx + // we return '9' - we force a FolderSync + status = 9; + + [self _setFolderSyncKey: syncKey]; + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", status]; + [s appendFormat: @"%@", syncKey]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; + } + else + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unable to update folder."]; + } +} + + +// +// +// +// +// 0 +// +// +- (void) processFolderSync: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSMutableDictionary *metadata; + NSMutableString *s; + NSString *syncKey; + NSData *d; + + BOOL first_sync; + int status; + + metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; + syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; + s = [NSMutableString string]; + + first_sync = NO; + status = 1; + + if ([syncKey isEqualToString: @"0"]) + { + first_sync = YES; + syncKey = @"1"; + } + else if (![syncKey isEqualToString: [[metadata objectForKey: @"FolderSync"] objectForKey: @"SyncKey"]]) + { + // Synchronization key mismatch or invalid synchronization key + status = 9; + } + + [self _setFolderSyncKey: syncKey]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d%@", status, syncKey]; + + // Initial sync, let's return the complete folder list + if (first_sync) + { + SOGoMailAccounts *accountsFolder; + SOGoMailAccount *accountFolder; + SOGoUserFolder *userFolder; + id currentFolder; + + NSDictionary *folderMetadata; + NSArray *allFoldersMetadata; + NSString *name, *serverId, *parentId; + + int i, type; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + allFoldersMetadata = [accountFolder allFoldersMetadata]; + + // See 2.2.3.170.3 Type (FolderSync) - http://msdn.microsoft.com/en-us/library/gg650877(v=exchg.80).aspx + [s appendFormat: @"%d", [allFoldersMetadata count]+3]; + + for (i = 0; i < [allFoldersMetadata count]; i++) + { + folderMetadata = [allFoldersMetadata objectAtIndex: i]; + serverId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"path"]]; + name = [folderMetadata objectForKey: @"displayName"]; + + if ([name hasPrefix: @"/"]) + name = [name substringFromIndex: 1]; + + if ([name hasSuffix: @"/"]) + name = [name substringToIndex: [name length]-2]; + + type = [[folderMetadata objectForKey: @"type"] activeSyncFolderType]; + + parentId = @"0"; + + if ([folderMetadata objectForKey: @"parent"]) + { + parentId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"parent"]]; + name = [[name pathComponents] lastObject]; + } + + [s appendFormat: @"%@%@%d%@", + [serverId stringByEscapingURL], + [parentId stringByEscapingURL], + type, + [name activeSyncRepresentationInContext: context]]; + } + + // We add the personal calendar - events + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal calendar - tasks + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal address book + // FIXME: add all address books + currentFolder = [[context activeUser] personalContactsFolderInContext: context]; + name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + } + + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +// +// From: http://msdn.microsoft.com/en-us/library/ee157980(v=exchg.80).aspx : +// +// <2> Section 2.2.2.6: The GetAttachment command is not supported when the MS-ASProtocolVersion header is set to 14.0 or 14.1 +// in the GetAttachment command request. Use the Fetch element of the ItemOperations command instead. For more information about +// the MS-ASProtocolVersion header, see [MS-ASHTTP] section 2.2.1.1.2.4. +// +- (void) processGetAttachment: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + +} + +// +// +// +// +// +// +// 1 +// folderINBOX +// +// 3 +// +// +// +// +// +- (void) processGetItemEstimate: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *collectionId, *realCollectionId; + id currentCollection; + NSMutableString *s; + NSData *d; + + SOGoMicrosoftActiveSyncFolderType folderType; + int status, count; + + s = [NSMutableString string]; + status = 1; + count = 0; + + collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; + realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + currentCollection = [self collectionFromId: realCollectionId type: folderType]; + + // + // For IMAP, we simply build a request like this: + // + // . UID SORT (SUBJECT) UTF-8 SINCE 1-Jan-2014 NOT DELETED + // * SORT 124576 124577 124579 124578 + // . OK Completed (4 msgs in 0.000 secs) + // + if (folderType == ActiveSyncMailFolder) + { + EOQualifier *notDeletedQualifier, *sinceDateQualifier; + EOAndQualifier *qualifier; + NSCalendarDate *filter; + NSArray *uids; + + filter = [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]; + + notDeletedQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(not (flags = %@))", + @"deleted"]; + sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(DATE >= %@)", filter]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: notDeletedQualifier, sinceDateQualifier, + nil]; + AUTORELEASE(qualifier); + + uids = [currentCollection fetchUIDsMatchingQualifier: qualifier + sortOrdering: @"REVERSE ARRIVAL" + threaded: NO]; + count = [uids count]; + } + else + { + count = [[currentCollection toOneRelationshipKeys] count]; + } + + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", status]; + + [s appendFormat: @"%@", collectionId]; + [s appendFormat: @"%d", count]; + + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +// +// +// +// +// +// Mailbox -- http://msdn.microsoft.com/en-us/library/gg663522(v=exchg.80).aspx +// 2 -- +// +// +// +// +- (void) processItemOperations: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *fileReference, *realCollectionId; + NSMutableString *s; + + SOGoMicrosoftActiveSyncFolderType folderType; + + fileReference = [[[(id)[theDocumentElement getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; + + realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; + + if (folderType == ActiveSyncMailFolder) + { + id currentFolder, currentCollection, currentBodyPart; + NSString *folderName, *messageName, *pathToPart; + SOGoMailAccounts *accountsFolder; + SOGoUserFolder *userFolder; + SOGoMailObject *mailObject; + NSData *d; + + 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]; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", folderName] + inContext: context + acquire: NO]; + + mailObject = [currentCollection lookupName: messageName inContext: context acquire: NO]; + currentBodyPart = [mailObject lookupImap4BodyPartKey: pathToPart inContext: context]; + + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @"1"]; + [s appendString: @""]; + + [s appendString: @""]; + [s appendString: @"1"]; + [s appendFormat: @"%@", [fileReference stringByEscapingURL]]; + [s appendString: @""]; + + [s appendFormat: @"%@/%@", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]]; + [s appendFormat: @"%@", [[currentBodyPart fetchBLOB] activeSyncRepresentationInContext: context]]; + + [s appendString: @""]; + [s appendString: @""]; + + + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + [theResponse setContent: d]; + } + else + { + [theResponse setStatus: 500]; + } +} + + +// +// +// +// +// +// 1 +// mail%2FINBOX +// 283 +// +// +// +- (void) processMeetingResponse: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *realCollectionId, *requestId, *participationStatus, *calendarId; + SOGoAppointmentObject *appointmentObject; + SOGoMailObject *mailObject; + NSMutableString *s; + NSData *d; + + id collection; + + SOGoMicrosoftActiveSyncFolderType folderType; + int userResponse; + int status; + + s = [NSMutableString string]; + status = 1; + + realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue]; + requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; + appointmentObject = nil; + calendarId = nil; + + // Outlook 2013 calls MeetingResponse on the calendar folder! We have + // no way of handling as we can't retrieve the email (using the id found + // in requestId) in any mail folder! If that happens, let's simply + // assume it comes from the INBOX. This should be generally safe as people + // will answer email invitations as they receive them on their INBOX. + // Note that the mail should also still be there as MeetingResponse is + // called *before* MoveItems. + // + // Apple iOS will also call MeetingResponse on the calendar folder when the + // user accepts/declines the meeting from the Calendar application. Before + // falling back on INBOX, we first check if we can find the event in the + // personal calendar. + if (folderType == ActiveSyncEventFolder) + { + collection = [[context activeUser] personalCalendarFolderInContext: context]; + appointmentObject = [collection lookupName: [requestId sanitizedServerIdWithType: ActiveSyncEventFolder] + inContext: context + acquire: NO]; + calendarId = requestId; + + // Object not found, let's fallback on the INBOX folder + if ([appointmentObject isKindOfClass: [NSException class]]) + { + folderType = ActiveSyncMailFolder; + realCollectionId = @"INBOX"; + appointmentObject = nil; + } + } + + // Fetch the appointment object from the mail message + if (!appointmentObject) + { + collection = [self collectionFromId: realCollectionId type: folderType]; + + // + // We fetch the calendar information based on the email (requestId) in the user's INBOX (or elsewhere) + // + // FIXME: that won't work too well for external invitations... + mailObject = [collection lookupName: requestId + inContext: context + acquire: 0]; + + if (![mailObject isKindOfClass: [NSException class]]) + { + iCalCalendar *calendar; + iCalEvent *event; + + calendar = [mailObject calendarFromIMIPMessage]; + event = [[calendar events] lastObject]; + calendarId = [event uid]; + + // Fetch the SOGoAppointmentObject + collection = [[context activeUser] personalCalendarFolderInContext: context]; + appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]] + inContext: context + acquire: NO]; + } + } + + if (appointmentObject && + calendarId && + (![appointmentObject isKindOfClass: [NSException class]])) + { + // 1 -> accepted, 2 -> tentative, 3 -> declined + if (userResponse == 1) + participationStatus = @"ACCEPTED"; + else if (userResponse == 2) + participationStatus = @"TENTATIVE"; + else + participationStatus = @"DECLINED"; + + [appointmentObject changeParticipationStatus: participationStatus + withDelegate: nil]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%@", requestId]; + [s appendFormat: @"%@", calendarId]; + [s appendFormat: @"%d", status]; + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; + } + else + { + [theResponse setStatus: 500]; + } +} + + +// +// +// +// +// +// 85 +// mail/INBOX +// mail/toto +// +// +// +- (void) processMoveItems: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *srcMessageId, *srcFolderId, *dstFolderId, *dstMessageId; + SOGoMicrosoftActiveSyncFolderType srcFolderType, dstFolderType; + + srcMessageId = [[(id)[theDocumentElement getElementsByTagName: @"SrcMsgId"] lastObject] textValue]; + srcFolderId = [[[(id)[theDocumentElement getElementsByTagName: @"SrcFldId"] lastObject] textValue] realCollectionIdWithFolderType: &srcFolderType]; + dstFolderId = [[[(id)[theDocumentElement getElementsByTagName: @"DstFldId"] lastObject] textValue] realCollectionIdWithFolderType: &dstFolderType]; + + // FIXME + if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder) + { + NGImap4Client *client; + id currentCollection; + + NSDictionary *response; + NSString *v; + + // userFolder = [[context activeUser] homeFolderInContext: context]; + // accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + // currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + currentCollection = [self collectionFromId: srcFolderId type: srcFolderType]; + + // [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", srcFolderId] + // inContext: context + // acquire: NO]; + + client = [[currentCollection imap4Connection] client]; + [client select: srcFolderId]; + response = [client copyUid: [srcMessageId intValue] + toFolder: [NSString stringWithFormat: @"/%@", dstFolderId]]; + + // We extract the destionation message id + dstMessageId = nil; + + if ([[response objectForKey: @"result"] boolValue] + && (v = [[[response objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"flag"]) + && [v hasPrefix: @"COPYUID "]) + { + dstMessageId = [[v componentsSeparatedByString: @" "] lastObject]; + + // We mark the original message as deleted + response = [client storeFlags: [NSArray arrayWithObject: @"Deleted"] + forUIDs: [NSArray arrayWithObject: srcMessageId] + addOrRemove: YES]; + + if ([[response valueForKey: @"result"] boolValue]) + [(SOGoMailFolder *)currentCollection expunge]; + + } + + if (!dstMessageId) + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unable to move message"]; + } + else + { + NSMutableString *s; + NSData *d; + + // Everything is alright, lets return the proper response. "Status == 3" means success. + s = [NSMutableString string]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%@", dstMessageId]; + [s appendFormat: @"%d", 3]; + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; + } + } + else + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"Unsupported move operation"]; + } +} + +// +// Ping requests make a little sense because the request +// doesn't contain the SyncKey on the client. So we can't +// really know if something has changed on the server. What we +// do for now is simply return Status=5 with the HeartbeatInterval +// set at 60 seconds or we wait 60 seconds before responding with +// Status=1 +// +- (void) processPing: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + SOGoSystemDefaults *defaults; + NSMutableString *s; + NSData *d; + + int heartbeatInterval, defaultInterval, status; + + defaults = [SOGoSystemDefaults sharedSystemDefaults]; + defaultInterval = [defaults maximumPingInterval]; + + if (theDocumentElement) + heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue]; + else + heartbeatInterval = defaultInterval; + + if (heartbeatInterval > defaultInterval || heartbeatInterval == 0) + { + heartbeatInterval = defaultInterval; + status = 5; + } + else + { + status = 1; + } + + NSLog(@"Got Ping request with valid interval - sleeping for %d seconds.", heartbeatInterval); + sleep(heartbeatInterval); + + // We generate our response + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", status]; + + if (status == 5) + { + [s appendFormat: @"%d", heartbeatInterval]; + } + + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +// +// +// +// +// sogo1@example.com +// sogo10@sogoludo.inverse +// +// 19 +// +// 2014-01-16T05:00:00.000Z +// 2014-01-17T04:59:00.000Z +// +// +// +// +- (void) processResolveRecipients: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSArray *allRecipients; + int i, j, k; + + allRecipients = (id)[theDocumentElement getElementsByTagName: @"To"]; + + if ([allRecipients count] && [(id)[theDocumentElement getElementsByTagName: @"Availability"] count]) + { + NSCalendarDate *startDate, *endDate; + SOGoAppointmentFolder *folder; + NSString *aRecipient, *login; + NSMutableString *s; + NSArray *freebusy; + SOGoUser *user; + NSData *d; + + unsigned int startdate, enddate, increments; + char c; + + startDate = [[[(id)[theDocumentElement getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate]; + startdate = [startDate timeIntervalSince1970]; + + endDate = [[[(id)[theDocumentElement getElementsByTagName: @"EndTime"] lastObject] textValue] calendarDate]; + enddate = [endDate timeIntervalSince1970]; + + // Number of 30 mins increments between our two dates + increments = ceil((float)((enddate - startdate)/60/30)) + 1; + + s = [NSMutableString string]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + + for (i = 0; i < [allRecipients count]; i++) + { + aRecipient = [[allRecipients objectAtIndex: i] textValue]; + + login = [[SOGoUserManager sharedUserManager] getUIDForEmail: aRecipient]; + + if (login) + { + user = [SOGoUser userWithLogin: login]; + + [s appendString: @""]; + [s appendFormat: @"%@", aRecipient]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", 1]; + + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%@", [user cn]]; + [s appendFormat: @"%@", [[user allEmails] objectAtIndex: 0]]; + + // Freebusy structure: http://msdn.microsoft.com/en-us/library/gg663493(v=exchg.80).aspx + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendString: @""]; + + folder = [user personalCalendarFolderInContext: context]; + freebusy = [folder fetchFreeBusyInfosFrom: startDate to: endDate]; + + + NGCalendarDateRange *r1, *r2; + + for (j = 1; j <= increments; j++) + { + c = '0'; + + r1 = [NGCalendarDateRange calendarDateRangeWithStartDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60)] + endDate: [NSDate dateWithTimeIntervalSince1970: (startdate+j*30*60 + 30)]]; + + + for (k = 0; k < [freebusy count]; k++) + { + + r2 = [NGCalendarDateRange calendarDateRangeWithStartDate: [[freebusy objectAtIndex: k] objectForKey: @"startDate"] + endDate: [[freebusy objectAtIndex: k] objectForKey: @"endDate"]]; + + if ([r2 doesIntersectWithDateRange: r1]) + { + c = '2'; + break; + } + } + + + [s appendFormat: @"%c", c]; + } + + + [s appendString: @""]; + [s appendString: @""]; + + + [s appendString: @""]; + [s appendString: @""]; + } + } + + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; + } +} + +// +// +// +// +// +// GAL +// so +// +// 0-19 +// +// +// +// +- (void) processSearch: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + SOGoContactSourceFolder *currentFolder; + NSDictionary *systemSources, *contact; + SOGoContactFolders *contactFolders; + NSArray *allKeys, *allContacts; + SOGoUserFolder *userFolder; + NSString *name, *query; + NSMutableString *s; + NSData *d; + + int i, j, total; + + name = [[(id)[theDocumentElement getElementsByTagName: @"Name"] lastObject] textValue]; + query = [[(id)[theDocumentElement getElementsByTagName: @"Query"] lastObject] textValue]; + + // FIXME: for now, we only search in the GAL + if (![name isEqualToString: @"GAL"]) + { + [theResponse setStatus: 500]; + return; + } + + + userFolder = [[context activeUser] homeFolderInContext: context]; + contactFolders = [userFolder privateContacts: @"Contacts" inContext: context]; + systemSources = [contactFolders systemSources]; + allKeys = [systemSources allKeys]; + + s = [NSMutableString string]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"1"]; + [s appendFormat: @""]; + [s appendFormat: @""]; + [s appendFormat: @"1"]; + + total = 0; + + for (i = 0; i < [allKeys count]; i++) + { + currentFolder = [systemSources objectForKey: [allKeys objectAtIndex: i]]; + allContacts = [currentFolder lookupContactsWithFilter: query + onCriteria: @"name_or_address" + sortBy: @"c_cn" + ordering: NSOrderedAscending + inDomain: [[context activeUser] domain]]; + + for (j = 0; j < [allContacts count]; j++) + { + contact = [allContacts objectAtIndex: j]; + + // We skip lists for now + if ([[contact objectForKey: @"c_component"] isEqualToString: @"vlist"]) + continue; + + // We get the LDIF entry of our record, for easier processing + contact = [[currentFolder lookupName: [contact objectForKey: @"c_name"] inContext: context acquire: NO] ldifRecord]; + + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @"%@", [contact objectForKey: @"displayname"]]; + [s appendFormat: @"%@", [contact objectForKey: @"givenname"]]; + [s appendFormat: @"%@", [contact objectForKey: @"sn"]]; + [s appendFormat: @"%@", [contact objectForKey: @"mail"]]; + [s appendFormat: @"%@", [contact objectForKey: @"telephonenumber"]]; + [s appendFormat: @"%@", [contact objectForKey: @"o"]]; + [s appendString: @""]; + [s appendString: @""]; + total++; + } + } + + [s appendFormat: @"0-%d", total-1]; + [s appendFormat: @"%d", total]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + +// +// +// +- (NSException *) _sendMail: (NSData *) theMail + recipients: (NSArray *) theRecipients + saveInSentItems: (BOOL) saveInSentItems +{ + id authenticator; + SOGoDomainDefaults *dd; + NSException *error; + NSString *from; + + authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator]; + dd = [[context activeUser] domainDefaults]; + + // We generate the Sender + from = [[[context activeUser] allEmails] objectAtIndex: 0]; + + error = [[SOGoMailer mailerWithDomainDefaults: dd] + sendMailData: theMail + toRecipients: theRecipients + sender: from + withAuthenticator: authenticator + inContext: context]; + + if (error) + { + return error; + } + + if (saveInSentItems) + { + SOGoMailAccounts *accountsFolder; + SOGoMailAccount *accountFolder; + SOGoUserFolder *userFolder; + SOGoSentFolder *sentFolder; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + sentFolder = [accountFolder sentFolderInContext: context]; + + [sentFolder postData: theMail flags: @"seen"]; + } + + return nil; +} + +// +// +// +- (void) processSendMail: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NGMimeMessageParser *parser; + NGMimeMessage *message; + NSException *error; + NSData *data; + + // We get the mail's data + data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]; + + // We extract the recipients + parser = [[NGMimeMessageParser alloc] init]; + message = [parser parsePartFromData: data]; + RELEASE(parser); + + error = [self _sendMail: data + recipients: [message allRecipients] + saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; + + if (error) + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"FATAL ERROR occured during SendMail"]; + } +} + + + +// +// +// Examples: +// +// +// +// +// +// +// text +// +// +// +// +// +// +// "POST /SOGo/Microsoft-Server-ActiveSync?Cmd=Settings&User=sogo10&DeviceId=SEC17CD1A3E9E3F2&DeviceType=SAMSUNGSGHI317M HTTP/1.1" +// +// +// +// +// +// +// SGH-I317M +// 354422050248226 +// t0ltevl +// Android +// English +// 15147553630 +// SAMSUNG-SGH-I317M/100.40102 +// 0 +// Koodo +// +// +// +// +// We ignore everything for now +// +- (void) processSettings: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + + NSMutableString *s; + NSData *d; + + s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @""]; + [s appendString: @""]; + [s appendFormat: @" 1"]; + [s appendString: @""]; + + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; + + [theResponse setContent: d]; +} + + +// +// +// +// +// C9FF94FE-EA40-473A-B3E2-AAEE94F753A4 +// +// +// +// mail/INBOX +// 82 +// +// ... the data ... +// +// +- (void) processSmartForward: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + NSString *folderId, *itemId, *realCollectionId; + SOGoMicrosoftActiveSyncFolderType folderType; + + folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue]; + itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue]; + realCollectionId = [folderId realCollectionIdWithFolderType: &folderType]; + + if (folderType == ActiveSyncMailFolder) + { + SOGoMailAccounts *accountsFolder; + SOGoMailFolder *currentFolder; + SOGoUserFolder *userFolder; + SOGoMailObject *mailObject; + id currentCollection; + + NGMimeMessage *messageFromSmartForward, *messageToSend; + NGMimeMessageParser *parser; + NSData *data; + + NGMimeMessageGenerator *generator; + NGMimeBodyPart *bodyPart; + NGMutableHashMap *map; + NGMimeFileData *fdata; + NSException *error; + id body; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", realCollectionId] + inContext: context + acquire: NO]; + + mailObject = [currentCollection lookupName: itemId inContext: context acquire: NO]; + + + parser = [[NGMimeMessageParser alloc] init]; + data = [[[[(id)[theDocumentElement getElementsByTagName: @"MIME"] lastObject] textValue] stringByDecodingBase64] dataUsingEncoding: NSUTF8StringEncoding]; + messageFromSmartForward = [parser parsePartFromData: data]; + + RELEASE(parser); + + + // We create a new MIME multipart/mixed message. The first part will be the text part + // of our "smart forward" and the second part will be the message/rfc822 part of the + // "smart forwarded" message. + map = [NGHashMap hashMapWithDictionary: [messageFromSmartForward headers]]; + [map setObject: @"multipart/mixed" forKey: @"content-type"]; + + messageToSend = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; + body = [[[NGMimeMultipartBody alloc] initWithPart: messageToSend] autorelease]; + + // First part + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; + [map setObject: @"text/plain" forKey: @"content-type"]; + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + [bodyPart setBody: [messageFromSmartForward body]]; + [body addBodyPart: bodyPart]; + + // Second part + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; + [map setObject: @"message/rfc822" forKey: @"content-type"]; + [map setObject: @"8bit" forKey: @"content-transfer-encoding"]; + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + + data = [mailObject content]; + fdata = [[NGMimeFileData alloc] initWithBytes:[data bytes] + length:[data length]]; + + [bodyPart setBody: fdata]; + RELEASE(fdata); + [body addBodyPart: bodyPart]; + [messageToSend setBody: body]; + + generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; + data = [generator generateMimeFromPart: messageToSend]; + + error = [self _sendMail: data + recipients: [messageFromSmartForward allRecipients] + saveInSentItems: ([(id)[theDocumentElement getElementsByTagName: @"SaveInSentItems"] count] ? YES : NO)]; + + if (error) + { + [theResponse setStatus: 500]; + [theResponse appendContentString: @"FATAL ERROR occured during SmartForward"]; + } + } + else + { + // FIXME + [theResponse setStatus: 500]; + [theResponse appendContentString: @"SmartForward not-implemented on non-mail folders."]; + } +} + +// +// +// +// +// DD40B5DC-4BDF-4A6A-9D8B-4B02BE5342CD +// +// -- http://msdn.microsoft.com/en-us/library/gg663506(v=exchg.80).aspx +// +// mail/INBOX +// 82 +// +// ... the data ... +// +// +- (void) processSmartReply: (id ) theDocumentElement + inResponse: (WOResponse *) theResponse +{ + [self processSmartForward: theDocumentElement inResponse: theResponse]; +} + + + +// +// +// +- (NSException *) dispatchRequest: (id) theRequest + inResponse: (id) theResponse + context: (id) theContext +{ + id documentElement; + id builder, dom; + SEL aSelector; + + NSString *cmdName, *deviceId; + NSData *d; + + ASSIGN(context, theContext); + + // Get the device ID and "stash" it + deviceId = [[theRequest uri] deviceId]; + [context setObject: deviceId forKey: @"DeviceId"]; + + d = [[theRequest content] wbxml2xml]; + documentElement = nil; + + if (!d) + { + // We check if it's a Ping command with no body. + // See http://msdn.microsoft.com/en-us/library/ee200913(v=exchg.80).aspx for details + cmdName = [[theRequest uri] command]; + + if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame) + return [NSException exceptionWithHTTPStatus: 500]; + } + + if (d) + { + builder = [[[NSClassFromString(@"DOMSaxBuilder") alloc] init] autorelease]; + dom = [builder buildFromData: d]; + documentElement = [dom documentElement]; + + // See 2.2.2 Commands - http://msdn.microsoft.com/en-us/library/ee202197(v=exchg.80).aspx + // for all potential commands + cmdName = [NSString stringWithFormat: @"process%@:inResponse:", [documentElement tagName]]; + } + else + { + // Ping command with empty body + cmdName = [NSString stringWithFormat: @"process%@:inResponse:", cmdName]; + } + + aSelector = NSSelectorFromString(cmdName); + + [self performSelector: aSelector withObject: documentElement withObject: theResponse]; + + [theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"]; + [theResponse setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"]; + [theResponse setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"]; + [theResponse setHeader: @"2.0,2.1,2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"]; + + RELEASE(context); + + return nil; +} + +@end diff --git a/ActiveSync/SOGoMailObject+ActiveSync.h b/ActiveSync/SOGoMailObject+ActiveSync.h new file mode 100644 index 000000000..44ceec168 --- /dev/null +++ b/ActiveSync/SOGoMailObject+ActiveSync.h @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __SOGOMAILOBJECTACTIVESYNC_H__ +#define __SOGOMAILOBJECTACTIVESYNC_H__ + +#import + +@class iCalCalendar; +@class NSDictionary; +@class WOContext; + +@interface SOGoMailObject (ActiveSync) + +- (iCalCalendar *) calendarFromIMIPMessage; +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m new file mode 100644 index 000000000..191aef8c5 --- /dev/null +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -0,0 +1,638 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "SOGoMailObject+ActiveSync.h" + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import + +#include "iCalTimeZone+ActiveSync.h" +#include "NSData+ActiveSync.h" +#include "NSDate+ActiveSync.h" +#include "NSString+ActiveSync.h" + +#include +#include +#include +#include + +#include + +typedef struct { + uint32_t dwLowDateTime; + uint32_t dwHighDateTime; +} FILETIME; + +struct GlobalObjectId { + uint8_t ByteArrayID[16]; + uint8_t YH; + uint8_t YL; + uint8_t Month; + uint8_t D; + FILETIME CreationTime; + uint8_t X[8]; + uint32_t Size; + uint8_t* Data; +}; + +@implementation SOGoMailObject (ActiveSync) + +// +// +// +- (void) _setInstanceDate: (struct GlobalObjectId *) newGlobalId + fromDate: (NSCalendarDate *) instanceDate +{ + uint16_t year; + + if (instanceDate) + { + //[instanceDate setTimeZone: timeZone]; + year = [instanceDate yearOfCommonEra]; + newGlobalId->YH = year >> 8; + newGlobalId->YL = year & 0xff; + newGlobalId->Month = [instanceDate monthOfYear]; + newGlobalId->D = [instanceDate dayOfMonth]; + } +} + +// +// The GlobalObjId is documented here: http://msdn.microsoft.com/en-us/library/ee160198(v=EXCHG.80).aspx +// +- (NSData *) _computeGlobalObjectIdFromEvent: (iCalEvent *) event +{ + NSData *binPrefix, *globalObjectId, *uidAsASCII; + NSString *prefix, *uid; + + struct GlobalObjectId newGlobalId; + const char *bytes; + + prefix = @"040000008200e00074c5b7101a82e008"; + + // dataPrefix is "vCal-Uid %x01 %x00 %x00 %x00" + uint8_t dataPrefix[] = { 0x76, 0x43, 0x61, 0x6c, 0x2d, 0x55, 0x69, 0x64, 0x01, 0x00, 0x00, 0x00 }; + uid = [event uid]; + + binPrefix = [prefix convertHexStringToBytes]; + [binPrefix getBytes: &newGlobalId.ByteArrayID]; + [self _setInstanceDate: &newGlobalId + fromDate: [event recurrenceId]]; + uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding]; + bytes = [uidAsASCII bytes]; + + // 0x0c is the size of our dataPrefix + newGlobalId.Size = 0x0c + [uidAsASCII length]; + newGlobalId.Data = malloc(newGlobalId.Size * sizeof(uint8_t)); + memcpy(newGlobalId.Data, dataPrefix, 0x0c); + memcpy(newGlobalId.Data + 0x0c, bytes, newGlobalId.Size - 0x0c); + + globalObjectId = [[NSData alloc] initWithBytes: &newGlobalId length: 40 + newGlobalId.Size*sizeof(uint8_t)]; + free(newGlobalId.Data); + + return [globalObjectId autorelease]; +} + +// +// For debugging purposes... +// +- (NSString *) _uidFromGlobalObjectId: (NSData *) objectId +{ + NSString *uid; + + struct GlobalObjectId *newGlobalId; + NSUInteger length; + uint8_t *bytes; + + length = [objectId length]; + uid = nil; + + bytes = malloc(length*sizeof(uint8_t)); + [objectId getBytes: bytes length: length]; + + newGlobalId = (struct GlobalObjectId *)bytes; + + // We must take the offset (dataPrefix) into account + uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding]; + free(bytes); + + return AUTORELEASE(uid); +} + +// +// +// +- (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses +{ + NGImap4EnvelopeAddress *address; + NSMutableArray *addresses; + NSString *email, *rc; + int i, max; + + rc = nil; + max = [enveloppeAddresses count]; + + if (max > 0) + { + addresses = [NSMutableArray array]; + for (i = 0; i < max; i++) + { + address = [enveloppeAddresses objectAtIndex: i]; + email = [NSString stringWithFormat: @"\"%@\" <%@>", [address personalName], [address baseEMail]]; + + if (email) + [addresses addObject: email]; + } + rc = [addresses componentsJoinedByString: @", "]; + } + + return rc; +} + +// +// +// +- (NSData *) _preferredBodyDataInMultipartUsingType: (int) theType +{ + NSString *key, *plainKey, *htmlKey, *type, *subtype; + NSDictionary *textParts, *part; + NSEnumerator *e; + NSData *d; + + textParts = [self fetchPlainTextParts]; + e = [textParts keyEnumerator]; + plainKey = nil; + htmlKey = nil; + d = nil; + + while ((key = [e nextObject])) + { + part = [self lookupInfoForBodyPart: key]; + type = [part valueForKey: @"type"]; + subtype = [part valueForKey: @"subtype"]; + + if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"html"]) + htmlKey = key; + else if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"plain"]) + plainKey = key; + } + + if (theType == 2) + { + d = [[self fetchPlainTextParts] objectForKey: htmlKey]; + } + else if (theType == 1) + { + d = [[self fetchPlainTextParts] objectForKey: plainKey]; + } + + return d; +} + +// +// +// +- (NSData *) _preferredBodyDataUsingType: (int) theType + nativeType: (int *) theNativeType +{ + NSString *type, *subtype, *encoding; + NSData *d; + + type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString]; + subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString]; + + d = nil; + + // We determine the native type + if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"plain"]) + *theNativeType = 1; + else if ([type isEqualToString: @"text"] && [subtype isEqualToString: @"html"]) + *theNativeType = 2; + else if ([type isEqualToString: @"multipart"]) + *theNativeType = 4; + + // We get the right part based on the preference + if (theType == 1 || theType == 2) + { + if ([type isEqualToString: @"text"]) + { + d = [[self fetchPlainTextParts] objectForKey: @""]; + + // We check if we have base64 encoded parts. If so, we just + // un-encode them before using them + encoding = [[self lookupInfoForBodyPart: @""] objectForKey: @"encoding"]; + + if ([encoding caseInsensitiveCompare: @"base64"] == NSOrderedSame) + d = [d dataByDecodingBase64]; + + // Check if we must convert html->plain + if (theType == 1 && [subtype isEqualToString: @"html"]) + { + NSString *s; + + s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; + AUTORELEASE(s); + + s = [s htmlToText]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; + } + } + else if ([type isEqualToString: @"multipart"]) + { + d = [self _preferredBodyDataInMultipartUsingType: theType]; + } + } + else if (theType == 4) + { + d = [self content]; + } + + return d; +} + +// +// +// +- (iCalCalendar *) calendarFromIMIPMessage +{ + NSDictionary *part; + NSArray *parts; + int i; + + // We check if we have at least 2 parts and if one of them is a text/calendar + parts = [[self bodyStructure] objectForKey: @"parts"]; + + if ([parts count] > 1) + { + for (i = 0; i < [parts count]; i++) + { + part = [parts objectAtIndex: i]; + + if ([[part objectForKey: @"type"] isEqualToString: @"text"] && + [[part objectForKey: @"subtype"] isEqualToString: @"calendar"]) + { + id bodyPart; + + bodyPart = [self lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1] + inContext: self->context]; + + if (bodyPart) + { + iCalCalendar *calendar; + NSData *calendarData; + + calendarData = [bodyPart fetchBLOB]; + calendar = nil; + + NS_DURING + calendar = [iCalCalendar parseSingleFromSource: calendarData]; + NS_HANDLER + calendar = nil; + NS_ENDHANDLER + + return calendar; + } + } + } + } + + return nil; +} + +// +// +// +- (NSString *) activeSyncRepresentationInContext: (WOContext *) _context +{ + NSData *d, *globalObjId; + NSMutableString *s; + id value; + + iCalCalendar *calendar; + + int preferredBodyType, nativeBodyType; + + s = [NSMutableString string]; + + // To - "The value of this element contains one or more e-mail addresses. + // If there are multiple e-mail addresses, they are separated by commas." + value = [self _emailAddressesFrom: [[self envelope] to]]; + if (value) + [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; + + // From + value = [self _emailAddressesFrom: [[self envelope] from]]; + if (value) + [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; + + // Subject + value = [self decodedSubject]; + if (value) + { + [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; + [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; + } + + // DateReceived + value = [self date]; + if (value) + [s appendFormat: @"%@", [value activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // DisplayTo + [s appendFormat: @"%@", [[context activeUser] login]]; + + // Cc - same syntax as the To field + value = [self _emailAddressesFrom: [[self envelope] cc]]; + if (value) + [s appendFormat: @"%@", [value activeSyncRepresentationInContext: context]]; + + // Importance - FIXME + [s appendFormat: @"%@", @"1"]; + + // Read + [s appendFormat: @"%d", ([self read] ? 1 : 0)]; + + // We handle MeetingRequest + calendar = [self calendarFromIMIPMessage]; + + if (calendar) + { + NSString *method, *className; + iCalPerson *attendee; + iCalTimeZone *tz; + iCalEvent *event; + + iCalPersonPartStat partstat; + int v; + + event = [[calendar events] lastObject]; + method = [[event parent] method]; + + // If we are the organizer, let's pick the attendee based on the From address + if ([event userIsOrganizer: [context activeUser]]) + attendee = [event findAttendeeWithEmail: [[[[self envelope] from] lastObject] baseEMail]]; + else + attendee = [event findAttendeeWithEmail: [[[context activeUser] allEmails] objectAtIndex: 0]]; + + partstat = [attendee participationStatus]; + + // We generate the correct MessageClass + if ([method isEqualToString: @"REQUEST"]) + className = @"IPM.Schedule.Meeting.Request"; + else if ([method isEqualToString: @"REPLY"]) + { + switch (partstat) + { + case iCalPersonPartStatAccepted: + className = @"IPM.Schedule.Meeting.Resp.Pos"; + break; + case iCalPersonPartStatDeclined: + className = @"IPM.Schedule.Meeting.Resp.Neg"; + break; + case iCalPersonPartStatTentative: + case iCalPersonPartStatNeedsAction: + className = @"IPM.Schedule.Meeting.Resp.Tent"; + break; + default: + className = @"IPM.Appointment"; + NSLog(@"unhandled part stat"); + } + } + else if ([method isEqualToString: @"COUNTER"]) + className = @"IPM.Schedule.Meeting.Resp.Tent"; + else if ([method isEqualToString: @"CANCEL"]) + className = @"IPM.Schedule.Meeting.Cancelled"; + else + className = @"IPM.Appointment"; + + [s appendFormat: @"%@", className]; + + [s appendString: @""]; + + [s appendFormat: @"%d", ([event isAllDay] ? 1 : 0)]; + + // StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx + if ([event startDate]) + [s appendFormat: @"%@", [[event startDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + if ([event timeStampAsDate]) + [s appendFormat: @"%@", [[event timeStampAsDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + else if ([event created]) + [s appendFormat: @"%@", [[event created] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx + if ([event endDate]) + [s appendFormat: @"%@", [[event endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // FIXME: Single appointment - others are not supported right now + [s appendFormat: @"%d", 0]; + + // Location + if ([[event location] length]) + [s appendFormat: @"%@", [[event location] activeSyncRepresentationInContext: context]]; + + [s appendFormat: @"%@", [[[event organizer] mailAddress] activeSyncRepresentationInContext: context]]; + + // This will trigger the SendMail command. We set it to no for email invitations as + // SOGo will send emails when MeetingResponse is called. + [s appendFormat: @"%d", 0]; + + // Sensitivity + if ([[event accessClass] isEqualToString: @"PRIVATE"]) + v = 2; + if ([[event accessClass] isEqualToString: @"CONFIDENTIAL"]) + v = 3; + else + v = 0; + + [s appendFormat: @"%d", v]; + + [s appendFormat: @"%d", 2]; + + // Timezone + tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone]; + + if (!tz) + tz = [iCalTimeZone timeZoneForName: @"Europe/London"]; + + [s appendFormat: @"%@", [tz activeSyncRepresentationInContext: context]]; + + + // We disallow new time proposals + [s appendFormat: @"%d", 1]; + + // From http://blogs.msdn.com/b/exchangedev/archive/2011/07/22/working-with-meeting-requests-in-exchange-activesync.aspx: + // + // "Clients that need to determine whether the GlobalObjId element for a meeting request corresponds to an existing Calendar + // object in the Calendar folder have to convert the GlobalObjId element value to a UID element value to make the comparison." + // + globalObjId = [self _computeGlobalObjectIdFromEvent: event]; + [s appendFormat: @"%@", [globalObjId activeSyncRepresentationInContext: context]]; + + // We set the right message type - we must set AS version to 14.1 for this + [s appendFormat: @"%d", 1]; + [s appendString: @""]; + + // ContentClass + [s appendFormat: @"%@", @"urn:content-classes:calendarmessage"]; + } + else + { + // MesssageClass and ContentClass + [s appendFormat: @"%@", @"IPM.Note"]; + [s appendFormat: @"%@", @"urn:content-classes:message"]; + } + + // Reply-To - FIXME + //NSArray *replyTo = [[message objectForKey: @"envelope"] replyTo]; + //if ([replyTo count]) + // [s appendFormat: @"%@", [addressFormatter stringForArray: replyTo]]; + + // InternetCPID - 65001 == UTF-8, we use this all the time for now. + [s appendFormat: @"%@", @"65001"]; + + // Body - namespace 17 + preferredBodyType = [[context objectForKey: @"BodyPreferenceType"] intValue]; + + nativeBodyType = 1; + d = [self _preferredBodyDataUsingType: preferredBodyType nativeType: &nativeBodyType]; + + if (d) + { + NSString *content; + int len, truncated; + + content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; + + // FIXME: This is a hack. We should normally avoid doing this as we might get + // broken encodings. We should rather tell that the data was truncated and expect + // a ItemOperations call to download the whole base64 encoding multipart. + if (!content) + content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding]; + + AUTORELEASE(content); + + content = [content activeSyncRepresentationInContext: context]; + truncated = 0; + + len = [content length]; + + [s appendString: @""]; + [s appendFormat: @"%d", preferredBodyType]; + [s appendFormat: @"%d", len]; + [s appendFormat: @"%d", 0]; + if (!truncated) + [s appendFormat: @"%@", content]; + [s appendString: @""]; + } + + // Attachments -namespace 16 + NSArray *attachmentKeys = [self fetchFileAttachmentKeys]; + if ([attachmentKeys count]) + { + int i; + + [s appendString: @""]; + + for (i = 0; i < [attachmentKeys count]; i++) + { + value = [attachmentKeys objectAtIndex: i]; + + [s appendString: @""]; + [s appendFormat: @"%@", [[value objectForKey: @"filename"] activeSyncRepresentationInContext: context]]; + + // FileReference must be a unique identifier across the whole store. We use the following structure: + // mail// + // mail/INBOX/2 + [s appendFormat: @"mail/%@/%@/%@", [[[self container] relativeImap4Name] stringByEscapingURL], [self nameInContainer], [value objectForKey: @"path"]]; + + [s appendFormat: @"%d", 1]; // See: http://msdn.microsoft.com/en-us/library/ee160322(v=exchg.80).aspx + [s appendFormat: @"%d", [[value objectForKey: @"size"] intValue]]; + //[s appendFormat: @"%d", 1]; + [s appendString: @""]; + } + + [s appendString: @""]; + } + + // Flags + [s appendString: @""]; + [s appendFormat: @"%d", 0]; + [s appendString: @""]; + + // FIXME - support these in the future + //[s appendString: @"foobar"]; + //[s appendString: @"zot="]; + + // NativeBodyType -- http://msdn.microsoft.com/en-us/library/ee218276(v=exchg.80).aspx + // This is a required child element. + // 1 -> plain/text, 2 -> HTML and 3 -> RTF + if (nativeBodyType == 4) + nativeBodyType = 1; + + [s appendFormat: @"%d", nativeBodyType]; + + return s; +} + +// +// +// +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) _context +{ + id o; + + if ((o = [theValues objectForKey: @"Flag"])) + { + o = [o objectForKey: @"FlagStatus"]; + + if ([o intValue]) + [self addFlags: @"\\Flagged"]; + else + [self removeFlags: @"\\Flagged"]; + } +} + +@end diff --git a/ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m b/ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m new file mode 100644 index 000000000..9e3ccfaf4 --- /dev/null +++ b/ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m @@ -0,0 +1,101 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import + +#include +#include +#include +#include +#include + +#import + +@interface SoObjectWebDAVDispatcher (ActiveSync) + +- (id)_callObjectMethod:(NSString *)_method inContext:(WOContext *)_ctx; +- (id) doOPTIONS:(WOContext *)_ctx; + +@end + +@implementation SoObjectWebDAVDispatcher (ActiveSync) + +- (id) doOPTIONS:(WOContext *)_ctx +{ + WOResponse *response; + + /* + See example: http://msdn.microsoft.com/en-us/library/ee204257(v=exchg.80).aspx + */ + if ([[[_ctx request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) + { + response = [_ctx response]; + [response setStatus: 200]; + + [response setHeader: @"private" forKey: @"Cache-Control"]; + [response setHeader: @"OPTIONS, POST" forKey: @"Allow"]; + [response setHeader: @"14.1" forKey: @"MS-Server-ActiveSync"]; + [response setHeader: @"2.0,2.1,2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"]; + [response setHeader: @"Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"]; + [response setHeader: @"OPTIONS, POST" forKey: @"Public"]; + } + else + { + NSArray *tmp; + id result; + + /* this checks whether the object provides a specific OPTIONS method */ + if ((result = [self _callObjectMethod:@"OPTIONS" inContext:_ctx]) != nil) + return result; + + response = [_ctx response]; + [response setStatus:200 /* OK */]; + + if ((tmp = [self->object davAllowedMethodsInContext:_ctx]) != nil) + [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"allow"]; + + if ([[[_ctx request] clientCapabilities] isWebFolder]) { + /* + As described over here: + http://teyc.editthispage.com/2005/06/02 + + This page also says that: "MS-Auth-Via header is not required to work + with Web Folders". + */ + [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"public"]; + } + + if ((tmp = [self->object davComplianceClassesInContext:_ctx]) != nil) + [response setHeader:[tmp componentsJoinedByString:@", "] forKey:@"dav"]; + } + + return response; +} + +@end diff --git a/ActiveSync/common.make b/ActiveSync/common.make new file mode 100644 index 000000000..78ad58bd1 --- /dev/null +++ b/ActiveSync/common.make @@ -0,0 +1,36 @@ +# common make file for SoObject bundles + +include ../config.make +include $(GNUSTEP_MAKEFILES)/common.make +include ../Version + +NEEDS_GUI=no +BUNDLE_EXTENSION = .SOGo +BUNDLE_INSTALL_DIR = $(SOGO_LIBDIR) +WOBUNDLE_EXTENSION = $(BUNDLE_EXTENSION) +WOBUNDLE_INSTALL_DIR = $(BUNDLE_INSTALL_DIR) + +# SYSTEM_LIB_DIR += -L/usr/local/lib -L/usr/lib + +ADDITIONAL_INCLUDE_DIRS += \ + -I.. \ + -I../.. \ + -I../../SOPE + +ADDITIONAL_LIB_DIRS += \ + -L../SOGo/SOGo.framework/ \ + -L../../SOGo/$(GNUSTEP_OBJ_DIR)/ \ + -L../../OGoContentStore/$(GNUSTEP_OBJ_DIR)/ \ + -L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \ + -L/usr/local/lib + +BUNDLE_LIBS += \ + -lSOGo \ + -lGDLContentStore \ + -lGDLAccess \ + -lNGObjWeb \ + -lNGCards -lNGMime -lNGLdap \ + -lNGStreams -lNGExtensions -lEOControl \ + -lDOM -lSaxObjC -lSBJson + +ADDITIONAL_BUNDLE_LIBS += $(BUNDLE_LIBS) diff --git a/ActiveSync/iCalEvent+ActiveSync.h b/ActiveSync/iCalEvent+ActiveSync.h new file mode 100644 index 000000000..90fa92cd5 --- /dev/null +++ b/ActiveSync/iCalEvent+ActiveSync.h @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef __ICALEVENTACTIVESYNC_H__ +#define __ICALEVENTACTIVESYNC_H__ + +#import + +@class NSString; +@class WOContext; + +@interface iCalEvent (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/iCalEvent+ActiveSync.m b/ActiveSync/iCalEvent+ActiveSync.m new file mode 100644 index 000000000..05f18a3ce --- /dev/null +++ b/ActiveSync/iCalEvent+ActiveSync.m @@ -0,0 +1,471 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#import "iCalEvent+ActiveSync.h" + +#import +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import +#import + +#import +#import + +#import + +#include "iCalRecurrenceRule+ActiveSync.h" +#include "iCalTimeZone+ActiveSync.h" +#include "NSDate+ActiveSync.h" +#include "NSString+ActiveSync.h" + +@implementation iCalEvent (ActiveSync) + +- (int) _attendeeStatus: (iCalPerson *) attendee +{ + int attendee_status; + + attendee_status = 5; + if ([[attendee partStat] caseInsensitiveCompare: @"ACCEPTED"] == NSOrderedSame) + attendee_status = 3; + else if ([[attendee partStat] caseInsensitiveCompare: @"DECLINED"] == NSOrderedSame) + attendee_status = 4; + else if ([[attendee partStat] caseInsensitiveCompare: @"TENTATIVE"] == NSOrderedSame) + attendee_status = 2; + + return attendee_status; +} + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + NSMutableString *s; + NSArray *attendees; + + iCalPerson *organizer, *attendee; + iCalTimeZone *tz; + id o; + + int v; + + s = [NSMutableString string]; + + [s appendFormat: @"%d", ([self isAllDay] ? 1 : 0)]; + + // DTStamp -- http://msdn.microsoft.com/en-us/library/ee219470(v=exchg.80).aspx + if ([self timeStampAsDate]) + [s appendFormat: @"%@", [[self timeStampAsDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + else if ([self created]) + [s appendFormat: @"%@", [[self created] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx + if ([self startDate]) + [s appendFormat: @"%@", [[self startDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx + if ([self endDate]) + [s appendFormat: @"%@", [[self endDate] activeSyncRepresentationWithoutSeparatorsInContext: context]]; + + // Timezone + tz = [(iCalDateTime *)[self firstChildWithTag: @"dtstart"] timeZone]; + + if (!tz) + tz = [iCalTimeZone timeZoneForName: @"Europe/London"]; + + [s appendFormat: @"%@", [tz activeSyncRepresentationInContext: context]]; + + // Organizer and other invitations related properties + if ((organizer = [self organizer])) + { + o = [organizer rfc822Email]; + if ([o length]) + { + [s appendFormat: @"%@", o]; + + o = [organizer cn]; + if ([o length]) + [s appendFormat: @"%@", o]; + } + } + + // Attendees + attendees = [self attendees]; + + if ([attendees count]) + { + int i, attendee_status, attendee_type; + + [s appendString: @""]; + + for (i = 0; i < [attendees count]; i++) + { + [s appendString: @""]; + + attendee = [attendees objectAtIndex: i]; + [s appendFormat: @"%@", [attendee rfc822Email]]; + [s appendFormat: @"%@", [attendee cn]]; + + attendee_status = [self _attendeeStatus: attendee]; + + [s appendFormat: @"%d", attendee_status]; + + // FIXME: handle resource + if ([[attendee role] caseInsensitiveCompare: @"REQ-PARTICIPANT"] == NSOrderedSame) + attendee_type = 1; + else + attendee_type = 2; + + [s appendFormat: @"%d", attendee_type]; + [s appendString: @""]; + } + [s appendString: @""]; + } + + // This depends on the 'NEEDS-ACTION' parameter. + // This will trigger the SendMail command + if ([self userIsAttendee: [context activeUser]]) + { + iCalPerson *attendee; + + int attendee_status; + + attendee = [self userAsAttendee: [context activeUser]]; + attendee_status = [self _attendeeStatus: attendee]; + + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", attendee_status]; + [s appendFormat: @"%d", 3]; + [s appendFormat: @"%d", 1]; + + // BusyStatus -- http://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx + [s appendFormat: @"%d", 2]; + } + + // Subject -- http://msdn.microsoft.com/en-us/library/ee157192(v=exchg.80).aspx + if ([[self summary] length]) + [s appendFormat: @"%@", [[self summary] activeSyncRepresentationInContext: context]]; + + // Location + if ([[self location] length]) + [s appendFormat: @"%@", [[self location] activeSyncRepresentationInContext: context]]; + + // Importance - NOT SUPPORTED - DO NOT ENABLE + //o = [self priority]; + //if ([o isEqualToString: @"9"]) + // v = 0; + //else if ([o isEqualToString: @"1"]) + // v = 2; + //else + // v = 1; + //[s appendFormat: @"%d", v]; + + // UID -- http://msdn.microsoft.com/en-us/library/ee159919(v=exchg.80).aspx + if ([[self uid] length]) + [s appendFormat: @"%@", [self uid]]; + + // Sensitivity + if ([[self accessClass] isEqualToString: @"PRIVATE"]) + v = 2; + if ([[self accessClass] isEqualToString: @"CONFIDENTIAL"]) + v = 3; + else + v = 0; + + [s appendFormat: @"%d", v]; + + // Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx + // TODO + + // Recurrence rules + if ([self isRecurrent]) + { + [s appendString: [[[self recurrenceRules] lastObject] activeSyncRepresentationInContext: context]]; + } + + // Comment + o = [self comment]; + if ([o length]) + { + o = [o activeSyncRepresentationInContext: context]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [o length]]; + [s appendFormat: @"%d", 0]; + [s appendFormat: @"%@", o]; + [s appendString: @""]; + } + + [s appendFormat: @"%d", 1]; + + return s; +} + +// +// To understand meeting requests/responses, see: +// +// http://blogs.msdn.com/b/exchangedev/archive/2011/07/22/working-with-meeting-requests-in-exchange-activesync.aspx +// http://blogs.msdn.com/b/exchangedev/archive/2011/07/29/working-with-meeting-responses-in-exchange-activesync.aspx +// +// +// Here is an example of a Sync call when sogo10 accepts an invitation from sogo3: +// +// +// 2978-52EA9D00-1-A253E70.ics +// +// LAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w== +// 0 +// 20140207T130000Z +// 20140207T140000Z +// 20140130T185245Z +// test 8 +// 0 +// +// 1 +// +// +// sogo3@example.com +// 2978-52EA9D00-1-A253E70 +// +// +// sogo10 +// sogo10@example.com +// 1 +// +// +// 2 +// 3 +// Wolfgang Fritz +// +// +// +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context +{ + iCalDateTime *start, *end; + NSTimeZone *userTimeZone; + iCalTimeZone *tz; + id o; + + NSInteger tzOffset; + BOOL isAllDay; + + if ((o = [theValues objectForKey: @"UID"])) + [self setUid: o]; + + // FIXME: merge with iCalToDo + if ((o = [theValues objectForKey: @"Subject"])) + [self setSummary: o]; + + isAllDay = NO; + if ([[theValues objectForKey: @"AllDayEvent"] intValue]) + { + isAllDay = YES; + } + + // + // 0- free, 1- tentative, 2- busy and 3- out of office + // + if ((o = [theValues objectForKey: @"BusyStatus"])) + { + [o intValue]; + } + + // + // + // + if ((o = [theValues objectForKey: @"MeetingStatus"])) + { + [o intValue]; + } + + // + // 0- normal, 1- personal, 2- private and 3-confidential + // + if ((o = [theValues objectForKey: @"Sensitivy"])) + { + switch ([o intValue]) + { + case 2: + [self setAccessClass: @"PRIVATE"]; + break; + case 3: + [self setAccessClass: @"CONFIDENTIAL"]; + break; + case 0: + case 1: + default: + [self setAccessClass: @"PUBLIC"]; + } + } + + if ((o = [theValues objectForKey: @"TimeZone"])) + { + // Ugh, we ignore it for now. + userTimeZone = [[[context activeUser] userDefaults] timeZone]; + tz = [iCalTimeZone timeZoneForName: [userTimeZone name]]; + [(iCalCalendar *) parent addTimeZone: tz]; + } + + // FIXME: merge with iCalToDo + if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) + [self setComment: o]; + + if ((o = [theValues objectForKey: @"Location"])) + [self setLocation: o]; + + if ((o = [theValues objectForKey: @"StartTime"])) + { + o = [o calendarDate]; + start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"]; + [start setTimeZone: tz]; + + if (isAllDay) + { + [start setDate: o]; + [start setTimeZone: nil]; + } + else + { + tzOffset = [userTimeZone secondsFromGMTForDate: o]; + o = [o dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + [start setDateTime: o]; + } + } + + if ((o = [theValues objectForKey: @"EndTime"])) + { + o = [o calendarDate]; + end = (iCalDateTime *) [self uniqueChildWithTag: @"dtend"]; + [end setTimeZone: tz]; + + if (isAllDay) + { + [end setDate: o]; + [end setTimeZone: nil]; + } + else + { + tzOffset = [userTimeZone secondsFromGMTForDate: o]; + o = [o dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + [end setDateTime: o]; + } + } + + // Recurrence + if ((o = [theValues objectForKey: @"Recurrence"])) + { + iCalRecurrenceRule *rule; + + rule = [[iCalRecurrenceRule alloc] init]; + [self setRecurrenceRules: [NSArray arrayWithObject: rule]]; + RELEASE(rule); + + [rule takeActiveSyncValues: o inContext: context]; + } + + // Organizer - we don't touch the value unless we're the organizer + if ((o = [theValues objectForKey: @"Organizer_Email"]) && + ([self userIsOrganizer: [context activeUser]] || [[context activeUser] hasEmail: o])) + { + iCalPerson *person; + + person = [iCalPerson elementWithTag: @"organizer"]; + [person setEmail: o]; + [person setCn: [theValues objectForKey: @"Organizer_Name"]]; + [person setPartStat: @"ACCEPTED"]; + [self setOrganizer: person]; + } + + // Attendees - we don't touch the values if we're an attendee. This is gonna + // be done automatically by the ActiveSync client when invoking MeetingResponse. + if (![self userIsAttendee: [context activeUser]]) + { + if ((o = [theValues objectForKey: @"Attendees"])) + { + NSMutableArray *attendees; + NSDictionary *attendee; + iCalPerson *person; + int status, i; + + attendees = [NSMutableArray array]; + + for (i = 0; i < [o count]; i++) + { + // Each attendee has is a dictionary similar to this: + // { "Attendee_Email" = "sogo3@example.com"; "Attendee_Name" = "Wolfgang Fritz"; "Attendee_Status" = 5; "Attendee_Type" = 1; } + attendee = [o objectAtIndex: i]; + + person = [iCalPerson elementWithTag: @"attendee"]; + [person setCn: [attendee objectForKey: @"Attendee_Name"]]; + [person setEmail: [attendee objectForKey: @"Attendee_Email"]]; + + status = [[attendee objectForKey: @"Attendee_Status"] intValue]; + + switch (status) + { + case 2: + [person setPartStat: @"TENTATIVE"]; + break; + case 3: + [person setPartStat: @"ACCEPTED"]; + break; + case 4: + [person setPartStat: @"DECLINED"]; + break; + case 0: + case 5: + default: + [person setPartStat: @"NEEDS-ACTION"]; + break; + } + + // FIXME: handle Attendee_Type + + [attendees addObject: person]; + } + + [self setAttendees: attendees]; + } + } +} + +@end diff --git a/ActiveSync/iCalRecurrenceRule+ActiveSync.h b/ActiveSync/iCalRecurrenceRule+ActiveSync.h new file mode 100644 index 000000000..1de1a5316 --- /dev/null +++ b/ActiveSync/iCalRecurrenceRule+ActiveSync.h @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef __ICALRECURRENCERULEACTIVESYNC_H__ +#define __ICALRECURRENCERULEACTIVESYNC_H__ + +#import + +@class NSDictionary; +@class NSString; +@class WOContext; + +@interface iCalRecurrenceRule (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext:(WOContext *) context; +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/iCalRecurrenceRule+ActiveSync.m b/ActiveSync/iCalRecurrenceRule+ActiveSync.m new file mode 100644 index 000000000..18bdf595c --- /dev/null +++ b/ActiveSync/iCalRecurrenceRule+ActiveSync.m @@ -0,0 +1,355 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#import "iCalRecurrenceRule+ActiveSync.h" + +#import +#import +#import + +#import + +#import "NSCalendarDate+ActiveSync.h" +#import "NSDate+ActiveSync.h" + +@implementation iCalRecurrenceRule (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + NSMutableString *s; + int type; + + s = [NSMutableString string]; + + [s appendString: @""]; + + // 0 -> daily, 1 -> weekly, 2 -> montly, 5 -> yearly + type = 0; + + if ([self frequency] == iCalRecurrenceFrequenceDaily) + { + type = 0; + + // 1 -> sunday, 2 -> monday, 4 -> tuesday, 8 -> wednesday, 16 -> thursday, 32 -> friday, 64 -> saturday, 127 -> last day of month (montly or yearl recurrences only) + if ([[self byDayMask] isWeekDays]) + { + [s appendFormat: @"%d", (2|4|8|16|32)]; + } + else + { + [s appendFormat: @"%d", [self repeatInterval]]; + } + } + else if ([self frequency] == iCalRecurrenceFrequenceWeekly) + { + iCalWeekOccurrences *occurrences; + int i, v; + + type = 1; + + occurrences = [[self byDayMask] weekDayOccurrences]; + v = 0; + + for (i = 0; i < 7; i++) + { + if (occurrences[i]) + v += (1 << i); + } + + [s appendFormat: @"%d", v]; + [s appendFormat: @"%d", [self repeatInterval]]; + } + else if ([self frequency] == iCalRecurrenceFrequenceMonthly) + { + if ([[self byDay] length]) + { + int firstOccurrence; + iCalByDayMask *dm; + + type = 3; // recurs monthly on the nth day + dm = [self byDayMask]; + firstOccurrence = [dayMask firstOccurrence]; + + // Handle the case for "Last day of the month" + if (firstOccurrence < 0) + firstOccurrence = 5; + + [s appendFormat: @"%d", (1 << [dm firstDay])]; + [s appendFormat: @"%d", firstOccurrence]; + } + else if ([[self byMonthDay] count]) + { + NSArray *days; + + type = 2; // recurs monthly + days = [self byMonthDay]; + if ([days count] > 0 && [[days objectAtIndex: 0] intValue] < 0) + { + // Last day of the month + iCalByDayMask *dm; + + dm = [self byDayMask]; + [s appendFormat: @"%d", (1 << [dm firstDay])]; + [s appendFormat: @"%d", 5]; + } + else + { + // Unsupported rule in ActiveSync/Outlook. Rule that says "Repeat on the 7th and 8th of each month". + // FIXME + } + } + } + else if ([self frequency] == iCalRecurrenceFrequenceYearly) + { + type = 6; // Yearly on the nth day + + if ([[self flattenedValuesForKey: @"bymonth"] length]) + { + if ([[self byDay] length]) + { + int firstOccurrence; + iCalByDayMask *dm; + + dm = [self byDayMask]; + firstOccurrence = [dm firstOccurrence]; + if (firstOccurrence < 0) + firstOccurrence = 5; + + [s appendFormat: @"%d", (1 << [dm firstDay])]; + [s appendFormat: @"%d", firstOccurrence]; + [s appendFormat: @"%@", + [self flattenedValuesForKey: @"bymonth"]]; + } + else + { + type = 5; // Yearly + + [s appendFormat: @"%@", + [self flattenedValuesForKey: @"bymonthday"]]; + + [s appendFormat: @"%@", + [self flattenedValuesForKey: @"bymonth"]]; + } + } + else + type = 5; + } + + [s appendFormat: @"%d", type]; + + // Occurrences / Until + //[s appendFormat: @"%d", 5]; + if ([self repeatCount]) + { + [s appendFormat: @"%@", + [self flattenedValuesForKey: @"count"]]; + } + else if ([self untilDate]) + { + NSCalendarDate *date; + + date = [self untilDate]; + //ud = [[context activeUser] userDefaults]; + //[date setTimeZone: [ud timeZone]]; + + [s appendFormat: @"%@", + [date activeSyncRepresentationWithoutSeparatorsInContext: context]]; + } + + + [s appendString: @""]; + + return s; +} + +// +// +// +- (void) _setByDayFromValues: (NSDictionary *) theValues +{ + NSString *day; + id o; + + unsigned int day_of_week; + int i, week_of_month; + + o = [theValues objectForKey: @"Recurrence_DayOfWeek"]; + + // The documentation says WeekOfMonth must be between 1 and 5. The value + // 5 means "last week of month" + week_of_month = [[theValues objectForKey: @"Recurrence_WeekOfMonth"] intValue]; + + if (week_of_month > 4) + week_of_month = -1; + + // We find the correct day of the week + day_of_week = [o intValue]; + + for (i = 0; i < 7; i++) + { + if ((1< BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __ICALTIMEZONEACTIVESYNC_H__ +#define __ICALTIMEZONEACTIVESYNC_H__ + +#import + +@class NSString; +@class WOContext; + +@interface iCalTimeZone (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/iCalTimeZone+ActiveSync.m b/ActiveSync/iCalTimeZone+ActiveSync.m new file mode 100644 index 000000000..0670acc11 --- /dev/null +++ b/ActiveSync/iCalTimeZone+ActiveSync.m @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "iCalTimeZone+ActiveSync.h" + +#include +#include +#include +#include +#include + +#import +#import +#import + +#import + +#include "NSData+ActiveSync.h" + +struct SYSTEMTIME { + uint16_t wYear; + uint16_t wMonth; + uint16_t wDayOfWeek; + uint16_t wDay; + uint16_t wHour; + uint16_t wMinute; + uint16_t wSecond; + uint16_t wMilliseconds; +}; + +@interface iCalTimeZonePeriod (ActiveSync) + +- (void) _fillTZDate: (struct SYSTEMTIME *) tzData; + +@end + +@implementation iCalTimeZonePeriod (ActiveSync) + +// +// FIXME - combine with iCalTimeZone+MAPIStore.m +// +- (void) _fillTZDate: (struct SYSTEMTIME *) tzData +{ + iCalRecurrenceRule *rrule; + NSArray *byMonth; + iCalByDayMask *mask; + NSCalendarDate *dateValue; + + rrule = [self recurrenceRule]; + byMonth = [rrule byMonth]; + if ([byMonth count] > 0) + { + tzData->wMonth = [[byMonth objectAtIndex: 0] intValue]; + mask = [rrule byDayMask]; + tzData->wDayOfWeek = [mask firstDay]; + tzData->wDay = [mask firstOccurrence]; + + dateValue = [self startDate]; + tzData->wHour = [dateValue hourOfDay]; + tzData->wMinute = [dateValue minuteOfHour]; + tzData->wSecond = [dateValue secondOfMinute]; + } +} + +@end + +@implementation iCalTimeZone (ActiveSync) + +// +// FIXME - combine with iCalTimeZone+MAPIStore.m +// +- (iCalTimeZonePeriod *) _mostRecentPeriodWithName: (NSString *) periodName +{ + NSArray *periods; + iCalTimeZonePeriod *period; + NSUInteger max; + + periods = [self childrenWithTag: periodName]; + max = [periods count]; + if (max > 0) + { + periods = [periods sortedArrayUsingSelector: @selector (compare:)]; + period = (iCalTimeZonePeriod *) [periods objectAtIndex: (max - 1)]; + } + else + period = nil; + + return period; +} + + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + iCalTimeZonePeriod *period; + NSMutableData *bytes; + + uint32_t lBias; + uint32_t lStandardBias; + uint32_t lDaylightBias; + //uint16_t wStandardYear; + struct SYSTEMTIME stStandardDate; + //uint16_t wDaylightYear; + struct SYSTEMTIME stDaylightDate; + + char standardName[64], daylightName[64]; + + bytes = [NSMutableData data]; + + memset(standardName, 0, 64); + memset(daylightName, 0, 64); + lStandardBias = 0; + + period = [self _mostRecentPeriodWithName: @"STANDARD"]; + lBias = -[period secondsOffsetFromGMT] / 60; + [period _fillTZDate: &stStandardDate]; + + period = [self _mostRecentPeriodWithName: @"DAYLIGHT"]; + if (!period) + stStandardDate.wMonth = 0; + + lDaylightBias = (uint32_t) -([period secondsOffsetFromGMT] / 60) - lBias; + [period _fillTZDate: &stDaylightDate]; + //wStandardYear = stStandardDate.wYear; + //wDaylightYear = stDaylightDate.wYear; + + + // We build the timezone + [bytes appendBytes: &lBias length: 4]; + [bytes appendBytes: standardName length: 64]; + [bytes appendBytes: &stStandardDate length: 16]; + [bytes appendBytes: &lStandardBias length: 4]; + [bytes appendBytes: daylightName length: 64]; + [bytes appendBytes: &stDaylightDate length: 16]; + [bytes appendBytes: &lDaylightBias length: 4]; + + return [bytes activeSyncRepresentationInContext: context]; +} + +@end diff --git a/ActiveSync/iCalToDo+ActiveSync.h b/ActiveSync/iCalToDo+ActiveSync.h new file mode 100644 index 000000000..933f26eb7 --- /dev/null +++ b/ActiveSync/iCalToDo+ActiveSync.h @@ -0,0 +1,46 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __ICALTODOACTIVESYNC_H__ +#define __ICALTODOACTIVESYNC_H__ + +#import + +@class NSString; +@class WOContext; + +@interface iCalToDo (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context; +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context; + +@end + +#endif diff --git a/ActiveSync/iCalToDo+ActiveSync.m b/ActiveSync/iCalToDo+ActiveSync.m new file mode 100644 index 000000000..d09fda688 --- /dev/null +++ b/ActiveSync/iCalToDo+ActiveSync.m @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "iCalToDo+ActiveSync.h" + +#import +#import +#import +#import +#import + +#import +#import +#import + +#import +#import + +#import +#import +#import + +#include "NSDate+ActiveSync.h" +#include "NSString+ActiveSync.h" + +@implementation iCalToDo (ActiveSync) + +- (NSString *) activeSyncRepresentationInContext: (WOContext *) context +{ + NSMutableString *s; + id o; + + int v; + + s = [NSMutableString string]; + + // Complete + o = [self completed]; + [s appendFormat: @"%d", (o ? 1 : 0)]; + + // DateCompleted + if (o) + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + + // Start date + if ((o = [self startDate])) + { + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + } + + + // Due date + if ((o = [self due])) + { + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + [s appendFormat: @"%@", [o activeSyncRepresentationInContext: context]]; + } + + // Importance + o = [self priority]; + if ([o isEqualToString: @"9"]) + v = 0; + else if ([o isEqualToString: @"1"]) + v = 2; + else + v = 1; + [s appendFormat: @"%d", v]; + + // Reminder - FIXME + [s appendFormat: @"%d", 0]; + + // Sensitivity - FIXME + [s appendFormat: @"%d", 0]; + + // Subject + o = [self summary]; + if ([o length]) + [s appendFormat: @"%@", [[self summary] activeSyncRepresentationInContext: context]]; + + if ((o = [self comment])) + { + o = [o activeSyncRepresentationInContext: context]; + [s appendString: @""]; + [s appendFormat: @"%d", 1]; + [s appendFormat: @"%d", [o length]]; + [s appendFormat: @"%d", 0]; + [s appendFormat: @"%@", o]; + [s appendString: @""]; + } + + return s; +} + +- (void) takeActiveSyncValues: (NSDictionary *) theValues + inContext: (WOContext *) context +{ + NSTimeZone *userTimeZone; + iCalTimeZone *tz; + id o; + + NSInteger tzOffset; + + userTimeZone = [[[context activeUser] userDefaults] timeZone]; + tz = [iCalTimeZone timeZoneForName: [userTimeZone name]]; + [(iCalCalendar *) parent addTimeZone: tz]; + + // FIXME: merge with iCalEvent + if ((o = [theValues objectForKey: @"Subject"])) + [self setSummary: o]; + + // FIXME: merge with iCalEvent + if ((o = [[theValues objectForKey: @"Body"] objectForKey: @"Data"])) + [self setComment: o]; + + + if ([[theValues objectForKey: @"Complete"] intValue] && + ((o = [theValues objectForKey: @"DateCompleted"])) ) + { + iCalDateTime *completed; + + o = [o calendarDate]; + completed = (iCalDateTime *) [self uniqueChildWithTag: @"completed"]; + //tzOffset = [[o timeZone] secondsFromGMTForDate: o]; + //o = [o dateByAddingYears: 0 months: 0 days: 0 + // hours: 0 minutes: 0 + // seconds: -tzOffset]; + [completed setDate: o]; + [self setStatus: @"COMPLETED"]; + } + + if ((o = [theValues objectForKey: @"DueDate"])) + { + iCalDateTime *due; + + + o = [o calendarDate]; + due = (iCalDateTime *) [self uniqueChildWithTag: @"due"]; + [due setTimeZone: tz]; + + tzOffset = [userTimeZone secondsFromGMTForDate: o]; + o = [o dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + [due setDateTime: o]; + } + + // 2 == high, 1 == normal, 0 == low + if ((o = [theValues objectForKey: @"Importance"])) + { + if ([o intValue] == 2) + [self setPriority: @"1"]; + else if ([o intValue] == 1) + [self setPriority: @"5"]; + else + [self setPriority: @"9"]; + } + + + if ((o = [theValues objectForKey: @"ReminderTime"])) + { + + } +} + +@end diff --git a/ActiveSync/product.plist b/ActiveSync/product.plist new file mode 100644 index 000000000..5e5eadabc --- /dev/null +++ b/ActiveSync/product.plist @@ -0,0 +1,3 @@ +{ + requires = ( MAIN, Appointments, Contacts, Mailer ); +} \ No newline at end of file diff --git a/Apache/SOGo.conf b/Apache/SOGo.conf index e065c3240..f36fbbe8f 100644 --- a/Apache/SOGo.conf +++ b/Apache/SOGo.conf @@ -2,8 +2,6 @@ Alias /SOGo.woa/WebServerResources/ \ /usr/lib/GNUstep/SOGo/WebServerResources/ Alias /SOGo/WebServerResources/ \ /usr/lib/GNUstep/SOGo/WebServerResources/ -AliasMatch /SOGo/so/ControlPanel/Products/(.*)/Resources/(.*) \ - /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2 AllowOverride None @@ -18,10 +16,6 @@ AliasMatch /SOGo/so/ControlPanel/Products/(.*)/Resources/(.*) \ - - SetHandler default-handler - - ## Uncomment the following to enable proxy-side authentication, you will then ## need to set the "SOGoTrustProxyAuthentication" SOGo user default to YES and ## adjust the "x-webobjects-remote-user" proxy header in the "Proxy" section @@ -48,6 +42,14 @@ ProxyPreserveHost On ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0 +# Enable to use Microsoft ActiveSync support +# Note that you MUST have many sogod workers to use ActiveSync. +# See the SOGo Installation and Configuration guide for more details. +# +#ProxyPass /Microsoft-Server-ActiveSync \ +# http://127.0.0.1:20000/SOGo/Microsoft-Server-ActiveSync \ +# retry=60 connectiontimeout=5 timeout=360 + ## adjust the following to your configuration RequestHeader set "x-webobjects-server-port" "443" @@ -66,7 +68,7 @@ ProxyPass /SOGo http://127.0.0.1:20000/SOGo retry=0 Allow from all -# For apple autoconfiguration +# For Apple autoconfiguration RewriteEngine On RewriteRule ^/.well-known/caldav/?$ /SOGo/dav [R=301] diff --git a/ChangeLog b/ChangeLog index 393bf3486..7a29bd637 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,2366 @@ +commit 5f2920db9b1c7fd89af8019d8a007bfddb20a742 +Author: Francis Lachapelle +Date: Mon Feb 24 16:47:41 2014 -0500 + + Update translations + +M NEWS +M UI/Common/French.lproj/Localizable.strings +M UI/Common/Polish.lproj/Localizable.strings +M UI/MailerUI/Polish.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/German.lproj/Localizable.strings +M UI/PreferencesUI/Polish.lproj/Localizable.strings +M UI/PreferencesUI/Russian.lproj/Localizable.strings +M UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings + +commit 761a7e2be11aed9cdafe7cd76e52fab19bc5dbd5 +Author: Francis Lachapelle +Date: Mon Feb 24 16:42:59 2014 -0500 + + Update Microsoft Outlook Configuration guide + +M Documentation/SOGo Native Microsoft Outlook Configuration.odt + +commit 9ed1d57ec2c22a3382e02caaa4f13937e6863b11 +Author: Francis Lachapelle +Date: Mon Feb 24 16:07:55 2014 -0500 + + Fix auto-acceptation of resources as attendees + + Fixes #2541 + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 2b6428efd084f7b09833cd7b854a737feb9f0f2d +Author: Francis Lachapelle +Date: Mon Feb 24 07:55:21 2014 -0500 + + Always check resource conflict for new events + + Fixes #2541 + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit be36e0ceea2e2a9c6aa1099a7d854a65a7b75765 +Author: Francis Lachapelle +Date: Fri Feb 21 20:35:54 2014 -0500 + + Fix view changes in calendar module + + Fixes #2613 + +M NEWS +M UI/WebServerResources/SchedulerUI.js + +commit 070d3dd825fb81c5c05e8f2bbd760a1ac438a81c +Author: Francis Lachapelle +Date: Fri Feb 21 15:48:41 2014 -0500 + + Extract node value with 'textValue' + +M ActiveSync/NGDOMElement+ActiveSync.m + +commit 96f2552b45cd788b784afc349af96ca79c1bff8a +Author: Francis Lachapelle +Date: Wed Feb 19 21:57:01 2014 -0500 + + Preparation for release 2.2.0 + +M Apache/SOGo.conf +M Documentation/SOGo Installation Guide.odt +M Documentation/SOGo Mobile Devices Configuration.odt +M Documentation/SOGo Mozilla Thunderbird Configuration.odt +M Documentation/SOGo Native Microsoft Outlook Configuration.odt +M Documentation/architecture.png +M Documentation/openchange.png +M NEWS +M Scripts/updates.php +M Version + +commit 2948920e0b68057cb14b2b26dada0dcd8aae51c1 +Author: Francis Lachapelle +Date: Tue Feb 18 16:13:40 2014 -0500 + + Improve display of contact + + Fixes #2350 + +M NEWS +M UI/WebServerResources/ContactsUI.css + +commit 212d7149ed20175a4f047fce88bcd760ff6de9c9 +Author: Francis Lachapelle +Date: Tue Feb 18 10:13:34 2014 -0500 + + Fix display of a contact's birthday + + Fixes #2503 + +M NEWS +M SoObjects/Contacts/NGVCard+SOGo.m + +commit 668c7e8b4948ddac12ffbe6a6026af1db2ea2eac +Author: Ludovic Marcotte +Date: Mon Feb 17 16:08:29 2014 -0500 + + Correctly handle email invitations as attendee/organizer + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 9851c32a1e5d5d584b350a22605a12b800fa0634 +Author: Ludovic Marcotte +Date: Mon Feb 17 15:49:22 2014 -0500 + + Fixed the handling of organizers + +M ActiveSync/iCalEvent+ActiveSync.m + +commit e7f27427c87498185d95e6a4c0607175c46b5fc0 +Author: Francis Lachapelle +Date: Mon Feb 17 15:42:55 2014 -0500 + + Add missing import in UIxPreferences.m + +M UI/PreferencesUI/UIxPreferences.m + +commit f0a29d3ca27a9166dd789288d3f2064c809ba1ae +Author: Francis Lachapelle +Date: Mon Feb 17 15:05:31 2014 -0500 + + Fix debian dependencies for sogo-activesync + +M packaging/debian-multiarch/control +M packaging/debian/control + +commit ec0a5ac4bfe65a1aecf225830c16f6c7e1d7cb22 +Author: Ludovic Marcotte +Date: Mon Feb 17 11:30:41 2014 -0500 + + Properly handle event updates/pull when we are or not an attendee/organizer + +M ActiveSync/iCalEvent+ActiveSync.m + +commit 4ca8b9d0aa0eb276cedd53f566f0ea1767f10ba6 +Author: Ludovic Marcotte +Date: Mon Feb 17 11:30:00 2014 -0500 + + Copyright updates + +M SoObjects/Appointments/iCalEntityObject+SOGo.h +M SoObjects/Appointments/iCalEntityObject+SOGo.m + +commit d560d0e4703cef7bfa0cb63e2ebb5553de0fc06f +Author: Ludovic Marcotte +Date: Mon Feb 17 11:28:06 2014 -0500 + + Always return a response avoiding iOS crashes on no-changes + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 401320e08be253770d783be6f55af7e4b9f46341 +Author: Francis Lachapelle +Date: Mon Feb 17 11:12:09 2014 -0500 + + Contact: fix display of urls + +M UI/Contacts/UIxContactView.m + +commit 8083b41092d672d620e3826e66717d974925403d +Author: Ludovic Marcotte +Date: Mon Feb 17 10:01:44 2014 -0500 + + Added the context everywhere + +M ActiveSync/NGVCard+ActiveSync.h +M ActiveSync/NGVCard+ActiveSync.m +M ActiveSync/NSData+ActiveSync.h +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/NSDate+ActiveSync.h +M ActiveSync/NSDate+ActiveSync.m +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.h +M ActiveSync/SOGoMailObject+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.h +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalRecurrenceRule+ActiveSync.h +M ActiveSync/iCalRecurrenceRule+ActiveSync.m +M ActiveSync/iCalTimeZone+ActiveSync.h +M ActiveSync/iCalTimeZone+ActiveSync.m +M ActiveSync/iCalToDo+ActiveSync.h +M ActiveSync/iCalToDo+ActiveSync.m + +commit 9218c7f253ae94e3b5cfc960b28612c764aec671 +Author: Ludovic Marcotte +Date: Mon Feb 17 08:46:05 2014 -0500 + + Added missing recurrences support, improved MeetingResponse, added WindowSize support. + + Also more bug fixes regarding event invitations, and ServerId handling for + calendar objects. + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalRecurrenceRule+ActiveSync.m +M ActiveSync/iCalTimeZone+ActiveSync.m + +commit 4d1fdb33f586ae9b06a4e282fb51fba38ad583fc +Author: Ludovic Marcotte +Date: Mon Feb 17 08:42:35 2014 -0500 + + Deleted wrongly added file + +D SoObjects/Mailer/SOGoMailFolder.m.orig + +commit 23565627784460fddbfad09b4173dd1f38a894a9 +Author: Ludovic Marcotte +Date: Mon Feb 17 08:41:12 2014 -0500 + + Added method to get synctag starting from an other one. + + We also no longer fetch vanished items of modseq == 0 + +M SoObjects/Mailer/SOGoMailFolder.h +M SoObjects/Mailer/SOGoMailFolder.m +A SoObjects/Mailer/SOGoMailFolder.m.orig + +commit c686e3294d010b3f8af8ae4cb1b88d8970e4ff7f +Author: Ludovic Marcotte +Date: Mon Feb 17 08:39:48 2014 -0500 + + Added method to sanitize calendar "ServerId" + +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m + +commit 1ff91f7b592a1aaa8960d8878ff2389b1f61f174 +Author: Ludovic Marcotte +Date: Mon Feb 17 08:38:34 2014 -0500 + + Added -activeSyncRepresentation to NSData objects + +M ActiveSync/NSData+ActiveSync.h +M ActiveSync/NSData+ActiveSync.m + +commit b34c6324cdf6e55583114c6110f89a4d3e01bb37 +Author: Francis Lachapelle +Date: Fri Feb 14 21:31:32 2014 -0500 + + Fix custom mail labels in Sieve filter editor + +M SoObjects/SOGo/SOGoSieveManager.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxFilterEditor.js + +commit 75c8362df833bc4204bf6a83cc037eaa719d20cf +Author: Ludovic Marcotte +Date: Fri Feb 14 16:39:26 2014 -0500 + + Avoid over-using LDAP connections when decomposing groups + +M NEWS +M SoObjects/Appointments/SOGoCalendarComponent.m +M SoObjects/SOGo/SOGoGroup.m + +commit ae07de5f6fa2127152761eb8732fa116ee5cae4d +Author: Ludovic Marcotte +Date: Fri Feb 14 13:46:21 2014 -0500 + + Updated the Active Sync documentation + +M Documentation/SOGo Installation Guide.odt + +commit 06d2364ea255462262511b4f38f3c556d7fcb7f9 +Author: Francis Lachapelle +Date: Thu Feb 13 12:56:24 2014 -0500 + + Improve display of toolboor menus with checkmarks + +M UI/WebServerResources/generic.css + +commit 29e081ec5629e06c5f0839e5bc1d21122100fee8 +Author: Francis Lachapelle +Date: Thu Feb 13 12:53:07 2014 -0500 + + Add more parameters examples to sogo.conf + +M Scripts/sogo.conf + +commit 113b02fd71e4c88eea5ece62bc929f38285a38e4 +Author: Francis Lachapelle +Date: Thu Feb 13 12:12:31 2014 -0500 + + Fix encoding of contact ID in Ajax requests + +M UI/WebServerResources/ContactsUI.js + +commit 35b108e645c3e3dd7ece038c04b72a2819aa4b8a +Author: Francis Lachapelle +Date: Thu Feb 13 12:11:25 2014 -0500 + + Contact: fix display of links + +M UI/Contacts/UIxContactView.m + +commit 70266155d3a4a015462223c5fc632219a8ad1e71 +Author: Ludovic Marcotte +Date: Wed Feb 12 21:55:09 2014 -0500 + + Fix double return + +M ActiveSync/iCalToDo+ActiveSync.m + +commit fcfd8be7719a1670bf2574486e057e3db8eba82d +Author: Francis Lachapelle +Date: Mon Feb 10 21:49:49 2014 -0500 + + Improve URL handling in popup of events + +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/generic.js + +commit b91032db8e9be48162934b586edd35c4ba55c36e +Author: Francis Lachapelle +Date: Mon Feb 10 16:23:16 2014 -0500 + + Cleanup common English Localizable.strings + +M UI/Common/English.lproj/Localizable.strings + +commit f4874600fe195e86ef3652a4c0096529f9a72e89 +Author: Ludovic Marcotte +Date: Mon Feb 10 20:17:54 2014 -0500 + + Bumped to v14.1 + +M ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m + +commit b14e99b32a609063d484eb2472684831e4faa214 +Author: Ludovic Marcotte +Date: Mon Feb 10 20:16:43 2014 -0500 + + New prefs for intervals, fixed missing events in meeting requests, bumped v14.1 + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M SoObjects/SOGo/SOGoSystemDefaults.h +M SoObjects/SOGo/SOGoSystemDefaults.m + +commit ea4b21e9916877aa182a5a8dfa32c519b04ae821 +Author: Francis Lachapelle +Date: Mon Feb 10 13:49:02 2014 -0500 + + Decode HTML entities in JSON of calendar module + +M UI/WebServerResources/SchedulerUI.js + +commit 0307b8339a9de48340eb11a4a81176315b7fe69d +Author: Francis Lachapelle +Date: Mon Feb 10 13:45:51 2014 -0500 + + Detect URLs in popup of events + +M NEWS +M UI/Scheduler/UIxAppointmentEditor.m + +commit b5f1d3a19a295bb1a642b3d08d8b2af37dc1a23a +Author: Francis Lachapelle +Date: Mon Feb 10 11:25:38 2014 -0500 + + Guide: add SOGoCalendarDefaultReminder paramter + +M Documentation/SOGo Installation Guide.odt + +commit 8225f76279e54dc2e8e9ed2162428569f8c81f47 +Author: Ludovic Marcotte +Date: Fri Feb 7 16:17:11 2014 -0500 + + Fixed globalobjid so it doesn't contain \n characters! + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit c94595ea7f0f843c2d7abf25df039b2bbe707625 +Author: Francis Lachapelle +Date: Fri Feb 7 16:12:14 2014 -0500 + + Escape HTML in CSS dialogs + +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/generic.js + +commit 3a5e44e7eb8b390b67a8f8a83030b49606956501 +Author: Francis Lachapelle +Date: Fri Feb 7 15:53:39 2014 -0500 + + Decode HTML entities in JSON of calendar module + +M UI/WebServerResources/SchedulerUI.js + +commit 80a09407652ec04e8c9fb6cb48e1029e69a15765 +Author: Francis Lachapelle +Date: Fri Feb 7 15:52:43 2014 -0500 + + Escape HTML in JSON of contacts module + +M NEWS +M UI/Contacts/UIxContactView.m +M UI/Contacts/UIxContactsListActions.m +M UI/WebServerResources/ContactsUI.js + +commit 7118bbe0ab7790db0321122fe2b6462a204bd4b9 +Author: Francis Lachapelle +Date: Fri Feb 7 14:00:36 2014 -0500 + + Replace '/' by '-' in filenames of attachments + + Fixes #2537 + +M NEWS +M SoObjects/Mailer/SOGoMailObject.m +M UI/MailPartViewers/UIxMailPartViewer.m + +commit eee5beb6987e763e4a99b5a7baf76b1e1944a389 +Author: Francis Lachapelle +Date: Fri Feb 7 10:51:42 2014 -0500 + + Add message-id header to appointment notification + + Fixes #2535 + +M NEWS +M SoObjects/Appointments/SOGoCalendarComponent.m +M SoObjects/Mailer/NSString+Mail.h +M SoObjects/Mailer/NSString+Mail.m +M SoObjects/Mailer/SOGoDraftObject.m + +commit 3363b253ac9f7e2643b66390467c6c78a750393f +Author: Ludovic Marcotte +Date: Fri Feb 7 10:45:09 2014 -0500 + + Set additional properties on meeting requests + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 4070b9222d65b265fc67b54c5af095cdadbfaef9 +Author: Francis Lachapelle +Date: Fri Feb 7 09:59:45 2014 -0500 + + Rework HTML of appointment notifications + + Needed to simplify the HTML so it displays properly in Outlook. + + Fixes #2233 + +M NEWS +M UI/Templates/Appointments/SOGoAptMailDeletion.wox +M UI/Templates/Appointments/SOGoAptMailICalReply.wox +M UI/Templates/Appointments/SOGoAptMailInvitation.wox +M UI/Templates/Appointments/SOGoAptMailReceipt.wox +M UI/Templates/Appointments/SOGoAptMailUpdate.wox + +commit 5ea56a6f8a94e356912d7dda51f391c5a20edf8b +Author: Francis Lachapelle +Date: Thu Feb 6 14:43:40 2014 -0500 + + Enlarge the note field of the contact editor + +M UI/Templates/ContactsUI/UIxContactEditor.wox + +commit 379258de59df7cf47364ed4037d609d31ea5677f +Author: Ludovic Marcotte +Date: Thu Feb 6 21:25:08 2014 -0500 + + Pimped the doc for AS support and dropped all references to Funambol. + +M Documentation/SOGo Installation Guide.odt + +commit 93b3685aa454be981b6184ff49850bb60874f8d5 +Author: Ludovic Marcotte +Date: Thu Feb 6 19:35:23 2014 -0500 + + Added birthday support + +M ActiveSync/NGVCard+ActiveSync.m + +commit c4dc4d4edacc959cc0c3d7f6b16ed1ce59e94d78 +Author: Ludovic Marcotte +Date: Thu Feb 6 17:59:33 2014 -0500 + + Fixed compilation warning + +M ActiveSync/NGVCard+ActiveSync.m + +commit c6d104fac51a9b981a125c8d7adb725b8b56be1d +Author: Ludovic Marcotte +Date: Thu Feb 6 17:57:01 2014 -0500 + + Fixed contact sync'ing on Android + +M ActiveSync/NGVCard+ActiveSync.m + +commit ef79c09642009cb7eb74d5261e60bb48e5c2d6b7 +Author: Francis Lachapelle +Date: Thu Feb 6 14:43:05 2014 -0500 + + Don't use the HTML editor with Internet Explorer 7 + +M NEWS +M SoObjects/SOGo/SOGoUserDefaults.m + +commit aeb712083a1bbb77d4b2483a3301aa73240e4e1e +Author: Francis Lachapelle +Date: Thu Feb 6 14:37:43 2014 -0500 + + Fix static array retain + +M SoObjects/Appointments/SOGoAppointmentFolder.m + +commit 2c678101fcb5a73097cd409a832710bfbc9c916f +Author: Francis Lachapelle +Date: Thu Feb 6 14:21:36 2014 -0500 + + Fix handling of ACLs with multiple groups + + Fixes #1854 + +M NEWS +M SoObjects/Appointments/SOGoAppointmentFolder.m + +commit b95362f96368dcc93a4fd781c24c1c6bd7b25944 +Author: Ludovic Marcotte +Date: Thu Feb 6 14:05:00 2014 -0500 + + Now make use of a NGMimeFileData object to avoid broken SOPE behaviour + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 3175a9169f136a4c87480403f9b8035f3d06da96 +Author: Ludovic Marcotte +Date: Wed Feb 5 20:56:01 2014 -0500 + + Mapped lots of contact properties + +M ActiveSync/NGVCard+ActiveSync.m +M SoObjects/Contacts/NGVCard+SOGo.h +M SoObjects/Contacts/NGVCard+SOGo.m + +commit 9311f05cc0a96f6ab731030a4d6eb276549320c3 +Author: Ludovic Marcotte +Date: Wed Feb 5 17:30:34 2014 -0500 + + Fixed the non-filtering of recurrence exceptions + +M SoObjects/Appointments/SOGoCalendarComponent.m + +commit 78cbcfb560a3ec8142669f575442b13d3509b633 +Author: Francis Lachapelle +Date: Wed Feb 5 16:15:12 2014 -0500 + + Update translations + +M UI/MailerUI/Czech.lproj/Localizable.strings +M UI/MailerUI/Hungarian.lproj/Localizable.strings +M UI/MailerUI/Russian.lproj/Localizable.strings +M UI/MailerUI/Slovak.lproj/Localizable.strings + +commit b8610b3eca1a73a5e2eb689e63e013a48cbcafb8 +Author: Ludovic Marcotte +Date: Wed Feb 5 16:12:08 2014 -0500 + + Extended GetItemEstimate to GCS collections + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 1a7fc2a0e90a19dfb1fce292ae5ff53aa513ade9 +Author: Francis Lachapelle +Date: Wed Feb 5 16:02:38 2014 -0500 + + Escape HTML in JSON of calendar module + + Fixes #2598 + +M NEWS +M UI/Scheduler/UIxAppointmentEditor.m +M UI/Scheduler/UIxCalListingActions.m +M UI/WebServerResources/SchedulerUI.js + +commit f7a20d0a83f5be75d157e1a969fb0b7bc63ae2b7 +Author: Ludovic Marcotte +Date: Wed Feb 5 15:23:09 2014 -0500 + + Fixed broken encoding of single-value element + +M SOPE/NGCards/NGCardsSaxHandler.m + +commit a1cf0668282e9c0227f48d944de9f79ceb97e87e +Author: Francis Lachapelle +Date: Wed Feb 5 11:43:25 2014 -0500 + + Update translations + +M UI/Scheduler/Dutch.lproj/Localizable.strings +M UI/Scheduler/Finnish.lproj/Localizable.strings +M UI/Scheduler/German.lproj/Localizable.strings +M UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings +M UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings +M UI/Scheduler/Polish.lproj/Localizable.strings +M UI/Scheduler/Swedish.lproj/Localizable.strings +M UI/Scheduler/Welsh.lproj/Localizable.strings + +commit 359b6a99aa2ef31f356ea6e8246e6a0ff2d47945 +Author: Francis Lachapelle +Date: Wed Feb 5 11:17:22 2014 -0500 + + Encode HTML entities for inline forwards in text + + Fixes #2411 + +M NEWS +M SoObjects/Mailer/NSString+Mail.m +M SoObjects/Mailer/SOGoMailObject+Draft.m + +commit 826537ed018053dcbe0012afc60750bb0b68b9f0 +Author: Francis Lachapelle +Date: Wed Feb 5 09:04:00 2014 -0500 + + Fix caching of DN in LDAP source + +M SoObjects/SOGo/LDAPSource.m + +commit 3950e9d4c33417cbf821f5554e566e6a68b7d0b0 +Author: Ludovic Marcotte +Date: Tue Feb 4 21:03:11 2014 -0500 + + Moved DN cache to SOGoCache + +M NEWS +M SoObjects/SOGo/LDAPSource.h +M SoObjects/SOGo/LDAPSource.m +M SoObjects/SOGo/SOGoCache.h +M SoObjects/SOGo/SOGoCache.m + +commit 9885211a5b804f885c5cc8edbc88b8f03f9ffbe8 +Author: Francis Lachapelle +Date: Tue Feb 4 16:48:06 2014 -0500 + + Replace VARCHAR(1000000) by TEXT field type + + Fixes #2516 + +M OGoContentStore/appointment.ocs + +commit ef1be5dc5f6b746162b8cda5d0222a74ebcd7514 +Author: Francis Lachapelle +Date: Tue Feb 4 16:25:52 2014 -0500 + + Improve deleting occurences of recurrent events + +M NEWS +M SoObjects/SOGo/SOGoGCSFolder.m +M UI/Common/UIxFolderActions.m +M UI/Scheduler/Arabic.lproj/Localizable.strings +M UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings +M UI/Scheduler/Catalan.lproj/Localizable.strings +M UI/Scheduler/Czech.lproj/Localizable.strings +M UI/Scheduler/Danish.lproj/Localizable.strings +M UI/Scheduler/English.lproj/Localizable.strings +M UI/Scheduler/French.lproj/Localizable.strings +M UI/Scheduler/German.lproj/Localizable.strings +M UI/Scheduler/Hungarian.lproj/Localizable.strings +M UI/Scheduler/Icelandic.lproj/Localizable.strings +M UI/Scheduler/Italian.lproj/Localizable.strings +M UI/Scheduler/Russian.lproj/Localizable.strings +M UI/Scheduler/Slovak.lproj/Localizable.strings +M UI/Scheduler/SpanishArgentina.lproj/Localizable.strings +M UI/Scheduler/SpanishSpain.lproj/Localizable.strings +M UI/Scheduler/UIxCalMainView.m +M UI/Scheduler/Ukrainian.lproj/Localizable.strings +M UI/Templates/SchedulerUI/UIxCalMainView.wox +M UI/WebServerResources/ContactsUI.js +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/generic.css +M UI/WebServerResources/generic.js + +commit 4e42d5d41045be78cd0879054ed5649707ffc35a +Author: Ludovic Marcotte +Date: Tue Feb 4 15:07:23 2014 -0500 + + Disabled debugging... + +M ActiveSync/NSData+ActiveSync.m + +commit 31969d162d9bf4747367807c863c0d311d4986b9 +Author: Ludovic Marcotte +Date: Tue Feb 4 15:03:02 2014 -0500 + + Properly escape some control chars (and generalized it) + +M ActiveSync/NGVCard+ActiveSync.m +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalToDo+ActiveSync.m +M SoObjects/SOGo/NSString+Utilities.h +M SoObjects/SOGo/NSString+Utilities.m + +commit 2ff3b5ef5f5542cc53c668f6ed4e65ec6ecf046a +Author: Ludovic Marcotte +Date: Tue Feb 4 11:19:33 2014 -0500 + + First pass at 'push' support for Ping and Sync + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M Apache/SOGo.conf + +commit 7fe48a1555414406bd86c7b986596e929e0bfd05 +Author: Ludovic Marcotte +Date: Tue Feb 4 09:02:40 2014 -0500 + + Fix for bug #2544 + +M Documentation/SOGo Native Microsoft Outlook Configuration.odt + +commit fcddeb4224308f80184000ad34add7ffc3dcf3c2 +Author: Francis Lachapelle +Date: Mon Feb 3 15:15:14 2014 -0500 + + Visually identify users with no freebusy + + Fixes #2565 + +M NEWS +M UI/Templates/SchedulerUI/UIxAttendeesEditor.wox +M UI/WebServerResources/UIxAttendeesEditor.css +M UI/WebServerResources/UIxAttendeesEditor.js + +commit f1cb87aa02b606b7be8457bbce08128974072a09 +Author: Francis Lachapelle +Date: Mon Feb 3 12:03:51 2014 -0500 + + Convert JS alerts to CSS dialogs in aptmt editor + +M NEWS +M UI/WebServerResources/UIxAppointmentEditor.js +M UI/WebServerResources/UIxAttendeesEditor.js +M UI/WebServerResources/UIxComponentEditor.css + +commit a6424680cc112b06c7a96d766c5769dfc8e7105f +Author: Francis Lachapelle +Date: Mon Feb 3 11:56:00 2014 -0500 + + Respect max bookings of resources in freebusy data + + Fixes #2560 + +M NEWS +M UI/MainUI/SOGoUserHomePage.m + +commit b3dc645282e5fca0cac9baef2250e979e2e5cb39 +Author: Francis Lachapelle +Date: Mon Feb 3 11:09:03 2014 -0500 + + Warn user when dnd failed with resource conflict + + Fixes #1613 + +M NEWS +M UI/Scheduler/UIxAppointmentActions.m +M UI/WebServerResources/SchedulerUI.js + +commit 2f0419c18a17e2d88c0c43771b92541aa2cd645b +Author: Francis Lachapelle +Date: Mon Feb 3 11:03:23 2014 -0500 + + Warn user when overbooking a resource + + Fixes #2541 + +M NEWS +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit 9e9407cf30ef51c3ba6e5e6f840d4a0433ad73af +Author: Ludovic Marcotte +Date: Mon Feb 3 10:24:33 2014 -0500 + + First pass at event invitations support + few bug fixes. + +M ActiveSync/NGDOMElement+ActiveSync.m +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.h +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.h +M ActiveSync/SOGoMailObject+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalTimeZone+ActiveSync.m + +commit d709a1216b71995b25087356f7b968f7d3d7f65f +Author: Ludovic Marcotte +Date: Mon Feb 3 09:54:21 2014 -0500 + + Updated NEWS file + +M NEWS + +commit 588352be7d22f23602582e07650454a7634a9f96 +Author: Ludovic Marcotte +Date: Mon Feb 3 09:53:47 2014 -0500 + + Fix for bug #2587 + +M SoObjects/SOGo/SOGoCache.m + +commit a40d7ca342248d04e33baf303c36203239af1846 +Author: Ludovic Marcotte +Date: Fri Jan 31 15:11:13 2014 -0500 + + Updated NEWS file + +M NEWS + +commit e4ce687e29d6232228e6bc9b6943f4e4a0573786 +Author: Ludovic Marcotte +Date: Fri Jan 31 15:05:33 2014 -0500 + + Update the content when the request is from Active Sync + +M SoObjects/Appointments/SOGoAppointmentObject.m + +commit bcb0764b1901f8ea9ee799a80594ae554e80e827 +Author: Ludovic Marcotte +Date: Fri Jan 31 15:04:49 2014 -0500 + + Fix for bug #2505 + +M SOPE/NGCards/NGCardsSaxHandler.m + +commit ddd7be433346fce2d1f6c6ed52ead56c1f289f41 +Author: Ludovic Marcotte +Date: Fri Jan 31 15:03:40 2014 -0500 + + Fix for bug #2187 + +M SoObjects/SOGo/LDAPSource.m + +commit d9f4a9935a741e6a537127a4f3ed992b86f2b421 +Author: Ludovic Marcotte +Date: Fri Jan 31 10:33:36 2014 -0500 + + Properly escape all foldernames + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit ae75fee512ffc410e920e47d9382b07749970460 +Author: Francis Lachapelle +Date: Thu Jan 30 10:37:36 2014 -0500 + + Update NEWS file + +M NEWS + +commit 8cdacd065f5e0e6b0171c60199b71618c0d3af23 +Author: Ludovic Marcotte +Date: Thu Jan 30 08:25:55 2014 -0500 + + Properly escape the filename + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit af6a69c9fc504eb569971091fec5c1b7c154c58f +Author: Ludovic Marcotte +Date: Wed Jan 29 11:21:36 2014 -0500 + + Expunge immediately after MoveItems to force the Delete response + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit dc41e29f05f431ee73563fdbf8e32a866dcc9fd0 +Author: Ludovic Marcotte +Date: Wed Jan 29 11:16:58 2014 -0500 + + Fixed the MoveItems response + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit afc7519beb60d52375102af71886f77733f4348a +Author: Ludovic Marcotte +Date: Wed Jan 29 10:30:31 2014 -0500 + + Properly escape values + +M ActiveSync/NGVCard+ActiveSync.m + +commit f2d5a7691115390c5f0ead5b8276c248504fb7e8 +Author: Ludovic Marcotte +Date: Wed Jan 29 09:44:03 2014 -0500 + + Type fix for recurrence rules. + +M ActiveSync/iCalRecurrenceRule+ActiveSync.m + +commit 342ad84a2458320e698a6273bec7b0d6d7a240a8 +Author: Francis Lachapelle +Date: Tue Jan 28 15:36:48 2014 -0500 + + Fix issues with contextual menu in calendars + + Creating an event or a task using the contextual menu will now consider + the day and hour of the menu position (fixes #2557). + The next/previous day/month menu options have been fixed. + The menu will now disappear when clicking outside the contextual menu on + the calendar area. + +M NEWS +M UI/Templates/SchedulerUI/UIxCalDayView.wox +M UI/Templates/SchedulerUI/UIxCalMonthView.wox +M UI/Templates/SchedulerUI/UIxCalMulticolumnDayView.wox +M UI/Templates/SchedulerUI/UIxCalWeekView.wox +M UI/WebServerResources/SchedulerUI.js +M UI/WebServerResources/SchedulerUIDnD.js + +commit 9580a8f8ee92b4b9ea7f6d592a059ea79a4e33f5 +Author: Ludovic Marcotte +Date: Tue Jan 28 14:26:35 2014 -0500 + + Added initial support for recurring events. + +M ActiveSync/GNUmakefile +M ActiveSync/iCalEvent+ActiveSync.m +A ActiveSync/iCalRecurrenceRule+ActiveSync.h +A ActiveSync/iCalRecurrenceRule+ActiveSync.m + +commit a96380a0dc6dbf8c3443560eec458ac0d22d7820 +Author: Ludovic Marcotte +Date: Tue Jan 28 13:51:21 2014 -0500 + + Fixed SyncKey issue not being updated during the sync operation + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 2fed02da5988a9a78ee1b4ecf1c8efb724286546 +Author: Ludovic Marcotte +Date: Tue Jan 28 13:21:34 2014 -0500 + + When creating a new object, mark it as new. + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 84dc08adb60c2a3589be74ed7067918753030015 +Author: Francis Lachapelle +Date: Mon Jan 27 15:43:48 2014 -0500 + + Fix display of folder names in filter editor + + Fixes #2569 + +M NEWS +M UI/WebServerResources/UIxFilterEditor.js +M UI/WebServerResources/UIxPreferences.js + +commit c160edf20abdb728135eb624e28ad490b8892153 +Author: Francis Lachapelle +Date: Mon Jan 27 15:09:22 2014 -0500 + + Add support for Sieve body extension + +M NEWS +M SoObjects/Mailer/SOGoMailAccount.m +M SoObjects/SOGo/SOGoSieveManager.h +M SoObjects/SOGo/SOGoSieveManager.m +M UI/PreferencesUI/UIxPreferences.m +M UI/WebServerResources/UIxFilterEditor.js + +commit 3b7a3c94d429d74e6a027b93dadf19664191f811 +Author: Ludovic Marcotte +Date: Mon Jan 27 11:30:43 2014 -0500 + + Properly escape fields and improved data dumps. + +M ActiveSync/NGVCard+ActiveSync.m +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalToDo+ActiveSync.m + +commit 7b97e28cfffa271ebc09f495d893ebc3225d7905 +Author: Ludovic Marcotte +Date: Fri Jan 24 16:33:31 2014 -0500 + + Fixed compiler warning + +M ActiveSync/NSString+ActiveSync.m + +commit 81bf1b41e9d7e353e03abe336b8276ae61e9270b +Author: Ludovic Marcotte +Date: Fri Jan 24 16:28:08 2014 -0500 + + Fixed the rename operation to force a FolderSync + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit ea86f136b757a0668d2aabe8e533547f81a9bd23 +Author: Ludovic Marcotte +Date: Fri Jan 24 14:10:19 2014 -0500 + + Properly escape folder/file names and fixed date representation in emails + +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit fc0c580fe1179852ca7ab207bbd863a8506ecca4 +Author: Francis Lachapelle +Date: Fri Jan 24 13:45:29 2014 -0500 + + Fix validation of subscribed folders + + Fixes #2583 + +M NEWS +M SoObjects/SOGo/SOGoParentFolder.m + +commit 42a7bcb9ac8ccbb2ab9f7f3f88875ff06fdb01ff +Author: Ludovic Marcotte +Date: Fri Jan 24 11:44:12 2014 -0500 + + Added verbosity in case wbxml <-> xml conversions fail + +M ActiveSync/NSData+ActiveSync.m + +commit 9669f2bbbaa4be60710154eacaaef2723f2e5592 +Author: Ludovic Marcotte +Date: Fri Jan 24 11:09:37 2014 -0500 + + Added more props and fixed the NativeBodyType preventing OL2013 to work + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 25634088722bbc269e4d1d61c956923c3746f240 +Author: Ludovic Marcotte +Date: Fri Jan 24 07:38:03 2014 -0500 + + Improved the README regarding MS licensing + +M ActiveSync/README + +commit 0f9d8cdd2c7bf44e3ab4274b047598f6ab745ded +Author: Francis Lachapelle +Date: Thu Jan 23 14:48:36 2014 -0500 + + Remove xml tag before doctype declaration + +M UI/Common/UIxPageFrame.m + +commit 5105dbb3428e586a5006770d6a08b4a175df2b63 +Author: Francis Lachapelle +Date: Thu Jan 23 14:39:03 2014 -0500 + + Make all attachments available when re/fwd'ing + + To do so, we save the draft to the mailstore. + +M SoObjects/Mailer/SOGoDraftObject.m + +commit b26e767a106e604bc13c6c5bfdd1a379739f7999 +Author: Francis Lachapelle +Date: Thu Jan 23 14:34:14 2014 -0500 + + Respect signature placement when forwarding a msg + +M NEWS +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.html +M SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.wod +M SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.html +M SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod +M SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.html +M SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.wod +M SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.html +M SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.wod +M SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.html +M SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.wod +M SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.html +M SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.wod +M SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html +M SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.wod +M SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.html +M SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.wod +M SoObjects/Mailer/SOGoMailForward.h +M SoObjects/Mailer/SOGoMailForward.m +M SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.html +M SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.wod +M SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html +M SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.wod +M SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.html +M SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod +M SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.html +M SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.wod +M SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.html +M SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.wod +M SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.html +M SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.wod +M SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.html +M SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.wod +M SoObjects/Mailer/SOGoMailObject+Draft.m +M SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.html +M SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.wod +M SoObjects/Mailer/SOGoMailReply.h +M SoObjects/Mailer/SOGoMailReply.m +M SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.html +M SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.wod +M SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.html +M SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.wod +M SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.html +M SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.wod +M SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.html +M SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.wod +M SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.html +M SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.wod +M SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.html +M SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.wod +M SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html +M SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.wod + +commit 940f85fde27e19fb772b5ef8284bbdb1308d5513 +Author: Francis Lachapelle +Date: Thu Jan 23 11:25:14 2014 -0500 + + Add missing localizable string to webmail + +M UI/MailerUI/English.lproj/Localizable.strings +M UI/MailerUI/French.lproj/Localizable.strings + +commit adee80529e66eea9acfef9e311a9ff3365114690 +Author: Ludovic Marcotte +Date: Thu Jan 23 11:09:32 2014 -0500 + + Added more event props and fixed date issues + +M ActiveSync/NSDate+ActiveSync.h +M ActiveSync/NSDate+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.m + +commit 295dfcfdd5598b488a3a68aeb545349c901bdc58 +Author: Francis Lachapelle +Date: Thu Jan 23 10:19:25 2014 -0500 + + Webmail: allow the backspace on any platform + +M UI/WebServerResources/MailerUI.js + +commit aa18abd8428e5154e045f3dcb844ee152ab60721 +Author: Ludovic Marcotte +Date: Thu Jan 23 09:41:41 2014 -0500 + + Added more props and cleaned up the code + +M ActiveSync/iCalToDo+ActiveSync.m + +commit feb398d59c20f1a72be40a276f97792c53168f29 +Author: Francis Lachapelle +Date: Thu Jan 23 09:10:17 2014 -0500 + + Fix IE11 issue with mail editor + +M NEWS +M UI/Templates/MailerUI/UIxMailEditor.wox +M UI/WebServerResources/UIxMailEditor.css +M UI/WebServerResources/UIxMailEditor.js + +commit 19b404de45941cd1913fa5ab36c282cda1e35d51 +Author: Francis Lachapelle +Date: Thu Jan 23 08:17:02 2014 -0500 + + Update French translation + +M UI/PreferencesUI/French.lproj/Localizable.strings + +commit d1a384e539e52bdb40e326ef41149fe7b28fff04 +Author: Jean Raby +Date: Wed Jan 22 11:40:04 2014 -0500 + + Send IMIP responses if the event is in the future + + Avoids sending responses for past events when importing events into + a new calendar from thunderbird or any DAV client. + +M SoObjects/Appointments/SOGoCalendarComponent.m + +commit e7f38f940ea02849b6fb8b1a2898066181969495 +Author: Ludovic Marcotte +Date: Wed Jan 22 11:27:27 2014 -0500 + + Moved the cards' logic into a category and added support for more AS ones + +M ActiveSync/NGVCard+ActiveSync.m +M SoObjects/Contacts/NGVCard+SOGo.h +M SoObjects/Contacts/NGVCard+SOGo.m +M UI/Contacts/UIxContactView.m + +commit f7ba5d2346cfb0e60ae41087963432fd11154d0f +Author: Ludovic Marcotte +Date: Wed Jan 22 11:25:18 2014 -0500 + + Don't add DateCompleted if none exist + +M ActiveSync/iCalToDo+ActiveSync.m + +commit eeddf43a781aab6648c18c9751dc54fce21f9f0b +Author: Ludovic Marcotte +Date: Wed Jan 22 11:22:52 2014 -0500 + + Add the milliseconds + +M ActiveSync/NSDate+ActiveSync.m + +commit eabe829236b7c25c3cc60eca481d9a1fc156e19b +Author: Ludovic Marcotte +Date: Wed Jan 22 11:02:12 2014 -0500 + + Don't add empty and specify the folder Class + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 762b841249fdff7cf3412414f703ab465c15583a +Author: Ludovic Marcotte +Date: Tue Jan 21 16:40:57 2014 -0500 + + Avoid crashes on broken configurations + +M SoObjects/Appointments/SOGoCalendarComponent.m + +commit 3090448fcb1411f49776bd8fd2e23c2b5b60775d +Author: Francis Lachapelle +Date: Mon Jan 20 15:43:33 2014 -0500 + + Update prototype.js to fix IE 10 issues + + Updated to commit @8c9ead49ec of + https://github.com/sstephenson/prototype/ + +M UI/WebServerResources/prototype.js + +commit a3e1d4813acffb17d6495a5b68ba20e5a9ebdf0a +Author: Ludovic Marcotte +Date: Mon Jan 20 10:21:33 2014 -0500 + + Removed debug + refactored NSDate class + +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/NSDate+ActiveSync.h +M ActiveSync/NSDate+ActiveSync.m + +commit 046a64511900c58433cd72068b185508db6e9888 +Author: Ludovic Marcotte +Date: Mon Jan 20 10:13:16 2014 -0500 + + Refactored the code and added support for FilterType + +M ActiveSync/GNUmakefile +A ActiveSync/NSCalendarDate+ActiveSync.h +A ActiveSync/NSCalendarDate+ActiveSync.m +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.m +M SoObjects/Mailer/SOGoMailFolder.h +M SoObjects/Mailer/SOGoMailFolder.m +M SoObjects/SOGo/SOGoGCSFolder.h +M SoObjects/SOGo/SOGoGCSFolder.m + +commit 343f2d8bfef38d0dff74dc122b12ce4c2ff2ad3c +Author: Ludovic Marcotte +Date: Thu Jan 16 15:13:09 2014 -0500 + + Fixed the GetChanges detection and added FilterType decoding + +M ActiveSync/NSDate+ActiveSync.h +M ActiveSync/NSDate+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 65276f7dc005625bcdc72b5c0e09e418c3eb9252 +Author: Jean Raby +Date: Wed Jan 15 16:37:24 2014 -0500 + + Add dependency on tmpwatch. The cronjob uses it. + + Fixes #2577 + +M packaging/rhel/sogo.spec + +commit 24663682d625e61e2a67a1c5bce4e427d8411e3a +Author: Jean Raby +Date: Wed Jan 15 12:08:29 2014 -0500 + + New package: sogo-activesync + sogo-tool fixes + + Added dependency on sogo = %version for sogo-tool + +M packaging/rhel/sogo.spec + +commit 57cd315f2a37dd4355eb3753f50f1a12131c1ea2 +Author: Jean Raby +Date: Wed Jan 15 11:07:14 2014 -0500 + + explicitly list all *.SOGo folders + + Otherwise, ActiveSync.SOGo would be listed twice, once in sogo-activesync and + once in sogo. + +M packaging/debian-multiarch/sogo.install +M packaging/debian/sogo.install + +commit 29fcfda1183dbd6c6de6e30d43c9d83004b46380 +Author: Ludovic Marcotte +Date: Wed Jan 15 09:36:25 2014 -0500 + + Implemented FolderDelete for mail folders + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit ada90677e1129df102591882a1266205bdc63631 +Author: Ludovic Marcotte +Date: Tue Jan 14 16:08:04 2014 -0500 + + Correctly update the SyncKey when creating or updating folders + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit b71180b0064c4634edfc96741566dc8cb9d158b4 +Author: Ludovic Marcotte +Date: Tue Jan 14 15:47:33 2014 -0500 + + Use the right DeviceId + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 1ed41e28317258ca9da8cff93bd22d4aa28719a3 +Author: Jean Raby +Date: Tue Jan 14 14:39:51 2014 -0500 + + New deb package: sogo-activesync + +M packaging/debian-multiarch/control +M packaging/debian-multiarch/rules +A packaging/debian-multiarch/sogo-activesync.docs +A packaging/debian-multiarch/sogo-activesync.install +M packaging/debian/control +M packaging/debian/control-squeeze +M packaging/debian/rules +A packaging/debian/sogo-activesync.docs +A packaging/debian/sogo-activesync.install + +commit 948553dce3d6d968a2e85c335aff54ac5a6dc76f +Author: Ludovic Marcotte +Date: Tue Jan 14 13:50:17 2014 -0500 + + Properly extract the foldername + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 74adf784461325eb8962214e77cc7c89e9957c9c +Author: Jean Raby +Date: Tue Jan 14 13:37:51 2014 -0500 + + Fix include directories for libwbxml + +M ActiveSync/GNUmakefile +M ActiveSync/NSData+ActiveSync.m + +commit ee0ae8a1c01b239f5c5fe1b8e67279cfa91537ff +Author: Ludovic Marcotte +Date: Tue Jan 14 11:44:33 2014 -0500 + + Correctly add the folder type prefix when creating them + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 38c649751c530d57c76857e58a9185b98754aac9 +Author: Ludovic Marcotte +Date: Tue Jan 14 11:41:26 2014 -0500 + + If GetChanges is omitted, consider it as YES + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 645c718dfa24a82e921eafd6ff128dd2bc802423 +Author: Ludovic Marcotte +Date: Tue Jan 14 10:49:55 2014 -0500 + + Fixed broken comparison and added stub for processMeeting... + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 02992a70181512a1d02f2f4afb7797aed9db4935 +Author: Ludovic Marcotte +Date: Tue Jan 14 10:42:15 2014 -0500 + + Correctly decode base64 text parts before returning them + +M ActiveSync/SOGoMailObject+ActiveSync.m + +commit 0ffa0649c5c933e7f2b4ba3d0afac74ff7ba67b4 +Author: Ludovic Marcotte +Date: Tue Jan 14 10:09:10 2014 -0500 + + Wrap Fetch responses into tags so iOS works correctly + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit 7c7cabe4329207a124dac3e4fcdafacb42f3dcf9 +Author: Ludovic Marcotte +Date: Mon Jan 13 16:25:14 2014 -0500 + + First pass at organizer/attendees support + +M ActiveSync/iCalEvent+ActiveSync.m + +commit bb9c4cf0396cef689dde5400e9d28409780c7e2a +Author: Ludovic Marcotte +Date: Mon Jan 13 16:24:15 2014 -0500 + + Handle not found objects and correctly get all "Collection" + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit c252f5b7a861ea79d671397e50400e4105f17fd7 +Author: Ludovic Marcotte +Date: Mon Jan 13 11:46:32 2014 -0500 + + Fixed the license indent + +M ActiveSync/ActiveSyncProduct.m +M ActiveSync/LICENSE +M ActiveSync/NGDOMElement+ActiveSync.h +M ActiveSync/NGDOMElement+ActiveSync.m +M ActiveSync/NGMimeMessage+ActiveSync.h +M ActiveSync/NGMimeMessage+ActiveSync.m +M ActiveSync/NGVCard+ActiveSync.h +M ActiveSync/NGVCard+ActiveSync.m +M ActiveSync/NSData+ActiveSync.h +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/NSDate+ActiveSync.h +M ActiveSync/NSDate+ActiveSync.m +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m +M ActiveSync/SOGoActiveSyncConstants.h +M ActiveSync/SOGoActiveSyncDispatcher+Sync.h +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/SOGoActiveSyncDispatcher.h +M ActiveSync/SOGoActiveSyncDispatcher.m +M ActiveSync/SOGoMailObject+ActiveSync.h +M ActiveSync/SOGoMailObject+ActiveSync.m +M ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m +M ActiveSync/iCalEvent+ActiveSync.h +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalTimeZone+ActiveSync.h +M ActiveSync/iCalTimeZone+ActiveSync.m +M ActiveSync/iCalToDo+ActiveSync.h +M ActiveSync/iCalToDo+ActiveSync.m + +commit 2e44ac0f9b9b513b40e1b490ac1d48e846108d5f +Author: Ludovic Marcotte +Date: Mon Jan 13 10:19:00 2014 -0500 + + Fixed MoveItems and also fixed Ping with no content + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit 2a9ccf8122fbea723785737482ce9d48813803bd +Author: Ludovic Marcotte +Date: Mon Jan 13 10:18:20 2014 -0500 + + Now able to grab the command from the URI + +M ActiveSync/NSString+ActiveSync.h +M ActiveSync/NSString+ActiveSync.m + +commit f74066c9a6421abba4c370ee7dca609f4404c74a +Author: Ludovic Marcotte +Date: Sat Jan 11 19:31:39 2014 -0500 + + Support for more props + +M ActiveSync/iCalEvent+ActiveSync.m +M ActiveSync/iCalToDo+ActiveSync.m + +commit ee49836f2ee5a90b203367f9d386a16c494bce91 +Author: Ludovic Marcotte +Date: Fri Jan 10 16:49:40 2014 -0500 + + Disabled debugging... + +M ActiveSync/NSData+ActiveSync.m + +commit bf798061d3e2aae345cf2cd7ba0d3105f3e215c0 +Author: Ludovic Marcotte +Date: Fri Jan 10 16:48:39 2014 -0500 + + Fixed timezones support for events and added more supported props + +M ActiveSync/NSData+ActiveSync.m +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m +M ActiveSync/iCalEvent+ActiveSync.m + +commit 834e05bab1f925cb2a9dbeb48a24cd31cab7e7ae +Author: Ludovic Marcotte +Date: Fri Jan 10 15:29:57 2014 -0500 + + Fixed typo messing up adds + +M ActiveSync/SOGoActiveSyncDispatcher+Sync.m + +commit e8438a6235f0a37f5126c7253700dbab3dcff4d1 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:53:49 2014 -0500 + + Don't write decoded stuff if not needed + +M ActiveSync/NSData+ActiveSync.m + +commit 68dafdfdeb15ac2d33c21f00a7db521b7d400e72 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:25:00 2014 -0500 + + Removed old comments + +M ActiveSync/SOGoActiveSyncDispatcher.m + +commit e5bc46710c715a9dc2e1801bba236fa7e7848b3d +Author: Ludovic Marcotte +Date: Fri Jan 10 14:12:53 2014 -0500 + + Initial Active Sync Support! + +A ActiveSync/ActiveSyncProduct.m +A ActiveSync/GNUmakefile +A ActiveSync/GNUmakefile.preamble +A ActiveSync/LICENSE +A ActiveSync/NGDOMElement+ActiveSync.h +A ActiveSync/NGDOMElement+ActiveSync.m +A ActiveSync/NGMimeMessage+ActiveSync.h +A ActiveSync/NGMimeMessage+ActiveSync.m +A ActiveSync/NGVCard+ActiveSync.h +A ActiveSync/NGVCard+ActiveSync.m +A ActiveSync/NSData+ActiveSync.h +A ActiveSync/NSData+ActiveSync.m +A ActiveSync/NSDate+ActiveSync.h +A ActiveSync/NSDate+ActiveSync.m +A ActiveSync/NSString+ActiveSync.h +A ActiveSync/NSString+ActiveSync.m +A ActiveSync/README +A ActiveSync/SOGoActiveSyncConstants.h +A ActiveSync/SOGoActiveSyncDispatcher+Sync.h +A ActiveSync/SOGoActiveSyncDispatcher+Sync.m +A ActiveSync/SOGoActiveSyncDispatcher.h +A ActiveSync/SOGoActiveSyncDispatcher.m +A ActiveSync/SOGoMailObject+ActiveSync.h +A ActiveSync/SOGoMailObject+ActiveSync.m +A ActiveSync/SoObjectWebDAVDispatcher+ActiveSync.m +A ActiveSync/common.make +A ActiveSync/iCalEvent+ActiveSync.h +A ActiveSync/iCalEvent+ActiveSync.m +A ActiveSync/iCalTimeZone+ActiveSync.h +A ActiveSync/iCalTimeZone+ActiveSync.m +A ActiveSync/iCalToDo+ActiveSync.h +A ActiveSync/iCalToDo+ActiveSync.m +A ActiveSync/product.plist + +commit 7355eae1dc7a04eac5cc46b4e95e57a461a71db6 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:10:53 2014 -0500 + + Now able to set Active Sync metadata in the user settings + +M SoObjects/SOGo/SOGoUserSettings.h +M SoObjects/SOGo/SOGoUserSettings.m + +commit 1d9febb51139924ac7563c43e7ceeb9d2f54f4b2 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:10:16 2014 -0500 + + Correctly handle the Active Sync requests + +M SoObjects/SOGo/SOGoUserFolder.h +M SoObjects/SOGo/WORequest+SOGo.m + +commit e21b30d768c06b2507dba6e2b35e511534affab0 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:09:32 2014 -0500 + + Code cleanups + +M SoObjects/SOGo/SOGoObject.h + +commit ae200360ba17719e8794231b5d43c4f232b4aa71 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:09:02 2014 -0500 + + Code cleanups + +M SoObjects/SOGo/SOGoGCSFolder.h +M SoObjects/SOGo/SOGoGCSFolder.m + +commit e217ffb6c4896c10d6ddaa784880a74d6e19fdcd +Author: Ludovic Marcotte +Date: Fri Jan 10 14:08:12 2014 -0500 + + Code generalization to be usable from the ActiveSync bundle + +M SoObjects/Contacts/SOGoContactFolders.h +M SoObjects/Contacts/SOGoContactFolders.m +M SoObjects/Contacts/SOGoContactGCSEntry.m + +commit a4a3a735b47d14af91ca2b822eb74292f9930cfe +Author: Ludovic Marcotte +Date: Fri Jan 10 14:06:53 2014 -0500 + + Moved the folder metadata generation to SoObject and added 'tag based' sync'ing support for IMAP + +M SoObjects/Mailer/GNUmakefile +M SoObjects/Mailer/SOGoMailAccount.h +M SoObjects/Mailer/SOGoMailAccount.m +M SoObjects/Mailer/SOGoMailFolder.h +M SoObjects/Mailer/SOGoMailFolder.m +M SoObjects/Mailer/SOGoMailObject.h +M UI/MailerUI/UIxMailAccountActions.h +M UI/MailerUI/UIxMailAccountActions.m +M UI/MailerUI/UIxMailFolderActions.m + +commit 13721b961b2224b3918b421c13816d045c86e322 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:03:50 2014 -0500 + + Add an easy way to get the personal contact folder, just like we have for calendars + +M SoObjects/SOGo/SOGoUser.h +M SoObjects/SOGo/SOGoUser.m + +commit 689a1e94e0e8b3e8b60debcffd413765629c8d94 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:02:32 2014 -0500 + + Sample Active Sync configuration + +M Apache/SOGo.conf + +commit afcd92fb719ca01767519d19b61320f8e8315e05 +Author: Ludovic Marcotte +Date: Fri Jan 10 14:01:39 2014 -0500 + + Load the ActiveSync bundle code and bind it + +M UI/MainUI/GNUmakefile +A UI/MainUI/SOGoMicrosoftActiveSyncActions.m +M UI/MainUI/product.plist + +commit 7260c07628c90450035381f975141f230522dc1e +Author: Ludovic Marcotte +Date: Thu Jan 9 21:10:48 2014 -0500 + + More cleanups + +M UI/MainUI/SOGoSAML2Actions.m + +commit a24d809e4061056be6082b310bae1b9d8edb6883 +Author: Ludovic Marcotte +Date: Thu Jan 9 21:06:31 2014 -0500 + + Minor code/copyright/authors cleanups + +M Main/SOGo+DAV.m +M Main/SOGo.m +M OpenChange/MAPIStoreMailFolder.m +M SoObjects/Appointments/MSExchangeFreeBusy.m +M SoObjects/Appointments/SOGoCalendarComponent.h +M SoObjects/Appointments/SOGoTaskObject.h +M SoObjects/Contacts/NGVCard+SOGo.m +M SoObjects/Contacts/SOGoContactGCSEntry.h +M SoObjects/Contacts/SOGoContactGCSFolder.m +M SoObjects/Contacts/SOGoContactObject.h +M SoObjects/Contacts/SOGoContactSourceFolder.h +M SoObjects/Contacts/SOGoContactSourceFolder.m +M SoObjects/Mailer/SOGoMailManager.h +M SoObjects/SOGo/NSArray+DAV.m +M SoObjects/SOGo/SOGoDAVAuthenticator.h +M SoObjects/SOGo/SOGoFolder.h +M SoObjects/SOGo/SOGoParentFolder.h +M SoObjects/SOGo/SOGoUserDefaults.h +M SoObjects/SOGo/SOGoUserFolder.h +M SoObjects/SOGo/SOGoWebAuthenticator.h +M SoObjects/SOGo/SOGoWebAuthenticator.m +M UI/Contacts/UIxContactView.m +M UI/MailPartViewers/UIxMailRenderingContext.m +M UI/MainUI/SOGoRootPage.m + +commit ed97407578d74351e8e2fb2002a949a0168039f2 +Author: Ludovic Marcotte +Date: Thu Jan 9 20:42:16 2014 -0500 + + Dropped old data types + +M UI/WebServerResources/MailerUI.js + +commit ca541d7299f4aa93dbf8eeaced497ddcbebfab19 +Author: Ludovic Marcotte +Date: Thu Jan 9 20:41:16 2014 -0500 + + Cleanups in conf file + +M Apache/SOGo.conf + +commit 2d683ffc7791d766f353be39391cca20c012bcde +Author: Ludovic Marcotte +Date: Thu Jan 9 20:40:25 2014 -0500 + + Always capitalize HTTP headers + +M SoObjects/SOGo/SOGoDefaults.plist + +commit d603a8672e72aeb0d3d51decd1455f9dfc193cac +Author: Francis Lachapelle +Date: Thu Jan 9 09:13:11 2014 -0500 + + Consider 'background' attribute as unsafe + + When loading a message, background attributes will be disabled if the + user as chosen to not automatically load external images. + + Fixes #2437 + +M NEWS +M UI/MailPartViewers/UIxMailPartHTMLViewer.m +M UI/WebServerResources/MailerUI.js + +commit d51e1da57f6ce9f9dabffcb5cf3480a82be501aa +Author: Francis Lachapelle +Date: Wed Jan 8 15:02:41 2014 -0500 + + Increase height of signature editor + +M UI/WebServerResources/UIxPreferences.css +M UI/WebServerResources/UIxPreferences.js + +commit 80fd439dea8a87727f0bc2f6a947d12450a68006 +Author: Francis Lachapelle +Date: Wed Jan 8 14:40:39 2014 -0500 + + Fix message forwarding as attachment + + Restored the filename of the message source in the body part + content-disposition header. + Also forced the mail to be saved to the mailstore immediately in order + to have a clickable link to the attached message. + +M SoObjects/Mailer/SOGoDraftObject.m + +commit a0d7f184fe793f103ab1f05e49ea0366f4d4e738 +Author: Francis Lachapelle +Date: Tue Jan 7 11:51:35 2014 -0500 + + Update French translation + +M UI/Common/French.lproj/Localizable.strings +M UI/MailerUI/French.lproj/Localizable.strings + +commit fe2826ca76cb3963c2fdaaedeb751049824813cc +Author: Francis Lachapelle +Date: Tue Jan 7 11:11:39 2014 -0500 + + Draft: Improve error handling when attaching files + +M UI/Common/English.lproj/Localizable.strings +M UI/MailerUI/English.lproj/Localizable.strings +M UI/WebServerResources/UIxMailEditor.js + +commit ff9ea3b779b27baa9ddff5b7a79942980ab0f28a +Author: Francis Lachapelle +Date: Tue Jan 7 10:28:53 2014 -0500 + + Don't alter the draft when saving it + + We must not extract inline HTML images when simply saving a draft. The + images extraction process must only be performed when sending the + message. + +M SoObjects/Mailer/SOGoDraftObject.m + +commit e70793e7aee7fa02141fed97a174778304d4834e +Author: Francis Lachapelle +Date: Mon Jan 6 15:09:09 2014 -0500 + + Don't follow link of attachment not yet uploaded + +M UI/WebServerResources/UIxMailEditor.js + +commit 0fdea48ce85afe1598cc199590f3b8afab4df30b +Author: Francis Lachapelle +Date: Mon Jan 6 15:08:45 2014 -0500 + + Restore CSS of disabled menu options + +M UI/WebServerResources/generic.css + +commit 2b53705d449089a66f23cd055f6c0c92a01890c9 +Author: Francis Lachapelle +Date: Mon Jan 6 14:32:58 2014 -0500 + + Untabify + +M SoObjects/Mailer/NSString+Mail.m + +commit b716331d8ee99123aeb3e9b51a13baa8dde8ba66 +Author: Francis Lachapelle +Date: Mon Jan 6 14:20:47 2014 -0500 + + Append tags when using a sanitizerContentHandler + +M SoObjects/Mailer/NSString+Mail.m + +commit 586d66b1134889ba1ff97ea2b0d55cdf3b3d82e7 +Author: Jean Raby +Date: Mon Jan 6 13:28:47 2014 -0500 + + Updated description of SOGoUIAdditionalJSFiles + + Document that the files must be placed under the WebServerResources directory + +M Documentation/SOGo Installation Guide.odt + +commit 5756defbf1d3fac4d6923732a0a7986f9ce6e9ec +Author: Francis Lachapelle +Date: Mon Jan 6 11:10:28 2014 -0500 + + Mail editor: don't give focus to file input field. + +M UI/Templates/MailerUI/UIxMailEditor.wox + +commit 7130cec4d1eff93ce6acc84e4fda5258878e5082 +Author: Francis Lachapelle +Date: Fri Jan 3 16:56:51 2014 -0500 + + Move & copy messages between different accounts + +M NEWS +M SoObjects/Mailer/SOGoMailFolder.m +M UI/Templates/MailerUI/UIxMailMainFrame.wox +M UI/WebServerResources/MailerUI.js + +commit 009cfccb2c8ea94e5a235964c4214553ac54c299 +Author: Francis Lachapelle +Date: Fri Jan 3 14:33:27 2014 -0500 + + Fix JS error when saving/sending plain text msg + +M UI/WebServerResources/UIxMailEditor.js + +commit 09e14df79b4ba9cd9a319b41f2d386ba50a5cc63 +Author: Francis Lachapelle +Date: Fri Jan 3 14:32:48 2014 -0500 + + Preferences: fix display of calendar categories + +M UI/WebServerResources/UIxPreferences.css + +commit ba29e7620d895ef183898465c6757dbd211d1daa +Author: Francis Lachapelle +Date: Fri Jan 3 14:32:01 2014 -0500 + + Update French translation + +M UI/MailerUI/French.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings + +commit 32516b162e5f719ccc86e0dfcc40654a8eb17390 +Author: Francis Lachapelle +Date: Tue Dec 31 15:52:15 2013 -0500 + + Update textarea before saving/sending HTML message + +M UI/WebServerResources/UIxMailEditor.js + +commit a8e780516bbac198b68c75638916ebb93c42af2e +Author: Francis Lachapelle +Date: Mon Dec 23 15:54:22 2013 -0500 + + Fix display of "edit draft" and "load images" + + The buttons were placed one over the other. + +M UI/Templates/MailerUI/UIxMailView.wox +M UI/WebServerResources/MailerUI.css + +commit bd7ad5be16b98848ac14e06546fb2db8a7927182 +Author: Francis Lachapelle +Date: Mon Dec 23 15:51:36 2013 -0500 + + Fix initialization of arrays in NSString+Mail.m + +M SoObjects/Mailer/NSString+Mail.m + +commit bed57af0157cc167bda6d2651b8f07b23eec3f4a +Author: Francis Lachapelle +Date: Fri Dec 20 16:18:19 2013 -0500 + + Improve display of linked attachments + +M UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox + +commit 5f369f201d0020c3d4720fd710eceaebe7dd10e5 +Author: Francis Lachapelle +Date: Fri Dec 20 15:37:01 2013 -0500 + + Add links to download one or all attachments + + Also removed the contextual menu over file attachments and changed the + label color when moving over the file attachments. + +M NEWS +M UI/MailerUI/English.lproj/Localizable.strings +M UI/MailerUI/UIxMailEditor.m +M UI/MailerUI/UIxMailView.m +M UI/Templates/MailerUI/UIxMailMainFrame.wox +M UI/Templates/MailerUI/UIxMailPopupView.wox +M UI/Templates/MailerUI/UIxMailView.wox +M UI/WebServerResources/MailerUI.css +M UI/WebServerResources/MailerUI.js +M UI/WebServerResources/generic.css +M UI/WebServerResources/generic.js + +commit dc21c723f695524bc8d2bf9088f3575b518b1e46 +Author: Francis Lachapelle +Date: Fri Dec 20 15:20:16 2013 -0500 + + Move method fetchFileAttachmentKeys from category + + Moved fetchFileAttachmentKeys from SOGoDraftObjectExtensions to + SOGoDraftObject. Renamed fetchAttachmentIds to fetchFileAttachmentIds + for consistency. + +M SoObjects/Mailer/SOGoMailObject+Draft.h +M SoObjects/Mailer/SOGoMailObject+Draft.m +M SoObjects/Mailer/SOGoMailObject.h +M SoObjects/Mailer/SOGoMailObject.m +M UI/MailPartViewers/UIxMailPartHTMLViewer.m + +commit 1f7994d1bf9302e02940de5fc75c0a53a9911d75 +Author: Francis Lachapelle +Date: Wed Dec 18 22:16:28 2013 -0500 + + Respect locale in time format of attendees window + +M NEWS +M UI/Templates/SchedulerUI/UIxAttendeesEditor.wox +M UI/WebServerResources/JavascriptAPIExtensions.js +M UI/WebServerResources/UIxAttendeesEditor.js + +commit 7369a82bab05f5044432343de49c70a7600166e5 +Author: Francis Lachapelle +Date: Wed Dec 18 16:36:49 2013 -0500 + + Improve upload of attachments to messages + +M NEWS +M UI/MailerUI/Toolbars/SOGoDraftObject.toolbar +M UI/MailerUI/UIxMailEditor.m +M UI/Templates/MailerUI/UIxMailEditor.wox +M UI/WebServerResources/UIxMailEditor.css +M UI/WebServerResources/UIxMailEditor.js +A UI/WebServerResources/attachment.png +M UI/WebServerResources/generic.css +A UI/WebServerResources/jquery.fileupload.css +A UI/WebServerResources/jquery.fileupload.js +A UI/WebServerResources/jquery.iframe-transport.js +A UI/WebServerResources/upload_document.png + +commit 1a900b05d9cb6def48849209cde72225ab65fcff +Author: Francis Lachapelle +Date: Wed Dec 18 14:12:29 2013 -0500 + + DraftObject: return more attachments attributes + +M SoObjects/Mailer/SOGoDraftObject.h +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/Mailer/SOGoMailObject+Draft.m + +commit a8e3418a4cac2812c2fe1234ef0c95b88100c9d5 +Author: Francis Lachapelle +Date: Wed Dec 18 08:38:03 2013 -0500 + + Use the UIxMailSizeFormatter in messages listing + +M UI/MailPartViewers/UIxMailSizeFormatter.m +M UI/MailerUI/UIxMailListActions.h +M UI/MailerUI/UIxMailListActions.m +M UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox +M UI/WebServerResources/generic.css + +commit 59ce12a51b352bc800a3347b00df80a296a84e62 +Author: Francis Lachapelle +Date: Tue Dec 17 16:39:30 2013 -0500 + + Load XMLHttpRequest conditionally (< IE9) + +M NEWS +M UI/SOGoElements/SOGoIEConditional.h +M UI/SOGoElements/SOGoIEConditional.m +M UI/Templates/UIxPageFrame.wox +M UI/WebServerResources/XMLHttpRequest.js + +commit 08ab36244c1b7e4144f06948fb4c981540032248 +Author: Ludovic Marcotte +Date: Mon Dec 16 17:48:23 2013 -0500 + + Improved the logic behind refusing too-many submitted mails. + +M UI/MailerUI/UIxMailEditor.m + +commit 9ea880e80bc041e538d038c7a3246861df9bdb2e +Author: Jean Raby +Date: Wed Dec 11 14:36:28 2013 -0500 + + fix backup path + +M Scripts/sogo.cron + +commit 7d0a9aa1b7f88cfb03353a1fb18b8d74e9f8f550 +Author: Ludovic Marcotte +Date: Tue Dec 10 20:09:32 2013 -0500 + + Cleanups. + +M UI/Templates/UIxWinClose.wox + +commit 2fa654fadbd6a7a8086329c0a4435b6b7d091ff3 +Author: Jean Raby +Date: Tue Dec 10 15:55:44 2013 -0500 + + sogo.conf: fix typo and add AD/Samba4 example + +M Scripts/sogo.conf + +commit 4a98e5b521dcd64fef7d08521d10d5dd32e3817d +Author: Ludovic Marcotte +Date: Mon Dec 9 10:31:34 2013 -0500 + + Don't append unknown objects to the REPORT result. + +M SoObjects/Contacts/SOGoContactSourceFolder.m + +commit e5103faed49fe86e222c86d88bdc8afda7061ba0 +Author: Francis Lachapelle +Date: Thu Dec 5 21:33:09 2013 -0500 + + Update Finnish translation + +M NEWS +M UI/Common/Finnish.lproj/Localizable.strings +M UI/MailerUI/Finnish.lproj/Localizable.strings +M UI/PreferencesUI/Finnish.lproj/Localizable.strings + +commit f9ad9ea8150ab123ee3d353f78d8d144d4eeac6d +Author: Francis Lachapelle +Date: Thu Dec 5 21:10:03 2013 -0500 + + Bump version to 2.1.2 + +M Version + +commit cfbd53e374a75a70e6d16875d5eb880d3de03d23 +Author: Francis Lachapelle +Date: Thu Dec 5 09:00:01 2013 -0500 + + Fix initialization of preferences module + + Don't try to initialize the tabs controller on the mail options tab if + the mail module is disabled. + +M UI/WebServerResources/UIxPreferences.js + +commit f2beabec8887981ee91362295b38db5a62d933c9 +Author: Jean Raby +Date: Wed Dec 4 19:50:22 2013 -0500 + + document organization and country ldap mapping + +M Documentation/SOGo Installation Guide.odt + +commit eaa0fb3cf1ec654e79a1a9522376a4ad2902c285 +Author: Jean Raby +Date: Wed Dec 4 11:11:10 2013 -0500 + + Update NEWS CKEditor is in 2.1.1b + +M NEWS + +commit db56d1fa6ce2736b1cefd0f931d5747899a7860d +Author: Jean Raby +Date: Wed Dec 4 10:33:48 2013 -0500 + + Bump version to 2.1.1b + adjust NEWS + +M NEWS +M Version + +commit 37de8c6141a04fdd76f5d1b439f937aab1d2ec0b +Author: Francis Lachapelle +Date: Mon Dec 2 13:41:39 2013 -0500 + + CKEditor: don't filter tags + +M UI/WebServerResources/ckeditor/config.js + +commit b0eb34f6b72f0619f277ef34d9ef3ad7584709ca +Author: Francis Lachapelle +Date: Mon Dec 2 11:58:28 2013 -0500 + + Add 'div' plugin to CKEditor + +M UI/WebServerResources/ckeditor/build-config.js +M UI/WebServerResources/ckeditor/ckeditor.js +M UI/WebServerResources/ckeditor/config.js +M UI/WebServerResources/ckeditor/lang/ar.js +M UI/WebServerResources/ckeditor/lang/ca.js +M UI/WebServerResources/ckeditor/lang/cs.js +M UI/WebServerResources/ckeditor/lang/cy.js +M UI/WebServerResources/ckeditor/lang/da.js +M UI/WebServerResources/ckeditor/lang/de.js +M UI/WebServerResources/ckeditor/lang/en.js +M UI/WebServerResources/ckeditor/lang/es.js +M UI/WebServerResources/ckeditor/lang/fi.js +M UI/WebServerResources/ckeditor/lang/fr.js +M UI/WebServerResources/ckeditor/lang/hu.js +M UI/WebServerResources/ckeditor/lang/is.js +M UI/WebServerResources/ckeditor/lang/it.js +M UI/WebServerResources/ckeditor/lang/nb.js +M UI/WebServerResources/ckeditor/lang/nl.js +M UI/WebServerResources/ckeditor/lang/no.js +M UI/WebServerResources/ckeditor/lang/pl.js +M UI/WebServerResources/ckeditor/lang/pt-br.js +M UI/WebServerResources/ckeditor/lang/ru.js +M UI/WebServerResources/ckeditor/lang/sk.js +M UI/WebServerResources/ckeditor/lang/sv.js +M UI/WebServerResources/ckeditor/lang/uk.js +A UI/WebServerResources/ckeditor/plugins/div/dialogs/div.js +M UI/WebServerResources/ckeditor/plugins/icons.png +M UI/WebServerResources/ckeditor/plugins/icons_hidpi.png +M UI/WebServerResources/ckeditor/skins/moono/editor.css +M UI/WebServerResources/ckeditor/skins/moono/editor_gecko.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/editor_iequirks.css +M UI/WebServerResources/ckeditor/skins/moono/icons.png +M UI/WebServerResources/ckeditor/skins/moono/icons_hidpi.png + +commit 1cf696f57c7347040bff272a7873d7a5ac8d9fbe +Author: Francis Lachapelle +Date: Mon Nov 25 09:52:18 2013 -0500 + + Mail composition: add text part before html part + + Fixes #2512 + +M NEWS +M SoObjects/Mailer/SOGoDraftObject.m + +commit 2114a48222b73c1edad3e2dbdbf39898103b23eb +Author: Francis Lachapelle +Date: Fri Nov 22 15:03:57 2013 -0500 + + Update NEWS file + +M NEWS + +commit 384d998c471f52eeed24b749f64873374e46ebfc +Author: Francis Lachapelle +Date: Fri Nov 22 14:42:16 2013 -0500 + + Cleanup wox templates + +M UI/Templates/MailerUI/UIxMailWindowCloser.wox +M UI/Templates/PreferencesUI/UIxFilterEditor.wox + +commit 2211f6d168e84cc9d0157e400276cfdaae242647 +Author: Francis Lachapelle +Date: Fri Nov 22 14:39:24 2013 -0500 + + Move mail tags strings to UI/Common + +M SoObjects/Mailer/SOGoMailLabel.m +M UI/Common/Arabic.lproj/Localizable.strings +M UI/Common/BrazilianPortuguese.lproj/Localizable.strings +M UI/Common/Catalan.lproj/Localizable.strings +M UI/Common/Czech.lproj/Localizable.strings +M UI/Common/Danish.lproj/Localizable.strings +M UI/Common/Dutch.lproj/Localizable.strings +M UI/Common/English.lproj/Localizable.strings +M UI/Common/Finnish.lproj/Localizable.strings +M UI/Common/French.lproj/Localizable.strings +M UI/Common/German.lproj/Localizable.strings +M UI/Common/Hungarian.lproj/Localizable.strings +M UI/Common/Icelandic.lproj/Localizable.strings +M UI/Common/Italian.lproj/Localizable.strings +M UI/Common/NorwegianBokmal.lproj/Localizable.strings +M UI/Common/NorwegianNynorsk.lproj/Localizable.strings +M UI/Common/Polish.lproj/Localizable.strings +M UI/Common/Russian.lproj/Localizable.strings +M UI/Common/Slovak.lproj/Localizable.strings +M UI/Common/SpanishArgentina.lproj/Localizable.strings +M UI/Common/SpanishSpain.lproj/Localizable.strings +M UI/Common/Swedish.lproj/Localizable.strings +M UI/Common/Ukrainian.lproj/Localizable.strings +M UI/Common/Welsh.lproj/Localizable.strings +M UI/MailerUI/Arabic.lproj/Localizable.strings +M UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings +M UI/MailerUI/Catalan.lproj/Localizable.strings +M UI/MailerUI/Czech.lproj/Localizable.strings +M UI/MailerUI/Danish.lproj/Localizable.strings +M UI/MailerUI/Dutch.lproj/Localizable.strings +M UI/MailerUI/English.lproj/Localizable.strings +M UI/MailerUI/Finnish.lproj/Localizable.strings +M UI/MailerUI/French.lproj/Localizable.strings +M UI/MailerUI/German.lproj/Localizable.strings +M UI/MailerUI/Hungarian.lproj/Localizable.strings +M UI/MailerUI/Icelandic.lproj/Localizable.strings +M UI/MailerUI/Italian.lproj/Localizable.strings +M UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings +M UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings +M UI/MailerUI/Polish.lproj/Localizable.strings +M UI/MailerUI/Russian.lproj/Localizable.strings +M UI/MailerUI/Slovak.lproj/Localizable.strings +M UI/MailerUI/SpanishArgentina.lproj/Localizable.strings +M UI/MailerUI/SpanishSpain.lproj/Localizable.strings +M UI/MailerUI/Swedish.lproj/Localizable.strings +M UI/MailerUI/Ukrainian.lproj/Localizable.strings +M UI/MailerUI/Welsh.lproj/Localizable.strings +M UI/PreferencesUI/Arabic.lproj/Localizable.strings +M UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings +M UI/PreferencesUI/Catalan.lproj/Localizable.strings +M UI/PreferencesUI/Czech.lproj/Localizable.strings +M UI/PreferencesUI/Danish.lproj/Localizable.strings +M UI/PreferencesUI/Dutch.lproj/Localizable.strings +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/Finnish.lproj/Localizable.strings +M UI/PreferencesUI/French.lproj/Localizable.strings +M UI/PreferencesUI/German.lproj/Localizable.strings +M UI/PreferencesUI/Hungarian.lproj/Localizable.strings +M UI/PreferencesUI/Icelandic.lproj/Localizable.strings +M UI/PreferencesUI/Italian.lproj/Localizable.strings +M UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings +M UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings +M UI/PreferencesUI/Polish.lproj/Localizable.strings +M UI/PreferencesUI/Russian.lproj/Localizable.strings +M UI/PreferencesUI/Slovak.lproj/Localizable.strings +M UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings +M UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +M UI/PreferencesUI/Swedish.lproj/Localizable.strings +M UI/PreferencesUI/Ukrainian.lproj/Localizable.strings +M UI/PreferencesUI/Welsh.lproj/Localizable.strings +M UI/WebServerResources/MailerUI.js + +commit 32ba01315f13631ff40d00081f4e481c3614af72 +Author: Ludovic Marcotte +Date: Fri Nov 22 11:29:24 2013 -0500 + + Init local variable to avoid potential crasher. + +M OpenChange/MAPIStoreGCSBaseContext.m + +commit 682ed767d956f0650de36e352ff3285856fdc0b3 +Author: Francis Lachapelle +Date: Thu Nov 21 08:50:24 2013 -0500 + + Fix the Sieve filters editor with new mail flags + +M UI/Templates/PreferencesUI/UIxFilterEditor.wox +M UI/WebServerResources/UIxFilterEditor.js + +commit 5536f8967eea7c441d4e89bd246d1c113d0d8445 +Author: Jean Raby +Date: Thu Nov 21 07:11:05 2013 -0500 + + don't escape html in label names + +M UI/Templates/PreferencesUI/UIxFilterEditor.wox + +commit df3e9033f3723e63235ff4cc8fa9257fc2c6f176 +Author: Ludovic Marcotte +Date: Wed Nov 20 17:42:11 2013 -0500 + + Added calendar default reminder support. + +M NEWS +M SoObjects/SOGo/SOGoDefaults.plist +M SoObjects/SOGo/SOGoUserDefaults.h +M SoObjects/SOGo/SOGoUserDefaults.m +M UI/PreferencesUI/English.lproj/Localizable.strings +M UI/PreferencesUI/UIxPreferences.m +M UI/Scheduler/UIxComponentEditor.h +M UI/Scheduler/UIxComponentEditor.m +M UI/Templates/PreferencesUI/UIxPreferences.wox +M UI/Templates/SchedulerUI/UIxComponentEditor.wox + +commit f167475c91d9481914e7ccfb930ae05963db2bd3 +Author: Francis Lachapelle +Date: Wed Nov 20 11:17:42 2013 -0500 + + Update CKEditor to version 4.3.0 + +M NEWS +M UI/WebServerResources/UIxMailEditor.js +M UI/WebServerResources/ckeditor/build-config.js +M UI/WebServerResources/ckeditor/ckeditor.js +M UI/WebServerResources/ckeditor/config.js +M UI/WebServerResources/ckeditor/contents.css +M UI/WebServerResources/ckeditor/plugins/image/dialogs/image.js +M UI/WebServerResources/ckeditor/plugins/link/dialogs/link.js +M UI/WebServerResources/ckeditor/plugins/table/dialogs/table.js +M UI/WebServerResources/ckeditor/skins/moono/dialog.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_iequirks.css +M UI/WebServerResources/ckeditor/skins/moono/dialog_opera.css +M UI/WebServerResources/ckeditor/skins/moono/editor.css +M UI/WebServerResources/ckeditor/skins/moono/editor_gecko.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie7.css +M UI/WebServerResources/ckeditor/skins/moono/editor_ie8.css +M UI/WebServerResources/ckeditor/skins/moono/editor_iequirks.css + +commit e4aedbac080d85ac4f927acb9e015b9e8839891b +Author: Ludovic Marcotte +Date: Wed Nov 20 08:56:29 2013 -0500 + + conversion to file attachments + CIDs. + +M NEWS +M SoObjects/Mailer/NSString+Mail.h +M SoObjects/Mailer/NSString+Mail.m +M SoObjects/Mailer/SOGoDraftObject.m +M SoObjects/Mailer/SOGoMailObject+Draft.m +M UI/MailPartViewers/UIxMailPartHTMLViewer.h +M UI/MailPartViewers/UIxMailPartHTMLViewer.m + +commit 6587f4f19319aea549ffdd8b4e6a8752c1b3095d +Author: Jean Raby +Date: Tue Nov 19 13:59:20 2013 -0500 + + Update ChangeLog + +M ChangeLog + commit c8a4ea5548e06de1341312a1cabb00fb0ef232e8 Author: Francis Lachapelle Date: Tue Nov 19 13:57:18 2013 -0500 diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index 1e8bf6000..f7dd2f676 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/Documentation/SOGo Mobile Devices Configuration.odt b/Documentation/SOGo Mobile Devices Configuration.odt index f50348e19..bb85b0b93 100644 Binary files a/Documentation/SOGo Mobile Devices Configuration.odt and b/Documentation/SOGo Mobile Devices Configuration.odt differ diff --git a/Documentation/SOGo Mozilla Thunderbird Configuration.odt b/Documentation/SOGo Mozilla Thunderbird Configuration.odt index b47b7f312..5f73e81c1 100644 Binary files a/Documentation/SOGo Mozilla Thunderbird Configuration.odt and b/Documentation/SOGo Mozilla Thunderbird Configuration.odt differ diff --git a/Documentation/SOGo Native Microsoft Outlook Configuration.odt b/Documentation/SOGo Native Microsoft Outlook Configuration.odt index 3c206b8f8..eeb4d1853 100644 Binary files a/Documentation/SOGo Native Microsoft Outlook Configuration.odt and b/Documentation/SOGo Native Microsoft Outlook Configuration.odt differ diff --git a/Documentation/architecture.png b/Documentation/architecture.png index 116059c9a..ef5cbf446 100644 Binary files a/Documentation/architecture.png and b/Documentation/architecture.png differ diff --git a/Documentation/openchange.png b/Documentation/openchange.png index 30e3df855..75c8f3036 100644 Binary files a/Documentation/openchange.png and b/Documentation/openchange.png differ diff --git a/Main/SOGo+DAV.m b/Main/SOGo+DAV.m index 67f0b3a6a..256abdba9 100644 --- a/Main/SOGo+DAV.m +++ b/Main/SOGo+DAV.m @@ -1,8 +1,6 @@ /* SOGo+DAV.m - this file is part of SOGo * - * Copyright (C) 2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-2013 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/Main/SOGo.m b/Main/SOGo.m index d8bc89623..2eb4a9bd8 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2005-2011 Inverse inc. + Copyright (C) 2005-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo diff --git a/NEWS b/NEWS index c5184cf7c..88fceeb4f 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,74 @@ +2.2.0 (2014-02-24) +------------------ + +New features + - initial implementation of Microsoft ActiveSync protocol + - it's now possible to set a default reminder for calendar components + using SOGoCalendarDefaultReminder + - select multiple files to attach to a message or drag'n'drop files onto the + mail editor; will also now display progress of uploads + - new popup menu to download all attachments of a mail + - move & copy messages between different accounts + - support for the Sieve 'body' extension (mail filter based on the body content) + +Enhancements + - we now automatically convert into file attachments + using CIDs to prevent Outlook issues + - updated French, Finnish, Polish, German, Russian, and Spanish (Spain) translations + - XMLHttpRequest.js is now loaded conditionaly (< IE9) + - format time in attendees invitation window according to the user's locale + - improved IE11 support + - respect signature placement when forwarding a message + - respect Sieve server capabilities + - encode messages in quoted-printable when content is bigger than 72 bytes + - we now use binary encoding in memcached (#2587) + - warn user when overbooking a resource by creating an event in its calendar (#2541) + - converted JavaScript alerts to inline CSS dialogs in appointment editor + - visually identify users with no freebusy information in autocompletion widget of attendees editor (#2565) + - respect occurences of recurrent events when deleting selected events (#1950) + - improved confirmation dialog box when deleting events and tasks + - moved the DN cache to SOGoCache - avoiding sogod restarts after RDN operations + - don't use the HTML editor with Internet Explorer 7 + - add message-id header to appointment notifications (#2535) + - detect URLs in popup of events + - improved display of a contact (#2350) + +Bug fixes + - don't load 'background' attribute (#2437) + - fixed validation of subscribed folders (#2583) + - fixed display of folder names in messages filter editor (#2569) + - fixed contextual menu of the current calendar view (#2557) + - fixed handling of the '=' character in cards/events/tasks (#2505) + - simplify searches in the address book (#2187) + - warn user when dnd failed because of a resource conflict (#1613) + - respect the maximum number of bookings when viewing the freebusy information of a resource (#2560) + - encode HTML entities when forwarding an HTML message inline in plain text composition mode (#2411) + - encode HTML entities in JSON data (#2598) + - fixed handling of ACLs on shared calendars with multiple groups (#1854) + - fixed HTML formatting of appointment notifications for Outlook (#2233) + - replace slashes by dashes in filenames of attachments to avoid a 404 return code (#2537) + - avoid over-using LDAP connections when decomposing groups + - fixed display of a contact's birthday when not defined (#2503) + - fixed JavaScript error when switching views in calendar module (#2613) + +2.1.1b (2013-12-04) +------------------- + +Enhancements + - updated CKEditor to version 4.3.0 and added tab module + +Bug fixes + - HTML formatting is now retained when forwarding/replying to a mail using the HTML editor + - put the text part before the HTML part when composing mail to fix a display issue with Thunderbird (#2512) + +2.1.1a (2013-11-22) +------------------- + +Bug fixes + - fixed Sieve filters editor (#2504) + - moved missing translation to UI/Common (#2499) + - fixed potential crasher in OpenChange + 2.1.1 (2013-11-19) ------------------ diff --git a/OGoContentStore/appointment.ocs b/OGoContentStore/appointment.ocs index ee400953d..d17e7d69f 100644 --- a/OGoContentStore/appointment.ocs +++ b/OGoContentStore/appointment.ocs @@ -76,7 +76,7 @@ }, { columnName = c_participants; - sqlType = "VARCHAR(1000000)"; + sqlType = "TEXT"; allowsNull = YES; }, { @@ -91,7 +91,7 @@ }, { columnName = c_cycleinfo; - sqlType = "VARCHAR(1000000)"; + sqlType = "TEXT"; allowsNull = YES; }, { @@ -126,7 +126,7 @@ }, { columnName = c_partmails; - sqlType = "VARCHAR(100000)"; + sqlType = "TEXT"; allowsNull = YES; }, { diff --git a/OpenChange/MAPIStoreGCSBaseContext.m b/OpenChange/MAPIStoreGCSBaseContext.m index 4da73b349..cc09a41fe 100644 --- a/OpenChange/MAPIStoreGCSBaseContext.m +++ b/OpenChange/MAPIStoreGCSBaseContext.m @@ -122,6 +122,8 @@ [MAPIApp setUserContext: userContext]; moduleName = [self MAPIModuleName]; parentFolder = [[userContext rootFolders] objectForKey: moduleName]; + nameInContainer = nil; + if (![parentFolder newFolderWithName: folderName nameInContainer: &nameInContainer]) mapistoreURI = [NSString stringWithFormat: @"sogo://%@@%@/%@/", diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index 3aa86688d..bcc0253ef 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -1,8 +1,6 @@ /* MAPIStoreMailFolder.m - this file is part of SOGo * - * Copyright (C) 2011-2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2011-2013 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/SOPE/NGCards/NGCardsSaxHandler.m b/SOPE/NGCards/NGCardsSaxHandler.m index f8cddc53e..db2a46f5b 100644 --- a/SOPE/NGCards/NGCardsSaxHandler.m +++ b/SOPE/NGCards/NGCardsSaxHandler.m @@ -35,6 +35,8 @@ @"http://www.ietf.org/internet-drafts/draft-dawson-vcard-xml-dtd-03.txt" #endif +static NSArray *privilegedTagNames = nil; + @implementation NGCardsSaxHandler - (id) init @@ -42,6 +44,12 @@ if ((self = [super init])) topGroupClass = nil; + if (!privilegedTagNames) + { + privilegedTagNames = [NSArray arrayWithObjects: @"ADR", @"N", @"RRULE", nil]; + RETAIN(privilegedTagNames); + } + return self; } @@ -210,8 +218,12 @@ length: contentLength]; free (content); content = NULL; -// NSLog (@"content: '%@'", s); - contentValues = [s vCardSubvalues]; + //NSLog(@"content: '%@'", s); + if ([privilegedTagNames containsObject: [currentElement tag]]) + contentValues = [s vCardSubvalues]; + else + contentValues = [NSMutableDictionary dictionaryWithObject: [NSMutableArray arrayWithObject: [s asCardAttributeValues]] + forKey: @""]; } else contentValues = nil; diff --git a/Scripts/sogo.conf b/Scripts/sogo.conf index f502c6135..df878f811 100644 --- a/Scripts/sogo.conf +++ b/Scripts/sogo.conf @@ -33,8 +33,10 @@ //SOGoMailSpoolPath = /var/spool/sogo; //NGImap4ConnectionStringSeparator = "/"; + /* Notifications */ //SOGoAppointmentSendEMailNotifications = NO; //SOGoACLsSendEMailNotifications = NO; + //SOGoFoldersSendEMailNotifications = NO; /* Authentication */ //SOGoPasswordChangeEnabled = YES; @@ -58,13 +60,33 @@ // } //); + /* LDAP AD/Samba4 example */ + //SOGoUserSources = ( + // { + // type = ldap; + // CNFieldName = cn; + // UIDFieldName = sAMAccountName; + // baseDN = "CN=users,dc=domain,dc=tld"; + // bindDN = "CN=sogo,CN=users,DC=domain,DC=tld"; + // bindFields = (sAMAccountName, mail); + // bindPassword = password; + // canAuthenticate = YES; + // displayName = "Public"; + // hostname = ldap://127.0.0.1:389; + // filter = "mail = '*'"; + // id = directory; + // isAddressBook = YES; + // } + //); + + /* SQL authentication example */ /* These database columns MUST be present in the view/table: * c_uid - will be used for authentication - it's the username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries * c_password - password of the user, plain-text, md5 or sha encoded for now * c_cn - the user's common name - such as "John Doe" - * mail - the user's mail address + * mail - the user's mail address * See the installation guide for more details */ //SOGoUserSources = @@ -85,6 +107,7 @@ //SOGoForwardEnabled = YES; //SOGoSieveScriptsEnabled = YES; //SOGoMailAuxiliaryUserAccountsEnabled = YES; + //SOGoTrustProxyAuthentication = NO; /* General */ //SOGoLanguage = English; @@ -94,7 +117,9 @@ // ConfidentialDAndTViewer //); //SOGoSuperUsernames = (sogo1, sogo2); // This is an array - keep the parens! - //SxVMemLimit = 384 + //SxVMemLimit = 384; + //WOPidFile = "/var/run/sogo/sogo.pid"; + //SOGoMemcachedHost = "/var/run/memcached.sock"; /* Debug */ //SOGoDebugRequests = YES; diff --git a/Scripts/sogo.cron b/Scripts/sogo.cron index 5e1450093..08aaa96bb 100644 --- a/Scripts/sogo.cron +++ b/Scripts/sogo.cron @@ -15,7 +15,7 @@ #* * * * * sogo /usr/sbin/sogo-ealarms-notify # Daily backups -# - writes to /home/sogo/backups/ by default +# - writes to ~sogo/backups/ by default # - will keep 31 days worth of backups by default # - runs once a day by default, but can run more frequently # - make sure to set the path to sogo-backup.sh correctly diff --git a/Scripts/updates.php b/Scripts/updates.php index b49910cff..057d74353 100755 --- a/Scripts/updates.php +++ b/Scripts/updates.php @@ -28,22 +28,22 @@ $plugins = array( "sogo-connector@inverse.ca" => array( "application" => "thunderbird", - "version" => "17.0.3", - "filename" => "sogo-connector-17.0.3.xpi" ), + "version" => "24.0.4", + "filename" => "sogo-connector-24.0.4.xpi" ), "sogo-integrator@inverse.ca" => array( "application" => "thunderbird", - "version" => "17.0.3", - "filename" => "sogo-integrator-17.0.3.xpi" ), + "version" => "24.0.4", + "filename" => "sogo-integrator-24.0.4.xpi" ), "{e2fda1a4-762b-4020-b5ad-a41df1933103}" => array( "application" => "thunderbird", - "version" => "1.9", - "filename" => "lightning-1.9.xpi" ) + "version" => "2.6.4", + "filename" => "lightning-2.6.4.xpi" ) ); $applications = array( "thunderbird" => "{3550f703-e582-4d05-9a08-453d09bdfdc6} - 17.0 - 17.*" ); + 24.0 + 24.*" ); $pluginname = $_GET["plugin"]; $plugin =& $plugins[$pluginname]; diff --git a/SoObjects/Appointments/MSExchangeFreeBusy.m b/SoObjects/Appointments/MSExchangeFreeBusy.m index c11ca9f31..57ae29e5d 100644 --- a/SoObjects/Appointments/MSExchangeFreeBusy.m +++ b/SoObjects/Appointments/MSExchangeFreeBusy.m @@ -1,8 +1,6 @@ /* MSExchangeFreeBusy.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc. - * - * Author: Francis Lachapelle + * Copyright (C) 2012-2014 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/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 03cbb118b..8552ae72f 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -573,17 +573,27 @@ static iCalEvent *iCalEventK = nil; } grantedCount = [grantedClasses count]; if (grantedCount == 3) - filter = @""; + { + // User have access to all three classifications + filter = @""; + } else if (grantedCount == 2) - filter - = [NSString stringWithFormat: @"c_classification != %@", - [deniedClasses objectAtIndex: 0]]; + { + // User has access to all but one of the classifications + filter = [NSString stringWithFormat: @"c_classification != %@", + [deniedClasses objectAtIndex: 0]]; + } else if (grantedCount == 1) - filter - = [NSString stringWithFormat: @"c_classification = %@", - [grantedClasses objectAtIndex: 0]]; + { + // User has access to only one classification + filter = [NSString stringWithFormat: @"c_classification = %@", + [grantedClasses objectAtIndex: 0]]; + } else - filter = nil; + { + // User has access to no classification + filter = nil; + } return filter; } @@ -676,7 +686,6 @@ static iCalEvent *iCalEventK = nil; qualifier = nil; /* fetch non-recurrent apts first */ - records = [folder fetchFields: fields matchingQualifier: qualifier]; } else @@ -871,7 +880,6 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir { NSCalendarDate *recurrenceId; NSMutableDictionary *newRecord; - NSDictionary *oldRecord; NGCalendarDateRange *newRecordRange; NSComparisonResult compare; int recordIndex, secondsOffsetFromGMT; @@ -2533,7 +2541,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir unsigned int permStrIndex; [super initializeQuickTablesAclsInContext: localContext]; - /* We assume "userIsOwner" will be set after calling the super method. */ + /* We assume "userCanAccessAllObjects" will be set after calling the super method. */ if (!userCanAccessAllObjects) { login = [[localContext activeUser] login]; @@ -3123,6 +3131,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir { NSMutableArray *aclsForUser; NSArray *superAcls; + static NSArray *rolesClassifications = nil; superAcls = [super aclsForUser: uid forObjectAtPath: objectPathArray]; if ([uid isEqualToString: [self defaultUserID]]) @@ -3137,14 +3146,52 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir [aclsForUser addObject: SoRole_Authenticated]; } else - aclsForUser = (NSMutableArray *) superAcls; + { + aclsForUser = [NSMutableArray array]; + if (!rolesClassifications) + { + rolesClassifications = + [NSArray arrayWithObjects: + [NSArray arrayWithObjects: + SOGoCalendarRole_PublicModifier, + SOGoCalendarRole_PublicResponder, + SOGoCalendarRole_PublicViewer, + SOGoCalendarRole_PublicDAndTViewer, + nil], + [NSArray arrayWithObjects: + SOGoCalendarRole_ConfidentialModifier, + SOGoCalendarRole_ConfidentialResponder, + SOGoCalendarRole_ConfidentialViewer, + SOGoCalendarRole_ConfidentialDAndTViewer, + nil], + [NSArray arrayWithObjects: + SOGoCalendarRole_PrivateModifier, + SOGoCalendarRole_PrivateResponder, + SOGoCalendarRole_PrivateViewer, + SOGoCalendarRole_PrivateDAndTViewer, + nil], + [NSArray arrayWithObject: SOGoRole_ObjectCreator], + [NSArray arrayWithObject: SOGoRole_ObjectEraser], + nil]; + [rolesClassifications retain]; + } + // When a user is a member of many groups for which there are access rights, multiple access rights + // can be returned for each classification. In this case, we only keep the highest access right. + int i, count = [rolesClassifications count]; + NSString *role; + for (i = 0; i < count; i++) + { + role = [[rolesClassifications objectAtIndex: i] firstObjectCommonWithArray: superAcls]; + if (role) + [aclsForUser addObject: role]; + } + } return aclsForUser; } /* caldav-proxy */ -- (SOGoAppointmentProxyPermission) - proxyPermissionForUserWithLogin: (NSString *) login +- (SOGoAppointmentProxyPermission) proxyPermissionForUserWithLogin: (NSString *) login { SOGoAppointmentProxyPermission permission; NSArray *roles; diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index a2d279ec0..6deb4ca10 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -431,18 +431,38 @@ forEvent: (iCalEvent *) theEvent { iCalPerson *currentAttendee; + NSMutableArray *attendees; NSEnumerator *enumerator; NSString *currentUID; - SOGoUser *user; + SOGoUser *user, *currentUser, *ownerUser; + // Build a list of the attendees uids + attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]]; enumerator = [theAttendees objectEnumerator]; - while ((currentAttendee = [enumerator nextObject])) { currentUID = [currentAttendee uid]; - if (currentUID) - { + { + [attendees addObject: currentUID]; + } + } + + // If the active user is not the owner of the calendar, check possible conflict when + // the owner is a resource + currentUser = [context activeUser]; + if (!activeUserIsOwner && ![currentUser isSuperUser]) + { + ownerUser = [SOGoUser userWithLogin: owner]; + if ([ownerUser isResource]) + { + [attendees addObject: owner]; + } + } + + enumerator = [attendees objectEnumerator]; + while ((currentUID = [enumerator nextObject])) + { user = [SOGoUser userWithLogin: currentUID]; if ([user isResource]) @@ -461,8 +481,7 @@ 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 = [[SOGoUser userWithLogin: currentUID] - personalCalendarFolderInContext: context]; + folder = [user personalCalendarFolderInContext: context]; // Deny access to the resource if the ACLs don't allow the user if (![folder aclSQLListingFilter]) @@ -526,52 +545,64 @@ [fbInfo removeObjectAtIndex: i]; } } - - if ([fbInfo count]) - { - // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit - // is imposed) or if numberOfSimultaneousBookings is greater than the number of - // overlapping events - if ([user numberOfSimultaneousBookings] == 0 || - [user numberOfSimultaneousBookings] > [fbInfo count]) + + // Find the attendee associated to the current UID + for (i = 0; i < [theAttendees count]; i++) + { + currentAttendee = [theAttendees objectAtIndex: i]; + if ([[currentAttendee uid] isEqualToString: currentUID]) + break; + else + currentAttendee = nil; + } + + if (currentAttendee) + { + if ([fbInfo count]) { + // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit + // is imposed) or if numberOfSimultaneousBookings is greater than the number of + // overlapping events + if ([user numberOfSimultaneousBookings] == 0 || + [user numberOfSimultaneousBookings] > [fbInfo count]) + { + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } + else + { + iCalCalendar *calendar; + 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]; + + 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 + { + // 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]; } - else - { - iCalCalendar *calendar; - 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]; - - 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 - { - // 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]; - } - } - } + } + } } return nil; @@ -787,7 +818,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent [self expandGroupsInEvent: newEvent]; - // We first update the event. It is important to this initially + // We first update the event. It is important to do this initially // as the event's UID might get modified. [super updateComponent: newEvent]; @@ -795,13 +826,14 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { // New event -- send invitation to all attendees attendees = [newEvent attendeesWithoutUser: ownerUser]; + + // We catch conflicts and abort the save process immediately + // in case of one with resources + if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) + return ex; + if ([attendees count]) { - // We catch conflicts and abort the save process immediately - // in case of one with resources - if ((ex = [self _handleAddedUsers: attendees fromEvent: newEvent])) - return ex; - [self sendEMailUsingTemplateNamed: @"Invitation" forObject: [newEvent itipEntryWithMethod: @"request"] previousObject: nil @@ -1375,9 +1407,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent { // We generate the updated iCalendar file and we save it in // the database. We do this ONLY when using SOGo from the - // Web interface. Over DAV, it'll be handled directly in - // PUTAction: - if (![context request] || [[context request] handledByDefaultHandler]) + // Web interface or over ActiveSync. + // Over DAV, it'll be handled directly in PUTAction: + if (![context request] + || [[context request] handledByDefaultHandler] + || [[[context request] requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]) ex = [self saveContentString: [[event parent] versitString]]; } } diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index 3512339c2..61aca5c3d 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -1,9 +1,6 @@ /* SOGoCalendarComponent.h - this file is part of SOGo * - * Copyright (C) 2006-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2006-2013 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/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 2169f8f6e..d595bdba0 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -1,10 +1,6 @@ /* SOGoCalendarComponent.m - this file is part of SOGo * - * Copyright (C) 2006-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle - * Ludovic Marcotte + * Copyright (C) 2006-2014 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 @@ -22,8 +18,10 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import +#import #import #import @@ -62,6 +60,7 @@ #import #import #import +#import #import "SOGoAptMailICalReply.h" #import "SOGoAptMailNotification.h" @@ -219,29 +218,13 @@ - (NSString *) secureContentAsString { - iCalCalendar *tmpCalendar; iCalRepeatableEntityObject *tmpComponent; -// NSArray *roles; + iCalCalendar *tmpCalendar; + NSArray *allComponents; SoSecurityManager *sm; NSString *iCalString; -// uid = [[context activeUser] login]; -// roles = [self aclsForUser: uid]; -// if ([roles containsObject: SOGoCalendarRole_Organizer] -// || [roles containsObject: SOGoCalendarRole_Participant] -// || [roles containsObject: SOGoCalendarRole_ComponentViewer]) -// calContent = content; -// else if ([roles containsObject: SOGoCalendarRole_ComponentDAndTViewer]) -// { -// tmpCalendar = [[self calendar: NO] copy]; -// tmpComponent = (iCalRepeatableEntityObject *) -// [tmpCalendar firstChildWithTag: [self componentTag]]; -// [self _filterComponent: tmpComponent]; -// calContent = [tmpCalendar versitString]; -// [tmpCalendar release]; -// } -// else -// calContent = nil; + int i; sm = [SoSecurityManager sharedSecurityManager]; if (activeUserIsOwner @@ -253,14 +236,21 @@ onObject: self inContext: context]) { tmpCalendar = [[self calendar: NO secure: NO] mutableCopy]; - tmpComponent = (iCalRepeatableEntityObject *) - [tmpCalendar firstChildWithTag: [self componentTag]]; - [self _filterComponent: tmpComponent]; + + // We filter all components, in case we have RECURRENCE-ID + allComponents = [tmpCalendar childrenWithTag: [self componentTag]]; + + for (i = 0; i < [allComponents count]; i++) + { + tmpComponent = (iCalRepeatableEntityObject *)[allComponents objectAtIndex:i]; + [self _filterComponent: tmpComponent]; - // We add an additional header here to inform clients (if necessary) that - // we churned the content of the calendar. - [tmpComponent addChild: [CardElement simpleElementWithTag: @"X-SOGo-Secure" - value: @"YES"]]; + // We add an additional header here to inform clients (if necessary) that + // we churned the content of the calendar. + [tmpComponent addChild: [CardElement simpleElementWithTag: @"X-SOGo-Secure" + value: @"YES"]]; + } + iCalString = [tmpCalendar versitString]; [tmpCalendar release]; } @@ -499,18 +489,31 @@ NSMutableArray *allAttendees; iCalPerson *currentAttendee; NSEnumerator *enumerator; + NSAutoreleasePool *pool; SOGoGroup *group; BOOL eventWasModified; - unsigned int i; + unsigned int i, j; + domain = [[context activeUser] domain]; organizerEmail = [[theEvent organizer] rfc822Email]; eventWasModified = NO; allAttendees = [NSMutableArray arrayWithArray: [theEvent attendees]]; enumerator = [[theEvent attendees] objectEnumerator]; + + j = 0; + + pool = [[NSAutoreleasePool alloc] init]; + while ((currentAttendee = [enumerator nextObject])) { + if (j%5 == 0) + { + RELEASE(pool); + pool = [[NSAutoreleasePool alloc] init]; + } + group = [SOGoGroup groupWithEmail: [currentAttendee rfc822Email] inDomain: domain]; if (group) @@ -554,11 +557,15 @@ eventWasModified = YES; } } + + j++; } // while (currentAttendee ... if (eventWasModified) [theEvent setAttendees: allAttendees]; + RELEASE(pool); + return eventWasModified; } @@ -841,6 +848,7 @@ mailDate = [[NSCalendarDate date] rfc822DateString]; [headerMap setObject: mailDate forKey: @"date"]; [headerMap setObject: subject forKey: @"subject"]; + [headerMap setObject: [NSString generateMessageID] forKey: @"message-id"]; if ([msgType length] > 0) [headerMap setObject: msgType forKey: @"x-sogo-message-type"]; msg = [NGMimeMessage messageWithHeader: headerMap]; @@ -870,8 +878,7 @@ sendMimePart: msg toRecipients: [NSArray arrayWithObject: email] sender: shortSenderEmail - withAuthenticator: [self - authenticatorInContext: context] + withAuthenticator: [self authenticatorInContext: context] inContext: context]; } } @@ -896,7 +903,7 @@ SOGoDomainDefaults *dd; dd = [from domainDefaults]; - if ([dd appointmentSendEMailNotifications]) + if ([dd appointmentSendEMailNotifications] && [event isStillRelevant]) { /* get WOApplication instance */ app = [WOApplication application]; @@ -925,6 +932,7 @@ [headerMap setObject: mailDate forKey: @"date"]; [headerMap setObject: [[p getSubject] asQPSubjectString: @"UTF-8"] forKey: @"subject"]; + [headerMap setObject: [NSString generateMessageID] forKey: @"message-id"]; [headerMap setObject: @"1.0" forKey: @"MIME-Version"]; [headerMap setObject: @"multipart/mixed" forKey: @"content-type"]; [headerMap setObject: @"calendar:invitation-reply" forKey: @"x-sogo-message-type"]; @@ -1034,6 +1042,11 @@ // Recipient is fixed, which is the calendar owner ownerUser = [SOGoUser userWithLogin: self->owner]; recipientIdentity = [ownerUser primaryIdentity]; + + // Safety net for broken configurations + if (!recipientIdentity) + return; + recipientEmail = [recipientIdentity objectForKey: @"email"]; fullRecipientEmail = [recipientIdentity keysWithFormat: @"%{fullName} <%{email}>"]; @@ -1042,6 +1055,7 @@ mailDate = [[NSCalendarDate date] rfc822DateString]; [headerMap setObject: mailDate forKey: @"date"]; [headerMap setObject: [page getSubject] forKey: @"subject"]; + [headerMap setObject: [NSString generateMessageID] forKey: @"message-id"]; [headerMap setObject: @"1.0" forKey: @"MIME-Version"]; [headerMap setObject: @"text/html; charset=utf-8" forKey: @"content-type"]; diff --git a/SoObjects/Appointments/SOGoTaskObject.h b/SoObjects/Appointments/SOGoTaskObject.h index c369e6fb1..6b5d27d60 100644 --- a/SoObjects/Appointments/SOGoTaskObject.h +++ b/SoObjects/Appointments/SOGoTaskObject.h @@ -1,14 +1,16 @@ /* + + Copyright (C) 2006-2013 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - 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. diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.h b/SoObjects/Appointments/iCalEntityObject+SOGo.h index 8963a1d69..dee22f353 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.h +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.h @@ -1,6 +1,6 @@ /* iCalEntityObject+SOGo.h - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-2014 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/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index 70aeffaf0..97f9ce336 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -1,8 +1,6 @@ /* iCalEntityObject+SOGo.m - this file is part of SOGo * - * Copyright (C) 2007-2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2014 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/SoObjects/Contacts/NGVCard+SOGo.h b/SoObjects/Contacts/NGVCard+SOGo.h index 49dcd55d0..512037888 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.h +++ b/SoObjects/Contacts/NGVCard+SOGo.h @@ -1,8 +1,6 @@ /* NGVCard+SOGo.h - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Cyril Robert + * Copyright (C) 2009-2014 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 @@ -30,9 +28,23 @@ @interface NGVCard (SOGoExtensions) +- (CardElement *) elementWithTag: (NSString *) elementTag + ofType: (NSString *) type; + - (void) updateFromLDIFRecord: (NSDictionary *) ldifRecord; - (NSMutableDictionary *) asLDIFRecord; +- (NSString *) workCompany; +- (NSString *) fullName; +- (NSArray *) secondaryEmails; + +- (NSString *) workPhone; +- (NSString *) homePhone; +- (NSString *) fax; +- (NSString *) mobile; +- (NSString *) pager; +- (NSCalendarDate *) birthday; + @end #endif /* NGVCARD_SOGO_H */ diff --git a/SoObjects/Contacts/NGVCard+SOGo.m b/SoObjects/Contacts/NGVCard+SOGo.m index 1f26f81fd..e13cbfffe 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.m +++ b/SoObjects/Contacts/NGVCard+SOGo.m @@ -1,8 +1,6 @@ /* NGVCard+SOGo.m - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Cyril Robert + * Copyright (C) 2009-2014 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 @@ -29,6 +27,8 @@ #import #import +#import + #import "NSDictionary+LDIF.h" #import "NGVCard+SOGo.h" @@ -164,8 +164,8 @@ convention: @implementation NGVCard (SOGoExtensions) /* LDIF -> VCARD */ -- (CardElement *) _elementWithTag: (NSString *) elementTag - ofType: (NSString *) type +- (CardElement *) elementWithTag: (NSString *) elementTag + ofType: (NSString *) type { NSArray *elements; CardElement *element; @@ -188,16 +188,16 @@ convention: { CardElement *phone; - phone = [self _elementWithTag: @"tel" ofType: @"work"]; + phone = [self elementWithTag: @"tel" ofType: @"work"]; [phone setSingleValue: [ldifRecord objectForKey: @"telephonenumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"home"]; + phone = [self elementWithTag: @"tel" ofType: @"home"]; [phone setSingleValue: [ldifRecord objectForKey: @"homephone"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"cell"]; + phone = [self elementWithTag: @"tel" ofType: @"cell"]; [phone setSingleValue: [ldifRecord objectForKey: @"mobile"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"fax"]; + phone = [self elementWithTag: @"tel" ofType: @"fax"]; [phone setSingleValue: [ldifRecord objectForKey: @"facsimiletelephonenumber"] forKey: @""]; - phone = [self _elementWithTag: @"tel" ofType: @"pager"]; + phone = [self elementWithTag: @"tel" ofType: @"pager"]; [phone setSingleValue: [ldifRecord objectForKey: @"pager"] forKey: @""]; } @@ -205,9 +205,9 @@ convention: { CardElement *mail, *homeMail; - mail = [self _elementWithTag: @"email" ofType: @"work"]; + mail = [self elementWithTag: @"email" ofType: @"work"]; [mail setSingleValue: [ldifRecord objectForKey: @"mail"] forKey: @""]; - homeMail = [self _elementWithTag: @"email" ofType: @"home"]; + homeMail = [self elementWithTag: @"email" ofType: @"home"]; [homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""]; [[self uniqueChildWithTag: @"x-mozilla-html"] setSingleValue: [ldifRecord objectForKey: @"mozillausehtmlmail"] @@ -230,7 +230,7 @@ convention: [self setFn: [ldifRecord objectForKey: @"displayname"]]; [self setTitle: [ldifRecord objectForKey: @"title"]]; - element = [self _elementWithTag: @"adr" ofType: @"home"]; + element = [self elementWithTag: @"adr" ofType: @"home"]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet2"] atIndex: 1 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"mozillahomestreet"] @@ -244,7 +244,7 @@ convention: [element setSingleValue: [ldifRecord objectForKey: @"mozillahomecountryname"] atIndex: 6 forKey: @""]; - element = [self _elementWithTag: @"adr" ofType: @"work"]; + element = [self elementWithTag: @"adr" ofType: @"work"]; [element setSingleValue: [ldifRecord objectForKey: @"mozillaworkstreet2"] atIndex: 1 forKey: @""]; [element setSingleValue: [ldifRecord objectForKey: @"street"] @@ -268,9 +268,9 @@ convention: [self _setPhoneValues: ldifRecord]; [self _setEmails: ldifRecord]; - [[self _elementWithTag: @"url" ofType: @"home"] + [[self elementWithTag: @"url" ofType: @"home"] setSingleValue: [ldifRecord objectForKey: @"mozillahomeurl"] forKey: @""]; - [[self _elementWithTag: @"url" ofType: @"work"] + [[self elementWithTag: @"url" ofType: @"work"] setSingleValue: [ldifRecord objectForKey: @"mozillaworkurl"] forKey: @""]; [[self uniqueChildWithTag: @"x-aim"] @@ -639,4 +639,153 @@ convention: return ldifRecord; } +- (NSString *) workCompany +{ + CardElement *org; + NSString *company; + + org = [self org]; + company = [org flattenedValueAtIndex: 0 forKey: @""]; + if ([company length] == 0) + company = nil; + + return company; +} + +- (NSString *) fullName +{ + CardElement *n; + NSString *fn, *firstName, *lastName, *org; + + fn = [self fn]; + if ([fn length] == 0) + { + n = [self n]; + lastName = [n flattenedValueAtIndex: 0 forKey: @""]; + firstName = [n flattenedValueAtIndex: 1 forKey: @""]; + if ([firstName length] > 0) + { + if ([lastName length] > 0) + fn = [NSString stringWithFormat: @"%@ %@", firstName, lastName]; + else + fn = firstName; + } + else if ([lastName length] > 0) + fn = lastName; + else + { + n = [self org]; + org = [n flattenedValueAtIndex: 0 forKey: @""]; + fn = org; + } + } + + return fn; +} + +- (NSArray *) secondaryEmails +{ + NSMutableArray *emails; + NSString *email; + int i; + + emails = [NSMutableArray array]; + + [emails addObjectsFromArray: [self childrenWithTag: @"email"]]; + [emails removeObjectsInArray: [self childrenWithTag: @"email" + andAttribute: @"type" + havingValue: @"pref"]]; + + for (i = [emails count]-1; i >= 0; i--) + { + email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; + + if ([email caseInsensitiveCompare: [self preferredEMail]] == NSOrderedSame) + [emails removeObjectAtIndex: i]; + } + + return emails; +} + +- (NSString *) _phoneOfType: (NSString *) aType + excluding: (NSString *) aTypeToExclude +{ + NSArray *elements, *phones; + NSString *phone; + + phones = [self childrenWithTag: @"tel"]; + elements = [phones cardElementsWithAttribute: @"type" + havingValue: aType]; + + phone = nil; + + if ([elements count] > 0) + { + CardElement *ce; + int i; + + for (i = 0; i < [elements count]; i++) + { + ce = [elements objectAtIndex: i]; + phone = [ce flattenedValuesForKey: @""]; + + if (!aTypeToExclude) + break; + + if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) + break; + + phone = nil; + } + } + + return phone; +} + +- (NSString *) workPhone +{ + // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX + // one if we do see the FAX field BEFORE the WORK number. + return [self _phoneOfType: @"work" excluding: @"fax"]; +} + +- (NSString *) homePhone +{ + return [self _phoneOfType: @"home" excluding: @"fax"]; +} + +- (NSString *) fax +{ + return [self _phoneOfType: @"fax" excluding: nil]; +} + +- (NSString *) mobile +{ + return [self _phoneOfType: @"cell" excluding: nil]; +} + +- (NSString *) pager +{ + return [self _phoneOfType: @"pager" excluding: nil]; +} + +- (NSCalendarDate *) birthday +{ + NSString *bday, *value; + NSCalendarDate *date; + + bday = [self bday]; + date = nil; + if ([bday length] > 0) + { + // Expected format of BDAY is YYYY[-]MM[-]DD + value = [bday stringByReplacingString: @"-" withString: @""]; + date = [NSCalendarDate dateFromShortDateString: value + andShortTimeString: nil + inTimeZone: nil]; + } + + return date; +} + @end /* NGVCard */ diff --git a/SoObjects/Contacts/SOGoContactFolders.h b/SoObjects/Contacts/SOGoContactFolders.h index 820efb92d..1b685c85b 100644 --- a/SoObjects/Contacts/SOGoContactFolders.h +++ b/SoObjects/Contacts/SOGoContactFolders.h @@ -1,8 +1,6 @@ /* SOGoContactFolders.h - this file is part of SOGo * - * Copyright (C) 2006, 2007 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2013 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 @@ -31,6 +29,8 @@ withDisplayName: (NSString *) newDisplayName; - (NSException *) removeLDAPAddressBook: (NSString *) sourceID; +- (NSDictionary *) systemSources; + @end #endif /* SOGOCONTACTFOLDERS_H */ diff --git a/SoObjects/Contacts/SOGoContactFolders.m b/SoObjects/Contacts/SOGoContactFolders.m index 5bf9ba7bd..3c3e938f0 100644 --- a/SoObjects/Contacts/SOGoContactFolders.m +++ b/SoObjects/Contacts/SOGoContactFolders.m @@ -110,8 +110,9 @@ return result; } -- (NSException *) appendSystemSources +- (NSDictionary *) systemSources { + NSMutableDictionary *systemSources; SOGoUserManager *um; SOGoSystemDefaults *sd; NSEnumerator *sourceIDs, *domains; @@ -119,6 +120,8 @@ SOGoContactSourceFolder *currentFolder; SOGoUser *currentUser; + systemSources = [NSMutableDictionary dictionary]; + if (! ([[context request] isIPhoneAddressBookApp] && ![[context request] isAndroid])) { @@ -143,13 +146,20 @@ andDisplayName: srcDisplayName inContainer: self]; [currentFolder setSource: [um sourceWithID: currentSourceID]]; - [subFolders setObject: currentFolder forKey: currentSourceID]; + [systemSources setObject: currentFolder forKey: currentSourceID]; } domain = [domains nextObject]; } } } + return systemSources; +} + +- (NSException *) appendSystemSources +{ + [subFolders addEntriesFromDictionary: [self systemSources]]; + return nil; } diff --git a/SoObjects/Contacts/SOGoContactGCSEntry.h b/SoObjects/Contacts/SOGoContactGCSEntry.h index f9d6a7e18..20fb4961f 100644 --- a/SoObjects/Contacts/SOGoContactGCSEntry.h +++ b/SoObjects/Contacts/SOGoContactGCSEntry.h @@ -1,8 +1,6 @@ /* SOGoContactGCSEntry.h - this file is part of SOGo * - * Copyright (C) 2006-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2014 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/SoObjects/Contacts/SOGoContactGCSEntry.m b/SoObjects/Contacts/SOGoContactGCSEntry.m index 436cb92c4..6e3365d4e 100644 --- a/SoObjects/Contacts/SOGoContactGCSEntry.m +++ b/SoObjects/Contacts/SOGoContactGCSEntry.m @@ -1,8 +1,6 @@ /* SOGoContactGCSEntry.h - this file is part of SOGo * - * Copyright (C) 2006-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2014 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 @@ -159,6 +157,12 @@ return result; } +- (NSException *) saveComponent: (NGVCard *) newCard +{ + ASSIGN(card, newCard); + return [self save]; +} + - (NSException *) saveContentString: (NSString *) newContent baseVersion: (unsigned int) newVersion { diff --git a/SoObjects/Contacts/SOGoContactGCSFolder.m b/SoObjects/Contacts/SOGoContactGCSFolder.m index 7b8bfdac4..290f5d27c 100644 --- a/SoObjects/Contacts/SOGoContactGCSFolder.m +++ b/SoObjects/Contacts/SOGoContactGCSFolder.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2006-2011 Inverse inc. + Copyright (C) 2006-2013 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. diff --git a/SoObjects/Contacts/SOGoContactObject.h b/SoObjects/Contacts/SOGoContactObject.h index e49219a91..3131a9f42 100644 --- a/SoObjects/Contacts/SOGoContactObject.h +++ b/SoObjects/Contacts/SOGoContactObject.h @@ -1,14 +1,16 @@ /* + + Copyright (C) 2006-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - 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. diff --git a/SoObjects/Contacts/SOGoContactSourceFolder.h b/SoObjects/Contacts/SOGoContactSourceFolder.h index d37b1c631..610cf63df 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.h +++ b/SoObjects/Contacts/SOGoContactSourceFolder.h @@ -1,8 +1,6 @@ /* SOGoContactSourceFolder.h - this file is part of SOGo * - * Copyright (C) 2006-2009 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2013 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/SoObjects/Contacts/SOGoContactSourceFolder.m b/SoObjects/Contacts/SOGoContactSourceFolder.m index faa4f5ed5..19e726a96 100644 --- a/SoObjects/Contacts/SOGoContactSourceFolder.m +++ b/SoObjects/Contacts/SOGoContactSourceFolder.m @@ -1,8 +1,6 @@ /* SOGoContactSourceFolder.m - this file is part of SOGo * - * Copyright (C) 2006-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2013 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 @@ -534,14 +532,12 @@ [r appendString: [[object objectForKey: @"c_name"] stringByEscapingURL]]; [r appendString: @""]; -// NSLog (@"(appendPropstats...): %@", [NSDate date]); propstats = [self _propstats: properties count: propertiesCount ofObject: object]; max = [propstats count]; for (count = 0; count < max; count++) [self _appendPropstat: [propstats objectAtIndex: count] - toBuffer: r]; -// NSLog (@"/(appendPropstats...): %@", [NSDate date]); + toBuffer: r]; [r appendString: @""]; } @@ -562,8 +558,10 @@ NSString *url, *baseURL, *cname; NSString **propertiesArray; NSMutableString *buffer; - unsigned int count, max, propertiesCount; + NSDictionary *object; + unsigned int count, max, propertiesCount; + baseURL = [self davURLAsString]; #warning review this when fixing http://www.scalableogo.org/bugs/view.php?id=276 if (![baseURL hasSuffix: @"/"]) @@ -580,8 +578,9 @@ element = [refs objectAtIndex: count]; url = [[[element firstChild] nodeValue] stringByUnescapingURL]; cname = [self _deduceObjectNameFromURL: url fromBaseURL: baseURL]; - if (cname) - [self appendObject: [source lookupContactEntry: cname] + object = [source lookupContactEntry: cname]; + if (object) + [self appendObject: object properties: propertiesArray count: propertiesCount withBaseURL: baseURL diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index d96f672af..92ca7d45b 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -92,6 +92,10 @@ Mailer_RESOURCE_FILES += \ SOGoMailWelshReply.wo +Mailer_LANGUAGES = English French + +Mailer_LOCALIZED_RESOURCE_FILES = Localizable.strings + ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/ ADDITIONAL_INCLUDE_DIRS += $(shell xml2-config --cflags) ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/ diff --git a/SoObjects/Mailer/NSString+Mail.h b/SoObjects/Mailer/NSString+Mail.h index e967622b5..a4851855e 100644 --- a/SoObjects/Mailer/NSString+Mail.h +++ b/SoObjects/Mailer/NSString+Mail.h @@ -1,6 +1,6 @@ /* NSString+Mail.h - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-2014 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 @@ -21,12 +21,17 @@ #ifndef NSSTRING_MAIL_H #define NSSTRING_MAIL_H +#import #import @interface NSString (SOGoExtension) ++ (NSString *) generateMessageID; - (NSString *) htmlToText; +- (NSString *) htmlByExtractingImages: (NSMutableArray *) theImages; - (NSString *) stringByConvertingCRLNToHTML; +- (int) indexOf: (unichar) _c + fromIndex: (int) start; - (int) indexOf: (unichar) _c; - (NSString *) decodedHeader; diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m index 94f8e9b4f..7e7f1fbaf 100644 --- a/SoObjects/Mailer/NSString+Mail.m +++ b/SoObjects/Mailer/NSString+Mail.m @@ -1,6 +1,6 @@ /* NSString+Mail.m - this file is part of SOGo * - * Copyright (C) 2008-2013 Inverse inc. + * Copyright (C) 2008-2014 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 @@ -20,21 +20,28 @@ #import #import -#import #import +#import +#import +#import + #import #import #import #import #import +#import #import #import #import +#import +#import #include #import "NSString+Mail.h" #import "NSData+Mail.h" +#import "../SOGo/SOGoObject.h" #if 0 #define showWhoWeAre() \ @@ -43,11 +50,16 @@ #define showWhoWeAre() {} #endif -@interface _SOGoHTMLToTextContentHandler : NSObject +#define paddingBuffer 8192 + +@interface _SOGoHTMLContentHandler : NSObject { + NSMutableArray *images; + NSArray *ignoreContentTags; NSArray *specialTreatmentTags; - + NSArray *voidTags; + BOOL ignoreContent; BOOL orderedList; BOOL unorderedList; @@ -57,32 +69,27 @@ } + (id) htmlToTextContentHandler; ++ (id) sanitizerContentHandler; - (NSString *) result; +- (void) setIgnoreContentTags: (NSArray *) theTags; +- (void) setSpecialTreatmentTags: (NSArray *) theTags; +- (void) setVoidTags: (NSArray *) theTags; +- (void) setImages: (NSMutableArray *) theImages; + @end -@implementation _SOGoHTMLToTextContentHandler - -+ (id) htmlToTextContentHandler -{ - static id htmlToTextContentHandler; - - if (!htmlToTextContentHandler) - htmlToTextContentHandler = [self new]; - - return htmlToTextContentHandler; -} +@implementation _SOGoHTMLContentHandler - (id) init { if ((self = [super init])) { - ignoreContentTags = [NSArray arrayWithObjects: @"head", @"script", - @"style", nil]; - specialTreatmentTags = [NSArray arrayWithObjects: @"body", @"p", @"ul", - @"li", @"table", @"tr", @"td", @"th", - @"br", @"hr", @"dt", @"dd", nil]; + images = nil; + + ignoreContentTags = nil; + specialTreatmentTags = nil; [ignoreContentTags retain]; [specialTreatmentTags retain]; @@ -97,6 +104,37 @@ return self; } ++ (id) htmlToTextContentHandler +{ + static id htmlToTextContentHandler; + + if (!htmlToTextContentHandler) + htmlToTextContentHandler = [self new]; + + [htmlToTextContentHandler setIgnoreContentTags: [NSArray arrayWithObjects: @"head", @"script", + @"style", nil]]; + [htmlToTextContentHandler setSpecialTreatmentTags: [NSArray arrayWithObjects: @"p", @"ul", + @"li", @"table", @"tr", @"td", @"th", + @"br", @"hr", @"dt", @"dd", nil]]; + + return htmlToTextContentHandler; +} + ++ (id) sanitizerContentHandler +{ + static id sanitizerContentHandler; + + if (!sanitizerContentHandler) + sanitizerContentHandler = [self new]; + + [sanitizerContentHandler setVoidTags: [NSArray arrayWithObjects: @"area", @"base", + @"basefont", @"br", @"col", @"frame", @"hr", + @"img", @"input", @"isindex", @"link", + @"meta", @"param", @"", nil]]; + + return sanitizerContentHandler; +} + - (xmlCharEncoding) contentEncoding { return XML_CHAR_ENCODING_UTF8; @@ -121,6 +159,30 @@ return newResult; } +- (void) setIgnoreContentTags: (NSArray *) theTags +{ + ASSIGN(ignoreContentTags, theTags); +} + +- (void) setSpecialTreatmentTags: (NSArray *) theTags +{ + ASSIGN(specialTreatmentTags, theTags); +} + +- (void) setVoidTags: (NSArray *) theTags +{ + ASSIGN(voidTags, theTags); +} + +// +// We MUST NOT retain the array here +// +- (void) setImages: (NSMutableArray *) theImages +{ + images = theImages; +} + + /* SaxContentHandler */ - (void) startDocument { @@ -169,12 +231,12 @@ else if ([tagName isEqualToString: @"li"]) { if (orderedList) - { - listCount++; - [result appendFormat: @" %d. ", listCount]; - } + { + listCount++; + [result appendFormat: @" %d. ", listCount]; + } else - [result appendString: @" * "]; + [result appendString: @" * "]; } else if ([tagName isEqualToString: @"dd"]) [result appendString: @" "]; @@ -197,7 +259,7 @@ [result appendString: @":\n"]; } else if ([tagName isEqualToString: @"li"] - || [tagName isEqualToString: @"dd"]) + || [tagName isEqualToString: @"dd"]) [result appendString: @"\n"]; } @@ -210,13 +272,116 @@ showWhoWeAre(); - if (!ignoreContent) + tagName = [rawName lowercaseString]; + + if (!ignoreContent && ignoreContentTags && specialTreatmentTags) { - tagName = [rawName lowercaseString]; if ([ignoreContentTags containsObject: tagName]) - ignoreContent = YES; + ignoreContent = YES; else if ([specialTreatmentTags containsObject: tagName]) - [self _startSpecialTreatment: tagName]; + [self _startSpecialTreatment: tagName]; + } + else + { + if ([tagName isEqualToString: @"img"]) + { + NSString *value; + + value = [attributes valueForRawName: @"src"]; + + // + // Check for Data URI Scheme + // + // data:[][;charset=][;base64], + // + if ([value length] > 5 && [[value substringToIndex: 5] caseInsensitiveCompare: @"data:"] == NSOrderedSame) + { + NSString *uniqueId, *mimeType, *encoding; + NGMimeBodyPart *bodyPart; + NGMutableHashMap *map; + NSData *data; + id body; + + int i, j, k; + + i = [value indexOf: ';']; + j = [value indexOf: ';' fromIndex: i+1]; + k = [value indexOf: ',']; + + // We try to get the MIME type + mimeType = nil; + + if (i > 5 && i < k) + { + mimeType = [value substringWithRange: NSMakeRange(5, i-5)]; + } + else + i = 5; + + // We might get a stupid value. We discard anything that doesn't have a / in it + if ([mimeType indexOf: '/'] < 0) + mimeType = @"image/jpeg"; + + // We check and skip the charset + if (j < i) + j = i; + + // We check the encoding and we completely ignore it + encoding = [value substringWithRange: NSMakeRange(j+1, k-j-1)]; + + if (![encoding length]) + encoding = @"base64"; + + data = [[value substringFromIndex: k+1] dataUsingEncoding: NSASCIIStringEncoding]; + + uniqueId = [SOGoObject globallyUniqueObjectId]; + + map = [[[NGMutableHashMap alloc] initWithCapacity:5] autorelease]; + [map setObject: encoding forKey: @"content-transfer-encoding"]; + [map setObject:[NSNumber numberWithInt:[data length]] forKey: @"content-length"]; + [map setObject: [NSString stringWithFormat: @"inline; filename=\"%@\"", uniqueId] forKey: @"content-disposition"]; + [map setObject: [NSString stringWithFormat: @"%@; name=\"%@\"", mimeType, uniqueId] forKey: @"content-type"]; + [map setObject: [NSString stringWithFormat: @"<%@>", uniqueId] forKey: @"content-id"]; + + + body = [[NGMimeFileData alloc] initWithBytes: [data bytes] length: [data length]]; + + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + [bodyPart setBody: body]; + [body release]; + + [images addObject: bodyPart]; + + [result appendFormat: @"", uniqueId, mimeType]; + } + } + else if (voidTags) + { + NSString *type; + int i; + + [result appendString: @"<"]; + [result appendString: rawName]; + for (i = 0; i < [attributes count]; i++) + { + [result appendString: @" "]; + [result appendString: [attributes nameAtIndex: i]]; + [result appendString: @"='"]; + [result appendString: [attributes valueAtIndex: i]]; + [result appendString: @"'"]; + + type = [attributes typeAtIndex: i]; + if (![type isEqualToString: @"CDATA"]) + { + [result appendString: @"["]; + [result appendString: type]; + [result appendString: @"]"]; + } + } + if ([voidTags containsObject: tagName]) + [result appendString: @"/"]; + [result appendString: @">"]; + } } } @@ -228,13 +393,22 @@ showWhoWeAre(); - if (ignoreContent) + if (ignoreContentTags && specialTreatmentTags) + { + if (ignoreContent) + { + tagName = [rawName lowercaseString]; + if ([ignoreContentTags containsObject: tagName]) + ignoreContent = NO; + else if ([specialTreatmentTags containsObject: tagName]) + [self _endSpecialTreatment: tagName]; + } + } + else if (voidTags) { tagName = [rawName lowercaseString]; - if ([ignoreContentTags containsObject: tagName]) - ignoreContent = NO; - else if ([specialTreatmentTags containsObject: tagName]) - [self _endSpecialTreatment: tagName]; + if (![voidTags containsObject: tagName]) + [result appendFormat: @"", rawName]; } } @@ -343,15 +517,28 @@ @implementation NSString (SOGoExtension) ++ (NSString *) generateMessageID +{ + NSMutableString *messageID; + NSString *pGUID; + + messageID = [NSMutableString string]; + [messageID appendFormat: @"<%@", [SOGoObject globallyUniqueObjectId]]; + pGUID = [[NSProcessInfo processInfo] globallyUniqueString]; + [messageID appendFormat: @"@%u>", [pGUID hash]]; + + return [messageID lowercaseString]; +} + - (NSString *) htmlToText { - _SOGoHTMLToTextContentHandler *handler; + _SOGoHTMLContentHandler *handler; id parser; NSData *d; parser = [[SaxXMLReaderFactory standardXMLReaderFactory] createXMLReaderForMimeType: @"text/html"]; - handler = [_SOGoHTMLToTextContentHandler htmlToTextContentHandler]; + handler = [_SOGoHTMLContentHandler htmlToTextContentHandler]; [parser setContentHandler: handler]; d = [self dataUsingEncoding: NSUTF8StringEncoding]; @@ -360,7 +547,24 @@ return [handler result]; } -#define paddingBuffer 8192 +- (NSString *) htmlByExtractingImages: (NSMutableArray *) theImages +{ + _SOGoHTMLContentHandler *handler; + id parser; + NSData *d; + + parser = [[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType: @"text/html"]; + handler = [_SOGoHTMLContentHandler sanitizerContentHandler]; + [handler setImages: theImages]; + + [parser setContentHandler: handler]; + + d = [self dataUsingEncoding: NSUTF8StringEncoding]; + [parser parseFromSource: d]; + + return [handler result]; +} static inline char * convertChars (const char *oldString, unsigned int oldLength, @@ -423,8 +627,7 @@ convertChars (const char *oldString, unsigned int oldLength, unsigned int newLength; utf8String = [self UTF8String]; - newString = convertChars (utf8String, strlen (utf8String), - &newLength); + newString = convertChars (utf8String, strlen (utf8String), &newLength); convertedString = [[NSString alloc] initWithBytes: newString length: newLength encoding: NSUTF8StringEncoding]; @@ -434,18 +637,29 @@ convertChars (const char *oldString, unsigned int oldLength, return convertedString; } + - (int) indexOf: (unichar) _c + fromIndex: (int) start { int i, len; - + len = [self length]; - - for (i = 0; i < len; i++) + + if (start < 0 || start >= len) + start = 0; + + for (i = start; i < len; i++) { if ([self characterAtIndex: i] == _c) return i; } return -1; + +} + +- (int) indexOf: (unichar) _c +{ + return [self indexOf: _c fromIndex: 0]; } - (NSString *) decodedHeader @@ -453,7 +667,7 @@ convertChars (const char *oldString, unsigned int oldLength, NSString *decodedHeader; decodedHeader = [[self dataUsingEncoding: NSASCIIStringEncoding] - decodedHeader]; + decodedHeader]; if (!decodedHeader) decodedHeader = self; diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index a53d2a9ba..94c5d2888 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -40,10 +40,11 @@ @class NSData; @class NSDictionary; @class NSException; -@class NGImap4Envelope; -@class NGMimeMessage; @class NSMutableDictionary; @class NSString; +@class NGImap4Envelope; +@class NGMimeBodyPart; +@class NGMimeMessage; @class SOGoMailObject; @@ -87,8 +88,10 @@ /* attachments */ -- (NSArray *) fetchAttachmentNames; +- (NSArray *) fetchAttachmentAttrs; - (BOOL) isValidAttachmentName: (NSString *) _name; +- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name; +- (NSString *) pathToAttachmentWithName: (NSString *) _name; - (NSException *) saveAttachment: (NSData *) _attach withMetadata: (NSDictionary *) metadata; - (NSException *) deleteAttachmentWithName: (NSString *) _name; diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 9ce6b4136..bd5a0d469 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2012 Inverse inc. + Copyright (C) 2007-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. @@ -256,19 +256,6 @@ static NSString *userAgent = nil; /* contents */ -- (NSString *) _generateMessageID -{ - NSMutableString *messageID; - NSString *pGUID; - - messageID = [NSMutableString string]; - [messageID appendFormat: @"<%@", [self globallyUniqueObjectId]]; - pGUID = [[NSProcessInfo processInfo] globallyUniqueString]; - [messageID appendFormat: @"@%u>", [pGUID hash]]; - - return [messageID lowercaseString]; -} - - (void) setHeaders: (NSDictionary *) newHeaders { id headerValue; @@ -288,7 +275,7 @@ static NSString *userAgent = nil; messageID = [headers objectForKey: @"message-id"]; if (!messageID) { - messageID = [self _generateMessageID]; + messageID = [NSString generateMessageID]; [headers setObject: messageID forKey: @"message-id"]; } @@ -395,11 +382,17 @@ static NSString *userAgent = nil; [self setSourceFolder: [paths componentsJoinedByString: @"/"]]; } +// +// +// - (NSString *) sourceFolder { return sourceFolder; } +// +// Store the message definition in a plist file (.info.plist) in the spool directory +// - (NSException *) storeInfo { NSMutableDictionary *infos; @@ -446,6 +439,9 @@ static NSString *userAgent = nil; return error; } +// +// +// - (void) _loadInfosFromDictionary: (NSDictionary *) infoDict { id value; @@ -478,11 +474,17 @@ static NSString *userAgent = nil; [self setInReplyTo: value]; } +// +// +// - (NSString *) relativeImap4Name { return [NSString stringWithFormat: @"%d", IMAP4ID]; } +// +// +// - (void) fetchInfo { NSString *p; @@ -504,16 +506,25 @@ static NSString *userAgent = nil; [self debugWithFormat: @"Note: info object does not yet exist: %@", p]; } +// +// +// - (void) setIMAP4ID: (int) newIMAP4ID { IMAP4ID = newIMAP4ID; } +// +// +// - (int) IMAP4ID { return IMAP4ID; } +// +// +// - (NSException *) save { NGImap4Client *client; @@ -535,23 +546,31 @@ static NSString *userAgent = nil; } folder = [imap4 imap4FolderNameForURL: [container imap4URL]]; - result - = [client append: message toFolder: folder - withFlags: [NSArray arrayWithObjects: @"seen", @"draft", nil]]; + result = [client append: message toFolder: folder + withFlags: [NSArray arrayWithObjects: @"seen", @"draft", nil]]; if ([[result objectForKey: @"result"] boolValue]) { if (IMAP4ID > -1) error = [imap4 markURLDeleted: [self imap4URL]]; IMAP4ID = [self IMAP4IDFromAppendResult: result]; + if (imap4URL) + { + // Invalidate the IMAP message URL since the message ID has changed + [imap4URL release]; + imap4URL = nil; + } [self storeInfo]; } else - error = [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason: @"Failed to store message"]; + error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: [result objectForKey: @"reason"]]; return error; } +// +// +// - (void) _addEMailsOfAddresses: (NSArray *) _addrs toArray: (NSMutableArray *) _ma { @@ -564,6 +583,9 @@ static NSString *userAgent = nil; [_ma addObject: [currentAddress email]]; } +// +// +// - (void) _addRecipients: (NSArray *) recipients toArray: (NSMutableArray *) array { @@ -576,6 +598,9 @@ static NSString *userAgent = nil; [array addObject: [currentAddress baseEMail]]; } +// +// +// - (void) _purgeRecipients: (NSArray *) recipients fromAddresses: (NSMutableArray *) addresses { @@ -602,6 +627,9 @@ static NSString *userAgent = nil; } } +// +// +// - (void) _fillInReplyAddresses: (NSMutableDictionary *) _info replyToAll: (BOOL) _replyToAll envelope: (NGImap4Envelope *) _envelope @@ -711,6 +739,9 @@ static NSString *userAgent = nil; } } +// +// +// - (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths fromResponseFetch: (NSDictionary *) fetch; { @@ -731,15 +762,18 @@ static NSString *userAgent = nil; return bodies; } -- (void) _fetchAttachments: (NSArray *) parts - fromMail: (SOGoMailObject *) sourceMail +// +// +// +- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail { unsigned int count, max; - NSArray *paths, *bodies; + NSArray *parts, *paths, *bodies; NSData *body; NSDictionary *currentInfo; NGHashMap *response; + parts = [sourceMail fetchFileAttachmentKeys]; max = [parts count]; if (max > 0) { @@ -758,6 +792,9 @@ static NSString *userAgent = nil; } } +// +// +// - (void) fetchMailForEditing: (SOGoMailObject *) sourceMail { NSString *subject, *msgid; @@ -767,8 +804,7 @@ static NSString *userAgent = nil; [sourceMail fetchCoreInfos]; - [self _fetchAttachments: [sourceMail fetchFileAttachmentKeys] - fromMail: sourceMail]; + [self _fetchAttachmentsFromMail: sourceMail]; info = [NSMutableDictionary dictionaryWithCapacity: 16]; subject = [sourceMail subject]; if ([subject length] > 0) @@ -804,10 +840,13 @@ static NSString *userAgent = nil; [self storeInfo]; } +// +// +// - (void) fetchMailForReplying: (SOGoMailObject *) sourceMail toAll: (BOOL) toAll { - NSString *contentForReply, *msgID; + NSString *msgID; NSMutableDictionary *info; NGImap4Envelope *sourceEnvelope; @@ -822,8 +861,7 @@ static NSString *userAgent = nil; msgID = [sourceEnvelope messageID]; if ([msgID length] > 0) [self setInReplyTo: msgID]; - contentForReply = [sourceMail contentForReply]; - [self setText: contentForReply]; + [self setText: [sourceMail contentForReply]]; [self setHeaders: info]; [self setSourceURL: [sourceMail imap4URLString]]; [self setSourceFlag: @"Answered"]; @@ -858,8 +896,7 @@ static NSString *userAgent = nil; if ([[ud mailMessageForwarding] isEqualToString: @"inline"]) { [self setText: [sourceMail contentForInlineForward]]; - [self _fetchAttachments: [sourceMail fetchFileAttachmentKeys] - fromMail: sourceMail]; + [self _fetchAttachmentsFromMail: sourceMail]; } else { @@ -869,7 +906,7 @@ static NSString *userAgent = nil; if ([signature length]) { nl = (isHTML ? @"
" : @"\n"); - [self setText: [NSString stringWithFormat: @"%@-- %@%@", nl, nl, signature]]; + [self setText: [NSString stringWithFormat: @"%@%@-- %@%@", nl, nl, nl, signature]]; } attachment = [NSDictionary dictionaryWithObjectsAndKeys: [sourceMail filenameForForward], @"filename", @@ -880,6 +917,10 @@ static NSString *userAgent = nil; } [self storeInfo]; + + // Save the message to the IMAP store so the user can eventually view the attached file(s) + // from the Web interface + [self save]; } /* accessors */ @@ -898,13 +939,19 @@ static NSString *userAgent = nil; /* attachments */ -- (NSArray *) fetchAttachmentNames +// +// Return the attributes (name, size and mime body part) of the files found in the draft folder +// on the local filesystem +// +- (NSArray *) fetchAttachmentAttrs { NSMutableArray *ma; NSFileManager *fm; NSArray *files; - unsigned count, max; NSString *filename; + NSDictionary *fileAttrs; + NGMimeBodyPart *bodyPart; + unsigned count, max; fm = [NSFileManager defaultManager]; files = [fm directoryContentsAtPath: [self draftFolderPath]]; @@ -915,7 +962,13 @@ static NSString *userAgent = nil; { filename = [files objectAtIndex: count]; if (![filename hasPrefix: @"."]) - [ma addObject: filename]; + { + fileAttrs = [fm fileAttributesAtPath: [self pathToAttachmentWithName: filename] traverseLink: YES]; + bodyPart = [self bodyPartForAttachmentWithName: filename]; + [ma addObject: [NSDictionary dictionaryWithObjectsAndKeys: filename, @"filename", + [fileAttrs objectForKey: @"NSFileSize"], @"size", + bodyPart, @"part", nil]]; + } } return ma; @@ -1081,11 +1134,11 @@ static NSString *userAgent = nil; body = [[[NGMimeMultipartBody alloc] initWithPart: message] autorelease]; [map addObject: MultiAlternativeType forKey: @"content-type"]; + // Get the text part from it and add it + [body addBodyPart: [self plainTextBodyPartForText]]; + // Add the HTML part [body addBodyPart: [self bodyPartForText]]; - - // Get the text part from it and add it too - [body addBodyPart: [self plainTextBodyPartForText]]; } [message setBody: body]; @@ -1174,8 +1227,8 @@ static NSString *userAgent = nil; /* check attachment */ fm = [NSFileManager defaultManager]; - p = [self pathToAttachmentWithName:_name]; - if (![fm isReadableFileAtPath:p]) { + p = [self pathToAttachmentWithName: _name]; + if (![fm isReadableFileAtPath: p]) { [self errorWithFormat: @"did not find attachment: '%@'", _name]; return nil; } @@ -1184,21 +1237,21 @@ static NSString *userAgent = nil; /* prepare header of body part */ - map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease]; + map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease]; if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) { - [map setObject:s forKey: @"content-type"]; + [map setObject: s forKey: @"content-type"]; if ([s hasPrefix: @"text/plain"] || [s hasPrefix: @"text/html"]) attachAsString = YES; else if ([s hasPrefix: @"message/rfc822"]) attachAsRFC822 = YES; } - if ((s = [self contentDispositionForAttachmentWithName:_name])) + if ((s = [self contentDispositionForAttachmentWithName: _name])) { NGMimeContentDispositionHeaderField *o; o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s]; - [map setObject:o forKey: @"content-disposition"]; + [map setObject: o forKey: @"content-disposition"]; [o release]; } @@ -1233,7 +1286,6 @@ static NSString *userAgent = nil; if (attachAsRFC822) { [map setObject: @"8bit" forKey: @"content-transfer-encoding"]; - [map setObject: @"inline" forKey: @"content-disposition"]; } else { @@ -1255,27 +1307,33 @@ static NSString *userAgent = nil; return bodyPart; } +// +// +// - (NSArray *) bodyPartsForAllAttachments { /* returns nil on error */ - NSArray *names; + NSArray *attrs; unsigned i, count; NGMimeBodyPart *bodyPart; NSMutableArray *bodyParts; - names = [self fetchAttachmentNames]; - count = [names count]; + attrs = [self fetchAttachmentAttrs]; + count = [attrs count]; bodyParts = [NSMutableArray arrayWithCapacity: count]; for (i = 0; i < count; i++) { - bodyPart = [self bodyPartForAttachmentWithName: [names objectAtIndex: i]]; + bodyPart = [self bodyPartForAttachmentWithName: [[attrs objectAtIndex: i] objectForKey: @"filename"]]; [bodyParts addObject: bodyPart]; } return bodyParts; } +// +// +// - (NGMimeBodyPart *) mimeMultipartAlternative { NGMimeMultipartBody *textParts; @@ -1289,11 +1347,11 @@ static NSString *userAgent = nil; textParts = [[NGMimeMultipartBody alloc] initWithPart: part]; + // Get the text part from it and add it + [textParts addBodyPart: [self plainTextBodyPartForText]]; + // Add the HTML part [textParts addBodyPart: [self bodyPartForText]]; - - // Get the text part from it and add it too - [textParts addBodyPart: [self plainTextBodyPartForText]]; [part setBody: textParts]; RELEASE(textParts); @@ -1301,6 +1359,9 @@ static NSString *userAgent = nil; return part; } +// +// +// - (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map andBodyParts: (NSArray *) _bodyParts { @@ -1340,6 +1401,9 @@ static NSString *userAgent = nil; return message; } +// +// +// - (void) _addHeaders: (NSDictionary *) _h toHeaderMap: (NGMutableHashMap *) _map { @@ -1511,60 +1575,78 @@ static NSString *userAgent = nil; return map; } +// +// +// - (NGMimeMessage *) mimeMessageWithHeaders: (NSDictionary *) _headers excluding: (NSArray *) _exclude + extractingImages: (BOOL) _extractImages { - NGMutableHashMap *map; - NSArray *bodyParts; - NGMimeMessage *message; + NSMutableArray *bodyParts; + NGMimeMessage *message; + NGMutableHashMap *map; + NSString *newText; message = nil; + bodyParts = [NSMutableArray array]; + + if (_extractImages) + { + newText = [text htmlByExtractingImages: bodyParts]; + if ([bodyParts count]) + [self setText: newText]; + } + map = [self mimeHeaderMapWithHeaders: _headers - excluding: _exclude]; + excluding: _exclude]; if (map) { //[self debugWithFormat: @"MIME Envelope: %@", map]; - - bodyParts = [self bodyPartsForAllAttachments]; - if (bodyParts) - { - //[self debugWithFormat: @"attachments: %@", bodyParts]; - - if ([bodyParts count] == 0) - /* no attachments */ - message = [self mimeMessageForContentWithHeaderMap: map]; - else - /* attachments, create multipart/mixed */ - message = [self mimeMultiPartMessageWithHeaderMap: map - andBodyParts: bodyParts]; - //[self debugWithFormat: @"message: %@", message]; - } + + [bodyParts addObjectsFromArray: [self bodyPartsForAllAttachments]]; + + //[self debugWithFormat: @"attachments: %@", bodyParts]; + + if ([bodyParts count] == 0) + /* no attachments */ + message = [self mimeMessageForContentWithHeaderMap: map]; else - [self errorWithFormat: - @"could not create body parts for attachments!"]; + /* attachments, create multipart/mixed */ + message = [self mimeMultiPartMessageWithHeaderMap: map + andBodyParts: bodyParts]; + //[self debugWithFormat: @"message: %@", message]; } - + return message; } +// +// Return a NGMimeMessage object with inline HTML images () extracted as attachments (). +// - (NGMimeMessage *) mimeMessage { - return [self mimeMessageWithHeaders: nil excluding: nil]; + return [self mimeMessageWithHeaders: nil excluding: nil extractingImages: YES]; } +// +// Return a NSData object of the message with no alteration. +// - (NSData *) mimeMessageAsData { NGMimeMessageGenerator *generator; NSData *message; generator = [NGMimeMessageGenerator new]; - message = [generator generateMimeFromPart: [self mimeMessage]]; + message = [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO]]; [generator release]; return message; } +// +// +// - (NSArray *) allRecipients { NSMutableArray *allRecipients; @@ -1584,6 +1666,9 @@ static NSString *userAgent = nil; return allRecipients; } +// +// +// - (NSArray *) allBareRecipients { NSMutableArray *bareRecipients; @@ -1599,11 +1684,17 @@ static NSString *userAgent = nil; return bareRecipients; } +// +// +// - (NSException *) sendMail { return [self sendMailAndCopyToSent: YES]; } +// +// +// - (NSException *) sendMailAndCopyToSent: (BOOL) copyToSent { NSMutableData *cleaned_message; @@ -1620,9 +1711,8 @@ static NSString *userAgent = nil; NGMimeMessageGenerator *generator; generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; - message = [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil - excluding: nil]]; - + message = [generator generateMimeFromPart: [self mimeMessage]]; + // // We now look for the Bcc: header. If it is present, we remove it. // Some servers, like qmail, do not remove it automatically. diff --git a/SoObjects/Mailer/SOGoMailAccount.h b/SoObjects/Mailer/SOGoMailAccount.h index 011a7620e..cec85d61e 100644 --- a/SoObjects/Mailer/SOGoMailAccount.h +++ b/SoObjects/Mailer/SOGoMailAccount.h @@ -80,10 +80,11 @@ typedef enum { - (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs; - (NSArray *) allFolderPaths; +- (NSArray *) allFoldersMetadata; + - (BOOL) isInDraftsFolder; /* special folders */ - - (NSString *) inboxFolderNameInContext: (id)_ctx; - (NSString *) draftsFolderNameInContext: (id)_ctx; - (NSString *) sentFolderNameInContext: (id)_ctx; diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index c396b04d1..2beb53acb 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -21,6 +21,7 @@ */ #import +#import #import #import #import @@ -47,6 +48,7 @@ #import #import #import +#import #import #import "SOGoDraftsFolder.h" @@ -309,10 +311,7 @@ static NSString *inboxFolderName = @"INBOX"; manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; - return [manager updateFiltersForLogin: [[self imap4URL] user] - authname: [[self imap4URL] user] - password: [self imap4PasswordRenewed: NO] - account: self]; + return [manager updateFiltersForAccount: self]; } @@ -339,6 +338,9 @@ static NSString *inboxFolderName = @"INBOX"; return folders; } +// +// +// - (NSArray *) allFolderPaths { NSMutableArray *folderPaths, *namespaces; @@ -382,8 +384,128 @@ static NSString *inboxFolderName = @"INBOX"; return folderPaths; } -/* IMAP4 */ +// +// +// +- (NSString *) _folderType: (NSString *) folderName +{ + NSString *folderType; + if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", inboxFolderName]]) + folderType = @"inbox"; + else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self draftsFolderNameInContext: context]]]) + folderType = @"draft"; + else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self sentFolderNameInContext: context]]]) + folderType = @"sent"; + else if ([folderName isEqualToString: [NSString stringWithFormat: @"/%@", [self trashFolderNameInContext: context]]]) + folderType = @"trash"; + else + folderType = @"folder"; + + return folderType; +} + +- (NSString *) _parentForFolder: (NSString *) folderName + foldersList: (NSArray *) theFolders +{ + NSArray *pathComponents; + NSString *s; + int i; + + pathComponents = [folderName pathComponents]; + s = [[[pathComponents subarrayWithRange: NSMakeRange(0,[pathComponents count]-1)] componentsJoinedByString: @"/"] substringFromIndex: 1]; + + for (i = 0; i < [theFolders count]; i++) + { + if ([s isEqualToString: [theFolders objectAtIndex: i]]) + return s; + } + + return nil; +} + +// +// +// +- (NSArray *) allFoldersMetadata +{ + NSString *currentFolder, *currentDecodedFolder, *currentDisplayName, *currentFolderType, *login, *fullName, *parent; + NSMutableArray *pathComponents, *folders; + SOGoUserManager *userManager; + NSEnumerator *rawFolders; + NSDictionary *folderData; + NSAutoreleasePool *pool; + NSArray *allFolderPaths; + + allFolderPaths = [self allFolderPaths]; + rawFolders = [allFolderPaths objectEnumerator]; + + folders = [NSMutableArray array]; + while ((currentFolder = [rawFolders nextObject])) + { + // Using a local pool to avoid using too many file descriptors. This could + // happen with tons of mailboxes under "Other Users" as LDAP connections + // are never reused and "autoreleased" at the end. This loop would consume + // lots of LDAP connections during its execution. + pool = [[NSAutoreleasePool alloc] init]; + + currentDecodedFolder = [currentFolder stringByDecodingImap4FolderName]; + currentFolderType = [self _folderType: currentFolder]; + + // We translate the "Other Users" and "Shared Folders" namespaces. + // While we're at it, we also translate the user's mailbox names + // to the full name of the person. + if (otherUsersFolderName && [currentDecodedFolder hasPrefix: [NSString stringWithFormat: @"/%@", otherUsersFolderName]]) + { + // We have a string like /Other Users/lmarcotte/... under Cyrus, but we could + // also have something like /shared under Dovecot. So we swap the username only + // if we have one, of course. + pathComponents = [NSMutableArray arrayWithArray: [currentDecodedFolder pathComponents]]; + + if ([pathComponents count] > 2) + { + login = [pathComponents objectAtIndex: 2]; + userManager = [SOGoUserManager sharedUserManager]; + fullName = [userManager getCNForUID: login]; + [pathComponents removeObjectsInRange: NSMakeRange(0,3)]; + + currentDisplayName = [NSString stringWithFormat: @"/%@/%@/%@", + [self labelForKey: @"OtherUsersFolderName"], + (fullName != nil ? fullName : login), + [pathComponents componentsJoinedByString: @"/"]]; + + } + else + { + currentDisplayName = [NSString stringWithFormat: @"/%@%@", + [self labelForKey: @"OtherUsersFolderName"], + [currentDecodedFolder substringFromIndex: + [otherUsersFolderName length]+1]]; + } + } + else if (sharedFoldersName && [currentDecodedFolder hasPrefix: [NSString stringWithFormat: @"/%@", sharedFoldersName]]) + currentDisplayName = [NSString stringWithFormat: @"/%@%@", [self labelForKey: @"SharedFoldersName"], + [currentDecodedFolder substringFromIndex: [sharedFoldersName length]+1]]; + else + currentDisplayName = currentDecodedFolder; + + parent = [self _parentForFolder: currentFolder foldersList: allFolderPaths]; + + folderData = [NSDictionary dictionaryWithObjectsAndKeys: + currentFolder, @"path", + currentFolderType, @"type", + currentDisplayName, @"displayName", + parent, @"parent", + nil]; + [folders addObject: folderData]; + [pool release]; + } + + return folders; +} + + +/* IMAP4 */ - (NSDictionary *) _mailAccount { NSDictionary *mailAccount; diff --git a/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.html b/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.html index 379fc9c16..e942dd79d 100644 --- a/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.html +++ b/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- الرسالة الاصلية --------<#newLine/> الموضوع: <#subject/><#newLine/> التاريخ: <#date/><#newLine/> @@ -9,5 +10,7 @@ <#hasCc>CC: <#cc/><#hasNewsGroups>مجموعات الأخبار: <#newsgroups/><#hasReferences>المراجع: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.wod b/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.wod +++ b/SoObjects/Mailer/SOGoMailArabicForward.wo/SOGoMailArabicForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.html b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.html index 595e8aefb..75b423fe1 100644 --- a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.html +++ b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.html @@ -1,10 +1,16 @@ --------- Original Message -------- -Assunto: <#subject/> -Data: <#date/> -De: <#from/> -<#hasReplyTo>Responder-Para: <#replyTo/><#hasOrganization>Organização: <#organization/>Para: <#to/> -<#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>Referências: <#references/> - -<#messageBody/> - -<#signature/> +<#newLine/> +<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> +-------- Original Message --------<#newLine/> +Assunto: <#subject/><#newLine/> +Data: <#date/><#newLine/> +De: <#from/><#newLine/> +<#hasReplyTo>Responder-Para: <#replyTo/><#hasOrganization>Organização: <#organization/>Para: <#to/><#newLine/> +<#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>Referências: <#references/><#newLine/> +<#newLine/> +<#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> +<#newLine/> diff --git a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod index df237286f..9afd448d9 100644 --- a/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod +++ b/SoObjects/Mailer/SOGoMailBrazilianPortugueseForward.wo/SOGoMailBrazilianPortugueseForward.wod @@ -72,3 +72,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.html b/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.html index f8b141ae2..ef3b83e17 100644 --- a/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.html +++ b/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Subject: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ From: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>References: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.wod b/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.wod +++ b/SoObjects/Mailer/SOGoMailCatalanForward.wo/SOGoMailCatalanForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.html b/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.html index 37d17d8b9..a551d66ce 100644 --- a/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.html +++ b/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.html @@ -1,10 +1,16 @@ --------- Původní zpráva -------- -Předmět: <#subject/> -Datum: <#date/> -Od: <#from/> -<#hasReplyTo>Odpověď na: <#replyTo/><#hasOrganization>Organizace: <#organization/>Komu: <#to/> -<#hasCc>Kopie: <#cc/><#hasNewsGroups>Diskuzní skupiny: <#newsgroups/><#hasReferences>Odkazy: <#references/> - -<#messageBody/> - -<#signature/> +<#newLine/> +<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> +-------- Původní zpráva --------<#newLine/> +Předmět: <#subject/><#newLine/> +Datum: <#date/><#newLine/> +Od: <#from/><#newLine/> +<#hasReplyTo>Odpověď na: <#replyTo/><#hasOrganization>Organizace: <#organization/>Komu: <#to/><#newLine/> +<#hasCc>Kopie: <#cc/><#hasNewsGroups>Diskuzní skupiny: <#newsgroups/><#hasReferences>Odkazy: <#references/><#newLine/> +<#newLine/> +<#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> +<#newLine/> diff --git a/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.wod b/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.wod +++ b/SoObjects/Mailer/SOGoMailCzechForward.wo/SOGoMailCzechForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.html b/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.html index f8b141ae2..ef3b83e17 100644 --- a/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.html +++ b/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Subject: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ From: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>References: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.wod b/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.wod +++ b/SoObjects/Mailer/SOGoMailDanishForward.wo/SOGoMailDanishForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.html b/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.html index f8b141ae2..ef3b83e17 100644 --- a/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.html +++ b/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Subject: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ From: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>References: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.wod b/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.wod +++ b/SoObjects/Mailer/SOGoMailDutchForward.wo/SOGoMailDutchForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html b/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html index f8b141ae2..ef3b83e17 100644 --- a/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html +++ b/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Subject: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ From: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>References: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.wod b/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.wod index 7787fa18e..96bb247ee 100644 --- a/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.wod +++ b/SoObjects/Mailer/SOGoMailEnglishForward.wo/SOGoMailEnglishForward.wod @@ -77,3 +77,21 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.html b/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.html index eddf96b46..562258b27 100644 --- a/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.html +++ b/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Alkuperäinen viesti --------<#newLine/> Aihe: <#subject/><#newLine/> Pvm: <#date/><#newLine/> @@ -9,5 +10,7 @@ Keneltä: <#from/><#newLine/> <#hasCc>Kopio: <#cc/><#hasNewsGroups>Uutisryhmät: <#newsgroups/><#hasReferences>Viitteet: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.wod b/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.wod +++ b/SoObjects/Mailer/SOGoMailFinnishForward.wo/SOGoMailFinnishForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index f80702ce5..eac1aec50 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -94,6 +94,13 @@ - (NSCalendarDate *) mostRecentMessageDate; +- (NSString *) davCollectionTagFromId: (NSString *) theId; +- (NSString *) davCollectionTag; + +- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties + matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate; + /* flags */ - (NSException *) addFlagsToAllMessages: (id) _f; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index c74f8ec70..d83a30917 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2009-2013 Inverse inc. + Copyright (C) 2009-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. @@ -24,6 +24,7 @@ #import #import #import +#import #import #import @@ -614,20 +615,19 @@ static NSString *defaultUserID = @"anyone"; if (max > 1) { currentAccountName = [[self mailAccountFolder] nameInContainer]; - if ([[folders objectAtIndex: 1] isEqualToString: currentAccountName]) - { - for (count = 2; count < max; count++) - { - currentFolderName - = [[folders objectAtIndex: count] substringFromIndex: 6]; - [imapDestinationFolder appendFormat: @"/%@", currentFolderName]; - } + client = [[self imap4Connection] client]; + [imap4 selectFolder: [self imap4URL]]; - client = [[self imap4Connection] client]; - if (client) + for (count = 2; count < max; count++) + { + currentFolderName = [[folders objectAtIndex: count] substringFromIndex: 6]; + [imapDestinationFolder appendFormat: @"/%@", currentFolderName]; + } + + if (client) + { + if ([[folders objectAtIndex: 1] isEqualToString: currentAccountName]) { - [imap4 selectFolder: [self imap4URL]]; - // We make sure the destination IMAP folder exist, if not, we create it. result = [[client status: imapDestinationFolder flags: [NSArray arrayWithObject: @"UIDVALIDITY"]] @@ -647,13 +647,69 @@ static NSString *defaultUserID = @"anyone"; objectForKey: @"description"]]; } else - result = [NSException exceptionWithName: @"SOGoMailException" - reason: @"IMAP connection is invalid" - userInfo: nil]; + { + // Destination folder is in a different account + SOGoMailAccounts *accounts; + SOGoMailAccount *account; + accounts = [[self container] container]; + account = [accounts lookupName: [folders objectAtIndex: 1] inContext: localContext acquire: NO]; + if ([account isKindOfClass: [NSException class]]) + { + result = [NSException exceptionWithHTTPStatus: 500 + reason: @"Cannot copy messages to other account."]; + } + else + { + NSEnumerator *messages; + NSDictionary *message; + NSData *content; + NSArray *flags; + + // Fetch messages + result = [client fetchUids: uids parts: [NSArray arrayWithObjects: @"RFC822", @"FLAGS", nil]]; + if ([[result objectForKey: @"result"] boolValue]) + { + result = [result valueForKey: @"fetch"]; + if ([result isKindOfClass: [NSArray class]] && [result count] > 0) + { + // Copy each message to the other account + client = [[account imap4Connection] client]; + [[account imap4Connection] selectFolder: imapDestinationFolder]; + messages = [result objectEnumerator]; + result = nil; + while (result == nil && (message = [messages nextObject])) + { + if ((content = [message valueForKey: @"message"]) != nil) + { + flags = [message valueForKey: @"flags"]; + result = [client append: content toFolder: imapDestinationFolder withFlags: flags]; + if ([[result objectForKey: @"result"] boolValue]) + result = nil; + else + [self logWithFormat: @"ERROR: Can't append message: %@", result]; + } + } + } + else + { + [self logWithFormat: @"ERROR: unexpected IMAP4 result (missing 'fetch'): %@", result]; + result = [NSException exceptionWithHTTPStatus: 500 + reason: @"Unexpected IMAP4 result"]; + } + } + else + { + [self logWithFormat: @"ERROR: Can't fetch messages: %@", result]; + result = [NSException exceptionWithHTTPStatus: 500 + reason: @"Can't fetch messages"]; + } + } + } } else - result = [NSException exceptionWithHTTPStatus: 500 - reason: @"Cannot copy messages across different accounts."]; + result = [NSException exceptionWithName: @"SOGoMailException" + reason: @"IMAP connection is invalid" + userInfo: nil]; } else result = [NSException exceptionWithHTTPStatus: 500 @@ -1869,6 +1925,193 @@ static NSString *defaultUserID = @"anyone"; return date; } +- (NSString *) davCollectionTagFromId: (NSString *) theId +{ + NSString *tag; + + tag = @"-1"; + + if ([self imap4Connection]) + { + NSDictionary *result; + unsigned int modseq, uid; + + uid = [theId intValue]; + result = [[imap4 client] fetchModseqForUid: uid]; + modseq = [[[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"] intValue]; + + tag = [NSString stringWithFormat: @"%d-%d", uid, modseq-1]; + } + + return tag; +} + +- (NSString *) davCollectionTag +{ + NSString *tag; + + tag = @"-1"; + + if ([self imap4Connection]) + { + NSString *folderName; + NSDictionary *result; + + folderName = [imap4 imap4FolderNameForURL: [self imap4URL]]; + + [[imap4 client] unselect]; + + result = [[imap4 client] select: folderName]; + + tag = [NSString stringWithFormat: @"%@-%@", [result objectForKey: @"uidnext"], [result objectForKey: @"highestmodseq"]]; + } + + return tag; +} + +// +// FIXME - see below for code refactoring with MAPIStoreMailFolder. +// +- (EOQualifier *) _nonDeletedQualifier +{ + static EOQualifier *nonDeletedQualifier = nil; + EOQualifier *deletedQualifier; + + if (!nonDeletedQualifier) + { + deletedQualifier + = [[EOKeyValueQualifier alloc] + initWithKey: @"FLAGS" + operatorSelector: EOQualifierOperatorContains + value: [NSArray arrayWithObject: @"Deleted"]]; + nonDeletedQualifier = [[EONotQualifier alloc] + initWithQualifier: deletedQualifier]; + [deletedQualifier release]; + } + + return nonDeletedQualifier; +} + + + +// +// Check updated items +// +// +// . uid fetch 1:* (FLAGS) (changedsince 171) +// +// To get the modseq of a specific message: +// +// . uid fetch 124569:124569 uid (changedsince 1) +// +// +// Deleted: "UID FETCH 1:* (UID) (CHANGEDSINCE 171 VANISHED)" + +// fetchUIDsOfVanishedItems .. +// +// . uid fetch 1:* (FLAGS) (changedsince 176 vanished) +// * VANISHED (EARLIER) 36 +// +// +// FIXME: refactor MAPIStoreMailFolder.m - synchroniseCache to use this method +// +- (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties + matchingSyncToken: (NSString *) theSyncToken + fromDate: (NSCalendarDate *) theStartDate +{ + EOQualifier *searchQualifier; + NSMutableArray *allTokens; + NSArray *a, *uids; + NSDictionary *d; + + int uidnext, highestmodseq, i; + + allTokens = [NSMutableArray array]; + + if ([theSyncToken isEqualToString: @"-1"]) + { + uidnext = highestmodseq = 0; + } + else + { + a = [theSyncToken componentsSeparatedByString: @"-"]; + uidnext = [[a objectAtIndex: 0] intValue]; + highestmodseq = [[a objectAtIndex: 1] intValue]; + } + + // We first make sure QRESYNC is enabled + [[self imap4Connection] enableExtensions: [NSArray arrayWithObject: @"QRESYNC"]]; + + + // We fetch new messages and modified messages + if (highestmodseq) + { + EOKeyValueQualifier *kvQualifier; + NSNumber *nextModseq; + + nextModseq = [NSNumber numberWithUnsignedLongLong: highestmodseq + 1]; + kvQualifier = [[EOKeyValueQualifier alloc] + initWithKey: @"modseq" + operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo + value: nextModseq]; + searchQualifier = [[EOAndQualifier alloc] + initWithQualifiers: + kvQualifier, [self _nonDeletedQualifier], nil]; + [kvQualifier release]; + [searchQualifier autorelease]; + } + else + { + searchQualifier = [self _nonDeletedQualifier]; + } + + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(DATE >= %@)", theStartDate]; + + searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: searchQualifier, sinceDateQualifier, + nil]; + [searchQualifier autorelease]; + } + + + // we fetch modified or added uids + uids = [self fetchUIDsMatchingQualifier: searchQualifier + sortOrdering: nil]; + + for (i = 0; i < [uids count]; i++) + { + // New messages + if ([[uids objectAtIndex: i] intValue] >= uidnext) + { + d = [NSDictionary dictionaryWithObject: @"added" forKey: [uids objectAtIndex: i]]; + } + // Changed messages + else + { + d = [NSDictionary dictionaryWithObject: @"changed" forKey: [uids objectAtIndex: i]]; + } + + [allTokens addObject: d]; + } + + + // We fetch deleted ones + if (highestmodseq > 0) + { + uids = [self fetchUIDsOfVanishedItems: highestmodseq]; + + for (i = 0; i < [uids count]; i++) + { + d = [NSDictionary dictionaryWithObject: @"deleted" forKey: [uids objectAtIndex: i]]; + [allTokens addObject: d]; + } + } + + return allTokens; +} + @end /* SOGoMailFolder */ @implementation SOGoSpecialMailFolder diff --git a/SoObjects/Mailer/SOGoMailForward.h b/SoObjects/Mailer/SOGoMailForward.h index a330a033a..54dc5f63b 100644 --- a/SoObjects/Mailer/SOGoMailForward.h +++ b/SoObjects/Mailer/SOGoMailForward.h @@ -31,9 +31,12 @@ NSString *field; NSString *currentValue; BOOL htmlComposition; + NSString *signaturePlacement; } - (void) setSourceMail: (SOGoMailObject *) newSourceMail; +- (void) setSignaturePlacement: (NSString *) newPlacement; +- (BOOL) signaturePlacementOnTop; @end diff --git a/SoObjects/Mailer/SOGoMailForward.m b/SoObjects/Mailer/SOGoMailForward.m index ad7f12c23..2de3a64c1 100644 --- a/SoObjects/Mailer/SOGoMailForward.m +++ b/SoObjects/Mailer/SOGoMailForward.m @@ -56,6 +56,16 @@ [super dealloc]; } +- (void) setSignaturePlacement: (NSString *) newPlacement +{ + signaturePlacement = newPlacement; +} + +- (BOOL) signaturePlacementOnTop +{ + return [signaturePlacement isEqual: @"above"]; +} + - (void) setSourceMail: (SOGoMailObject *) newSourceMail { ASSIGN (sourceMail, newSourceMail); diff --git a/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.html b/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.html index b7e19d1ee..dfe8cc948 100644 --- a/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.html +++ b/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Message original --------<#newLine/> Sujet: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ De: <#from/><#newLine/> <#hasCc>Copie: <#cc/><#hasNewsGroups>Forums de discussion: <#newsgroups/><#hasReferences>Références: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.wod b/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.wod +++ b/SoObjects/Mailer/SOGoMailFrenchForward.wo/SOGoMailFrenchForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html index e85ff84e1..9c0965b44 100644 --- a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html +++ b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original-Nachricht --------<#newLine/> Betreff: <#subject/><#newLine/> Datum: <#date/><#newLine/> @@ -9,5 +10,7 @@ Von: <#from/><#newLine/> <#hasCc>Kopie: <#cc/><#hasNewsGroups>Newsgroup: <#newsgroups/><#hasReferences>Referenzen: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.wod b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.wod +++ b/SoObjects/Mailer/SOGoMailGermanForward.wo/SOGoMailGermanForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.html b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.html index 150f7de03..89425fbd2 100644 --- a/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.html +++ b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Eredeti üzenet --------<#newLine/> Tárgy: <#subject/><#newLine/> Dátum: <#date/><#newLine/> @@ -9,5 +10,7 @@ Feladó: <#from/><#newLine/> <#hasCc>Másolat: <#cc/><#hasNewsGroups>Hírcsoportok: <#newsgroups/><#hasReferences>Hivatkozások: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod +++ b/SoObjects/Mailer/SOGoMailHungarianForward.wo/SOGoMailHungarianForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.html b/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.html index ab729ce4e..d9aaf9248 100644 --- a/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.html +++ b/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Upprunalegt bréf --------<#newLine/> Efni: <#subject/><#newLine/> Dags: <#date/><#newLine/> @@ -9,5 +10,7 @@ Frá: <#from/><#newLine/> <#hasCc>Afrit: <#cc/><#hasNewsGroups>Fréttahópar: <#newsgroups/><#hasReferences>Tilvísanir: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.wod b/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.wod +++ b/SoObjects/Mailer/SOGoMailIcelandicForward.wo/SOGoMailIcelandicForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.html b/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.html index 8f7c009a3..d50ff360c 100644 --- a/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.html +++ b/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Messaggio originale --------<#newLine/> Oggetto: <#subject/><#newLine/> Data: <#date/><#newLine/> @@ -9,5 +10,7 @@ Da: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>Riferimento: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.wod b/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.wod index add34bf51..6bd3f55a2 100644 --- a/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.wod +++ b/SoObjects/Mailer/SOGoMailItalianForward.wo/SOGoMailItalianForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailLabel.m b/SoObjects/Mailer/SOGoMailLabel.m index 413684985..1d822e90b 100644 --- a/SoObjects/Mailer/SOGoMailLabel.m +++ b/SoObjects/Mailer/SOGoMailLabel.m @@ -68,7 +68,6 @@ component: (UIxComponent *) theComponent { NSMutableArray *allLabels, *allKeys; - NSDictionary *mailLabelsColors; NSString *key, *name; SOGoMailLabel *label; NSArray *values; @@ -81,7 +80,7 @@ { key = [allKeys objectAtIndex: i]; values = [theDefaults objectForKey: key]; - name = [theComponent labelForKey: [values objectAtIndex: 0]]; + name = [theComponent commonLabelForKey: [values objectAtIndex: 0]]; label = [[self alloc] initWithName: key label: name diff --git a/SoObjects/Mailer/SOGoMailManager.h b/SoObjects/Mailer/SOGoMailManager.h index fdd743656..54b8002d0 100644 --- a/SoObjects/Mailer/SOGoMailManager.h +++ b/SoObjects/Mailer/SOGoMailManager.h @@ -1,14 +1,14 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - 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. diff --git a/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.html b/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.html index e630e89f0..4c293b941 100644 --- a/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.html +++ b/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Originalpost --------<#newLine/> Emne: <#subject/><#newLine/> Dato: <#date/><#newLine/> @@ -9,5 +10,7 @@ Fra: <#from/><#newLine/> <#hasCc>Kopi: <#cc/><#hasNewsGroups>Nyhetsgrupper: <#newsgroups/><#hasReferences>Referanser: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.wod b/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.wod +++ b/SoObjects/Mailer/SOGoMailNorwegianBokmalForward.wo/SOGoMailNorwegianBokmalForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.html b/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.html index e630e89f0..4c293b941 100644 --- a/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.html +++ b/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Originalpost --------<#newLine/> Emne: <#subject/><#newLine/> Dato: <#date/><#newLine/> @@ -9,5 +10,7 @@ Fra: <#from/><#newLine/> <#hasCc>Kopi: <#cc/><#hasNewsGroups>Nyhetsgrupper: <#newsgroups/><#hasReferences>Referanser: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.wod b/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.wod +++ b/SoObjects/Mailer/SOGoMailNorwegianNynorskForward.wo/SOGoMailNorwegianNynorskForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.h b/SoObjects/Mailer/SOGoMailObject+Draft.h index c4054fa1d..1a106a2c0 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.h +++ b/SoObjects/Mailer/SOGoMailObject+Draft.h @@ -28,7 +28,6 @@ @interface SOGoMailObject (SOGoDraftObjectExtensions) - (NSString *) contentForEditing; -- (NSArray *) fetchFileAttachmentKeys; - (NSString *) subjectForReply; - (NSString *) contentForReply; diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 00f34be50..1aa24ea5b 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -34,7 +34,6 @@ #import #import -#import "NSDictionary+Mail.h" #import "NSString+Mail.h" #import "SOGoMailForward.h" #import "SOGoMailObject+Draft.h" @@ -42,6 +41,9 @@ #define maxFilenameLength 64 +// +// +// @implementation SOGoMailObject (SOGoDraftObjectExtensions) - (NSString *) subjectForReply @@ -77,6 +79,9 @@ return newSubject; } +// +// +// - (NSString *) _convertRawContentForEditing: (NSString *) raw rawHtml: (BOOL) html { @@ -89,13 +94,16 @@ if (html && !htmlComposition) rc = [raw htmlToText]; else if (!html && htmlComposition) - rc = [raw stringByConvertingCRLNToHTML]; + rc = [[raw stringByEscapingHTMLString] stringByConvertingCRLNToHTML]; else rc = raw; return rc; } +// +// +// - (NSString *) _contentForEditingFromKeys: (NSArray *) keys { NSArray *types; @@ -151,6 +159,9 @@ return content; } +// +// +// - (NSString *) contentForEditing { NSMutableArray *keys; @@ -160,12 +171,15 @@ = [NSArray arrayWithObjects: @"text/plain", @"text/html", nil]; keys = [NSMutableArray array]; [self addRequiredKeysOfStructure: [self bodyStructure] - path: @"" toArray: keys acceptedTypes: acceptedTypes - withPeek: NO]; + path: @"" toArray: keys acceptedTypes: acceptedTypes + withPeek: NO]; return [self _contentForEditingFromKeys: keys]; } +// +// +// - (NSString *) contentForReply { NSString *pageName; @@ -185,6 +199,9 @@ return [[page generateResponse] contentAsString]; } +// +// +// - (NSString *) filenameForForward { NSString *subject; @@ -218,6 +235,9 @@ return newSubject; } +// +// +// - (NSString *) subjectForForward { NSString *subject, *newSubject; @@ -231,110 +251,24 @@ return newSubject; } +// +// +// - (NSString *) contentForInlineForward { - SOGoUserDefaults *ud; + SOGoUserDefaults *userDefaults; NSString *pageName; SOGoMailForward *page; - ud = [[context activeUser] userDefaults]; + userDefaults = [[context activeUser] userDefaults]; pageName = [NSString stringWithFormat: @"SOGoMail%@Forward", - [ud language]]; + [userDefaults language]]; page = [[WOApplication application] pageWithName: pageName inContext: context]; [page setSourceMail: self]; + [page setSignaturePlacement: [userDefaults mailSignaturePlacement]]; return [[page generateResponse] contentAsString]; } -- (void) _fetchFileAttachmentKey: (NSDictionary *) part - intoArray: (NSMutableArray *) keys - withPath: (NSString *) path -{ - NSString *filename, *mimeType; - NSDictionary *currentFile; - - filename = [part filename]; - - mimeType = [NSString stringWithFormat: @"%@/%@", - [part objectForKey: @"type"], - [part objectForKey: @"subtype"]]; - - if (filename) - { - currentFile = [NSDictionary dictionaryWithObjectsAndKeys: - filename, @"filename", - [mimeType lowercaseString], @"mimetype", - path, @"path", - [part objectForKey: @"encoding"], @"encoding", nil]; - [keys addObject: currentFile]; - } - else - { - // We might end up here because of MUA that actually strips the - // Content-Disposition (and thus, the filename) when mails containing - // attachments have been forwarded. Thunderbird (2.x) does just that - // when forwarding mails with images attached to them (using cid:...). - if ([mimeType hasPrefix: @"application/"] || - [mimeType hasPrefix: @"audio/"] || - [mimeType hasPrefix: @"image/"] || - [mimeType hasPrefix: @"video/"]) - { - currentFile = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat: @"unkown_%@", path], @"filename", - [mimeType lowercaseString], @"mimetype", - path, @"path", - [part objectForKey: @"encoding"], @"encoding", - nil]; - [keys addObject: currentFile]; - } - } -} - -- (void) _fetchFileAttachmentKeysInPart: (NSDictionary *) part - intoArray: (NSMutableArray *) keys - withPath: (NSString *) path -{ - NSMutableDictionary *currentPart; - NSString *newPath; - NSArray *subparts; - NSString *type; - NSUInteger i; - - type = [[part objectForKey: @"type"] lowercaseString]; - if ([type isEqualToString: @"multipart"]) - { - subparts = [part objectForKey: @"parts"]; - for (i = 1; i <= [subparts count]; i++) - { - currentPart = [subparts objectAtIndex: i-1]; - if (path) - newPath = [NSString stringWithFormat: @"%@.%d", path, i]; - else - newPath = [NSString stringWithFormat: @"%d", i]; - [self _fetchFileAttachmentKeysInPart: currentPart - intoArray: keys - withPath: newPath]; - } - } - else - { - if (!path) - path = @"1"; - [self _fetchFileAttachmentKey: part intoArray: keys withPath: path]; - } -} - -#warning we might need to handle parts with a "name" attribute -- (NSArray *) fetchFileAttachmentKeys -{ - NSMutableArray *keys; - - keys = [NSMutableArray array]; - [self _fetchFileAttachmentKeysInPart: [self bodyStructure] - intoArray: keys withPath: nil]; - - return keys; -} - @end diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index cc56324d7..46c1d7c14 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -81,6 +81,8 @@ NSArray *SOGoMailCoreInfoKeys; - (id) bodyStructure; - (id) lookupInfoForBodyPart:(id)_path; +- (id) lookupImap4BodyPartKey: (NSString *) _key + inContext: (id) _ctx; /* content */ @@ -102,7 +104,9 @@ NSArray *SOGoMailCoreInfoKeys; - (NSDictionary *) fetchPlainTextParts; - (NSDictionary *) fetchPlainTextStrings:(NSArray *)_fetchKeys; -- (NSDictionary *) fetchAttachmentIds; +- (BOOL) hasAttachment; +- (NSDictionary *) fetchFileAttachmentIds; +- (NSArray *) fetchFileAttachmentKeys; /* flags */ diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 9a73eb92b..85eacf5b9 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -52,6 +52,7 @@ #import "NSString+Mail.h" #import "NSData+Mail.h" +#import "NSDictionary+Mail.h" #import "SOGoMailFolder.h" #import "SOGoMailAccount.h" #import "SOGoMailAccounts.h" @@ -705,9 +706,9 @@ static BOOL debugSoParts = NO; return urlToPart; } -- (void) _feedAttachmentIds: (NSMutableDictionary *) attachmentIds - withInfos: (NSDictionary *) infos - andPrefix: (NSString *) prefix +- (void) _feedFileAttachmentIds: (NSMutableDictionary *) attachmentIds + withInfos: (NSDictionary *) infos + andPrefix: (NSString *) prefix { NSArray *parts; NSDictionary *currentPart; @@ -727,14 +728,14 @@ static BOOL debugSoParts = NO; for (count = 0; count < max; count++) { currentPart = [parts objectAtIndex: count]; - [self _feedAttachmentIds: attachmentIds - withInfos: currentPart - andPrefix: [NSString stringWithFormat: @"%@/%d", - prefix, count + 1]]; + [self _feedFileAttachmentIds: attachmentIds + withInfos: currentPart + andPrefix: [NSString stringWithFormat: @"%@/%d", + prefix, count + 1]]; } } -- (NSDictionary *) fetchAttachmentIds +- (NSDictionary *) fetchFileAttachmentIds { NSMutableDictionary *attachmentIds; NSString *prefix; @@ -745,13 +746,122 @@ static BOOL debugSoParts = NO; prefix = [[self soURL] absoluteString]; if ([prefix hasSuffix: @"/"]) prefix = [prefix substringToIndex: [prefix length] - 1]; - [self _feedAttachmentIds: attachmentIds + [self _feedFileAttachmentIds: attachmentIds withInfos: [coreInfos objectForKey: @"bodystructure"] andPrefix: prefix]; return attachmentIds; } +// +// +// +- (void) _fetchFileAttachmentKey: (NSDictionary *) part + intoArray: (NSMutableArray *) keys + withPath: (NSString *) path + andPrefix: (NSString *) prefix +{ + NSString *filename, *mimeType, *filenameURL; + NSDictionary *currentFile; + + filename = [part filename]; + + mimeType = [NSString stringWithFormat: @"%@/%@", + [part objectForKey: @"type"], + [part objectForKey: @"subtype"]]; + + if (!filename) + // We might end up here because of MUA that actually strips the + // Content-Disposition (and thus, the filename) when mails containing + // attachments have been forwarded. Thunderbird (2.x) does just that + // when forwarding mails with images attached to them (using cid:...). + if ([mimeType hasPrefix: @"application/"] || + [mimeType hasPrefix: @"audio/"] || + [mimeType hasPrefix: @"image/"] || + [mimeType hasPrefix: @"video/"]) + filename = [NSString stringWithFormat: @"unknown_%@", path]; + + if (filename) + { + // We replace any slash by a dash since Apache won't allow encoded slashes by default. + // See http://httpd.apache.org/docs/2.2/mod/core.html#allowencodedslashes + // See [UIxMailPartViewer _filenameForAttachment:] + filenameURL = [[filename stringByReplacingString: @"/" withString: @"-"] stringByEscapingURL]; + currentFile = [NSDictionary dictionaryWithObjectsAndKeys: + filename, @"filename", + [mimeType lowercaseString], @"mimetype", + path, @"path", + [part objectForKey: @"encoding"], @"encoding", + [part objectForKey:@ "size"], @"size", + [NSString stringWithFormat: @"%@/%@", prefix, filenameURL], @"url", + [NSString stringWithFormat: @"%@/asAttachment/%@", prefix, filenameURL], @"urlAsAttachment", + nil]; + [keys addObject: currentFile]; + } +} + +// +// +// +- (void) _fetchFileAttachmentKeysInPart: (NSDictionary *) part + intoArray: (NSMutableArray *) keys + withPath: (NSString *) path + andPrefix: (NSString *) prefix +{ + NSMutableDictionary *currentPart; + NSString *newPath; + NSArray *subparts; + NSString *type; + NSUInteger i; + + type = [[part objectForKey: @"type"] lowercaseString]; + if ([type isEqualToString: @"multipart"]) + { + subparts = [part objectForKey: @"parts"]; + for (i = 1; i <= [subparts count]; i++) + { + currentPart = [subparts objectAtIndex: i-1]; + if (path) + newPath = [NSString stringWithFormat: @"%@.%d", path, i]; + else + newPath = [NSString stringWithFormat: @"%d", i]; + [self _fetchFileAttachmentKeysInPart: currentPart + intoArray: keys + withPath: newPath + andPrefix: [NSString stringWithFormat: @"%@/%i", prefix, i]]; + } + } + else + { + if (!path) + path = @"1"; + [self _fetchFileAttachmentKey: part + intoArray: keys + withPath: path + andPrefix: prefix]; + } +} + +// +// +// +#warning we might need to handle parts with a "name" attribute +- (NSArray *) fetchFileAttachmentKeys +{ + NSString *prefix; + NSMutableArray *keys; + + prefix = [[self soURL] absoluteString]; + if ([prefix hasSuffix: @"/"]) + prefix = [prefix substringToIndex: [prefix length] - 1]; + + keys = [NSMutableArray array]; + [self _fetchFileAttachmentKeysInPart: [self bodyStructure] + intoArray: keys withPath: nil andPrefix: prefix]; + + return keys; +} + /* convert parts to strings */ - (NSString *) stringForData: (NSData *) _data partInfo: (NSDictionary *) _info @@ -1353,7 +1463,7 @@ static BOOL debugSoParts = NO; - (BOOL) hasAttachment { - return ([[self fetchAttachmentIds] count] > 0); + return ([[self fetchFileAttachmentKeys] count] > 0); } - (BOOL) isNewMail diff --git a/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.html b/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.html index 0b2851965..7beabc8df 100644 --- a/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.html +++ b/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Oryginalna wiadomość --------<#newLine/> Temat: <#subject/><#newLine/> Data: <#date/><#newLine/> @@ -9,5 +10,7 @@ Od: <#from/><#newLine/> <#hasCc>DW: <#cc/><#hasNewsGroups>Grupy dyskusyjne: <#newsgroups/><#hasReferences>Odniesienia: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.wod b/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.wod +++ b/SoObjects/Mailer/SOGoMailPolishForward.wo/SOGoMailPolishForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailReply.h b/SoObjects/Mailer/SOGoMailReply.h index e519138bb..f83d1e872 100644 --- a/SoObjects/Mailer/SOGoMailReply.h +++ b/SoObjects/Mailer/SOGoMailReply.h @@ -29,15 +29,12 @@ { BOOL outlookMode; NSString *replyPlacement; - NSString *signaturePlacement; } - (void) setOutlookMode: (BOOL) newOutlookMode; - (BOOL) outlookMode; - (void) setReplyPlacement: (NSString *) newPlacement; - (BOOL) replyPlacementOnTop; -- (void) setSignaturePlacement: (NSString *) newPlacement; -- (BOOL) signaturePlacementOnTop; - (NSString *) messageBody; @end diff --git a/SoObjects/Mailer/SOGoMailReply.m b/SoObjects/Mailer/SOGoMailReply.m index 8755306ee..a47e65d54 100644 --- a/SoObjects/Mailer/SOGoMailReply.m +++ b/SoObjects/Mailer/SOGoMailReply.m @@ -60,16 +60,6 @@ return [replyPlacement isEqual: @"above"]; } -- (void) setSignaturePlacement: (NSString *) newPlacement -{ - signaturePlacement = newPlacement; -} - -- (BOOL) signaturePlacementOnTop -{ - return [signaturePlacement isEqual: @"above"]; -} - - (NSString *) messageBody { NSString *s, *msgid; diff --git a/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.html b/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.html index f8b141ae2..ef3b83e17 100644 --- a/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.html +++ b/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Subject: <#subject/><#newLine/> Date: <#date/><#newLine/> @@ -9,5 +10,7 @@ From: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Newsgroups: <#newsgroups/><#hasReferences>References: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.wod b/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.wod +++ b/SoObjects/Mailer/SOGoMailRussianForward.wo/SOGoMailRussianForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.html b/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.html index 6ae765bb6..1019ae763 100644 --- a/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.html +++ b/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.html @@ -1,10 +1,16 @@ --------- Pôvodná správa -------- -Predmet: <#subject/> -Dátum: <#date/> -Od: <#from/> -<#hasReplyTo>Odpoveď na: <#replyTo/><#hasOrganization>Organizácia: <#organization/>Komu: <#to/> -<#hasCc>Kópia: <#cc/><#hasNewsGroups>Diskusné skupiny: <#newsgroups/><#hasReferences>Odkazy: <#references/> - -<#messageBody/> - -<#signature/> +<#newLine/> +<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> +-------- Pôvodná správa --------<#newLine/> +Predmet: <#subject/><#newLine/> +Dátum: <#date/><#newLine/> +Od: <#from/><#newLine/> +<#hasReplyTo>Odpoveď na: <#replyTo/><#hasOrganization>Organizácia: <#organization/>Komu: <#to/><#newLine/> +<#hasCc>Kópia: <#cc/><#hasNewsGroups>Diskusné skupiny: <#newsgroups/><#hasReferences>Odkazy: <#references/><#newLine/> +<#newLine/> +<#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> +<#newLine/> diff --git a/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.wod b/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.wod +++ b/SoObjects/Mailer/SOGoMailSlovakForward.wo/SOGoMailSlovakForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.html b/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.html index f706f241e..53988660d 100644 --- a/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.html +++ b/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Mensaje Original --------<#newLine/> Asunto: <#subject/><#newLine/> Fecha: <#date/><#newLine/> @@ -9,5 +10,7 @@ De: <#from/><#newLine/> <#hasCc>Cc: <#cc/><#hasNewsGroups>Grupo Noticias: <#newsgroups/><#hasReferences>Referencias: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.wod b/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.wod +++ b/SoObjects/Mailer/SOGoMailSpanishArgentinaForward.wo/SOGoMailSpanishArgentinaForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.html b/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.html index f706f241e..53988660d 100644 --- a/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.html +++ b/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Mensaje Original --------<#newLine/> Asunto: <#subject/><#newLine/> Fecha: <#date/><#newLine/> @@ -9,5 +10,7 @@ De: <#from/><#newLine/> <#hasCc>Cc: <#cc/><#hasNewsGroups>Grupo Noticias: <#newsgroups/><#hasReferences>Referencias: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.wod b/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.wod +++ b/SoObjects/Mailer/SOGoMailSpanishSpainForward.wo/SOGoMailSpanishSpainForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.html b/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.html index 6956b9c2b..715e700c3 100644 --- a/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.html +++ b/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Originalmeddelande --------<#newLine/> Ämne: <#subject/><#newLine/> Datum: <#date/><#newLine/> @@ -9,5 +10,7 @@ Från: <#from/><#newLine/> <#hasCc>Kopia: <#cc/><#hasNewsGroups>Nyhetsgrupper: <#newsgroups/><#hasReferences>Referenser: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.wod b/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.wod +++ b/SoObjects/Mailer/SOGoMailSwedishForward.wo/SOGoMailSwedishForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.html b/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.html index efeb00aed..ed584d61c 100644 --- a/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.html +++ b/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Початкове повідомлення --------<#newLine/> Тема: <#subject/><#newLine/> Дата: <#date/><#newLine/> @@ -9,5 +10,7 @@ <#hasCc>Копія: <#cc/><#hasNewsGroups>Групи новин: <#newsgroups/><#hasReferences>Посилання: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.wod b/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.wod +++ b/SoObjects/Mailer/SOGoMailUkrainianForward.wo/SOGoMailUkrainianForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html index 0e90a3f5a..1fbdb471a 100644 --- a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html +++ b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.html @@ -1,6 +1,7 @@ <#newLine/> <#newLine/> -<#newLine/> +<#signaturePlacementOnTop><#newLine/> +<#signature/><#newLine/> -------- Original Message --------<#newLine/> Testun: <#subject/><#newLine/> Dyddiad: <#date/><#newLine/> @@ -9,5 +10,7 @@ Oddi wrth: <#from/><#newLine/> <#hasCc>CC: <#cc/><#hasNewsGroups>Grwpiau newyddion: <#newsgroups/><#hasReferences>Cyfeirnodau: <#references/><#newLine/> <#newLine/> <#messageBody/><#newLine/> +<#signaturePlacementOnBottom><#newLine/> +<#newLine/> +<#signature/> <#newLine/> -<#signature/><#newLine/> diff --git a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.wod b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.wod index 7787fa18e..f2436acc9 100644 --- a/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.wod +++ b/SoObjects/Mailer/SOGoMailWelshForward.wo/SOGoMailWelshForward.wod @@ -77,3 +77,12 @@ signature: WOString { value = signature; escapeHTML = NO; } + +signaturePlacementOnTop: WOConditional { + condition = signaturePlacementOnTop; +} + +signaturePlacementOnBottom: WOConditional { + condition = signaturePlacementOnTop; + negate = YES; +} diff --git a/SoObjects/SOGo/LDAPSource.h b/SoObjects/SOGo/LDAPSource.h index 6dd6a2325..982c39543 100644 --- a/SoObjects/SOGo/LDAPSource.h +++ b/SoObjects/SOGo/LDAPSource.h @@ -1,6 +1,6 @@ /* LDAPSource.h - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-2014 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 @@ -79,8 +79,6 @@ BOOL passwordPolicy; - NSMutableDictionary *_dnCache; - /* resources handling */ NSString *kindField; NSString *multipleBookingsField; diff --git a/SoObjects/SOGo/LDAPSource.m b/SoObjects/SOGo/LDAPSource.m index af71b29eb..0495ee671 100644 --- a/SoObjects/SOGo/LDAPSource.m +++ b/SoObjects/SOGo/LDAPSource.m @@ -1,6 +1,6 @@ /* LDAPSource.m - this file is part of SOGo * - * Copyright (C) 2007-2013 Inverse inc. + * Copyright (C) 2007-2014 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 @@ -37,6 +37,7 @@ #import "NSArray+Utilities.h" #import "NSString+Utilities.h" #import "NSString+Crypto.h" +#import "SOGoCache.h" #import "SOGoDomainDefaults.h" #import "SOGoSystemDefaults.h" @@ -118,7 +119,6 @@ static Class NSStringK; MSExchangeHostname = nil; - _dnCache = [[NSMutableDictionary alloc] init]; modifiers = nil; } @@ -155,7 +155,6 @@ static Class NSStringK; [_scope release]; [searchAttributes release]; [domain release]; - [_dnCache release]; [kindField release]; [multipleBookingsField release]; [MSExchangeHostname release]; @@ -528,7 +527,7 @@ static Class NSStringK; if (queryTimeout > 0) [bindConnection setQueryTimeLimit: queryTimeout]; - userDN = [_dnCache objectForKey: _login]; + userDN = [[SOGoCache sharedCache] distinguishedNameForLogin: _login]; if (!userDN) { @@ -550,9 +549,6 @@ static Class NSStringK; if (userDN) { - // We cache the _login <-> userDN entry to speed up things - [_dnCache setObject: userDN forKey: _login]; - if (!passwordPolicy) didBind = [bindConnection bindWithMethod: @"simple" binddn: userDN @@ -564,6 +560,11 @@ static Class NSStringK; perr: (void *)_perr expire: _expire grace: _grace]; + + if (didBind) + // We cache the _login <-> userDN entry to speed up things + [[SOGoCache sharedCache] setDistinguishedName: userDN + forLogin: _login]; } } } @@ -717,7 +718,7 @@ static Class NSStringK; [qs appendFormat: @"(%@='*')", CNField]; else { - fieldFormat = [NSString stringWithFormat: @"(%%@='%@*')", escapedFilter]; + fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter]; fields = [NSMutableArray arrayWithArray: searchFields]; [fields addObjectsFromArray: mailFields]; [fields addObject: CNField]; @@ -1238,7 +1239,7 @@ static Class NSStringK; - (NSString *) lookupDNByLogin: (NSString *) theLogin { - return [_dnCache objectForKey: theLogin]; + return [[SOGoCache sharedCache] distinguishedNameForLogin: theLogin]; } - (NGLdapEntry *) _lookupGroupEntryByAttributes: (NSArray *) theAttributes diff --git a/SoObjects/SOGo/NSArray+DAV.m b/SoObjects/SOGo/NSArray+DAV.m index 836c98036..fe2e1a6d7 100644 --- a/SoObjects/SOGo/NSArray+DAV.m +++ b/SoObjects/SOGo/NSArray+DAV.m @@ -1,8 +1,6 @@ /* NSArray+DAV.m - this file is part of SOGo * - * Copyright (C) 2008 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2008-2013 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/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 245df3a3e..034d9818b 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -1,6 +1,6 @@ /* NSString+Utilities.h - this file is part of SOGo * - * Copyright (C) 2006-2013 Inverse inc. + * Copyright (C) 2006-2014 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 @@ -23,6 +23,7 @@ #import +@class NSCharacterSet; @class NSDictionary; @class NSObject; @@ -49,6 +50,7 @@ - (NSString *) asSafeSQLString; /* JSON */ +- (NSCharacterSet *) safeCharacterSet; - (NSString *) jsonRepresentation; - (BOOL) isJSONString; - (id) objectFromJSONString; diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index cb9889eb8..ba297cac7 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -1,6 +1,6 @@ /* NSString+Utilities.m - this file is part of SOGo * - * Copyright (C) 2006-2013 Inverse inc. + * Copyright (C) 2006-2014 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 @@ -46,6 +46,10 @@ static NSString **cssEscapingStrings = NULL; static unichar *cssEscapingCharacters = NULL; static int cssEscapingCount; +static unichar thisCharCode[29]; +static NSString *controlCharString = nil; +static NSCharacterSet *controlCharSet = nil; + @implementation NSString (SOGoURLExtension) - (NSString *) composeURLWithAction: (NSString *) action @@ -274,16 +278,12 @@ static int cssEscapingCount; return [NSString stringWithFormat: @"\"%@\"", representation]; } -- (NSString *) jsonRepresentation +- (NSCharacterSet *) safeCharacterSet { - static unichar thisCharCode[29]; - static NSString *controlCharString = nil; - static NSCharacterSet *controlCharSet = nil; - NSString *cleanedString; - int i, j; - if (!controlCharSet) { + int i, j; + // Create an array of chars for all control characters between 0x00 and 0x1F, // apart from \t, \n, \f and \r (0x08, 0x09, 0x0A, 0x0C and 0x0D) for (i = 0, j = 0x00; j < 0x08; i++, j++) { @@ -293,7 +293,7 @@ static int cssEscapingCount; for (j = 0x0E; j <= 0x1F; i++, j++) { thisCharCode[i] = j; } - + // Also add some unicode separators thisCharCode[i++] = 0x2028; // line separator thisCharCode[i++] = 0x2029; // paragraph separator @@ -302,8 +302,15 @@ static int cssEscapingCount; [controlCharSet retain]; } + return controlCharSet; +} + +- (NSString *) jsonRepresentation +{ + NSString *cleanedString; + // Escape double quotes and remove control characters - cleanedString = [[[self doubleQuotedString] componentsSeparatedByCharactersInSet: controlCharSet] + cleanedString = [[[self doubleQuotedString] componentsSeparatedByCharactersInSet: [self safeCharacterSet]] componentsJoinedByString: @""]; return cleanedString; } diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index af6be9787..648fc4b60 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -1,9 +1,6 @@ /* SOGoCache.h - this file is part of SOGo * - * Copyright (C) 2008-2013 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte + * Copyright (C) 2008-2014 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 @@ -109,6 +106,11 @@ - (NSDictionary *) messageSubmissionsCountForLogin: (NSString *) theLogin; +- (NSString *) distinguishedNameForLogin: (NSString *) theLogin; + +- (void) setDistinguishedName: (NSString *) theDN + forLogin: (NSString *) theLogin; + // // CAS support // @@ -124,7 +126,9 @@ - (void) removeCASSessionWithTicket: (NSString *) ticket; +// // SAML2 support +// - (NSDictionary *) saml2LoginDumpsForIdentifier: (NSString *) identifier; - (void) setSaml2LoginDumps: (NSDictionary *) dump forIdentifier: (NSString *) identifier; diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index 6e46fa11c..dac0b2d85 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -1,9 +1,6 @@ /* SOGoCache.m - this file is part of SOGo * - * Copyright (C) 2008-2013 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte + * Copyright (C) 2008-2014 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 @@ -34,6 +31,9 @@ * +defaults value = NSDictionary instance > user's defaults * +settings value = NSDictionary instance > user's settings * +attributes value = NSMutableDictionary instance > user's LDAP attributes + * +failedlogins value = + * +messagesubmissions value = + * +dn value = NSString instance > cached user's DN * +acl value = NSDictionary instance > ACLs on an object at specified path * + value = NSString instance (array components separated by ",") or group member logins for a specific group in domain * cas-id:< > value = @@ -114,6 +114,8 @@ static memcached_st *handle = NULL; handle = memcached_create(NULL); if (handle) { + memcached_behavior_set(handle, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + sd = [SOGoSystemDefaults sharedSystemDefaults]; // We define the default value for cleaning up cached users' @@ -433,7 +435,9 @@ static memcached_st *handle = NULL; } // -// +// Try to hit the local cache. If it misses, it'll +// then try memcached. If it hits memcached, it'll +// populate the local cache. // - (NSString *) _valuesOfType: (NSString *) theType forKey: (NSString *) theKey @@ -602,6 +606,21 @@ static memcached_st *handle = NULL; return d; } +// +// DN caching +// +- (NSString *) distinguishedNameForLogin: (NSString *) theLogin +{ + return [self _valuesOfType: @"dn" forKey: theLogin]; +} + +- (void) setDistinguishedName: (NSString *) theDN + forLogin: (NSString *) theLogin +{ + [self _cacheValues: theDN + ofType: @"dn" + forKey: theLogin]; +} // // CAS session support diff --git a/SoObjects/SOGo/SOGoDAVAuthenticator.h b/SoObjects/SOGo/SOGoDAVAuthenticator.h index 0691cb2bf..acbdb2349 100644 --- a/SoObjects/SOGo/SOGoDAVAuthenticator.h +++ b/SoObjects/SOGo/SOGoDAVAuthenticator.h @@ -1,14 +1,15 @@ /* + Copyright (C) 2007-2013 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - 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. @@ -26,18 +27,6 @@ #import "SOGoAuthenticator.h" -/* - SOGoDAVAuthenticator - - This just overrides the login/pwd check method and always returns YES since - the password is already checked in Apache. -*/ - -@class NSUserDefaults; -@class NSString; - -@class SOGoUser; - @interface SOGoDAVAuthenticator : SoHTTPAuthenticator + (id) sharedSOGoDAVAuthenticator; diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index 9b5b7050d..f9bb2fef0 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -3,7 +3,7 @@ SxVMemLimit = 384; WOLogFile = "/var/log/sogo/sogo.log"; WOPidFile = "/var/run/sogo/sogo.pid"; - + WOHTTPAdaptorCapitalizeHeaders = YES; WOPort = "127.0.0.1:20000"; NGImap4ConnectionStringSeparator = "/"; @@ -69,12 +69,10 @@ SOGoCalendarShouldDisplayWeekend = YES; SOGoCalendarEventsDefaultClassification = "PUBLIC"; SOGoCalendarTasksDefaultClassification = "PUBLIC"; + SOGoCalendarDefaultReminder = "NONE"; SOGoFreeBusyDefaultInterval = ( 7, 7 ); - SOGoReminderEnabled = YES; - SOGoRemindWithASound = YES; - SOGoSearchMinimumWordLength = 2; SOGoMailLabelsColors = { diff --git a/SoObjects/SOGo/SOGoFolder.h b/SoObjects/SOGo/SOGoFolder.h index 1fa1c6ef8..186036db3 100644 --- a/SoObjects/SOGo/SOGoFolder.h +++ b/SoObjects/SOGo/SOGoFolder.h @@ -1,8 +1,6 @@ /* SOGoFolder.h - this file is part of SOGo * - * Copyright (C) 2007 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2013 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/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index 9eab2ff90..ec6795bd2 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -1,6 +1,6 @@ /* Copyright (C) 2004-2005 SKYRIX Software AG - Copyright (C) 2006-2010 Inverse inc. + Copyright (C) 2006-2013 Inverse inc. This file is part of SOGo. @@ -125,6 +125,11 @@ - (NSString *) davCollectionTag; +- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties + matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate; + + /* multiget helper */ - (WOResponse *) performMultigetInContext: (WOContext *) queryContext inNamespace: (NSString *) namespace; diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index f5b2ee691..135a07dc0 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -788,16 +788,22 @@ static NSArray *childRecordFields = nil; - (void) deleteEntriesWithIds: (NSArray *) ids { unsigned int count, max; - NSString *currentID; + NSEnumerator *names; + NSString *currentID, *currentName; SOGoContentObject *deleteObject; max = [ids count]; for (count = 0; count < max; count++) { currentID = [ids objectAtIndex: count]; - deleteObject = [self lookupName: currentID + names = [[currentID componentsSeparatedByString: @"/"] objectEnumerator]; + deleteObject = self; + while ((currentName = [names nextObject])) + { + deleteObject = [deleteObject lookupName: currentName inContext: context acquire: NO]; + } if (![deleteObject isKindOfClass: [NSException class]]) { if ([deleteObject respondsToSelector: @selector (prepareDelete)]) @@ -1093,8 +1099,15 @@ static NSArray *childRecordFields = nil; return @""; } -- (NSArray *) _fetchSyncTokenFields: (NSDictionary *) properties - matchingSyncToken: (NSString *) syncToken +// +// Method used to get all changes since a particular sync token +// +// It'll return standard properties (c_name, c_creationdate, etc...) +// of new, modified and deleted components. +// +- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties + matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate { /* TODO: - validation: @@ -1118,8 +1131,20 @@ static NSArray *childRecordFields = nil; if ([syncToken length]) { syncTokenInt = [syncToken intValue]; + qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_lastmodified > %d", syncTokenInt]; + + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier, + nil]; + [qualifier autorelease]; + } + mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields withQualifier: qualifier ignoreDeleted: YES]]; @@ -1139,10 +1164,24 @@ static NSArray *childRecordFields = nil; qualifier = [EOQualifier qualifierWithQualifierFormat: filter]; else qualifier = nil; - records = [self _fetchFields: fields withQualifier: qualifier + + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier, + nil]; + [qualifier autorelease]; + } + + records = [self _fetchFields: fields + withQualifier: qualifier ignoreDeleted: YES]; } + + return records; } @@ -1400,8 +1439,9 @@ static NSArray *childRecordFields = nil; propElement = [(NGDOMNodeWithChildren *) documentElement firstElementWithTag: @"prop" inNamespace: XMLNS_WEBDAV]; properties = [self parseDAVRequestedProperties: propElement]; - records = [self _fetchSyncTokenFields: properties - matchingSyncToken: syncToken]; + records = [self syncTokenFieldsWithProperties: properties + matchingSyncToken: syncToken + fromDate: nil]; [self _appendComponentProperties: [properties allKeys] fromRecords: records matchingSyncToken: [syncToken intValue] diff --git a/SoObjects/SOGo/SOGoGroup.m b/SoObjects/SOGo/SOGoGroup.m index 4a170be9b..648b97ae4 100644 --- a/SoObjects/SOGo/SOGoGroup.m +++ b/SoObjects/SOGo/SOGoGroup.m @@ -1,8 +1,6 @@ /* SOGoGroup.m - this file is part of SOGo * - * Copyright (C) 2009-2012 Inverse inc. - * - * Author: Ludovic Marcotte + * Copyright (C) 2009-2014 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/SoObjects/SOGo/SOGoObject.h b/SoObjects/SOGo/SOGoObject.h index 172f45a60..114b0c55d 100644 --- a/SoObjects/SOGo/SOGoObject.h +++ b/SoObjects/SOGo/SOGoObject.h @@ -122,7 +122,7 @@ - (NSException *)delete; - (id) DELETEAction: (id) _ctx; -- (id)GETAction:(id)_ctx; +- (id) GETAction:(id)_ctx; /* etag support */ diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h index c0041879b..0413faafd 100644 --- a/SoObjects/SOGo/SOGoParentFolder.h +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -1,8 +1,6 @@ /* SOGoParentFolder.h - this file is part of SOGo * - * Copyright (C) 2006-2009 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2013 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/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index e806beca0..da5a1c5b5 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -278,12 +278,11 @@ static SoSecurityManager *sm = nil; NSMutableDictionary *folderDisplayNames; NSMutableArray *subscribedReferences; SOGoUserSettings *settings; - NSEnumerator *allKeys; NSString *currentKey; SOGoUser *ownerUser; NSException *error; id o; - + int i; BOOL dirty; error = nil; /* we ignore non-DB errors at this time... */ @@ -300,9 +299,9 @@ static SoSecurityManager *sm = nil; else folderDisplayNames = nil; - allKeys = [subscribedReferences objectEnumerator]; - while ((currentKey = [allKeys nextObject])) + for (i = [subscribedReferences count] - 1; i >= 0; i--) { + currentKey = [subscribedReferences objectAtIndex: i]; if (![self _appendSubscribedSource: currentKey]) { // We no longer have access to this subscription, let's @@ -322,6 +321,7 @@ static SoSecurityManager *sm = nil; if (folderDisplayNames) [[settings objectForKey: nameInContainer] setObject: folderDisplayNames forKey: @"FolderDisplayNames"]; + [settings synchronize]; } return error; diff --git a/SoObjects/SOGo/SOGoSieveManager.h b/SoObjects/SOGo/SOGoSieveManager.h index 2f180426a..783a948d8 100644 --- a/SoObjects/SOGo/SOGoSieveManager.h +++ b/SoObjects/SOGo/SOGoSieveManager.h @@ -29,6 +29,7 @@ @class NSDictionary; @class NSMutableArray; @class NSString; +@class NGSieveClient; @class SOGoMailAccount; @class SOGoUser; @@ -45,10 +46,9 @@ - (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements; - (NSString *) lastScriptError; -- (BOOL) updateFiltersForLogin: (NSString *) theLogin - authname: (NSString *) theAuthName - password: (NSString *) thePassword - account: (SOGoMailAccount *) theAccount; +- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount; + +- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount; @end diff --git a/SoObjects/SOGo/SOGoSieveManager.m b/SoObjects/SOGo/SOGoSieveManager.m index 603e21e28..72a4cfc25 100644 --- a/SoObjects/SOGo/SOGoSieveManager.m +++ b/SoObjects/SOGo/SOGoSieveManager.m @@ -42,6 +42,7 @@ typedef enum { UIxFilterFieldTypeAddress, UIxFilterFieldTypeHeader, + UIxFilterFieldTypeBody, UIxFilterFieldTypeSize, } UIxFilterFieldType; @@ -50,6 +51,7 @@ static NSArray *sieveSizeOperators = nil; static NSMutableDictionary *fieldTypes = nil; static NSDictionary *sieveFields = nil; static NSDictionary *sieveFlags = nil; +static NSDictionary *typeRequirements = nil; static NSDictionary *operatorRequirements = nil; static NSMutableDictionary *methodRequirements = nil; static NSString *sieveScriptName = @"sogo"; @@ -136,13 +138,13 @@ static NSString *sieveScriptName = @"sogo"; fieldTypes = [NSMutableDictionary new]; fields = [NSArray arrayWithObjects: @"to", @"cc", @"to_or_cc", @"from", nil]; - [fieldTypes setObject: [NSNumber - numberWithInt: UIxFilterFieldTypeAddress] + [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeAddress] forKeys: fields]; fields = [NSArray arrayWithObjects: @"header", @"subject", nil]; - [fieldTypes setObject: [NSNumber - numberWithInt: UIxFilterFieldTypeHeader] + [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeHeader] forKeys: fields]; + [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeBody] + forKey: @"body"]; [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeSize] forKey: @"size"]; } @@ -169,14 +171,17 @@ static NSString *sieveScriptName = @"sogo"; @"Junk", @"junk", @"NotJunk", @"not_junk", @"\\Seen", @"seen", - @"$Label1", @"label1", - @"$Label2", @"label2", - @"$Label3", @"label3", - @"$Label4", @"label4", - @"$Label5", @"label5", nil]; [sieveFlags retain]; } + if (!typeRequirements) + { + typeRequirements + = [NSDictionary dictionaryWithObjectsAndKeys: + @"body", [NSNumber numberWithInt: UIxFilterFieldTypeBody], + nil]; + [typeRequirements retain]; + } if (!operatorRequirements) { operatorRequirements @@ -252,7 +257,7 @@ static NSString *sieveScriptName = @"sogo"; andType: (UIxFilterFieldType *) type { NSNumber *fieldType; - NSString *jsonField, *customHeader; + NSString *jsonField, *customHeader, *requirement; jsonField = [rule objectForKey: @"field"]; if (jsonField) @@ -270,10 +275,15 @@ static NSString *sieveScriptName = @"sogo"; scriptError = (@"Pseudo-header field 'header' without" @" 'custom_header' parameter."); } - else if ([jsonField isEqualToString: @"size"]) + else if ([jsonField isEqualToString: @"body"] || + [jsonField isEqualToString: @"size"]) *field = nil; else *field = [sieveFields objectForKey: jsonField]; + + requirement = [typeRequirements objectForKey: fieldType]; + if (requirement) + [requirements addObjectUniquely: requirement]; } else scriptError @@ -332,6 +342,7 @@ static NSString *sieveScriptName = @"sogo"; if (type == UIxFilterFieldTypeSize) rc = [sieveSizeOperators containsObject: operator]; else + // Header and Body types rc = (![sieveSizeOperators containsObject: operator] && [sieveOperators containsObject: operator]); @@ -370,17 +381,23 @@ static NSString *sieveScriptName = @"sogo"; sieveRule = [NSMutableString stringWithCapacity: 100]; if (revert) [sieveRule appendString: @"not "]; + if (type == UIxFilterFieldTypeAddress) [sieveRule appendString: @"address "]; else if (type == UIxFilterFieldTypeHeader) [sieveRule appendString: @"header "]; + else if (type == UIxFilterFieldTypeBody) + [sieveRule appendString: @"body :text "]; else if (type == UIxFilterFieldTypeSize) [sieveRule appendString: @"size "]; [sieveRule appendFormat: @":%@ ", operator]; + if (type == UIxFilterFieldTypeSize) [sieveRule appendFormat: @"%@K", value]; - else + else if (field) [sieveRule appendFormat: @"%@ %@", field, value]; + else + [sieveRule appendFormat: @"%@", value]; return sieveRule; } @@ -431,8 +448,8 @@ static NSString *sieveScriptName = @"sogo"; - (NSString *) _extractSieveAction: (NSDictionary *) action { - NSString *sieveAction, *method, *requirement, *argument, - *flag, *mailbox; + NSString *sieveAction, *method, *requirement, *argument, *flag, *mailbox; + NSDictionary *mailLabels; SOGoDomainDefaults *dd; sieveAction = nil; @@ -452,6 +469,12 @@ static NSString *sieveScriptName = @"sogo"; if ([method isEqualToString: @"addflag"]) { flag = [sieveFlags objectForKey: argument]; + if (!flag) + { + mailLabels = [[user userDefaults] mailLabelsColors]; + if ([mailLabels objectForKey: argument]) + flag = argument; + } if (flag) sieveAction = [NSString stringWithFormat: @"%@ %@", method, [flag asSieveQuotedString]]; @@ -529,24 +552,21 @@ static NSString *sieveScriptName = @"sogo"; { if ([match isEqualToString: @"all"] || [match isEqualToString: @"any"]) { - sieveRules - = [self _extractSieveRules: [newScript objectForKey: @"rules"]]; + sieveRules = [self _extractSieveRules: [newScript objectForKey: @"rules"]]; if (sieveRules) [sieveText appendFormat: @"if %@of (%@) {\r\n", - match, - [sieveRules componentsJoinedByString: @", "]]; + match, + [sieveRules componentsJoinedByString: @", "]]; else scriptError = [NSString stringWithFormat: - @"Test '%@' used without any" + @"Test '%@' used without any" @" specified rule", match]; } else - scriptError = [NSString stringWithFormat: @"Bad test: %@", - match]; + scriptError = [NSString stringWithFormat: @"Bad test: %@", match]; } - sieveActions = [self _extractSieveActions: - [newScript objectForKey: @"actions"]]; + sieveActions = [self _extractSieveActions: [newScript objectForKey: @"actions"]]; if ([sieveActions count]) [sieveText appendFormat: @" %@;\r\n", [sieveActions componentsJoinedByString: @";\r\n "]]; @@ -604,34 +624,25 @@ static NSString *sieveScriptName = @"sogo"; // // // -- (BOOL) updateFiltersForLogin: (NSString *) theLogin - authname: (NSString *) theAuthName - password: (NSString *) thePassword - account: (SOGoMailAccount *) theAccount +- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount { - NSMutableArray *req; - NSMutableString *script, *header; - NSDictionary *result, *values; - SOGoUserDefaults *ud; + NSDictionary *result; + NSString *login, *authname, *password; SOGoDomainDefaults *dd; NGSieveClient *client; - NSString *filterScript, *v, *sieveServer, *sieveScheme, *sieveQuery, *imapServer; + NSString *sieveServer, *sieveScheme, *sieveQuery, *imapServer; NSURL *url, *cUrl; - int sievePort; - BOOL b, connected; + BOOL connected; dd = [user domainDefaults]; - if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled])) - return YES; - - req = [NSMutableArray arrayWithCapacity: 15]; - ud = [user userDefaults]; - connected = YES; - b = NO; - + // Extract credentials from mail account + login = [[theAccount imap4URL] user]; + authname = [[theAccount imap4URL] user]; + password = [theAccount imap4PasswordRenewed: NO]; + // We connect to our Sieve server and check capabilities, in order // to generate the right script, based on capabilities // @@ -687,20 +698,20 @@ static NSString *sieveScriptName = @"sogo"; sieveScheme, sieveServer, sievePort, sieveQuery]]; client = [[NGSieveClient alloc] initWithURL: url]; - + if (!client) { NSLog(@"Sieve connection failed on %@", [url description]); - return NO; + return nil; } - - if (!thePassword) { + + if (!password) { [client closeConnection]; - return NO; + return nil; } NS_DURING { - result = [client login: theLogin authname: theAuthName password: thePassword]; + result = [client login: login authname: authname password: password]; } NS_HANDLER { @@ -711,22 +722,50 @@ static NSString *sieveScriptName = @"sogo"; if (!connected) { NSLog(@"Sieve connection failed on %@", [url description]); - return NO; + return nil; } if (![[result valueForKey:@"result"] boolValue]) { NSLog(@"failure. Attempting with a renewed password (no authname supported)"); - thePassword = [theAccount imap4PasswordRenewed: YES]; - result = [client login: theLogin password: thePassword]; + password = [theAccount imap4PasswordRenewed: YES]; + result = [client login: login password: password]; } - + if (![[result valueForKey:@"result"] boolValue]) { NSLog(@"Could not login '%@' on Sieve server: %@: %@", - theLogin, client, result); + login, client, result); [client closeConnection]; - return NO; + return nil; } - + + return client; +} + +// +// +// +- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount +{ + NSMutableArray *req; + NSMutableString *script, *header; + NSDictionary *result, *values; + SOGoUserDefaults *ud; + SOGoDomainDefaults *dd; + NGSieveClient *client; + NSString *filterScript, *v; + BOOL b; + + dd = [user domainDefaults]; + if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled])) + return YES; + + req = [NSMutableArray arrayWithCapacity: 15]; + ud = [user userDefaults]; + + client = [self clientForAccount: theAccount]; + if (!client) + return NO; + // We adjust the "methodRequirements" based on the server's // capabilities. Cyrus exposes "imapflags" while Dovecot (and // potentially others) expose "imap4flags" as specified in RFC5332 diff --git a/SoObjects/SOGo/SOGoSystemDefaults.h b/SoObjects/SOGo/SOGoSystemDefaults.h index cb705f560..3678516ff 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -1,9 +1,6 @@ /* SOGoSystemDefaults.h - this file is part of SOGo * - * Copyright (C) 2009-2013 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2009-2014 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 @@ -95,6 +92,10 @@ - (int) maximumSubmissionInterval; - (int) messageSubmissionBlockInterval; +- (int) maximumPingInterval; +- (int) maximumSyncInterval; +- (int) internalSyncInterval; + @end #endif /* SOGOSYSTEMDEFAULTS_H */ diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index 0457fae4b..52a35f868 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -1,11 +1,8 @@ /* SOGoSystemDefaults.m - this file is part of SOGo * - * Copyright (C) 2009-2013 Inverse inc. + * Copyright (C) 2009-2014 Inverse inc. * Copyright (C) 2012 Jeroen Dekkers * - * Author: Wolfgang Sourdeau - * Francis Lachapelle - * * 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 * the Free Software Foundation; either version 2, or (at your option) @@ -582,4 +579,40 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return v; } +- (int) maximumPingInterval +{ + int v; + + v = [self integerForKey: @"SOGoMaximumPingInterval"]; + + if (!v) + v = 5; + + return v; +} + +- (int) maximumSyncInterval +{ + int v; + + v = [self integerForKey: @"SOGoMaximumSyncInterval"]; + + if (!v) + v = 30; + + return v; +} + +- (int) internalSyncInterval +{ + int v; + + v = [self integerForKey: @"SOGoInternalSyncInterval"]; + + if (!v) + v = 10; + + return v; +} + @end diff --git a/SoObjects/SOGo/SOGoUser.h b/SoObjects/SOGo/SOGoUser.h index e1737cd2f..fad66e4e8 100644 --- a/SoObjects/SOGo/SOGoUser.h +++ b/SoObjects/SOGo/SOGoUser.h @@ -45,6 +45,7 @@ @class SOGoAppointmentFolder; @class SOGoAppointmentFolders; +@class SOGoContactFolder; @class SOGoDateFormatter; @class SOGoDomainDefaults; @class SOGoUserDefaults; @@ -130,6 +131,7 @@ - (SOGoUserFolder *) homeFolderInContext: (id) context; - (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context; - (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context; +- (SOGoContactFolder *) personalContactsFolderInContext: (WOContext *) context; @end diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index c46331d78..8f2768571 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -841,13 +841,25 @@ acquire: NO]; } -- (SOGoAppointmentFolder *) - personalCalendarFolderInContext: (WOContext *) context +- (SOGoAppointmentFolder *) personalCalendarFolderInContext: (WOContext *) context { return [[self calendarsFolderInContext: context] lookupPersonalFolder: @"personal" ignoringRights: YES]; } +- (SOGoContactFolder *) personalContactsFolderInContext: (WOContext *) context +{ + SOGoContactFolders *folders; + + folders = [[self homeFolderInContext: context] lookupName: @"Contacts" + inContext: context + acquire: NO]; + + return [folders lookupPersonalFolder: @"personal" + ignoringRights: YES]; +} + + - (NSArray *) rolesForObject: (NSObject *) object inContext: (WOContext *) context { diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index 3fdaf0306..83d6c18bb 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -25,6 +25,7 @@ @class NSArray; @class NSDictionary; +@class NSMutableDictionary; @class NSString; @class NSTimeZone; @@ -183,14 +184,8 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (void) setCalendarTasksDefaultClassification: (NSString *) newValue; - (NSString *) calendarTasksDefaultClassification; -- (void) setReminderEnabled: (BOOL) newValue; -- (BOOL) reminderEnabled; - -- (void) setReminderTime: (NSString *) newValue; -- (NSString *) reminderTime; - -- (void) setRemindWithASound: (BOOL) newValue; -- (BOOL) remindWithASound; +- (void) setCalendarDefaultReminder: (NSString *) newValue; +- (NSString *) calendarDefaultReminder; /* contacts */ - (void) setContactsCategories: (NSArray *) newValues; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 006a9d346..b862c5523 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -25,6 +25,9 @@ #import #import +#import +#import +#import #import "NSString+Utilities.h" #import "SOGoDomainDefaults.h" @@ -51,6 +54,8 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; SOGoUserProfile *up; SOGoUserDefaults *ud; SOGoDefaultsSource *parent; + WOContext *context; + WEClientCapabilities *cc; static Class SOGoUserProfileKlass = Nil; if (!SOGoUserProfileKlass) @@ -59,29 +64,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; up = [SOGoUserProfileKlass userProfileWithType: SOGoUserProfileTypeDefaults forUID: userId]; [up fetchProfile]; - // if ([_defaults values]) - // { - // BOOL b; - // b = NO; - - // if (![[_defaults stringForKey: @"MessageCheck"] length]) - // { - // [_defaults setObject: defaultMessageCheck forKey: @"MessageCheck"]; - // b = YES; - // } - // if (![[_defaults stringForKey: @"TimeZone"] length]) - // { - // [_defaults setObject: [serverTimeZone name] forKey: @"TimeZone"]; - // b = YES; - // } - - // if (b) - // [_defaults synchronize]; - - - // See explanation in -language - // [self invalidateLanguage]; - // } parent = [SOGoDomainDefaults defaultsForDomain: domainId]; if (!parent) @@ -89,6 +71,15 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; ud = [self defaultsSourceWithSource: up andParentSource: parent]; + // CKEditor (the HTML editor) is no longer compatible with IE7; + // force the user to use the plain text editor with IE7 + context = [[WOApplication application] context]; + cc = [[context request] clientCapabilities]; + if ([cc isInternetExplorer] && [cc majorVersion] < 8) + { + [ud setObject: @"text" forKey: @"SOGoMailComposeMessageType"]; + } + return ud; } @@ -706,34 +697,14 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self stringForKey: @"SOGoCalendarTasksDefaultClassification"]; } -- (void) setReminderEnabled: (BOOL) newValue +- (void) setCalendarDefaultReminder: (NSString *) newValue { - [self setBool: newValue forKey: @"SOGoReminderEnabled"]; + [self setObject: newValue forKey: @"SOGoCalendarDefaultReminder"]; } -- (BOOL) reminderEnabled +- (NSString *) calendarDefaultReminder { - return [self boolForKey: @"SOGoReminderEnabled"]; -} - -- (void) setReminderTime: (NSString *) newValue -{ - [self setObject: newValue forKey: @"SOGoReminderTime"]; -} - -- (NSString *) reminderTime -{ - return [self stringForKey: @"SOGoReminderTime"]; -} - -- (void) setRemindWithASound: (BOOL) newValue -{ - [self setBool: newValue forKey: @"SOGoRemindWithASound"]; -} - -- (BOOL) remindWithASound -{ - return [self boolForKey: @"SOGoRemindWithASound"]; + return [self stringForKey: @"SOGoCalendarDefaultReminder"]; } // diff --git a/SoObjects/SOGo/SOGoUserFolder.h b/SoObjects/SOGo/SOGoUserFolder.h index 2cb02aa3e..4ee6bdd65 100644 --- a/SoObjects/SOGo/SOGoUserFolder.h +++ b/SoObjects/SOGo/SOGoUserFolder.h @@ -1,14 +1,15 @@ /* + Copyright (C) 2006-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG - 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. @@ -39,6 +40,7 @@ @class NSDictionary; @class NSString; @class WOContext; +@class SOGoAppointmentFolders; @class SOGoContactFolders; @interface SOGoUserFolder : SOGoFolder @@ -59,6 +61,9 @@ - (BOOL) collectionDavKey: (NSString *) key matches: (NSString *) value; +- (SOGoAppointmentFolders *) privateCalendars: (NSString *) key + inContext: (WOContext *) localContext; + - (SOGoContactFolders *) privateContacts: (NSString *) _key inContext: (WOContext *) _ctx; diff --git a/SoObjects/SOGo/SOGoUserSettings.h b/SoObjects/SOGo/SOGoUserSettings.h index 6a07fb962..673f148c8 100644 --- a/SoObjects/SOGo/SOGoUserSettings.h +++ b/SoObjects/SOGo/SOGoUserSettings.h @@ -1,8 +1,6 @@ /* SOGoUserSettings.h - this file is part of SOGo * - * Copyright (C) 2009 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-2013 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 @@ -26,6 +24,7 @@ #import "SOGoDefaultsSource.h" @class NSArray; +@class NSMutableDictionary; @class NSString; @interface SOGoUserSettings : SOGoDefaultsSource @@ -35,6 +34,13 @@ - (NSArray *) subscribedCalendars; - (NSArray *) subscribedAddressBooks; + +/* Microsoft Active Sync support */ +- (void) setMicrosoftActiveSyncMetadata: (NSDictionary *) theMetadata + forDevice: (NSString *) theDeviceID; + +- (NSMutableDictionary *) microsoftActiveSyncMetadataForDevice: (NSString *) theDevice; + @end #endif /* SOGOUSERSETTINGS_H */ diff --git a/SoObjects/SOGo/SOGoUserSettings.m b/SoObjects/SOGo/SOGoUserSettings.m index 9739e6f85..107b5d835 100644 --- a/SoObjects/SOGo/SOGoUserSettings.m +++ b/SoObjects/SOGo/SOGoUserSettings.m @@ -1,8 +1,6 @@ /* SOGoUserSettings.m - this file is part of SOGo * - * Copyright (C) 2009-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2009-2013 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 @@ -71,4 +69,28 @@ static Class SOGoUserProfileKlass = Nil; return [self _subscribedFoldersForModule: @"Contacts"]; } +/* Microsoft Active Sync support */ +- (void) setMicrosoftActiveSyncMetadata: (NSDictionary *) theMetadata + forDevice: (NSString *) theDeviceID +{ + if (theMetadata && theDeviceID) + { + NSMutableDictionary *d; + + d = [NSMutableDictionary dictionaryWithDictionary: [self dictionaryForKey: @"SOGoMicrosoftActiveSyncMetadata"]]; + [d setObject: theMetadata forKey: theDeviceID]; + + [self setObject: d forKey: @"SOGoMicrosoftActiveSyncMetadata"]; + } +} + +- (NSMutableDictionary *) microsoftActiveSyncMetadataForDevice: (NSString *) theDevice +{ + NSDictionary *d; + + d = [self dictionaryForKey: @"SOGoMicrosoftActiveSyncMetadata"]; + + return [NSMutableDictionary dictionaryWithDictionary: [d objectForKey: theDevice]]; +} + @end diff --git a/SoObjects/SOGo/SOGoWebAuthenticator.h b/SoObjects/SOGo/SOGoWebAuthenticator.h index a2d9eeb5b..e225105bf 100644 --- a/SoObjects/SOGo/SOGoWebAuthenticator.h +++ b/SoObjects/SOGo/SOGoWebAuthenticator.h @@ -1,10 +1,6 @@ /* SOGoWebAuthenticator.h - this file is part of SOGo * - * Copyright (C) 2007-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte - * Francis Lachapelle + * Copyright (C) 2007-2013 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/SoObjects/SOGo/SOGoWebAuthenticator.m b/SoObjects/SOGo/SOGoWebAuthenticator.m index 923ee7d15..6d6aacad1 100644 --- a/SoObjects/SOGo/SOGoWebAuthenticator.m +++ b/SoObjects/SOGo/SOGoWebAuthenticator.m @@ -1,9 +1,6 @@ /* SOGoWebAuthenticator.m - this file is part of SOGo * - * Copyright (C) 2007-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2007-2013 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/SoObjects/SOGo/WORequest+SOGo.m b/SoObjects/SOGo/WORequest+SOGo.m index 9c803a0f1..b622fb1d1 100644 --- a/SoObjects/SOGo/WORequest+SOGo.m +++ b/SoObjects/SOGo/WORequest+SOGo.m @@ -1,8 +1,6 @@ /* WORequest+SOGo.m - this file is part of SOGo * - * Copyright (C) 2007-2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2013 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 @@ -37,7 +35,7 @@ - (BOOL) handledByDefaultHandler { #warning this should be changed someday - return ![[self requestHandlerKey] isEqualToString:@"dav"]; + return !([[self requestHandlerKey] isEqualToString: @"dav"] || [[self requestHandlerKey] isEqualToString: @"Microsoft-Server-ActiveSync"]); } - (NSArray *) _propertiesOfElement: (id ) startElement diff --git a/UI/Common/Arabic.lproj/Localizable.strings b/UI/Common/Arabic.lproj/Localizable.strings index 2f3b62db7..56679e57d 100644 --- a/UI/Common/Arabic.lproj/Localizable.strings +++ b/UI/Common/Arabic.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "تاريخ الاستحقاق:"; "Location:" = "الموقع:"; +/* Mail labels */ +"Important" = "مهم"; +"Work" = "عمل"; +"Work" = "عمل"; +"Personal" = "شخصي"; +"To Do" = "تفعل"; +"Later" = "لاحقا"; + "a2_Sunday" = "ح"; "a2_Monday" = "ن"; "a2_Tuesday" = "ث"; diff --git a/UI/Common/BrazilianPortuguese.lproj/Localizable.strings b/UI/Common/BrazilianPortuguese.lproj/Localizable.strings index 47c6c9901..606594535 100644 --- a/UI/Common/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Common/BrazilianPortuguese.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Data:"; "Location:" = "Localização:"; +/* Mail labels */ +"Important" = "Importante"; +"Work" = "Trabalho"; +"Work" = "Trabalho"; +"Personal" = "Pessoal"; +"To Do" = "Tarefa"; +"Later" = "Adiar"; + "a2_Sunday" = "Do"; "a2_Monday" = "Se"; "a2_Tuesday" = "Te"; diff --git a/UI/Common/Catalan.lproj/Localizable.strings b/UI/Common/Catalan.lproj/Localizable.strings index 0f0169369..a29d35ddd 100644 --- a/UI/Common/Catalan.lproj/Localizable.strings +++ b/UI/Common/Catalan.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Data límit:"; "Location:" = "Lloc:"; +/* Mail labels */ +"Important" = "Important"; +"Work" = "Feina"; +"Work" = "Feina"; +"Personal" = "Personal"; +"To Do" = "Per fer"; +"Later" = "Més tard"; + "a2_Sunday" = "dg"; "a2_Monday" = "dl"; "a2_Tuesday" = "dm"; diff --git a/UI/Common/Czech.lproj/Localizable.strings b/UI/Common/Czech.lproj/Localizable.strings index c38243cde..011219d31 100644 --- a/UI/Common/Czech.lproj/Localizable.strings +++ b/UI/Common/Czech.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Do dne:"; "Location:" = "Místo:"; +/* Mail labels */ +"Important" = "Důležitý"; +"Work" = "Pracovní"; +"Work" = "Pracovní"; +"Personal" = "Osobní"; +"To Do" = "Třeba udělat"; +"Later" = "Později"; + "a2_Sunday" = "Ne"; "a2_Monday" = "Po"; "a2_Tuesday" = "Út"; diff --git a/UI/Common/Danish.lproj/Localizable.strings b/UI/Common/Danish.lproj/Localizable.strings index 1c4f4456a..11edd76d4 100644 --- a/UI/Common/Danish.lproj/Localizable.strings +++ b/UI/Common/Danish.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Forfaldsdato:"; "Location:" = "Sted:"; +/* Mail labels */ +"Important" = "Vigtigt"; +"Work" = "Arbejde"; +"Work" = "Arbejde"; +"Personal" = "Privat"; +"To Do" = "To Do"; +"Later" = "Senere"; + "a2_Sunday" = "Sø"; "a2_Monday" = "Ma"; "a2_Tuesday" = "Ti"; diff --git a/UI/Common/Dutch.lproj/Localizable.strings b/UI/Common/Dutch.lproj/Localizable.strings index a7f590061..7b69510c0 100644 --- a/UI/Common/Dutch.lproj/Localizable.strings +++ b/UI/Common/Dutch.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Verloopdatum:"; "Location:" = "Plaats:"; +/* Mail labels */ +"Important" = "Belangrijk"; +"Work" = "Werk"; +"Work" = "Werk"; +"Personal" = "Persoonlijk"; +"To Do" = "Te doen"; +"Later" = "Later"; + "a2_Sunday" = "Zo"; "a2_Monday" = "Ma"; "a2_Tuesday" = "Di"; diff --git a/UI/Common/English.lproj/Localizable.strings b/UI/Common/English.lproj/Localizable.strings index 995671940..365cc4363 100644 --- a/UI/Common/English.lproj/Localizable.strings +++ b/UI/Common/English.lproj/Localizable.strings @@ -69,6 +69,7 @@ "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; "Warning" = "Warning"; +"Can't contact server" = "An error occurred while contacting the server. Please try again later."; "You are not allowed to access this module or this system. Please contact your system administrator." = "You are not allowed to access this module or this system. Please contact your system administrator."; @@ -101,6 +102,13 @@ "Due Date:" = "Due Date:"; "Location:" = "Location:"; +/* mail labels */ +"Important" = "Important"; +"Work" = "Work"; +"Personal" = "Personal"; +"To Do" = "To Do"; +"Later" = "Later"; + "a2_Sunday" = "Su"; "a2_Monday" = "Mo"; "a2_Tuesday" = "Tu"; diff --git a/UI/Common/Finnish.lproj/Localizable.strings b/UI/Common/Finnish.lproj/Localizable.strings index 5eff8d825..dccd332d8 100644 --- a/UI/Common/Finnish.lproj/Localizable.strings +++ b/UI/Common/Finnish.lproj/Localizable.strings @@ -101,6 +101,15 @@ "Due Date:" = "Päättyy:"; "Location:" = "Sijainti:"; +/* mail labels */ +/* Mail labels */ +"Important" = "Tärkeä"; +"Work" = "Työ"; +"Work" = "Työ"; +"Personal" = "Henkilökohtainen"; +"To Do" = "Tehtävä"; +"Later" = "Myöhemmin"; + "a2_Sunday" = "Su"; "a2_Monday" = "Ma"; "a2_Tuesday" = "Ti"; diff --git a/UI/Common/French.lproj/Localizable.strings b/UI/Common/French.lproj/Localizable.strings index 8797794e1..65d42f941 100644 --- a/UI/Common/French.lproj/Localizable.strings +++ b/UI/Common/French.lproj/Localizable.strings @@ -69,6 +69,7 @@ "You cannot create a list in a shared address book." = "Impossible de créer une liste dans un dossier partagé."; "Warning" = "Avertissement"; +"Can't contact server" = "Une erreur est survenue lors de la connexion au serveur. Veuillez réessayer plus tard."; "You are not allowed to access this module or this system. Please contact your system administrator." = "Vous n'êtes pas autorisé à accéder à ce module ou ce système. Veuillez contacter votre administrateur système."; @@ -101,6 +102,13 @@ "Due Date:" = "Échéance :"; "Location:" = "Lieu :"; +/* mail labels */ +"Important" = "Important"; +"Work" = "Travail"; +"Personal" = "Personnel"; +"To Do" = "À faire"; +"Later" = "Peut attendre"; + "a2_Sunday" = "Di"; "a2_Monday" = "Lu"; "a2_Tuesday" = "Ma"; diff --git a/UI/Common/German.lproj/Localizable.strings b/UI/Common/German.lproj/Localizable.strings index 223d166af..68195a15e 100644 --- a/UI/Common/German.lproj/Localizable.strings +++ b/UI/Common/German.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Fällig:"; "Location:" = "Ort:"; +/* Mail labels */ +"Important" = "Wichtig"; +"Work" = "Geschäftlich"; +"Work" = "Geschäftlich"; +"Personal" = "Persönlich"; +"To Do" = "To-Do"; +"Later" = "Später"; + "a2_Sunday" = "So"; "a2_Monday" = "Mo"; "a2_Tuesday" = "Di"; diff --git a/UI/Common/Hungarian.lproj/Localizable.strings b/UI/Common/Hungarian.lproj/Localizable.strings index 7a9a91cfa..2d2940263 100644 --- a/UI/Common/Hungarian.lproj/Localizable.strings +++ b/UI/Common/Hungarian.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Lejárat dátuma:"; "Location:" = "Hely:"; +/* Mail labels */ +"Important" = "Fontos"; +"Work" = "Hivatalos"; +"Work" = "Hivatalos"; +"Personal" = "Személyes"; +"To Do" = "Teendő"; +"Later" = "Később"; + "a2_Sunday" = "Va"; "a2_Monday" = "Hé"; "a2_Tuesday" = "Ke"; diff --git a/UI/Common/Icelandic.lproj/Localizable.strings b/UI/Common/Icelandic.lproj/Localizable.strings index 5ff813858..3825f20b6 100644 --- a/UI/Common/Icelandic.lproj/Localizable.strings +++ b/UI/Common/Icelandic.lproj/Localizable.strings @@ -78,6 +78,14 @@ "Due Date:" = "Lokadagur:"; "Location:" = "Staðsetning:"; +/* Mail labels */ +"Important" = "Mikilvægt"; +"Work" = "Vinna"; +"Work" = "Vinna"; +"Personal" = "Persónulegt"; +"To Do" = "Verkþáttur"; +"Later" = "Seinna"; + "a2_Sunday" = "Su"; "a2_Monday" = "Má"; "a2_Tuesday" = "Þr"; diff --git a/UI/Common/Italian.lproj/Localizable.strings b/UI/Common/Italian.lproj/Localizable.strings index 65844b366..56ee93251 100644 --- a/UI/Common/Italian.lproj/Localizable.strings +++ b/UI/Common/Italian.lproj/Localizable.strings @@ -100,6 +100,14 @@ "Due Date:" = "Scadenza:"; "Location:" = "Luogo:"; +/* Mail labels */ +"Important" = "Importante"; +"Work" = "Lavoro"; +"Work" = "Lavoro"; +"Personal" = "Personale"; +"To Do" = "Da fare"; +"Later" = "Posponi"; + "a2_Sunday" = "Do"; "a2_Monday" = "Lu"; "a2_Tuesday" = "Ma"; diff --git a/UI/Common/NorwegianBokmal.lproj/Localizable.strings b/UI/Common/NorwegianBokmal.lproj/Localizable.strings index ffbb7e833..d73850120 100644 --- a/UI/Common/NorwegianBokmal.lproj/Localizable.strings +++ b/UI/Common/NorwegianBokmal.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Forfallsdato:"; "Location:" = "Lokasjon:"; +/* Mail labels */ +"Important" = "Viktig"; +"Work" = "Arbeid"; +"Work" = "Arbeid"; +"Personal" = "Personlig"; +"To Do" = "Gjøremål"; +"Later" = "Senere"; + "a2_Sunday" = "Sø"; "a2_Monday" = "Ma"; "a2_Tuesday" = "Ti"; diff --git a/UI/Common/NorwegianNynorsk.lproj/Localizable.strings b/UI/Common/NorwegianNynorsk.lproj/Localizable.strings index 57d63bc74..314a2c6fc 100644 --- a/UI/Common/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/Common/NorwegianNynorsk.lproj/Localizable.strings @@ -78,6 +78,14 @@ "Due Date:" = "Forfallsdato:"; "Location:" = "Lokasjon:"; +/* Mail labels */ +"Important" = "Viktig"; +"Work" = "Arbeid"; +"Work" = "Arbeid"; +"Personal" = "Personlig"; +"To Do" = "Gjøremål"; +"Later" = "Senere"; + "a2_Sunday" = "Sø"; "a2_Monday" = "Ma"; "a2_Tuesday" = "Ti"; diff --git a/UI/Common/Polish.lproj/Localizable.strings b/UI/Common/Polish.lproj/Localizable.strings index 1ff72f385..a041733e5 100644 --- a/UI/Common/Polish.lproj/Localizable.strings +++ b/UI/Common/Polish.lproj/Localizable.strings @@ -69,6 +69,7 @@ "You cannot create a list in a shared address book." = "Nie możesz tworzyć list w udostępnionej książce adresowej."; "Warning" = "Uwaga"; +"Can't contact server" = "Wystąpił błąd w trakcie komunikacji z serwerem. Spróbuj później."; "You are not allowed to access this module or this system. Please contact your system administrator." = "Nie masz pozwolenia na dostęp do tego modułu lub tego systemu. Skontaktuj się ze swoim administratorem."; @@ -101,6 +102,15 @@ "Due Date:" = "Termin:"; "Location:" = "Miejsce:"; +/* mail labels */ +/* Mail labels */ +"Important" = "Ważne"; +"Work" = "Praca"; +"Work" = "Praca"; +"Personal" = "Osobiste"; +"To Do" = "Do zrobienia"; +"Later" = "Później"; + "a2_Sunday" = "Ni"; "a2_Monday" = "Pn"; "a2_Tuesday" = "Wt"; diff --git a/UI/Common/Russian.lproj/Localizable.strings b/UI/Common/Russian.lproj/Localizable.strings index aca037cee..6442926e6 100644 --- a/UI/Common/Russian.lproj/Localizable.strings +++ b/UI/Common/Russian.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Дата начала:"; "Location:" = "Место:"; +/* Mail labels */ +"Important" = "Важно"; +"Work" = "Работа"; +"Work" = "Работа"; +"Personal" = "Личное"; +"To Do" = "К исполнению"; +"Later" = "Позже"; + "a2_Sunday" = "Вс"; "a2_Monday" = "Пн"; "a2_Tuesday" = "Вт"; diff --git a/UI/Common/Slovak.lproj/Localizable.strings b/UI/Common/Slovak.lproj/Localizable.strings index 526b44371..a03d6f0d8 100644 --- a/UI/Common/Slovak.lproj/Localizable.strings +++ b/UI/Common/Slovak.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Splatnosť:"; "Location:" = "Umiestnenie:"; +/* Mail labels */ +"Important" = "Dôležité"; +"Work" = "Pracovné"; +"Work" = "Pracovné"; +"Personal" = "Osobné"; +"To Do" = "Treba urobiť"; +"Later" = "Neskôr"; + "a2_Sunday" = "Ne"; "a2_Monday" = "Po"; "a2_Tuesday" = "Ut"; diff --git a/UI/Common/SpanishArgentina.lproj/Localizable.strings b/UI/Common/SpanishArgentina.lproj/Localizable.strings index 5976df052..2f655bd7e 100644 --- a/UI/Common/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Common/SpanishArgentina.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Vencimiento:"; "Location:" = "Lugar:"; +/* Mail labels */ +"Important" = "Importante"; +"Work" = "Trabajo"; +"Work" = "Trabajo"; +"Personal" = "Personal"; +"To Do" = "Por hacer"; +"Later" = "Más tarde"; + "a2_Sunday" = "Do"; "a2_Monday" = "Lu"; "a2_Tuesday" = "Ma"; diff --git a/UI/Common/SpanishSpain.lproj/Localizable.strings b/UI/Common/SpanishSpain.lproj/Localizable.strings index f5a2b883a..1be575659 100644 --- a/UI/Common/SpanishSpain.lproj/Localizable.strings +++ b/UI/Common/SpanishSpain.lproj/Localizable.strings @@ -101,6 +101,14 @@ "Due Date:" = "Vencimiento:"; "Location:" = "Lugar:"; +/* Mail labels */ +"Important" = "Importante"; +"Work" = "Trabajo"; +"Work" = "Trabajo"; +"Personal" = "Personal"; +"To Do" = "Por hacer"; +"Later" = "Más tarde"; + "a2_Sunday" = "Do"; "a2_Monday" = "Lu"; "a2_Tuesday" = "Ma"; diff --git a/UI/Common/Swedish.lproj/Localizable.strings b/UI/Common/Swedish.lproj/Localizable.strings index 8afdd9fc2..c0ad4df92 100644 --- a/UI/Common/Swedish.lproj/Localizable.strings +++ b/UI/Common/Swedish.lproj/Localizable.strings @@ -78,6 +78,14 @@ "Due Date:" = "Förfallodag:"; "Location:" = "Plats:"; +/* Mail labels */ +"Important" = "Viktigt"; +"Work" = "Arbete"; +"Work" = "Arbete"; +"Personal" = "Personligt"; +"To Do" = "Att göra"; +"Later" = "Senare"; + "a2_Sunday" = "Sö"; "a2_Monday" = "Må"; "a2_Tuesday" = "Ti"; diff --git a/UI/Common/UIxFolderActions.m b/UI/Common/UIxFolderActions.m index a527699ec..fb0a95eeb 100644 --- a/UI/Common/UIxFolderActions.m +++ b/UI/Common/UIxFolderActions.m @@ -215,7 +215,7 @@ NSArray *ids; idsParam = [[context request] formValueForKey: @"ids"]; - ids = [idsParam componentsSeparatedByString: @"/"]; + ids = [idsParam componentsSeparatedByString: @","]; if ([ids count]) { clientObject = [self clientObject]; diff --git a/UI/Common/UIxPageFrame.m b/UI/Common/UIxPageFrame.m index 215088a55..3d33384c0 100644 --- a/UI/Common/UIxPageFrame.m +++ b/UI/Common/UIxPageFrame.m @@ -113,8 +113,7 @@ - (NSString *) doctype { - return (@"\n" - @""); } diff --git a/UI/Common/Ukrainian.lproj/Localizable.strings b/UI/Common/Ukrainian.lproj/Localizable.strings index 3b3d596a1..b86c35d79 100644 --- a/UI/Common/Ukrainian.lproj/Localizable.strings +++ b/UI/Common/Ukrainian.lproj/Localizable.strings @@ -100,6 +100,14 @@ "Due Date:" = "Дата початку:"; "Location:" = "Місце:"; +/* Mail labels */ +"Important" = "Важливе"; +"Work" = "Робоче"; +"Work" = "Робоче"; +"Personal" = "Особисте"; +"To Do" = "До виконання"; +"Later" = "Відсунуте"; + "a2_Sunday" = "Нд"; "a2_Monday" = "Пн"; "a2_Tuesday" = "Вт"; diff --git a/UI/Common/Welsh.lproj/Localizable.strings b/UI/Common/Welsh.lproj/Localizable.strings index 0933a7918..b493374da 100644 --- a/UI/Common/Welsh.lproj/Localizable.strings +++ b/UI/Common/Welsh.lproj/Localizable.strings @@ -78,6 +78,14 @@ "Due Date:" = "Dyddiad dyledus:"; "Location:" = "Lleoliad:"; +/* Mail labels */ +"Important" = "Pwysig"; +"Work" = "gwaith"; +"Work" = "gwaith"; +"Personal" = "Personol"; +"To Do" = "I'w wneud"; +"Later" = "Hwyrach"; + "a2_Sunday" = "Su"; "a2_Monday" = "Ll"; "a2_Tuesday" = "Ma"; diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 392aabdaf..4880fa667 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -1,6 +1,6 @@ /* Copyright (C) 2004 SKYRIX Software AG - Copyright (C) 2005-2012 Inverse inc. + Copyright (C) 2005-2014 Inverse inc. This file is part of SOGo. @@ -29,10 +29,13 @@ #import #import #import +#import #import #import #import + +#import #import #import "UIxContactView.h" @@ -64,16 +67,17 @@ - (NSString *) _cardStringWithLabel: (NSString *) label value: (NSString *) value - url: (NSString *) url + asLinkScheme: (NSString *) scheme + withLinkAttributes: (NSString *) attrs { NSMutableString *cardString; cardString = [NSMutableString stringWithCapacity: 80]; - value = [value stringByReplacingString: @"\r" withString: @""]; + value = [[value stringByReplacingString: @"\r" withString: @""] stringByEscapingHTMLString]; if ([value length] > 0) { - if ([url length] > 0) - value = [NSString stringWithFormat: @"%@", url, value, value]; + if ([scheme length] > 0) + value = [NSString stringWithFormat: @"%@", scheme, value, attrs, value]; if (label) [cardString appendFormat: @"
%@
%@
\n", @@ -90,7 +94,18 @@ { return [self _cardStringWithLabel: label value: value - url: nil]; + asLinkScheme: nil + withLinkAttributes: nil]; +} + +- (NSString *) _cardStringWithLabel: (NSString *) label + value: (NSString *) value + asLinkScheme: (NSString *) scheme +{ + return [self _cardStringWithLabel: label + value: value + asLinkScheme: scheme + withLinkAttributes: nil]; } - (NSString *) displayName @@ -107,38 +122,12 @@ - (NSString *) fullName { - CardElement *n; - NSString *fn, *firstName, *lastName, *org; - - fn = [card fn]; - if ([fn length] == 0) - { - n = [card n]; - lastName = [n flattenedValueAtIndex: 0 forKey: @""]; - firstName = [n flattenedValueAtIndex: 1 forKey: @""]; - if ([firstName length] > 0) - { - if ([lastName length] > 0) - fn = [NSString stringWithFormat: @"%@ %@", firstName, lastName]; - else - fn = firstName; - } - else if ([lastName length] > 0) - fn = lastName; - else - { - n = [card org]; - org = [n flattenedValueAtIndex: 0 forKey: @""]; - fn = org; - } - } - - return fn; + return [card fullName]; } - (NSString *) primaryEmail { - NSString *email, *fn, *mailTo; + NSString *email, *fn, *attrs; email = [card preferredEMail]; if ([email length] > 0) @@ -146,31 +135,28 @@ fn = [card fn]; fn = [fn stringByReplacingString: @"\"" withString: @""]; fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; - mailTo = [NSString stringWithFormat: @"');\">" - @"%@", email, fn, email, email]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; } else - mailTo = nil; + { + attrs = nil; + } return [self _cardStringWithLabel: @"Email:" - value: mailTo]; + value: email + asLinkScheme: @"mailto:" + withLinkAttributes: attrs]; } - (NSArray *) secondaryEmails { - NSString *email, *fn, *mailTo; - NSMutableArray *emails; NSMutableArray *secondaryEmails; + NSString *email, *fn, *attrs; + NSArray *emails; - emails = [NSMutableArray array]; + emails = [card secondaryEmails]; secondaryEmails = [NSMutableArray array]; - mailTo = nil; - - [emails addObjectsFromArray: [card childrenWithTag: @"email"]]; - [emails removeObjectsInArray: [card childrenWithTag: @"email" - andAttribute: @"type" - havingValue: @"pref"]]; + attrs = nil; // We might not have a preferred item but rather something like this: // EMAIL;TYPE=work:dd@ee.com @@ -189,25 +175,21 @@ for (i = 0; i < [emails count]; i++) { email = [[emails objectAtIndex: i] flattenedValuesForKey: @""]; - - // skip primary email - if ([email caseInsensitiveCompare: [card preferredEMail]] != NSOrderedSame) - { - fn = [card fn]; - fn = [fn stringByReplacingString: @"\"" withString: @""]; - fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; - mailTo = [NSString stringWithFormat: @"');\">" - @"%@", email, fn, email, email]; - [secondaryEmails addObject: [self _cardStringWithLabel: nil - value: mailTo]]; - } - } + fn = [card fn]; + fn = [fn stringByReplacingString: @"\"" withString: @""]; + fn = [fn stringByReplacingString: @"'" withString: @"\\\'"]; + attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@ <%@>');\"", fn, email]; + + [secondaryEmails addObject: [self _cardStringWithLabel: nil + value: email + asLinkScheme: @"mailto:" + withLinkAttributes: attrs]]; + } } else { [secondaryEmails addObject: [self _cardStringWithLabel: nil - value: mailTo]]; + value: nil]]; } @@ -216,22 +198,19 @@ - (NSString *) screenName { - NSString *screenName, *goim; + NSString *screenName; screenName = [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""]; - if ([screenName length] > 0) - goim = [NSString stringWithFormat: @"%@", screenName, screenName]; - else - goim = nil; - return [self _cardStringWithLabel: @"Screen Name:" value: goim]; + return [self _cardStringWithLabel: @"Screen Name:" + value: screenName + asLinkScheme: @"aim:goim?screenname="]; } - (NSString *) preferredTel { return [self _cardStringWithLabel: @"Phone Number:" - value: [card preferredTel] url: @"tel"]; + value: [card preferredTel] asLinkScheme: @"tel:"]; } - (NSString *) preferredAddress @@ -256,66 +235,31 @@ return ([phones count] > 0); } -- (NSString *) _phoneOfType: (NSString *) aType - withLabel: (NSString *) aLabel - excluding: (NSString *) aTypeToExclude -{ - NSArray *elements; - NSString *phone; - - elements = [phones cardElementsWithAttribute: @"type" - havingValue: aType]; - - phone = nil; - - if ([elements count] > 0) - { - CardElement *ce; - int i; - - for (i = 0; i < [elements count]; i++) - { - ce = [elements objectAtIndex: i]; - phone = [ce flattenedValuesForKey: @""]; - - if (!aTypeToExclude) - break; - - if (![ce hasAttribute: @"type" havingValue: aTypeToExclude]) - break; - - phone = nil; - } - } - - return [self _cardStringWithLabel: aLabel value: phone url: @"tel"]; -} - - (NSString *) workPhone { // We do this (exclude FAX) in order to avoid setting the WORK number as the FAX // one if we do see the FAX field BEFORE the WORK number. - return [self _phoneOfType: @"work" withLabel: @"Work:" excluding: @"fax"]; + return [self _cardStringWithLabel: @"Work:" value: [card workPhone] asLinkScheme: @"tel:"]; } - (NSString *) homePhone { - return [self _phoneOfType: @"home" withLabel: @"Home:" excluding: @"fax"]; + return [self _cardStringWithLabel: @"Home:" value: [card homePhone] asLinkScheme: @"tel:"]; } - (NSString *) fax { - return [self _phoneOfType: @"fax" withLabel: @"Fax:" excluding: nil]; + return [self _cardStringWithLabel: @"Fax:" value: [card fax] asLinkScheme: @"tel:"]; } - (NSString *) mobile { - return [self _phoneOfType: @"cell" withLabel: @"Mobile:" excluding: nil]; + return [self _cardStringWithLabel: @"Mobile:" value: [card mobile] asLinkScheme: @"tel:"]; } - (NSString *) pager { - return [self _phoneOfType: @"pager" withLabel: @"Pager:" excluding: nil]; + return [self _cardStringWithLabel: @"Pager:" value: [card pager] asLinkScheme: @"tel:"]; } - (BOOL) hasHomeInfos @@ -396,21 +340,33 @@ - (NSString *) _formattedURL: (NSString *) url { - NSString *data; + NSRange schemaR; + NSString *schema, *data; if ([url length] > 0) { - if (![[url lowercaseString] rangeOfString: @"://"].length) - url = [NSString stringWithFormat: @"http://%@", url]; - - data = [NSString stringWithFormat: - @"%@", - url, url]; + schemaR = [url rangeOfString: @"://"]; + if (schemaR.length > 0) + { + schema = [url substringToIndex: schemaR.location + schemaR.length]; + data = [url substringFromIndex: schemaR.location + schemaR.length]; + } + else + { + schema = @"http://"; + data = url; + } } else - data = nil; + { + schema = nil; + data = nil; + } - return [self _cardStringWithLabel: nil value: data]; + return [self _cardStringWithLabel: nil + value: data + asLinkScheme: schema + withLinkAttributes: @"target=\"_blank\""]; } @@ -420,8 +376,8 @@ NSString *url; elements = [card childrenWithTag: @"url" - andAttribute: @"type" - havingValue: aType]; + andAttribute: @"type" + havingValue: aType]; if ([elements count] > 0) url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""]; else @@ -535,15 +491,7 @@ - (NSString *) workCompany { - CardElement *org; - NSString *company; - - org = [card org]; - company = [org flattenedValueAtIndex: 0 forKey: @""]; - if ([company length] == 0) - company = nil; - - return [self _cardStringWithLabel: nil value: company]; + return [self _cardStringWithLabel: nil value: [card workCompany]]; } - (NSString *) workPobox @@ -615,18 +563,15 @@ - (NSString *) bday { - NSString *bday, *value; - NSCalendarDate *date; SOGoDateFormatter *dateFormatter; + NSCalendarDate *date; + NSString *bday; + + date = [card birthday]; + bday = nil; - bday = [card bday]; - if (bday) + if (date) { - // Expected format of BDAY is YYYY[-]MM[-]DD - value = [bday stringByReplacingString: @"-" withString: @""]; - date = [NSCalendarDate dateFromShortDateString: value - andShortTimeString: nil - inTimeZone: nil]; dateFormatter = [[[self context] activeUser] dateFormatterInContext: context]; bday = [dateFormatter formattedDate: date]; } diff --git a/UI/Contacts/UIxContactsListActions.m b/UI/Contacts/UIxContactsListActions.m index 1b9ab62f4..c3438d46d 100644 --- a/UI/Contacts/UIxContactsListActions.m +++ b/UI/Contacts/UIxContactsListActions.m @@ -127,11 +127,32 @@ - (id ) contactsListAction { id result; + id currentInfo; NSArray *contactsList; + NSEnumerator *contactsListEnumerator, *keysEnumerator; + NSMutableArray *newContactsList; + NSMutableDictionary *currentContactDictionary; + NSString *key; contactsList = [self contactInfos]; + contactsListEnumerator = [contactsList objectEnumerator]; + newContactsList = [NSMutableArray arrayWithCapacity: [contactsList count]]; + + // Escape HTML + while ((currentContactDictionary = [contactsListEnumerator nextObject])) + { + keysEnumerator = [currentContactDictionary keyEnumerator]; + while ((key = [keysEnumerator nextObject])) + { + currentInfo = [currentContactDictionary objectForKey: key]; + if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)]) + [currentContactDictionary setObject: [currentInfo stringByEscapingHTMLString] forKey: key]; + } + [newContactsList addObject: currentContactDictionary]; + } + result = [self responseWithStatus: 200 - andString: [contactsList jsonRepresentation]]; + andString: [newContactsList jsonRepresentation]]; return result; } diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.h b/UI/MailPartViewers/UIxMailPartHTMLViewer.h index bbfb0e049..5ba673c31 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.h +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.h @@ -1,8 +1,6 @@ /* UIxMailPartHTMLViewer.h - this file is part of SOGo * - * Copyright (C) 2007, 2008 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2013 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/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 99d43abdd..e9e45fbbc 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -1,10 +1,6 @@ /* UIxMailPartHTMLViewer.m - this file is part of SOGo * - * Copyright (C) 2007-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte - * Francis Lachapelle + * Copyright (C) 2007-2013 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 @@ -546,7 +542,8 @@ static NSData* _sanitizeContent(NSData *theData) else skipAttribute = YES; } - else if (([name isEqualToString: @"data"] + else if ([name isEqualToString: @"background"] || + ([name isEqualToString: @"data"] || [name isEqualToString: @"classid"]) && [lowerName isEqualToString: @"object"]) { @@ -843,7 +840,7 @@ static NSData* _sanitizeContent(NSData *theData) createXMLReaderForMimeType: @"text/html"]; handler = [_UIxHTMLMailContentHandler new]; - [handler setAttachmentIds: [mail fetchAttachmentIds]]; + [handler setAttachmentIds: [mail fetchFileAttachmentIds]]; // We check if we got an unsupported charset. If so // we convert everything to UTF-16{LE,BE} so it passes @@ -951,7 +948,7 @@ static NSData* _sanitizeContent(NSData *theData) encoding = @"us-ascii"; handler = [_UIxHTMLMailContentHandler new]; - [handler setAttachmentIds: [mail fetchAttachmentIds]]; + [handler setAttachmentIds: [mail fetchFileAttachmentIds]]; // We check if we got an unsupported charset. If so // we convert everything to UTF-16{LE,BE} so it passes diff --git a/UI/MailPartViewers/UIxMailPartViewer.m b/UI/MailPartViewers/UIxMailPartViewer.m index 21aeec623..c973acbb6 100644 --- a/UI/MailPartViewers/UIxMailPartViewer.m +++ b/UI/MailPartViewers/UIxMailPartViewer.m @@ -267,7 +267,11 @@ NSString *extension; filename = [NSMutableString stringWithString: [self filename]]; - if (![filename length]) + if ([filename length]) + // We replace any slash by a dash since Apache won't allow encoded slashes by default. + // See http://httpd.apache.org/docs/2.2/mod/core.html#allowencodedslashes + filename = [filename stringByReplacingString: @"/" withString: @"-"]; + else [filename appendFormat: @"%@-%@", [self labelForKey: @"Untitled"], [bodyPart nameInContainer]]; diff --git a/UI/MailPartViewers/UIxMailRenderingContext.m b/UI/MailPartViewers/UIxMailRenderingContext.m index 03df297cc..ca76bc739 100644 --- a/UI/MailPartViewers/UIxMailRenderingContext.m +++ b/UI/MailPartViewers/UIxMailRenderingContext.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2009 Inverse inc. + Copyright (C) 2007-2013 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. diff --git a/UI/MailPartViewers/UIxMailSizeFormatter.m b/UI/MailPartViewers/UIxMailSizeFormatter.m index c4fdbe09e..2e5b133de 100644 --- a/UI/MailPartViewers/UIxMailSizeFormatter.m +++ b/UI/MailPartViewers/UIxMailSizeFormatter.m @@ -25,7 +25,8 @@ @implementation UIxMailSizeFormatter -+ (id)sharedMailSizeFormatter { ++ (id) sharedMailSizeFormatter +{ static UIxMailSizeFormatter *fmt = nil; // THREAD if (fmt == nil) fmt = [[self alloc] init]; return fmt; @@ -33,20 +34,24 @@ /* formatting */ -- (NSString *)stringForSize:(unsigned int)size { +- (NSString *) stringForSize: (unsigned int) size +{ char buf[128]; - - if (size < 1024) - sprintf(buf, "%d", size); - else if (size < 1024 * 1024) - sprintf(buf, "%.1fK", ((double)size / 1024)); + + if (size > 1024*1024) + sprintf(buf, "%.1f MiB", ((double)size / 1024 / 1024)); + else if (size > 1024*100) + sprintf(buf, "%d KiB", (size / 1024)); + else if (size > 1024) + sprintf(buf, "%.1f KiB", ((double)size / 1024)); else - sprintf(buf, "%.1fM", ((double)size / 1024 / 1024)); + sprintf(buf, "%d B", size); return [NSString stringWithCString:buf]; } -- (NSString *)stringForObjectValue:(id)_object { +- (NSString *) stringForObjectValue: (id) _object +{ return [self stringForSize:[_object unsignedIntValue]]; } diff --git a/UI/MailerUI/Arabic.lproj/Localizable.strings b/UI/MailerUI/Arabic.lproj/Localizable.strings index ed074efca..1de5b21db 100644 --- a/UI/MailerUI/Arabic.lproj/Localizable.strings +++ b/UI/MailerUI/Arabic.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "لا شيء"; -"Important" = "مهم"; -"Work" = "عمل"; -"Personal" = "شخصي"; -"To Do" = "تفعل"; -"Later" = "لاحقا"; /* Mark popup menu */ "As Read" = "مقروء"; diff --git a/UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings b/UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings index 4cccf3143..9e03c615d 100644 --- a/UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/MailerUI/BrazilianPortuguese.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Nenhum"; -"Important" = "Importante"; -"Work" = "Trabalho"; -"Personal" = "Pessoal"; -"To Do" = "Tarefa"; -"Later" = "Adiar"; /* Mark popup menu */ "As Read" = "Como Lido"; diff --git a/UI/MailerUI/Catalan.lproj/Localizable.strings b/UI/MailerUI/Catalan.lproj/Localizable.strings index 401299ece..b79834487 100644 --- a/UI/MailerUI/Catalan.lproj/Localizable.strings +++ b/UI/MailerUI/Catalan.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Cap"; -"Important" = "Important"; -"Work" = "Feina"; -"Personal" = "Personal"; -"To Do" = "Per fer"; -"Later" = "Més tard"; /* Mark popup menu */ "As Read" = "Com a llegits"; diff --git a/UI/MailerUI/Czech.lproj/Localizable.strings b/UI/MailerUI/Czech.lproj/Localizable.strings index 212baea30..60d42d5d0 100644 --- a/UI/MailerUI/Czech.lproj/Localizable.strings +++ b/UI/MailerUI/Czech.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Odpovědět komu"; "Add address" = "Přidat adresu"; -"Attachments:" = "Přílohy:"; "Open" = "Otevřít"; "Select All" = "Vybrat vše"; "Attach Web Page..." = "Připojit WWW stránku..."; -"Attach File(s)..." = "Připojit soubor(y)..."; +"file" = "soubor"; +"files" = "soubory"; +"Save all" = "Uložit všechno"; "to" = "Komu"; "cc" = "Kopie"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Žádný"; -"Important" = "Důležitý"; -"Work" = "Pracovní"; -"Personal" = "Osobní"; -"To Do" = "Třeba udělat"; -"Later" = "Později"; /* Mark popup menu */ "As Read" = "Jako přečtené"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Chybí předmět"; "error_missingrecipients" = "Příjemci nebyli specifikováni"; "Send Anyway" = "Odeslat"; +"Error while saving the draft:" = "Při uložení konceptu došlo k chybě:"; +"Error while uploading the file \"%{0}\":" = "Při přenosu souboru \"%{0}\" došlo k chybě:"; +"There is an active file upload. Closing the window will interrupt it." = "Přenáší se soubor. Zavření okna způsobí přerušení přenosu."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Zprávu nelze odeslat: adresy všech příjemců jsou neplatné."; diff --git a/UI/MailerUI/Danish.lproj/Localizable.strings b/UI/MailerUI/Danish.lproj/Localizable.strings index 8e5592395..a73b7a3be 100644 --- a/UI/MailerUI/Danish.lproj/Localizable.strings +++ b/UI/MailerUI/Danish.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Ingen"; -"Important" = "Vigtigt"; -"Work" = "Arbejde"; -"Personal" = "Privat"; -"To Do" = "To Do"; -"Later" = "Senere"; /* Mark popup menu */ "As Read" = "Som læst"; diff --git a/UI/MailerUI/Dutch.lproj/Localizable.strings b/UI/MailerUI/Dutch.lproj/Localizable.strings index 87f98b0b4..850923362 100644 --- a/UI/MailerUI/Dutch.lproj/Localizable.strings +++ b/UI/MailerUI/Dutch.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Geen label"; -"Important" = "Belangrijk"; -"Work" = "Werk"; -"Personal" = "Persoonlijk"; -"To Do" = "Te doen"; -"Later" = "Later"; /* Mark popup menu */ "As Read" = "Als gelezen"; diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index 92d7f6f5f..02d512726 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Reply-To"; "Add address" = "Add address"; -"Attachments:" = "Attachments:"; "Open" = "Open"; "Select All" = "Select All"; "Attach Web Page..." = "Attach Web Page..."; -"Attach File(s)..." = "Attach File(s)..."; +"file" = "file"; +"files" = "files"; +"Save all" = "Save all"; "to" = "To"; "cc" = "Cc"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "None"; -"Important" = "Important"; -"Work" = "Work"; -"Personal" = "Personal"; -"To Do" = "To Do"; -"Later" = "Later"; /* Mark popup menu */ "As Read" = "As Read"; @@ -285,6 +281,9 @@ "error_missingsubject" = "The message has no subject. Are you sure you want to send it?"; "error_missingrecipients" = "Please specify at least one recipient."; "Send Anyway" = "Send Anyway"; +"Error while saving the draft:" = "Error while saving the draft:"; +"Error while uploading the file \"%{0}\":" = "Error while uploading the file \"%{0}\":"; +"There is an active file upload. Closing the window will interrupt it." = "There is an active file upload. Closing the window will interrupt it."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Cannot send message: all recipients are invalid."; diff --git a/UI/MailerUI/Finnish.lproj/Localizable.strings b/UI/MailerUI/Finnish.lproj/Localizable.strings index e5de5d9d9..734bb155b 100644 --- a/UI/MailerUI/Finnish.lproj/Localizable.strings +++ b/UI/MailerUI/Finnish.lproj/Localizable.strings @@ -108,7 +108,7 @@ "bcc" = "Piilokopio"; "Edit Draft..." = "Muokkaa luonnosta..."; -"Load Images" = "Lataa kuvia"; +"Load Images" = "Lataa kuvat"; "Return Receipt" = "Vastaanottokuittaus"; "The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "Viestin lähettäjä on pyytänyt lukukuittausta. Haluatko lähettää kuittauksen viestin lähettäjälle?"; @@ -165,7 +165,7 @@ "SentFolderName" = "Lähetetyt"; "TrashFolderName" = "Roskakori"; -"InboxFolderName" = "Sapuneet"; +"InboxFolderName" = "Saapuneet"; "DraftsFolderName" = "Luonnokset"; "SieveFolderName" = "Suodattimet"; "OtherUsersFolderName" = "Muut käyttäjät"; @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Ei mitään"; -"Important" = "Tärkeä"; -"Work" = "Työ"; -"Personal" = "Henkilökohtainen"; -"To Do" = "Tehtävä"; -"Later" = "Myöhemmin"; /* Mark popup menu */ "As Read" = "Luetuksi"; diff --git a/UI/MailerUI/French.lproj/Localizable.strings b/UI/MailerUI/French.lproj/Localizable.strings index f54cc94be..5201806cd 100644 --- a/UI/MailerUI/French.lproj/Localizable.strings +++ b/UI/MailerUI/French.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Répondre à"; "Add address" = "Ajouter Adresse"; -"Attachments:" = "Pièces jointes :"; "Open" = "Ouvrir"; "Select All" = "Tout sélectionner"; "Attach Web Page..." = "Joindre une page Web..."; -"Attach File(s)..." = "Joindre un fichier..."; +"file" = "fichier"; +"files" = "fichiers"; +"Save all" = "Tout sauvegarder"; "to" = "Pour"; "cc" = "Copie à"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Aucune"; -"Important" = "Important"; -"Work" = "Travail"; -"Personal" = "Personnel"; -"To Do" = "À faire"; -"Later" = "Peut attendre"; /* Mark popup menu */ "As Read" = "Comme lu"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Le message n'a pas de sujet. Êtes-vous certain de vouloir l'envoyer?"; "error_missingrecipients" = "Veuillez spécifier au moins un destinataire."; "Send Anyway" = "Envoyer sans sujet"; +"Error while saving the draft:" = "Une erreur est survenue lors de la sauvegarde du brouillon :"; +"Error while uploading the file \"%{0}\":" = "Une erreur est survenue lors du téléversement du fichier « %{0} » :"; +"There is an active file upload. Closing the window will interrupt it." = "Un transfert est actif. La fermeture de la fenêtre causera son interruption."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Le message n'a pas pu être envoyé car aucune adresse n'est valide."; diff --git a/UI/MailerUI/German.lproj/Localizable.strings b/UI/MailerUI/German.lproj/Localizable.strings index 18accabf7..9096d6c68 100644 --- a/UI/MailerUI/German.lproj/Localizable.strings +++ b/UI/MailerUI/German.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Kein"; -"Important" = "Wichtig"; -"Work" = "Geschäftlich"; -"Personal" = "Persönlich"; -"To Do" = "To-Do"; -"Later" = "Später"; /* Mark popup menu */ "As Read" = "Gelesen"; diff --git a/UI/MailerUI/Hungarian.lproj/Localizable.strings b/UI/MailerUI/Hungarian.lproj/Localizable.strings index 9bd0d51c4..cc957d651 100644 --- a/UI/MailerUI/Hungarian.lproj/Localizable.strings +++ b/UI/MailerUI/Hungarian.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Válaszcím"; "Add address" = "Cím hozzáadása"; -"Attachments:" = "Mellékletek:"; "Open" = "Megnyitás"; "Select All" = "Összes kijelölése"; "Attach Web Page..." = "Weboldal csatolása"; -"Attach File(s)..." = "Fájl(ok) csatolása"; +"file" = "állomány"; +"files" = "állomány"; +"Save all" = "Összes mentése"; "to" = "Címzett"; "cc" = "Másolat"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Nincs cimke"; -"Important" = "Fontos"; -"Work" = "Hivatalos"; -"Personal" = "Személyes"; -"To Do" = "Teendő"; -"Later" = "Később"; /* Mark popup menu */ "As Read" = "Olvasottként"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Az üzenet tárgya hiányzik"; "error_missingrecipients" = "Nincsenek címzettek megadva"; "Send Anyway" = "Így küldöm el"; +"Error while saving the draft:" = "Hiba történt a piszkozat mentésekor:"; +"Error while uploading the file \"%{0}\":" = "Hiba történt a \"%{0}\" állomány feltöltésekor:"; +"There is an active file upload. Closing the window will interrupt it." = "Állomány feltöltés folyamatban. Az ablak bezárása megszakítja ezt a folyamatot."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Az üzenetet nem lehetett elküldeni: az összes címzett érvénytelen."; diff --git a/UI/MailerUI/Icelandic.lproj/Localizable.strings b/UI/MailerUI/Icelandic.lproj/Localizable.strings index f8a6cf239..345af9765 100644 --- a/UI/MailerUI/Icelandic.lproj/Localizable.strings +++ b/UI/MailerUI/Icelandic.lproj/Localizable.strings @@ -225,11 +225,6 @@ /* Label popup menu */ "None" = "Engin"; -"Important" = "Mikilvægt"; -"Work" = "Vinna"; -"Personal" = "Persónulegt"; -"To Do" = "Verkþáttur"; -"Later" = "Seinna"; /* Mark popup menu */ "As Read" = "Sem lesið"; diff --git a/UI/MailerUI/Italian.lproj/Localizable.strings b/UI/MailerUI/Italian.lproj/Localizable.strings index 5d5b84071..7adcab42f 100644 --- a/UI/MailerUI/Italian.lproj/Localizable.strings +++ b/UI/MailerUI/Italian.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Nessuno"; -"Important" = "Importante"; -"Work" = "Lavoro"; -"Personal" = "Personale"; -"To Do" = "Da fare"; -"Later" = "Posponi"; /* Mark popup menu */ "As Read" = "Già letto"; diff --git a/UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings b/UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings index 1b384df68..8d55d640b 100644 --- a/UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings +++ b/UI/MailerUI/NorwegianBokmal.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Ingen"; -"Important" = "Viktig"; -"Work" = "Arbeid"; -"Personal" = "Personlig"; -"To Do" = "Gjøremål"; -"Later" = "Senere"; /* Mark popup menu */ "As Read" = "Som lest"; diff --git a/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings b/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings index 27bb80b5b..db7bdaa9f 100644 --- a/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/MailerUI/NorwegianNynorsk.lproj/Localizable.strings @@ -225,11 +225,6 @@ /* Label popup menu */ "None" = "Ingen"; -"Important" = "Viktig"; -"Work" = "Arbeid"; -"Personal" = "Personlig"; -"To Do" = "Gjøremål"; -"Later" = "Senere"; /* Mark popup menu */ "As Read" = "Som lest"; diff --git a/UI/MailerUI/Polish.lproj/Localizable.strings b/UI/MailerUI/Polish.lproj/Localizable.strings index 973baee49..b427b01b1 100644 --- a/UI/MailerUI/Polish.lproj/Localizable.strings +++ b/UI/MailerUI/Polish.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Odpowiedź do"; "Add address" = "Dodaj adres"; -"Attachments:" = "Załączniki:"; "Open" = "Otwórz"; "Select All" = "Zaznacz wszystkie"; "Attach Web Page..." = "Załącz stronę Web"; -"Attach File(s)..." = "Załącz plik(i)"; +"file" = "plik"; +"files" = "pliki"; +"Save all" = "Zapisz wszystkie"; "to" = "Do"; "cc" = "DW"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Brak"; -"Important" = "Ważne"; -"Work" = "Praca"; -"Personal" = "Osobiste"; -"To Do" = "Do zrobienia"; -"Later" = "Później"; /* Mark popup menu */ "As Read" = "Jako przeczytane"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Brak tematu"; "error_missingrecipients" = "Brak odbiorców"; "Send Anyway" = "Wyślij mimo wszystko"; +"Error while saving the draft:" = "Błąd zapisu kopii roboczej"; +"Error while uploading the file \"%{0}\":" = "Błąd w trakcie wysyłania pliku \"%{0}\":"; +"There is an active file upload. Closing the window will interrupt it." = "Trwa przesyłanie pliku. Zamknięcie okna przerwie tą transmisję."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Nie można wysłać wiadomości - wszyscy odbiorcy zostali odrzuceni."; diff --git a/UI/MailerUI/Russian.lproj/Localizable.strings b/UI/MailerUI/Russian.lproj/Localizable.strings index 935200461..5e2202404 100644 --- a/UI/MailerUI/Russian.lproj/Localizable.strings +++ b/UI/MailerUI/Russian.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Обратный адрес"; "Add address" = "Добавить адрес"; -"Attachments:" = "Вложения:"; "Open" = "Открыть"; "Select All" = "Выбрать все"; "Attach Web Page..." = "Прикрепить веб-страницы ..."; -"Attach File(s)..." = "Прикрепить файл (ы) ..."; +"file" = "файл"; +"files" = "файлы"; +"Save all" = "Сохранить все"; "to" = "Кому"; "cc" = "Копия"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Удалить все метки"; -"Important" = "Важно"; -"Work" = "Работа"; -"Personal" = "Личное"; -"To Do" = "К исполнению"; -"Later" = "Позже"; /* Mark popup menu */ "As Read" = "как прочтанное"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Тема сообщения не указана"; "error_missingrecipients" = "Не указан адрес получателя"; "Send Anyway" = "Всё равно послать"; +"Error while saving the draft:" = "Ошибка при сохранении черновика:"; +"Error while uploading the file \"%{0}\":" = "Ошибка при загрузке файла \"%{0}\":"; +"There is an active file upload. Closing the window will interrupt it." = "Сейчас выполняется загрузка файла. Закрытие окна прервет её."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Не удается отправить сообщение: всем получателям, все получатели являются недействительными."; diff --git a/UI/MailerUI/Slovak.lproj/Localizable.strings b/UI/MailerUI/Slovak.lproj/Localizable.strings index 7bae452d5..4e2a34637 100644 --- a/UI/MailerUI/Slovak.lproj/Localizable.strings +++ b/UI/MailerUI/Slovak.lproj/Localizable.strings @@ -97,11 +97,12 @@ "Reply-To" = "Odpovedz odosielateľovi"; "Add address" = "Pridať adresu"; -"Attachments:" = "Prílohy"; "Open" = "Otvoriť"; "Select All" = "Vyber všetko"; "Attach Web Page..." = "Prilož WWW stránku..."; -"Attach File(s)..." = "Prilož súbor(y)..."; +"file" = "súbor"; +"files" = "súbory"; +"Save all" = "Save all"; "to" = "Pre"; "cc" = "Kópia"; @@ -163,7 +164,7 @@ /* Tree */ -"SentFolderName" = "Poslať"; +"SentFolderName" = "Odoslané"; "TrashFolderName" = "Kôš"; "InboxFolderName" = "Prijaté"; "DraftsFolderName" = "Koncepty"; @@ -227,11 +228,6 @@ /* Label popup menu */ "None" = "Žiaden"; -"Important" = "Dôležité"; -"Work" = "Pracovné"; -"Personal" = "Osobné"; -"To Do" = "Treba urobiť"; -"Later" = "Neskôr"; /* Mark popup menu */ "As Read" = "Prečítané"; @@ -285,6 +281,9 @@ "error_missingsubject" = "Správa nemá žiadny predmet. Skutočne ju checete odoslať?"; "error_missingrecipients" = "Prosím zvoľte aspoň jedného príjemcu."; "Send Anyway" = "Poslať napriek tomu"; +"Error while saving the draft:" = "Počas ukladania konceptu nastala chyba:"; +"Error while uploading the file \"%{0}\":" = "Nastala chyba počas nahrávania súboru \"%{0}\":"; +"There is an active file upload. Closing the window will interrupt it." = "Práve prebieha nahrávanie súboru. Zatvorením okna nahrávanie prerušíte."; /* Message sending */ "cannot send message: (smtp) all recipients discarded" = "Správa sa nedá odoslať: žiadny príjemca nie je platný."; diff --git a/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings b/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings index f8e9c707a..357fdb6ab 100644 --- a/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings +++ b/UI/MailerUI/SpanishArgentina.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Ninguna"; -"Important" = "Importante"; -"Work" = "Trabajo"; -"Personal" = "Personal"; -"To Do" = "Por hacer"; -"Later" = "Más tarde"; /* Mark popup menu */ "As Read" = "Como leídos"; diff --git a/UI/MailerUI/SpanishSpain.lproj/Localizable.strings b/UI/MailerUI/SpanishSpain.lproj/Localizable.strings index a70f71412..f2bb7d703 100644 --- a/UI/MailerUI/SpanishSpain.lproj/Localizable.strings +++ b/UI/MailerUI/SpanishSpain.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Ninguna"; -"Important" = "Importante"; -"Work" = "Trabajo"; -"Personal" = "Personal"; -"To Do" = "Por hacer"; -"Later" = "Más tarde"; /* Mark popup menu */ "As Read" = "Como leídos"; diff --git a/UI/MailerUI/Swedish.lproj/Localizable.strings b/UI/MailerUI/Swedish.lproj/Localizable.strings index 3ac0c88cd..d7151af2f 100644 --- a/UI/MailerUI/Swedish.lproj/Localizable.strings +++ b/UI/MailerUI/Swedish.lproj/Localizable.strings @@ -225,11 +225,6 @@ /* Label popup menu */ "None" = "Inget"; -"Important" = "Viktigt"; -"Work" = "Arbete"; -"Personal" = "Personligt"; -"To Do" = "Att göra"; -"Later" = "Senare"; /* Mark popup menu */ "As Read" = "Som läst"; diff --git a/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar b/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar index 05e7a38ff..84fe4cea5 100644 --- a/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar +++ b/UI/MailerUI/Toolbars/SOGoDraftObject.toolbar @@ -15,13 +15,6 @@ cssClass = "tbicon_addressbook"; label = "Contacts"; tooltip = "Select a recipient from an Address Book"; }, - { link = "#"; - isSafe = NO; - onclick = "return clickedEditorAttach(this)"; - image = "tb-compose-attach-flat-24x24.png"; - cssClass = "tbicon_attach single-window-not-conditional"; - label = "Attach"; - tooltip = "Include an attachment"; }, { link = "#"; isSafe = NO; onclick = "return clickedEditorSave(this);"; diff --git a/UI/MailerUI/UIxMailAccountActions.h b/UI/MailerUI/UIxMailAccountActions.h index 08874fcf3..286b543c5 100644 --- a/UI/MailerUI/UIxMailAccountActions.h +++ b/UI/MailerUI/UIxMailAccountActions.h @@ -1,8 +1,6 @@ /* UIxMailAccountActions.h - this file is part of SOGo * - * Copyright (C) 2007-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2007-2013 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 @@ -29,12 +27,6 @@ @interface UIxMailAccountActions : WODirectAction { - NSString *inboxFolderName; - NSString *draftsFolderName; - NSString *sentFolderName; - NSString *trashFolderName; - NSString *otherUsersFolderName; - NSString *sharedFoldersName; } - (WOResponse *) listMailboxesAction; diff --git a/UI/MailerUI/UIxMailAccountActions.m b/UI/MailerUI/UIxMailAccountActions.m index a06411294..e331ab4ab 100644 --- a/UI/MailerUI/UIxMailAccountActions.m +++ b/UI/MailerUI/UIxMailAccountActions.m @@ -2,9 +2,6 @@ * * Copyright (C) 2007-2013 Inverse inc. * - * Author: Wolfgang Sourdeau - * Ludovic Marcotte - * * 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 * the Free Software Foundation; either version 2, or (at your option) @@ -51,159 +48,16 @@ @implementation UIxMailAccountActions -- (id) init -{ - if ((self = [super init])) - { - inboxFolderName = nil; - draftsFolderName = nil; - sentFolderName = nil; - trashFolderName = nil; - otherUsersFolderName = nil; - sharedFoldersName = nil; - } - - return self; -} - -- (void) dealloc -{ - [inboxFolderName release]; - [draftsFolderName release]; - [sentFolderName release]; - [trashFolderName release]; - [otherUsersFolderName release]; - [sharedFoldersName release]; - [super dealloc]; -} - -- (NSString *) _folderType: (NSString *) folderName -{ - NSString *folderType; - SOGoMailAccount *co; - NSArray *specialFolders; - - if (!inboxFolderName) - { - co = [self clientObject]; - specialFolders = [[NSArray arrayWithObjects: - [co inboxFolderNameInContext: context], - [co draftsFolderNameInContext: context], - [co sentFolderNameInContext: context], - [co trashFolderNameInContext: context], - [co otherUsersFolderNameInContext: context], - [co sharedFoldersNameInContext: context], - nil] stringsWithFormat: @"/%@"]; - ASSIGN(inboxFolderName, [specialFolders objectAtIndex: 0]); - ASSIGN(draftsFolderName, [specialFolders objectAtIndex: 1]); - ASSIGN(sentFolderName, [specialFolders objectAtIndex: 2]); - ASSIGN(trashFolderName, [specialFolders objectAtIndex: 3]); - if ([specialFolders count] > 4) - ASSIGN(otherUsersFolderName, [specialFolders objectAtIndex: 4]); - if ([specialFolders count] > 5) - ASSIGN(sharedFoldersName, [specialFolders objectAtIndex: 5]); - } - - if ([folderName isEqualToString: inboxFolderName]) - folderType = @"inbox"; - else if ([folderName isEqualToString: draftsFolderName]) - folderType = @"draft"; - else if ([folderName isEqualToString: sentFolderName]) - folderType = @"sent"; - else if ([folderName isEqualToString: trashFolderName]) - folderType = @"trash"; - else if ([folderName hasPrefix: [NSString stringWithFormat: @"%@/", draftsFolderName]]) - folderType = @"draft/folder"; - else if ([folderName hasPrefix: [NSString stringWithFormat: @"%@/", sentFolderName]]) - folderType = @"sent/folder"; - else - folderType = @"folder"; - - return folderType; -} - -- (NSArray *) _jsonFolders: (NSEnumerator *) rawFolders -{ - NSString *currentFolder, *currentDecodedFolder, *currentDisplayName, *currentFolderType, *login, *fullName; - NSMutableArray *pathComponents; - SOGoUserManager *userManager; - NSDictionary *folderData; - NSMutableArray *folders; - NSAutoreleasePool *pool; - - folders = [NSMutableArray array]; - while ((currentFolder = [rawFolders nextObject])) - { - // Using a local pool to avoid using too many file descriptors. This could - // happen with tons of mailboxes under "Other Users" as LDAP connections - // are never reused and "autoreleased" at the end. This loop would consume - // lots of LDAP connections during its execution. - pool = [[NSAutoreleasePool alloc] init]; - - currentDecodedFolder = [currentFolder stringByDecodingImap4FolderName]; - currentFolderType = [self _folderType: currentFolder]; - - // We translate the "Other Users" and "Shared Folders" namespaces. - // While we're at it, we also translate the user's mailbox names - // to the full name of the person. - if (otherUsersFolderName && [currentDecodedFolder hasPrefix: otherUsersFolderName]) - { - // We have a string like /Other Users/lmarcotte/... under Cyrus, but we could - // also have something like /shared under Dovecot. So we swap the username only - // if we have one, of course. - pathComponents = [NSMutableArray arrayWithArray: [currentDecodedFolder pathComponents]]; - - if ([pathComponents count] > 2) - { - login = [pathComponents objectAtIndex: 2]; - userManager = [SOGoUserManager sharedUserManager]; - fullName = [userManager getCNForUID: login]; - [pathComponents removeObjectsInRange: NSMakeRange(0,3)]; - - currentDisplayName = [NSString stringWithFormat: @"/%@/%@/%@", - [self labelForKey: @"OtherUsersFolderName"], - (fullName != nil ? fullName : login), - [pathComponents componentsJoinedByString: @"/"]]; - - } - else - { - currentDisplayName = [NSString stringWithFormat: @"/%@%@", - [self labelForKey: @"OtherUsersFolderName"], - [currentDecodedFolder substringFromIndex: - [otherUsersFolderName length]]]; - } - } - else if (sharedFoldersName && [currentDecodedFolder hasPrefix: sharedFoldersName]) - currentDisplayName = [NSString stringWithFormat: @"/%@%@", [self labelForKey: @"SharedFoldersName"], - [currentDecodedFolder substringFromIndex: [sharedFoldersName length]]]; - else - currentDisplayName = currentDecodedFolder; - - folderData = [NSDictionary dictionaryWithObjectsAndKeys: - currentFolder, @"path", - currentFolderType, @"type", - currentDisplayName, @"displayName", - nil]; - [folders addObject: folderData]; - [pool release]; - } - - return folders; -} - - (WOResponse *) listMailboxesAction { SOGoMailAccount *co; - NSEnumerator *rawFolders; NSArray *folders; NSDictionary *data; WOResponse *response; co = [self clientObject]; - rawFolders = [[co allFolderPaths] objectEnumerator]; - folders = [self _jsonFolders: rawFolders]; + folders = [co allFoldersMetadata]; data = [NSDictionary dictionaryWithObjectsAndKeys: folders, @"mailboxes", nil]; response = [self responseWithStatus: 200 diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 3eb7878b8..470e2a93c 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -56,6 +56,7 @@ #import #import #import +#import #import #import #import @@ -63,6 +64,8 @@ #import #import +#import + /* UIxMailEditor @@ -89,8 +92,9 @@ id currentFolder; /* these are for the inline attachment list */ - NSString *attachmentName; - NSArray *attachmentNames; + NSDictionary *attachment; + NSArray *attachmentAttrs; + NSString *currentAttachment; NSMutableArray *attachedFiles; } @@ -117,6 +121,9 @@ static NSArray *infoKeys = nil; priority = @"NORMAL"; receipt = nil; currentFolder = nil; + currentAttachment = nil; + attachmentAttrs = nil; + attachedFiles = nil; } return self; @@ -137,8 +144,9 @@ static NSArray *infoKeys = nil; [bcc release]; [sourceUID release]; [sourceFolder release]; - [attachmentName release]; - [attachmentNames release]; + [attachment release]; + [currentAttachment release]; + [attachmentAttrs release]; [attachedFiles release]; [currentFolder release]; [super dealloc]; @@ -155,6 +163,11 @@ static NSArray *infoKeys = nil; return item; } +- (NSString *) uid +{ + return [[self clientObject] nameInContainer]; +} + - (NSArray *) priorityClasses { static NSArray *priorities = nil; @@ -369,14 +382,19 @@ static NSArray *infoKeys = nil; return (([to count] + [cc count] + [bcc count]) > 0); } -- (void) setAttachmentName: (NSString *) newAttachmentName +- (void) setAttachment: (NSDictionary *) newAttachment { - ASSIGN (attachmentName, newAttachmentName); + ASSIGN (attachment, newAttachment); } -- (NSString *) attachmentName +- (NSDictionary *) attachment { - return attachmentName; + return attachment; +} + +- (NSFormatter *) sizeFormatter +{ + return [UIxMailSizeFormatter sharedMailSizeFormatter]; } /* from addresses */ @@ -417,7 +435,7 @@ static NSArray *infoKeys = nil; - (NSDictionary *) storeInfo { [self debugWithFormat:@"storing info ..."]; - return [self valuesForKeys:infoKeys]; + return [self valuesForKeys: infoKeys]; } /* contacts search */ @@ -517,8 +535,8 @@ static NSArray *infoKeys = nil; - (NSDictionary *) _scanAttachmentFilenamesInRequest: (id) httpBody { - NSMutableDictionary *filenames; - NSDictionary *attachment; + NSMutableDictionary *files; + NSDictionary *file; NSArray *parts; unsigned int count, max; NGMimeBodyPart *part; @@ -527,117 +545,136 @@ static NSArray *infoKeys = nil; parts = [httpBody parts]; max = [parts count]; - filenames = [NSMutableDictionary dictionaryWithCapacity: max]; + files = [NSMutableDictionary dictionaryWithCapacity: max]; for (count = 0; count < max; count++) { part = [parts objectAtIndex: count]; - header = (NGMimeContentDispositionHeaderField *) - [part headerForKey: @"content-disposition"]; - mimeType = [(NGMimeType *) - [part headerForKey: @"content-type"] stringValue]; - filename = [self _fixedFilename: [header filename]]; - attachment = [NSDictionary dictionaryWithObjectsAndKeys: - filename, @"filename", - mimeType, @"mimetype", nil]; - [filenames setObject: attachment forKey: [header name]]; + header = (NGMimeContentDispositionHeaderField *)[part headerForKey: @"content-disposition"]; + if ([[header name] hasPrefix: @"attachments"]) + { + mimeType = [(NGMimeType *)[part headerForKey: @"content-type"] stringValue]; + filename = [self _fixedFilename: [header filename]]; + file = [NSDictionary dictionaryWithObjectsAndKeys: + filename, @"filename", + mimeType, @"mimetype", + [part body], @"body", + nil]; + [files setObject: file forKey: [NSString stringWithFormat: @"%@_%@", [header name], filename]]; + } } - return filenames; + return files; } -- (BOOL) _saveAttachments +- (NSException *) _saveAttachments { + NSException *error; WORequest *request; - NSEnumerator *allKeys; - NSString *key; - BOOL success; - NSDictionary *filenames; + NSEnumerator *allAttachments; + NSDictionary *attrs, *filenames; + NGMimeType *mimeType; id httpBody; SOGoDraftObject *co; - success = YES; + error = nil; request = [context request]; - httpBody = [[request httpRequest] body]; - filenames = [self _scanAttachmentFilenamesInRequest: httpBody]; + mimeType = [[request httpRequest] contentType]; + if ([[mimeType type] isEqualToString: @"multipart"]) + { + httpBody = [[request httpRequest] body]; + filenames = [self _scanAttachmentFilenamesInRequest: httpBody]; - co = [self clientObject]; - allKeys = [[request formValueKeys] objectEnumerator]; - while ((key = [allKeys nextObject]) && success) - if ([key hasPrefix: @"attachment"]) - success - = (![co saveAttachment: (NSData *) [request formValueForKey: key] - withMetadata: [filenames objectForKey: key]]); + co = [self clientObject]; + allAttachments = [filenames objectEnumerator]; + while ((attrs = [allAttachments nextObject]) && !error) + { + error = [co saveAttachment: (NSData *) [attrs objectForKey: @"body"] + withMetadata: attrs]; + // Keep the name of the last attachment saved + ASSIGN(currentAttachment, [attrs objectForKey: @"filename"]); + } + } - return success; + return error; } -- (BOOL) _saveFormInfo +- (NSException *) _saveFormInfo { NSDictionary *info; NSException *error; - BOOL success; SOGoDraftObject *co; co = [self clientObject]; [co fetchInfo]; - success = YES; - - if ([self _saveAttachments]) + error = [self _saveAttachments]; + if (!error) { info = [self storeInfo]; [co setHeaders: info]; [co setIsHTML: isHTML]; [co setText: (isHTML ? [NSString stringWithFormat: @"%@", text] : text)];; error = [co storeInfo]; - if (error) - { - [self errorWithFormat: @"failed to store draft: %@", error]; - // TODO: improve error handling - success = NO; - } } - else - success = NO; - // TODO: wrap content - - return success; + return error; } -- (id) failedToSaveFormResponse +- (id) failedToSaveFormResponse: (NSString *) msg { - // TODO: improve error handling - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to store draft object on server!"]; + NSDictionary *d; + + d = [NSDictionary dictionaryWithObjectsAndKeys: msg, @"textStatus", nil]; + + return [self responseWithStatus: 500 + andString: [d jsonRepresentation]]; } /* attachment helper */ -- (NSArray *) attachmentNames +- (NSArray *) attachmentAttrs { NSArray *a; + SOGoDraftObject *co; + SOGoMailObject *mail; - if (!attachmentNames) + co = [self clientObject]; + if (!attachmentAttrs || ![co imap4URL]) + { + [co fetchInfo]; + if ([co IMAP4ID] > -1) + { + mail = [[[SOGoMailObject alloc] initWithImap4URL: [co imap4URL] inContainer: [co container]] autorelease]; + a = [mail fetchFileAttachmentKeys]; + ASSIGN (attachmentAttrs, a); + } + } + + if (currentAttachment) { - a = [[self clientObject] fetchAttachmentNames]; - ASSIGN (attachmentNames, - [a sortedArrayUsingSelector: @selector (compare:)]); + // When currentAttachment is defined, only return the attributes of the last + // attachment saved + NSEnumerator *allAttachments; + NSDictionary* attrs; + + allAttachments = [attachmentAttrs objectEnumerator]; + while ((attrs = [allAttachments nextObject])) + { + if ([[attrs objectForKey: @"filename"] isEqualToString: currentAttachment]) + { + return [NSArray arrayWithObject: attrs]; + } + } } - return attachmentNames; + return attachmentAttrs; } - (BOOL) hasAttachments { - return [[self attachmentNames] count] > 0 ? YES : NO; -} - -- (NSString *) uid -{ - return [[self clientObject] nameInContainer]; + return [[self attachmentAttrs] count] > 0 ? YES : NO; } - (id) defaultAction @@ -658,14 +695,20 @@ static NSArray *infoKeys = nil; { id result; - if ([self _saveFormInfo]) + result = [self _saveFormInfo]; + if (!result) { result = [[self clientObject] save]; - if (!result) - result = [self responseWith204]; + } + if (!result) + { + attachmentAttrs = nil; + NSArray *attrs = [self attachmentAttrs]; + result = [self responseWithStatus: 200 + andString: [attrs jsonRepresentation]]; } else - result = [self failedToSaveFormResponse]; + result = [self failedToSaveFormResponse: [result reason]]; return result; } @@ -714,25 +757,24 @@ static NSArray *infoKeys = nil; recipients_count = [[messageSubmissions objectForKey: @"RecipientsCount"] intValue]; if ((messages_count >= [dd maximumMessageSubmissionCount] || recipients_count >= [dd maximumRecipientCount]) && - delta >= [dd maximumSubmissionInterval] && - delta <= block_time ) + delta <= block_time) { jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: @"failure", @"status", - [self labelForKey: @"Tried to send too many mails. Please wait."], + [self labelForKey: @"Tried to send too many mails. Please wait."], @"message", nil]; return [self responseWithStatus: 200 andString: [jsonResponse jsonRepresentation]]; } - if (delta > block_time) + if (delta > block_time || + (delta >= [dd maximumSubmissionInterval] && messages_count < [dd maximumMessageSubmissionCount] && recipients_count < [dd maximumRecipientCount])) { [[SOGoCache sharedCache] setMessageSubmissionsCount: 0 recipientsCount: 0 forLogin: [[context activeUser] login]]; } - } co = [self clientObject]; @@ -741,10 +783,11 @@ static NSArray *infoKeys = nil; error = [self validateForSend]; if (!error) { - if ([self _saveFormInfo]) + error = [self _saveFormInfo]; + if (!error) error = [co sendMail]; else - error = [self failedToSaveFormResponse]; + error = [self failedToSaveFormResponse: [error reason]]; } if (error) diff --git a/UI/MailerUI/UIxMailFolderActions.m b/UI/MailerUI/UIxMailFolderActions.m index 41d4afc5e..6e3c4e4d5 100644 --- a/UI/MailerUI/UIxMailFolderActions.m +++ b/UI/MailerUI/UIxMailFolderActions.m @@ -1,9 +1,6 @@ /* UIxMailFolderActions.m - this file is part of SOGo * - * Copyright (C) 2007-2011 Inverse inc. - * - * Author: Wolfgang Sourdeau - * Francis Lachapelle + * Copyright (C) 2007-2013 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/MailerUI/UIxMailListActions.h b/UI/MailerUI/UIxMailListActions.h index bd80c7357..26a917778 100644 --- a/UI/MailerUI/UIxMailListActions.h +++ b/UI/MailerUI/UIxMailListActions.h @@ -27,6 +27,7 @@ @class NSDictionary; @class EOQualifier; @class SOGoDateFormatter; +@class UIxMailSizeFormatter; @interface UIxMailListActions : WODirectAction { @@ -35,6 +36,7 @@ id message; SOGoDateFormatter *dateFormatter; NSTimeZone *userTimeZone; + UIxMailSizeFormatter *sizeFormatter; BOOL sortByThread; int folderType; int specificMessageNumber; diff --git a/UI/MailerUI/UIxMailListActions.m b/UI/MailerUI/UIxMailListActions.m index 90acfd53e..33585e932 100644 --- a/UI/MailerUI/UIxMailListActions.m +++ b/UI/MailerUI/UIxMailListActions.m @@ -60,6 +60,7 @@ #import #import +#import #import "WOContext+UIxMailer.h" #import "UIxMailFormatter.h" @@ -120,20 +121,9 @@ return [dateFormatter formattedDateAndTime: messageDate]; } -- (NSString *) messageSize +- (UIxMailSizeFormatter *) sizeFormatter { - NSString *rc; - int size; - - size = [[message valueForKey: @"size"] intValue]; - if (size > 1024*1024) - rc = [NSString stringWithFormat: @"%.1f MB", (float) size/1024/1024]; - else if (size > 1024*100) - rc = [NSString stringWithFormat: @"%d KB", size/1024]; - else - rc = [NSString stringWithFormat: @"%.1f KB", (float) size/1024]; - - return rc; + return [UIxMailSizeFormatter sharedMailSizeFormatter]; } // @@ -806,7 +796,7 @@ [msg addObject: msgDate]; // Size - [msg addObject: [self messageSize]]; + [msg addObject: [[self sizeFormatter] stringForObjectValue: [message objectForKey: @"size"]]]; // rowClasses [msg addObject: [self messageRowStyleClass]]; diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index e714bf6ab..371a2a504 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -47,11 +47,12 @@ #import #import #import +#import #import #import #import -#import #import // cyclic +#import #import "WOContext+UIxMailer.h" @@ -60,6 +61,8 @@ id currentAddress; NSString *shouldAskReceipt; NSString *matchingIdentityEMail; + NSDictionary *attachment; + NSArray *attachmentAttrs; } @end @@ -80,6 +83,8 @@ static NSString *mailETag = nil; - (void) dealloc { [matchingIdentityEMail release]; + [attachment release]; + [attachmentAttrs release]; [super dealloc]; } @@ -111,6 +116,16 @@ static NSString *mailETag = nil; [self messageSubject]]; } +- (void) setAttachment: (NSDictionary *) newAttachment +{ + ASSIGN (attachment, newAttachment); +} + +- (NSDictionary *) attachment +{ + return attachment; +} + /* links (DUP to UIxMailPartViewer!) */ - (NSString *) linkToEnvelopeAddress: (NGImap4EnvelopeAddress *) _address @@ -146,6 +161,36 @@ static NSString *mailETag = nil; return [[[self clientObject] replyToEnvelopeAddresses] count] > 0 ? YES : NO; } +/* attachment helper */ + +- (NSArray *) attachmentAttrs +{ + if (!attachmentAttrs) + { + ASSIGN (attachmentAttrs, [[self clientObject] fetchFileAttachmentKeys]); + } + + return attachmentAttrs; +} + +- (BOOL) hasAttachments +{ + return [[self attachmentAttrs] count] > 0 ? YES : NO; +} + +- (NSFormatter *) sizeFormatter +{ + return [UIxMailSizeFormatter sharedMailSizeFormatter]; +} + +- (NSString *) attachmentsText +{ + if ([[self attachmentAttrs] count] > 1) + return [self labelForKey: @"files"]; + else + return [self labelForKey: @"file"]; +} + /* viewers */ - (id) contentViewerComponent diff --git a/UI/MailerUI/Ukrainian.lproj/Localizable.strings b/UI/MailerUI/Ukrainian.lproj/Localizable.strings index 8a7f7b729..4e0555af3 100644 --- a/UI/MailerUI/Ukrainian.lproj/Localizable.strings +++ b/UI/MailerUI/Ukrainian.lproj/Localizable.strings @@ -227,11 +227,6 @@ /* Label popup menu */ "None" = "Вилучити всі позначки"; -"Important" = "Важливе"; -"Work" = "Робоче"; -"Personal" = "Особисте"; -"To Do" = "До виконання"; -"Later" = "Відсунуте"; /* Mark popup menu */ "As Read" = "Прочитаним"; diff --git a/UI/MailerUI/Welsh.lproj/Localizable.strings b/UI/MailerUI/Welsh.lproj/Localizable.strings index 61ad37797..1da56be42 100644 --- a/UI/MailerUI/Welsh.lproj/Localizable.strings +++ b/UI/MailerUI/Welsh.lproj/Localizable.strings @@ -225,11 +225,6 @@ /* Label popup menu */ "None" = "Dim"; -"Important" = "Pwysig"; -"Work" = "gwaith"; -"Personal" = "Personol"; -"To Do" = "I'w wneud"; -"Later" = "Hwyrach"; /* Mark popup menu */ "As Read" = "Darllenwyd"; diff --git a/UI/MainUI/GNUmakefile b/UI/MainUI/GNUmakefile index b2f8986c4..62837f307 100644 --- a/UI/MainUI/GNUmakefile +++ b/UI/MainUI/GNUmakefile @@ -14,6 +14,7 @@ MainUI_OBJC_FILES += \ SOGoUserHomePage.m \ SOGoBrowsersPanel.m \ UIxLoading.m \ + SOGoMicrosoftActiveSyncActions.m \ ifeq ($(saml2_config), yes) MainUI_OBJC_FILES += SOGoSAML2Actions.m diff --git a/UI/MainUI/SOGoMicrosoftActiveSyncActions.m b/UI/MainUI/SOGoMicrosoftActiveSyncActions.m new file mode 100644 index 000000000..a00344fba --- /dev/null +++ b/UI/MainUI/SOGoMicrosoftActiveSyncActions.m @@ -0,0 +1,67 @@ +/* + Copyright (C) 2014 Inverse inc. + + This file is part of SOGo. + + 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. + + 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. + + You should have received a copy of the GNU Lesser General Public + License along with SOGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#import + +#import + +#import +#import +#import +#import + +@interface SOGoMicrosoftActiveSyncActions : WODirectAction +@end + +@implementation SOGoMicrosoftActiveSyncActions + +// +// Invoked on POST actions +// +- (WOResponse *) microsoftServerActiveSyncAction +{ + WOResponse *response; + WORequest *request; + NSBundle *bundle; + NSException *ex; + id dispatcher; + Class clazz; + + request = [context request]; + response = [self responseWithStatus: 200]; + + bundle = [NSBundle bundleForClass: NSClassFromString(@"ActiveSyncProduct")]; + clazz = [bundle classNamed: @"SOGoActiveSyncDispatcher"]; + dispatcher = [[clazz alloc] init]; + + ex = [dispatcher dispatchRequest: request inResponse: response context: context]; + + if (ex) + { + return [NSException exceptionWithHTTPStatus: 500]; + } + + RELEASE(dispatcher); + + return response; +} + +@end diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 5f10391f4..d8e32c1db 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -1,6 +1,6 @@ /* - Copyright (C) 2006-2011 Inverse inc. + Copyright (C) 2006-2014 Inverse inc. Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOGo. @@ -537,7 +537,7 @@ { NSString *aString; - aString = [NSString stringWithString: SOGoVersion ]; + aString = [NSString stringWithString: SOGoVersion]; return aString; } diff --git a/UI/MainUI/SOGoSAML2Actions.m b/UI/MainUI/SOGoSAML2Actions.m index d081f63d2..53ab5d1db 100644 --- a/UI/MainUI/SOGoSAML2Actions.m +++ b/UI/MainUI/SOGoSAML2Actions.m @@ -1,8 +1,6 @@ /* SOGoSAML2Actions.m - this file is part of SOGo * - * Copyright (C) 2012 Inverse inc - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2012-2014 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/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 623bbf135..1e2e361ae 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -91,67 +91,90 @@ NSDictionary *record; SOGoUser *user; - int recordCount, recordMax, count, startInterval, endInterval, i, type; + int recordCount, recordMax, count, startInterval, endInterval, i, type, maxBookings, isResource; recordMax = [records count]; - user = [SOGoUser userWithLogin: [[self clientObject] ownerInContext: context] - roles: nil]; + user = [SOGoUser userWithLogin: [[self clientObject] ownerInContext: context] roles: nil]; + maxBookings = [user numberOfSimultaneousBookings]; + isResource = [user isResource]; - for (recordCount = 0; recordCount < recordMax; recordCount++) + // Don't fetch freebusy information if the user is of type 'resource' and has unlimited bookings + if (!isResource || maxBookings > 0) { - record = [records objectAtIndex: recordCount]; - if ([[record objectForKey: @"c_isopaque"] boolValue]) - { - type = 0; + for (recordCount = 0; recordCount < recordMax; recordCount++) + { + record = [records objectAtIndex: recordCount]; + if ([[record objectForKey: @"c_isopaque"] boolValue]) + { + type = 0; - // If the event has NO organizer (which means it's the user that has created it) OR - // If we are the organizer of the event THEN we are automatically busy - if ([[record objectForKey: @"c_orgmail"] length] == 0 || - [user hasEmail: [record objectForKey: @"c_orgmail"]]) - { - type = 1; - } - else - { - // We check if the user has accepted/declined or needs action - // on the current event. - emails = [[record objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"]; + // If the event has NO organizer (which means it's the user that has created it) OR + // If we are the organizer of the event THEN we are automatically busy + if ([[record objectForKey: @"c_orgmail"] length] == 0 || + [user hasEmail: [record objectForKey: @"c_orgmail"]]) + { + type = 1; + } + else + { + // We check if the user has accepted/declined or needs action + // on the current event. + emails = [[record objectForKey: @"c_partmails"] componentsSeparatedByString: @"\n"]; - for (i = 0; i < [emails count]; i++) - { - if ([user hasEmail: [emails objectAtIndex: i]]) - { - // We now fetch the c_partstates array and get the participation - // status of the user for the event - partstates = [[record objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"]; + for (i = 0; i < [emails count]; i++) + { + if ([user hasEmail: [emails objectAtIndex: i]]) + { + // We now fetch the c_partstates array and get the participation + // status of the user for the event + partstates = [[record objectForKey: @"c_partstates"] componentsSeparatedByString: @"\n"]; - if (i < [partstates count]) - { - type = ([[partstates objectAtIndex: i] intValue] < 2 ? 1 : 0); - } - break; - } - } - } + if (i < [partstates count]) + { + type = ([[partstates objectAtIndex: i] intValue] < 2 ? 1 : 0); + } + break; + } + } + } - currentDate = [record objectForKey: @"startDate"]; - if ([currentDate earlierDate: startDate] == currentDate) - startInterval = 0; - else - startInterval = ([currentDate timeIntervalSinceDate: startDate] - / intervalSeconds); + if (type == 1) + { + // User is busy for this event; update items bit string + currentDate = [record objectForKey: @"startDate"]; + if ([currentDate earlierDate: startDate] == currentDate) + startInterval = 0; + else + startInterval = ([currentDate timeIntervalSinceDate: startDate] + / intervalSeconds); - currentDate = [record objectForKey: @"endDate"]; - if ([currentDate earlierDate: endDate] == endDate) - endInterval = itemCount - 1; - else - endInterval = ([currentDate timeIntervalSinceDate: startDate] - / intervalSeconds); + currentDate = [record objectForKey: @"endDate"]; + if ([currentDate earlierDate: endDate] == endDate) + endInterval = itemCount - 1; + else + endInterval = ([currentDate timeIntervalSinceDate: startDate] + / intervalSeconds); - if (type == 1) - for (count = startInterval; count < endInterval; count++) - *(items + count) = 1; - } + // Update bit string representation + // If the user is a resource, keep the sum of overlapping events + for (count = startInterval; count < endInterval; count++) + { + *(items + count) = isResource ? *(items + count) + 1 : 1; + } + } + } + } + if (maxBookings > 0) + { + // Reset the freebusy for the periods that are bellow the maximum number of bookings + for (count = 0; count < itemCount; count++) + { + if (*(items + count) < maxBookings) + *(items + count) = 0; + else + *(items + count) = 1; + } + } } } @@ -168,17 +191,23 @@ interval = [endDate timeIntervalSinceDate: startDate] + 60; intervals = interval / intervalSeconds; /* slices of 15 minutes */ + // Build a bit string representation of the freebusy data for the period freeBusyItems = NSZoneCalloc (NULL, intervals, sizeof (int)); - [self _fillFreeBusyItems: freeBusyItems count: intervals + [self _fillFreeBusyItems: freeBusyItems + count: intervals withRecords: [fb fetchFreeBusyInfosFrom: startDate to: endDate forContact: uid] - fromStartDate: startDate toEndDate: endDate]; + fromStartDate: startDate + toEndDate: endDate]; + // Convert bit string to a NSArray freeBusy = [NSMutableArray arrayWithCapacity: intervals]; for (count = 0; count < intervals; count++) - [freeBusy - addObject: [NSString stringWithFormat: @"%d", *(freeBusyItems + count)]]; + { + [freeBusy addObject: [NSString stringWithFormat: @"%d", *(freeBusyItems + count)]]; + } NSZoneFree (NULL, freeBusyItems); + // Return a NSString representation return [freeBusy componentsJoinedByString: @","]; } diff --git a/UI/MainUI/product.plist b/UI/MainUI/product.plist index 399eaad05..e5c9d2ec1 100644 --- a/UI/MainUI/product.plist +++ b/UI/MainUI/product.plist @@ -101,8 +101,7 @@ SOGo = { // TODO: move decls to class slots = { toolbar = { - value = "none"; // keep this in order to avoid lookups on username - // "toolbar" + value = "none"; // keep this in order to avoid lookups on username "toolbar" }; }; methods = { @@ -114,6 +113,11 @@ protectedBy = ""; pageName = "SOGoRootPage"; }; + Microsoft-Server-ActiveSync = { + protectedBy = ""; + actionClass = "SOGoMicrosoftActiveSyncActions"; + actionName = "microsoftServerActiveSync"; + }; casProxy = { protectedBy = ""; pageName = "SOGoRootPage"; @@ -229,9 +233,5 @@ }; }; }; - // SOGoCustomGroupFolder = { - // methods = { - // }; - // }; }; } diff --git a/UI/PreferencesUI/Arabic.lproj/Localizable.strings b/UI/PreferencesUI/Arabic.lproj/Localizable.strings index 5b27b560a..3b526b597 100644 --- a/UI/PreferencesUI/Arabic.lproj/Localizable.strings +++ b/UI/PreferencesUI/Arabic.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "التقويم الشخصي"; "firstCalendar" = "أول تقويم ممكن"; -"reminderTime_0000" = "0 دقيقة"; -"reminderTime_0005" = "5 دقائق"; -"reminderTime_0010" = "10 دقائق"; -"reminderTime_0015" = "15 دقيقة"; -"reminderTime_0030" = "30 دقيقة"; -"reminderTime_0100" = "1 ساعة"; -"reminderTime_0200" = "2 ساعة"; -"reminderTime_0400" = "4 ساعات"; -"reminderTime_0800" = "8 ساعات"; -"reminderTime_1200" = "1/2 يوم"; -"reminderTime_2400" = "1 يوم"; -"reminderTime_4800" = "2 يوم"; +"reminder_5_MINUTES_BEFORE" = "5 دقائق"; +"reminder_10_MINUTES_BEFORE" = "10 دقائق"; +"reminder_15_MINUTES_BEFORE" = "15 دقيقة"; +"reminder_30_MINUTES_BEFORE" = "30 دقيقة"; +"reminder_1_HOUR_BEFORE" = "1 ساعة"; +"reminder_2_HOURS_BEFORE" = "2 ساعة"; +"reminder_5_HOURS_BEFORE"= "5 ساعات"; +"reminder_15_HOURS_BEFORE"= "15 ساعات"; +"reminder_1_DAY_BEFORE" = "1 يوم"; +"reminder_2_DAYS_BEFORE" = "2 يوم"; /* Mailer */ +"Label" = "ملصق"; "Show subscribed mailboxes only" = "عرض صناديق البريد المشترك بها فقط"; "Sort messages by threads" = "رتب الرسائل حسب المواضيع"; "Check for new mail:" = "تحقق من وجود بريد جديد:"; diff --git a/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings b/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings index d5eaccafb..933d6250f 100644 --- a/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Calendário pessoal"; "firstCalendar" = "Calendário habilizado pela primeira vez"; -"reminderTime_0000" = "0 minutos"; -"reminderTime_0005" = "5 minutos"; -"reminderTime_0010" = "10 minutos"; -"reminderTime_0015" = "15 minutos"; -"reminderTime_0030" = "30 minutos"; -"reminderTime_0100" = "1 hora"; -"reminderTime_0200" = "2 horas"; -"reminderTime_0400" = "4 horas"; -"reminderTime_0800" = "8 horas"; -"reminderTime_1200" = "1/2 dia"; -"reminderTime_2400" = "1 dia"; -"reminderTime_4800" = "2 dias"; +"reminder_5_MINUTES_BEFORE" = "5 minutos"; +"reminder_10_MINUTES_BEFORE" = "10 minutos"; +"reminder_15_MINUTES_BEFORE" = "15 minutos"; +"reminder_30_MINUTES_BEFORE" = "30 minutos"; +"reminder_1_HOUR_BEFORE" = "1 hora"; +"reminder_2_HOURS_BEFORE" = "2 horas"; +"reminder_5_HOURS_BEFORE"= "5 horas"; +"reminder_15_HOURS_BEFORE"= "15 horas"; +"reminder_1_DAY_BEFORE" = "1 dia"; +"reminder_2_DAYS_BEFORE" = "2 dias"; /* Mailer */ +"Label" = "Etiqueta"; "Show subscribed mailboxes only" = "Exibir somente caixas de correio inscritas"; "Sort messages by threads" = "Ordenar mensagens por tópicos"; "Check for new mail:" = "Checar novos emails:"; diff --git a/UI/PreferencesUI/Catalan.lproj/Localizable.strings b/UI/PreferencesUI/Catalan.lproj/Localizable.strings index 784b5b41c..2668c029b 100644 --- a/UI/PreferencesUI/Catalan.lproj/Localizable.strings +++ b/UI/PreferencesUI/Catalan.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Calendari personal"; "firstCalendar" = "Primer calendari habilitat"; -"reminderTime_0000" = "0 minuts"; -"reminderTime_0005" = "5 minuts"; -"reminderTime_0010" = "10 minuts"; -"reminderTime_0015" = "15 minuts"; -"reminderTime_0030" = "30 minuts"; -"reminderTime_0100" = "1 hora"; -"reminderTime_0200" = "2 hores"; -"reminderTime_0400" = "4 hores"; -"reminderTime_0800" = "8 hores"; -"reminderTime_1200" = "1/2 dia"; -"reminderTime_2400" = "1 dia"; -"reminderTime_4800" = "2 dies"; +"reminder_5_MINUTES_BEFORE" = "5 minuts"; +"reminder_10_MINUTES_BEFORE" = "10 minuts"; +"reminder_15_MINUTES_BEFORE" = "15 minuts"; +"reminder_30_MINUTES_BEFORE" = "30 minuts"; +"reminder_1_HOUR_BEFORE" = "1 hora"; +"reminder_2_HOURS_BEFORE" = "2 hores"; +"reminder_5_HOURS_BEFORE"= "5 hores"; +"reminder_15_HOURS_BEFORE"= "15 hores"; +"reminder_1_DAY_BEFORE" = "1 dia"; +"reminder_2_DAYS_BEFORE" = "2 dies"; /* Mailer */ +"Label" = "Etiquetar"; "Show subscribed mailboxes only" = "Mostrar només les bústies subscrites"; "Sort messages by threads" = "Ordenar els missatges per temes"; "Check for new mail:" = "Comprovar nou correu: "; diff --git a/UI/PreferencesUI/Czech.lproj/Localizable.strings b/UI/PreferencesUI/Czech.lproj/Localizable.strings index a0d3e9c2b..792dcab92 100644 --- a/UI/PreferencesUI/Czech.lproj/Localizable.strings +++ b/UI/PreferencesUI/Czech.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Osobní kalendář"; "firstCalendar" = "První kalendář"; -"reminderTime_0000" = "0 minut"; -"reminderTime_0005" = "5 minut"; -"reminderTime_0010" = "10 minut"; -"reminderTime_0015" = "15 minut"; -"reminderTime_0030" = "30 minut"; -"reminderTime_0100" = "1 hodina"; -"reminderTime_0200" = "2 hodiny"; -"reminderTime_0400" = "4 hodiny"; -"reminderTime_0800" = "8 hodin"; -"reminderTime_1200" = "1/2 dne"; -"reminderTime_2400" = "1 den"; -"reminderTime_4800" = "2 dny"; +"reminder_5_MINUTES_BEFORE" = "5 minut"; +"reminder_10_MINUTES_BEFORE" = "10 minut"; +"reminder_15_MINUTES_BEFORE" = "15 minut"; +"reminder_30_MINUTES_BEFORE" = "30 minut"; +"reminder_1_HOUR_BEFORE" = "1 hodina"; +"reminder_2_HOURS_BEFORE" = "2 hodiny"; +"reminder_5_HOURS_BEFORE"= "5 hodiny"; +"reminder_15_HOURS_BEFORE"= "15 hodin"; +"reminder_1_DAY_BEFORE" = "1 den"; +"reminder_2_DAYS_BEFORE" = "2 dny"; /* Mailer */ +"Label" = "Označkovat"; "Show subscribed mailboxes only" = "Ukázat pouze odebírané mailové schránky"; "Sort messages by threads" = "Třídit zprávy podle souvislostí"; "Check for new mail:" = "Zkontrolovat novou poštu:"; diff --git a/UI/PreferencesUI/Danish.lproj/Localizable.strings b/UI/PreferencesUI/Danish.lproj/Localizable.strings index 90d420a71..ad2b8b98c 100644 --- a/UI/PreferencesUI/Danish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Danish.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Personlig kalender"; "firstCalendar" = "Første aktiveret kalender"; -"reminderTime_0000" = "0 minutter"; -"reminderTime_0005" = "5 minutter"; -"reminderTime_0010" = "10 minutter"; -"reminderTime_0015" = "15 minutter"; -"reminderTime_0030" = "30 minutter"; -"reminderTime_0100" = "1 time"; -"reminderTime_0200" = "2 timer"; -"reminderTime_0400" = "4 timer"; -"reminderTime_0800" = "8 timer"; -"reminderTime_1200" = "1/2 dag"; -"reminderTime_2400" = "1 dag"; -"reminderTime_4800" = "2 dage"; +"reminder_5_MINUTES_BEFORE" = "5 minutter"; +"reminder_10_MINUTES_BEFORE" = "10 minutter"; +"reminder_15_MINUTES_BEFORE" = "15 minutter"; +"reminder_30_MINUTES_BEFORE" = "30 minutter"; +"reminder_1_HOUR_BEFORE" = "1 time"; +"reminder_2_HOURS_BEFORE" = "2 timer"; +"reminder_5_HOURS_BEFORE"= "5 timer"; +"reminder_15_HOURS_BEFORE"= "15 timer"; +"reminder_1_DAY_BEFORE" = "1 dag"; +"reminder_2_DAYS_BEFORE" = "2 dage"; /* Mailer */ +"Label" = "Mærke"; "Show subscribed mailboxes only" = "Vis kun abonnerede postkasser"; "Sort messages by threads" = "Sortér beskeder efter tråd"; "Check for new mail:" = "Tjek for ny post:"; diff --git a/UI/PreferencesUI/Dutch.lproj/Localizable.strings b/UI/PreferencesUI/Dutch.lproj/Localizable.strings index 45c345516..e74ce41f5 100644 --- a/UI/PreferencesUI/Dutch.lproj/Localizable.strings +++ b/UI/PreferencesUI/Dutch.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Persoonlijke agenda"; "firstCalendar" = "Eerste ingeschakelde agenda"; -"reminderTime_0000" = "0 minuten"; -"reminderTime_0005" = "5 minuten"; -"reminderTime_0010" = "10 minuten"; -"reminderTime_0015" = "15 minuten"; -"reminderTime_0030" = "30 minuten"; -"reminderTime_0100" = "1 uur"; -"reminderTime_0200" = "2 uren"; -"reminderTime_0400" = "4 uren"; -"reminderTime_0800" = "8 uren"; -"reminderTime_1200" = "1/2 dag"; -"reminderTime_2400" = "1 dag"; -"reminderTime_4800" = "2 dagen"; +"reminder_5_MINUTES_BEFORE" = "5 minuten"; +"reminder_10_MINUTES_BEFORE" = "10 minuten"; +"reminder_15_MINUTES_BEFORE" = "15 minuten"; +"reminder_30_MINUTES_BEFORE" = "30 minuten"; +"reminder_1_HOUR_BEFORE" = "1 uur"; +"reminder_2_HOURS_BEFORE" = "2 uren"; +"reminder_5_HOURS_BEFORE"= "5 uren"; +"reminder_15_HOURS_BEFORE"= "15 uren"; +"reminder_1_DAY_BEFORE" = "1 dag"; +"reminder_2_DAYS_BEFORE" = "2 dagen"; /* Mailer */ +"Label" = "Labelen"; "Show subscribed mailboxes only" = "Toon alleen geabonneerde postvakken"; "Sort messages by threads" = "Berichten sorteren op threads"; "Check for new mail:" = "Controleren op nieuwe mail:"; diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index d739522c9..afa832c3e 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -111,20 +111,23 @@ "personalCalendar" = "Personal calendar"; "firstCalendar" = "First enabled calendar"; -"reminderTime_0000" = "0 minutes"; -"reminderTime_0005" = "5 minutes"; -"reminderTime_0010" = "10 minutes"; -"reminderTime_0015" = "15 minutes"; -"reminderTime_0030" = "30 minutes"; -"reminderTime_0100" = "1 hour"; -"reminderTime_0200" = "2 hours"; -"reminderTime_0400" = "4 hours"; -"reminderTime_0800" = "8 hours"; -"reminderTime_1200" = "1/2 day"; -"reminderTime_2400" = "1 day"; -"reminderTime_4800" = "2 days"; +"reminder_NONE" = "No reminder"; +"reminder_5_MINUTES_BEFORE" = "5 minutes before"; +"reminder_10_MINUTES_BEFORE" = "10 minutes before"; +"reminder_15_MINUTES_BEFORE" = "15 minutes before"; +"reminder_30_MINUTES_BEFORE" = "30 minutes before"; +"reminder_45_MINUTES_BEFORE" = "45 minutes before"; +"reminder_1_HOUR_BEFORE" = "1 hour before"; +"reminder_2_HOURS_BEFORE" = "2 hours before"; +"reminder_5_HOURS_BEFORE" = "5 hours before"; +"reminder_15_HOURS_BEFORE" = "15 hours before"; +"reminder_1_DAY_BEFORE" = "1 day before"; +"reminder_2_DAYS_BEFORE" = "2 days before"; +"reminder_1_WEEK_BEFORE" = "1 week before"; /* Mailer */ +"Labels" = "Labels"; +"Label" = "Label"; "Show subscribed mailboxes only" = "Show subscribed mailboxes only"; "Sort messages by threads" = "Sort messages by threads"; "Check for new mail:" = "Check for new mail:"; @@ -262,6 +265,7 @@ "To or Cc" = "To or Cc"; "Size (Kb)" = "Size (Kb)"; "Header" = "Header"; +"Body" = "Body"; "Flag the message with:" = "Flag the message with:"; "Discard the message" = "Discard the message"; "File the message in:" = "File the message in:"; @@ -288,11 +292,6 @@ "Flagged" = "Flagged"; "Junk" = "Junk"; "Not Junk" = "Not Junk"; -"Important" = "Important"; -"Work" = "Work"; -"Personal" = "Personal"; -"To Do" = "To Do"; -"Later" = "Later"; /* Password policy */ "The password was changed successfully." = "The password was changed successfully."; diff --git a/UI/PreferencesUI/Finnish.lproj/Localizable.strings b/UI/PreferencesUI/Finnish.lproj/Localizable.strings index b4a9a7261..d30f5cfe8 100644 --- a/UI/PreferencesUI/Finnish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Finnish.lproj/Localizable.strings @@ -111,20 +111,23 @@ "personalCalendar" = "Henkilökohtainen kalenteri"; "firstCalendar" = "Ensimmäinen käyttöönotettu kalenteri"; -"reminderTime_0000" = "0 minuuttia"; -"reminderTime_0005" = "5 minuuttia"; -"reminderTime_0010" = "10 minuuttia"; -"reminderTime_0015" = "15 minuuttia"; -"reminderTime_0030" = "30 minuuttia"; -"reminderTime_0100" = "1 tunti"; -"reminderTime_0200" = "2 tuntia"; -"reminderTime_0400" = "4 tuntia"; -"reminderTime_0800" = "8 tuntia"; -"reminderTime_1200" = "puoli päivää"; -"reminderTime_2400" = "1 päivä"; -"reminderTime_4800" = "2 päivää"; +"reminder_NONE" = "Ei muistutusta"; +"reminder_5_MINUTES_BEFORE" = "5 minuuttia"; +"reminder_10_MINUTES_BEFORE" = "10 minuuttia"; +"reminder_15_MINUTES_BEFORE" = "15 minuuttia"; +"reminder_30_MINUTES_BEFORE" = "30 minuuttia"; +"reminder_45_MINUTES_BEFORE" = "45 minuuttia ennen"; +"reminder_1_HOUR_BEFORE" = "1 tunti"; +"reminder_2_HOURS_BEFORE" = "2 tuntia"; +"reminder_5_HOURS_BEFORE" = "5 tuntia"; +"reminder_15_HOURS_BEFORE" = "15 tuntia"; +"reminder_1_DAY_BEFORE" = "1 päivä"; +"reminder_2_DAYS_BEFORE" = "2 päivää"; +"reminder_1_WEEK_BEFORE" = "Viikkoa ennen"; /* Mailer */ +"Labels" = "Otsikot"; +"Label" = "Otsikoi"; "Show subscribed mailboxes only" = "Näytä vain tilatut sähköpostikansiot"; "Sort messages by threads" = "Järjestä viestit ketjuiksi"; "Check for new mail:" = "Hae uudet sähköpostit"; @@ -288,12 +291,8 @@ "Flagged" = "Merkitty"; "Junk" = "Roskaa"; "Not Junk" = "Ei roskaa"; -"Label 1" = "Merkki 1"; -"Label 2" = "Merkki 2"; -"Label 3" = "Merkki 3"; -"Label 4" = "Merkki 4"; -"Label 5" = "Merkki 5"; +/* Password policy */ "The password was changed successfully." = "Salasana vaihdettu onnistuneesti."; "Password must not be empty." = "Salasana ei saa olla tyhjä"; "The passwords do not match. Please try again." = "Salasanat eivät täsmää. Ole hyvä ja yritä uudelleen."; @@ -306,4 +305,4 @@ "Unhandled policy error: %{0}" = "Käsittelemätön sääntövirhe: %{0} "; "Unhandled error response" = "Käsittelemätön virhevastaus"; "Password change is not supported." = "Salasanan vaihto ei ole tuettu."; -"Unhandled HTTP error code: %{0}" = "Käsittelämätön HTTP virhekoodi: %{0}"; +"Unhandled HTTP error code: %{0}" = "Käsittelämätön HTTP virhekoodi: %{0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index 73c301fea..b3d49aab2 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -32,7 +32,7 @@ = "Veuillez définir un message et vos adresses pour lesquelles pour désirez activer une réponse automatique."; "Your vacation message must not end with a single dot on a line." = "Le message de vacances ne doit pas se terminer par une ligne ne contenant qu'un point."; "End date of your auto reply must be in the future." -= "La date de fin de la réponse automatique doit être dans le future."; += "La date de fin de la réponse automatique doit être dans le futur."; /* forward messages */ "Forward incoming messages" = "Transférer les messages entrant"; @@ -111,20 +111,23 @@ "personalCalendar" = "le calendrier personnel"; "firstCalendar" = "le premier calendrier actif"; -"reminderTime_0000" = "0 minutes"; -"reminderTime_0005" = "5 minutes"; -"reminderTime_0010" = "10 minutes"; -"reminderTime_0015" = "15 minutes"; -"reminderTime_0030" = "30 minutes"; -"reminderTime_0100" = "1 heure"; -"reminderTime_0200" = "2 heures"; -"reminderTime_0400" = "4 heures"; -"reminderTime_0800" = "8 heures"; -"reminderTime_1200" = "1/2 journée"; -"reminderTime_2400" = "1 journée"; -"reminderTime_4800" = "2 journées"; +"reminder_NONE" = "Pas de rappel"; +"reminder_5_MINUTES_BEFORE" = "5 minutes avant"; +"reminder_10_MINUTES_BEFORE" = "10 minutes avant"; +"reminder_15_MINUTES_BEFORE" = "15 minutes avant"; +"reminder_30_MINUTES_BEFORE" = "30 minutes avant"; +"reminder_45_MINUTES_BEFORE" = "45 minutes avant"; +"reminder_1_HOUR_BEFORE" = "1 heure avant"; +"reminder_2_HOURS_BEFORE" = "2 heures avant"; +"reminder_5_HOURS_BEFORE" = "5 heures avant"; +"reminder_15_HOURS_BEFORE" = "15 heures avant"; +"reminder_1_DAY_BEFORE" = "1 jour avant"; +"reminder_2_DAYS_BEFORE" = "2 jours avant"; +"reminder_1_WEEK_BEFORE" = "1 semaine avant"; /* Mailer */ +"Labels" = "Étiquettes"; +"Label" = "Étiquette"; "Show subscribed mailboxes only" = "Afficher seulement les abonnements"; "Sort messages by threads" = "Grouper les discussions"; "Check for new mail:" = "Vérifier l'arrivée de messages :"; @@ -262,6 +265,7 @@ "To or Cc" = "\"Dest.\" ou \"Copie à\""; "Size (Kb)" = "Taille (Ko)"; "Header" = "En-tête"; +"Body" = "Contenu"; "Flag the message with:" = "Marquer le message comme :"; "Discard the message" = "Annuler le message"; "File the message in:" = "Placer le message dans :"; @@ -288,12 +292,8 @@ "Flagged" = "marqué"; "Junk" = "spam"; "Not Junk" = "non spam"; -"Label 1" = "étiquette 1"; -"Label 2" = "étiquette 2"; -"Label 3" = "étiquette 3"; -"Label 4" = "étiquette 4"; -"Label 5" = "étiquette 5"; +/* Password policy */ "The password was changed successfully." = "Votre mot de passe a bien été changé."; "Password must not be empty." = "Le mot de passe ne doit pas être vide."; "The passwords do not match. Please try again." = "Les mots de passe ne sont pas identiques. Essayez de nouveau."; @@ -306,4 +306,4 @@ "Unhandled policy error: %{0}" = "Erreur inconnue pour le ppolicy: %{0}"; "Unhandled error response" = "Erreur inconnue"; "Password change is not supported." = "Changement de mot de passe non-supporté."; -"Unhandled HTTP error code: %{0}" = "Code HTTP non-géré: %{0}"; +"Unhandled HTTP error code: %{0}" = "Code HTTP non-géré: %{0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/German.lproj/Localizable.strings b/UI/PreferencesUI/German.lproj/Localizable.strings index ce5de69ce..d00f5ac26 100644 --- a/UI/PreferencesUI/German.lproj/Localizable.strings +++ b/UI/PreferencesUI/German.lproj/Localizable.strings @@ -111,20 +111,23 @@ "personalCalendar" = "Persönlicher Kalender"; "firstCalendar" = "Erster eingeschalteter Kalender"; -"reminderTime_0000" = "0 Minuten"; -"reminderTime_0005" = "5 Minuten"; -"reminderTime_0010" = "10 Minuten"; -"reminderTime_0015" = "15 Minuten"; -"reminderTime_0030" = "30 Minuten"; -"reminderTime_0100" = "1 Stunde"; -"reminderTime_0200" = "2 Stunden"; -"reminderTime_0400" = "4 Stunden"; -"reminderTime_0800" = "8 Stunden"; -"reminderTime_1200" = "1/2 Tag"; -"reminderTime_2400" = "1 Tag"; -"reminderTime_4800" = "2 Tage"; +"reminder_NONE" = "Keine Erinnerung"; +"reminder_5_MINUTES_BEFORE" = "5 Minuten vorher"; +"reminder_10_MINUTES_BEFORE" = "10 Minuten vorher"; +"reminder_15_MINUTES_BEFORE" = "15 Minuten vorher"; +"reminder_30_MINUTES_BEFORE" = "30 Minuten vorher"; +"reminder_45_MINUTES_BEFORE" = "45 Minuten vorher"; +"reminder_1_HOUR_BEFORE" = "1 Stunde vorher"; +"reminder_2_HOURS_BEFORE" = "2 Stunden vorher"; +"reminder_5_HOURS_BEFORE" = "5 Stunden vorher"; +"reminder_15_HOURS_BEFORE" = "15 Stunden vorher"; +"reminder_1_DAY_BEFORE" = "1 Tag vorher"; +"reminder_2_DAYS_BEFORE" = "2 Tage vorher"; +"reminder_1_WEEK_BEFORE" = "1 Woche vorher"; /* Mailer */ +"Labels" = "Bezeichnungen"; +"Label" = "Schlagwort"; "Show subscribed mailboxes only" = "Nur abonnierte Ordner anzeigen"; "Sort messages by threads" = "Nachrichten nach Thema sortieren"; "Check for new mail:" = "Auf neue Nachrichten prüfen:"; @@ -200,7 +203,7 @@ "Contacts" = "Adressbuch"; "Mail" = "E-Mail"; "Last" = "Zuletzt benutztes"; -"Default module :" = "Vorgegebenes Modul:"; +"Default module :" = "Standard Modul:"; "Language :" = "Sprache :"; "choose" = "Auswählen ..."; @@ -262,6 +265,7 @@ "To or Cc" = "An oder CC"; "Size (Kb)" = "Größe (KB)"; "Header" = "Header"; +"Body" = "Inhalt"; "Flag the message with:" = "Markiere die Nachricht als:"; "Discard the message" = "Verwerfe die Nachricht"; "File the message in:" = "Verschiebe die Nachricht nach:"; @@ -288,22 +292,18 @@ "Flagged" = "Markiert"; "Junk" = "Junk"; "Not Junk" = "Kein Junk"; -"Label 1" = "Label 1"; -"Label 2" = "Label 2"; -"Label 3" = "Label 3"; -"Label 4" = "Label 4"; -"Label 5" = "Label 5"; +/* Password policy */ "The password was changed successfully." = "Das Passwort wurde erfolgreich geändert."; "Password must not be empty." = "Das Passwort darf nicht leer sein."; "The passwords do not match. Please try again." = "Die Passwörter stimmen nicht überein. Bitte versuchen Sie es noch einmal."; -"Password change failed" = "Passwortänderung fehlgeschlagen."; +"Password change failed" = "Passwortänderung fehlgeschlagen"; "Password change failed - Permission denied" = "Passwortänderung fehlgeschlagen - Zugriff verweigert"; "Password change failed - Insufficient password quality" = "Passwortänderung fehlgeschlagen - Passwortqualität unzureichend"; "Password change failed - Password is too short" = "Passwortänderung fehlgeschlagen - Passwort ist zu kurz"; -"Password change failed - Password is too young" = "Passwortänderung fehlgeschlagen - Passwortänderung ist zu früh"; -"Password change failed - Password is in history" = "Passwortänderung fehlgeschlagen - Passwort existiert bereits in der Historie"; +"Password change failed - Password is too young" = "Passwortänderung fehlgeschlagen - zu frühe Passwortänderung"; +"Password change failed - Password is in history" = "Passwortänderung fehlgeschlagen - Passwort wurde zuvor verwendet"; "Unhandled policy error: %{0}" = "Unbearbeiteter Vorgabenfehler: %{0}"; "Unhandled error response" = "Unbearbeitete Fehlerantwort"; "Password change is not supported." = "Passwortänderung wird nicht unterstützt."; -"Unhandled HTTP error code: %{0}" = "Unbearbeiteter HTTP-Fehlercode: %{0}"; +"Unhandled HTTP error code: %{0}" = "Unbearbeiteter HTTP-Fehlercode: %{0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings index 26af9a277..a5fb50826 100644 --- a/UI/PreferencesUI/Hungarian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Hungarian.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Személyes naptár"; "firstCalendar" = "Első engedélyezett naptár"; -"reminderTime_0000" = "0 perc"; -"reminderTime_0005" = "5 perc"; -"reminderTime_0010" = "10 perc"; -"reminderTime_0015" = "15 perc"; -"reminderTime_0030" = "30 perc"; -"reminderTime_0100" = "1 óra"; -"reminderTime_0200" = "2 óra"; -"reminderTime_0400" = "4 óra"; -"reminderTime_0800" = "8 óra"; -"reminderTime_1200" = "1/2 nap"; -"reminderTime_2400" = "1 nap"; -"reminderTime_4800" = "2 nap"; +"reminder_5_MINUTES_BEFORE" = "5 perc"; +"reminder_10_MINUTES_BEFORE" = "10 perc"; +"reminder_15_MINUTES_BEFORE" = "15 perc"; +"reminder_30_MINUTES_BEFORE" = "30 perc"; +"reminder_1_HOUR_BEFORE" = "1 óra"; +"reminder_2_HOURS_BEFORE" = "2 óra"; +"reminder_5_HOURS_BEFORE"= "5 óra"; +"reminder_15_HOURS_BEFORE"= "15 óra"; +"reminder_1_DAY_BEFORE" = "1 nap"; +"reminder_2_DAYS_BEFORE" = "2 nap"; /* Mailer */ +"Label" = "Cimke"; "Show subscribed mailboxes only" = "Csak azok a fiókok mutatása, amelyre feliratkozott"; "Sort messages by threads" = "Üzenetek beszélgetések szerinti rendezése "; "Check for new mail:" = "Új üzenetek letöltése:"; diff --git a/UI/PreferencesUI/Icelandic.lproj/Localizable.strings b/UI/PreferencesUI/Icelandic.lproj/Localizable.strings index 31e569cb9..c4b496ecd 100644 --- a/UI/PreferencesUI/Icelandic.lproj/Localizable.strings +++ b/UI/PreferencesUI/Icelandic.lproj/Localizable.strings @@ -92,20 +92,19 @@ "firstWeekOfYear_First4DayWeek" = "Fyrstu 4 dagar viku"; "firstWeekOfYear_FirstFullWeek" = "Fyrsta heila vika"; -"reminderTime_0000" = "0 mínútur"; -"reminderTime_0005" = "5 mínútur"; -"reminderTime_0010" = "10 mínútur"; -"reminderTime_0015" = "15 mínútur"; -"reminderTime_0030" = "30 mínútur"; -"reminderTime_0100" = "1 klst."; -"reminderTime_0200" = "2 klst."; -"reminderTime_0400" = "4 klst."; -"reminderTime_0800" = "8 klst."; -"reminderTime_1200" = "1/2 dagur"; -"reminderTime_2400" = "1 dagur"; -"reminderTime_4800" = "2 dagar"; +"reminder_5_MINUTES_BEFORE" = "5 mínútur"; +"reminder_10_MINUTES_BEFORE" = "10 mínútur"; +"reminder_15_MINUTES_BEFORE" = "15 mínútur"; +"reminder_30_MINUTES_BEFORE" = "30 mínútur"; +"reminder_1_HOUR_BEFORE" = "1 klst."; +"reminder_2_HOURS_BEFORE" = "2 klst."; +"reminder_5_HOURS_BEFORE"= "5 klst."; +"reminder_15_HOURS_BEFORE"= "15 klst."; +"reminder_1_DAY_BEFORE" = "1 dagur"; +"reminder_2_DAYS_BEFORE" = "2 dagar"; /* Mailer */ +"Label" = "Merki"; "Show subscribed mailboxes only" = "Sýna aðeins pósthólf sem eru í áskrift"; "Check for new mail:" = "Sækja nýjan póst:"; "messagecheck_manually" = "Handvirkt"; diff --git a/UI/PreferencesUI/Italian.lproj/Localizable.strings b/UI/PreferencesUI/Italian.lproj/Localizable.strings index d6daf9208..546109f36 100644 --- a/UI/PreferencesUI/Italian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Italian.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Calendario personale"; "firstCalendar" = "Primo calendario abilitato"; -"reminderTime_0000" = "0 minuti"; -"reminderTime_0005" = "5 minuti"; -"reminderTime_0010" = "10 minuti"; -"reminderTime_0015" = "15 minuti"; -"reminderTime_0030" = "30 minuti"; -"reminderTime_0100" = "1 ora"; -"reminderTime_0200" = "2 ore"; -"reminderTime_0400" = "4 ore"; -"reminderTime_0800" = "8 ore"; -"reminderTime_1200" = "1/2 giornata"; -"reminderTime_2400" = "1 giorno"; -"reminderTime_4800" = "2 giorni"; +"reminder_5_MINUTES_BEFORE" = "5 minuti"; +"reminder_10_MINUTES_BEFORE" = "10 minuti"; +"reminder_15_MINUTES_BEFORE" = "15 minuti"; +"reminder_30_MINUTES_BEFORE" = "30 minuti"; +"reminder_1_HOUR_BEFORE" = "1 ora"; +"reminder_2_HOURS_BEFORE" = "2 ore"; +"reminder_5_HOURS_BEFORE"= "5 ore"; +"reminder_15_HOURS_BEFORE"= "15 ore"; +"reminder_1_DAY_BEFORE" = "1 giorno"; +"reminder_2_DAYS_BEFORE" = "2 giorni"; /* Mailer */ +"Label" = "Etichetta"; "Show subscribed mailboxes only" = "Mostra solo le cartelle sottoscritte"; "Sort messages by threads" = "Ordina i messaggi per conversazione"; "Check for new mail:" = "Controlla la posta in arrivo:"; diff --git a/UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings b/UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings index 08a03d1f2..221db266c 100644 --- a/UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings +++ b/UI/PreferencesUI/NorwegianBokmal.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Personlig kalender"; "firstCalendar" = "Første aktiverte kalender"; -"reminderTime_0000" = "0 minutter"; -"reminderTime_0005" = "5 minutter"; -"reminderTime_0010" = "10 minutter"; -"reminderTime_0015" = "15 minutter"; -"reminderTime_0030" = "30 minutter"; -"reminderTime_0100" = "1 time"; -"reminderTime_0200" = "2 timer"; -"reminderTime_0400" = "4 timer"; -"reminderTime_0800" = "8 timer"; -"reminderTime_1200" = "1/2 dag"; -"reminderTime_2400" = "1 dag"; -"reminderTime_4800" = "2 dager"; +"reminder_5_MINUTES_BEFORE" = "5 minutter"; +"reminder_10_MINUTES_BEFORE" = "10 minutter"; +"reminder_15_MINUTES_BEFORE" = "15 minutter"; +"reminder_30_MINUTES_BEFORE" = "30 minutter"; +"reminder_1_HOUR_BEFORE" = "1 time"; +"reminder_2_HOURS_BEFORE" = "2 timer"; +"reminder_5_HOURS_BEFORE"= "5 timer"; +"reminder_15_HOURS_BEFORE"= "15 timer"; +"reminder_1_DAY_BEFORE" = "1 dag"; +"reminder_2_DAYS_BEFORE" = "2 dager"; /* Mailer */ +"Label" = "Etikett"; "Show subscribed mailboxes only" = "Vis bare abonnerte postbokser"; "Sort messages by threads" = "Sorter meldinger etter tråder"; "Check for new mail:" = "Hent ny post:"; diff --git a/UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings b/UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings index 8e97a7694..718b758bc 100644 --- a/UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/PreferencesUI/NorwegianNynorsk.lproj/Localizable.strings @@ -98,20 +98,19 @@ "personalCalendar" = "Personal calendar"; "firstCalendar" = "First enabled calendar"; -"reminderTime_0000" = "0 minutter"; -"reminderTime_0005" = "5 minutter"; -"reminderTime_0010" = "10 minutter"; -"reminderTime_0015" = "15 minutter"; -"reminderTime_0030" = "30 minutter"; -"reminderTime_0100" = "1 time"; -"reminderTime_0200" = "2 timer"; -"reminderTime_0400" = "4 timer"; -"reminderTime_0800" = "8 timer"; -"reminderTime_1200" = "1/2 dag"; -"reminderTime_2400" = "1 dag"; -"reminderTime_4800" = "2 dager"; +"reminder_5_MINUTES_BEFORE" = "5 minutter"; +"reminder_10_MINUTES_BEFORE" = "10 minutter"; +"reminder_15_MINUTES_BEFORE" = "15 minutter"; +"reminder_30_MINUTES_BEFORE" = "30 minutter"; +"reminder_1_HOUR_BEFORE" = "1 time"; +"reminder_2_HOURS_BEFORE" = "2 timer"; +"reminder_5_HOURS_BEFORE"= "5 timer"; +"reminder_15_HOURS_BEFORE"= "15 timer"; +"reminder_1_DAY_BEFORE" = "1 dag"; +"reminder_2_DAYS_BEFORE" = "2 dager"; /* Mailer */ +"Label" = "Etikett"; "Show subscribed mailboxes only" = "Vis bare abonnerte postbokser"; "Sort messages by threads" = "Sort messages by threads"; "Check for new mail:" = "Hent ny post:"; diff --git a/UI/PreferencesUI/Polish.lproj/Localizable.strings b/UI/PreferencesUI/Polish.lproj/Localizable.strings index 0a6a8433f..788db6408 100644 --- a/UI/PreferencesUI/Polish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Polish.lproj/Localizable.strings @@ -111,20 +111,23 @@ "personalCalendar" = "Kalendarz osobisty"; "firstCalendar" = "Pierwszy kalendarz"; -"reminderTime_0000" = "0 minut"; -"reminderTime_0005" = "5 minut"; -"reminderTime_0010" = "10 minut"; -"reminderTime_0015" = "15 minut"; -"reminderTime_0030" = "30 minut"; -"reminderTime_0100" = "1 godzina"; -"reminderTime_0200" = "2 godziny"; -"reminderTime_0400" = "4 godziny"; -"reminderTime_0800" = "8 godzin"; -"reminderTime_1200" = "1/2 dnia"; -"reminderTime_2400" = "1 dzień"; -"reminderTime_4800" = "2 dni"; +"reminder_NONE" = "Nie przypominaj"; +"reminder_5_MINUTES_BEFORE" = "5 minut wcześniej"; +"reminder_10_MINUTES_BEFORE" = "10 minut wcześniej"; +"reminder_15_MINUTES_BEFORE" = "15 minut wcześniej"; +"reminder_30_MINUTES_BEFORE" = "30 minut wcześniej"; +"reminder_45_MINUTES_BEFORE" = "45 minut wcześniej"; +"reminder_1_HOUR_BEFORE" = "1 godzinę wcześniej"; +"reminder_2_HOURS_BEFORE" = "2 godziny wcześniej"; +"reminder_5_HOURS_BEFORE" = "5 godzin wcześniej"; +"reminder_15_HOURS_BEFORE" = "15 godzin wcześniej"; +"reminder_1_DAY_BEFORE" = "1 dzień wcześniej"; +"reminder_2_DAYS_BEFORE" = "2 dni wcześniej"; +"reminder_1_WEEK_BEFORE" = "1 tydzień wcześniej"; /* Mailer */ +"Labels" = "Etykiety"; +"Label" = "Etykieta"; "Show subscribed mailboxes only" = "Pokaż tylko subskrybowane konta pocztowe"; "Sort messages by threads" = "Sortuj wiadomości według wątków"; "Check for new mail:" = "Sprawdzaj nowe wiadomości:"; @@ -262,6 +265,7 @@ "To or Cc" = "Do lub DW"; "Size (Kb)" = "Rozmiar (KB)"; "Header" = "Nagłówek"; +"Body" = "Treść"; "Flag the message with:" = "Oflaguj wiadomość:"; "Discard the message" = "Odrzuć wiadomość"; "File the message in:" = "Zapisz wiadomość w:"; @@ -288,12 +292,8 @@ "Flagged" = "Oflagowane"; "Junk" = "Śmieć"; "Not Junk" = "Nie śmieć"; -"Label 1" = "Etykieta 1"; -"Label 2" = "Etykieta 2"; -"Label 3" = "Etykieta 3"; -"Label 4" = "Etykieta 4"; -"Label 5" = "Etykieta 5"; +/* Password policy */ "The password was changed successfully." = "Hasło zostało zmienione."; "Password must not be empty." = "Hasło nie może być puste."; "The passwords do not match. Please try again." = "Hasła nie są takie same. Spróbuj ponownie."; @@ -306,4 +306,4 @@ "Unhandled policy error: %{0}" = "Nieznany błąd: %{0}"; "Unhandled error response" = "Nieznany błąd"; "Password change is not supported." = "Zmiana hasła jest nieobsługiwana."; -"Unhandled HTTP error code: %{0}" = "Nieznany kod błędu HTTP: %{0}"; +"Unhandled HTTP error code: %{0}" = "Nieznany kod błędu HTTP: %{0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/Russian.lproj/Localizable.strings b/UI/PreferencesUI/Russian.lproj/Localizable.strings index 2b64bb9e9..29a9a2137 100644 --- a/UI/PreferencesUI/Russian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Russian.lproj/Localizable.strings @@ -75,9 +75,9 @@ "longDateFmt_1" = "%e %b. %Y"; "longDateFmt_2" = "%e %B %Y"; "longDateFmt_3" = "%A, %B %d, %Y"; -"longDateFmt_4" = "%B %d, %Y"; -"longDateFmt_5" = "%A, %d %B, %Y"; -"longDateFmt_6" = "%d %B, %Y"; +"longDateFmt_4" = ""; +"longDateFmt_5" = ""; +"longDateFmt_6" = ""; "longDateFmt_7" = ""; "longDateFmt_8" = ""; "longDateFmt_9" = ""; @@ -85,8 +85,8 @@ "timeFmt_0" = "%H:%M"; "timeFmt_1" = "%H.%M"; -"timeFmt_2" = "%H h. %M"; -"timeFmt_3" = "%I:%M %p"; +"timeFmt_2" = ""; +"timeFmt_3" = ""; "timeFmt_4" = ""; /* calendar */ @@ -111,20 +111,23 @@ "personalCalendar" = "Персональный календарь"; "firstCalendar" = "Первый разрешенный календарь"; -"reminderTime_0000" = "0 минут"; -"reminderTime_0005" = "5 минут"; -"reminderTime_0010" = "10 минут"; -"reminderTime_0015" = "15 минут"; -"reminderTime_0030" = "30 минут"; -"reminderTime_0100" = "1 час"; -"reminderTime_0200" = "2 часа"; -"reminderTime_0400" = "4 часа"; -"reminderTime_0800" = "8 часов"; -"reminderTime_1200" = "1/2дня"; -"reminderTime_2400" = "1 день"; -"reminderTime_4800" = "2 дня"; +"reminder_NONE" = "Нет напоминания"; +"reminder_5_MINUTES_BEFORE" = "5 минут"; +"reminder_10_MINUTES_BEFORE" = "10 минут"; +"reminder_15_MINUTES_BEFORE" = "15 минут"; +"reminder_30_MINUTES_BEFORE" = "30 минут"; +"reminder_45_MINUTES_BEFORE" = "За 45 минут"; +"reminder_1_HOUR_BEFORE" = "1 час"; +"reminder_2_HOURS_BEFORE" = "2 часа"; +"reminder_5_HOURS_BEFORE" = "5 часа"; +"reminder_15_HOURS_BEFORE" = "15 часов"; +"reminder_1_DAY_BEFORE" = "1 день"; +"reminder_2_DAYS_BEFORE" = "2 дня"; +"reminder_1_WEEK_BEFORE" = "За 1 неделю"; /* Mailer */ +"Labels" = "Метки"; +"Label" = "Метка"; "Show subscribed mailboxes only" = "Показывать только почтовые ящики, на которые подписан"; "Sort messages by threads" = "Сортировать сообщения по нитям"; "Check for new mail:" = "Проверять новую почту:"; @@ -262,6 +265,7 @@ "To or Cc" = "To or Cc"; "Size (Kb)" = "Size (Kb)"; "Header" = "Header"; +"Body" = "Тело письма"; "Flag the message with:" = "Пометить сообщение с:"; "Discard the message" = "Уничтожить сообщение"; "File the message in:" = "Сохранить сообщение в:"; @@ -288,12 +292,8 @@ "Flagged" = "Помечено флагом"; "Junk" = "Спам"; "Not Junk" = "Не спам"; -"Label 1" = "Label 1"; -"Label 2" = "Label 2"; -"Label 3" = "Label 3"; -"Label 4" = "Label 4"; -"Label 5" = "Label 5"; +/* Password policy */ "The password was changed successfully." = "Пароль был успешно изменен."; "Password must not be empty." = "Пароль не должен быть пустым"; "The passwords do not match. Please try again." = "Пароли не совпадают. Пожалуйста попробуйте заново."; @@ -306,4 +306,4 @@ "Unhandled policy error: %{0}" = "Unhandled policy error: %{0}"; "Unhandled error response" = "Unhandled error response"; "Password change is not supported." = "Изменение пароля не поддерживается"; -"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; +"Unhandled HTTP error code: %{0}" = "Unhandled HTTP error code: %{0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/Slovak.lproj/Localizable.strings b/UI/PreferencesUI/Slovak.lproj/Localizable.strings index 65726f2df..0f4cd14d5 100644 --- a/UI/PreferencesUI/Slovak.lproj/Localizable.strings +++ b/UI/PreferencesUI/Slovak.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Osobný kalendár"; "firstCalendar" = "Prvý zapnutý kalendár"; -"reminderTime_0000" = "0 minút"; -"reminderTime_0005" = "5 minút"; -"reminderTime_0010" = "10 minút"; -"reminderTime_0015" = "15 minút"; -"reminderTime_0030" = "30 minút"; -"reminderTime_0100" = "1 hodina"; -"reminderTime_0200" = "2 hodiny"; -"reminderTime_0400" = "4 hodiny"; -"reminderTime_0800" = "8 hodín"; -"reminderTime_1200" = "1/2 dňa"; -"reminderTime_2400" = "1 deň"; -"reminderTime_4800" = "2 dni"; +"reminder_5_MINUTES_BEFORE" = "5 minút"; +"reminder_10_MINUTES_BEFORE" = "10 minút"; +"reminder_15_MINUTES_BEFORE" = "15 minút"; +"reminder_30_MINUTES_BEFORE" = "30 minút"; +"reminder_1_HOUR_BEFORE" = "1 hodina"; +"reminder_2_HOURS_BEFORE" = "2 hodiny"; +"reminder_5_HOURS_BEFORE"= "5 hodiny"; +"reminder_15_HOURS_BEFORE"= "15 hodín"; +"reminder_1_DAY_BEFORE" = "1 deň"; +"reminder_2_DAYS_BEFORE" = "2 dni"; /* Mailer */ +"Label" = "Štítok"; "Show subscribed mailboxes only" = "Ukazuj iba odoberané účty"; "Sort messages by threads" = "Zoraď správy do konverzácií"; "Check for new mail:" = "Kontrola nových mailov:"; diff --git a/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings b/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings index b287a970a..703c33c31 100644 --- a/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings +++ b/UI/PreferencesUI/SpanishArgentina.lproj/Localizable.strings @@ -111,20 +111,19 @@ "personalCalendar" = "Calendario personal"; "firstCalendar" = "Primer calendario habilitado"; -"reminderTime_0000" = "0 minutos"; -"reminderTime_0005" = "5 minutos"; -"reminderTime_0010" = "10 minutos"; -"reminderTime_0015" = "15 minutos"; -"reminderTime_0030" = "30 minutos"; -"reminderTime_0100" = "1 hora"; -"reminderTime_0200" = "2 horas"; -"reminderTime_0400" = "4 horas"; -"reminderTime_0800" = "8 horas"; -"reminderTime_1200" = "1/2 día"; -"reminderTime_2400" = "1 día"; -"reminderTime_4800" = "2 días"; +"reminder_5_MINUTES_BEFORE" = "5 minutos"; +"reminder_10_MINUTES_BEFORE" = "10 minutos"; +"reminder_15_MINUTES_BEFORE" = "15 minutos"; +"reminder_30_MINUTES_BEFORE" = "30 minutos"; +"reminder_1_HOUR_BEFORE" = "1 hora"; +"reminder_2_HOURS_BEFORE" = "2 horas"; +"reminder_5_HOURS_BEFORE"= "5 horas"; +"reminder_15_HOURS_BEFORE"= "15 horas"; +"reminder_1_DAY_BEFORE" = "1 día"; +"reminder_2_DAYS_BEFORE" = "2 días"; /* Mailer */ +"Label" = "Etiquetar"; "Show subscribed mailboxes only" = "Mostrar sólo buzones suscritos"; "Sort messages by threads" = "Ordenar mensajes por conversaciones"; "Check for new mail:" = "Comprobar si hay nuevos correos: "; diff --git a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings index 1f42bb17c..efd2588e4 100644 --- a/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings +++ b/UI/PreferencesUI/SpanishSpain.lproj/Localizable.strings @@ -111,20 +111,23 @@ "personalCalendar" = "Calendario personal"; "firstCalendar" = "Primer calendario disponible"; -"reminderTime_0000" = "0 minutos"; -"reminderTime_0005" = "5 minutos"; -"reminderTime_0010" = "10 minutos"; -"reminderTime_0015" = "15 minutos"; -"reminderTime_0030" = "30 minutos"; -"reminderTime_0100" = "1 hora"; -"reminderTime_0200" = "2 horas"; -"reminderTime_0400" = "4 horas"; -"reminderTime_0800" = "8 horas"; -"reminderTime_1200" = "1/2 día"; -"reminderTime_2400" = "1 día"; -"reminderTime_4800" = "2 días"; +"reminder_NONE" = "Sin recordatorio"; +"reminder_5_MINUTES_BEFORE" = "5 minutos"; +"reminder_10_MINUTES_BEFORE" = "10 minutos"; +"reminder_15_MINUTES_BEFORE" = "15 minutos"; +"reminder_30_MINUTES_BEFORE" = "30 minutos"; +"reminder_45_MINUTES_BEFORE" = "45 minutos antes"; +"reminder_1_HOUR_BEFORE" = "1 hora"; +"reminder_2_HOURS_BEFORE" = "2 horas"; +"reminder_5_HOURS_BEFORE" = "5 horas"; +"reminder_15_HOURS_BEFORE" = "15 horas"; +"reminder_1_DAY_BEFORE" = "1 día"; +"reminder_2_DAYS_BEFORE" = "2 días"; +"reminder_1_WEEK_BEFORE" = "1 semana antes"; /* Mailer */ +"Labels" = "Etiquetas"; +"Label" = "Etiquetar"; "Show subscribed mailboxes only" = "Mostrar sólo buzones suscritos"; "Sort messages by threads" = "Ordenar mensajes por temas"; "Check for new mail:" = "Comprobar correo nuevo: "; @@ -262,6 +265,7 @@ "To or Cc" = "Para o Cc"; "Size (Kb)" = "Tamaño (Kb)"; "Header" = "Cabecera"; +"Body" = "Cuerpo"; "Flag the message with:" = "Marca el mensaje con:"; "Discard the message" = "Descarta el mensaje"; "File the message in:" = "Archiva el mensaje en:"; @@ -288,12 +292,8 @@ "Flagged" = "Marcado"; "Junk" = "Spam"; "Not Junk" = "No es Spam"; -"Label 1" = "Etiqueta 1"; -"Label 2" = "Etiqueta 2"; -"Label 3" = "Etiqueta 3"; -"Label 4" = "Etiqueta 4"; -"Label 5" = "Etiqueta 5"; +/* Password policy */ "The password was changed successfully." = "La contraseña se ha cambiado correctamente."; "Password must not be empty." = "La contraseña no puede estar vacía."; "The passwords do not match. Please try again." = "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."; @@ -306,4 +306,4 @@ "Unhandled policy error: %{0}" = "Error de la política no controlada:% {0}"; "Unhandled error response" = "Respuesta de error no controlado"; "Password change is not supported." = "Cambio de contraseña no compatible."; -"Unhandled HTTP error code: %{0}" = "Código de error HTTP no controlada:% {0}"; +"Unhandled HTTP error code: %{0}" = "Código de error HTTP no controlada:% {0}"; \ No newline at end of file diff --git a/UI/PreferencesUI/Swedish.lproj/Localizable.strings b/UI/PreferencesUI/Swedish.lproj/Localizable.strings index c1e981dbe..73e4b0282 100644 --- a/UI/PreferencesUI/Swedish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Swedish.lproj/Localizable.strings @@ -98,20 +98,19 @@ "personalCalendar" = "Personal calendar"; "firstCalendar" = "First enabled calendar"; -"reminderTime_0000" = "0 minuter"; -"reminderTime_0005" = "5 minuter"; -"reminderTime_0010" = "10 minuter"; -"reminderTime_0015" = "15 minuter"; -"reminderTime_0030" = "30 minuter"; -"reminderTime_0100" = "1 timme"; -"reminderTime_0200" = "2 timmar"; -"reminderTime_0400" = "4 timmar"; -"reminderTime_0800" = "8 timmar"; -"reminderTime_1200" = "1/2 dag"; -"reminderTime_2400" = "1 dag"; -"reminderTime_4800" = "2 dagar"; +"reminder_5_MINUTES_BEFORE" = "5 minuter"; +"reminder_10_MINUTES_BEFORE" = "10 minuter"; +"reminder_15_MINUTES_BEFORE" = "15 minuter"; +"reminder_30_MINUTES_BEFORE" = "30 minuter"; +"reminder_1_HOUR_BEFORE" = "1 timme"; +"reminder_2_HOURS_BEFORE" = "2 timmar"; +"reminder_5_HOURS_BEFORE"= "5 timmar"; +"reminder_15_HOURS_BEFORE"= "15 timmar"; +"reminder_1_DAY_BEFORE" = "1 dag"; +"reminder_2_DAYS_BEFORE" = "2 dagar"; /* Mailer */ +"Label" = "Etikett"; "Show subscribed mailboxes only" = "Visa endast prenumrerade postlådor"; "Sort messages by threads" = "Sort messages by threads"; "Check for new mail:" = "Hämta ny post:"; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 7b082f622..63798b11d 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -28,6 +28,7 @@ #import #import +#import #import #import @@ -41,6 +42,7 @@ #import #import #import +#import #import #import #import @@ -58,11 +60,55 @@ workweek = from -> to identities */ +static NSArray *reminderItems = nil; +static NSArray *reminderValues = nil; + @implementation UIxPreferences ++ (void) initialize +{ + if (!reminderItems && !reminderValues) + { + reminderItems = [NSArray arrayWithObjects: + @"5_MINUTES_BEFORE", + @"10_MINUTES_BEFORE", + @"15_MINUTES_BEFORE", + @"30_MINUTES_BEFORE", + @"45_MINUTES_BEFORE", + @"-", + @"1_HOUR_BEFORE", + @"2_HOURS_BEFORE", + @"5_HOURS_BEFORE", + @"15_HOURS_BEFORE", + @"-", + @"1_DAY_BEFORE", + @"2_DAYS_BEFORE", + @"1_WEEK_BEFORE", + nil]; + reminderValues = [NSArray arrayWithObjects: + @"-PT5M", + @"-PT10M", + @"-PT15M", + @"-PT30M", + @"-PT45M", + @"", + @"-PT1H", + @"-PT2H", + @"-PT5H", + @"-PT15H", + @"", + @"-P1D", + @"-P2D", + @"-P1W", + nil]; + + [reminderItems retain]; + [reminderValues retain]; + } +} + - (id) init { - //NSDictionary *locale; SOGoDomainDefaults *dd; if ((self = [super init])) @@ -485,6 +531,54 @@ return [userDefaults calendarTasksDefaultClassification]; } +- (NSArray *) reminderList +{ + return reminderItems; +} + +- (NSString *) itemReminderText +{ + NSString *text; + + if ([item isEqualToString: @"-"]) + text = item; + else + text = [self labelForKey: [NSString stringWithFormat: @"reminder_%@", item]]; + + return text; +} + +- (void) setReminder: (NSString *) theReminder +{ + NSString *value; + int index; + + index = NSNotFound; + value = @"NONE"; + + if (theReminder && [theReminder caseInsensitiveCompare: @"-"] != NSOrderedSame) + index = [reminderItems indexOfObject: theReminder]; + + if (index != NSNotFound) + value = [reminderValues objectAtIndex: index]; + + [userDefaults setCalendarDefaultReminder: value]; +} + +- (NSString *) reminder +{ + NSString *value; + int index; + + value = [userDefaults calendarDefaultReminder]; + index = [reminderValues indexOfObject: value]; + + if (index != NSNotFound) + return [reminderItems objectAtIndex: index]; + + return @"NONE"; +} + - (NSArray *) hoursList { static NSMutableArray *hours = nil; @@ -558,58 +652,6 @@ [userDefaults setFirstWeekOfYear: newFirstWeek]; } -- (BOOL) reminderEnabled -{ - return [userDefaults reminderEnabled]; -} - -- (void) setReminderEnabled: (BOOL) newValue -{ - [userDefaults setReminderEnabled: newValue]; -} - -- (BOOL) remindWithASound -{ - return [userDefaults remindWithASound]; -} - -- (void) setRemindWithASound: (BOOL) newValue -{ - [userDefaults setRemindWithASound: newValue]; -} - -- (NSArray *) reminderTimesList -{ - static NSArray *reminderTimesList = nil; - - if (!reminderTimesList) - { - reminderTimesList = [NSArray arrayWithObjects: @"0000", @"0005", - @"0010", @"0015", @"0030", @"0100", - @"0200", @"0400", @"0800", @"1200", - @"2400", @"4800", nil]; - [reminderTimesList retain]; - } - - return reminderTimesList; -} - -- (NSString *) itemReminderTimeText -{ - return [self labelForKey: - [NSString stringWithFormat: @"reminderTime_%@", item]]; -} - -- (NSString *) userReminderTime -{ - return [userDefaults reminderTime]; -} - -- (void) setReminderTime: (NSString *) newTime -{ - [userDefaults setReminderTime: newTime]; -} - /* Mailer */ - (void) setShowSubscribedFoldersOnly: (BOOL) showSubscribedFoldersOnly { @@ -797,13 +839,23 @@ { #warning sieve caps should be deduced from the server static NSArray *capabilities = nil; + SOGoMailAccounts *folder; + SOGoMailAccount *account; + SOGoSieveManager *manager; + NGSieveClient *client; if (!capabilities) { - capabilities = [NSArray arrayWithObjects: @"fileinto", @"reject", - @"envelope", @"vacation", @"imapflags", - @"notify", @"subaddress", @"relational", - @"comparator-i;ascii-numeric", @"regex", nil]; + folder = [[self clientObject] mailAccountsFolder: @"Mail" + inContext: context]; + account = [folder lookupName: @"0" inContext: context acquire: NO]; + manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; + client = [manager clientForAccount: account]; + + if (client) + capabilities = [client capabilities]; + else + capabilities = [NSArray array]; [capabilities retain]; } @@ -1196,7 +1248,7 @@ { NSDictionary *v; - v = [[[context activeUser] userDefaults] mailLabelsColors]; + v = [[[context activeUser] userDefaults] mailLabelsColors]; ASSIGN(mailLabels, [SOGoMailLabel labelsFromDefaults: v component: self]); } diff --git a/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings b/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings index 8a8ba55e5..ead74f31c 100644 --- a/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings @@ -110,20 +110,19 @@ "personalCalendar" = "Особистий календар"; "firstCalendar" = "Перший активний календар"; -"reminderTime_0000" = "0 хвилин"; -"reminderTime_0005" = "5 хвилин"; -"reminderTime_0010" = "10 хвилин"; -"reminderTime_0015" = "15 хвилин"; -"reminderTime_0030" = "30 хвилин"; -"reminderTime_0100" = "1 година"; -"reminderTime_0200" = "2 години"; -"reminderTime_0400" = "4 години"; -"reminderTime_0800" = "8 годин"; -"reminderTime_1200" = "пів дня"; -"reminderTime_2400" = "1 день"; -"reminderTime_4800" = "2 дні"; +"reminder_5_MINUTES_BEFORE" = "5 хвилин"; +"reminder_10_MINUTES_BEFORE" = "10 хвилин"; +"reminder_15_MINUTES_BEFORE" = "15 хвилин"; +"reminder_30_MINUTES_BEFORE" = "30 хвилин"; +"reminder_1_HOUR_BEFORE" = "1 година"; +"reminder_2_HOURS_BEFORE" = "2 години"; +"reminder_5_HOURS_BEFORE"= "5 години"; +"reminder_15_HOURS_BEFORE"= "15 годин"; +"reminder_1_DAY_BEFORE" = "1 день"; +"reminder_2_DAYS_BEFORE" = "2 дні"; /* Mailer */ +"Label" = "Позначка"; "Show subscribed mailboxes only" = "Показувати лише поштові скриньки, на які я підписаний"; "Sort messages by threads" = "Сортувати повідомлення за гілками"; "Check for new mail:" = "Перевіряти нову пошту:"; diff --git a/UI/PreferencesUI/Welsh.lproj/Localizable.strings b/UI/PreferencesUI/Welsh.lproj/Localizable.strings index 425195c15..fd7dc78a1 100644 --- a/UI/PreferencesUI/Welsh.lproj/Localizable.strings +++ b/UI/PreferencesUI/Welsh.lproj/Localizable.strings @@ -98,20 +98,19 @@ "personalCalendar" = "Personal calendar"; "firstCalendar" = "First enabled calendar"; -"reminderTime_0000" = "0 munud"; -"reminderTime_0005" = "5 munud"; -"reminderTime_0010" = "10 munud"; -"reminderTime_0015" = "15 munud"; -"reminderTime_0030" = "30 munud"; -"reminderTime_0100" = "1 awr"; -"reminderTime_0200" = "2 awr"; -"reminderTime_0400" = "4 awr"; -"reminderTime_0800" = "8 awr"; -"reminderTime_1200" = "1/2 diwrnod"; -"reminderTime_2400" = "1 diwrnod"; -"reminderTime_4800" = "2 ddiwrnod"; +"reminder_5_MINUTES_BEFORE" = "5 munud"; +"reminder_10_MINUTES_BEFORE" = "10 munud"; +"reminder_15_MINUTES_BEFORE" = "15 munud"; +"reminder_30_MINUTES_BEFORE" = "30 munud"; +"reminder_1_HOUR_BEFORE" = "1 awr"; +"reminder_2_HOURS_BEFORE" = "2 awr"; +"reminder_5_HOURS_BEFORE"= "5 awr"; +"reminder_15_HOURS_BEFORE"= "15 awr"; +"reminder_1_DAY_BEFORE" = "1 diwrnod"; +"reminder_2_DAYS_BEFORE" = "2 ddiwrnod"; /* Mailer */ +"Label" = "Label"; "Show subscribed mailboxes only" = "Show subscribed mailboxes only"; "Sort messages by threads" = "Sort messages by threads"; "Check for new mail:" = "Chwilio am ebost newydd:"; diff --git a/UI/SOGoElements/SOGoIEConditional.h b/UI/SOGoElements/SOGoIEConditional.h index e59ed63ac..23779f800 100644 --- a/UI/SOGoElements/SOGoIEConditional.h +++ b/UI/SOGoElements/SOGoIEConditional.h @@ -1,8 +1,8 @@ /* SOGoIEConditional.h - this file is part of SOGo * - * Copyright (C) 2007 Inverse inc. + * Copyright (C) 2007-2013 Inverse inc. * - * Author: Wolfgang Sourdeau + * Author: Inverse * * 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 @@ -32,6 +32,7 @@ @interface SOGoIEConditional : WODynamicElement { WOElement *template; + WOAssociation *lte; // int } - (void) appendToResponse: (WOResponse *) _response diff --git a/UI/SOGoElements/SOGoIEConditional.m b/UI/SOGoElements/SOGoIEConditional.m index e3ac46bab..09f156e48 100644 --- a/UI/SOGoElements/SOGoIEConditional.m +++ b/UI/SOGoElements/SOGoIEConditional.m @@ -1,8 +1,8 @@ /* SOGoIEConditional.m - this file is part of SOGo * - * Copyright (C) 2007 Inverse inc. + * Copyright (C) 2007-2013 Inverse inc. * - * Author: Wolfgang Sourdeau + * Author: Inverse * * 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 @@ -22,6 +22,7 @@ #import #import +#import #import "SOGoIEConditional.h" @@ -34,7 +35,10 @@ if ((self = [super initWithName: name associations: associations template: newTemplate])) - ASSIGN (template, newTemplate); + { + ASSIGN (template, newTemplate); + lte = OWGetProperty(associations, @"lte"); + } return self; } @@ -42,13 +46,21 @@ - (void) dealloc { [template release]; + [lte release]; [super dealloc]; } - (void) appendToResponse: (WOResponse *) response inContext: (WOContext *) context { - [response appendContentString: @""]; } diff --git a/UI/Scheduler/Arabic.lproj/Localizable.strings b/UI/Scheduler/Arabic.lproj/Localizable.strings index 6fc599a80..1d4b86057 100644 --- a/UI/Scheduler/Arabic.lproj/Localizable.strings +++ b/UI/Scheduler/Arabic.lproj/Localizable.strings @@ -411,8 +411,9 @@ validate_endbeforestart = "تاريخ الانتهاء الذي أدخلته "Workweek days only" = "ايام العمل الأسبوعية فقط"; "Tasks in View" = "المهمات في العرض"; -"eventDeleteConfirmation" = "الحدث (الأحداث) الآتية ستُمحى: \n%{0}\nهل تريد المتابعة؟"; -"taskDeleteConfirmation" = "المهمة (المهام) التالية ستمحى: \n %{0}\n هل تريد المتابعة؟"; +"eventDeleteConfirmation" = "الحدث (الأحداث) الآتية ستُمحى:"; +"taskDeleteConfirmation" = "المهمة (المهام) التالية ستمحى:"; +"Would you like to continue?" = " هل تريد المتابعة؟"; "You cannot remove nor unsubscribe from your personal calendar." = "لا يمكنك حذف أو إلغاء اشتراكك من تقويمك الشخصي."; diff --git a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings index 73e31e23a..a2c92dfe9 100644 --- a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "A data que você informou ocorre antes da data ini "Workweek days only" = "Somente semanas úteis"; "Tasks in View" = "Tarefas na vista"; -"eventDeleteConfirmation" = "O(s) seguinte(s) evento(s) será(ão) apagado(s): \n%{0}\nGostaria de continuar?"; -"taskDeleteConfirmation" = "Apagar permanentemente esta tarefa.\nVocê gostaria de continuar?"; +"eventDeleteConfirmation" = "O(s) seguinte(s) evento(s) será(ão) apagado(s):"; +"taskDeleteConfirmation" = "Apagar permanentemente esta tarefa."; +"Would you like to continue?" = "Gostaria de continuar?"; "You cannot remove nor unsubscribe from your personal calendar." = "Você não pode remover nem retirar-se do seu calendário pessoal."; diff --git a/UI/Scheduler/Catalan.lproj/Localizable.strings b/UI/Scheduler/Catalan.lproj/Localizable.strings index 0acc5a3f6..030dd4644 100644 --- a/UI/Scheduler/Catalan.lproj/Localizable.strings +++ b/UI/Scheduler/Catalan.lproj/Localizable.strings @@ -411,8 +411,9 @@ validate_endbeforestart = "La data/hora de començament és posterior a la d' "Workweek days only" = "Només dies laborables"; "Tasks in View" = "Mostrar tasques"; -"eventDeleteConfirmation" = "El següent esdeveniment (s) s'esborrarà: \n%{0}\nVoleu continuar?"; -"taskDeleteConfirmation" = "Aquesta tasca s'esborrarà definitivament. Voleu continuar?"; +"eventDeleteConfirmation" = "El següent esdeveniment (s) s'esborrarà:"; +"taskDeleteConfirmation" = "Aquesta tasca s'esborrarà definitivament."; +"Would you like to continue?" = "Voleu continuar?"; "You cannot remove nor unsubscribe from your personal calendar." = "No podeu cancel·lar la subscripció al calendari personal o esborrar-lo."; diff --git a/UI/Scheduler/Czech.lproj/Localizable.strings b/UI/Scheduler/Czech.lproj/Localizable.strings index d625e8210..4b0688657 100644 --- a/UI/Scheduler/Czech.lproj/Localizable.strings +++ b/UI/Scheduler/Czech.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "Zadané datum konce je před začátkem události. "Workweek days only" = "Pouze pracovní dny"; "Tasks in View" = "Zobrazené úkoly"; -"eventDeleteConfirmation" = "Tato událost(i) bude smazána:\n%{0}\nChcete pokračovat?"; -"taskDeleteConfirmation" = "Smazání tohoto úkolu je permanentní.\nChcete pokračovat?"; +"eventDeleteConfirmation" = "Tato událost(i) bude smazána:"; +"taskDeleteConfirmation" = "Smazání tohoto úkolu je permanentní."; +"Would you like to continue?" = "Chcete pokračovat?"; "You cannot remove nor unsubscribe from your personal calendar." = "Nemůžete odebrat nebo se odhlásit z odebírání svého vlastního kalendáře."; diff --git a/UI/Scheduler/Danish.lproj/Localizable.strings b/UI/Scheduler/Danish.lproj/Localizable.strings index 6a70c7038..f3141ffa5 100644 --- a/UI/Scheduler/Danish.lproj/Localizable.strings +++ b/UI/Scheduler/Danish.lproj/Localizable.strings @@ -411,8 +411,9 @@ validate_endbeforestart = "Indtastet slutdato ligger før startdato."; "Workweek days only" = "Arbejdsuge dage kun"; "Tasks in View" = "Opgaver i visning"; -"eventDeleteConfirmation" = "Følgende begivenhed er/bliver slettet: \n%{0}\nFortsæt?"; -"taskDeleteConfirmation" = "Følgende opgave(r) bliver slettet: \n%{0}\nFortsæt?"; +"eventDeleteConfirmation" = "Følgende begivenhed er/bliver slettet:"; +"taskDeleteConfirmation" = "Følgende opgave(r) bliver slettet:"; +"Would you like to continue?" = "Fortsæt?"; "You cannot remove nor unsubscribe from your personal calendar." = "Du kan ikke fjerne eller afmelde din personlige kalender."; diff --git a/UI/Scheduler/Dutch.lproj/Localizable.strings b/UI/Scheduler/Dutch.lproj/Localizable.strings index 6c0695653..5dbfad68b 100644 --- a/UI/Scheduler/Dutch.lproj/Localizable.strings +++ b/UI/Scheduler/Dutch.lproj/Localizable.strings @@ -416,8 +416,8 @@ validate_endbeforestart = "Het einde is voor de begindatum."; "Workweek days only" = "Alleen werkdagen weergeven"; "Tasks in View" = "Taken in binnen het zicht"; -"eventDeleteConfirmation" = "Weet u zeker dat u de volgende afspraken wilt verwijderen?\n%{0}"; -"taskDeleteConfirmation" = "Weet u zeker dat u de volgende taken wilt verwijderen?\n%{0}"; +"eventDeleteConfirmation" = "Weet u zeker dat u de volgende afspraken wilt verwijderen?"; +"taskDeleteConfirmation" = "Weet u zeker dat u de volgende taken wilt verwijderen?"; "You cannot remove nor unsubscribe from your personal calendar." = "U kunt niet uw persoonlijke agenda verwijderen of opzeggen."; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index 6378e645e..900fa9614 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "The end date that you entered occurs before the st "Workweek days only" = "Workweek days only"; "Tasks in View" = "Tasks in View"; -"eventDeleteConfirmation" = "The following event(s) will be erased: \n%{0}\nWould you like to continue?"; -"taskDeleteConfirmation" = "The following task(s) will be erased: \n%{0}\nWould you like to continue?"; +"eventDeleteConfirmation" = "The following event(s) will be erased:"; +"taskDeleteConfirmation" = "The following task(s) will be erased:"; +"Would you like to continue?" = "Would you like to continue?"; "You cannot remove nor unsubscribe from your personal calendar." = "You cannot remove nor unsubscribe from your personal calendar."; diff --git a/UI/Scheduler/Finnish.lproj/Localizable.strings b/UI/Scheduler/Finnish.lproj/Localizable.strings index de789251f..71a41a7cc 100644 --- a/UI/Scheduler/Finnish.lproj/Localizable.strings +++ b/UI/Scheduler/Finnish.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "Syöttämäsi loppupäivä on ennen alkupäivää. "Workweek days only" = "Vain työpäivät"; "Tasks in View" = "Tehtävät näkymässä"; -"eventDeleteConfirmation" = "Seuraava(t) tapahtuma(t) poistetaan: ⏎ %{0}⏎ Jatketaanko?"; -"taskDeleteConfirmation" = "Seuraava(t) tehtävä(t) poistetaan: ⏎ %{0}⏎ Jatketaanko?"; +"eventDeleteConfirmation" = "Seuraava(t) tapahtuma(t) poistetaan:"; +"taskDeleteConfirmation" = "Seuraava(t) tehtävä(t) poistetaan:"; +"Would you like to continue?" = "Haluatko jatkaa?"; "You cannot remove nor unsubscribe from your personal calendar." = "Et voi poistaa tai kirjautua ulos henkilökohtaisesta kalenteristasi."; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index cb81c3d21..4a044df33 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "La date de fin est avant la date de début."; "Workweek days only" = "Semaine de travail seulement"; "Tasks in View" = "Afficher les tâches"; -"eventDeleteConfirmation" = "Le ou les événements suivants seront supprimés :\n%{0}\nVoulez-vous continuer?"; -"taskDeleteConfirmation" = "Le ou les tâches suivantes seront supprimées :\n%{0}\nVoulez-vous continuer?"; +"eventDeleteConfirmation" = "Le ou les événements suivants seront supprimés :"; +"taskDeleteConfirmation" = "Le ou les tâches suivantes seront supprimées :"; +"Would you like to continue?" = "Voulez-vous continuer?"; "You cannot remove nor unsubscribe from your personal calendar." = "Vous ne pouvez pas supprimer ni vous désabonner de votre agenda personnel."; diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index 06f2c7bea..cfbad6e7f 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -163,7 +163,7 @@ "% complete" = "% fertig"; "Location:" = "Ort: "; "Priority:" = "Priorität: "; -"Privacy" = "Datenschutz"; +"Privacy" = "Vertraulichkeit"; "Cycle" = "Wiederholen"; "Cycle End" = "Wiederholungsende"; "Categories" = "Kategorien"; @@ -210,7 +210,7 @@ /* Appointments (error messages) */ -"Conflicts found!" = "Ein order mehrere Konflikte wurden gefunden!"; +"Conflicts found!" = "Es wurden Konflikte gefunden!"; "Invalid iCal data!" = "Ungültige iCal-Daten!"; "Could not create iCal data!" = "iCal-Daten konnten nicht erstellt werden!"; @@ -292,7 +292,7 @@ "cycle_end_never" = "Unendlich oft wiederholen"; "cycle_end_until" = "Wiederholen bis :"; -"Recurrence pattern" = "Wiederholungsschema"; +"Recurrence pattern" = "Wiederholungsmuster"; "Range of recurrence" = "Bereich der Wiederholung"; "Repeat" = "Wiederhole"; @@ -416,8 +416,9 @@ validate_endbeforestart = "Ihr Ende ist vor dem Beginndatum."; "Workweek days only" = "nur Arbeitstage"; "Tasks in View" = "Aufgaben anzeigen"; -"eventDeleteConfirmation" = "Diese Termine werden gelöscht: \n%{0}\nFortfahren?"; -"taskDeleteConfirmation" = "Diese Aufgaben werden gelöscht: \n%{0}\nFortfahren?"; +"eventDeleteConfirmation" = "Die folgenden Termine werden gelöscht:"; +"taskDeleteConfirmation" = "Die folgenden Aufgaben werden gelöscht:"; +"Would you like to continue?" = "Möchten Sie fortfahren?"; "You cannot remove nor unsubscribe from your personal calendar." = "Der persönliche Kalender kann weder gelöscht noch abbestellt werden."; diff --git a/UI/Scheduler/Hungarian.lproj/Localizable.strings b/UI/Scheduler/Hungarian.lproj/Localizable.strings index 96965355e..24f2382fb 100644 --- a/UI/Scheduler/Hungarian.lproj/Localizable.strings +++ b/UI/Scheduler/Hungarian.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "A megadott befejező dátum korábbi, mint a kezd "Workweek days only" = "Csak hétköznapok"; "Tasks in View" = "Feladatok megjelenítése"; -"eventDeleteConfirmation" = "Az alábbi esemény(eke)t törli: \n%{0}\nFolytatja?"; -"taskDeleteConfirmation" = "A feladat törlése végleges.\nFolytatja?"; +"eventDeleteConfirmation" = "Az alábbi esemény(eke)t törli:"; +"taskDeleteConfirmation" = "A feladat törlése végleges."; +"Would you like to continue?" = "Folytatja?"; "You cannot remove nor unsubscribe from your personal calendar." = "Nem törölhet, valamint nem iratkozhat le egy személyes naptárról."; diff --git a/UI/Scheduler/Icelandic.lproj/Localizable.strings b/UI/Scheduler/Icelandic.lproj/Localizable.strings index b9c599c28..499ed3c80 100644 --- a/UI/Scheduler/Icelandic.lproj/Localizable.strings +++ b/UI/Scheduler/Icelandic.lproj/Localizable.strings @@ -409,8 +409,9 @@ validate_endbeforestart = "Lokadagurinn sem er tilgreindur, er fyrr en byrjun "Workweek days only" = "Aðeins vinnudagar"; "Tasks in View" = "Sýnd verkefni"; -"eventDeleteConfirmation" = "Eftirfarandi viðburði/viðburðum verður eytt: \n%{0}\nViltu halda áfram?"; -"taskDeleteConfirmation" = "Eftirfarandi verkefni/verkefnum verður eytt: \n%{0}\nViltu halda áfram?"; +"eventDeleteConfirmation" = "Eftirfarandi viðburði/viðburðum verður eytt:"; +"taskDeleteConfirmation" = "Eftirfarandi verkefni/verkefnum verður eytt:"; +"Would you like to continue?" = "Viltu halda áfram?"; "You cannot remove nor unsubscribe from your personal calendar." = "Ekki er hægt að fjarlægja eða segja upp áskrift að sínu eigin persónulega dagatali."; diff --git a/UI/Scheduler/Italian.lproj/Localizable.strings b/UI/Scheduler/Italian.lproj/Localizable.strings index 45bb915a4..3ca6154f9 100644 --- a/UI/Scheduler/Italian.lproj/Localizable.strings +++ b/UI/Scheduler/Italian.lproj/Localizable.strings @@ -410,8 +410,9 @@ validate_endbeforestart = "La data finale specificata è precedente alla data "Workweek days only" = "Solo giorni lavorativi"; "Tasks in View" = "Attività in elenco"; -"eventDeleteConfirmation" = "Il seguente evento(i) sarà cancellato: \n%{0}\nVuoi continuare?"; -"taskDeleteConfirmation" = "Stai per cancellare in maniera permanente il l'attività.\nVuoi procedere?"; +"eventDeleteConfirmation" = "Il seguente evento(i) sarà cancellato:"; +"taskDeleteConfirmation" = "Stai per cancellare in maniera permanente il l'attività."; +"Would you like to continue?" = "Vuoi procedere?"; "You cannot remove nor unsubscribe from your personal calendar." = "Non puoi rimuovere la sottoscrizione del tuo calendario personale."; diff --git a/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings b/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings index 7c4713516..244420adc 100644 --- a/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings +++ b/UI/Scheduler/NorwegianBokmal.lproj/Localizable.strings @@ -414,8 +414,9 @@ validate_endbeforestart = "Angitt sluttdato inntreffer før angitt startdato. "Workweek days only" = "Bare arbeidsdager"; "Tasks in View" = "Oppgaver i visning"; -"eventDeleteConfirmation" = "Sletting av hendelsen er permanent.\nVil du fortsette?"; -"taskDeleteConfirmation" = "Sletting av oppgaven er permanent.\nVil du fortsette?"; +"eventDeleteConfirmation" = "Sletting av hendelsen er permanent."; +"taskDeleteConfirmation" = "Sletting av oppgaven er permanent."; +"Would you like to continue?" = "Vil du fortsette?"; "You cannot remove nor unsubscribe from your personal calendar." = "Du kan ikke slette eller avbryte abonnement på en personlig kalender."; diff --git a/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings b/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings index 8535f2a60..e5ead80b3 100644 --- a/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings +++ b/UI/Scheduler/NorwegianNynorsk.lproj/Localizable.strings @@ -409,8 +409,9 @@ validate_endbeforestart = "Angitt sluttdato inntreffer før angitt startdato. "Workweek days only" = "Bare arbeidsdager"; "Tasks in View" = "Oppgaver i visning"; -"eventDeleteConfirmation" = "Sletting av hendelsen er permanent.\nVil du fortsette?"; -"taskDeleteConfirmation" = "Sletting av oppgaven er permanent.\nVil du fortsette?"; +"eventDeleteConfirmation" = "Sletting av hendelsen er permanent."; +"taskDeleteConfirmation" = "Sletting av oppgaven er permanent."; +"Would you like to continue?" = "Vil du fortsette?"; "You cannot remove nor unsubscribe from your personal calendar." = "Du kan ikke slette eller avbryta abonnement på en personlig kalender."; diff --git a/UI/Scheduler/Polish.lproj/Localizable.strings b/UI/Scheduler/Polish.lproj/Localizable.strings index 27c84e918..633957780 100644 --- a/UI/Scheduler/Polish.lproj/Localizable.strings +++ b/UI/Scheduler/Polish.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "Podana data końca jest wcześniejsza niż data po "Workweek days only" = "Tylko dni powszednie"; "Tasks in View" = "Zadania w widoku"; -"eventDeleteConfirmation" = "Usunięcie tego wydarzenia będzie trwałe.\nCzy chcesz kontynuować?"; -"taskDeleteConfirmation" = "Usunięcie tego zadania będzie trwałe.\nCzy chcesz kontynuować?"; +"eventDeleteConfirmation" = "Usunięcie tego wydarzenia będzie trwałe."; +"taskDeleteConfirmation" = "Usunięcie tego zadania będzie trwałe."; +"Would you like to continue?" = "Czy chcesz kontynuować?"; "You cannot remove nor unsubscribe from your personal calendar." = "Nie możesz usunąć ani zrezygnować z subskrypcji kalendarza osobistego."; diff --git a/UI/Scheduler/Russian.lproj/Localizable.strings b/UI/Scheduler/Russian.lproj/Localizable.strings index ac361dd93..992c40d15 100644 --- a/UI/Scheduler/Russian.lproj/Localizable.strings +++ b/UI/Scheduler/Russian.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "Дата начала позже даты конц "Workweek days only" = "Только рабочие дни недели"; "Tasks in View" = "Задания в виде"; -"eventDeleteConfirmation" = "Следующие события будут удалены: \n%{0}\nПродолжить удаление?"; -"taskDeleteConfirmation" = "Событие будет удалено безвозвратно.\nПродолжить?"; +"eventDeleteConfirmation" = "Следующие события будут удалены:"; +"taskDeleteConfirmation" = "Событие будет удалено безвозвратно."; +"Would you like to continue?" = "Продолжить?"; "You cannot remove nor unsubscribe from your personal calendar." = "Вы не можете удалить персональный календарь, равно как и выключить подписку на него."; diff --git a/UI/Scheduler/Slovak.lproj/Localizable.strings b/UI/Scheduler/Slovak.lproj/Localizable.strings index e69e9b461..d88998233 100644 --- a/UI/Scheduler/Slovak.lproj/Localizable.strings +++ b/UI/Scheduler/Slovak.lproj/Localizable.strings @@ -373,6 +373,9 @@ "Show Time as Free" = "Čas zobraziť ako voľný"; +/* email notifications */ +"Send Appointment Notifications" = "Odoslať notifikáciu o stretnutí"; + /* validation errors */ validate_notitle = "Názov nebol nastavený, pokračovať?"; @@ -411,8 +414,9 @@ validate_endbeforestart = "Zadaný dátum konca je pred začiatkom udalosti." "Workweek days only" = "Len pracovné dni"; "Tasks in View" = "Zobrazené úlohy"; -"eventDeleteConfirmation" = "Nasledujúca udalosť(i) bude odstránená:\n%{0}\nChcete pokračovať?"; -"taskDeleteConfirmation" = "Odstránenie nasledujúcej úlohy(oh) je nevratné.\n%{0}\nChcete pokračovať?"; +"eventDeleteConfirmation" = "Nasledujúca udalosť(i) bude odstránená:"; +"taskDeleteConfirmation" = "Odstránenie nasledujúcej úlohy(oh) je nevratné."; +"Would you like to continue?" = "Chcete pokračovať?"; "You cannot remove nor unsubscribe from your personal calendar." = "Nemôžete odstrániť ani sa odhlásiť z odoberania svojho vlastného kalendára."; diff --git a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings index 8cd2657c9..ad0080835 100644 --- a/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishArgentina.lproj/Localizable.strings @@ -411,8 +411,9 @@ validate_endbeforestart = "La fecha/hora de inicio es posterior a la de fin." "Workweek days only" = "Sólo días laborales"; "Tasks in View" = "Mostrar tareas"; -"eventDeleteConfirmation" = "Se eliminarán el/los siguiente(s) evento(s) : \n%{0}\n¿Desea proceder?"; -"taskDeleteConfirmation" = "No se puede deshacer el borrado de esta tarea. ¿Desea continuar?"; +"eventDeleteConfirmation" = "Se eliminarán el/los siguiente(s) evento(s) :"; +"taskDeleteConfirmation" = "No se puede deshacer el borrado de esta tarea."; +"Would you like to continue?" = "¿Desea continuar?"; "You cannot remove nor unsubscribe from your personal calendar." = "No puede quitarse ni darse de baja de su calendario personal."; diff --git a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings index a448e2e52..aca45c2cb 100644 --- a/UI/Scheduler/SpanishSpain.lproj/Localizable.strings +++ b/UI/Scheduler/SpanishSpain.lproj/Localizable.strings @@ -416,8 +416,9 @@ validate_endbeforestart = "La fecha/hora de inicio es posterior a la de fin." "Workweek days only" = "Sólo días laborables"; "Tasks in View" = "Mostrar tareas"; -"eventDeleteConfirmation" = "Se eliminarán el/los siguiente(s) evento(s) : \n%{0}\n¿Desea proceder?"; -"taskDeleteConfirmation" = "No se puede deshacer el borrado de esta tarea. ¿Desea continuar?"; +"eventDeleteConfirmation" = "Se eliminarán el/los siguiente(s) evento(s) :"; +"taskDeleteConfirmation" = "No se puede deshacer el borrado de esta tarea."; +"Would you like to continue?" = "¿Desea continuar?"; "You cannot remove nor unsubscribe from your personal calendar." = "No se puede quitar ni darse de baja de su calendario personal."; diff --git a/UI/Scheduler/Swedish.lproj/Localizable.strings b/UI/Scheduler/Swedish.lproj/Localizable.strings index e116adf3a..7a151b7ca 100644 --- a/UI/Scheduler/Swedish.lproj/Localizable.strings +++ b/UI/Scheduler/Swedish.lproj/Localizable.strings @@ -409,8 +409,9 @@ validate_endbeforestart = "Angivet slutdatumet inträffar före angivet start "Workweek days only" = "Bara arbetsdagar"; "Tasks in View" = "Uppgifter i vy"; -"eventDeleteConfirmation" = "Radering av händelsen är permanent.\nVill du fortsätta?"; -"taskDeleteConfirmation" = "Radering av uppgiften är permanent.\nVill du fortsätta?"; +"eventDeleteConfirmation" = "Radering av händelsen är permanent."; +"taskDeleteConfirmation" = "Radering av uppgiften är permanent."; +"Would you like to continue?" = "Vill du fortsätta?"; "You cannot remove nor unsubscribe from your personal calendar." = "Du kan inte ta bort eller avbryta prenumration på en personlig kalender."; diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index 08d6c9f9c..db6d55c91 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -1,6 +1,6 @@ /* UIxAppointmentActions.m - this file is part of SOGo * - * Copyright (C) 2011 Inverse inc. + * Copyright (C) 2011-2014 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -21,6 +21,7 @@ */ #import +#import #import #import @@ -33,6 +34,7 @@ #import #import +#import #import #import #import @@ -56,6 +58,7 @@ SOGoUserDefaults *ud; NSString *daysDelta, *startDelta, *durationDelta; NSTimeZone *tz; + NSException *ex; rq = [context request]; @@ -105,16 +108,24 @@ [event updateRecurrenceRulesUntilDate: end]; [event setLastModified: [NSCalendarDate calendarDate]]; - [co saveComponent: event]; - - response = [self responseWith204]; + ex = [co saveComponent: event]; + if (ex) + { + NSDictionary *jsonResponse; + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: + [ex reason], @"message", + nil]; + response = [self responseWithStatus: 403 + andString: [jsonResponse jsonRepresentation]]; + } + else + response = [self responseWith204]; } else response = (WOResponse *) [NSException exceptionWithHTTPStatus: 400 reason: @"missing 'days', 'start' and/or 'duration' parameters"]; - return response; } diff --git a/UI/Scheduler/UIxAppointmentEditor.m b/UI/Scheduler/UIxAppointmentEditor.m index 3c4b92b76..5f40e2396 100644 --- a/UI/Scheduler/UIxAppointmentEditor.m +++ b/UI/Scheduler/UIxAppointmentEditor.m @@ -31,6 +31,7 @@ #import #import #import +#import #import #import @@ -518,7 +519,7 @@ created_by = [event createdBy]; data = [NSDictionary dictionaryWithObjectsAndKeys: - [componentCalendar displayName], @"calendar", + [[componentCalendar displayName] stringByEscapingHTMLString], @"calendar", [event tag], @"component", [dateFormatter formattedDate: eventStartDate], @"startDate", [dateFormatter formattedTime: eventStartDate], @"startTime", @@ -526,10 +527,10 @@ [dateFormatter formattedTime: eventEndDate], @"endTime", //([event hasRecurrenceRules] ? @"1": @"0"), @"isRecurring", ([event isAllDay] ? @"1": @"0"), @"isAllDay", - [event summary], @"summary", - [event location], @"location", - created_by, @"created_by", - [event comment], @"description", + [[event summary] stringByEscapingHTMLString], @"summary", + [[event location] stringByEscapingHTMLString], @"location", + [created_by stringByEscapingHTMLString], @"created_by", + [[[event comment] stringByEscapingHTMLString] stringByDetectingURLs], @"description", nil]; [result appendContentString: [data jsonRepresentation]]; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index cbb0b0087..5f99777a7 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -41,6 +41,7 @@ #import #import #import +#import #import #import @@ -55,7 +56,6 @@ #import #import #import -#import #import @@ -310,12 +310,14 @@ static NSArray *tasksFields = nil; NSEnumerator *folders, *currentInfos; SOGoAppointmentFolder *currentFolder; NSMutableDictionary *newInfo; - NSMutableArray *infos; + NSMutableArray *infos, *newInfoForComponent; NSNull *marker; SOGoAppointmentFolders *clientObject; SOGoUser *ownerUser; NSString *owner, *role, *calendarName; BOOL isErasable, folderIsRemote; + id currentInfo; + int i, count; infos = [NSMutableArray array]; marker = [NSNull null]; @@ -395,8 +397,17 @@ static NSArray *tasksFields = nil; // Possible improvement: only call _fixDates if event is recurrent // or the view range span a daylight saving time change [self _fixDates: newInfo]; - [infos addObject: [newInfo objectsForKeys: fields - notFoundMarker: marker]]; + newInfoForComponent = [NSMutableArray arrayWithArray: [newInfo objectsForKeys: fields + notFoundMarker: marker]]; + // Escape HTML + count = [newInfoForComponent count]; + for (i = 0; i < count; i++) + { + currentInfo = [newInfoForComponent objectAtIndex: i]; + if ([currentInfo respondsToSelector: @selector (stringByEscapingHTMLString)]) + [newInfoForComponent replaceObjectAtIndex: i withObject: [currentInfo stringByEscapingHTMLString]]; + } + [infos addObject: newInfoForComponent]; } } } diff --git a/UI/Scheduler/UIxCalMainView.m b/UI/Scheduler/UIxCalMainView.m index ea93a63b9..7846b43f8 100644 --- a/UI/Scheduler/UIxCalMainView.m +++ b/UI/Scheduler/UIxCalMainView.m @@ -79,6 +79,12 @@ } } +- (NSString *) localeCode +{ + // WARNING : NSLocaleCode is not defined in + return [locale objectForKey: @"NSLocaleCode"]; +} + - (NSArray *) monthMenuItems { static NSMutableArray *monthMenuItems = nil; diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index ed7b38d5a..ba592a562 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -1,8 +1,6 @@ /* UIxComponentEditor.h - this file is part of SOGo * - * Copyright (C) 2006-2012 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2006-2013 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 @@ -42,8 +40,6 @@ NSString *saveURL; NSMutableArray *calendarList; - //NSMutableArray *organizerList; - //NSDictionary *organizerIdentity; NSDictionary *organizerProfile; /* individual values */ diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 9c3e7cbc5..86bd0cb0b 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -1152,25 +1152,39 @@ iRANGE(2); return reminderItems; } - - (void) setReminder: (NSString *) theReminder - { - ASSIGN(reminder, theReminder); - } +- (void) setReminder: (NSString *) theReminder +{ + ASSIGN(reminder, theReminder); +} - (NSString *) reminder - { - return reminder; - } +{ + if ([[self clientObject] isNew]) + { + NSString *value; + int index; + + value = [userDefaults calendarDefaultReminder]; + index = [reminderValues indexOfObject: value]; + + if (index != NSNotFound) + return [reminderItems objectAtIndex: index]; + + return @"NONE"; + } - - (void) setReminderQuantity: (NSString *) theReminderQuantity - { - ASSIGN(reminderQuantity, theReminderQuantity); - } + return reminder; +} + +- (void) setReminderQuantity: (NSString *) theReminderQuantity +{ + ASSIGN(reminderQuantity, theReminderQuantity); +} - (NSString *) reminderQuantity - { - return reminderQuantity; - } +{ + return reminderQuantity; +} - (NSString *) itemReminderText { diff --git a/UI/Scheduler/Ukrainian.lproj/Localizable.strings b/UI/Scheduler/Ukrainian.lproj/Localizable.strings index 9d0074480..8818182b4 100644 --- a/UI/Scheduler/Ukrainian.lproj/Localizable.strings +++ b/UI/Scheduler/Ukrainian.lproj/Localizable.strings @@ -409,8 +409,9 @@ validate_endbeforestart = "Дата закінчення передує да "Workweek days only" = "Лише робочі дні"; "Tasks in View" = "Перегляд завдань"; -"eventDeleteConfirmation" = "Ці події буде вилучено: \n%{0}\nПродовжити?"; -"taskDeleteConfirmation" = "Завдання буде вилучено назавжди.\nПродовжити?"; +"eventDeleteConfirmation" = "Ці події буде вилучено:"; +"taskDeleteConfirmation" = "Завдання буде вилучено назавжди."; +"Would you like to continue?" = "Продовжити?"; "You cannot remove nor unsubscribe from your personal calendar." = "Ви не можете вилучити персональнтй календар,а також відписатись від нього."; diff --git a/UI/Scheduler/Welsh.lproj/Localizable.strings b/UI/Scheduler/Welsh.lproj/Localizable.strings index c84ae1939..a68551a4d 100644 --- a/UI/Scheduler/Welsh.lproj/Localizable.strings +++ b/UI/Scheduler/Welsh.lproj/Localizable.strings @@ -409,8 +409,9 @@ validate_endbeforestart = "Mae'r dyddiad gorffen sydd wedi'i roi yn digwydd c "Workweek days only" = "diwrnodau gwaith yn unig"; "Tasks in View" = "Tasgau mewn golwg"; -"eventDeleteConfirmation" = "Bydd dileu y digwyddiad yn barhaol.\nHoffech barhau?"; -"taskDeleteConfirmation" = "Bydd dileu'r dasg yma'n barhaol.\nHoffech barhau?"; +"eventDeleteConfirmation" = "Bydd dileu y digwyddiad yn barhaol."; +"taskDeleteConfirmation" = "Bydd dileu'r dasg yma'n barhaol."; +"Would you like to continue?" = "Hoffech barhau?"; "You cannot remove nor unsubscribe from your personal calendar." = "You cannot remove nor unsubscribe from your personal calendar."; diff --git a/UI/Templates/Appointments/SOGoAptMailDeletion.wox b/UI/Templates/Appointments/SOGoAptMailDeletion.wox index aea834b62..544be59bd 100644 --- a/UI/Templates/Appointments/SOGoAptMailDeletion.wox +++ b/UI/Templates/Appointments/SOGoAptMailDeletion.wox @@ -8,37 +8,39 @@ -
-

-

- -
-
-
-
-
+ + +

+ + + + + + + + - -
-
-
+ + + + - -
-
-
-
-
+ + + + + + + + diff --git a/UI/Templates/Appointments/SOGoAptMailICalReply.wox b/UI/Templates/Appointments/SOGoAptMailICalReply.wox index fb8e01b75..6b1e28499 100644 --- a/UI/Templates/Appointments/SOGoAptMailICalReply.wox +++ b/UI/Templates/Appointments/SOGoAptMailICalReply.wox @@ -8,23 +8,20 @@ -
-

- -
-
-
-
-
+ + + + + + + + + +

diff --git a/UI/Templates/Appointments/SOGoAptMailInvitation.wox b/UI/Templates/Appointments/SOGoAptMailInvitation.wox index 8efa43197..1abc71423 100644 --- a/UI/Templates/Appointments/SOGoAptMailInvitation.wox +++ b/UI/Templates/Appointments/SOGoAptMailInvitation.wox @@ -8,37 +8,41 @@ -
-

-

- -
-
-
-
-
+ + +

+ + + + + + + + - -
-
-
+ + + + - -
-
-
-
-
+ + + + + + + + diff --git a/UI/Templates/Appointments/SOGoAptMailReceipt.wox b/UI/Templates/Appointments/SOGoAptMailReceipt.wox index 4ff295e9a..fc9f701d7 100644 --- a/UI/Templates/Appointments/SOGoAptMailReceipt.wox +++ b/UI/Templates/Appointments/SOGoAptMailReceipt.wox @@ -9,48 +9,72 @@ -
-

- -
-
-
-
-
-
-
-
-
-
+ + + + + + + + + + + + + + + + + + + + + -
- -
- -
+
+ + + + + + + + - -
- -
+ +
+ + + + + + + + - -
- -
+ +
+ + + + + + + + - - +

diff --git a/UI/Templates/Appointments/SOGoAptMailUpdate.wox b/UI/Templates/Appointments/SOGoAptMailUpdate.wox index 54ef9a5c6..f47f6afe5 100644 --- a/UI/Templates/Appointments/SOGoAptMailUpdate.wox +++ b/UI/Templates/Appointments/SOGoAptMailUpdate.wox @@ -8,38 +8,31 @@ -
- -

-

- -
-
-
-
- -
+ + + + + + + + +
-
+ >
+ + + - - -
-
-
-
- + + + + +

\ No newline at end of file diff --git a/UI/Templates/ContactsUI/UIxContactEditor.wox b/UI/Templates/ContactsUI/UIxContactEditor.wox index 37849396b..14f4206c3 100644 --- a/UI/Templates/ContactsUI/UIxContactEditor.wox +++ b/UI/Templates/ContactsUI/UIxContactEditor.wox @@ -373,7 +373,7 @@ diff --git a/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox b/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox index 1e7368086..525b16086 100644 --- a/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox +++ b/UI/Templates/MailPartViewers/UIxMailPartLinkViewer.wox @@ -8,14 +8,14 @@ >
- + var:title="filenameForDisplay" + const:absolute="YES" + >
() + /> ()
+
-
-
+ -
- -
    -
  • -
-
:
-
: -
-
-
- - +
+ : + +
+
+
    +
  • +
  • + () +
  • +
+
+
+ ");return""+encodeURIComponent(a)+""})}function g(a){return a.replace(D,function(a,b){return decodeURIComponent(b)})} -function p(a){return a.replace(/<\!--(?!{cke_protected})[\s\S]+?--\>/g,function(a){return"<\!--"+B+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\>"})}function v(a){return a.replace(/<\!--\{cke_protected\}\{C\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)})}function t(a,b){var c=b._.dataStore;return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function n(a, -b){for(var c=[],d=b.config.protectedSource,g=b._.dataStore||(b._.dataStore={id:1}),e=/<\!--\{cke_temp(comment)?\}(\d*?)--\>/g,d=[//gi,//gi].concat(d),a=a.replace(/<\!--[\s\S]*?--\>/g,function(a){return"<\!--{cke_tempcomment}"+(c.push(a)-1)+"--\>"}),f=0;f"});a=a.replace(e,function(a,b,d){return"<\!--"+ -B+(b?"{C}":"")+encodeURIComponent(c[d]).replace(/--/g,"%2D%2D")+"--\>"});return a.replace(/(['"]).*?\1/g,function(a){return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){g[g.id]=decodeURIComponent(b);return"{cke_protected_"+g.id++ +"}"})})}CKEDITOR.htmlDataProcessor=function(a){var d,e,f=this;this.editor=a;this.dataFilter=d=new CKEDITOR.htmlParser.filter;this.htmlFilter=e=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;d.addRules(s);d.addRules(b(a, -"data"));e.addRules(C);e.addRules(b(a,"html"));a.on("toHtml",function(b){var b=b.data,d=b.dataValue,d=n(d,a),d=k(d,G),d=w(d),d=k(d,I),d=d.replace(Q,"$1cke:$2"),d=d.replace(F,""),d=CKEDITOR.env.opera?d:d.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),e=b.context||a.editable().getName(),f;if(CKEDITOR.env.ie&&CKEDITOR.env.version<9&&e=="pre"){e="div";d="
"+d+"
";f=1}e=a.document.createElement(e);e.setHtml("a"+d);d=e.getHtml().substr(1);d=d.replace(RegExp(" data-cke-"+CKEDITOR.rnd+ -"-","ig")," ");f&&(d=d.replace(/^
|<\/pre>$/gi,""));d=d.replace(L,"$1$2");d=g(d);d=v(d);b.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(d,b.context,b.fixForBody===false?false:c(a.config))},null,null,5);a.on("toHtml",function(a){a.data.dataValue.filterChildren(f.dataFilter,true)},null,null,10);a.on("toHtml",function(a){var a=a.data,b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(true);a.dataValue=p(b)},null,null,15);a.on("toDataFormat",function(b){b.data.dataValue=
-CKEDITOR.htmlParser.fragment.fromHtml(b.data.dataValue,a.editable().getName(),c(a.config))},null,null,5);a.on("toDataFormat",function(a){a.data.dataValue.filterChildren(f.htmlFilter,true)},null,null,10);a.on("toDataFormat",function(b){var c=b.data.dataValue,d=f.writer;d.reset();c.writeChildrenHtml(d);c=d.getHtml(true);c=v(c);c=t(c,a);b.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype={toHtml:function(a,b,c,d){var g=this.editor;!b&&b!==null&&(b=g.editable().getName());return g.fire("toHtml",
-{dataValue:a,context:b,fixForBody:c,dontFilter:!!d}).dataValue},toDataFormat:function(a){return this.editor.fire("toDataFormat",{dataValue:a}).dataValue}};var E=/(?: |\xa0)$/,B="{cke_protected}",r=CKEDITOR.dtd,x=["caption","colgroup","col","thead","tfoot","tbody"],z=CKEDITOR.tools.extend({},r.$blockLimit,r.$block),s={elements:{},attributeNames:[[/^on/,"data-cke-pa-on"]]},C={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]],attributeNames:[[/^data-cke-(saved|pa)-/,""],[/^data-cke-.*/,""],["hidefocus",
-""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return false;for(var c=["name","href","src"],d,g=0;g-1&&d>-1&&c!=d)){c=l(a);d=l(b)}return c>d?1:-1})},embed:function(a){var b=a.parent;if(b&&b.name=="object"){var c=
-b.attributes.width,b=b.attributes.height;c&&(a.attributes.width=c);b&&(a.attributes.height=b)}},param:function(a){a.children=[];a.isEmpty=true;return a},a:function(a){if(!a.children.length&&!a.attributes.name&&!a.attributes["data-cke-saved-name"])return false},span:function(a){a.attributes["class"]=="Apple-style-span"&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},
-style:function(a){var b=a.children[0];b&&b.value&&(b.value=CKEDITOR.tools.trim(b.value));if(!a.attributes.type)a.attributes.type="text/css"},title:function(a){var b=a.children[0];!b&&j(a,b=new CKEDITOR.htmlParser.text);b.value=a.attributes["data-cke-title"]||""}},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||false}}};if(CKEDITOR.env.ie)C.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})};for(var y in{input:1,
-textarea:1}){s.elements[y]=m;C.elements[y]=q}var o=/<(a|area|img|input|source)\b([^>]*)>/gi,u=/\s(on\w+|href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,I=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,G=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,D=/([^<]*)<\/cke:encoded>/gi,Q=/(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,L=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,F=/]*?)\/?>(?!\s*<\/cke:\1)/gi})();
-"use strict";CKEDITOR.htmlParser.element=function(b,c){this.name=b;this.attributes=c||{};this.children=[];var a=b||"",f=a.match(/^cke:(.*)/);f&&(a=f[1]);a=!(!CKEDITOR.dtd.$nonBodyContent[a]&&!CKEDITOR.dtd.$block[a]&&!CKEDITOR.dtd.$listItem[a]&&!CKEDITOR.dtd.$tableContent[a]&&!(CKEDITOR.dtd.$nonEditable[a]||a=="br"));this.isEmpty=!!CKEDITOR.dtd.$empty[b];this.isUnknown=!CKEDITOR.dtd[b];this._={isBlockLike:a,hasInlineStarted:this.isEmpty||!a}};
-CKEDITOR.htmlParser.cssStyle=function(b){var c={};((b instanceof CKEDITOR.htmlParser.element?b.attributes.style:b)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,b,d){b=="font-family"&&(d=d.replace(/["']/g,""));c[b.toLowerCase()]=d});return{rules:c,populate:function(a){var b=this.toString();if(b)a instanceof CKEDITOR.dom.element?a.setAttribute("style",b):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=b:a.style=b},toString:function(){var a=[],b;
-for(b in c)c[b]&&a.push(b,":",c[b],";");return a.join("")}}};
-(function(){var b=function(a,b){a=a[0];b=b[0];return ab?1:0},c=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:c.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a){var b=this,c,e;if(!b.parent)a.onRoot(b);for(;;){c=b.name;if(!(e=a.onElementName(c))){this.remove();return false}b.name=e;if(!(b=a.onElement(b))){this.remove();return false}if(b!==
-this){this.replaceWith(b);return false}if(b.name==c)break;if(b.type!=CKEDITOR.NODE_ELEMENT){this.replaceWith(b);return false}if(!b.name){this.replaceWithChildren();return false}}c=b.attributes;var h,j;for(h in c){j=h;for(e=c[h];;)if(j=a.onAttributeName(h))if(j!=h){delete c[h];h=j}else break;else{delete c[h];break}j&&((e=a.onAttribute(b,j,e))===false?delete c[j]:c[j]=e)}b.isEmpty||this.filterChildren(a);return true},filterChildren:c.filterChildren,writeHtml:function(a,c){c&&this.filter(c);var d=this.name,
-e=[],h=this.attributes,j,i;a.openTag(d,h);for(j in h)e.push([j,h[j]]);a.sortAttributes&&e.sort(b);j=0;for(i=e.length;j]+)))|(?=\s|$))/g,d={checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};CKEDITOR.htmlParser.prototype={onTagOpen:function(){},onTagClose:function(){},onText:function(){},onCDATA:function(){},onComment:function(){},parse:function(b){for(var c,e,f=0,h;c=this._.htmlPartsRegex.exec(b);){e=c.index;if(e>f){f=b.substring(f,e);if(h)h.push(f);else this.onText(f)}f=
+this._.htmlPartsRegex.lastIndex;if(e=c[1]){e=e.toLowerCase();if(h&&CKEDITOR.dtd.$cdata[e]){this.onCDATA(h.join(""));h=null}if(!h){this.onTagClose(e);continue}}if(h)h.push(c[0]);else if(e=c[3]){e=e.toLowerCase();if(!/="/.test(e)){var n={},j;c=c[4];var k=!!(c&&c.charAt(c.length-1)=="/");if(c)for(;j=a.exec(c);){var l=j[1].toLowerCase();j=j[2]||j[3]||j[4]||"";n[l]=!j&&d[l]?l:CKEDITOR.tools.htmlDecodeAttr(j)}this.onTagOpen(e,n,k);!h&&CKEDITOR.dtd.$cdata[e]&&(h=[])}}else if(e=c[2])this.onComment(e)}if(b.length>
+f)this.onText(b.substring(f,b.length))}}})();
+CKEDITOR.htmlParser.basicWriter=CKEDITOR.tools.createClass({$:function(){this._={output:[]}},proto:{openTag:function(a){this._.output.push("<",a)},openTagClose:function(a,d){d?this._.output.push(" />"):this._.output.push(">")},attribute:function(a,d){typeof d=="string"&&(d=CKEDITOR.tools.htmlEncodeAttr(d));this._.output.push(" ",a,'="',d,'"')},closeTag:function(a){this._.output.push("")},text:function(a){this._.output.push(a)},comment:function(a){this._.output.push("<\!--",a,"--\>")},write:function(a){this._.output.push(a)},
+reset:function(){this._.output=[];this._.indent=false},getHtml:function(a){var d=this._.output.join("");a&&this.reset();return d}}});"use strict";
+(function(){CKEDITOR.htmlParser.node=function(){};CKEDITOR.htmlParser.node.prototype={remove:function(){var a=this.parent.children,d=CKEDITOR.tools.indexOf(a,this),b=this.previous,c=this.next;b&&(b.next=c);c&&(c.previous=b);a.splice(d,1);this.parent=null},replaceWith:function(a){var d=this.parent.children,b=CKEDITOR.tools.indexOf(d,this),c=a.previous=this.previous,e=a.next=this.next;c&&(c.next=a);e&&(e.previous=a);d[b]=a;a.parent=this.parent;this.parent=null},insertAfter:function(a){var d=a.parent.children,
+b=CKEDITOR.tools.indexOf(d,a),c=a.next;d.splice(b+1,0,this);this.next=a.next;this.previous=a;a.next=this;c&&(c.previous=this);this.parent=a.parent},insertBefore:function(a){var d=a.parent.children,b=CKEDITOR.tools.indexOf(d,a);d.splice(b,0,this);this.next=a;(this.previous=a.previous)&&(a.previous.next=this);a.previous=this;this.parent=a.parent},getAscendant:function(a){var d=typeof a=="function"?a:typeof a=="string"?function(b){return b.name==a}:function(b){return b.name in a},b=this.parent;for(;b&&
+b.type==CKEDITOR.NODE_ELEMENT;){if(d(b))return b;b=b.parent}return null},wrapWith:function(a){this.replaceWith(a);a.add(this);return a},getIndex:function(){return CKEDITOR.tools.indexOf(this.parent.children,this)},getFilterContext:function(a){return a||{}}}})();"use strict";CKEDITOR.htmlParser.comment=function(a){this.value=a;this._={isBlockLike:false}};
+CKEDITOR.htmlParser.comment.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_COMMENT,filter:function(a,d){var b=this.value;if(!(b=a.onComment(d,b,this))){this.remove();return false}if(typeof b!="string"){this.replaceWith(b);return false}this.value=b;return true},writeHtml:function(a,d){d&&this.filter(d);a.comment(this.value)}});"use strict";
+(function(){CKEDITOR.htmlParser.text=function(a){this.value=a;this._={isBlockLike:false}};CKEDITOR.htmlParser.text.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(a,d){if(!(this.value=a.onText(d,this.value,this))){this.remove();return false}},writeHtml:function(a,d){d&&this.filter(d);a.text(this.value)}})})();"use strict";
+(function(){CKEDITOR.htmlParser.cdata=function(a){this.value=a};CKEDITOR.htmlParser.cdata.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_TEXT,filter:function(){},writeHtml:function(a){a.write(this.value)}})})();"use strict";CKEDITOR.htmlParser.fragment=function(){this.children=[];this.parent=null;this._={isBlockLike:true,hasInlineStarted:false}};
+(function(){function a(a){return a.attributes["data-cke-survive"]?false:a.name=="a"&&a.attributes.href||CKEDITOR.dtd.$removeEmpty[a.name]}var d=CKEDITOR.tools.extend({table:1,ul:1,ol:1,dl:1},CKEDITOR.dtd.table,CKEDITOR.dtd.ul,CKEDITOR.dtd.ol,CKEDITOR.dtd.dl),b={ol:1,ul:1},c=CKEDITOR.tools.extend({},{html:1},CKEDITOR.dtd.html,CKEDITOR.dtd.body,CKEDITOR.dtd.head,{style:1,script:1});CKEDITOR.htmlParser.fragment.fromHtml=function(e,f,h){function n(a){var b;if(r.length>0)for(var c=0;c=0;b--)if(a==r[b].name){r.splice(b,1);return}for(var c=[],e=[],d=m;d!=g&&d.name!=a;){d._.isBlockLike||e.unshift(d);c.push(d);d=d.returnPoint||d.parent}if(d!=g){for(b=0;b0?this.children[b-1]:null;if(c){if(a._.isBlockLike&&c.type==CKEDITOR.NODE_TEXT){c.value=CKEDITOR.tools.rtrim(c.value);if(c.value.length===
+0){this.children.pop();this.add(a);return}}c.next=a}a.previous=c;a.parent=this;this.children.splice(b,0,a);if(!this._.hasInlineStarted)this._.hasInlineStarted=a.type==CKEDITOR.NODE_TEXT||a.type==CKEDITOR.NODE_ELEMENT&&!a._.isBlockLike},filter:function(a,b){b=this.getFilterContext(b);a.onRoot(b,this);this.filterChildren(a,false,b)},filterChildren:function(a,b,c){if(this.childrenFilteredBy!=a.id){c=this.getFilterContext(c);if(b&&!this.parent)a.onRoot(c,this);this.childrenFilteredBy=a.id;for(b=0;b=0&&a7||d.name in CKEDITOR.dtd.tr||d.name in CKEDITOR.dtd.$listItem))i=false;else{i=b(d);i=!i||d.name=="form"&&i.name=="input"}i&&d.add(g(a))}}}function q(a,b){if((!t||CKEDITOR.env.needsBrFiller)&&a.type==CKEDITOR.NODE_ELEMENT&&a.name=="br"&&!a.attributes["data-cke-eol"])return true;var c;if(a.type==CKEDITOR.NODE_TEXT&&(c=a.value.match(w))){if(c.index){(new CKEDITOR.htmlParser.text(a.value.substring(0,
+c.index))).insertBefore(a);a.value=c[0]}if(!CKEDITOR.env.needsBrFiller&&t&&(!b||a.parent.name in v))return true;if(!t)if((c=a.previous)&&c.name=="br"||!c||f(c))return true}return false}var p={elements:{}},t=d=="html",v=CKEDITOR.tools.extend({},o),j;for(j in v)"#"in i[j]||delete v[j];for(j in v)p.elements[j]=m(t,a.config.fillEmptyBlocks!==false);p.root=m(t);p.elements.br=function(a){return function(b){if(b.parent.type!=CKEDITOR.NODE_DOCUMENT_FRAGMENT){var d=b.attributes;if("data-cke-bogus"in d||"data-cke-eol"in
+d)delete d["data-cke-bogus"];else{for(d=b.next;d&&e(d);)d=d.next;var i=c(b);!d&&f(b.parent)?h(b.parent,g(a)):f(d)&&(i&&!f(i))&&g(a).insertBefore(d)}}}}(t);return p}function d(a,b){return a!=CKEDITOR.ENTER_BR&&b!==false?a==CKEDITOR.ENTER_DIV?"div":"p":false}function b(a){for(a=a.children[a.children.length-1];a&&e(a);)a=a.previous;return a}function c(a){for(a=a.previous;a&&e(a);)a=a.previous;return a}function e(a){return a.type==CKEDITOR.NODE_TEXT&&!CKEDITOR.tools.trim(a.value)||a.type==CKEDITOR.NODE_ELEMENT&&
+a.attributes["data-cke-bookmark"]}function f(a){return a&&(a.type==CKEDITOR.NODE_ELEMENT&&a.name in o||a.type==CKEDITOR.NODE_DOCUMENT_FRAGMENT)}function h(a,b){var c=a.children[a.children.length-1];a.children.push(b);b.parent=a;if(c){c.next=b;b.previous=c}}function n(a){a=a.attributes;a.contenteditable!="false"&&(a["data-cke-editable"]=a.contenteditable?"true":1);a.contenteditable="false"}function j(a){a=a.attributes;switch(a["data-cke-editable"]){case "true":a.contenteditable="true";break;case "1":delete a.contenteditable}}
+function k(a){return a.replace(I,function(a,b,c){return"<"+b+c.replace(C,function(a,b){if(!/^on/.test(b)&&c.indexOf("data-cke-saved-"+b)==-1){a=a.slice(1);return" data-cke-saved-"+a+" data-cke-"+CKEDITOR.rnd+"-"+a}return a})+">"})}function l(a,b){return a.replace(b,function(a,b,c){a.indexOf("/g,">")+"");return""+encodeURIComponent(a)+""})}function u(a){return a.replace(y,function(a,b){return decodeURIComponent(b)})}
+function s(a){return a.replace(/<\!--(?!{cke_protected})[\s\S]+?--\>/g,function(a){return"<\!--"+m+"{C}"+encodeURIComponent(a).replace(/--/g,"%2D%2D")+"--\>"})}function t(a){return a.replace(/<\!--\{cke_protected\}\{C\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)})}function g(a,b){var c=b._.dataStore;return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){return decodeURIComponent(b)}).replace(/\{cke_protected_(\d+)\}/g,function(a,b){return c&&c[b]||""})}function r(a,
+b){for(var c=[],e=b.config.protectedSource,d=b._.dataStore||(b._.dataStore={id:1}),i=/<\!--\{cke_temp(comment)?\}(\d*?)--\>/g,e=[//gi,//gi].concat(e),a=a.replace(/<\!--[\s\S]*?--\>/g,function(a){return"<\!--{cke_tempcomment}"+(c.push(a)-1)+"--\>"}),g=0;g"});a=a.replace(i,function(a,b,e){return"<\!--"+
+m+(b?"{C}":"")+encodeURIComponent(c[e]).replace(/--/g,"%2D%2D")+"--\>"});return a.replace(/(['"]).*?\1/g,function(a){return a.replace(/<\!--\{cke_protected\}([\s\S]+?)--\>/g,function(a,b){d[d.id]=decodeURIComponent(b);return"{cke_protected_"+d.id++ +"}"})})}CKEDITOR.htmlDataProcessor=function(b){var c,e,i=this;this.editor=b;this.dataFilter=c=new CKEDITOR.htmlParser.filter;this.htmlFilter=e=new CKEDITOR.htmlParser.filter;this.writer=new CKEDITOR.htmlParser.basicWriter;c.addRules(B);c.addRules(p,{applyToAll:true});
+c.addRules(a(b,"data"),{applyToAll:true});e.addRules(L);e.addRules(E,{applyToAll:true});e.addRules(a(b,"html"),{applyToAll:true});b.on("toHtml",function(a){var a=a.data,c=a.dataValue,c=r(c,b),c=l(c,M),c=k(c),c=l(c,x),c=c.replace(v,"$1cke:$2"),c=c.replace(F,""),c=CKEDITOR.env.opera?c:c.replace(/(]*>)(\r\n|\n)/g,"$1$2$2"),e=a.context||b.editable().getName(),i;if(CKEDITOR.env.ie&&CKEDITOR.env.version<9&&e=="pre"){e="div";c="
"+c+"
";i=1}e=b.document.createElement(e); +e.setHtml("a"+c);c=e.getHtml().substr(1);c=c.replace(RegExp(" data-cke-"+CKEDITOR.rnd+"-","ig")," ");i&&(c=c.replace(/^
|<\/pre>$/gi,""));c=c.replace(D,"$1$2");c=u(c);c=t(c);a.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.context,a.fixForBody===false?false:d(a.enterMode,b.config.autoParagraph))},null,null,5);b.on("toHtml",function(a){a.data.filter.applyTo(a.data.dataValue,true,a.data.dontFilter,a.data.enterMode)&&b.fire("dataFiltered")},null,null,6);b.on("toHtml",function(a){a.data.dataValue.filterChildren(i.dataFilter,
+true)},null,null,10);b.on("toHtml",function(a){var a=a.data,b=a.dataValue,c=new CKEDITOR.htmlParser.basicWriter;b.writeChildrenHtml(c);b=c.getHtml(true);a.dataValue=s(b)},null,null,15);b.on("toDataFormat",function(a){var c=a.data.dataValue;a.data.enterMode!=CKEDITOR.ENTER_BR&&(c=c.replace(/^
/i,""));a.data.dataValue=CKEDITOR.htmlParser.fragment.fromHtml(c,a.data.context,d(a.data.enterMode,b.config.autoParagraph))},null,null,5);b.on("toDataFormat",function(a){a.data.dataValue.filterChildren(i.htmlFilter, +true)},null,null,10);b.on("toDataFormat",function(a){a.data.filter.applyTo(a.data.dataValue,false,true)},null,null,11);b.on("toDataFormat",function(a){var c=a.data.dataValue,e=i.writer;e.reset();c.writeChildrenHtml(e);c=e.getHtml(true);c=t(c);c=g(c,b);a.data.dataValue=c},null,null,15)};CKEDITOR.htmlDataProcessor.prototype={toHtml:function(a,b,c,e){var d=this.editor,i,g,f;if(b&&typeof b=="object"){i=b.context;c=b.fixForBody;e=b.dontFilter;g=b.filter;f=b.enterMode}else i=b;!i&&i!==null&&(i=d.editable().getName()); +return d.fire("toHtml",{dataValue:a,context:i,fixForBody:c,dontFilter:e,filter:g||d.filter,enterMode:f||d.enterMode}).dataValue},toDataFormat:function(a,b){var c,e,d;if(b){c=b.context;e=b.filter;d=b.enterMode}!c&&c!==null&&(c=this.editor.editable().getName());return this.editor.fire("toDataFormat",{dataValue:a,filter:e||this.editor.filter,context:c,enterMode:d||this.editor.enterMode}).dataValue}};var w=/(?: |\xa0)$/,m="{cke_protected}",i=CKEDITOR.dtd,q=["caption","colgroup","col","thead","tfoot", +"tbody"],o=CKEDITOR.tools.extend({},i.$blockLimit,i.$block),B={elements:{input:n,textarea:n}},p={attributeNames:[[/^on/,"data-cke-pa-on"],[/^data-cke-expando$/,""]]},L={elements:{embed:function(a){var b=a.parent;if(b&&b.name=="object"){var c=b.attributes.width,b=b.attributes.height;if(c)a.attributes.width=c;if(b)a.attributes.height=b}},a:function(a){if(!a.children.length&&!a.attributes.name&&!a.attributes["data-cke-saved-name"])return false}}},E={elementNames:[[/^cke:/,""],[/^\?xml:namespace$/,""]], +attributeNames:[[/^data-cke-(saved|pa)-/,""],[/^data-cke-.*/,""],["hidefocus",""]],elements:{$:function(a){var b=a.attributes;if(b){if(b["data-cke-temp"])return false;for(var c=["name","href","src"],e,d=0;d-1&&e>-1&&c!=e)){c=a.parent?a.getIndex(): +-1;e=b.parent?b.getIndex():-1}return c>e?1:-1})},param:function(a){a.children=[];a.isEmpty=true;return a},span:function(a){a.attributes["class"]=="Apple-style-span"&&delete a.name},html:function(a){delete a.attributes.contenteditable;delete a.attributes["class"]},body:function(a){delete a.attributes.spellcheck;delete a.attributes.contenteditable},style:function(a){var b=a.children[0];if(b&&b.value)b.value=CKEDITOR.tools.trim(b.value);if(!a.attributes.type)a.attributes.type="text/css"},title:function(a){var b= +a.children[0];!b&&h(a,b=new CKEDITOR.htmlParser.text);b.value=a.attributes["data-cke-title"]||""},input:j,textarea:j},attributes:{"class":function(a){return CKEDITOR.tools.ltrim(a.replace(/(?:^|\s+)cke_[^\s]*/g,""))||false}}};if(CKEDITOR.env.ie)E.attributes.style=function(a){return a.replace(/(^|;)([^\:]+)/g,function(a){return a.toLowerCase()})};var I=/<(a|area|img|input|source)\b([^>]*)>/gi,C=/\s(on\w+|href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,x=/(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, +M=/(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,y=/([^<]*)<\/cke:encoded>/gi,v=/(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,D=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,F=/]*?)\/?>(?!\s*<\/cke:\1)/gi})();"use strict"; +CKEDITOR.htmlParser.element=function(a,d){this.name=a;this.attributes=d||{};this.children=[];var b=a||"",c=b.match(/^cke:(.*)/);c&&(b=c[1]);b=!(!CKEDITOR.dtd.$nonBodyContent[b]&&!CKEDITOR.dtd.$block[b]&&!CKEDITOR.dtd.$listItem[b]&&!CKEDITOR.dtd.$tableContent[b]&&!(CKEDITOR.dtd.$nonEditable[b]||b=="br"));this.isEmpty=!!CKEDITOR.dtd.$empty[a];this.isUnknown=!CKEDITOR.dtd[a];this._={isBlockLike:b,hasInlineStarted:this.isEmpty||!b}}; +CKEDITOR.htmlParser.cssStyle=function(a){var d={};((a instanceof CKEDITOR.htmlParser.element?a.attributes.style:a)||"").replace(/"/g,'"').replace(/\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,function(a,c,e){c=="font-family"&&(e=e.replace(/["']/g,""));d[c.toLowerCase()]=e});return{rules:d,populate:function(a){var c=this.toString();if(c)a instanceof CKEDITOR.dom.element?a.setAttribute("style",c):a instanceof CKEDITOR.htmlParser.element?a.attributes.style=c:a.style=c},toString:function(){var a=[],c; +for(c in d)d[c]&&a.push(c,":",d[c],";");return a.join("")}}}; +(function(){function a(a){return function(b){return b.type==CKEDITOR.NODE_ELEMENT&&(typeof a=="string"?b.name==a:b.name in a)}}var d=function(a,b){a=a[0];b=b[0];return ab?1:0},b=CKEDITOR.htmlParser.fragment.prototype;CKEDITOR.htmlParser.element.prototype=CKEDITOR.tools.extend(new CKEDITOR.htmlParser.node,{type:CKEDITOR.NODE_ELEMENT,add:b.add,clone:function(){return new CKEDITOR.htmlParser.element(this.name,this.attributes)},filter:function(a,b){var d=this,h,n,b=d.getFilterContext(b);if(b.off)return true; +if(!d.parent)a.onRoot(b,d);for(;;){h=d.name;if(!(n=a.onElementName(b,h))){this.remove();return false}d.name=n;if(!(d=a.onElement(b,d))){this.remove();return false}if(d!==this){this.replaceWith(d);return false}if(d.name==h)break;if(d.type!=CKEDITOR.NODE_ELEMENT){this.replaceWith(d);return false}if(!d.name){this.replaceWithChildren();return false}}h=d.attributes;var j,k;for(j in h){k=j;for(n=h[j];;)if(k=a.onAttributeName(b,j))if(k!=j){delete h[j];j=k}else break;else{delete h[j];break}k&&((n=a.onAttribute(b, +d,k,n))===false?delete h[k]:h[k]=n)}d.isEmpty||this.filterChildren(a,false,b);return true},filterChildren:b.filterChildren,writeHtml:function(a,b){b&&this.filter(b);var f=this.name,h=[],n=this.attributes,j,k;a.openTag(f,n);for(j in n)h.push([j,n[j]]);a.sortAttributes&&h.sort(d);j=0;for(k=h.length;j0)this.children[a-1].next=null;this.parent.add(d,this.getIndex()+1);return d},removeClass:function(a){var b=this.attributes["class"];if(b)(b=CKEDITOR.tools.trim(b.replace(RegExp("(?:\\s+|^)"+a+ +"(?:\\s+|$)")," ")))?this.attributes["class"]=b:delete this.attributes["class"]},hasClass:function(a){var b=this.attributes["class"];return!b?false:RegExp("(?:^|\\s)"+a+"(?=\\s|$)").test(b)},getFilterContext:function(a){var b=[];a||(a={off:false,nonEditable:false});!a.off&&this.attributes["data-cke-processor"]=="off"&&b.push("off",true);!a.nonEditable&&this.attributes.contenteditable=="false"&&b.push("nonEditable",true);if(b.length)for(var a=CKEDITOR.tools.copy(a),d=0;d'+f.getValue()+"
",CKEDITOR.document); -b.insertAfter(f);f.hide();f.$.form&&a._attachToForm()}else a.setData(b.getHtml(),null,true);a.on("loaded",function(){a.fire("uiReady");a.editable(b);a.container=b;a.setData(a.getData(1));a.resetDirty();a.fire("contentDom");a.mode="wysiwyg";a.fire("mode");a.status="ready";a.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,a)},null,null,1E4);a.on("destroy",function(){if(f){a.container.clearCustomData();a.container.remove();f.show()}a.element.clearCustomData();delete a.element});return a}; -CKEDITOR.inlineAll=function(){var b,c,a;for(a in CKEDITOR.dtd.$editable)for(var f=CKEDITOR.document.getElementsByTag(a),d=0,e=f.count();d{voiceLabel}<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}')); -b=CKEDITOR.dom.element.createFromHtml(f.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:[a.lang.editor,a.name].join(", "),topHtml:i?''+i+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:l?''+l+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(j==CKEDITOR.ELEMENT_MODE_REPLACE){c.hide(); -b.insertAfter(c)}else c.append(b);a.container=b;i&&a.ui.space("top").unselectable();l&&a.ui.space("bottom").unselectable();c=a.config.width;j=a.config.height;c&&b.setStyle("width",CKEDITOR.tools.cssLength(c));j&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(j));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(a,c){return b(a,c,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(a,c,f){return b(a, -c,f,CKEDITOR.ELEMENT_MODE_APPENDTO)};CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",i="",a=h+a.replace(f,function(){return i+h})+i}a=a.replace(/\n/g,"
");b||(a=a.replace(RegExp("
(?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g," ");m(this,"text",a)},insertElement:function(b){e(this);for(var c=this.editor,d=c.config.enterMode, -g=c.getSelection(),f=g.getRanges(),i=b.getName(),j=CKEDITOR.dtd.$block[i],n,m,l,r=f.length-1;r>=0;r--){n=f[r];if(!n.checkReadOnly()){n.deleteContents(1);m=!r&&b||b.clone(1);var x,z;if(j)for(;(x=n.getCommonAncestor(0,1))&&(z=CKEDITOR.dtd[x.getName()])&&(!z||!z[i]);)if(x.getName()in CKEDITOR.dtd.span)n.splitElement(x);else if(n.checkStartOfBlock()&&n.checkEndOfBlock()){n.setStartBefore(x);n.collapse(true);x.remove()}else n.splitBlock(d==CKEDITOR.ENTER_DIV?"div":"p",c.editable());n.insertNode(m);l|| -(l=m)}}if(l){n.moveToPosition(l,CKEDITOR.POSITION_AFTER_END);if(j)if((b=l.getNext(a))&&b.type==CKEDITOR.NODE_ELEMENT&&b.is(CKEDITOR.dtd.$block))b.getDtd()["#"]?n.moveToElementEditStart(b):n.moveToElementEditEnd(l);else if(!b&&d!=CKEDITOR.ENTER_BR){b=n.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");n.moveToElementEditStart(b)}}g.selectRanges([n]);h(this,CKEDITOR.env.opera)},setData:function(a,b){!b&&this.editor.dataProcessor&&(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);this.editor.fire("dataReady")}, -getData:function(a){var b=this.getHtml();!a&&this.editor.dataProcessor&&(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable");var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")|| -a.config.ignoreEmptyParagraph!==false&&(b=b.replace(j,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml", -function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+ -a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(a){CKEDITOR.env.opera&&CKEDITOR.document.getActive().equals(this.isInline()?this:this.getWindow().getFrame())?a.cancel():this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus=true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus()})}this.isInline()&& -this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var b=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var e=a.config.contentsLangDirection;this.getDirection(1)!=e&&this.changeAttr("dir",e);var g=CKEDITOR.getCss();if(g){e=b.getHead();if(!e.getCustomData("stylesheet")){g=b.appendStyleText(g);g=new CKEDITOR.dom.element(g.ownerNode||g.owningElement);e.setCustomData("stylesheet",g);g.data("cke-temp",1)}}e= -b.getCustomData("stylesheet_ref")||0;b.setCustomData("stylesheet_ref",e+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()});this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.keyCode,g;if(c in{8:1,46:1}){var e=a.getSelection(),b=e.getRanges()[0],f=b.startPath(),h,k, -j,c=c==8;if(e=d(e)){a.fire("saveSnapshot");b.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove();b.select();a.fire("saveSnapshot");g=1}else if(b.collapsed)if((h=f.block)&&b[c?"checkStartOfBlock":"checkEndOfBlock"]()&&(j=h[c?"getPrevious":"getNext"](i))&&j.is("table")){a.fire("saveSnapshot");b[c?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove();b["moveToElementEdit"+(c?"End":"Start")](j);b.select();a.fire("saveSnapshot");g=1}else if(f.blockLimit&&f.blockLimit.is("td")&&(k=f.blockLimit.getAscendant("table"))&& -b.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END)&&(j=k[c?"getPrevious":"getNext"](i))){a.fire("saveSnapshot");b["moveToElementEdit"+(c?"End":"Start")](j);b.checkStartOfBlock()&&b.checkEndOfBlock()?j.remove():b.select();a.fire("saveSnapshot");g=1}else if((k=f.contains(["td","th","caption"]))&&b.checkBoundaryOfElement(k,c?CKEDITOR.START:CKEDITOR.END))g=1}return!g});CKEDITOR.env.ie&&this.attachListener(this,"click",c);!CKEDITOR.env.ie&&!CKEDITOR.env.opera&&this.attachListener(this,"mousedown", -function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==2){b=b.data.getTarget();if(!b.getOuterHtml().replace(j,"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&& +(function(){CKEDITOR.inline=function(a,d){if(!CKEDITOR.env.isCompatible)return null;a=CKEDITOR.dom.element.get(a);if(a.getEditor())throw'The editor instance "'+a.getEditor().name+'" is already attached to the provided element.';var b=new CKEDITOR.editor(d,a,CKEDITOR.ELEMENT_MODE_INLINE),c=a.is("textarea")?a:null;if(c){b.setData(c.getValue(),null,true);a=CKEDITOR.dom.element.createFromHtml('
'+c.getValue()+"
",CKEDITOR.document); +a.insertAfter(c);c.hide();c.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(c){b.container.clearCustomData();b.container.remove();c.show()}b.element.clearCustomData();delete b.element});return b}; +CKEDITOR.inlineAll=function(){var a,d,b;for(b in CKEDITOR.dtd.$editable)for(var c=CKEDITOR.document.getElementsByTag(b),e=0,f=c.count();e{voiceLabel}<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}')); +b=CKEDITOR.dom.element.createFromHtml(c.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:[a.lang.editor,a.name].join(", "),topHtml:j?''+j+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:k?''+k+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(n==CKEDITOR.ELEMENT_MODE_REPLACE){d.hide(); +b.insertAfter(d)}else d.append(b);a.container=b;j&&a.ui.space("top").unselectable();k&&a.ui.space("bottom").unselectable();d=a.config.width;n=a.config.height;d&&b.setStyle("width",CKEDITOR.tools.cssLength(d));n&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(n));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,c){return a(b,c,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,c,d){return a(b, +c,d,CKEDITOR.ELEMENT_MODE_APPENDTO)};CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",f="",a=i+a.replace(e,function(){return f+i})+f}a=a.replace(/\n/g,"
");b||(a=a.replace(RegExp("
(?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g," ");s(this,"text",a)},insertElement:function(a,b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a,b){var c=this.editor,d=c.config.enterMode,e=a.getName(), +i=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);var f,h;if(i)for(;(f=b.getCommonAncestor(0,1))&&(h=CKEDITOR.dtd[f.getName()])&&(!h||!h[e]);)if(f.getName()in CKEDITOR.dtd.span)b.splitElement(f);else if(b.checkStartOfBlock()&&b.checkEndOfBlock()){b.setStartBefore(f);b.collapse(true);f.remove()}else b.splitBlock(d==CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){var b=this.editor,d=b.activeEnterMode,b=b.getSelection(), +e=b.getRanges(),m=a.getName(),m=CKEDITOR.dtd.$block[m],i,f,o;h(this);for(var k=e.length;k--;){o=e[k];i=!k&&a||a.clone(1);this.insertElementIntoRange(i,o)&&!f&&(f=i)}if(f){o.moveToPosition(f,CKEDITOR.POSITION_AFTER_END);if(m)if((a=f.getNext(function(a){return c(a)&&!j(a)}))&&a.type==CKEDITOR.NODE_ELEMENT&&a.is(CKEDITOR.dtd.$block))a.getDtd()["#"]?o.moveToElementEditStart(a):o.moveToElementEditEnd(f);else if(!a&&d!=CKEDITOR.ENTER_BR){a=o.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");o.moveToElementEditStart(a)}}b.selectRanges([o]); +n(this,CKEDITOR.env.opera)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml();a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable");var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},setup:function(){var a= +this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(k,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a,"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b= +a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)},this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE|| +a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur",function(a){CKEDITOR.env.opera&&CKEDITOR.document.getActive().equals(this.isInline()?this:this.getWindow().getFrame())?a.cancel():this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus=true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus= +true;a.once("contentDom",function(){a.focusManager.focus()})}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var d=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var e=a.config.contentsLangDirection;this.getDirection(1)!=e&&this.changeAttr("dir",e);var h=CKEDITOR.getCss();if(h){e=d.getHead();if(!e.getCustomData("stylesheet")){h=d.appendStyleText(h);h=new CKEDITOR.dom.element(h.ownerNode|| +h.owningElement);e.setCustomData("stylesheet",h);h.data("cke-temp",1)}}e=d.getCustomData("stylesheet_ref")||0;d.setCustomData("stylesheet_ref",e+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(),this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()});var m={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.keyCode, +d;if(c in m){var e=a.getSelection(),b=e.getRanges()[0],g=b.startPath(),h,j,k,c=c==8;if(e=f(e)){a.fire("saveSnapshot");b.moveToPosition(e,CKEDITOR.POSITION_BEFORE_START);e.remove();b.select();a.fire("saveSnapshot");d=1}else if(b.collapsed)if((h=g.block)&&(k=h[c?"getPrevious":"getNext"](l))&&k.type==CKEDITOR.NODE_ELEMENT&&k.is("table")&&b[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");b[c?"checkEndOfBlock":"checkStartOfBlock"]()&&h.remove();b["moveToElementEdit"+(c?"End":"Start")](k); +b.select();a.fire("saveSnapshot");d=1}else if(g.blockLimit&&g.blockLimit.is("td")&&(j=g.blockLimit.getAscendant("table"))&&b.checkBoundaryOfElement(j,c?CKEDITOR.START:CKEDITOR.END)&&(k=j[c?"getPrevious":"getNext"](l))){a.fire("saveSnapshot");b["moveToElementEdit"+(c?"End":"Start")](k);b.checkStartOfBlock()&&b.checkEndOfBlock()?k.remove():b.select();a.fire("saveSnapshot");d=1}else if((j=g.contains(["td","th","caption"]))&&b.checkBoundaryOfElement(j,c?CKEDITOR.START:CKEDITOR.END))d=1}return!d});a.blockless&& +(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in m&&!this.getFirst(c)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}});this.attachListener(this,"dblclick",function(b){if(a.readOnly)return false;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);!CKEDITOR.env.ie&&!CKEDITOR.env.opera&&this.attachListener(this,"mousedown", +function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button==2){b=b.data.getTarget();if(!b.getOuterHtml().replace(k,"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&& a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref"); -b.removeCustomData("stylesheet").remove()}}delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var j=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,i=CKEDITOR.dom.walker.whitespaces(true),l=CKEDITOR.dom.walker.bookmark(false,true); -CKEDITOR.on("instanceLoaded",function(a){var c=a.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(a){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty();c.fire("lockSnapshot");b(a);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})}); +b.removeCustomData("stylesheet").remove()}}delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var j=CKEDITOR.dom.walker.bogus(),k=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,l=CKEDITOR.dom.walker.whitespaces(true),u=CKEDITOR.dom.walker.bookmark(false, +true);CKEDITOR.on("instanceLoaded",function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty();c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})}); CKEDITOR.on("instanceCreated",function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var d=CKEDITOR.tools.getNextId(),e=CKEDITOR.dom.element.createFromHtml(''+this.lang.common.editorHelp+"");c.append(e);a.changeAttr("aria-describedby", -d)}}})});CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var m=function(){function b(a){return a.type==CKEDITOR.NODE_ELEMENT}function c(a,d){var g,e,f,h,k=[],n=d.range.startContainer;g=d.range.startPath();for(var n=i[n.getName()],p=0,j=a.getChildren(),m=j.count(),l=-1,v=-1,E=0,B=g.contains(i.$list);p-1)k[l].firstNotAllowed=1;if(v>-1)k[v].lastNotAllowed=1;return k}function d(a,c){var g=[],e=a.getChildren(),f=e.count(),h,o=0,n=i[c],p=!a.is(i.$inline)||a.is("br");for(p&&g.push(" ");o ",s.document);s.insertNode(u);s.setStartAfter(u)}I=new CKEDITOR.dom.elementPath(s.startContainer); -l.endPath=G=new CKEDITOR.dom.elementPath(s.endContainer);if(!s.collapsed){var o=G.block||G.blockLimit,Q=s.getCommonAncestor();o&&(!o.equals(Q)&&!o.contains(Q)&&s.checkEndOfBlock())&&l.zombies.push(o);s.deleteContents()}for(;(D=b(s.startContainer)&&s.startContainer.getChild(s.startOffset-1))&&b(D)&&D.isBlockBoundary()&&I.contains(D);)s.moveToPosition(D,CKEDITOR.POSITION_BEFORE_END);e(s,l.blockLimit,I,G);if(u){s.setEndBefore(u);s.collapse();u.remove()}u=s.startPath();if(o=u.contains(g,false,1)){s.splitElement(o); -l.inlineStylesRoot=o;l.inlineStylesPeak=u.lastElement}u=s.createBookmark();(o=u.startNode.getPrevious(a))&&b(o)&&g(o)&&y.push(o);(o=u.startNode.getNext(a))&&b(o)&&g(o)&&y.push(o);for(o=u.startNode;(o=o.getParent())&&g(o);)y.push(o);s.moveToBookmark(u);if(u=z){u=l.range;if(l.type=="text"&&l.inlineStylesRoot){D=l.inlineStylesPeak;s=D.getDocument().createText("{cke-peak}");for(y=l.inlineStylesRoot.getParent();!D.equals(y);){s=s.appendTo(D.clone());D=D.getParent()}z=s.getOuterHtml().split("{cke-peak}").join(z)}D= -l.blockLimit.getName();if(/^\s+|\s+$/.test(z)&&"span"in CKEDITOR.dtd[D])var L=' ',z=L+z+L;z=l.editor.dataProcessor.toHtml(z,null,false,l.dontFilter);D=u.document.createElement("body");D.setHtml(z);if(L){D.getFirst().remove();D.getLast().remove()}if((L=u.startPath().block)&&!(L.getChildCount()==1&&L.getBogus()))a:{var F;if(D.getChildCount()==1&&b(F=D.getFirst())&&F.is(j)){L=F.getElementsByTag("*");u=0;for(y=L.count();u0;else{A=F.startPath();if(!G.isBlock&&(N=l.editor.config.enterMode!=CKEDITOR.ENTER_BR&&l.editor.config.autoParagraph!==false?l.editor.config.enterMode==CKEDITOR.ENTER_DIV?"div":"p":false)&&!A.block&&A.blockLimit&&A.blockLimit.equals(F.root)){N=L.createElement(N);!CKEDITOR.env.ie&&N.appendBogus();F.insertNode(N); -!CKEDITOR.env.ie&&(J=N.getBogus())&&J.remove();F.moveToPosition(N,CKEDITOR.POSITION_BEFORE_END)}if((A=F.startPath().block)&&!A.equals(H)){if(J=A.getBogus()){J.remove();D.push(A)}H=A}G.firstNotAllowed&&(s=1);if(s&&G.isElement){A=F.startContainer;for(K=null;A&&!i[A.getName()][G.name];){if(A.equals(z)){A=null;break}K=A;A=A.getParent()}if(A){if(K){O=F.splitElement(K);l.zombies.push(O);l.zombies.push(K)}}else{K=z.getName();P=!u;A=u==I.length-1;K=d(G.node,K);for(var M=[],R=K.length,T=0,U=void 0,V=0,W=-1;T< -R;T++){U=K[T];if(U==" "){if(!V&&(!P||T)){M.push(new CKEDITOR.dom.text(" "));W=M.length}V=1}else{M.push(U);V=0}}A&&W==M.length&&M.pop();P=M}}if(P){for(;A=P.pop();)F.insertNode(A);P=0}else F.insertNode(G.node);if(G.lastNotAllowed&&u1&&f&&f.intersectsNode(c.$)){d=[e.anchorOffset,e.focusOffset];f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0]--;f&&d[1]--;var i;f=e;if(!f.isCollapsed){i=f.getRangeAt(0);i.setStart(f.anchorNode,f.anchorOffset);i.setEnd(f.focusNode,f.focusOffset);i=i.collapsed}i&&d.unshift(d.pop())}}c.setText(h(c.getText()));if(d){c=e.getRangeAt(0);c.setStart(c.startContainer,d[0]);c.setEnd(c.startContainer,d[1]);e.removeAllRanges();e.addRange(c)}}}function h(a){return a.replace(/\u200B( )?/g, -function(a){return a[1]?" ":""})}function j(a,b,c){var d=a.on("focus",function(a){a.cancel()},null,null,-100);if(CKEDITOR.env.ie)var e=a.getDocument().on("selectionchange",function(a){a.cancel()},null,null,-100);else{var f=new CKEDITOR.dom.range(a);f.moveToElementEditStart(a);var h=a.getDocument().$.createRange();h.setStart(f.startContainer.$,f.startOffset);h.collapse(1);b.removeAllRanges();b.addRange(h)}c&&a.focus();d.removeListener();e&&e.removeListener()}var i,l,m=CKEDITOR.dom.walker.invisible(1); -CKEDITOR.on("instanceCreated",function(a){function d(){var a=f.getSelection();a&&a.removeAllRanges()}var f=a.editor;f.define("selectionChange",{errorProof:1});f.on("contentDom",function(){var a=f.document,d=CKEDITOR.document,g=f.editable(),h=a.getBody(),k=a.getDocumentElement(),i=g.isInline(),l;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(l!==0){a=f.getSelection().getNative();if(a.isCollapsed&&a.anchorNode==g.$){a=f.createRange();a.moveToElementEditStart(g);a.select()}}}, -null,null,-2);g.attachListener(g,"focus",function(){f.unlockSelection(l);l=0},null,null,-1);g.attachListener(g,"mousedown",function(){l=0});if(CKEDITOR.env.ie||CKEDITOR.env.opera||i){var j,m=function(){j=f.getSelection(1);j.lock()};q?g.attachListener(g,"beforedeactivate",m,null,null,-1):g.attachListener(f,"selectionCheck",m,null,null,-1);g.attachListener(g,"blur",function(){f.lockSelection(j);l=1},null,null,-1)}if(CKEDITOR.env.ie&&!i){var w;g.attachListener(g,"mousedown",function(a){a.data.$.button== -2&&f.document.$.selection.type=="None"&&(w=f.window.getScrollPosition())});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&w){f.document.$.documentElement.scrollLeft=w.x;f.document.$.documentElement.scrollTop=w.y}w=null});if(a.$.compatMode!="BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)k.on("mousedown",function(a){function b(a){a=a.data.$;if(g){var c=h.$.createTextRange();try{c.moveToPoint(a.x,a.y)}catch(d){}g.setEndPoint(f.compareEndPoints("StartToStart",c)<0?"EndToEnd": -"StartToStart",c);g.select()}}function c(){k.removeListener("mousemove",b);d.removeListener("mouseup",c);k.removeListener("mouseup",c);g.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y7){k.on("mousedown",function(a){if(a.data.getTarget().is("html")){d.on("mouseup",y);k.on("mouseup", -y)}});var y=function(){d.removeListener("mouseup",y);k.removeListener("mouseup",y);var b=CKEDITOR.document.$.selection,c=b.createRange();b.type!="None"&&c.parentElement().ownerDocument==a.$&&c.select()}}}}g.attachListener(g,"selectionchange",b,f);g.attachListener(g,"keyup",c,f);g.attachListener(g,"focus",function(){f.forceNextSelectionCheck();f.selectionChange(1)});if(i?CKEDITOR.env.webkit||CKEDITOR.env.gecko:CKEDITOR.env.opera){var o;g.attachListener(g,"mousedown",function(){o=1});g.attachListener(a.getDocumentElement(), -"mouseup",function(){o&&c.call(f);o=0})}else g.attachListener(CKEDITOR.env.ie?g:a.getDocumentElement(),"mouseup",c,f);CKEDITOR.env.webkit&&g.attachListener(a,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:e(g)}},null,null,-1)});f.on("contentDomUnload",f.forceNextSelectionCheck,f);f.on("dataReady",function(){f.selectionChange(1)});CKEDITOR.env.ie9Compat&&f.on("beforeDestroy",d,null,null,9);CKEDITOR.env.webkit&&f.on("setData", -d);f.on("contentDomUnload",function(){f.unlockSelection()})});CKEDITOR.on("instanceReady",function(a){var b=a.editor;if(CKEDITOR.env.webkit){b.on("selectionChange",function(){var a=b.editable(),c=d(a);c&&(c.getCustomData("ready")?e(a):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){e(b.editable())},null,null,-1);var c,f,a=function(){var a=b.editable();if(a)if(a=d(a)){var e=b.document.$.defaultView.getSelection();e.type=="Caret"&&e.anchorNode==a.$&&(f=1);c=a.getText();a.setText(h(c))}}, -i=function(){var a=b.editable();if(a)if(a=d(a)){a.setText(c);if(f){b.document.$.defaultView.getSelection().setPosition(a.$,a.getLength());f=0}}};b.on("beforeUndoImage",a);b.on("afterUndoImage",i);b.on("beforeGetData",a,null,null,0);b.on("getData",i)}});CKEDITOR.editor.prototype.selectionChange=function(a){(a?b:c).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){if(this._.savedSelection&&!a)return this._.savedSelection;return(a=this.editable())?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection= -function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&&a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection;return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)}; -CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var q=typeof window.getSelection!="function";CKEDITOR.dom.selection=function(a){var b=a instanceof CKEDITOR.dom.element;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=a=b?a:this.document.getBody(); -this.isLocked=0;this._={cache:{}};b=q?this.document.$.selection:this.document.getWindow().$.getSelection();if(CKEDITOR.env.webkit)(b.type=="None"&&this.document.getActive().equals(a)||b.type=="Caret"&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&j(a,b);else if(CKEDITOR.env.gecko)b&&(this.document.getActive().equals(a)&&b.anchorNode&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&j(a,b,true);else if(CKEDITOR.env.ie){var c;try{c=this.document.getActive()}catch(d){}if(q)b.type=="None"&&(c&&c.equals(this.document.getDocumentElement()))&& -j(a,null,true);else{(b=b&&b.anchorNode)&&(b=new CKEDITOR.dom.node(b));c&&(c.equals(this.document.getDocumentElement())&&b&&(a.equals(b)||a.contains(b)))&&j(a,null,true)}}c=this.getNative();var e,f;if(c)if(c.getRangeAt)e=(f=c.rangeCount&&c.getRangeAt(0))&&new CKEDITOR.dom.node(f.commonAncestorContainer);else{try{f=c.createRange()}catch(h){}e=f&&CKEDITOR.dom.element.get(f.item&&f.item(0)||f.parentElement())}if(!e||!a.equals(e)&&!a.contains(e)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement= -null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var w={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype={getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=q?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:q?function(){var a= -this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&& -d.nodeType==1&&c.endOffset-c.startOffset==1&&w[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=q?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c);var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,f,h,k=b.duplicate(),i=0,l=e.length-1,j=-1,m,o;i<=l;){j=Math.floor((i+l)/2);f=e[j];k.moveToElementText(f);m=k.compareEndPoints("StartToStart", -b);if(m>0)l=j-1;else if(m<0)i=j+1;else return{container:d,offset:a(f)}}if(j==-1||j==e.length-1&&m<0){k.moveToElementText(d);k.setEndPoint("StartToStart",b);k=k.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!k){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT?{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;k>0&&d>0;){h=e[--d];if(h.nodeType==CKEDITOR.NODE_TEXT){o=h;k=k-h.nodeValue.length}}return{container:o,offset:-k}}k.collapse(m>0?true:false);k.setEndPoint(m> -0?"StartToStart":"EndToStart",b);k=k.text.replace(/(\r\n|\r)/g,"\n").length;if(!k)return{container:d,offset:a(f)+(m>0?0:1)};for(;k>0;)try{h=f[m>0?"previousSibling":"nextSibling"];if(h.nodeType==CKEDITOR.NODE_TEXT){k=k-h.nodeValue.length;o=h}f=h}catch(u){return{container:d,offset:a(f)}}return{container:o,offset:m>0?-k:o.nodeValue.length+k}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root); -d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d==CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e=b.getLength()?j.setStartAfter(b):j.setStartBefore(b));h&&h.type==CKEDITOR.NODE_TEXT&&(l?j.setEndAfter(h): -j.setEndBefore(h));b=new CKEDITOR.dom.walker(j);b.evaluator=function(a){if(a.type==CKEDITOR.NODE_ELEMENT&&a.isReadOnly()){var b=f.clone();f.setEndBefore(a);f.collapsed&&d.splice(e--,1);if(!(a.getPosition(j.endContainer)&CKEDITOR.POSITION_CONTAINS)){b.setStartAfter(a);b.collapsed||d.splice(e+1,0,b)}return true}return false};b.next()}}return c.ranges}}(),getStartElement:function(){var a=this._.cache;if(a.startElement!==void 0)return a.startElement;var b;switch(this.getType()){case CKEDITOR.SELECTION_ELEMENT:return this.getSelectedElement(); -case CKEDITOR.SELECTION_TEXT:var c=this.getRanges()[0];if(c){if(c.collapsed){b=c.startContainer;b.type!=CKEDITOR.NODE_ELEMENT&&(b=b.getParent())}else{for(c.optimize();;){b=c.startContainer;if(c.startOffset==(b.getChildCount?b.getChildCount():b.getLength())&&!b.isBlockBoundary())c.setStartAfter(b);else break}b=c.startContainer;if(b.type!=CKEDITOR.NODE_ELEMENT)return b.getParent();b=b.getChild(c.startOffset);if(!b||b.type!=CKEDITOR.NODE_ELEMENT)b=c.startContainer;else for(c=b.getFirst();c&&c.type== -CKEDITOR.NODE_ELEMENT;){b=c;c=c.getFirst()}}b=b.$}}return a.startElement=b?new CKEDITOR.dom.element(b):null},getSelectedElement:function(){var a=this._.cache;if(a.selectedElement!==void 0)return a.selectedElement;var b=this,c=CKEDITOR.tools.tryThese(function(){return b.getNative().createRange().item(0)},function(){for(var a=b.getRanges()[0],c,d,e=2;e&&(!(c=a.getEnclosedNode())||!(c.type==CKEDITOR.NODE_ELEMENT&&w[c.getName()]&&(d=c)));e--)a.shrink(CKEDITOR.SHRINK_ELEMENT);return d.$});return a.selectedElement= -c?new CKEDITOR.dom.element(c):null},getSelectedText:function(){var a=this._.cache;if(a.selectedText!==void 0)return a.selectedText;var b=this.getNative(),b=q?b.type=="Control"?"":b.createRange().text:b.toString();return a.selectedText=b},lock:function(){this.getRanges();this.getStartElement();this.getSelectedElement();this.getSelectedText();this._.cache.nativeSel=null;this.isLocked=1},unlock:function(a){if(this.isLocked){if(a)var b=this.getSelectedElement(),c=!b&&this.getRanges();this.isLocked=0; -this.reset();if(a)(a=b||c[0]&&c[0].getCommonAncestor())&&a.getAscendant("body",1)&&(b?this.selectElement(b):this.selectRanges(c))}},reset:function(){this._.cache={}},selectElement:function(a){var b=new CKEDITOR.dom.range(this.root);b.setStartBefore(a);b.setEndAfter(a);this.selectRanges([b])},selectRanges:function(a){if(a.length)if(this.isLocked){var b=CKEDITOR.document.getActive();this.unlock();this.selectRanges(a);this.lock();!b.equals(this.root)&&b.focus()}else{if(q){var c=CKEDITOR.dom.walker.whitespaces(true), -d=/\ufeff|\u00a0/,h={table:1,tbody:1,tr:1};if(a.length>1){b=a[a.length-1];a[0].setEnd(b.endContainer,b.endOffset)}var b=a[0],a=b.collapsed,i,l,j,m=b.getEnclosedNode();if(m&&m.type==CKEDITOR.NODE_ELEMENT&&m.getName()in w&&(!m.is("a")||!m.getText()))try{j=m.$.createControlRange();j.addElement(m.$);j.select();return}catch(x){}(b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.getName()in h||b.endContainer.type==CKEDITOR.NODE_ELEMENT&&b.endContainer.getName()in h)&&b.shrink(CKEDITOR.NODE_ELEMENT, -true);j=b.createBookmark();var h=j.startNode,z;if(!a)z=j.endNode;j=b.document.$.body.createTextRange();j.moveToElementText(h.$);j.moveStart("character",1);if(z){d=b.document.$.body.createTextRange();d.moveToElementText(z.$);j.setEndPoint("EndToEnd",d);j.moveEnd("character",-1)}else{i=h.getNext(c);l=h.hasAscendant("pre");i=!(i&&i.getText&&i.getText().match(d))&&(l||!h.hasPrevious()||h.getPrevious().is&&h.getPrevious().is("br"));l=b.document.createElement("span");l.setHtml("");l.insertBefore(h); -i&&b.document.createText("").insertBefore(h)}b.setStartBefore(h);h.remove();if(a){if(i){j.moveStart("character",-1);j.select();b.document.$.selection.clear()}else j.select();b.moveToPosition(l,CKEDITOR.POSITION_BEFORE_START);l.remove()}else{b.setEndBefore(z);z.remove();j.select()}}else{z=this.getNative();if(!z)return;if(CKEDITOR.env.opera){b=this.document.$.createRange();b.selectNodeContents(this.root.$);z.addRange(b)}this.removeAllRanges();for(d=0;d=0){b.collapse(1);j.setEnd(b.endContainer.$,b.endOffset)}else throw s;}z.addRange(j)}}this.reset();this.root.fire("selectionchange")}},createBookmarks:function(a){return this.getRanges().createBookmarks(a)},createBookmarks2:function(a){return this.getRanges().createBookmarks2(a)},selectBookmarks:function(a){for(var b=[],c=0;c]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g," ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div");h.append(e);e.$.outerHTML="
"+f+"
";e.copyAttributes(h.getFirst());e=h.getFirst().remove()}else e.setHtml(f);b=e}else f?b=m(c?[a.getHtml()]:i(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,k;if((k=c.getPrevious(y))&&k.is&&k.is("pre")){d=l(k.getHtml(),/\n$/,"")+ -"\n\n"+l(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
"+d+"
":c.setHtml(d);k.remove()}}else c&&g(b)}function i(a){a.getName();var b=[];l(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+""+c+"
"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function l(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,
-b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function m(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",
-a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function q(a){var b=this._.definition,c=b.attributes,b=b.styles,d=n(this)[a.getName()],e=CKEDITOR.tools.isEmpty(c)&&CKEDITOR.tools.isEmpty(b),f;for(f in c)if(!((f=="class"||this._.definition.fullMatch)&&a.getAttribute(f)!=E(f,c[f]))){e=a.hasAttribute(f);a.removeAttribute(f)}for(var h in b)if(!(this._.definition.fullMatch&&a.getStyle(h)!=E(h,b[h],true))){e=e||!!a.getStyle(h);a.removeStyle(h)}k(a,d,r[a.getName()]);
-e&&(this._.definition.alwaysRemoveElement?g(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==CKEDITOR.ENTER_BR&&!a.hasAttributes()?g(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function w(a){for(var b=n(this),c=a.getElementsByTag(this.element),d=c.count();--d>=0;)q.call(this,c.getItem(d));for(var e in b)if(e!=this.element){c=a.getElementsByTag(e);for(d=c.count()-1;d>=0;d--){var f=c.getItem(d);k(f,b[e])}}}function k(a,b,c){if(b=b&&b.attributes)for(var d=0;d",a||b.name,"");return c.join("")},getDefinition:function(){return this._.definition}};CKEDITOR.style.getStyleText=function(a){var b=a._ST;
-if(b)return b;var b=a.styles,c=a.attributes&&a.attributes.style||"",d="";c.length&&(c=c.replace(z,";"));for(var e in b){var f=b[e],g=(e+":"+f).replace(z,";");f=="inherit"?d=d+g:c=c+g}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d}})();CKEDITOR.styleCommand=function(b,c){this.requiredContent=this.allowedContent=this.style=b;CKEDITOR.tools.extend(this,c,true)};
-CKEDITOR.styleCommand.prototype.exec=function(b){b.focus();this.state==CKEDITOR.TRISTATE_OFF?b.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&b.removeStyle(this.style)};CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);CKEDITOR.loadStylesSet=function(b,c,a){CKEDITOR.stylesSet.addExternal(b,c,"");CKEDITOR.stylesSet.load(b,a)};
-CKEDITOR.editor.prototype.getStylesSet=function(b){if(this._.stylesDefinitions)b(this._.stylesDefinitions);else{var c=this,a=c.config.stylesCombo_stylesSet||c.config.stylesSet;if(a===false)b(null);else if(a instanceof Array){c._.stylesDefinitions=a;b(a)}else{a||(a="default");var a=a.split(":"),f=a[0];CKEDITOR.stylesSet.addExternal(f,a[1]?a.slice(1).join(":"):CKEDITOR.getUrl("styles.js"),"");CKEDITOR.stylesSet.load(f,function(a){c._.stylesDefinitions=a[f];b(c._.stylesDefinitions)})}}};
-CKEDITOR.dom.comment=function(b,c){typeof b=="string"&&(b=(c?c.$:document).createComment(b));CKEDITOR.dom.domObject.call(this,b)};CKEDITOR.dom.comment.prototype=new CKEDITOR.dom.node;CKEDITOR.tools.extend(CKEDITOR.dom.comment.prototype,{type:CKEDITOR.NODE_COMMENT,getOuterHtml:function(){return"<\!--"+this.$.nodeValue+"--\>"}});
-(function(){var b={},c;for(c in CKEDITOR.dtd.$blockLimit)c in CKEDITOR.dtd.$list||(b[c]=1);var a={};for(c in CKEDITOR.dtd.$block)c in CKEDITOR.dtd.$blockLimit||c in CKEDITOR.dtd.$empty||(a[c]=1);CKEDITOR.dom.elementPath=function(c,d){var e=null,h=null,j=[],d=d||c.getDocument().getBody(),i=c;do if(i.type==CKEDITOR.NODE_ELEMENT){j.push(i);if(!this.lastElement){this.lastElement=i;if(i.is(CKEDITOR.dtd.$object))continue}var l=i.getName();if(!h){!e&&a[l]&&(e=i);if(b[l]){var m;if(m=!e){if(l=l=="div"){a:{l=
-i.getChildren();m=0;for(var q=l.count();m-1}:typeof b=="function"?f=b:typeof b=="object"&&(f=
-function(a){return a.getName()in b});var d=this.elements,e=d.length;c&&e--;if(a){d=Array.prototype.slice.call(d,0);d.reverse()}for(c=0;c=f){e=d.createText("");e.insertAfter(this)}else{b=d.createText("");b.insertAfter(e);b.remove()}return e},substring:function(b,
-c){return typeof c!="number"?this.$.nodeValue.substr(b):this.$.nodeValue.substring(b,c)}});
-(function(){function b(a,b,c){var e=a.serializable,h=b[c?"endContainer":"startContainer"],j=c?"endOffset":"startOffset",i=e?b.document.getById(a.startNode):a.startNode,a=e?b.document.getById(a.endNode):a.endNode;if(h.equals(i.getPrevious())){b.startOffset=b.startOffset-h.getLength()-a.getPrevious().getLength();h=a.getNext()}else if(h.equals(a.getPrevious())){b.startOffset=b.startOffset-h.getLength();h=a.getNext()}h.equals(i.getParent())&&b[j]++;h.equals(a.getParent())&&b[j]++;b[c?"endContainer":"startContainer"]=
-h;return b}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,c)};var c={createIterator:function(){var a=this,b=CKEDITOR.dom.walker.bookmark(),c=[],e;return{getNextRange:function(h){e=e==void 0?0:e+1;var j=a[e];if(j&&a.length>1){if(!e)for(var i=a.length-1;i>=0;i--)c.unshift(a[i].createBookmark(true));if(h)for(var l=0;a[e+l+1];){for(var m=j.document,h=0,i=m.getById(c[l].endNode),m=m.getById(c[l+
-1].startNode);;){i=i.getNextSourceNode(false);if(m.equals(i))h=1;else if(b(i)||i.type==CKEDITOR.NODE_ELEMENT&&i.isBlockBoundary())continue;break}if(!h)break;l++}for(j.moveToBookmark(c.shift());l--;){i=a[++e];i.moveToBookmark(c.shift());j.setEnd(i.endContainer,i.endOffset)}}return j}}},createBookmarks:function(a){for(var c=[],d,e=0;eb?-1:1}),e=0,f;e',CKEDITOR.document);b.appendTo(CKEDITOR.document.getHead());try{CKEDITOR.env.hc=b.getComputedStyle("border-top-color")==b.getComputedStyle("border-right-color")}catch(c){CKEDITOR.env.hc=false}b.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc";CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");
-CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(b=CKEDITOR._.pending){delete CKEDITOR._.pending;for(var a=0;a-1)p[l].firstNotAllowed=1;if(n>-1)p[n].lastNotAllowed=1;return p}function d(b,c){var e=[],i=b.getChildren(),m=i.count(),g,h=0,p=f[c],j=!b.is(f.$inline)||b.is("br");for(j&&e.push(" ");h ",l.document);l.insertNode(v);l.setStartAfter(v)}D=new CKEDITOR.dom.elementPath(l.startContainer);
+o.endPath=F=new CKEDITOR.dom.elementPath(l.endContainer);if(!l.collapsed){var y=F.block||F.blockLimit,X=l.getCommonAncestor();y&&(!y.equals(X)&&!y.contains(X)&&l.checkEndOfBlock())&&o.zombies.push(y);l.deleteContents()}for(;(z=a(l.startContainer)&&l.startContainer.getChild(l.startOffset-1))&&a(z)&&z.isBlockBoundary()&&D.contains(z);)l.moveToPosition(z,CKEDITOR.POSITION_BEFORE_END);m(l,o.blockLimit,D,F);if(v){l.setEndBefore(v);l.collapse();v.remove()}v=l.startPath();if(y=v.contains(e,false,1)){l.splitElement(y);
+o.inlineStylesRoot=y;o.inlineStylesPeak=v.lastElement}v=l.createBookmark();(y=v.startNode.getPrevious(c))&&a(y)&&e(y)&&u.push(y);(y=v.startNode.getNext(c))&&a(y)&&e(y)&&u.push(y);for(y=v.startNode;(y=y.getParent())&&e(y);)u.push(y);l.moveToBookmark(v);if(v=k){v=o.range;if(o.type=="text"&&o.inlineStylesRoot){z=o.inlineStylesPeak;l=z.getDocument().createText("{cke-peak}");for(u=o.inlineStylesRoot.getParent();!z.equals(u);){l=l.appendTo(z.clone());z=z.getParent()}k=l.getOuterHtml().split("{cke-peak}").join(k)}z=
+o.blockLimit.getName();if(/^\s+|\s+$/.test(k)&&"span"in CKEDITOR.dtd[z])var O=' ',k=O+k+O;k=o.editor.dataProcessor.toHtml(k,{context:null,fixForBody:false,dontFilter:o.dontFilter,filter:o.editor.activeFilter,enterMode:o.editor.activeEnterMode});z=v.document.createElement("body");z.setHtml(k);if(O){z.getFirst().remove();z.getLast().remove()}if((O=v.startPath().block)&&!(O.getChildCount()==1&&O.getBogus()))a:{var G;if(z.getChildCount()==1&&a(G=z.getFirst())&&G.is(j)){O=
+G.getElementsByTag("*");v=0;for(u=O.count();v0;else{A=G.startPath();if(!F.isBlock&&o.editor.config.autoParagraph!==false&&(o.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&o.editor.editable().equals(A.blockLimit)&&!A.block)&&(P=o.editor.activeEnterMode!=
+CKEDITOR.ENTER_BR&&o.editor.config.autoParagraph!==false?o.editor.activeEnterMode==CKEDITOR.ENTER_DIV?"div":"p":false)){P=O.createElement(P);P.appendBogus();G.insertNode(P);CKEDITOR.env.needsBrFiller&&(J=P.getBogus())&&J.remove();G.moveToPosition(P,CKEDITOR.POSITION_BEFORE_END)}if((A=G.startPath().block)&&!A.equals(H)){if(J=A.getBogus()){J.remove();z.push(A)}H=A}F.firstNotAllowed&&(l=1);if(l&&F.isElement){A=G.startContainer;for(K=null;A&&!f[A.getName()][F.name];){if(A.equals(k)){A=null;break}K=A;
+A=A.getParent()}if(A){if(K){R=G.splitElement(K);o.zombies.push(R);o.zombies.push(K)}}else{K=k.getName();S=!v;A=v==D.length-1;K=d(F.node,K);for(var N=[],U=K.length,Y=0,$=void 0,aa=0,ba=-1;Y1&&g&&g.intersectsNode(c.$)){d=[e.anchorOffset,e.focusOffset];g=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0]--;g&&d[1]--;var f;g=e;if(!g.isCollapsed){f=g.getRangeAt(0);f.setStart(g.anchorNode,g.anchorOffset);f.setEnd(g.focusNode,g.focusOffset);f=f.collapsed}f&&d.unshift(d.pop())}}c.setText(h(c.getText()));
+if(d){c=e.getRangeAt(0);c.setStart(c.startContainer,d[0]);c.setEnd(c.startContainer,d[1]);e.removeAllRanges();e.addRange(c)}}}function h(a){return a.replace(/\u200B( )?/g,function(a){return a[1]?" ":""})}function n(a,b,c){var d=a.on("focus",function(a){a.cancel()},null,null,-100);if(CKEDITOR.env.ie)var e=a.getDocument().on("selectionchange",function(a){a.cancel()},null,null,-100);else{var g=new CKEDITOR.dom.range(a);g.moveToElementEditStart(a);var f=a.getDocument().$.createRange();f.setStart(g.startContainer.$,
+g.startOffset);f.collapse(1);b.removeAllRanges();b.addRange(f)}c&&a.focus();d.removeListener();e&&e.removeListener()}function j(a){var b=CKEDITOR.dom.element.createFromHtml('
 
',a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START); +d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function k(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),g=e[0];if(e.length==1&&g.collapsed)if((d=g[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}} +var l,u,s=CKEDITOR.dom.walker.invisible(1),t=function(){function a(b){return function(a){var c=a.editor.createRange();c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return false}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!(e=d.moveToClosestEditablePosition(b.selected,a)))e=d.moveToClosestEditablePosition(b.selected,!a);e&&c.getSelection().selectRanges([d]);c.fire("saveSnapshot");b.selected.remove();if(!e){d.moveToElementEditablePosition(c.editable()); +c.getSelection().selectRanges([d])}c.fire("saveSnapshot");return false}}var c=a(),d=a(1);return{37:c,38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function c(){var a=e.getSelection();a&&a.removeAllRanges()}var e=b.editor;e.on("contentDom",function(){var b=e.document,c=CKEDITOR.document,i=e.editable(),m=b.getBody(),h=b.getDocumentElement(),j=i.isInline(),l,n;CKEDITOR.env.gecko&&i.attachListener(i,"focus",function(a){a.removeListener();if(l!==0)if((a=e.getSelection().getNative())&& +a.isCollapsed&&a.anchorNode==i.$){a=e.createRange();a.moveToElementEditStart(i);a.select()}},null,null,-2);i.attachListener(i,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){l&&CKEDITOR.env.webkit&&(l=e._.previousActive&&e._.previousActive.equals(b.getActive()));e.unlockSelection(l);l=0},null,null,-1);i.attachListener(i,"mousedown",function(){l=0});if(CKEDITOR.env.ie||CKEDITOR.env.opera||j){var s=function(){n=new CKEDITOR.dom.selection(e.getSelection());n.lock()};g?i.attachListener(i,"beforedeactivate", +s,null,null,-1):i.attachListener(e,"selectionCheck",s,null,null,-1);i.attachListener(i,CKEDITOR.env.webkit?"DOMFocusOut":"blur",function(){e.lockSelection(n);l=1},null,null,-1);i.attachListener(i,"mousedown",function(){l=0})}if(CKEDITOR.env.ie&&!j){var r;i.attachListener(i,"mousedown",function(a){if(a.data.$.button==2){a=e.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)r=e.window.getScrollPosition()}});i.attachListener(i,"mouseup",function(a){if(a.data.$.button==2&&r){e.document.$.documentElement.scrollLeft= +r.x;e.document.$.documentElement.scrollTop=r.y}r=null});if(b.$.compatMode!="BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)h.on("mousedown",function(a){function b(a){a=a.data.$;if(e){var c=m.$.createTextRange();try{c.moveToPoint(a.x,a.y)}catch(d){}e.setEndPoint(g.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);e.select()}}function d(){h.removeListener("mousemove",b);c.removeListener("mouseup",d);h.removeListener("mouseup",d);e.select()}a=a.data;if(a.getTarget().is("html")&& +a.$.y7&&CKEDITOR.env.version<11){h.on("mousedown",function(a){if(a.data.getTarget().is("html")){c.on("mouseup",v);h.on("mouseup",v)}});var v=function(){c.removeListener("mouseup",v);h.removeListener("mouseup",v);var a=CKEDITOR.document.$.selection,d=a.createRange();a.type!="None"&&d.parentElement().ownerDocument== +b.$&&d.select()}}}}i.attachListener(i,"selectionchange",a,e);i.attachListener(i,"keyup",d,e);i.attachListener(i,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){e.forceNextSelectionCheck();e.selectionChange(1)});if(j?CKEDITOR.env.webkit||CKEDITOR.env.gecko:CKEDITOR.env.opera){var D;i.attachListener(i,"mousedown",function(){D=1});i.attachListener(b.getDocumentElement(),"mouseup",function(){D&&d.call(e);D=0})}else i.attachListener(CKEDITOR.env.ie?i:b.getDocumentElement(),"mouseup",d,e);CKEDITOR.env.webkit&& +i.attachListener(b,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:f(i)}},null,null,-1);i.attachListener(i,"keydown",k(e),null,null,-1)});e.on("contentDomUnload",e.forceNextSelectionCheck,e);e.on("dataReady",function(){delete e._.fakeSelection;delete e._.hiddenSelectionContainer;e.selectionChange(1)});e.on("loadSnapshot",function(){var a=e.editable().getLast(function(a){return a.type==CKEDITOR.NODE_ELEMENT});a&&a.hasAttribute("data-cke-hidden-sel")&& +a.remove()},null,null,100);CKEDITOR.env.ie9Compat&&e.on("beforeDestroy",c,null,null,9);CKEDITOR.env.webkit&&e.on("setData",c);e.on("contentDomUnload",function(){e.unlockSelection()});e.on("key",function(a){if(e.mode=="wysiwyg"){var b=e.getSelection();if(b.isFake){var c=t[a.data.keyCode];if(c)return c({editor:e,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){var b=a.editor;if(CKEDITOR.env.webkit){b.on("selectionChange",function(){var a=b.editable(), +c=e(a);c&&(c.getCustomData("ready")?f(a):c.setCustomData("ready",1))},null,null,-1);b.on("beforeSetMode",function(){f(b.editable())},null,null,-1);var c,d,a=function(){var a=b.editable();if(a)if(a=e(a)){var g=b.document.$.defaultView.getSelection();g.type=="Caret"&&g.anchorNode==a.$&&(d=1);c=a.getText();a.setText(h(c))}},g=function(){var a=b.editable();if(a)if(a=e(a)){a.setText(c);if(d){b.document.$.defaultView.getSelection().setPosition(a.$,a.getLength());d=0}}};b.on("beforeUndoImage",a);b.on("afterUndoImage", +g);b.on("beforeGetData",a,null,null,0);b.on("getData",g)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:d).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){if((this._.savedSelection||this._.fakeSelection)&&!a)return this._.savedSelection||this._.fakeSelection;return(a=this.editable())&&this.mode=="wysiwyg"?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&& +a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection;return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath};CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable? +this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var g=typeof window.getSelection!="function",r=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b=a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:r++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=a=c?a:this.document.getBody();this.isLocked= +0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}b=g?this.document.$.selection:this.document.getWindow().$.getSelection();if(CKEDITOR.env.webkit)(b.type=="None"&&this.document.getActive().equals(a)||b.type=="Caret"&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b);else if(CKEDITOR.env.gecko)b&&(this.document.getActive().equals(a)&&b.anchorNode&&b.anchorNode.nodeType==CKEDITOR.NODE_DOCUMENT)&&n(a,b,true);else if(CKEDITOR.env.ie){var d; +try{d=this.document.getActive()}catch(e){}if(g)b.type=="None"&&(d&&d.equals(this.document.getDocumentElement()))&&n(a,null,true);else{(b=b&&b.anchorNode)&&(b=new CKEDITOR.dom.node(b));d&&(d.equals(this.document.getDocumentElement())&&b&&(a.equals(b)||a.contains(b)))&&n(a,null,true)}}d=this.getNative();var f,h;if(d)if(d.getRangeAt)f=(h=d.rangeCount&&d.getRangeAt(0))&&new CKEDITOR.dom.node(h.commonAncestorContainer);else{try{h=d.createRange()}catch(j){}f=h&&CKEDITOR.dom.element.get(h.item&&h.item(0)|| +h.parentElement())}if(!f||!(f.type==CKEDITOR.NODE_ELEMENT||f.type==CKEDITOR.NODE_TEXT)||!this.root.equals(f)&&!this.root.contains(f)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement=null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var w={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype= +{getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=g?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:g?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache; +if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&&d.nodeType==1&&c.endOffset-c.startOffset==1&&w[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=g?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c); +var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,g,f,h=b.duplicate(),m=0,j=e.length-1,k=-1,v,l;m<=j;){k=Math.floor((m+j)/2);g=e[k];h.moveToElementText(g);v=h.compareEndPoints("StartToStart",b);if(v>0)j=k-1;else if(v<0)m=k+1;else return{container:d,offset:a(g)}}if(k==-1||k==e.length-1&&v<0){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h){g=e[e.length-1];return g.nodeType!=CKEDITOR.NODE_TEXT? +{container:d,offset:e.length}:{container:g,offset:g.nodeValue.length}}for(d=e.length;h>0&&d>0;){f=e[--d];if(f.nodeType==CKEDITOR.NODE_TEXT){l=f;h=h-f.nodeValue.length}}return{container:l,offset:-h}}h.collapse(v>0?true:false);h.setEndPoint(v>0?"StartToStart":"EndToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;if(!h)return{container:d,offset:a(g)+(v>0?0:1)};for(;h>0;)try{f=g[v>0?"previousSibling":"nextSibling"];if(f.nodeType==CKEDITOR.NODE_TEXT){h=h-f.nodeValue.length;l=f}g=f}catch(q){return{container:d, +offset:a(g)}}return{container:l,offset:v>0?-h:l.nodeValue.length+h}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root);d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d== +CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e=b.getLength()?k.setStartAfter(b):k.setStartBefore(b));f&&f.type==CKEDITOR.NODE_TEXT&&(j?k.setEndAfter(f):k.setEndBefore(f));b=new CKEDITOR.dom.walker(k);b.evaluator=function(a){if(a.type==CKEDITOR.NODE_ELEMENT&&a.isReadOnly()){var b=g.clone();g.setEndBefore(a);g.collapsed&&d.splice(e--,1);if(!(a.getPosition(k.endContainer)&CKEDITOR.POSITION_CONTAINS)){b.setStartAfter(a); +b.collapsed||d.splice(e+1,0,b)}return true}return false};b.next()}}return c.ranges}}(),getStartElement:function(){var a=this._.cache;if(a.startElement!==void 0)return a.startElement;var b;switch(this.getType()){case CKEDITOR.SELECTION_ELEMENT:return this.getSelectedElement();case CKEDITOR.SELECTION_TEXT:var c=this.getRanges()[0];if(c){if(c.collapsed){b=c.startContainer;b.type!=CKEDITOR.NODE_ELEMENT&&(b=b.getParent())}else{for(c.optimize();;){b=c.startContainer;if(c.startOffset==(b.getChildCount?b.getChildCount(): +b.getLength())&&!b.isBlockBoundary())c.setStartAfter(b);else break}b=c.startContainer;if(b.type!=CKEDITOR.NODE_ELEMENT)return b.getParent();b=b.getChild(c.startOffset);if(!b||b.type!=CKEDITOR.NODE_ELEMENT)b=c.startContainer;else for(c=b.getFirst();c&&c.type==CKEDITOR.NODE_ELEMENT;){b=c;c=c.getFirst()}}b=b.$}}return a.startElement=b?new CKEDITOR.dom.element(b):null},getSelectedElement:function(){var a=this._.cache;if(a.selectedElement!==void 0)return a.selectedElement;var b=this,c=CKEDITOR.tools.tryThese(function(){return b.getNative().createRange().item(0)}, +function(){for(var a=b.getRanges()[0].clone(),c,d,e=2;e&&(!(c=a.getEnclosedNode())||!(c.type==CKEDITOR.NODE_ELEMENT&&w[c.getName()]&&(d=c)));e--)a.shrink(CKEDITOR.SHRINK_ELEMENT);return d&&d.$});return a.selectedElement=c?new CKEDITOR.dom.element(c):null},getSelectedText:function(){var a=this._.cache;if(a.selectedText!==void 0)return a.selectedText;var b=this.getNative(),b=g?b.type=="Control"?"":b.createRange().text:b.toString();return a.selectedText=b},lock:function(){this.getRanges();this.getStartElement(); +this.getSelectedElement();this.getSelectedText();this._.cache.nativeSel=null;this.isLocked=1},unlock:function(a){if(this.isLocked){if(a)var b=this.getSelectedElement(),c=!b&&this.getRanges(),d=this.isFake;this.isLocked=0;this.reset();if(a)(a=b||c[0]&&c[0].getCommonAncestor())&&a.getAscendant("body",1)&&(d?this.fake(b):b?this.selectElement(b):this.selectRanges(c))}},reset:function(){this._.cache={};this.isFake=0;var a=this.root.editor;if(a&&a._.fakeSelection&&this.rev==a._.fakeSelection.rev){delete a._.fakeSelection; +var b=a._.hiddenSelectionContainer;if(b){a.fire("lockSnapshot");b.remove();a.fire("unlockSnapshot")}delete a._.hiddenSelectionContainer}this.rev=r++},selectElement:function(a){var b=new CKEDITOR.dom.range(this.root);b.setStartBefore(a);b.setEndAfter(a);this.selectRanges([b])},selectRanges:function(a){this.reset();if(a.length)if(this.isLocked){var b=CKEDITOR.document.getActive();this.unlock();this.selectRanges(a);this.lock();!b.equals(this.root)&&b.focus()}else if(a.length==1&&!a[0].collapsed&&(b= +a[0].getEnclosedNode())&&b.type==CKEDITOR.NODE_ELEMENT&&b.getAttribute("contenteditable")=="false")this.fake(b);else{if(g){var d=CKEDITOR.dom.walker.whitespaces(true),e=/\ufeff|\u00a0/,h={table:1,tbody:1,tr:1};if(a.length>1){b=a[a.length-1];a[0].setEnd(b.endContainer,b.endOffset)}var b=a[0],a=b.collapsed,j,k,l,n=b.getEnclosedNode();if(n&&n.type==CKEDITOR.NODE_ELEMENT&&n.getName()in w&&(!n.is("a")||!n.getText()))try{l=n.$.createControlRange();l.addElement(n.$);l.select();return}catch(s){}(b.startContainer.type== +CKEDITOR.NODE_ELEMENT&&b.startContainer.getName()in h||b.endContainer.type==CKEDITOR.NODE_ELEMENT&&b.endContainer.getName()in h)&&b.shrink(CKEDITOR.NODE_ELEMENT,true);l=b.createBookmark();var h=l.startNode,r;if(!a)r=l.endNode;l=b.document.$.body.createTextRange();l.moveToElementText(h.$);l.moveStart("character",1);if(r){e=b.document.$.body.createTextRange();e.moveToElementText(r.$);l.setEndPoint("EndToEnd",e);l.moveEnd("character",-1)}else{j=h.getNext(d);k=h.hasAscendant("pre");j=!(j&&j.getText&& +j.getText().match(e))&&(k||!h.hasPrevious()||h.getPrevious().is&&h.getPrevious().is("br"));k=b.document.createElement("span");k.setHtml("");k.insertBefore(h);j&&b.document.createText("").insertBefore(h)}b.setStartBefore(h);h.remove();if(a){if(j){l.moveStart("character",-1);l.select();b.document.$.selection.clear()}else l.select();b.moveToPosition(k,CKEDITOR.POSITION_BEFORE_START);k.remove()}else{b.setEndBefore(r);r.remove();l.select()}}else{r=this.getNative();if(!r)return;if(CKEDITOR.env.opera){b= +this.document.$.createRange();b.selectNodeContents(this.root.$);r.addRange(b)}this.removeAllRanges();for(l=0;l=0){b.collapse(1);e.setEnd(b.endContainer.$,b.endOffset)}else throw t;}r.addRange(e)}}this.reset();this.root.fire("selectionchange")}},fake:function(a){var b=this.root.editor;this.reset();j(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root); +d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT;c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=r++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor();a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a}, +createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b=[],c=0;c]*>)[ \t\r\n]*/gi,"$1");g=g.replace(/([ \t\n\r]+| )/g, +" ");g=g.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var f=a.getDocument().createElement("div");f.append(e);e.$.outerHTML="
"+g+"
";e.copyAttributes(f.getFirst());e=f.getFirst().remove()}else e.setHtml(g);b=e}else g?b=u(c?[a.getHtml()]:k(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,h;if((h=c.getPrevious(x))&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("pre")){d=l(h.getHtml(),/\n$/,"")+"\n\n"+l(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
"+d+"
":c.setHtml(d);h.remove()}}else c&& +r(b)}function k(a){a.getName();var b=[];l(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+"
"+c+"
"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function l(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function u(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
+for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",a.length-1)+" "});if(c){var g=b.clone();g.setHtml(e);c.append(g)}else b.setHtml(e)}return c||b}function s(a){var b=this._.definition,
+c=b.attributes,b=b.styles,d=q(this)[a.getName()],e=CKEDITOR.tools.isEmpty(c)&&CKEDITOR.tools.isEmpty(b),f;for(f in c)if(!((f=="class"||this._.definition.fullMatch)&&a.getAttribute(f)!=o(f,c[f]))){e=a.hasAttribute(f);a.removeAttribute(f)}for(var h in b)if(!(this._.definition.fullMatch&&a.getStyle(h)!=o(h,b[h],true))){e=e||!!a.getStyle(h);a.removeStyle(h)}g(a,d,p[a.getName()]);e&&(this._.definition.alwaysRemoveElement?r(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==CKEDITOR.ENTER_BR&&!a.hasAttributes()?
+r(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function t(a){for(var b=q(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||s.call(this,d)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||g(d,b[f])}}}function g(a,b,c){if(b=b&&b.attributes)for(var d=0;d",a||b.name,"");return c.join("")},getDefinition:function(){return this._.definition}};
+CKEDITOR.style.getStyleText=function(a){var b=a._ST;if(b)return b;var b=a.styles,c=a.attributes&&a.attributes.style||"",d="";c.length&&(c=c.replace(E,";"));for(var e in b){var g=b[e],f=(e+":"+g).replace(E,";");g=="inherit"?d=d+f:c=c+f}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d};var M=CKEDITOR.POSITION_PRECEDING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED,y=CKEDITOR.POSITION_FOLLOWING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED})();
+CKEDITOR.styleCommand=function(a,d){this.requiredContent=this.allowedContent=this.style=a;CKEDITOR.tools.extend(this,d,true)};CKEDITOR.styleCommand.prototype.exec=function(a){a.focus();this.state==CKEDITOR.TRISTATE_OFF?a.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&a.removeStyle(this.style)};CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);
+CKEDITOR.loadStylesSet=function(a,d,b){CKEDITOR.stylesSet.addExternal(a,d,"");CKEDITOR.stylesSet.load(a,b)};
+CKEDITOR.editor.prototype.getStylesSet=function(a){if(this._.stylesDefinitions)a(this._.stylesDefinitions);else{var d=this,b=d.config.stylesCombo_stylesSet||d.config.stylesSet;if(b===false)a(null);else if(b instanceof Array){d._.stylesDefinitions=b;a(b)}else{b||(b="default");var b=b.split(":"),c=b[0];CKEDITOR.stylesSet.addExternal(c,b[1]?b.slice(1).join(":"):CKEDITOR.getUrl("styles.js"),"");CKEDITOR.stylesSet.load(c,function(b){d._.stylesDefinitions=b[c];a(d._.stylesDefinitions)})}}};
+CKEDITOR.dom.comment=function(a,d){typeof a=="string"&&(a=(d?d.$:document).createComment(a));CKEDITOR.dom.domObject.call(this,a)};CKEDITOR.dom.comment.prototype=new CKEDITOR.dom.node;CKEDITOR.tools.extend(CKEDITOR.dom.comment.prototype,{type:CKEDITOR.NODE_COMMENT,getOuterHtml:function(){return"<\!--"+this.$.nodeValue+"--\>"}});"use strict";
+(function(){var a={},d={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(d[b]=1);CKEDITOR.dom.elementPath=function(b,e){var f=null,h=null,n=[],j=b,k,e=e||b.getDocument().getBody();do if(j.type==CKEDITOR.NODE_ELEMENT){n.push(j);if(!this.lastElement){this.lastElement=j;if(j.is(CKEDITOR.dtd.$object)||j.getAttribute("contenteditable")=="false")continue}if(j.equals(e))break;if(!h){k=j.getName();
+j.getAttribute("contenteditable")=="true"?h=j:!f&&d[k]&&(f=j);if(a[k]){var l;if(l=!f){if(k=k=="div"){a:{k=j.getChildren();l=0;for(var u=k.count();l-1}:typeof a=="function"?c=a:typeof a=="object"&&(c=
+function(b){return b.getName()in a});var e=this.elements,f=e.length;d&&f--;if(b){e=Array.prototype.slice.call(e,0);e.reverse()}for(d=0;d=c){f=e.createText("");f.insertAfter(this)}else{a=e.createText("");a.insertAfter(f);a.remove()}return f},substring:function(a,
+d){return typeof d!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,d)}});
+(function(){function a(a,c,d){var f=a.serializable,h=c[d?"endContainer":"startContainer"],n=d?"endOffset":"startOffset",j=f?c.document.getById(a.startNode):a.startNode,a=f?c.document.getById(a.endNode):a.endNode;if(h.equals(j.getPrevious())){c.startOffset=c.startOffset-h.getLength()-a.getPrevious().getLength();h=a.getNext()}else if(h.equals(a.getPrevious())){c.startOffset=c.startOffset-h.getLength();h=a.getNext()}h.equals(j.getParent())&&c[n]++;h.equals(a.getParent())&&c[n]++;c[d?"endContainer":"startContainer"]=
+h;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,d)};var d={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),d=[],f;return{getNextRange:function(h){f=f==void 0?0:f+1;var n=a[f];if(n&&a.length>1){if(!f)for(var j=a.length-1;j>=0;j--)d.unshift(a[j].createBookmark(true));if(h)for(var k=0;a[f+k+1];){for(var l=n.document,h=0,j=l.getById(d[k].endNode),l=l.getById(d[k+
+1].startNode);;){j=j.getNextSourceNode(false);if(l.equals(j))h=1;else if(c(j)||j.type==CKEDITOR.NODE_ELEMENT&&j.isBlockBoundary())continue;break}if(!h)break;k++}for(n.moveToBookmark(d.shift());k--;){j=a[++f];j.moveToBookmark(d.shift());n.setEnd(j.endContainer,j.endOffset)}}return n}}},createBookmarks:function(b){for(var c=[],d,f=0;fb?-1:1}),e=0,f;e',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{CKEDITOR.env.hc=a.getComputedStyle("border-top-color")==a.getComputedStyle("border-right-color")}catch(d){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc";CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");
+CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(var b=0;barguments.length)){var c=i.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+"_label";this._.children=[];CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null,{role:"presentation"},function(){var f=[],d=a.required?" cke_required":"";"horizontal"!=
-a.labelLayout?f.push('",'"):(d={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'