Compare commits
158
Commits
SOGo-3.2.4
...
SOGo-3.2.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0636c756 | ||
|
|
655e5ffe01 | ||
|
|
f65514f13e | ||
|
|
b60749e86c | ||
|
|
1825b167dc | ||
|
|
214ce5ef86 | ||
|
|
927f0c3220 | ||
|
|
227cf582d9 | ||
|
|
8cdc435945 | ||
|
|
3efe0e8098 | ||
|
|
d38d42b83a | ||
|
|
1f3a89b984 | ||
|
|
0dea9c226d | ||
|
|
db1cc24c20 | ||
|
|
d7d88e2620 | ||
|
|
4fca484ed0 | ||
|
|
fcb79dc14b | ||
|
|
41dbb88b64 | ||
|
|
bb97b57ad7 | ||
|
|
5300d4185d | ||
|
|
26b69d25ee | ||
|
|
a3c056ea84 | ||
|
|
e4b2539b04 | ||
|
|
fcb6542d06 | ||
|
|
174fcd5c31 | ||
|
|
2f6c5148aa | ||
|
|
936b21b8c0 | ||
|
|
5f2a6c53c4 | ||
|
|
ba12c14a1d | ||
|
|
4158f7555e | ||
|
|
6e0f5b2254 | ||
|
|
4a21a02f98 | ||
|
|
9d7d2575d3 | ||
|
|
905859053c | ||
|
|
e5d0b0b0ca | ||
|
|
589827a414 | ||
|
|
42ee981613 | ||
|
|
5985262e0f | ||
|
|
c36fcd2902 | ||
|
|
bce09f413f | ||
|
|
9477c1af16 | ||
|
|
8011d9093e | ||
|
|
37334db0e3 | ||
|
|
6637bde56d | ||
|
|
e27efc4d3a | ||
|
|
ff7b9a3725 | ||
|
|
36c7ffcc3c | ||
|
|
3912d2ab3e | ||
|
|
5e775ea4ce | ||
|
|
841fdb96cc | ||
|
|
51ff746595 | ||
|
|
ef1f5a948f | ||
|
|
3560f0486e | ||
|
|
327ebf00a4 | ||
|
|
abb1e1b447 | ||
|
|
818233535f | ||
|
|
65e3fe978c | ||
|
|
0216f19e55 | ||
|
|
8404de758b | ||
|
|
5ec7ad54bb | ||
|
|
be8e1373ea | ||
|
|
9fbc84158a | ||
|
|
071c1032c1 | ||
|
|
af8baf41cb | ||
|
|
0b7ddf7ca4 | ||
|
|
942ddcabe4 | ||
|
|
bdd8254e02 | ||
|
|
ce0575517d | ||
|
|
ea7934a73c | ||
|
|
02fc147624 | ||
|
|
02a1ad48fc | ||
|
|
e7e1283fb3 | ||
|
|
d5e04be1df | ||
|
|
e2ec8af0db | ||
|
|
477c06a7ff | ||
|
|
479b534857 | ||
|
|
f6bcf26793 | ||
|
|
cdf6aaa53e | ||
|
|
1a9b20519e | ||
|
|
4f211678e9 | ||
|
|
e578787acd | ||
|
|
239dffa391 | ||
|
|
6cd3d90410 | ||
|
|
93dfbc5a94 | ||
|
|
896f57f634 | ||
|
|
f48ab310d6 | ||
|
|
5bc21e4f53 | ||
|
|
8296123650 | ||
|
|
2cebee42b7 | ||
|
|
7c7440c988 | ||
|
|
eed5679068 | ||
|
|
f1dbb88303 | ||
|
|
9cb6772ae8 | ||
|
|
dceab8fd2a | ||
|
|
7197b7eb41 | ||
|
|
18b502c39c | ||
|
|
94fc5d17db | ||
|
|
8c075d2e1b | ||
|
|
278aeef864 | ||
|
|
ef69344a59 | ||
|
|
457fccd5ac | ||
|
|
1da36a3ef9 | ||
|
|
7327ec21b2 | ||
|
|
94c522ea5c | ||
|
|
5c0229466e | ||
|
|
eb6d234c4b | ||
|
|
06903f8376 | ||
|
|
596c4bb4c2 | ||
|
|
3d16283d3c | ||
|
|
31d2db51ad | ||
|
|
abd0c0a744 | ||
|
|
1375237697 | ||
|
|
4c78d81bf7 | ||
|
|
2e9168080c | ||
|
|
1eb378c956 | ||
|
|
e739ed46a3 | ||
|
|
93f559a0df | ||
|
|
13a4398c4d | ||
|
|
462d274497 | ||
|
|
ab1c699bc7 | ||
|
|
9882052e81 | ||
|
|
3adbef5aa4 | ||
|
|
5ecf917e6b | ||
|
|
a02642cad9 | ||
|
|
92ba421dd1 | ||
|
|
ea940e645e | ||
|
|
651c0fdf14 | ||
|
|
15a3bf48d9 | ||
|
|
8493701408 | ||
|
|
f0085ef43e | ||
|
|
e0108088ee | ||
|
|
5591b9d423 | ||
|
|
ef96c21334 | ||
|
|
d8eae96d63 | ||
|
|
f6acc3e984 | ||
|
|
340ddf0ae6 | ||
|
|
b0fcaeef86 | ||
|
|
ec19a1290d | ||
|
|
384f1cb53f | ||
|
|
d0e0d5300a | ||
|
|
4b2b3693c5 | ||
|
|
011fae8a65 | ||
|
|
2a2ebd553e | ||
|
|
0038e4bc60 | ||
|
|
e053f12e78 | ||
|
|
c663f7ab4c | ||
|
|
68ddcd7045 | ||
|
|
ffd69337f6 | ||
|
|
ba5bd65ea3 | ||
|
|
6ce64a9a27 | ||
|
|
41701cc94c | ||
|
|
56d7f634a0 | ||
|
|
80f67785c3 | ||
|
|
29dfadfe75 | ||
|
|
fb743d6381 | ||
|
|
203dcf1d05 | ||
|
|
0d95267fe5 | ||
|
|
e997923c31 |
No files matched your search
@@ -83,7 +83,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context
|
||||
{
|
||||
NSArray *emails, *addresses, *categories, *elements;
|
||||
CardElement *n, *homeAdr, *workAdr;
|
||||
NSMutableArray *other_addresses;
|
||||
CardElement *n, *homeAdr, *workAdr, *otherAdr;
|
||||
NSMutableString *s, *a;
|
||||
NSString *url;
|
||||
id o;
|
||||
@@ -186,7 +187,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
[s appendFormat: @"<HomeStreet xmlns=\"Contacts:\">%@</HomeStreet>", a];
|
||||
|
||||
if ((o = [homeAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<HomeCity xmlns=\"Contacts:\">%@</HomeCity>", [o activeSyncRepresentationInContext: context]];
|
||||
@@ -217,7 +218,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ((o = [workAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", [a activeSyncRepresentationInContext: context]];
|
||||
[s appendFormat: @"<BusinessStreet xmlns=\"Contacts:\">%@</BusinessStreet>", a];
|
||||
|
||||
if ((o = [workAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<BusinessCity xmlns=\"Contacts:\">%@</BusinessCity>", [o activeSyncRepresentationInContext: context]];
|
||||
@@ -232,6 +233,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendFormat: @"<BusinessCountry xmlns=\"Contacts:\">%@</BusinessCountry>", [o activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
|
||||
// Other Address
|
||||
|
||||
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
|
||||
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
|
||||
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
|
||||
|
||||
if ([other_addresses count])
|
||||
{
|
||||
otherAdr = [other_addresses objectAtIndex: 0];
|
||||
a = [NSMutableString string];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 2 forKey: @""]))
|
||||
[a appendString: [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 1 forKey: @""]) && [o length])
|
||||
[a appendFormat: @"\n%@", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
[s appendFormat: @"<OtherStreet xmlns=\"Contacts:\">%@</OtherStreet>", a];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 3 forKey: @""]))
|
||||
[s appendFormat: @"<OtherCity xmlns=\"Contacts:\">%@</OtherCity>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 4 forKey: @""]))
|
||||
[s appendFormat: @"<OtherState xmlns=\"Contacts:\">%@</OtherState>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 5 forKey: @""]))
|
||||
[s appendFormat: @"<OtherPostalCode xmlns=\"Contacts:\">%@</OtherPostalCode>", [o activeSyncRepresentationInContext: context]];
|
||||
|
||||
if ((o = [otherAdr flattenedValueAtIndex: 6 forKey: @""]))
|
||||
[s appendFormat: @"<OtherCountry xmlns=\"Contacts:\">%@</OtherCountry>", [o activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
|
||||
// Other, less important fields
|
||||
if ((o = [self birthday]))
|
||||
{
|
||||
@@ -267,7 +301,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inContext: (WOContext *) context
|
||||
{
|
||||
CardElement *element;
|
||||
NSMutableArray *addressLines;
|
||||
NSMutableArray *addressLines, *other_addresses;
|
||||
id o;
|
||||
|
||||
// Contact's note
|
||||
@@ -398,6 +432,63 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
// OtherCountry
|
||||
//
|
||||
|
||||
other_addresses = [[self childrenWithTag: @"adr"] mutableCopy];
|
||||
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"work"]];
|
||||
[other_addresses removeObjectsInArray: [self childrenWithTag: @"adr" andAttribute: @"type" havingValue: @"home"]];
|
||||
|
||||
if ([other_addresses count])
|
||||
element = [other_addresses objectAtIndex: 0];
|
||||
else
|
||||
{
|
||||
element = [CardElement elementWithTag: @"adr"];
|
||||
[self addChild: element];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"OtherStreet"]) || ![self _isGhosted: @"OtherStreet" inContext: context])
|
||||
{
|
||||
addressLines = [NSMutableArray arrayWithArray: [o componentsSeparatedByString: @"\n"]];
|
||||
|
||||
[element setSingleValue: @""
|
||||
atIndex: 1 forKey: @""];
|
||||
[element setSingleValue: [addressLines count] ? [addressLines objectAtIndex: 0] : @""
|
||||
atIndex: 2 forKey: @""];
|
||||
|
||||
// Extended address line. If there are more then 2 address lines we add them to the extended address line.
|
||||
if ([addressLines count] > 1)
|
||||
{
|
||||
[addressLines removeObjectAtIndex: 0];
|
||||
[element setSingleValue: [addressLines componentsJoinedByString: @" "]
|
||||
atIndex: 1 forKey: @""];
|
||||
}
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"OtherCity"]) || ![self _isGhosted: @"OtherCity" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"OtherCity"]
|
||||
atIndex: 3 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"OtherState"]) || ![self _isGhosted: @"OtherState" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"OtherState"]
|
||||
atIndex: 4 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"OtherPostalCode"]) || ![self _isGhosted: @"OtherPostalCode" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"OtherPostalCode"]
|
||||
atIndex: 5 forKey: @""];
|
||||
}
|
||||
|
||||
if ((o = [theValues objectForKey: @"OtherCountry"]) || ![self _isGhosted: @"OtherCountry" inContext: context])
|
||||
{
|
||||
[element setSingleValue: [theValues objectForKey: @"OtherCountry"]
|
||||
atIndex: 6 forKey: @""];
|
||||
}
|
||||
|
||||
// Company's name
|
||||
if ((o = [theValues objectForKey: @"CompanyName"]))
|
||||
[self setOrg: o units: nil];
|
||||
|
||||
@@ -269,6 +269,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (void) processSyncAddCommand: (id <DOMElement>) theDocumentElement
|
||||
inCollection: (id) theCollection
|
||||
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
||||
objectsToTouch: (NSMutableArray *) objectsToTouch
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
{
|
||||
NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues;
|
||||
@@ -280,6 +281,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
int i;
|
||||
|
||||
additions = (id)[theDocumentElement getElementsByTagName: @"Add"];
|
||||
|
||||
if ([additions count])
|
||||
{
|
||||
for (i = 0; i < [additions count]; i++)
|
||||
@@ -367,9 +369,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
[o takeActiveSyncValues: allValues inContext: context];
|
||||
[sogoObject setIsNew: is_new];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
if (theFolderType == ActiveSyncEventFolder)
|
||||
[sogoObject saveComponent: o force: YES];
|
||||
else
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
}
|
||||
|
||||
if ([sogoObject isKindOfClass: [SOGoAppointmentObject class]] && [sogoObject resourceHasAutoAccepted])
|
||||
[objectsToTouch addObject: sogoObject];
|
||||
|
||||
// Update syncCache
|
||||
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
||||
|
||||
@@ -421,7 +431,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", easId];
|
||||
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
|
||||
[theBuffer appendString: @"</Add>"];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +474,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
- (void) processSyncChangeCommand: (id <DOMElement>) theDocumentElement
|
||||
inCollection: (id) theCollection
|
||||
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
objectsToTouch: (NSMutableArray *) objectsToTouch
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
{
|
||||
NSDictionary *allChanges;
|
||||
NSString *serverId, *easId, *origServerId, *mergedFolder;
|
||||
@@ -505,6 +515,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[self processSyncChangeCommand: theDocumentElement
|
||||
inCollection: [self collectionFromId: mergedFolder type: theFolderType]
|
||||
withType: theFolderType
|
||||
objectsToTouch: objectsToTouch
|
||||
inBuffer: theBuffer];
|
||||
|
||||
continue;
|
||||
@@ -588,7 +599,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
if ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
||||
{
|
||||
[o takeActiveSyncValues: allChanges inContext: context];
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
if (theFolderType == ActiveSyncEventFolder)
|
||||
{
|
||||
[sogoObject saveComponent: o force: YES];
|
||||
if ([sogoObject resourceHasAutoAccepted])
|
||||
[objectsToTouch addObject: sogoObject];
|
||||
}
|
||||
else
|
||||
[sogoObject saveComponent: o];
|
||||
|
||||
if ([syncCache objectForKey: serverId])
|
||||
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
||||
@@ -1637,6 +1656,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inCollection: (id) theCollection
|
||||
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
objectsToTouch: (NSMutableArray *) objectsToTouch
|
||||
processed: (BOOL *) processed
|
||||
{
|
||||
id <DOMElement> aCommand, element;
|
||||
@@ -1665,6 +1685,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[self processSyncAddCommand: element
|
||||
inCollection: theCollection
|
||||
withType: theFolderType
|
||||
objectsToTouch: objectsToTouch
|
||||
inBuffer: theBuffer];
|
||||
*processed = YES;
|
||||
}
|
||||
@@ -1674,6 +1695,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[self processSyncChangeCommand: element
|
||||
inCollection: theCollection
|
||||
withType: theFolderType
|
||||
objectsToTouch: objectsToTouch
|
||||
inBuffer: theBuffer];
|
||||
*processed = YES;
|
||||
}
|
||||
@@ -1711,7 +1733,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
{
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *filterType, *lastServerKey, *syncKeyInCache, *folderKey;
|
||||
NSMutableDictionary *folderMetadata, *folderOptions;
|
||||
NSMutableArray *supportedElements, *supportedElementNames;
|
||||
NSMutableArray *supportedElements, *supportedElementNames, *objectsToTouch;
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
id collection, value;
|
||||
|
||||
@@ -1941,6 +1963,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// We process the commands from the request
|
||||
//
|
||||
objectsToTouch = [NSMutableArray array];
|
||||
|
||||
if (!first_sync)
|
||||
{
|
||||
NSMutableString *s;
|
||||
@@ -1953,6 +1977,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
inCollection: collection
|
||||
withType: folderType
|
||||
inBuffer: s
|
||||
objectsToTouch: objectsToTouch
|
||||
processed: &processed];
|
||||
|
||||
// Windows phones don't like empty Responses tags - such as: <Responses></Responses>.
|
||||
@@ -2271,6 +2296,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[theBuffer appendString: changeBuffer];
|
||||
|
||||
[theBuffer appendString: @"</Collection>"];
|
||||
|
||||
// We touch objects (likely only SOGoAppointmentObjects for now) that might have been changed
|
||||
// by the server and that we want the EAS client to re-pull. For example, that's the case for
|
||||
// auto-accepted events by resources
|
||||
for (i = 0; i < [objectsToTouch count]; i++)
|
||||
{
|
||||
sleep(1);
|
||||
[[objectsToTouch objectAtIndex: i] touch];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -43,6 +43,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGObjWeb/WOCoreApplication.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
|
||||
@@ -59,8 +61,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <NGMime/NGMimeBodyPart.h>
|
||||
#import <NGMime/NGMimeFileData.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
#import <NGMime/NGMimeType.h>
|
||||
#import <NGMime/NGMimeMultipartBody.h>
|
||||
#import <NGMime/NGConcreteMimeType.h>
|
||||
#import <NGMail/NGMimeMessageParser.h>
|
||||
#import <NGMail/NGMimeMessageGenerator.h>
|
||||
|
||||
@@ -947,6 +950,7 @@ void handle_eas_terminate(int signum)
|
||||
command_count++;
|
||||
}
|
||||
[o destroy];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the folder from device if it is a contact folder and we have no SOGoRole_ObjectViewer.
|
||||
@@ -1097,6 +1101,7 @@ void handle_eas_terminate(int signum)
|
||||
![currentFolder synchronize])
|
||||
{
|
||||
[folders removeObjectAtIndex: count];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the folder from the device if it is a contact folder and we have no SOGoRole_ObjectViewer access right.
|
||||
@@ -1815,7 +1820,10 @@ void handle_eas_terminate(int signum)
|
||||
{
|
||||
appointmentObject = [[SOGoAppointmentObject alloc] initWithName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
||||
inContainer: collection];
|
||||
[appointmentObject saveComponent: event];
|
||||
if (folderType == ActiveSyncEventFolder)
|
||||
[appointmentObject saveComponent: event force: YES];
|
||||
else
|
||||
[appointmentObject saveComponent: event];
|
||||
}
|
||||
|
||||
if (uidCache && [calendarId length] > 64)
|
||||
@@ -2651,6 +2659,7 @@ void handle_eas_terminate(int signum)
|
||||
NSData *d;
|
||||
|
||||
unsigned int startdate, enddate, increments;
|
||||
NGCalendarDateRange *r1, *r2;
|
||||
char c;
|
||||
|
||||
startDate = [[[(id)[theDocumentElement getElementsByTagName: @"StartTime"] lastObject] textValue] calendarDate];
|
||||
@@ -2697,17 +2706,13 @@ void handle_eas_terminate(int signum)
|
||||
folder = [user personalCalendarFolderInContext: context];
|
||||
freebusy = [folder fetchFreeBusyInfosFrom: startDate to: endDate];
|
||||
|
||||
|
||||
NGCalendarDateRange *r1, *r2;
|
||||
|
||||
for (j = 1; j <= increments; j++)
|
||||
for (j = 0; 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++)
|
||||
{
|
||||
|
||||
@@ -2725,11 +2730,8 @@ void handle_eas_terminate(int signum)
|
||||
[s appendFormat: @"%c", c];
|
||||
}
|
||||
|
||||
|
||||
[s appendString: @"</MergedFreeBusy>"];
|
||||
[s appendString: @"</Availability>"];
|
||||
|
||||
|
||||
[s appendString: @"</Recipient>"];
|
||||
[s appendString: @"</Response>"];
|
||||
}
|
||||
@@ -3167,6 +3169,28 @@ void handle_eas_terminate(int signum)
|
||||
return nil;
|
||||
}
|
||||
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/ee218647(v=exchg.80).aspx
|
||||
// for valid status codes.
|
||||
//
|
||||
- (NSData *) _sendMailErrorResponseWithStatus: (int) status
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
|
||||
[s appendFormat: @"<Status>%d</Status>", status];
|
||||
[s appendString: @"</SendMail>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -3180,6 +3204,7 @@ void handle_eas_terminate(int signum)
|
||||
NSData *new_from_header;
|
||||
NSDictionary *identity;
|
||||
NSString *fullName, *email;
|
||||
NGMimeType *contentType;
|
||||
NSArray *from;
|
||||
|
||||
const char *bytes;
|
||||
@@ -3195,6 +3220,40 @@ void handle_eas_terminate(int signum)
|
||||
message = [parser parsePartFromData: data];
|
||||
RELEASE(parser);
|
||||
|
||||
// If an EAS client is trying to send an invitation email (request or response), we make sure to
|
||||
// remove all attendees that have NO email addresses. Outlook 2016 (and likely other EAS clients)
|
||||
// do that when sending IMIP only to "newly added or deleted attendees" - existing attendees have
|
||||
// their email addresses stripped, while keeping the display name value.
|
||||
contentType = [message contentType];
|
||||
|
||||
if ([contentType isKindOfClass: [NGConcreteTextMimeType class]] &&
|
||||
[[[message contentType] subType] caseInsensitiveCompare: @"calendar"] == NSOrderedSame &&
|
||||
([[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"request"] == NSOrderedSame ||
|
||||
[[(NGConcreteTextMimeType *)[message contentType] method] caseInsensitiveCompare: @"reply"] == NSOrderedSame))
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
iCalCalendar *calendar;
|
||||
iCalPerson *attendee;
|
||||
NSArray *attendees;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [iCalCalendar parseSingleFromSource: [message body]];
|
||||
event = [[calendar events] lastObject];
|
||||
attendees = [event attendees];
|
||||
|
||||
for (i = [attendees count]-1; i >= 0; i--)
|
||||
{
|
||||
attendee = [attendees objectAtIndex: i];
|
||||
if (![attendee rfc822Email] || [[attendee rfc822Email] caseInsensitiveCompare: @"nomail"] == NSOrderedSame)
|
||||
[event removeFromAttendees: attendee];
|
||||
}
|
||||
|
||||
// We regenerate the data to use
|
||||
[message setBody: [[calendar versitString] dataUsingEncoding: NSUTF8StringEncoding]];
|
||||
generator = [[[NGMimeMessageGenerator alloc] init] autorelease];
|
||||
data = [NSMutableData dataWithData: [generator generateMimeFromPart: message]];
|
||||
}
|
||||
|
||||
from = [message headersForKey: @"from"];
|
||||
|
||||
if (![from count] || ![self _isEMailValid: [[from objectAtIndex: 0] pureEMailAddress]] ||
|
||||
@@ -3296,20 +3355,7 @@ void handle_eas_terminate(int signum)
|
||||
{
|
||||
if ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 14.0)
|
||||
{
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
|
||||
s = [NSMutableString string];
|
||||
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<SendMail xmlns=\"ComposeMail:\">"];
|
||||
[s appendString: @"<Status>120</Status>"];
|
||||
[s appendString: @"</SendMail>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 120]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3993,23 +4039,32 @@ void handle_eas_terminate(int signum)
|
||||
}
|
||||
|
||||
xml = [NSString stringWithFormat: @"<?xml version=\"1.0\"?><!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\"><%@ xmlns=\"ComposeMail:\"><SaveInSentItems/><MIME>%@</MIME></%@>", cmdName, [s stringByEncodingBase64], cmdName];
|
||||
|
||||
|
||||
|
||||
d = [xml dataUsingEncoding: NSASCIIStringEncoding];
|
||||
}
|
||||
else
|
||||
{
|
||||
d = [[theRequest content] wbxml2xml];
|
||||
// Handle empty Ping request, no need to try decoding the WBXML blob here
|
||||
if ([[theRequest content] length])
|
||||
d = [[theRequest content] wbxml2xml];
|
||||
else
|
||||
d = nil;
|
||||
}
|
||||
|
||||
documentElement = nil;
|
||||
|
||||
if (!d)
|
||||
{
|
||||
// If we got no data in the SendMail request, that means SOPE rejected it because of the WOMaxUploadSize.
|
||||
// We generate here the proper failed response for SendMail
|
||||
if ([cmdName caseInsensitiveCompare: @"SendMail"] == NSOrderedSame)
|
||||
{
|
||||
[theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"];
|
||||
[theResponse setContent: [self _sendMailErrorResponseWithStatus: 122]];
|
||||
goto return_response;
|
||||
}
|
||||
// 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
|
||||
if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
|
||||
else if ([cmdName caseInsensitiveCompare: @"Ping"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"GetAttachment"] != NSOrderedSame && [cmdName caseInsensitiveCompare: @"Sync"] != NSOrderedSame)
|
||||
{
|
||||
RELEASE(context);
|
||||
RELEASE(pool);
|
||||
@@ -4047,6 +4102,7 @@ void handle_eas_terminate(int signum)
|
||||
|
||||
[self performSelector: aSelector withObject: documentElement withObject: theResponse];
|
||||
|
||||
return_response:
|
||||
[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,ResolveRecipients,ValidateCert" forKey: @"MS-ASProtocolCommands"];
|
||||
[theResponse setHeader: @"2.5,12.0,12.1,14.0,14.1" forKey: @"MS-ASProtocolVersions"];
|
||||
|
||||
@@ -163,7 +163,7 @@ struct GlobalObjectId {
|
||||
const char *bytes;
|
||||
|
||||
uid = [event uid];
|
||||
uidAsASCII = [uid decodeFromHexidecimal];
|
||||
uidAsASCII = [uid dataUsingEncoding: NSASCIIStringEncoding];
|
||||
newGlobalId = (struct GlobalObjectId*)calloc(sizeof(uint8_t), sizeof(struct GlobalObjectId) + 0x0c + [uidAsASCII length]);
|
||||
|
||||
prefix = @"040000008200e00074c5b7101a82e008";
|
||||
@@ -726,12 +726,53 @@ struct GlobalObjectId {
|
||||
//
|
||||
//
|
||||
//
|
||||
- (iCalCalendar *) calendarFromIMIPMessage
|
||||
- (iCalCalendar *) _calendarFromParts: (NSArray *) parts
|
||||
parent: (id) parent
|
||||
{
|
||||
NSDictionary *part;
|
||||
id bodyPart;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < [parts count]; i++)
|
||||
{
|
||||
part = [parts objectAtIndex: i];
|
||||
bodyPart = [parent lookupImap4BodyPartKey: [NSString stringWithFormat: @"%d", i+1]
|
||||
inContext: self->context];
|
||||
|
||||
if ([[part objectForKey: @"type"] isEqualToString: @"text"] &&
|
||||
[[part objectForKey: @"subtype"] isEqualToString: @"calendar"])
|
||||
{
|
||||
if (bodyPart)
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
NSData *calendarData;
|
||||
|
||||
calendarData = [bodyPart fetchBLOBWithPeek: YES];
|
||||
calendar = nil;
|
||||
|
||||
NS_DURING
|
||||
calendar = [iCalCalendar parseSingleFromSource: calendarData];
|
||||
NS_HANDLER
|
||||
calendar = nil;
|
||||
NS_ENDHANDLER
|
||||
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
else if ([[part objectForKey: @"type"] isEqualToString: @"multipart"])
|
||||
return [self _calendarFromParts: [part objectForKey: @"parts"] parent: bodyPart];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (iCalCalendar *) calendarFromIMIPMessage
|
||||
{
|
||||
NSString *type, *subtype;
|
||||
NSArray *parts;
|
||||
int i;
|
||||
|
||||
type = [[[self bodyStructure] valueForKey: @"type"] lowercaseString];
|
||||
subtype = [[[self bodyStructure] valueForKey: @"subtype"] lowercaseString];
|
||||
@@ -764,38 +805,7 @@ struct GlobalObjectId {
|
||||
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 fetchBLOBWithPeek: YES];
|
||||
calendar = nil;
|
||||
|
||||
NS_DURING
|
||||
calendar = [iCalCalendar parseSingleFromSource: calendarData];
|
||||
NS_HANDLER
|
||||
calendar = nil;
|
||||
NS_ENDHANDLER
|
||||
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [self _calendarFromParts: parts parent: self];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGCards/iCalAlarm.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalTrigger.h>
|
||||
#import <NGCards/NSCalendarDate+NGCards.h>
|
||||
|
||||
#include "NSDate+ActiveSync.h"
|
||||
|
||||
@implementation iCalAlarm (ActiveSync)
|
||||
|
||||
@@ -53,9 +56,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
nextAlarmDate = [self nextAlarmDate];
|
||||
delta = (int)(([[(iCalEvent *)parent startDate] timeIntervalSince1970] - [nextAlarmDate timeIntervalSince1970])/60);
|
||||
|
||||
// don't send negative reminder - not supported
|
||||
if (delta > 0)
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
|
||||
if ([parent isKindOfClass: [iCalEvent class]])
|
||||
{
|
||||
// don't send negative reminder - not supported
|
||||
if (delta > 0)
|
||||
[s appendFormat: @"<Reminder xmlns=\"Calendar:\">%d</Reminder>", (int)delta];
|
||||
}
|
||||
else
|
||||
{
|
||||
[s appendFormat: @"<ReminderTime xmlns=\"Task:\">%@</ReminderTime>", [nextAlarmDate activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -67,37 +77,47 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
iCalTrigger *trigger;
|
||||
id o;
|
||||
|
||||
o = [theValues objectForKey: @"Reminder"];
|
||||
|
||||
// Outlook: if reminder is set to 0 minutes before start save it as 1 minute since -> 0 minutes in not accepted by SOGo
|
||||
if ([o isEqualToString: @"0"])
|
||||
o = @"1";
|
||||
|
||||
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
|
||||
[trigger setValueType: @"DURATION"];
|
||||
[self setTrigger: trigger];
|
||||
[self setAction: @"DISPLAY"];
|
||||
|
||||
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
|
||||
// 1week = -P1W
|
||||
// 2weeks > -PxD
|
||||
// xdays > -PxD
|
||||
// xhours -> -PTxH
|
||||
// xmin -> -PTxM
|
||||
if ([o intValue] == 10080)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
|
||||
else
|
||||
if ((o = [theValues objectForKey: @"Reminder"]))
|
||||
{
|
||||
if (([o intValue] % 1440) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
|
||||
// Outlook: if reminder is set to 0 minutes before starttime, save it as 1 minute since -> 0 minutes in not accepted by SOGo
|
||||
if ([o isEqualToString: @"0"])
|
||||
o = @"1";
|
||||
|
||||
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
|
||||
[trigger setValueType: @"DURATION"];
|
||||
[self setTrigger: trigger];
|
||||
[self setAction: @"DISPLAY"];
|
||||
|
||||
// SOGo web ui only supports 1w but not 2w (custom reminder only supports min/hours/days)
|
||||
// 1week = -P1W
|
||||
// 2weeks > -PxD
|
||||
// xdays > -PxD
|
||||
// xhours -> -PTxH
|
||||
// xmin -> -PTxM
|
||||
if ([o intValue] == 10080)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P1W" ] forKey: @""];
|
||||
else
|
||||
{
|
||||
if (([o intValue] % 60) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
|
||||
if (([o intValue] % 1440) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-P%dD", ([o intValue] / 1440)] forKey: @""];
|
||||
else
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
|
||||
{
|
||||
if (([o intValue] % 60) == 0)
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%dH", ([o intValue] / 60)] forKey: @""];
|
||||
else
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"-PT%@M", o] forKey: @""];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((o = [theValues objectForKey: @"ReminderTime"]))
|
||||
{
|
||||
o = [o calendarDate];
|
||||
trigger = [iCalTrigger elementWithTag: @"TRIGGER"];
|
||||
[trigger setValueType: @"DATE-TIME"];
|
||||
[trigger setSingleValue: [NSString stringWithFormat: @"%@Z", [o iCalFormattedDateTimeString]] forKey: @""];
|
||||
[self setTrigger: trigger];
|
||||
[self setAction: @"DISPLAY"];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -602,7 +602,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
}
|
||||
|
||||
// Recurrence
|
||||
if ((o = [theValues objectForKey: @"Recurrence"]))
|
||||
if ((o = [theValues objectForKey: @"Recurrence"]) && [o isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
iCalRecurrenceRule *rule;
|
||||
|
||||
@@ -884,6 +884,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[self setAttendees: attendees];
|
||||
}
|
||||
}
|
||||
|
||||
[self setLastModified: [NSCalendarDate calendarDate]];
|
||||
}
|
||||
|
||||
- (void) changeParticipationStatus: (NSDictionary *) theValues
|
||||
|
||||
@@ -134,6 +134,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
|
||||
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, firstOccurrence];
|
||||
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
|
||||
}
|
||||
else if ([[self byMonthDay] count])
|
||||
{
|
||||
@@ -149,6 +150,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
dm = [self byDayMask];
|
||||
[s appendFormat: @"<Recurrence_DayOfWeek xmlns=\"%@:\">%d</Recurrence_DayOfWeek>", t, (1 << [dm firstDay])];
|
||||
[s appendFormat: @"<Recurrence_WeekOfMonth xmlns=\"%@:\">%d</Recurrence_WeekOfMonth>", t, 5];
|
||||
[s appendFormat: @"<Recurrence_Interval xmlns=\"%@:\">%d</Recurrence_Interval>", t, [self repeatInterval]];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -43,9 +43,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalTimeZone.h>
|
||||
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
|
||||
#include "NSDate+ActiveSync.h"
|
||||
#include "NSString+ActiveSync.h"
|
||||
#include "iCalRecurrenceRule+ActiveSync.h"
|
||||
#include "iCalAlarm+ActiveSync.h"
|
||||
|
||||
@implementation iCalToDo (ActiveSync)
|
||||
|
||||
@@ -95,8 +98,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
v = 1;
|
||||
[s appendFormat: @"<Importance xmlns=\"Tasks:\">%d</Importance>", v];
|
||||
|
||||
// Reminder - FIXME
|
||||
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
|
||||
// Reminder
|
||||
if ([self hasAlarms])
|
||||
{
|
||||
iCalAlarm *alarm;
|
||||
|
||||
alarm = [self firstDisplayOrAudioAlarm];
|
||||
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 1];
|
||||
[s appendString: [alarm activeSyncRepresentationInContext: context]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[s appendFormat: @"<ReminderSet xmlns=\"Tasks:\">%d</ReminderSet>", 0];
|
||||
}
|
||||
|
||||
// Sensitivity
|
||||
if ([[self accessClass] isEqualToString: @"PRIVATE"])
|
||||
@@ -145,7 +159,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -249,6 +263,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if ((o = [theValues objectForKey: @"ReminderTime"]))
|
||||
{
|
||||
iCalAlarm *alarm;
|
||||
|
||||
alarm = [[iCalAlarm alloc] init];
|
||||
[alarm takeActiveSyncValues: theValues inContext: context];
|
||||
|
||||
[self removeAllAlarms];
|
||||
[self addToAlarms: alarm];
|
||||
RELEASE(alarm);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,11 @@ http://trochette.github.io/Angular-Design-Patterns-Best-Practices/
|
||||
* Move business logic into models and share them.
|
||||
* Keep it simple, separate server interaction and error handling from the model. That way model only handle data processing and code is easier to maintain.
|
||||
|
||||
Controllers
|
||||
^^^^^^^^^^^
|
||||
|
||||
* Functions not exposed in the controller must be prefixed with an underscore.
|
||||
|
||||
SASS/CSS
|
||||
~~~~~~~~
|
||||
|
||||
@@ -133,11 +138,6 @@ SASS/CSS
|
||||
A `@mixin` is like a stamp: it creates a duplicated version of the property block (optionally) with arguments provided. An `@extend` appends the element you are extending to the property block. It is your "yes, and ___" statement.
|
||||
|
||||
* For documentation, we follow http://sassdoc.com/[SassDoc] annotations.
|
||||
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
* FlexBox compatibility http://caniuse.com/#feat=flexbox
|
||||
|
||||
Fonts
|
||||
@@ -155,10 +155,16 @@ Building frontend
|
||||
npm install -g grunt-cli
|
||||
npm install -g bower
|
||||
|
||||
* Generate JavaScript files
|
||||
* We need the SASS files of Angular Material to build our CSS. The git repository of Angular Material is included as a submodule of SOGo:
|
||||
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
* Generate the JavaScript and CSS files
|
||||
|
||||
cd UI/WebServerResources
|
||||
npm install
|
||||
bower install
|
||||
grunt build
|
||||
|
||||
Version Control
|
||||
|
||||
@@ -391,9 +391,14 @@ internet connection could be killed prematurely.
|
||||
|Parameter used to set the maximum allowed size for content being
|
||||
sent to SOGo using a PUT or a POST call. This can also limit the file
|
||||
attachment size being uploaded to SOGo when composing a mail. The
|
||||
value is in kilobyte. By default, the value is 0, or disabled so no
|
||||
value is in kilobytes. By default, the value is 0, or disabled so no
|
||||
limit will be set.
|
||||
|
||||
|S |SOGoMaximumMessageSizeLimit
|
||||
|Parameter used to set the maximum allowed email message size when
|
||||
composing a mail. The value is in kilobytes. By default, the value is 0,
|
||||
or disabled so no limit will be set.
|
||||
|
||||
|S |SxVMemLimit
|
||||
|Parameter used to set the maximum amount of memory (in
|
||||
megabytes) that a child can use. Reaching that value will force children
|
||||
@@ -501,7 +506,7 @@ SOGoSAML2LogoutEnabled must be set to YES. If unset, the user will be
|
||||
redirected to a blank page.
|
||||
|
||||
|D |SOGoTimeZone
|
||||
|Parameter used to set a default time zone for users. The default
|
||||
|Mandatory parameter used to set a default time zone for users. The default
|
||||
timezone is set to UTC. The Olson database is a standard database that
|
||||
takes all the time zones around the world into account and represents
|
||||
them along with their history. On GNU/Linux systems, time zone
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- TODO have the build system take care of this -->
|
||||
<releaseinfo>Version 3.2.4 - December 2016</releaseinfo>
|
||||
<subtitle>for version 3.2.4</subtitle>
|
||||
<date>2016-12-01</date>
|
||||
<releaseinfo>Version 3.2.6 - January 2017</releaseinfo>
|
||||
<subtitle>for version 3.2.6</subtitle>
|
||||
<date>2017-01-23</date>
|
||||
|
||||
<legalnotice>
|
||||
<para>Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".</para>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
Authors:
|
||||
- Inverse inc. <info@inverse.ca>
|
||||
|
||||
Copyright (C) 2008-2016 Inverse inc.
|
||||
Copyright (C) 2008-2017 Inverse inc.
|
||||
License: GFDL 1.2 or later. http://www.gnu.org/licenses/fdl.html
|
||||
|
||||
////
|
||||
|
||||
// TODO have the build system take care of this
|
||||
|
||||
:release_version: 3.2.4
|
||||
:release_version: 3.2.6
|
||||
|
||||
// vim: set syntax=asciidoc tabstop=2 shiftwidth=2 expandtab:
|
||||
+10
-3
@@ -27,6 +27,7 @@
|
||||
#import <GDLContentStore/GCSAlarmsFolder.h>
|
||||
#import <GDLContentStore/GCSSessionsFolder.h>
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGObjWeb/SoClassSecurityInfo.h>
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
#import <NGObjWeb/WORequest+So.h>
|
||||
@@ -114,7 +115,7 @@ static BOOL debugLeaks;
|
||||
vMemSizeLimit = [defaults vmemLimit];
|
||||
if (vMemSizeLimit > 0)
|
||||
[self logWithFormat: @"vmem size check enabled: shutting down app when "
|
||||
@"vmem > %d MB", vMemSizeLimit];
|
||||
@"vmem > %d MB. Currently at %d MB", vMemSizeLimit, [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
|
||||
|
||||
/* SoClass security declarations */
|
||||
sInfo = [self soClassSecurityInfo];
|
||||
@@ -137,6 +138,8 @@ static BOOL debugLeaks;
|
||||
|
||||
/* load products */
|
||||
[[SOGoProductLoader productLoader] loadAllProducts: YES];
|
||||
if (vMemSizeLimit > 0)
|
||||
[self logWithFormat: @"All products loaded - current memory usage at %d MB", [[NSProcessInfo processInfo] virtualMemorySize]/1048576];
|
||||
}
|
||||
|
||||
- (id) init
|
||||
@@ -418,8 +421,12 @@ static BOOL debugLeaks;
|
||||
"GET" if no method was provided in the query path.
|
||||
*/
|
||||
if ([_key length] > 0 && ![_key isEqualToString:@"favicon.ico"])
|
||||
obj = [self lookupUser: _key inContext: _ctx];
|
||||
}
|
||||
{
|
||||
obj = [self lookupUser: _key inContext: _ctx];
|
||||
if (!obj && ![_key isEqualToString: @"public"])
|
||||
obj = [self lookupUser: @"anonymous" inContext: _ctx];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
obj = nil;
|
||||
|
||||
@@ -1,3 +1,74 @@
|
||||
3.2.6 (2017-01-23)
|
||||
------------------
|
||||
|
||||
Enhancements
|
||||
- [web] show locale codes beside language names in Preferences module
|
||||
- [web] fixed visual glitches in Month view with Firefox
|
||||
- [web] mail editor can now be expanded horizontally and automatically expands vertically
|
||||
- [web] compose a new message inline or in a popup window
|
||||
- [web] allow to select multiple files when uploading attachments (#3999)
|
||||
- [web] use "date" extension of Sieve to enable/disable vacation auto-reply (#1530, #1949)
|
||||
- [web] updated Angular to version 1.6.1
|
||||
- [web] updated CKEditor to version 4.6.2
|
||||
|
||||
Bug fixes
|
||||
- [core] remove all alarms before sending IMIP replies (#3925)
|
||||
- [web] fixed rendering of forwared HTML message with inline images (#3981)
|
||||
- [web] fixed pasting images in CKEditor using Chrome (#3986)
|
||||
- [eas] make sure we trigger a download of service-side changed events
|
||||
- [eas] now strip attendees with no email during MeetingResponse calls
|
||||
|
||||
3.2.5 (2017-01-10)
|
||||
------------------
|
||||
|
||||
New features
|
||||
- [web] download attachments of a message as a zip archive
|
||||
|
||||
Enhancements
|
||||
- [core] improved IMIP handling from Exchange/Outlook clients
|
||||
- [web] prevent using localhost on additional IMAP accounts
|
||||
- [web] renamed buttons of alarm toast (#3945)
|
||||
- [web] load photos of LDAP-based address books in contacts list (#3942)
|
||||
- [web] added SOGoMaximumMessageSizeLimit to limit webmail message size
|
||||
- [web] added photo support for LDIF import (#1084)
|
||||
- [web] updated CKEditor to version 4.6.1
|
||||
|
||||
Bug fixes
|
||||
- [core] honor blocking wrong login attempts within time interval (#2850)
|
||||
- [core] better support for RFC 6638 (schedule-agent)
|
||||
- [core] use source's domain when none defined and trying to match users (#3523)
|
||||
- [core] handle delegation with no SENT-BY set (#3368)
|
||||
- [core] properly honor the "include in freebusy" setting (#3354)
|
||||
- [core] properly save next email alarm in the database (#3949)
|
||||
- [core] fix events in floating time during CalDAV's PUT operation (#2865)
|
||||
- [core] handle rounds in sha512-crypt password hashes
|
||||
- [web] fixed confusion between owner and active user in ACLs management of Administration module
|
||||
- [web] fixed JavaScript exception after renaming an address book
|
||||
- [web] fixed Sieve folder encoding support (#3904)
|
||||
- [web] fixed ordering of calendars when renaming or adding a calendar (#3931)
|
||||
- [web] use the organizer's alarm by default when accepting IMIP messages (#3934)
|
||||
- [web] switch on "Remember username" when cookie username is set
|
||||
- [web] return login page for unknown users (#2135)
|
||||
- [web] fixed saving monthly recurrence rule with "by day" condition (#3948)
|
||||
- [web] fixed display of message content when enabling auto-reply (#3940)
|
||||
- [web] don't allow to create lists in a remote address book (not yet supported)
|
||||
- [web] fixed attached links in task viewer (#3963)
|
||||
- [web] avoid duplicate mail entries in contact of LDAP-based address book (#3941)
|
||||
- [web] append ics file extension when importing events (#2308)
|
||||
- [web] handle URI in vCard photos (#2683)
|
||||
- [web] handle semicolon in values during LDIF import (#1760)
|
||||
- [web] fixed computation of week number (#3973, #3976)
|
||||
- [web] fixed saving of inactive calendars (#3862, #3980)
|
||||
- [web] fixed public URLs to Calendars (#3974)
|
||||
- [web] fixed hotkeys in Mail module when a dialog is active (#3983)
|
||||
- [eas] properly skip folders we don't want to synchronize (#3943)
|
||||
- [eas] fixed 30 mins freebusy offset with S Planner
|
||||
- [eas] now correctly handles reminders on tasks (#3964)
|
||||
- [eas] always force save events creation over EAS (#3958)
|
||||
- [eas] do not decode from hex the event's UID (#3965)
|
||||
- [eas] add support for "other addresses" (#3966)
|
||||
- [eas] provide correct response status when sending too big mails (#3956)
|
||||
|
||||
3.2.4 (2016-12-01)
|
||||
------------------
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@
|
||||
long i, count, repeatCount;
|
||||
unsigned interval;
|
||||
|
||||
//[self logWithFormat: @"Recurrence rule is %@", rrule];
|
||||
|
||||
firStart = [firstRange startDate];
|
||||
startDate = [_r startDate];
|
||||
endDate = [_r endDate];
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
[updatedProperties addObject:@"accessClass"];
|
||||
if(!IS_EQUAL([_from sequence], [_to sequence], isEqualToNumber:))
|
||||
[updatedProperties addObject:@"sequence"];
|
||||
if(!IS_EQUAL([_from organizer], [_to organizer], isEqual:))
|
||||
if(!IS_EQUAL([[_from organizer] rfc822Email], [[_to organizer] rfc822Email], isEqual:))
|
||||
[updatedProperties addObject:@"organizer"];
|
||||
if(!IS_EQUAL([_from recurrenceRules], [_to recurrenceRules], isEqual:))
|
||||
[updatedProperties addObject:@"rrule"];
|
||||
|
||||
@@ -108,9 +108,9 @@ extern NSString *iCalWeekDayString[];
|
||||
|
||||
/* parse complete iCal RRULE */
|
||||
|
||||
- (void) setRrule: (NSString *) _rrule; // TODO: weird name? (better: RRule?)
|
||||
- (void) setRrule: (NSString *) _rrule;
|
||||
|
||||
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;;
|
||||
- (NSString *) iCalRepresentationForWeekDay: (iCalWeekDay) _weekDay;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -921,4 +921,28 @@ NSString *iCalWeekDayString[] = { @"SU", @"MO", @"TU", @"WE", @"TH", @"FR",
|
||||
return isEqual;
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
{
|
||||
NSMutableString *str;
|
||||
|
||||
str = [NSMutableString stringWithString: [self flattenedValuesForKey: @"freq"]];
|
||||
|
||||
if ([self repeatInterval] > 1)
|
||||
[str appendFormat: @" interval of %i", [self repeatInterval]];
|
||||
if ([self repeatCount])
|
||||
[str appendFormat: @" for %i times", [self repeatCount]];
|
||||
if ([self untilDate])
|
||||
[str appendFormat: @" until %@", [self untilDate]];
|
||||
if ([[self valuesAtIndex: 0 forKey: @"bymonthday"] count])
|
||||
[str appendFormat: @" BYMONTHDAY:%@", [self flattenedValuesForKey: @"bymonthday"]];
|
||||
if ([[self valuesAtIndex: 0 forKey: @"byday"] count])
|
||||
[str appendFormat: @" BYDAY:%@", [self flattenedValuesForKey: @"byday"]];
|
||||
if ([[self valuesAtIndex: 0 forKey: @"bymonth"] count])
|
||||
[str appendFormat: @" BYMONTH:%@", [self flattenedValuesForKey: @"bymonth"]];
|
||||
if ([[self valuesAtIndex: 0 forKey: @"bysetpos"] count])
|
||||
[str appendFormat: @" BYSETPOS:%@", [self flattenedValuesForKey: @"bysetpos"]];
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@end /* iCalRecurrenceRule */
|
||||
@@ -70,7 +70,7 @@
|
||||
iCalByDayMask *dayMask;
|
||||
BOOL hasRepeatCount;
|
||||
|
||||
[self logWithFormat: @"Weekly %@", rrule];
|
||||
//[self logWithFormat: @"Recurrence rule is %@", rrule];
|
||||
|
||||
firStart = [firstRange startDate];
|
||||
startDate = [_r startDate];
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@
|
||||
//SOGoTrustProxyAuthentication = NO;
|
||||
//SOGoXSRFValidationEnabled = YES;
|
||||
|
||||
/* General */
|
||||
/* General - SOGoTimeZone *MUST* be defined */
|
||||
//SOGoLanguage = English;
|
||||
//SOGoTimeZone = America/Montreal;
|
||||
//SOGoCalendarDefaultRoles = (
|
||||
|
||||
@@ -133,6 +133,7 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, [rawRequest UTF8String]);
|
||||
//curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
|
||||
//curl_easy_setopt(curl, CURLOPT_HEADER, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function_freebusy);
|
||||
@@ -141,8 +142,9 @@ size_t curl_body_function_freebusy(void *ptr, size_t size, size_t nmemb, void *i
|
||||
|
||||
// Perform SOAP request
|
||||
rc = curl_easy_perform(curl);
|
||||
if (rc != 0)
|
||||
[self errorWithFormat: @"CURL error while accessing %@ (%d): ", url, rc, [NSString stringWithCString: error]];
|
||||
if (rc != CURLE_OK)
|
||||
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", url, rc,
|
||||
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
|
||||
curl_easy_cleanup(curl);
|
||||
curl_slist_free_all(headerlist);
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
|
||||
- (NSString *) bias
|
||||
{
|
||||
#if 0
|
||||
NSTimeZone *userTimeZone;
|
||||
NSInteger secs;
|
||||
|
||||
@@ -121,6 +122,9 @@
|
||||
secs = [userTimeZone secondsFromGMT];
|
||||
|
||||
return [NSString stringWithFormat: @"%i", (secs/60)];
|
||||
#else
|
||||
return @"0";
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -423,26 +423,32 @@ static Class iCalEventK = nil;
|
||||
//
|
||||
// If the user is the owner of the calendar, by default we include the freebusy information.
|
||||
//
|
||||
// If the user is NOT the owner of the calendar, by default we exclude the freebusy information.
|
||||
// If the user is NOT the owner of the calendar but is subscribed, by default we exclude the freebusy information.
|
||||
//
|
||||
// We must include the freebusy information of other users if we are actually looking at their freebusy information
|
||||
// but we aren't necessarily subscribed to their calendars.
|
||||
//
|
||||
- (BOOL) includeInFreeBusy
|
||||
{
|
||||
NSString *userLogin, *subscriptionReference;
|
||||
NSNumber *excludeFromFreeBusy;
|
||||
NSString *userLogin;
|
||||
BOOL is_owner;
|
||||
|
||||
BOOL is_owner, is_subscription;
|
||||
|
||||
userLogin = [[context activeUser] login];
|
||||
is_owner = [userLogin isEqualToString: self->owner];
|
||||
|
||||
// We check if we're subscribed. We must NOT use here [self isSubscribed] as it'll look
|
||||
// at the owner's settings, and not the activeUser's settings.
|
||||
subscriptionReference = [NSString stringWithFormat: @"%@:Calendar/%@", self->owner, [self nameInContainer]];
|
||||
is_subscription = [[[[[self->context activeUser] userSettings] objectForKey: @"Calendar"] objectForKey: @"SubscribedFolders"] containsObject: subscriptionReference];
|
||||
|
||||
// Check if the owner (not the active user) has excluded the calendar from her/his free busy data.
|
||||
excludeFromFreeBusy
|
||||
= [self folderPropertyValueInCategory: @"FreeBusyExclusions"
|
||||
forUser: [context activeUser]];
|
||||
|
||||
if ([self isSubscription])
|
||||
if (is_subscription)
|
||||
{
|
||||
// If the user has not yet set an include/not include fb information let's EXCLUDE it.
|
||||
if (!excludeFromFreeBusy)
|
||||
@@ -696,7 +702,7 @@ static Class iCalEventK = nil;
|
||||
if ([title length])
|
||||
[baseWhere
|
||||
addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
|
||||
[title asSafeSQLString]]];
|
||||
|
||||
if (component)
|
||||
{
|
||||
@@ -1436,14 +1442,14 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
if ([filters isEqualToString:@"title_Category_Location"] || [filters isEqualToString:@"entireContent"])
|
||||
{
|
||||
[baseWhere addObject: [NSString stringWithFormat: @"(c_title isCaseInsensitiveLike: '%%%@%%' OR c_category isCaseInsensitiveLike: '%%%@%%' OR c_location isCaseInsensitiveLike: '%%%@%%')",
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"],
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
|
||||
[title asSafeSQLString],
|
||||
[title asSafeSQLString],
|
||||
[title asSafeSQLString]]];
|
||||
}
|
||||
}
|
||||
else
|
||||
[baseWhere addObject: [NSString stringWithFormat: @"c_title isCaseInsensitiveLike: '%%%@%%'",
|
||||
[title stringByReplacingString: @"'" withString: @"\\'\\'"]]];
|
||||
[title asSafeSQLString]]];
|
||||
}
|
||||
|
||||
/* prepare mandatory fields */
|
||||
@@ -2619,7 +2625,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
if (uid && folder)
|
||||
{
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_uid = %@",
|
||||
uid];
|
||||
[uid asSafeSQLString]];
|
||||
records = [folder fetchFields: nameFields matchingQualifier: qualifier];
|
||||
count = [records count];
|
||||
if (count)
|
||||
@@ -3237,6 +3243,12 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
}
|
||||
}
|
||||
|
||||
// If the UID isn't ending with the ".ics" extension, let's add it to avoid
|
||||
// confusing broken CalDAV client (like Nokia N9 and Korganizer) that relies
|
||||
// on this (see #2308)
|
||||
if (![[uid lowercaseString] hasSuffix: @".ics"])
|
||||
uid = [NSString stringWithFormat: @"%@.ics", uid];
|
||||
|
||||
object = [SOGoAppointmentObject objectWithName: uid
|
||||
inContainer: self];
|
||||
[object setIsNew: YES];
|
||||
@@ -3316,7 +3328,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
else
|
||||
{
|
||||
// If the start date is a "floating time", let's use the user's timezone
|
||||
// during the import for both the start and end dates.
|
||||
// during the import for both the start and end dates. This is similar
|
||||
// to what we do in SOGoAppointmentObject: -_adjustFloatingTimeInRequestCalendar:
|
||||
NSString *s;
|
||||
|
||||
s = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
|
||||
@@ -3400,8 +3413,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
timezone: timezone]))
|
||||
{
|
||||
imported++;
|
||||
[uids setValue: uid
|
||||
forKey: originalUid];
|
||||
[uids setValue: uid forKey: originalUid];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#import "SOGoCalendarComponent.h"
|
||||
|
||||
@interface SOGoAppointmentObject : SOGoCalendarComponent
|
||||
{
|
||||
BOOL _resourceHasAutoAccepted;
|
||||
}
|
||||
|
||||
- (NSException *) changeParticipationStatus: (NSString *) status
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
@@ -57,6 +60,8 @@
|
||||
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
|
||||
fromRequest: (WORequest *) rq;
|
||||
|
||||
- (BOOL) resourceHasAutoAccepted;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __Appointments_SOGoAppointmentObject_H__ */
|
||||
@@ -369,6 +369,52 @@
|
||||
return listHasChanged;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) _shouldScheduleEvent: (iCalPerson *) thePerson
|
||||
{
|
||||
//NSArray *userAgents;
|
||||
NSString *v;
|
||||
BOOL b;
|
||||
//int i;
|
||||
|
||||
b = YES;
|
||||
|
||||
if (thePerson && (v = [thePerson value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
|
||||
{
|
||||
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
|
||||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
|
||||
b = NO;
|
||||
}
|
||||
|
||||
//
|
||||
// If we have to deal with Thunderbird/Lightning, we always send invitation
|
||||
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
|
||||
// to NONE/CLIENT when responding to an external invitation received by
|
||||
// SOGo - so no invitation responses are ever sent by Lightning. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
|
||||
//
|
||||
// This code has been disabled - see 0003274.
|
||||
//
|
||||
#if 0
|
||||
userAgents = [[context request] headersForKey: @"User-Agent"];
|
||||
|
||||
for (i = 0; i < [userAgents count]; i++)
|
||||
{
|
||||
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
|
||||
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
|
||||
{
|
||||
b = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -394,11 +440,12 @@
|
||||
owner: owner];
|
||||
}
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Update"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: updateAttendees
|
||||
withType: @"calendar:invitation-update"];
|
||||
if ([self _shouldScheduleEvent: [newEvent organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Update"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: updateAttendees
|
||||
withType: @"calendar:invitation-update"];
|
||||
}
|
||||
|
||||
// This method scans the list of attendees.
|
||||
@@ -503,7 +550,9 @@
|
||||
NSEnumerator *enumerator;
|
||||
NSString *currentUID;
|
||||
SOGoUser *user, *currentUser;
|
||||
|
||||
|
||||
_resourceHasAutoAccepted = NO;
|
||||
|
||||
// Build a list of the attendees uids
|
||||
attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]];
|
||||
enumerator = [theAttendees objectEnumerator];
|
||||
@@ -643,6 +692,7 @@
|
||||
{
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -713,6 +763,7 @@
|
||||
// set the resource as one!
|
||||
[[currentAttendee attributes] removeObjectForKey: @"RSVP"];
|
||||
[currentAttendee setParticipationStatus: iCalPersonPartStatAccepted];
|
||||
_resourceHasAutoAccepted = YES;
|
||||
}
|
||||
} // if ([user isResource]) ...
|
||||
|
||||
@@ -823,11 +874,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
{
|
||||
[self _handleRemovedUsers: deletedAttendees
|
||||
withRecurrenceId: [newEvent recurrenceId]];
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [newEvent itipEntryWithMethod: @"cancel"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: deletedAttendees
|
||||
withType: @"calendar:cancellation"];
|
||||
if ([self _shouldScheduleEvent: [newEvent organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [newEvent itipEntryWithMethod: @"cancel"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: deletedAttendees
|
||||
withType: @"calendar:cancellation"];
|
||||
}
|
||||
|
||||
if ((ex = [self _handleAttendeesConflicts: [newEvent attendees] forEvent: newEvent force: forceSave]))
|
||||
@@ -883,12 +935,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
// Send an invitation to new attendees
|
||||
if ((ex = [self _handleAddedUsers: addedAttendees fromEvent: newEvent force: forceSave]))
|
||||
return ex;
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: addedAttendees
|
||||
withType: @"calendar:invitation"];
|
||||
|
||||
if ([self _shouldScheduleEvent: [newEvent organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: oldEvent
|
||||
toAttendees: addedAttendees
|
||||
withType: @"calendar:invitation"];
|
||||
}
|
||||
|
||||
if ([changes hasMajorChanges])
|
||||
@@ -962,11 +1015,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
|
||||
if ([attendees count])
|
||||
{
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:invitation"];
|
||||
if ([self _shouldScheduleEvent: [newEvent organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [newEvent itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:invitation"];
|
||||
}
|
||||
|
||||
[self sendReceiptEmailForObject: newEvent
|
||||
@@ -1166,19 +1220,17 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
statusChange: (NSString *) newStatus
|
||||
inEvent: (iCalEvent *) event
|
||||
{
|
||||
iCalPerson *otherAttendee, *otherDelegate;
|
||||
NSString *currentStatus, *organizerUID;
|
||||
SOGoUser *ownerUser, *currentUser;
|
||||
NSString *delegateEmail;
|
||||
NSException *ex;
|
||||
|
||||
ex = nil;
|
||||
BOOL addDelegate, removeDelegate;
|
||||
|
||||
currentStatus = [attendee partStat];
|
||||
|
||||
iCalPerson *otherAttendee, *otherDelegate;
|
||||
NSString *delegateEmail;
|
||||
BOOL addDelegate, removeDelegate;
|
||||
|
||||
otherAttendee = attendee;
|
||||
ex = nil;
|
||||
|
||||
delegateEmail = [otherAttendee delegatedTo];
|
||||
if ([delegateEmail length])
|
||||
@@ -1189,7 +1241,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
else
|
||||
otherDelegate = nil;
|
||||
|
||||
/* We handle the addition/deletion of delegate users */
|
||||
// We handle the addition/deletion of delegate users
|
||||
addDelegate = NO;
|
||||
removeDelegate = NO;
|
||||
if (delegate)
|
||||
@@ -1272,12 +1324,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
else
|
||||
otherDelegate = nil;
|
||||
}
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [event itipEntryWithMethod: @"cancel"]
|
||||
previousObject: nil
|
||||
toAttendees: delegates
|
||||
withType: @"calendar:cancellation"];
|
||||
|
||||
if ([self _shouldScheduleEvent: [event organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [event itipEntryWithMethod: @"cancel"]
|
||||
previousObject: nil
|
||||
toAttendees: delegates
|
||||
withType: @"calendar:cancellation"];
|
||||
} // if (removeDelegate)
|
||||
|
||||
if (addDelegate)
|
||||
@@ -1291,12 +1344,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
[self _addOrUpdateEvent: event
|
||||
forUID: delegatedUID
|
||||
owner: [theOwnerUser login]];
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [event itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: delegates
|
||||
withType: @"calendar:invitation"];
|
||||
|
||||
if ([self _shouldScheduleEvent: [event organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [event itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: delegates
|
||||
withType: @"calendar:invitation"];
|
||||
} // if (addDelegate)
|
||||
|
||||
// If the current user isn't the organizer of the event
|
||||
@@ -1477,9 +1531,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
// within the repeating vEvent.
|
||||
recurrenceTime = [NSString stringWithFormat: @"%f", [_recurrenceId timeIntervalSince1970]];
|
||||
event = (iCalEvent*)[self lookupOccurrence: recurrenceTime];
|
||||
|
||||
|
||||
// If no occurence found, create one
|
||||
if (event == nil)
|
||||
// If no occurence found, create one
|
||||
event = (iCalEvent*)[self newOccurenceWithID: recurrenceTime];
|
||||
}
|
||||
else
|
||||
@@ -1558,50 +1612,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
return ex;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (BOOL) _shouldScheduleEvent: (iCalPerson *) theOrganizer
|
||||
{
|
||||
NSArray *userAgents;
|
||||
NSString *v;
|
||||
BOOL b;
|
||||
int i;
|
||||
|
||||
b = YES;
|
||||
|
||||
if (theOrganizer && (v = [theOrganizer value: 0 ofAttribute: @"SCHEDULE-AGENT"]))
|
||||
{
|
||||
if ([v caseInsensitiveCompare: @"NONE"] == NSOrderedSame ||
|
||||
[v caseInsensitiveCompare: @"CLIENT"] == NSOrderedSame)
|
||||
b = NO;
|
||||
}
|
||||
|
||||
//
|
||||
// If we have to deal with Thunderbird/Lightning, we always send invitation
|
||||
// reponses, as Lightning v2.6 (at least this version) sets SCHEDULE-AGENT
|
||||
// to NONE/CLIENT when responding to an external invitation received by
|
||||
// SOGo - so no invitation responses are ever sent by Lightning. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=865726 and
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=997784
|
||||
//
|
||||
userAgents = [[context request] headersForKey: @"User-Agent"];
|
||||
|
||||
for (i = 0; i < [userAgents count]; i++)
|
||||
{
|
||||
if ([[userAgents objectAtIndex: i] rangeOfString: @"Thunderbird"].location != NSNotFound &&
|
||||
[[userAgents objectAtIndex: i] rangeOfString: @"Lightning"].location != NSNotFound)
|
||||
{
|
||||
b = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -1613,14 +1623,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
iCalEvent *event;
|
||||
BOOL send_receipt;
|
||||
|
||||
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
event = [self component: NO secure: NO];
|
||||
send_receipt = YES;
|
||||
|
||||
if (![self _shouldScheduleEvent: [event organizer]])
|
||||
return;
|
||||
|
||||
if (occurence == nil)
|
||||
{
|
||||
// No occurence specified; use the master event.
|
||||
@@ -1646,11 +1652,13 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
// and send them an email.
|
||||
[self _handleRemovedUsers: attendees
|
||||
withRecurrenceId: recurrenceId];
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [occurence itipEntryWithMethod: @"cancel"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:cancellation"];
|
||||
|
||||
if ([self _shouldScheduleEvent: [event organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Deletion"
|
||||
forObject: [occurence itipEntryWithMethod: @"cancel"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:cancellation"];
|
||||
}
|
||||
}
|
||||
else if ([occurence userIsAttendee: ownerUser])
|
||||
@@ -1664,12 +1672,12 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
send_receipt = NO;
|
||||
}
|
||||
|
||||
if (send_receipt)
|
||||
[self sendReceiptEmailForObject: event
|
||||
addedAttendees: nil
|
||||
deletedAttendees: nil
|
||||
updatedAttendees: nil
|
||||
operation: EventDeleted];
|
||||
if (send_receipt)
|
||||
[self sendReceiptEmailForObject: event
|
||||
addedAttendees: nil
|
||||
deletedAttendees: nil
|
||||
updatedAttendees: nil
|
||||
operation: EventDeleted];
|
||||
}
|
||||
|
||||
- (NSException *) prepareDelete
|
||||
@@ -1853,6 +1861,54 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// This is similar to what we do in SOGoAppointmentFolder: -importCalendar:
|
||||
//
|
||||
- (void) _adjustFloatingTimeInRequestCalendar: (iCalCalendar *) rqCalendar
|
||||
{
|
||||
iCalDateTime *startDate, *endDate;
|
||||
NSString *startDateAsString;
|
||||
SOGoUserDefaults *ud;
|
||||
NSArray *allEvents;
|
||||
iCalTimeZone *tz;
|
||||
iCalEvent *event;
|
||||
int i, delta;
|
||||
|
||||
allEvents = [rqCalendar events];
|
||||
for (i = 0; i < [allEvents count]; i++)
|
||||
{
|
||||
event = [allEvents objectAtIndex: i];
|
||||
|
||||
if ([event isAllDay])
|
||||
continue;
|
||||
|
||||
startDate = (iCalDateTime *)[event uniqueChildWithTag: @"dtstart"];
|
||||
startDateAsString = [[startDate valuesAtIndex: 0 forKey: @""] objectAtIndex: 0];
|
||||
|
||||
if (![startDate timeZone] &&
|
||||
![startDateAsString hasSuffix: @"Z"] &&
|
||||
![startDateAsString hasSuffix: @"z"])
|
||||
{
|
||||
ud = [[context activeUser] userDefaults];
|
||||
tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]];
|
||||
if ([rqCalendar addTimeZone: tz])
|
||||
{
|
||||
delta = [[tz periodForDate: [startDate dateTime]] secondsOffsetFromGMT];
|
||||
|
||||
[event setStartDate: [[event startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[startDate setTimeZone: tz];
|
||||
|
||||
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
|
||||
if (endDate)
|
||||
{
|
||||
[event setEndDate: [[event endDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: -delta]];
|
||||
[endDate setTimeZone: tz];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _decomposeGroupsInRequestCalendar: (iCalCalendar *) rqCalendar
|
||||
{
|
||||
NSArray *allEvents;
|
||||
@@ -1942,9 +1998,11 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
- (NSException *) updateContentWithCalendar: (iCalCalendar *) calendar
|
||||
fromRequest: (WORequest *) rq
|
||||
{
|
||||
SOGoUser *ownerUser;
|
||||
NSException *ex;
|
||||
NSArray *roles;
|
||||
SOGoUser *ownerUser;
|
||||
|
||||
BOOL userIsOrganizer;
|
||||
|
||||
if (calendar == fullCalendar || calendar == safeCalendar
|
||||
|| calendar == originalCalendar)
|
||||
@@ -1974,6 +2032,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
[self _adjustEventsInRequestCalendar: calendar];
|
||||
[self adjustClassificationInRequestCalendar: calendar];
|
||||
[self _adjustPartStatInRequestCalendar: calendar];
|
||||
[self _adjustFloatingTimeInRequestCalendar: calendar];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1984,13 +2043,10 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
iCalEvent *event;
|
||||
NSArray *attendees;
|
||||
NSString *eventUID;
|
||||
BOOL scheduling;
|
||||
|
||||
attendees = nil;
|
||||
|
||||
event = [[calendar events] objectAtIndex: 0];
|
||||
eventUID = [event uid];
|
||||
scheduling = [self _shouldScheduleEvent: [event organizer]];
|
||||
attendees = nil;
|
||||
|
||||
// make sure eventUID doesn't conflict with an existing event - see bug #1853
|
||||
// TODO: send out a no-uid-conflict (DAV:href) xml element (rfc4791 section 5.3.2.1)
|
||||
@@ -2003,34 +2059,51 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
//
|
||||
// New event and we're the organizer -- send invitation to all attendees
|
||||
//
|
||||
if (scheduling && [event userIsOrganizer: ownerUser])
|
||||
{
|
||||
attendees = [event attendeesWithoutUser: ownerUser];
|
||||
if ([attendees count])
|
||||
{
|
||||
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
|
||||
return ex;
|
||||
else
|
||||
{
|
||||
// We might have auto-accepted resources here. If that's the
|
||||
// case, let's regenerate the versitstring and replace the
|
||||
// one from the request.
|
||||
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
|
||||
}
|
||||
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [event itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:invitation"];
|
||||
}
|
||||
}
|
||||
userIsOrganizer = [event userIsOrganizer: ownerUser];
|
||||
|
||||
// We handle the situation where the SOGo Integrator extension isn't installed or
|
||||
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
|
||||
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
|
||||
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
|
||||
// and set Bob as the organizer. We fix this for them. See #3368 for details.
|
||||
if (!userIsOrganizer &&
|
||||
[[context activeUser] hasEmail: [[event organizer] rfc822Email]])
|
||||
{
|
||||
[[event organizer] setCn: [ownerUser cn]];
|
||||
[[event organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
|
||||
[[event organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
|
||||
userIsOrganizer = YES;
|
||||
}
|
||||
|
||||
if (userIsOrganizer)
|
||||
{
|
||||
attendees = [event attendeesWithoutUser: ownerUser];
|
||||
if ([attendees count])
|
||||
{
|
||||
if ((ex = [self _handleAddedUsers: attendees fromEvent: event force: YES]))
|
||||
return ex;
|
||||
else
|
||||
{
|
||||
// We might have auto-accepted resources here. If that's the
|
||||
// case, let's regenerate the versitstring and replace the
|
||||
// one from the request.
|
||||
[rq setContent: [[[event parent] versitString] dataUsingEncoding: [rq contentEncoding]]];
|
||||
}
|
||||
|
||||
if ([self _shouldScheduleEvent: [event organizer]])
|
||||
[self sendEMailUsingTemplateNamed: @"Invitation"
|
||||
forObject: [event itipEntryWithMethod: @"request"]
|
||||
previousObject: nil
|
||||
toAttendees: attendees
|
||||
withType: @"calendar:invitation"];
|
||||
}
|
||||
}
|
||||
//
|
||||
// We aren't the organizer but we're an attendee. That can happen when
|
||||
// we receive an external invitation (IMIP/ITIP) and we accept it
|
||||
// from a CUA - it gets added to a specific CalDAV calendar using a PUT
|
||||
//
|
||||
else if (scheduling && [event userIsAttendee: ownerUser])
|
||||
else if ([event userIsAttendee: ownerUser] && [self _shouldScheduleEvent: [event userAsAttendee: ownerUser]])
|
||||
{
|
||||
[self sendIMIPReplyForEvent: event
|
||||
from: ownerUser
|
||||
@@ -2138,8 +2211,6 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
//
|
||||
if ([[newEvent attendees] count] || [[oldEvent attendees] count])
|
||||
{
|
||||
BOOL userIsOrganizer;
|
||||
|
||||
// newEvent might be nil here, if we're deleting a RECURRENCE-ID with attendees
|
||||
// If that's the case, we use the oldEvent to obtain the organizer
|
||||
if (newEvent)
|
||||
@@ -2147,6 +2218,20 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
else
|
||||
userIsOrganizer = [oldEvent userIsOrganizer: ownerUser];
|
||||
|
||||
// We handle the situation where the SOGo Integrator extension isn't installed or
|
||||
// if the SENT-BY isn't set. That can happen if Bob invites Alice by creating the event
|
||||
// in Annie's calendar. Annie should be the organizer, and Bob the SENT-BY. But most
|
||||
// broken CalDAV client that aren't identity-aware will create the event in Annie's calendar
|
||||
// and set Bob as the organizer. We fix this for them. See #3368 for details.
|
||||
if (!userIsOrganizer &&
|
||||
[[context activeUser] hasEmail: [[newEvent organizer] rfc822Email]])
|
||||
{
|
||||
[[newEvent organizer] setCn: [ownerUser cn]];
|
||||
[[newEvent organizer] setEmail: [[ownerUser allEmails] objectAtIndex: 0]];
|
||||
[[newEvent organizer] setSentBy: [NSString stringWithFormat: @"\"MAILTO:%@\"", [[[context activeUser] allEmails] objectAtIndex: 0]]];
|
||||
userIsOrganizer = YES;
|
||||
}
|
||||
|
||||
// With Thunderbird 10, if you create a recurring event with an exception
|
||||
// occurence, and invite someone, the PUT will have the organizer in the
|
||||
// recurrence-id and not in the master event. We must fix this, otherwise
|
||||
@@ -2344,4 +2429,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent
|
||||
return response;
|
||||
}
|
||||
|
||||
- (BOOL) resourceHasAutoAccepted
|
||||
{
|
||||
return _resourceHasAutoAccepted;
|
||||
}
|
||||
|
||||
@end /* SOGoAppointmentObject */
|
||||
@@ -619,7 +619,10 @@
|
||||
if (content)
|
||||
ASSIGN (originalCalendar, [iCalCalendar parseSingleFromSource: content]);
|
||||
else
|
||||
[self warnWithFormat: @"content not available, we will crash"];
|
||||
{
|
||||
[self warnWithFormat: @"content not available, we don't update the event"];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
oldMaster = (iCalRepeatableEntityObject *)
|
||||
@@ -794,6 +797,14 @@
|
||||
if ([object firstChildWithTag: @"X-SOGo-Send-Appointment-Notifications"])
|
||||
return;
|
||||
|
||||
// We never send IMIP inivitaton/deletion/update when the "initiator" is Outlook 2013/2016 over
|
||||
// the EAS protocol. That is because Outlook will always issue a SendMail command
|
||||
// with the meeting details (ie., IMIP message with METHOD:REQUEST) so there's
|
||||
// no need to send it twice. Moreover, Outlook users can also choose to NOT send
|
||||
// the IMIP messsage at all, so SOGo won't send one without user's consent
|
||||
if ([[context objectForKey: @"DeviceType"] isEqualToString: @"WindowsOutlook15"])
|
||||
return;
|
||||
|
||||
ownerUser = [SOGoUser userWithLogin: owner];
|
||||
dd = [ownerUser domainDefaults];
|
||||
if ([dd appointmentSendEMailNotifications] && [object isStillRelevant])
|
||||
@@ -933,6 +944,9 @@
|
||||
/* get WOApplication instance */
|
||||
app = [WOApplication application];
|
||||
|
||||
/* remove all alarms to avoid bug #3925 */
|
||||
[event removeAllAlarms];
|
||||
|
||||
/* construct message content */
|
||||
p = [app pageWithName: @"SOGoAptMailICalReply" inContext: context];
|
||||
[p setApt: (iCalEvent *) event];
|
||||
|
||||
@@ -124,14 +124,14 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
|
||||
url = [NSURL URLWithString: location];
|
||||
if (url)
|
||||
{
|
||||
curl_global_init (CURL_GLOBAL_SSL);
|
||||
curl = curl_easy_init ();
|
||||
curl_global_init(CURL_GLOBAL_SSL);
|
||||
curl = curl_easy_init();
|
||||
if (curl)
|
||||
{
|
||||
curl_easy_setopt (curl, CURLOPT_URL, [location UTF8String]);
|
||||
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt (curl, CURLOPT_TIMEOUT, 60L);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, [location UTF8String]);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 20L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
authInfos = [self _loadAuthData];
|
||||
@@ -145,17 +145,17 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
|
||||
/* buffering ivar, no need to retain/release */
|
||||
buffer = [NSMutableData data];
|
||||
|
||||
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, curl_body_function);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
|
||||
|
||||
error[0] = 0;
|
||||
curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, &error);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
|
||||
|
||||
// Perform SOAP request
|
||||
rc = curl_easy_perform (curl);
|
||||
if (rc == 0)
|
||||
rc = curl_easy_perform(curl);
|
||||
if (rc == CURLE_OK)
|
||||
{
|
||||
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
[result setObject: [NSNumber numberWithUnsignedInt: status]
|
||||
forKey: @"status"];
|
||||
[self logWithFormat: @"Load web calendar %@ (%i)", location, status];
|
||||
@@ -188,7 +188,11 @@ size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
else
|
||||
[result setObject: @"bad-url" forKey: @"error"];
|
||||
{
|
||||
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", location, rc,
|
||||
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
|
||||
[result setObject: @"bad-url" forKey: @"error"];
|
||||
}
|
||||
curl_easy_cleanup (curl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,7 +861,7 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
[row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"];
|
||||
|
||||
// Delete old email alarms
|
||||
if (alarm_number >= 0)
|
||||
if (!nextAlarmDate && alarm_number >= 0)
|
||||
[af deleteRecordForEntryWithCName: nameInContainer
|
||||
inCalendarAtPath: [theContainer ocsPath]];
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
- (NSString *) workCompany;
|
||||
- (NSString *) fullName;
|
||||
- (NSArray *) emails;
|
||||
- (NSArray *) secondaryEmails;
|
||||
|
||||
- (NSString *) workPhone;
|
||||
|
||||
@@ -236,6 +236,7 @@ convention:
|
||||
- (void) _setEmails: (NSDictionary *) ldifRecord
|
||||
{
|
||||
CardElement *homeMail;
|
||||
NSString* mail;
|
||||
|
||||
// Emails from the configured mail fields of the source have already been extracted in
|
||||
// [LDAPSource _fillEmailsOfEntry:intoLDIFRecord:]
|
||||
@@ -243,9 +244,11 @@ convention:
|
||||
ofType: @"work"
|
||||
withValue: [ldifRecord objectForKey: @"c_emails"]];
|
||||
// When importing an LDIF file, add the default mail attribute
|
||||
[self addElementWithTag: @"email"
|
||||
ofType: @"work"
|
||||
withValue: [ldifRecord objectForKey: @"mail"]];
|
||||
mail = [ldifRecord objectForKey: @"mail"];
|
||||
if ([mail length] && ![[self emails] containsObject: mail])
|
||||
[self addElementWithTag: @"email"
|
||||
ofType: @"work"
|
||||
withValue: [ldifRecord objectForKey: @"mail"]];
|
||||
homeMail = [self elementWithTag: @"email" ofType: @"home"];
|
||||
[homeMail setSingleValue: [ldifRecord objectForKey: @"mozillasecondemail"] forKey: @""];
|
||||
[[self uniqueChildWithTag: @"x-mozilla-html"]
|
||||
|
||||
@@ -40,25 +40,34 @@
|
||||
- (id) GETAction: (WOContext *) localContext
|
||||
{
|
||||
NGVCardPhoto *photo;
|
||||
NSData *data;
|
||||
NSString *uri;
|
||||
NSData *data;
|
||||
id response;
|
||||
|
||||
photo = [self photo];
|
||||
data = nil;
|
||||
uri = nil;
|
||||
|
||||
if ([photo isInline])
|
||||
data = [photo decodedContent];
|
||||
else
|
||||
data = [[photo flattenedValuesForKey: @""]
|
||||
dataUsingEncoding: NSISOLatin1StringEncoding];
|
||||
else if ([[photo value: 0 ofAttribute: @"value"] isEqualToString: @"uri"])
|
||||
uri = [photo flattenedValuesForKey: @""];
|
||||
|
||||
if (data)
|
||||
{
|
||||
response = [localContext response];
|
||||
|
||||
[response setHeader: [self davContentType] forKey: @"content-type"];
|
||||
[response setHeader: [NSString stringWithFormat:@" %d",
|
||||
(int)[data length]]
|
||||
forKey: @"content-length"];
|
||||
[response setContent: data];
|
||||
}
|
||||
else if (uri)
|
||||
{
|
||||
response = [localContext response];
|
||||
[response setStatus: 302];
|
||||
[response setHeader: uri forKey: @"location"];
|
||||
}
|
||||
else
|
||||
response = nil;
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ static NSArray *folderListingFields = nil;
|
||||
NSString *component;
|
||||
Class objectClass;
|
||||
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", name];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_name = %@", [name asSafeSQLString]];
|
||||
records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"c_component"]
|
||||
matchingQualifier: qualifier];
|
||||
|
||||
@@ -190,8 +190,7 @@ static NSArray *folderListingFields = nil;
|
||||
|
||||
if ([filter length] > 0)
|
||||
{
|
||||
filter = [[filter stringByReplacingString: @"\\" withString: @"\\\\"]
|
||||
stringByReplacingString: @"'" withString: @"\\'\\'"];
|
||||
filter = [filter asSafeSQLString];
|
||||
if ([criteria isEqualToString: @"name_or_address"])
|
||||
qs = [NSString stringWithFormat:
|
||||
@"(c_sn isCaseInsensitiveLike: '%%%@%%') OR "
|
||||
@@ -338,8 +337,7 @@ static NSArray *folderListingFields = nil;
|
||||
|
||||
if (aName && [aName length] > 0)
|
||||
{
|
||||
aName = [[aName stringByReplacingString: @"\\" withString: @"\\\\"]
|
||||
stringByReplacingString: @"'" withString: @"\\'\\'"];
|
||||
aName = [aName asSafeSQLString];
|
||||
qs = [NSString stringWithFormat: @"(c_name='%@')", aName];
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
|
||||
dbRecords = [[self ocsFolder] fetchFields: folderListingFields
|
||||
|
||||
@@ -338,6 +338,10 @@
|
||||
[newRecord setObject: data forKey: @"contactInfo"];
|
||||
}
|
||||
|
||||
// photo => hasPhoto
|
||||
data = [oldRecord objectForKey: @"photo"];
|
||||
[newRecord setObject: [NSNumber numberWithInt: [data length] ? 1 : 0] forKey: @"hasPhoto"];
|
||||
|
||||
recordSource = [oldRecord objectForKey: @"source"];
|
||||
if ([recordSource conformsToProtocol: @protocol (SOGoDNSource)] &&
|
||||
[[(NSObject <SOGoDNSource>*) recordSource MSExchangeHostname] length])
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
fromIndex: (int) start;
|
||||
- (int) indexOf: (unichar) _c;
|
||||
- (NSString *) decodedHeader;
|
||||
- (NSString *) asSafeFilename;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@
|
||||
@interface _SOGoHTMLContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
|
||||
{
|
||||
NSMutableArray *images;
|
||||
|
||||
|
||||
NSArray *ignoreContentTags;
|
||||
NSArray *specialTreatmentTags;
|
||||
NSArray *voidTags;
|
||||
|
||||
|
||||
BOOL ignoreContent;
|
||||
BOOL orderedList;
|
||||
BOOL unorderedList;
|
||||
@@ -300,7 +300,7 @@
|
||||
i = [value indexOf: ';'];
|
||||
j = [value indexOf: ';' fromIndex: i+1];
|
||||
k = [value indexOf: ','];
|
||||
|
||||
|
||||
// We try to get the MIME type
|
||||
mimeType = nil;
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
// 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;
|
||||
@@ -335,14 +335,14 @@
|
||||
[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: @"<img src=\"cid:%@\" type=\"%@\"", uniqueId, mimeType];
|
||||
@@ -357,7 +357,7 @@
|
||||
[result appendFormat: @" %@=\"%@\"", attrName, value];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[result appendString: @"/>"];
|
||||
}
|
||||
}
|
||||
@@ -656,19 +656,19 @@ convertChars (const char *oldString, unsigned int oldLength,
|
||||
fromIndex: (int) start
|
||||
{
|
||||
int i, len;
|
||||
|
||||
|
||||
len = [self length];
|
||||
|
||||
|
||||
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
|
||||
@@ -684,8 +684,30 @@ convertChars (const char *oldString, unsigned int oldLength,
|
||||
decodedHeader];
|
||||
if (!decodedHeader)
|
||||
decodedHeader = self;
|
||||
|
||||
|
||||
return decodedHeader;
|
||||
}
|
||||
|
||||
- (NSString *) asSafeFilename
|
||||
{
|
||||
NSRange r;
|
||||
NSMutableString *safeName;
|
||||
|
||||
r = [self rangeOfString: @"\\"
|
||||
options: NSBackwardsSearch];
|
||||
if (r.length > 0)
|
||||
safeName = [NSMutableString stringWithString: [self substringFromIndex: r.location + 1]];
|
||||
else
|
||||
safeName = [NSMutableString stringWithString: self];
|
||||
[safeName replaceString: @"/" withString: @"_"];
|
||||
|
||||
if ([self isEqualToString: @"."])
|
||||
return @"_";
|
||||
|
||||
if ([self isEqualToString: @".."])
|
||||
return @"__";
|
||||
|
||||
return safeName;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -97,7 +97,6 @@
|
||||
/* attachments */
|
||||
|
||||
- (NSArray *) fetchAttachmentAttrs;
|
||||
- (BOOL) isValidAttachmentName: (NSString *) _name;
|
||||
- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name;
|
||||
- (NSString *) pathToAttachmentWithName: (NSString *) _name;
|
||||
- (NSException *) saveAttachment: (NSData *) _attach
|
||||
|
||||
+139
-173
@@ -55,6 +55,8 @@
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
|
||||
#import <NGCards/NGVCard.h>
|
||||
|
||||
@@ -74,7 +76,7 @@
|
||||
|
||||
static NSString *contentTypeValue = @"text/plain; charset=utf-8";
|
||||
static NSString *htmlContentTypeValue = @"text/html; charset=utf-8";
|
||||
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
@"from", @"replyTo", @"message-id",
|
||||
nil};
|
||||
|
||||
@@ -142,18 +144,18 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
{
|
||||
const char *b, *bytes;
|
||||
int i, len, slen;
|
||||
|
||||
|
||||
if (!theCString)
|
||||
{
|
||||
return NSMakeRange(NSNotFound,0);
|
||||
}
|
||||
|
||||
|
||||
bytes = [self bytes];
|
||||
len = [self length];
|
||||
slen = strlen(theCString);
|
||||
|
||||
|
||||
b = bytes;
|
||||
|
||||
|
||||
if (len > theRange.location + theRange.length)
|
||||
{
|
||||
len = theRange.location + theRange.length;
|
||||
@@ -163,7 +165,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
{
|
||||
i = theRange.location;
|
||||
b += i;
|
||||
|
||||
|
||||
for (; i <= len-slen; i++, b++)
|
||||
{
|
||||
if (!strncasecmp(theCString,b,slen))
|
||||
@@ -176,7 +178,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
{
|
||||
i = theRange.location;
|
||||
b += i;
|
||||
|
||||
|
||||
for (; i <= len-slen; i++, b++)
|
||||
{
|
||||
if (!memcmp(theCString,b,slen))
|
||||
@@ -185,7 +187,7 @@ static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NSMakeRange(NSNotFound,0);
|
||||
}
|
||||
|
||||
@@ -271,7 +273,7 @@ static NSString *userAgent = nil;
|
||||
NSFileManager *fm;
|
||||
|
||||
fm = [NSFileManager defaultManager];
|
||||
|
||||
|
||||
return ([fm createDirectoriesAtPath: [container userSpoolFolderPath]
|
||||
attributes: nil]
|
||||
&& [fm createDirectoriesAtPath: [self draftFolderPath]
|
||||
@@ -308,7 +310,7 @@ static NSString *userAgent = nil;
|
||||
messageID = [NSString generateMessageID];
|
||||
[headers setObject: messageID forKey: @"message-id"];
|
||||
}
|
||||
|
||||
|
||||
priority = [newHeaders objectForKey: @"X-Priority"];
|
||||
if (priority)
|
||||
{
|
||||
@@ -641,6 +643,13 @@ static NSString *userAgent = nil;
|
||||
error = nil;
|
||||
message = [self mimeMessageAsData];
|
||||
|
||||
if (!message)
|
||||
{
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: @"Message is too big"];
|
||||
return error;
|
||||
}
|
||||
|
||||
client = [[self imap4Connection] client];
|
||||
|
||||
if (![imap4 doesMailboxExistAtURL: [container imap4URL]])
|
||||
@@ -649,7 +658,7 @@ static NSString *userAgent = nil;
|
||||
atURL: [[self mailAccountFolder] imap4URL]];
|
||||
[imap4 flushFolderHierarchyCache];
|
||||
}
|
||||
|
||||
|
||||
folder = [imap4 imap4FolderNameForURL: [container imap4URL]];
|
||||
result = [client append: message toFolder: folder
|
||||
withFlags: [NSArray arrayWithObjects: @"draft", nil]];
|
||||
@@ -745,7 +754,7 @@ static NSString *userAgent = nil;
|
||||
- if there is a 'reply-to' header, only include that (as TO)
|
||||
- if we reply to all, all non-from addresses are added as CC
|
||||
- the from is always the lone TO (except for reply-to)
|
||||
|
||||
|
||||
Note: we cannot check reply-to, because Cyrus even sets a reply-to in the
|
||||
envelope if none is contained in the message itself! (bug or
|
||||
feature?)
|
||||
@@ -776,11 +785,11 @@ static NSString *userAgent = nil;
|
||||
int i;
|
||||
|
||||
identities = [[[self container] mailAccountFolder] identities];
|
||||
|
||||
|
||||
for (i = 0; i < [identities count]; i++)
|
||||
{
|
||||
email = [[identities objectAtIndex: i] objectForKey: @"email"];
|
||||
|
||||
|
||||
if (email)
|
||||
[allRecipients addObject: email];
|
||||
}
|
||||
@@ -838,66 +847,29 @@ static NSString *userAgent = nil;
|
||||
[self _purgeRecipients: allRecipients
|
||||
fromAddresses: addrs];
|
||||
[self _addEMailsOfAddresses: addrs toArray: to];
|
||||
|
||||
|
||||
[_info setObject: to forKey: @"cc"];
|
||||
|
||||
[to release];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
|
||||
fromResponseFetch: (NSDictionary *) fetch;
|
||||
{
|
||||
NSEnumerator *attachmentKeys;
|
||||
NSMutableArray *bodies;
|
||||
NSString *currentKey;
|
||||
NSDictionary *body;
|
||||
|
||||
bodies = [NSMutableArray array];
|
||||
|
||||
attachmentKeys = [paths objectEnumerator];
|
||||
while ((currentKey = [attachmentKeys nextObject]))
|
||||
{
|
||||
body = [fetch objectForKey: [currentKey lowercaseString]];
|
||||
if (body)
|
||||
[bodies addObject: [body objectForKey: @"data"]];
|
||||
else
|
||||
[bodies addObject: [NSData data]];
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail
|
||||
{
|
||||
unsigned int count, max;
|
||||
NSArray *parts, *paths, *bodies;
|
||||
NSData *body;
|
||||
unsigned int max, count;
|
||||
NSArray *attachments;
|
||||
NSDictionary *currentInfo;
|
||||
NGHashMap *response;
|
||||
|
||||
parts = [sourceMail fetchFileAttachmentKeys];
|
||||
max = [parts count];
|
||||
if (max > 0)
|
||||
attachments = [sourceMail fetchFileAttachments];
|
||||
max = [attachments count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
paths = [parts keysWithFormat: @"BODY[%{path}]"];
|
||||
response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"];
|
||||
bodies = [self _attachmentBodiesFromPaths: paths
|
||||
fromResponseFetch: [response objectForKey: @"fetch"]];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentInfo = [parts objectAtIndex: count];
|
||||
body = [[bodies objectAtIndex: count]
|
||||
bodyDataFromEncoding: [currentInfo
|
||||
objectForKey: @"encoding"]];
|
||||
[self saveAttachment: body withMetadata: currentInfo];
|
||||
}
|
||||
currentInfo = [attachments objectAtIndex: count];
|
||||
[self saveAttachment: [currentInfo objectForKey: @"body"]
|
||||
withMetadata: currentInfo];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,14 +974,14 @@ static NSString *userAgent = nil;
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
[sourceMail fetchCoreInfos];
|
||||
|
||||
|
||||
if ([sourceMail subjectForForward])
|
||||
{
|
||||
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
|
||||
info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
|
||||
forKey: @"subject"];
|
||||
[self setHeaders: info];
|
||||
}
|
||||
|
||||
|
||||
[self setSourceURL: [sourceMail imap4URLString]];
|
||||
[self setSourceFlag: @"$Forwarded"];
|
||||
[self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]];
|
||||
@@ -1052,7 +1024,7 @@ static NSString *userAgent = nil;
|
||||
- (NSString *) sender
|
||||
{
|
||||
id tmp;
|
||||
|
||||
|
||||
if ((tmp = [headers objectForKey: @"from"]) == nil)
|
||||
return nil;
|
||||
if ([tmp isKindOfClass:[NSArray class]])
|
||||
@@ -1098,52 +1070,36 @@ static NSString *userAgent = nil;
|
||||
return ma;
|
||||
}
|
||||
|
||||
- (BOOL) isValidAttachmentName: (NSString *) filename
|
||||
{
|
||||
return (!([filename rangeOfString: @"/"].length
|
||||
|| [filename isEqualToString: @"."]
|
||||
|| [filename isEqualToString: @".."]));
|
||||
}
|
||||
|
||||
- (NSString *) pathToAttachmentWithName: (NSString *) _name
|
||||
{
|
||||
if ([_name length] == 0)
|
||||
return nil;
|
||||
|
||||
|
||||
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
|
||||
}
|
||||
|
||||
- (NSException *) invalidAttachmentNameError: (NSString *) _name
|
||||
{
|
||||
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
|
||||
reason: @"Invalid attachment name!"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write attachment file to the spool directory of the draft and write a dot
|
||||
* file with its mime type.
|
||||
*/
|
||||
- (NSException *) saveAttachment: (NSData *) _attach
|
||||
withMetadata: (NSDictionary *) metadata
|
||||
{
|
||||
NSString *p, *pmime, *name, *mimeType;
|
||||
NSRange r;
|
||||
|
||||
if (![_attach isNotNull]) {
|
||||
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
|
||||
reason: @"Missing attachment content!"];
|
||||
}
|
||||
|
||||
|
||||
if (![self _ensureDraftFolderPath]) {
|
||||
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
|
||||
reason: @"Could not create folder for draft!"];
|
||||
}
|
||||
|
||||
name = [metadata objectForKey: @"filename"];
|
||||
r = [name rangeOfString: @"\\"
|
||||
options: NSBackwardsSearch];
|
||||
if (r.length > 0)
|
||||
name = [name substringFromIndex: r.location + 1];
|
||||
|
||||
if (![self isValidAttachmentName: name])
|
||||
return [self invalidAttachmentNameError: name];
|
||||
|
||||
name = [[metadata objectForKey: @"filename"] asSafeFilename];
|
||||
p = [self pathToAttachmentWithName: name];
|
||||
if (![_attach writeToFile: p atomically: YES])
|
||||
{
|
||||
@@ -1162,7 +1118,7 @@ static NSString *userAgent = nil;
|
||||
reason: @"Could not write attachment to draft!"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return nil; /* everything OK */
|
||||
}
|
||||
|
||||
@@ -1173,21 +1129,14 @@ static NSString *userAgent = nil;
|
||||
NSException *error;
|
||||
|
||||
error = nil;
|
||||
fm = [NSFileManager defaultManager];
|
||||
p = [self pathToAttachmentWithName: [_name asSafeFilename]];
|
||||
if ([fm fileExistsAtPath: p])
|
||||
if (![fm removeFileAtPath: p handler: nil])
|
||||
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: @"Could not delete attachment from draft!"];
|
||||
|
||||
if ([self isValidAttachmentName:_name])
|
||||
{
|
||||
fm = [NSFileManager defaultManager];
|
||||
p = [self pathToAttachmentWithName:_name];
|
||||
if ([fm fileExistsAtPath: p])
|
||||
if (![fm removeFileAtPath: p handler: nil])
|
||||
error
|
||||
= [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||
reason: @"Could not delete attachment from draft!"];
|
||||
}
|
||||
else
|
||||
error = [self invalidAttachmentNameError:_name];
|
||||
|
||||
return error;
|
||||
return error;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1201,9 +1150,9 @@ static NSString *userAgent = nil;
|
||||
|
||||
/* prepare header of body part */
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
|
||||
|
||||
|
||||
[map setObject: contentTypeValue forKey: @"content-type"];
|
||||
|
||||
|
||||
/* prepare body content */
|
||||
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
|
||||
|
||||
@@ -1224,7 +1173,7 @@ static NSString *userAgent = nil;
|
||||
*/
|
||||
NGMutableHashMap *map;
|
||||
NGMimeBodyPart *bodyPart;
|
||||
|
||||
|
||||
/* prepare header of body part */
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease];
|
||||
|
||||
@@ -1232,7 +1181,7 @@ static NSString *userAgent = nil;
|
||||
if (text)
|
||||
[map setObject: (isHTML ? htmlContentTypeValue : contentTypeValue)
|
||||
forKey: @"content-type"];
|
||||
|
||||
|
||||
/* prepare body content */
|
||||
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
|
||||
[bodyPart setBody: text];
|
||||
@@ -1242,11 +1191,11 @@ static NSString *userAgent = nil;
|
||||
|
||||
- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map
|
||||
{
|
||||
NGMimeMessage *message;
|
||||
NGMimeMessage *message;
|
||||
id body;
|
||||
|
||||
message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
|
||||
|
||||
|
||||
if (!isHTML)
|
||||
{
|
||||
[message setHeader: contentTypeValue forKey: @"content-type"];
|
||||
@@ -1263,9 +1212,9 @@ static NSString *userAgent = nil;
|
||||
// Add the HTML part
|
||||
[body addBodyPart: [self bodyPartForText]];
|
||||
}
|
||||
|
||||
|
||||
[message setBody: body];
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -1287,9 +1236,8 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
NSString *s, *p;
|
||||
NSData *mimeData;
|
||||
|
||||
p = [self pathToAttachmentWithName:
|
||||
[NSString stringWithFormat: @".%@.mime", _name]];
|
||||
|
||||
p = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.mime", _name]];
|
||||
mimeData = [NSData dataWithContentsOfFile: p];
|
||||
if (mimeData)
|
||||
{
|
||||
@@ -1308,20 +1256,18 @@ static NSString *userAgent = nil;
|
||||
}
|
||||
|
||||
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
|
||||
andContentType: (NSString *) _type
|
||||
{
|
||||
NSString *type;
|
||||
NSString *cdtype;
|
||||
NSString *cd;
|
||||
SOGoDomainDefaults *dd;
|
||||
|
||||
type = [self contentTypeForAttachmentWithName:_name];
|
||||
|
||||
if ([type hasPrefix: @"text/"])
|
||||
if ([_type hasPrefix: @"text/"])
|
||||
{
|
||||
dd = [[context activeUser] domainDefaults];
|
||||
cdtype = [dd mailAttachTextDocumentsInline] ? @"inline" : @"attachment";
|
||||
}
|
||||
else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"])
|
||||
else if ([_type hasPrefix: @"image/"] || [_type hasPrefix: @"message"])
|
||||
cdtype = @"inline";
|
||||
else
|
||||
cdtype = @"attachment";
|
||||
@@ -1348,7 +1294,7 @@ static NSString *userAgent = nil;
|
||||
if (_name == nil) return nil;
|
||||
|
||||
/* check attachment */
|
||||
|
||||
|
||||
fm = [NSFileManager defaultManager];
|
||||
p = [self pathToAttachmentWithName: _name];
|
||||
if (![fm isReadableFileAtPath: p]) {
|
||||
@@ -1357,7 +1303,7 @@ static NSString *userAgent = nil;
|
||||
}
|
||||
attachAsString = NO;
|
||||
attachAsRFC822 = NO;
|
||||
|
||||
|
||||
/* prepare header of body part */
|
||||
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
|
||||
@@ -1369,22 +1315,22 @@ static NSString *userAgent = nil;
|
||||
else if ([s hasPrefix: @"message/rfc822"])
|
||||
attachAsRFC822 = YES;
|
||||
}
|
||||
if ((s = [self contentDispositionForAttachmentWithName: _name]))
|
||||
if ((s = [self contentDispositionForAttachmentWithName: _name andContentType: s]))
|
||||
{
|
||||
NGMimeContentDispositionHeaderField *o;
|
||||
|
||||
|
||||
o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s];
|
||||
[map setObject: o forKey: @"content-disposition"];
|
||||
[o release];
|
||||
}
|
||||
|
||||
|
||||
/* prepare body content */
|
||||
|
||||
|
||||
if (attachAsString) { // TODO: is this really necessary?
|
||||
NSString *s;
|
||||
|
||||
|
||||
content = [[NSData alloc] initWithContentsOfMappedFile:p];
|
||||
|
||||
|
||||
s = [[NSString alloc] initWithData: content
|
||||
encoding: [NSString defaultCStringEncoding]];
|
||||
if (s != nil) {
|
||||
@@ -1399,7 +1345,7 @@ static NSString *userAgent = nil;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
/*
|
||||
Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
|
||||
NGMimeFileData objects are not processed by the MIME generator!
|
||||
*/
|
||||
@@ -1415,34 +1361,43 @@ static NSString *userAgent = nil;
|
||||
content = [content dataByEncodingBase64];
|
||||
[map setObject: @"base64" forKey: @"content-transfer-encoding"];
|
||||
}
|
||||
[map setObject:[NSNumber numberWithInt:[content length]]
|
||||
[map setObject:[NSNumber numberWithInt:[content length]]
|
||||
forKey: @"content-length"];
|
||||
|
||||
|
||||
/* Note: the -init method will create a temporary file! */
|
||||
body = [[NGMimeFileData alloc] initWithBytes:[content bytes]
|
||||
length:[content length]];
|
||||
}
|
||||
|
||||
|
||||
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
|
||||
[bodyPart setBody:body];
|
||||
|
||||
|
||||
[body release]; body = nil;
|
||||
return bodyPart;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// returns nil on error
|
||||
//
|
||||
- (NSArray *) bodyPartsForAllAttachments
|
||||
{
|
||||
/* returns nil on error */
|
||||
NSArray *attrs;
|
||||
unsigned i, count;
|
||||
NGMimeBodyPart *bodyPart;
|
||||
NSMutableArray *bodyParts;
|
||||
NSArray *attrs;
|
||||
unsigned i, count, size, limit;
|
||||
|
||||
attrs = [self fetchAttachmentAttrs];
|
||||
count = [attrs count];
|
||||
size = 0;
|
||||
|
||||
// We first check if we don't go over our message size limit
|
||||
limit = [[SOGoSystemDefaults sharedSystemDefaults] maximumMessageSizeLimit] * 1024;
|
||||
for (i = 0; i < count; i++)
|
||||
size += [[[attrs objectAtIndex: i] objectForKey: @"size"] intValue];
|
||||
|
||||
if (limit && size > limit)
|
||||
return nil;
|
||||
|
||||
bodyParts = [NSMutableArray arrayWithCapacity: count];
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
@@ -1462,14 +1417,14 @@ static NSString *userAgent = nil;
|
||||
NGMimeMultipartBody *textParts;
|
||||
NGMutableHashMap *header;
|
||||
NGMimeBodyPart *part;
|
||||
|
||||
|
||||
header = [NGMutableHashMap hashMap];
|
||||
[header addObject: MultiAlternativeType forKey: @"content-type"];
|
||||
|
||||
|
||||
part = [NGMimeBodyPart bodyPartWithHeader: header];
|
||||
|
||||
|
||||
textParts = [[NGMimeMultipartBody alloc] initWithPart: part];
|
||||
|
||||
|
||||
// Get the text part from it and add it
|
||||
[textParts addBodyPart: [self plainTextBodyPartForText]];
|
||||
|
||||
@@ -1488,25 +1443,21 @@ static NSString *userAgent = nil;
|
||||
- (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map
|
||||
andBodyParts: (NSArray *) _bodyParts
|
||||
{
|
||||
NGMimeMessage *message;
|
||||
NGMimeMessage *message;
|
||||
NGMimeMultipartBody *mBody;
|
||||
NSEnumerator *e;
|
||||
id part;
|
||||
|
||||
|
||||
[map addObject: MultiMixedType forKey: @"content-type"];
|
||||
|
||||
message = [[NGMimeMessage alloc] initWithHeader: map];
|
||||
[message autorelease];
|
||||
mBody = [[NGMimeMultipartBody alloc] initWithPart: message];
|
||||
|
||||
|
||||
if (!isHTML)
|
||||
{
|
||||
part = [self bodyPartForText];
|
||||
}
|
||||
part = [self bodyPartForText];
|
||||
else
|
||||
{
|
||||
part = [self mimeMultipartAlternative];
|
||||
}
|
||||
part = [self mimeMultipartAlternative];
|
||||
|
||||
[mBody addBodyPart: part];
|
||||
|
||||
@@ -1535,11 +1486,11 @@ static NSString *userAgent = nil;
|
||||
|
||||
if ([_h count] == 0)
|
||||
return;
|
||||
|
||||
|
||||
names = [_h keyEnumerator];
|
||||
while ((name = [names nextObject]) != nil) {
|
||||
id value;
|
||||
|
||||
|
||||
value = [_h objectForKey:name];
|
||||
[_map addObject:value forKey:name];
|
||||
}
|
||||
@@ -1549,10 +1500,10 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
if (![_value isNotNull])
|
||||
return YES;
|
||||
|
||||
|
||||
if ([_value isKindOfClass: [NSArray class]])
|
||||
return [_value count] == 0 ? YES : NO;
|
||||
|
||||
|
||||
if ([_value isKindOfClass: [NSString class]])
|
||||
return [_value length] == 0 ? YES : NO;
|
||||
|
||||
@@ -1630,9 +1581,9 @@ static NSString *userAgent = nil;
|
||||
NSString *s, *dateString;
|
||||
NGMutableHashMap *map;
|
||||
id emails, from, replyTo;
|
||||
|
||||
|
||||
map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
|
||||
|
||||
|
||||
/* add recipients */
|
||||
if ((emails = [headers objectForKey: @"to"]) != nil && [emails isKindOfClass: [NSArray class]])
|
||||
[map setObjects: [self _quoteSpecialsInArray: emails] forKey: @"to"];
|
||||
@@ -1643,7 +1594,7 @@ static NSString *userAgent = nil;
|
||||
|
||||
/* add senders */
|
||||
from = [headers objectForKey: @"from"];
|
||||
|
||||
|
||||
if (![self isEmptyValue:from]) {
|
||||
if ([from isKindOfClass:[NSArray class]])
|
||||
[map setObjects: [self _quoteSpecialsInArray: from] forKey: @"from"];
|
||||
@@ -1661,7 +1612,7 @@ static NSString *userAgent = nil;
|
||||
if ([(s = [headers objectForKey: @"subject"]) length] > 0)
|
||||
[map setObject: [s asQPSubjectString: @"utf-8"]
|
||||
forKey: @"subject"];
|
||||
|
||||
|
||||
if ([(s = [headers objectForKey: @"message-id"]) length] > 0)
|
||||
[map setObject: s
|
||||
forKey: @"message-id"];
|
||||
@@ -1685,16 +1636,16 @@ static NSString *userAgent = nil;
|
||||
forKey: @"Disposition-Notification-To"];
|
||||
|
||||
[self _addHeaders: _headers toHeaderMap: map];
|
||||
|
||||
|
||||
// We remove what we have to...
|
||||
if (_exclude)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
||||
for (i = 0; i < [_exclude count]; i++)
|
||||
[map removeAllObjectsForKey: [_exclude objectAtIndex: i]];
|
||||
}
|
||||
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -1707,8 +1658,10 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
NSMutableArray *bodyParts;
|
||||
NGMimeMessage *message;
|
||||
NSArray *allBodyParts;
|
||||
NGMutableHashMap *map;
|
||||
NSString *newText;
|
||||
|
||||
BOOL has_inline_images;
|
||||
|
||||
message = nil;
|
||||
@@ -1730,11 +1683,15 @@ static NSString *userAgent = nil;
|
||||
if (map)
|
||||
{
|
||||
//[self debugWithFormat: @"MIME Envelope: %@", map];
|
||||
|
||||
[bodyParts addObjectsFromArray: [self bodyPartsForAllAttachments]];
|
||||
allBodyParts = [self bodyPartsForAllAttachments];
|
||||
|
||||
if (!allBodyParts)
|
||||
return nil;
|
||||
|
||||
[bodyParts addObjectsFromArray: allBodyParts];
|
||||
|
||||
//[self debugWithFormat: @"attachments: %@", bodyParts];
|
||||
|
||||
|
||||
if ([bodyParts count] == 0)
|
||||
/* no attachments */
|
||||
message = [self mimeMessageForContentWithHeaderMap: map];
|
||||
@@ -1748,13 +1705,13 @@ static NSString *userAgent = nil;
|
||||
[map addObject: MultiRelatedType forKey: @"content-type"];
|
||||
}
|
||||
|
||||
message = [self mimeMultiPartMessageWithHeaderMap: map
|
||||
message = [self mimeMultiPartMessageWithHeaderMap: map
|
||||
andBodyParts: bodyParts];
|
||||
|
||||
//[self debugWithFormat: @"message: %@", message];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -1772,10 +1729,19 @@ static NSString *userAgent = nil;
|
||||
- (NSData *) mimeMessageAsData
|
||||
{
|
||||
NGMimeMessageGenerator *generator;
|
||||
NGMimeMessage *mimeMessage;
|
||||
NSData *message;
|
||||
|
||||
generator = [NGMimeMessageGenerator new];
|
||||
message = [generator generateMimeFromPart: [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO]];
|
||||
mimeMessage = [self mimeMessageWithHeaders: nil excluding: nil extractingImages: NO];
|
||||
|
||||
if (!mimeMessage)
|
||||
{
|
||||
[generator release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
message = [generator generateMimeFromPart: mimeMessage];
|
||||
[generator release];
|
||||
|
||||
return message;
|
||||
@@ -1827,9 +1793,9 @@ static NSString *userAgent = nil;
|
||||
- (NSException *) sendMail
|
||||
{
|
||||
SOGoUserDefaults *ud;
|
||||
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
|
||||
|
||||
if ([ud mailAddOutgoingAddresses])
|
||||
{
|
||||
NSString *recipient, *emailAddress, *addressBook, *uid;
|
||||
@@ -1840,9 +1806,9 @@ static NSString *userAgent = nil;
|
||||
NGMailAddressParser *parser;
|
||||
SOGoFolder <SOGoContactFolder> *folder;
|
||||
NGVCard *card;
|
||||
|
||||
|
||||
int i;
|
||||
|
||||
|
||||
// Get all the addressbooks
|
||||
contactFolders = [[[context activeUser] homeFolderInContext: context]
|
||||
lookupName: @"Contacts"
|
||||
@@ -1857,7 +1823,7 @@ static NSString *userAgent = nil;
|
||||
parser = [NGMailAddressParser mailAddressParserWithString: recipient];
|
||||
parsedRecipient = [parser parse];
|
||||
emailAddress = [parsedRecipient address];
|
||||
|
||||
|
||||
matchingContacts = [contactFolders allContactsFromFilter: emailAddress
|
||||
excludeGroups: YES
|
||||
excludeLists: YES];
|
||||
@@ -1869,13 +1835,13 @@ static NSString *userAgent = nil;
|
||||
addressBook = [ud selectedAddressBook];
|
||||
folder = [contactFolders lookupName: addressBook inContext: context acquire: NO];
|
||||
uid = [folder globallyUniqueObjectId];
|
||||
|
||||
|
||||
if (folder && uid)
|
||||
{
|
||||
card = [NGVCard cardWithUid: uid];
|
||||
[card addEmail: emailAddress types: nil];
|
||||
[card setFn: [parsedRecipient displayName]];
|
||||
|
||||
|
||||
newContact = [SOGoContactGCSEntry objectWithName: uid
|
||||
inContainer: folder];
|
||||
[newContact setIsNew: YES];
|
||||
@@ -1921,7 +1887,7 @@ static NSString *userAgent = nil;
|
||||
r1 = [cleaned_message rangeOfCString: "\r\nbcc: "
|
||||
options: 0
|
||||
range: NSMakeRange(0,limit)];
|
||||
|
||||
|
||||
if (r1.location != NSNotFound)
|
||||
{
|
||||
// We search for the first \r\n AFTER the Bcc: header and
|
||||
|
||||
@@ -67,6 +67,7 @@ typedef enum {
|
||||
NSString *otherUsersFolderName;
|
||||
NSString *sharedFoldersName;
|
||||
NSMutableDictionary *subscribedFolders;
|
||||
BOOL sieveFolderUTF8Encoding;
|
||||
}
|
||||
|
||||
- (SOGoIMAPAclStyle) imapAclStyle;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoAuthenticator.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoSieveManager.h>
|
||||
@@ -62,6 +63,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
NSString *sieveFolderEncoding = [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
|
||||
inboxFolder = nil;
|
||||
draftsFolder = nil;
|
||||
sentFolder = nil;
|
||||
@@ -72,6 +74,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
otherUsersFolderName = nil;
|
||||
sharedFoldersName = nil;
|
||||
subscribedFolders = nil;
|
||||
sieveFolderUTF8Encoding = [sieveFolderEncoding isEqualToString: @"UTF-8"];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -461,7 +464,7 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
NSArray *pathComponents;
|
||||
NSMutableArray *folders, *flags;
|
||||
NSMutableDictionary *currentFolder, *parentFolder, *folder;
|
||||
NSString *currentFolderName, *currentPath, *fullName, *folderType;
|
||||
NSString *currentFolderName, *currentPath, *sievePath, *fullName, *folderType;
|
||||
SOGoUserManager *userManager;
|
||||
|
||||
BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed;
|
||||
@@ -550,6 +553,13 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
flags, @"flags",
|
||||
[NSNumber numberWithBool: isSubscribed], @"subscribed",
|
||||
nil];
|
||||
|
||||
if (sieveFolderUTF8Encoding)
|
||||
sievePath = [folderPath stringByDecodingImap4FolderName];
|
||||
else
|
||||
sievePath = folderPath;
|
||||
[folder setObject: [sievePath substringFromIndex: 1] forKey: @"sievePath"];
|
||||
|
||||
// Either add this new folder to its parent or the list of root folders
|
||||
[folders addObject: folder];
|
||||
}
|
||||
|
||||
@@ -132,10 +132,23 @@
|
||||
- (NGImap4Connection *) _createIMAP4Connection
|
||||
{
|
||||
NGImap4ConnectionManager *manager;
|
||||
NSString *password;
|
||||
NGImap4Connection *newConnection;
|
||||
NSString *password;
|
||||
NSHost *host;
|
||||
|
||||
[self imap4URL];
|
||||
|
||||
// We first check if we're trying to establish an IMAP connection to localhost
|
||||
// for an account number greater than 0 (default account). We prevent that
|
||||
// for security reasons if admins use an IMAP trust.
|
||||
host = [NSHost hostWithName: [[self imap4URL] host]];
|
||||
if (![[[self mailAccountFolder] nameInContainer] isEqualToString: @"0"] &&
|
||||
[[host address] isEqualToString: @"127.0.0.1"])
|
||||
{
|
||||
[self errorWithFormat: @"Trying to use localhost for additional IMAP account - aborting."];
|
||||
return nil;
|
||||
}
|
||||
|
||||
manager = [self mailManager];
|
||||
password = [self imap4PasswordRenewed: NO];
|
||||
if (password)
|
||||
@@ -212,8 +225,8 @@
|
||||
if (!imap4)
|
||||
{
|
||||
imap4 = [self _createIMAP4Connection];
|
||||
[sogoCache registerIMAP4Connection: imap4
|
||||
forKey: cacheKey];
|
||||
[sogoCache registerIMAP4Connection: imap4
|
||||
forKey: cacheKey];
|
||||
}
|
||||
[imap4 retain];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2009-2016 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of OpenGroupware.org.
|
||||
@@ -48,6 +49,8 @@
|
||||
@class NGImap4Envelope;
|
||||
@class NGImap4EnvelopeAddress;
|
||||
|
||||
@class WOResponse;
|
||||
|
||||
NSArray *SOGoMailCoreInfoKeys;
|
||||
|
||||
@interface SOGoMailObject : SOGoMailBaseObject
|
||||
@@ -107,6 +110,8 @@ NSArray *SOGoMailCoreInfoKeys;
|
||||
- (BOOL) hasAttachment;
|
||||
- (NSDictionary *) fetchFileAttachmentIds;
|
||||
- (NSArray *) fetchFileAttachmentKeys;
|
||||
- (NSArray *) fetchFileAttachments;
|
||||
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName;
|
||||
|
||||
/* flags */
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2007-2014 Inverse inc.
|
||||
Copyright (C) 2007-2016 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
This file is part of SOGo.
|
||||
@@ -20,6 +20,7 @@
|
||||
02111-1307, USA.
|
||||
*/
|
||||
|
||||
#import <Foundation/NSTask.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
@@ -27,6 +28,7 @@
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGExtensions/NGHashMap.h>
|
||||
#import <NGExtensions/NSFileManager+Extensions.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSString+Encoding.h>
|
||||
@@ -38,7 +40,9 @@
|
||||
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoPermissions.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
@@ -122,10 +126,10 @@ static BOOL debugSoParts = NO;
|
||||
- (NSString *) keyExtensionForPart: (id) _partInfo
|
||||
{
|
||||
NSString *mt, *st;
|
||||
|
||||
|
||||
if (_partInfo == nil)
|
||||
return nil;
|
||||
|
||||
|
||||
mt = [_partInfo valueForKey: @"type"];
|
||||
st = [[_partInfo valueForKey: @"subtype"] lowercaseString];
|
||||
if ([mt isEqualToString: @"text"]) {
|
||||
@@ -140,7 +144,7 @@ static BOOL debugSoParts = NO;
|
||||
if ([st isEqualToString: @"pgp-signature"])
|
||||
return @".asc";
|
||||
}
|
||||
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -149,18 +153,18 @@ static BOOL debugSoParts = NO;
|
||||
NSMutableArray *ma;
|
||||
NSArray *parts;
|
||||
unsigned i, count;
|
||||
|
||||
|
||||
parts = [[self bodyStructure] valueForKey: @"parts"];
|
||||
if (![parts isNotNull])
|
||||
if (![parts isNotNull])
|
||||
return nil;
|
||||
if ((count = [parts count]) == 0)
|
||||
return nil;
|
||||
|
||||
|
||||
for (i = 0, ma = nil; i < count; i++) {
|
||||
NSString *key, *ext;
|
||||
id part;
|
||||
BOOL hasParts;
|
||||
|
||||
|
||||
part = [parts objectAtIndex:i];
|
||||
hasParts = [part valueForKey: @"parts"] != nil ? YES:NO;
|
||||
if ((hasParts && !_withParts) || (_withParts && !hasParts))
|
||||
@@ -168,7 +172,7 @@ static BOOL debugSoParts = NO;
|
||||
|
||||
if (ma == nil)
|
||||
ma = [NSMutableArray arrayWithCapacity:count - i];
|
||||
|
||||
|
||||
ext = [self keyExtensionForPart:part];
|
||||
key = [[NSString alloc] initWithFormat: @"%d%@", i + 1, ((id)ext?(id)ext: (id)@"")];
|
||||
[ma addObject:key];
|
||||
@@ -204,15 +208,15 @@ static BOOL debugSoParts = NO;
|
||||
{
|
||||
static NSArray *existsKey = nil;
|
||||
id msgs;
|
||||
|
||||
|
||||
if (coreInfos != nil) /* if we have coreinfos, we can use them */
|
||||
return [coreInfos isNotNull];
|
||||
|
||||
|
||||
/* otherwise fetch something really simple */
|
||||
|
||||
|
||||
if (existsKey == nil) /* we use size, other suggestions? */
|
||||
existsKey = [[NSArray alloc] initWithObjects: @"RFC822.SIZE", nil];
|
||||
|
||||
|
||||
msgs = [self fetchParts:existsKey]; // returns dict
|
||||
msgs = [msgs valueForKey: @"fetch"];
|
||||
return [msgs count] > 0 ? YES : NO;
|
||||
@@ -229,7 +233,7 @@ static BOOL debugSoParts = NO;
|
||||
if (heavyDebug)
|
||||
[self logWithFormat: @"M: %@", msgs];
|
||||
msgs = [msgs valueForKey: @"fetch"];
|
||||
|
||||
|
||||
// We MUST honor untagged IMAP responses here otherwise we could
|
||||
// return really borken and nasty results.
|
||||
if ([msgs count] > 0)
|
||||
@@ -237,7 +241,7 @@ static BOOL debugSoParts = NO;
|
||||
for (i = 0; i < [msgs count]; i++)
|
||||
{
|
||||
coreInfos = [msgs objectAtIndex: i];
|
||||
|
||||
|
||||
if ([[coreInfos objectForKey: @"uid"] intValue] == [[self nameInContainer] intValue])
|
||||
break;
|
||||
|
||||
@@ -337,13 +341,13 @@ static BOOL debugSoParts = NO;
|
||||
{
|
||||
NGMimeMessageParser *parser;
|
||||
NSData *data;
|
||||
|
||||
|
||||
if (headerPart != nil)
|
||||
return [headerPart isNotNull] ? headerPart : nil;
|
||||
|
||||
|
||||
if ([(data = [self mailHeaderData]) length] == 0)
|
||||
return nil;
|
||||
|
||||
|
||||
// TODO: do we need to set some delegate method which stops parsing the body?
|
||||
parser = [[NGMimeMessageParser alloc] init];
|
||||
headerPart = [[parser parsePartFromData:data] retain];
|
||||
@@ -372,21 +376,21 @@ static BOOL debugSoParts = NO;
|
||||
|
||||
if (![_path isNotNull])
|
||||
return nil;
|
||||
|
||||
|
||||
if ((info = [self bodyStructure]) == nil) {
|
||||
[self errorWithFormat: @"got no body part structure!"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* ensure array argument */
|
||||
|
||||
|
||||
if ([_path isKindOfClass:[NSString class]]) {
|
||||
if ([_path length] == 0 || [_path isEqualToString: @"text"])
|
||||
return info;
|
||||
|
||||
|
||||
_path = [_path componentsSeparatedByString: @"."];
|
||||
}
|
||||
|
||||
|
||||
// deal with mails of type text/calendar
|
||||
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"text"] &&
|
||||
[[[info valueForKey: @"subtype"] lowercaseString] isEqualToString: @"calendar"])
|
||||
@@ -399,12 +403,12 @@ static BOOL debugSoParts = NO;
|
||||
if ([[[info valueForKey: @"type"] lowercaseString] isEqualToString: @"application"])
|
||||
return info;
|
||||
|
||||
/*
|
||||
For each path component, eg 1,1,3
|
||||
|
||||
/*
|
||||
For each path component, eg 1,1,3
|
||||
|
||||
Remember that we need special processing for message/rfc822 which maps the
|
||||
namespace of multiparts directly into the main namespace.
|
||||
|
||||
|
||||
TODO(hh): no I don't remember, please explain in more detail!
|
||||
*/
|
||||
pe = [_path objectEnumerator];
|
||||
@@ -412,7 +416,7 @@ static BOOL debugSoParts = NO;
|
||||
unsigned idx;
|
||||
NSArray *parts;
|
||||
NSString *mt;
|
||||
|
||||
|
||||
[self debugWithFormat: @"check PATH: %@", p];
|
||||
idx = [p intValue] - 1;
|
||||
|
||||
@@ -421,7 +425,7 @@ static BOOL debugSoParts = NO;
|
||||
if ([mt isEqualToString: @"message"]) {
|
||||
/* we have special behaviour for message types */
|
||||
id body;
|
||||
|
||||
|
||||
if ((body = [info valueForKey: @"body"]) != nil) {
|
||||
mt = [body valueForKey: @"type"];
|
||||
if ([mt isEqualToString: @"multipart"])
|
||||
@@ -430,10 +434,10 @@ static BOOL debugSoParts = NO;
|
||||
parts = [NSArray arrayWithObject:body];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (idx >= [parts count]) {
|
||||
[self errorWithFormat:
|
||||
@"body part index out of bounds(idx=%d vs count=%d): %@",
|
||||
@"body part index out of bounds(idx=%d vs count=%d): %@",
|
||||
(idx + 1), [parts count], info];
|
||||
return nil;
|
||||
}
|
||||
@@ -448,40 +452,40 @@ static BOOL debugSoParts = NO;
|
||||
{
|
||||
NSData *content;
|
||||
id result, fullResult;
|
||||
|
||||
|
||||
// We avoid using RFC822 here as the part name as it'll flag the message as Seen
|
||||
fullResult = [self fetchParts: [NSArray arrayWithObject: @"BODY.PEEK[]"]];
|
||||
if (fullResult == nil)
|
||||
return nil;
|
||||
|
||||
|
||||
if ([fullResult isKindOfClass: [NSException class]])
|
||||
return fullResult;
|
||||
|
||||
|
||||
/* extract fetch result */
|
||||
|
||||
|
||||
result = [fullResult valueForKey: @"fetch"];
|
||||
if (![result isKindOfClass:[NSArray class]]) {
|
||||
[self logWithFormat:
|
||||
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
|
||||
@"ERROR: unexpected IMAP4 result (missing 'fetch'): %@",
|
||||
fullResult];
|
||||
return [NSException exceptionWithHTTPStatus:500 /* server error */
|
||||
reason: @"unexpected IMAP4 result"];
|
||||
}
|
||||
if ([result count] == 0)
|
||||
return nil;
|
||||
|
||||
|
||||
result = [result objectAtIndex:0];
|
||||
|
||||
|
||||
/* extract message */
|
||||
|
||||
|
||||
if ((content = [[result valueForKey: @"body[]"] valueForKey: @"data"]) == nil) {
|
||||
[self logWithFormat:
|
||||
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
|
||||
@"ERROR: unexpected IMAP4 result (missing 'message'): %@",
|
||||
result];
|
||||
return [NSException exceptionWithHTTPStatus:500 /* server error */
|
||||
reason: @"unexpected IMAP4 result"];
|
||||
}
|
||||
|
||||
|
||||
return [[content copy] autorelease];
|
||||
}
|
||||
|
||||
@@ -507,7 +511,7 @@ static BOOL debugSoParts = NO;
|
||||
[s autorelease];
|
||||
else
|
||||
[self logWithFormat:
|
||||
@"ERROR: could not convert data of length %d to string",
|
||||
@"ERROR: could not convert data of length %d to string",
|
||||
[content length]];
|
||||
}
|
||||
else
|
||||
@@ -529,11 +533,11 @@ static BOOL debugSoParts = NO;
|
||||
withPeek: (BOOL) withPeek
|
||||
parentMultipart: (NSString *) parentMPart
|
||||
{
|
||||
/*
|
||||
/*
|
||||
This is used to collect the set of IMAP4 fetch-keys required to fetch
|
||||
the basic parts of the body structure. That is, to fetch all parts which
|
||||
are displayed 'inline' in a single IMAP4 fetch.
|
||||
|
||||
|
||||
The method calls itself recursively to walk the body structure.
|
||||
*/
|
||||
NSArray *parts;
|
||||
@@ -555,7 +559,7 @@ static BOOL debugSoParts = NO;
|
||||
multipart = mimeType;
|
||||
else
|
||||
multipart = parentMPart;
|
||||
|
||||
|
||||
if ([types containsObject: mimeType])
|
||||
{
|
||||
if ([p length] > 0)
|
||||
@@ -581,9 +585,9 @@ static BOOL debugSoParts = NO;
|
||||
sp = (([p length] > 0)
|
||||
? (id)[p stringByAppendingFormat: @".%d", i + 1]
|
||||
: (id)[NSString stringWithFormat: @"%d", i + 1]);
|
||||
|
||||
|
||||
childInfo = [parts objectAtIndex: i];
|
||||
|
||||
|
||||
[self addRequiredKeysOfStructure: childInfo
|
||||
path: sp
|
||||
toArray: keys
|
||||
@@ -591,7 +595,7 @@ static BOOL debugSoParts = NO;
|
||||
withPeek: withPeek
|
||||
parentMultipart: multipart];
|
||||
}
|
||||
|
||||
|
||||
/* check body */
|
||||
body = [info objectForKey: @"body"];
|
||||
if (body)
|
||||
@@ -651,7 +655,7 @@ static BOOL debugSoParts = NO;
|
||||
|
||||
[self addRequiredKeysOfStructure: [self bodyStructure]
|
||||
path: @""
|
||||
toArray: ma
|
||||
toArray: ma
|
||||
acceptedTypes: types
|
||||
withPeek: YES];
|
||||
|
||||
@@ -665,7 +669,7 @@ static BOOL debugSoParts = NO;
|
||||
unsigned i, count;
|
||||
NSArray *results;
|
||||
id result;
|
||||
|
||||
|
||||
[self debugWithFormat: @"fetch keys: %@", _fetchKeys];
|
||||
|
||||
result = [self fetchParts: [_fetchKeys objectsForKey: @"key"
|
||||
@@ -681,7 +685,7 @@ static BOOL debugSoParts = NO;
|
||||
for (i = 0; i < count; i++) {
|
||||
NSString *key;
|
||||
NSData *data;
|
||||
|
||||
|
||||
key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"];
|
||||
|
||||
// We'll ask for the body.peek[] but SOPE returns us body[] responses
|
||||
@@ -689,19 +693,19 @@ static BOOL debugSoParts = NO;
|
||||
if ([key hasPrefix: @"body.peek["])
|
||||
key = [NSString stringWithFormat: @"body[%@", [key substringFromIndex: 10]];
|
||||
|
||||
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
|
||||
data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key]
|
||||
objectForKey: @"data"];
|
||||
|
||||
|
||||
if (![data isNotNull]) {
|
||||
[self errorWithFormat: @"got no data for key: %@", key];
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if ([key isEqualToString: @"body[text]"])
|
||||
key = @""; // see key collector for explanation (TODO: where?)
|
||||
else if ([key hasPrefix: @"body["]) {
|
||||
NSRange r;
|
||||
|
||||
|
||||
key = [key substringFromIndex:5];
|
||||
r = [key rangeOfString: @"]"];
|
||||
if (r.length > 0)
|
||||
@@ -836,6 +840,7 @@ static BOOL debugSoParts = NO;
|
||||
path, @"path",
|
||||
[part objectForKey: @"encoding"], @"encoding",
|
||||
[part objectForKey:@ "size"], @"size",
|
||||
[part objectForKey: @"bodyId"], @"bodyId",
|
||||
[NSString stringWithFormat: @"%@/%@", prefix, filenameURL], @"url",
|
||||
[NSString stringWithFormat: @"%@/asAttachment/%@", prefix, filenameURL], @"urlAsAttachment",
|
||||
nil];
|
||||
@@ -913,13 +918,167 @@ static BOOL debugSoParts = NO;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of dictionaries with the following keys:
|
||||
* - encoding
|
||||
* - filename
|
||||
* - mimetype
|
||||
* - path
|
||||
* - size
|
||||
* - url
|
||||
* - urlAsAttachment
|
||||
* - body (NSData)
|
||||
*/
|
||||
- (NSArray *) fetchFileAttachments
|
||||
{
|
||||
unsigned int count, max;
|
||||
NGHashMap *response;
|
||||
NSArray *parts, *paths; //, *bodies;
|
||||
NSData *body;
|
||||
NSDictionary *fetch, *currentInfo, *currentBody;
|
||||
NSMutableArray *attachments;
|
||||
NSMutableDictionary *currentAttachment;
|
||||
NSString *currentPath;
|
||||
|
||||
parts = [self fetchFileAttachmentKeys];
|
||||
max = [parts count];
|
||||
attachments = [NSMutableArray arrayWithCapacity: max];
|
||||
if (max > 0)
|
||||
{
|
||||
paths = [parts keysWithFormat: @"BODY[%{path}]"];
|
||||
response = [[self fetchParts: paths] objectForKey: @"RawResponse"];
|
||||
fetch = [response objectForKey: @"fetch"];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentInfo = [parts objectAtIndex: count];
|
||||
currentPath = [[paths objectAtIndex: count] lowercaseString];
|
||||
currentBody = [fetch objectForKey: currentPath];
|
||||
|
||||
if (currentBody)
|
||||
{
|
||||
body = [currentBody objectForKey: @"data"];
|
||||
body = [body bodyDataFromEncoding: [currentInfo objectForKey: @"encoding"]];
|
||||
}
|
||||
else
|
||||
body = [NSData data];
|
||||
|
||||
currentAttachment = [NSMutableDictionary dictionaryWithDictionary: currentInfo];
|
||||
[currentAttachment setObject: body forKey: @"body"];
|
||||
[attachments addObject: currentAttachment];
|
||||
}
|
||||
}
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName
|
||||
{
|
||||
NSArray *attachments;
|
||||
NSData *body, *zipContent;
|
||||
NSDictionary *currentAttachment;
|
||||
NSException *error;
|
||||
NSFileManager *fm;
|
||||
NSMutableArray *zipTaskArguments;
|
||||
NSString *spoolPath, *name, *fileName, *baseName, *extension, *zipPath, *qpFileName;
|
||||
NSTask *zipTask;
|
||||
SOGoMailFolder *folder;
|
||||
WOResponse *response;
|
||||
unsigned int max, count;
|
||||
|
||||
if (!archiveName)
|
||||
archiveName = @"attachments.zip";
|
||||
|
||||
folder = [self container];
|
||||
spoolPath = [folder userSpoolFolderPath];
|
||||
|
||||
if (![folder ensureSpoolFolderPath])
|
||||
{
|
||||
[self errorWithFormat: @"spool directory '%@' doesn't exist", spoolPath];
|
||||
error = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"spool directory does not exist"];
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
// Prepare execution of zip
|
||||
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
|
||||
fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath: zipPath])
|
||||
{
|
||||
error = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"zip not available"];
|
||||
return (WOResponse *)error;
|
||||
}
|
||||
|
||||
zipTask = [[NSTask alloc] init];
|
||||
[zipTask setCurrentDirectoryPath: spoolPath];
|
||||
[zipTask setLaunchPath: zipPath];
|
||||
|
||||
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
|
||||
[zipTaskArguments addObject: @"attachments.zip"];
|
||||
|
||||
// Fetch attachments and write them on disk
|
||||
attachments = [self fetchFileAttachments];
|
||||
max = [attachments count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentAttachment = [attachments objectAtIndex: count];
|
||||
body = [currentAttachment objectForKey: @"body"];
|
||||
name = [[currentAttachment objectForKey: @"filename"] asSafeFilename];
|
||||
|
||||
fileName = [NSString stringWithFormat:@"%@/%@", spoolPath, name];
|
||||
[body writeToFile: fileName atomically: YES];
|
||||
|
||||
[zipTaskArguments addObject: name];
|
||||
}
|
||||
|
||||
// Zip files
|
||||
[zipTask setArguments: zipTaskArguments];
|
||||
[zipTask launch];
|
||||
[zipTask waitUntilExit];
|
||||
[zipTask release];
|
||||
zipContent = [[NSData alloc] initWithContentsOfFile:
|
||||
[NSString stringWithFormat: @"%@/attachments.zip", spoolPath]];
|
||||
|
||||
// Delete attachments from disk
|
||||
max = [zipTaskArguments count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
fileName = [zipTaskArguments objectAtIndex: count];
|
||||
[fm removeFileAtPath:
|
||||
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
response = [context response];
|
||||
baseName = [archiveName stringByDeletingPathExtension];
|
||||
extension = [archiveName pathExtension];
|
||||
if ([extension length] > 0)
|
||||
extension = [@"." stringByAppendingString: extension];
|
||||
else
|
||||
extension = @"";
|
||||
|
||||
qpFileName = [NSString stringWithFormat: @"%@%@",
|
||||
[baseName asQPSubjectString: @"utf-8"], extension];
|
||||
[response setHeader: [NSString stringWithFormat: @"application/zip;"
|
||||
@" name=\"%@\"", qpFileName]
|
||||
forKey: @"content-type"];
|
||||
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
|
||||
qpFileName]
|
||||
forKey: @"Content-Disposition"];
|
||||
[response setContent: zipContent];
|
||||
|
||||
[zipContent release];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/* convert parts to strings */
|
||||
- (NSString *) stringForData: (NSData *) _data
|
||||
partInfo: (NSDictionary *) _info
|
||||
{
|
||||
NSString *charset, *s;
|
||||
NSData *mailData;
|
||||
|
||||
|
||||
if ([_data isNotNull])
|
||||
{
|
||||
mailData
|
||||
@@ -934,13 +1093,13 @@ static BOOL debugSoParts = NO;
|
||||
{
|
||||
s = [NSString stringWithData: mailData usingEncodingNamed: charset];
|
||||
}
|
||||
|
||||
|
||||
// If it has failed, we try at least using UTF-8. Normally, this can NOT fail.
|
||||
// Unfortunately, it seems to fail under GNUstep so we try latin1 if that's
|
||||
// the case
|
||||
if (!s)
|
||||
s = [[[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding] autorelease];
|
||||
|
||||
|
||||
if (!s)
|
||||
s = [[[NSString alloc] initWithData: mailData encoding: NSISOLatin1StringEncoding] autorelease];
|
||||
}
|
||||
@@ -975,17 +1134,17 @@ static BOOL debugSoParts = NO;
|
||||
/*
|
||||
The fetched parts are NSData objects, this method converts them into
|
||||
NSString objects based on the information inside the bodystructure.
|
||||
|
||||
|
||||
The fetch-keys are body fetch-keys like: body[text] or body[1.2.3].
|
||||
The keys in the result dictionary are "" for 'text' and 1.2.3 for parts.
|
||||
*/
|
||||
NSDictionary *datas;
|
||||
|
||||
|
||||
if ((datas = [self fetchPlainTextParts:_fetchKeys]) == nil)
|
||||
return nil;
|
||||
if ([datas isKindOfClass:[NSException class]])
|
||||
return datas;
|
||||
|
||||
|
||||
return [self stringifyTextParts:datas];
|
||||
}
|
||||
|
||||
@@ -1065,13 +1224,13 @@ static BOOL debugSoParts = NO;
|
||||
acquire: (BOOL) _flag
|
||||
{
|
||||
id obj;
|
||||
|
||||
|
||||
/* first check attributes directly bound to the application */
|
||||
if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]) != nil)
|
||||
return obj;
|
||||
|
||||
|
||||
/* lookup body part */
|
||||
|
||||
|
||||
if ([self isBodyPartKey:_key]) {
|
||||
if ((obj = [self lookupImap4BodyPartKey:_key inContext:_ctx]) != nil) {
|
||||
if (debugSoParts)
|
||||
@@ -1087,7 +1246,7 @@ static BOOL debugSoParts = NO;
|
||||
[obj setAsAttachment];
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/* return 404 to stop acquisition */
|
||||
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
|
||||
reason: @"Did not find mail method or part-reference!"];
|
||||
@@ -1131,7 +1290,7 @@ static BOOL debugSoParts = NO;
|
||||
newName: (NSString *) _name
|
||||
inContext: (id)_ctx
|
||||
{
|
||||
/*
|
||||
/*
|
||||
Note: this is special because we create SOGoMailObject's even if they do
|
||||
not exist (for performance reasons).
|
||||
|
||||
@@ -1181,7 +1340,7 @@ static BOOL debugSoParts = NO;
|
||||
NSException *error;
|
||||
WOResponse *r;
|
||||
NSData *content;
|
||||
|
||||
|
||||
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) {
|
||||
/* check whether the mail still exists */
|
||||
if (![self doesMailExist]) {
|
||||
@@ -1190,7 +1349,7 @@ static BOOL debugSoParts = NO;
|
||||
}
|
||||
return error; /* return 304 or 416 */
|
||||
}
|
||||
|
||||
|
||||
content = [self content];
|
||||
if ([content isKindOfClass:[NSException class]])
|
||||
return content;
|
||||
@@ -1198,7 +1357,7 @@ static BOOL debugSoParts = NO;
|
||||
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
|
||||
reason: @"did not find IMAP4 message"];
|
||||
}
|
||||
|
||||
|
||||
r = [(WOContext *)_ctx response];
|
||||
[r setHeader: @"message/rfc822" forKey: @"content-type"];
|
||||
[r setContent:content];
|
||||
@@ -1257,19 +1416,19 @@ static BOOL debugSoParts = NO;
|
||||
inContext: _ctx])
|
||||
{
|
||||
/* b) mark deleted */
|
||||
|
||||
|
||||
error = [[self imap4Connection] markURLDeleted: [self imap4URL]];
|
||||
if (error != nil) return error;
|
||||
|
||||
[self flushMailCaches];
|
||||
}
|
||||
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSException *) delete
|
||||
{
|
||||
/*
|
||||
/*
|
||||
Note: delete is different to DELETEAction: for mails! The 'delete' runs
|
||||
either flags a message as deleted or moves it to the Trash while
|
||||
the DELETEAction: really deletes a message (by flagging it as
|
||||
@@ -1279,7 +1438,7 @@ static BOOL debugSoParts = NO;
|
||||
NSException *error;
|
||||
|
||||
// TODO: check for safe HTTP method
|
||||
|
||||
|
||||
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
|
||||
return error;
|
||||
}
|
||||
@@ -1287,15 +1446,15 @@ static BOOL debugSoParts = NO;
|
||||
- (id) DELETEAction: (id) _ctx
|
||||
{
|
||||
NSException *error;
|
||||
|
||||
|
||||
// TODO: ensure safe HTTP method
|
||||
|
||||
|
||||
error = [[self imap4Connection] markURLDeleted:[self imap4URL]];
|
||||
if (error != nil) return error;
|
||||
|
||||
|
||||
error = [[self imap4Connection] expungeAtURL:[[self container] imap4URL]];
|
||||
if (error != nil) return error; // TODO: unflag as deleted?
|
||||
|
||||
|
||||
return [NSNumber numberWithBool:YES]; /* delete was successful */
|
||||
}
|
||||
|
||||
@@ -1304,20 +1463,20 @@ static BOOL debugSoParts = NO;
|
||||
- (BOOL) isMailingListMail
|
||||
{
|
||||
NSDictionary *h;
|
||||
|
||||
|
||||
if ((h = [self mailHeaders]) == nil)
|
||||
return NO;
|
||||
|
||||
|
||||
return [[h objectForKey: @"list-id"] isNotEmpty];
|
||||
}
|
||||
|
||||
- (BOOL) isVirusScanned
|
||||
{
|
||||
NSDictionary *h;
|
||||
|
||||
|
||||
if ((h = [self mailHeaders]) == nil)
|
||||
return NO;
|
||||
|
||||
|
||||
if (![[h objectForKey: @"x-virus-status"] isNotEmpty]) return NO;
|
||||
if (![[h objectForKey: @"x-virus-scanned"] isNotEmpty]) return NO;
|
||||
return YES;
|
||||
@@ -1329,10 +1488,10 @@ static BOOL debugSoParts = NO;
|
||||
/* Note: not very tolerant on embedded commands and <> */
|
||||
// TODO: does not really belong here, should be a header-field-parser
|
||||
NSRange r;
|
||||
|
||||
|
||||
if (![_value isNotEmpty])
|
||||
return nil;
|
||||
|
||||
|
||||
if ([_value isKindOfClass:[NSArray class]]) {
|
||||
NSEnumerator *e;
|
||||
id value;
|
||||
@@ -1344,10 +1503,10 @@ static BOOL debugSoParts = NO;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
if (![_value isKindOfClass:[NSString class]])
|
||||
return nil;
|
||||
|
||||
|
||||
/* check for commas in string values */
|
||||
r = [_value rangeOfString: @","];
|
||||
if (r.length > 0) {
|
||||
@@ -1358,7 +1517,7 @@ static BOOL debugSoParts = NO;
|
||||
/* value qualifies */
|
||||
if (![(NSString *)_value hasPrefix:_prefix])
|
||||
return nil;
|
||||
|
||||
|
||||
/* unquote */
|
||||
if ([_value characterAtIndex:0] == '<') {
|
||||
r = [_value rangeOfString: @">"];
|
||||
@@ -1456,7 +1615,7 @@ static BOOL debugSoParts = NO;
|
||||
if (property)
|
||||
{
|
||||
parts = [NSArray arrayWithObject: property];
|
||||
|
||||
|
||||
msgs = [self fetchParts: parts];
|
||||
msgs = [msgs valueForKey: @"fetch"];
|
||||
if ([msgs count]) {
|
||||
@@ -1512,7 +1671,7 @@ static BOOL debugSoParts = NO;
|
||||
// date already exists, but this one is the correct format
|
||||
- (NSString *) davDate
|
||||
{
|
||||
return [[self date] rfc822DateString];
|
||||
return [[self date] rfc822DateString];
|
||||
}
|
||||
|
||||
- (BOOL) hasAttachment
|
||||
@@ -1597,12 +1756,12 @@ static BOOL debugSoParts = NO;
|
||||
if ([fetch count])
|
||||
{
|
||||
data = [fetch objectForKey: @"header"];
|
||||
value = [[NSString alloc] initWithData: data
|
||||
value = [[NSString alloc] initWithData: data
|
||||
encoding: NSUTF8StringEncoding];
|
||||
range = [value rangeOfString: @"received:"
|
||||
options: NSCaseInsensitiveSearch
|
||||
range: NSMakeRange (10, [value length] - 11)];
|
||||
if (range.length
|
||||
if (range.length
|
||||
&& range.location < [value length]
|
||||
&& range.length < [value length])
|
||||
{
|
||||
|
||||
@@ -1305,12 +1305,19 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
{
|
||||
NGLdapConnection *ldapConnection;
|
||||
NGLdapEntry *entry;
|
||||
EOQualifier *qualifier;
|
||||
NSString *login;
|
||||
|
||||
login = nil;
|
||||
qualifier = nil;
|
||||
|
||||
ldapConnection = [self _ldapConnection];
|
||||
|
||||
if (_filter)
|
||||
qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
|
||||
|
||||
entry = [ldapConnection entryAtDN: theDN
|
||||
qualifier: qualifier
|
||||
attributes: [NSArray arrayWithObject: UIDField]];
|
||||
if (entry)
|
||||
login = [[entry attributeWithName: UIDField] stringValueAtIndex: 0];
|
||||
|
||||
@@ -721,7 +721,7 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
|
||||
cryptParts = [cryptString componentsSeparatedByString: @"$"];
|
||||
// correct number of elements (first one is an empty string)
|
||||
if ([cryptParts count] != 4)
|
||||
if ([cryptParts count] < 4)
|
||||
{
|
||||
return [NSData data];
|
||||
}
|
||||
@@ -731,7 +731,16 @@ static void _nettle_md5_compress(uint32_t *digest, const uint8_t *input);
|
||||
[[cryptParts objectAtIndex: 1] caseInsensitiveCompare: @"6"] == NSOrderedSame)
|
||||
{
|
||||
// third is the salt; convert it to NSData
|
||||
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
if ([cryptParts count] == 4)
|
||||
return [[cryptParts objectAtIndex: 2] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
else
|
||||
{
|
||||
NSString *saltWithRounds;
|
||||
|
||||
saltWithRounds = [NSString stringWithFormat: @"%@$%@", [cryptParts objectAtIndex: 2], [cryptParts objectAtIndex: 3]];
|
||||
|
||||
return [saltWithRounds dataUsingEncoding: NSUTF8StringEncoding];
|
||||
}
|
||||
}
|
||||
// nothing good
|
||||
return [NSData data];
|
||||
|
||||
@@ -302,7 +302,8 @@ static int cssEscapingCount;
|
||||
{
|
||||
c = buf[i];
|
||||
|
||||
if (c == 0x9 ||
|
||||
if (c == 0x0 ||
|
||||
c == 0x9 ||
|
||||
c == 0xA ||
|
||||
c == 0xD ||
|
||||
(c >= 0x20 && c <= 0xD7FF) ||
|
||||
@@ -683,8 +684,9 @@ static int cssEscapingCount;
|
||||
|
||||
- (NSString *) asSafeSQLString
|
||||
{
|
||||
return [[self stringByReplacingString: @"\\" withString: @"\\\\"]
|
||||
stringByReplacingString: @"'" withString: @"\\'"];
|
||||
return [[[self stringByReplacingString: @"\\" withString: @"\\\\"]
|
||||
stringByReplacingString: @"'" withString: @"\\'"]
|
||||
stringByReplacingString: @"\%" withString: @"\\%"];
|
||||
}
|
||||
|
||||
- (NSUInteger) countOccurrencesOfString: (NSString *) substring
|
||||
|
||||
@@ -505,6 +505,8 @@ static memcached_st *handle = NULL;
|
||||
{
|
||||
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"];
|
||||
}
|
||||
|
||||
[d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"LastRequestDate"];
|
||||
|
||||
[d setObject: count forKey: @"FailedCount"];
|
||||
[self _cacheValues: [d jsonRepresentation]
|
||||
|
||||
@@ -475,9 +475,8 @@ static NSArray *childRecordFields = nil;
|
||||
davURL = [self realDavURL];
|
||||
newPath = [NSMutableArray arrayWithArray: [[davURL path] componentsSeparatedByString: @"/"]];
|
||||
[newPath insertObject: @"public" atIndex: 3];
|
||||
davURL = [[NSURL alloc] initWithScheme: [davURL scheme]
|
||||
host: [davURL host]
|
||||
path: [newPath componentsJoinedByString: @"/"]];
|
||||
davURL = [NSURL URLWithString: [newPath componentsJoinedByString: @"/"]
|
||||
relativeToURL: davURL];
|
||||
|
||||
return davURL;
|
||||
}
|
||||
@@ -500,10 +499,7 @@ static NSArray *childRecordFields = nil;
|
||||
[container nameInContainer],
|
||||
[self realNameInContainer]];
|
||||
currentDavURL = [self davURL];
|
||||
realDavURL = [[NSURL alloc] initWithScheme: [currentDavURL scheme]
|
||||
host: [currentDavURL host]
|
||||
path: path];
|
||||
[realDavURL autorelease];
|
||||
realDavURL = [NSURL URLWithString: path relativeToURL: currentDavURL];
|
||||
}
|
||||
else
|
||||
realDavURL = [self davURL];
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
|
||||
if (user)
|
||||
{
|
||||
[logins addObject: login];
|
||||
[logins addObject: [user loginInDomain]];
|
||||
[_members addObject: user];
|
||||
}
|
||||
[pool release];
|
||||
|
||||
@@ -365,7 +365,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
}
|
||||
else
|
||||
scriptError = @"Rule lacks a 'value' parameter";
|
||||
|
||||
|
||||
return (scriptError == nil);
|
||||
}
|
||||
|
||||
@@ -783,7 +783,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
SOGoDomainDefaults *dd;
|
||||
NGSieveClient *client;
|
||||
NSString *filterScript, *v;
|
||||
BOOL b;
|
||||
BOOL b, dateCapability;
|
||||
unsigned int now;
|
||||
|
||||
dd = [user domainDefaults];
|
||||
@@ -797,7 +797,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
if (!client)
|
||||
return NO;
|
||||
|
||||
// We adjust the "methodRequirements" based on the server's
|
||||
// We adjust the "methodRequirements" based on the server's
|
||||
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
|
||||
// potentially others) expose "imap4flags" as specified in RFC5332
|
||||
if ([client hasCapability: @"imap4flags"])
|
||||
@@ -806,7 +806,9 @@ static NSString *sieveScriptName = @"sogo";
|
||||
[methodRequirements setObject: @"imap4flags" forKey: @"removeflag"];
|
||||
[methodRequirements setObject: @"imap4flags" forKey: @"flag"];
|
||||
}
|
||||
|
||||
|
||||
dateCapability = [client hasCapability: @"date"] && [client hasCapability: @"relational"];
|
||||
|
||||
//
|
||||
// Now let's generate the script
|
||||
//
|
||||
@@ -835,16 +837,22 @@ static NSString *sieveScriptName = @"sogo";
|
||||
now = [[NSCalendarDate calendarDate] timeIntervalSince1970];
|
||||
|
||||
if (values && [[values objectForKey: @"enabled"] boolValue] &&
|
||||
(![values objectForKey: @"startDateEnabled"] || [[values objectForKey: @"startDate"] intValue] < now))
|
||||
(![values objectForKey: @"startDateEnabled"] ||
|
||||
dateCapability || [[values objectForKey: @"startDate"] intValue] < now) &&
|
||||
(![values objectForKey: @"endDateEnabled"] ||
|
||||
dateCapability || [[values objectForKey: @"endDate"] intValue] > now))
|
||||
{
|
||||
NSCalendarDate *startDate, *endDate;
|
||||
NSMutableArray *allConditions;
|
||||
NSMutableString *vacation_script;
|
||||
NSArray *addresses;
|
||||
NSString *text, *templateFilePath, *customSubject;
|
||||
SOGoTextTemplateFile *templateFile;
|
||||
|
||||
|
||||
BOOL ignore, alwaysSend, useCustomSubject;
|
||||
int days, i;
|
||||
|
||||
|
||||
allConditions = [NSMutableArray array];
|
||||
days = [[values objectForKey: @"daysBetweenResponse"] intValue];
|
||||
addresses = [values objectForKey: @"autoReplyEmailAddresses"];
|
||||
alwaysSend = [[values objectForKey: @"alwaysSend"] boolValue];
|
||||
@@ -883,12 +891,43 @@ static NSString *sieveScriptName = @"sogo";
|
||||
days = 7;
|
||||
|
||||
vacation_script = [NSMutableString string];
|
||||
|
||||
|
||||
[req addObjectUniquely: @"vacation"];
|
||||
|
||||
// Skip mailing lists
|
||||
if (ignore)
|
||||
[vacation_script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) { "];
|
||||
{
|
||||
[allConditions addObject: @"not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"]"];
|
||||
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"]"];
|
||||
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\""];
|
||||
}
|
||||
|
||||
// Start date of auto-reply
|
||||
if ([[values objectForKey: @"startDateEnabled"] boolValue] && dateCapability)
|
||||
{
|
||||
[req addObjectUniquely: @"date"];
|
||||
[req addObjectUniquely: @"relational"];
|
||||
startDate = [NSCalendarDate dateWithTimeIntervalSince1970:
|
||||
[[values objectForKey: @"startDate"] intValue]];
|
||||
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"ge\" \"date\" \"%@\"",
|
||||
[startDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
|
||||
}
|
||||
|
||||
// End date of auto-reply
|
||||
if ([[values objectForKey: @"endDateEnabled"] boolValue] && dateCapability)
|
||||
{
|
||||
[req addObjectUniquely: @"date"];
|
||||
[req addObjectUniquely: @"relational"];
|
||||
endDate = [NSCalendarDate dateWithTimeIntervalSince1970:
|
||||
[[values objectForKey: @"endDate"] intValue]];
|
||||
[allConditions addObject: [NSString stringWithFormat: @"currentdate :value \"le\" \"date\" \"%@\"",
|
||||
[endDate descriptionWithCalendarFormat: @"%Y-%m-%d"]]];
|
||||
}
|
||||
|
||||
// Apply conditions
|
||||
if ([allConditions count])
|
||||
[vacation_script appendFormat: @"if allof ( %@ ) { ",
|
||||
[allConditions componentsJoinedByString: @", "]];
|
||||
|
||||
// Custom subject
|
||||
if (useCustomSubject)
|
||||
@@ -910,20 +949,21 @@ static NSString *sieveScriptName = @"sogo";
|
||||
for (i = 0; i < [addresses count]; i++)
|
||||
{
|
||||
[vacation_script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]];
|
||||
|
||||
|
||||
if (i == [addresses count]-1)
|
||||
[vacation_script appendString: @"] "];
|
||||
else
|
||||
[vacation_script appendString: @", "];
|
||||
}
|
||||
|
||||
|
||||
[vacation_script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text];
|
||||
|
||||
if (ignore)
|
||||
|
||||
// Closing bracket of conditions
|
||||
if ([allConditions count])
|
||||
[vacation_script appendString: @"}\r\n"];
|
||||
|
||||
//
|
||||
// See http://sogo.nu/bugs/view.php?id=2332 for details
|
||||
// See https://sogo.nu/bugs/view.php?id=2332 for details
|
||||
//
|
||||
if (alwaysSend)
|
||||
[script insertString: vacation_script atIndex: 0];
|
||||
@@ -941,7 +981,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
int i;
|
||||
|
||||
b = YES;
|
||||
|
||||
|
||||
addresses = [values objectForKey: @"forwardAddress"];
|
||||
if ([addresses isKindOfClass: [NSString class]])
|
||||
addresses = [NSArray arrayWithObject: addresses];
|
||||
@@ -952,11 +992,11 @@ static NSString *sieveScriptName = @"sogo";
|
||||
if (v && [v length] > 0)
|
||||
[script appendFormat: @"redirect \"%@\";\r\n", v];
|
||||
}
|
||||
|
||||
|
||||
if ([[values objectForKey: @"keepCopy"] boolValue])
|
||||
[script appendString: @"keep;\r\n"];
|
||||
}
|
||||
|
||||
|
||||
if ([req count])
|
||||
{
|
||||
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
|
||||
@@ -970,7 +1010,7 @@ static NSString *sieveScriptName = @"sogo";
|
||||
result = [client setActiveScript: @""];
|
||||
// We delete the existing Sieve script
|
||||
result = [client deleteScript: sieveScriptName];
|
||||
|
||||
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"WARNING: Could not delete Sieve script - continuing...: %@", result];
|
||||
}
|
||||
@@ -980,13 +1020,13 @@ static NSString *sieveScriptName = @"sogo";
|
||||
if (b && [script length])
|
||||
{
|
||||
result = [client putScript: sieveScriptName script: script];
|
||||
|
||||
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"Could not upload Sieve script: %@", result];
|
||||
[client closeConnection];
|
||||
[client closeConnection];
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
result = [client setActiveScript: sieveScriptName];
|
||||
if (![[result valueForKey:@"result"] boolValue]) {
|
||||
[self logWithFormat: @"Could not enable Sieve script: %@", result];
|
||||
|
||||
@@ -94,6 +94,8 @@
|
||||
- (int) maximumFailedLoginInterval;
|
||||
- (int) failedLoginBlockInterval;
|
||||
|
||||
- (int) maximumMessageSizeLimit;
|
||||
|
||||
- (int) maximumMessageSubmissionCount;
|
||||
- (int) maximumRecipientCount;
|
||||
- (int) maximumSubmissionInterval;
|
||||
|
||||
@@ -578,6 +578,14 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return v;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (int) maximumMessageSizeLimit
|
||||
{
|
||||
return [self integerForKey: @"SOGoMaximumMessageSizeLimit"];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
@@ -531,9 +531,9 @@ static Class NSNullK;
|
||||
grace: (int *) _grace
|
||||
useCache: (BOOL) useCache
|
||||
{
|
||||
NSString *dictPassword, *username, *jsonUser;
|
||||
NSMutableDictionary *currentUser;
|
||||
NSDictionary *failedCount;
|
||||
NSString *dictPassword, *username, *jsonUser;
|
||||
SOGoSystemDefaults *sd;
|
||||
BOOL checkOK;
|
||||
|
||||
@@ -573,23 +573,25 @@ static Class NSNullK;
|
||||
failedCount = [[SOGoCache sharedCache] failedCountForLogin: username];
|
||||
if (failedCount)
|
||||
{
|
||||
unsigned int current_time, start_time, delta, block_time;
|
||||
unsigned int current_time, last_request_time, start_time, delta_start, delta_last_request, block_time;
|
||||
|
||||
current_time = [[NSCalendarDate date] timeIntervalSince1970];
|
||||
start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue];
|
||||
delta = current_time - start_time;
|
||||
last_request_time = [[failedCount objectForKey: @"LastRequestDate"] unsignedIntValue];
|
||||
delta_start = current_time - start_time;
|
||||
delta_last_request = current_time - last_request_time;
|
||||
|
||||
block_time = [sd failedLoginBlockInterval];
|
||||
|
||||
if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [sd maximumFailedLoginCount] &&
|
||||
delta >= [sd maximumFailedLoginInterval] &&
|
||||
delta <= block_time )
|
||||
delta_last_request >= [sd maximumFailedLoginInterval] &&
|
||||
delta_start <= block_time )
|
||||
{
|
||||
*_perr = PolicyAccountLocked;
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (delta > block_time)
|
||||
if (delta_start > block_time)
|
||||
{
|
||||
[[SOGoCache sharedCache] setFailedCount: 0
|
||||
forLogin: username];
|
||||
@@ -832,8 +834,11 @@ static Class NSNullK;
|
||||
{
|
||||
currentSource = [_sources objectForKey: sourceID];
|
||||
|
||||
// Use the provided domain during the lookup. If none is defined, use the source's one
|
||||
// so if there's a match based on the source's domain, the user ID will be associated
|
||||
// to the right source.
|
||||
userEntry = [currentSource lookupContactEntryWithUIDorEmail: theUID
|
||||
inDomain: theDomain];
|
||||
inDomain: (theDomain ? theDomain : [currentSource domain])];
|
||||
if (userEntry)
|
||||
{
|
||||
[theCurrentUser setObject: sourceID forKey: @"SOGoSource"];
|
||||
|
||||
@@ -107,10 +107,10 @@ class WebDAVClient:
|
||||
self.conn.request(query.method, query.url,
|
||||
body, self.prepare_headers(query, body))
|
||||
try:
|
||||
query.set_response(self.conn.getresponse());
|
||||
query.set_response(self.conn.getresponse())
|
||||
except httplib.BadStatusLine, e:
|
||||
print e
|
||||
query.set_response(self.conn.getresponse());
|
||||
query.set_response(self.conn.getresponse())
|
||||
query.duration = time.time() - query.start
|
||||
|
||||
class HTTPSimpleQuery:
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Delegovaný je organizátor. Prosím určete jiného delegovaného.";
|
||||
"delegate is a participant" = "Delegovaný je již účastníkem.";
|
||||
"delegate is a group" = "Určená adresa odpovídá skupině. Delegovat můžete pouze na osobu.";
|
||||
"Snooze for " = "Odložit o ";
|
||||
"5 minutes" = "5 minut";
|
||||
"10 minutes" = "10 minut";
|
||||
"15 minutes" = "15 minut";
|
||||
"30 minutes" = "30 minut";
|
||||
"45 minutes" = "45 minut";
|
||||
"1 hour" = "1 hodinu";
|
||||
"1 day" = "1 den";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Začátek";
|
||||
"Due Date" = "Do dne";
|
||||
"Location" = "Místo";
|
||||
"Snooze" = "Odložit";
|
||||
"Snooze for " = "Odložit o ";
|
||||
"5 minutes" = "5 minut";
|
||||
"10 minutes" = "10 minut";
|
||||
"15 minutes" = "15 minut";
|
||||
"30 minutes" = "30 minut";
|
||||
"45 minutes" = "45 minut";
|
||||
"1 hour" = "1 hodinu";
|
||||
"1 day" = "1 den";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Důležitý";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Špatné uživatelské jméno nebo heslo.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Zadejte alespoň %{minimumSearchLength} znaků";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Byl dosažen limit pro velikost nahraného souboru";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Přepnout zobrazení";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Přepnout rozsah položek";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Zobrazit nebo skrýt tuto nápovědu";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "shift + mezerník";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "The delegate is the organizer. Please specify a different delegate.";
|
||||
"delegate is a participant" = "The delegate is already a participant.";
|
||||
"delegate is a group" = "The specified address corresponds to a group. You can only delegate to a unique person.";
|
||||
"Snooze for " = "Snooze for ";
|
||||
"5 minutes" = "5 minutes";
|
||||
"10 minutes" = "10 minutes";
|
||||
"15 minutes" = "15 minutes";
|
||||
"30 minutes" = "30 minutes";
|
||||
"45 minutes" = "45 minutes";
|
||||
"1 hour" = "1 hour";
|
||||
"1 day" = "1 day";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Start";
|
||||
"Due Date" = "Due Date";
|
||||
"Location" = "Location";
|
||||
"Snooze" = "Snooze";
|
||||
"Snooze for " = "Snooze for ";
|
||||
"5 minutes" = "5 minutes";
|
||||
"10 minutes" = "10 minutes";
|
||||
"15 minutes" = "15 minutes";
|
||||
"30 minutes" = "30 minutes";
|
||||
"45 minutes" = "45 minutes";
|
||||
"1 hour" = "1 hour";
|
||||
"1 day" = "1 day";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Important";
|
||||
@@ -118,9 +119,12 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Wrong username or password.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Enter at least %{minimumSearchLength} characters";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "File size upload limit reached";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Toggle visibility";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "L'adresse spécifiée correspond à l'organisateur. Veuillez entrer un autre délégué.";
|
||||
"delegate is a participant" = "Le délégué est déjà un participant.";
|
||||
"delegate is a group" = "L'adresse spécifiée correspond à un groupe. Vous ne pouvez déléguer qu'à une personne.";
|
||||
"Snooze for " = "Rappel dans ";
|
||||
"5 minutes" = "5 minutes";
|
||||
"10 minutes" = "10 minutes";
|
||||
"15 minutes" = "15 minutes";
|
||||
"30 minutes" = "30 minutes";
|
||||
"45 minutes" = "45 minutes";
|
||||
"1 hour" = "1 heure";
|
||||
"1 day" = "1 jour";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Début";
|
||||
"Due Date" = "Échéance";
|
||||
"Location" = "Lieu";
|
||||
"Snooze" = "Rappel";
|
||||
"Snooze for " = "Rappel dans ";
|
||||
"5 minutes" = "5 minutes";
|
||||
"10 minutes" = "10 minutes";
|
||||
"15 minutes" = "15 minutes";
|
||||
"30 minutes" = "30 minutes";
|
||||
"45 minutes" = "45 minutes";
|
||||
"1 hour" = "1 heure";
|
||||
"1 day" = "1 jour";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Important";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Mauvais nom d'utilisateur ou mot de passe.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Entrez au moins %{minimumSearchLength} caractères";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Le fichier dépasse la taille limite permise";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Basculer l'affichage";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Basculer l'étendue des éléments";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Afficher ou cache cette aide";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "espace";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "maj. + espace";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Der Vertreter ist der Organisator. Bitte einen anderen Vertreter angeben.";
|
||||
"delegate is a participant" = "Der Vertreter ist bereits ein Teilnehmer.";
|
||||
"delegate is a group" = "Die angegebene Adresse gehört einer Gruppe. Es kann nur an eine einzelne Person delegiert werden.";
|
||||
"Snooze for " = "Erneut erinnern nach ";
|
||||
"5 minutes" = "5 Minuten";
|
||||
"10 minutes" = "10 Minuten";
|
||||
"15 minutes" = "15 Minuten";
|
||||
"30 minutes" = "30 Minuten";
|
||||
"45 minutes" = "45 Minuten";
|
||||
"1 hour" = "1 Stunde";
|
||||
"1 day" = "1 Tag";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Beginn";
|
||||
"Due Date" = "Fällig";
|
||||
"Location" = "Ort";
|
||||
"Snooze" = "Erneut erinnern";
|
||||
"Snooze for " = "Erneut erinnern nach ";
|
||||
"5 minutes" = "5 Minuten";
|
||||
"10 minutes" = "10 Minuten";
|
||||
"15 minutes" = "15 Minuten";
|
||||
"30 minutes" = "30 Minuten";
|
||||
"45 minutes" = "45 Minuten";
|
||||
"1 hour" = "1 Stunde";
|
||||
"1 day" = "1 Tag";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Wichtig";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Falscher Benutzername oder falsches Passwort";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Mindestens %{minimumSearchLength} Zeichen eingeben";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Maximale Dateigröße für das Hochladen erreicht";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Sichtbarkeit umschalten";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Mehrere Punkte umschalten";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Diese Hilfe anzeigen oder ausblenden";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "Umschalt + Leerzeichen";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Wskazany delegat jest organizatorem. Wskaż innego delegata.";
|
||||
"delegate is a participant" = "Delegat jest już uczestnikiem.";
|
||||
"delegate is a group" = "Wskazany adres jest grupą. Możesz oddelegować tylko pojedynczną osobę.";
|
||||
"Snooze for " = "Drzemka przez ";
|
||||
"5 minutes" = "5 minut";
|
||||
"10 minutes" = "10 minut";
|
||||
"15 minutes" = "15 minut";
|
||||
"30 minutes" = "30 minut";
|
||||
"45 minutes" = "45 minut";
|
||||
"1 hour" = "1 godzinę";
|
||||
"1 day" = "1 dzień";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Początek";
|
||||
"Due Date" = "Termin";
|
||||
"Location" = "Miejsce";
|
||||
"Snooze" = "Drzemka";
|
||||
"Snooze for " = "Drzemka przez ";
|
||||
"5 minutes" = "5 minut";
|
||||
"10 minutes" = "10 minut";
|
||||
"15 minutes" = "15 minut";
|
||||
"30 minutes" = "30 minut";
|
||||
"45 minutes" = "45 minut";
|
||||
"1 hour" = "1 godzinę";
|
||||
"1 day" = "1 dzień";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Ważne";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Zła nazwa użytkownika lub hasło.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Wprowadź co najmniej %{minimumSearchLength} znaków";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Rozmiar pliku jest większy niż dopuszczalny limit";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Odwróć widok";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Odwróć wskazany zakres";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Pokaż lub ukryj tę podpowiedź";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "shift + spacja";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Это организатор. Выберите другого приглашаемого.";
|
||||
"delegate is a participant" = "Приглашаемый уже участник.";
|
||||
"delegate is a group" = "Указанный адрес - адрес группы. Вы можете делегировать приглашение лишь отдельным людям.";
|
||||
"Snooze for " = "Отложить на";
|
||||
"5 minutes" = "5 минут";
|
||||
"10 minutes" = "10 минут";
|
||||
"15 minutes" = "15 минут";
|
||||
"30 minutes" = "30 минут";
|
||||
"45 minutes" = "45 минут";
|
||||
"1 hour" = "1 час";
|
||||
"1 day" = "1 день";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Начало";
|
||||
"Due Date" = "Дата начала";
|
||||
"Location" = "Место";
|
||||
"Snooze" = "Отложить";
|
||||
"Snooze for " = "Отложить на";
|
||||
"5 minutes" = "5 минут";
|
||||
"10 minutes" = "10 минут";
|
||||
"15 minutes" = "15 минут";
|
||||
"30 minutes" = "30 минут";
|
||||
"45 minutes" = "45 минут";
|
||||
"1 hour" = "1 час";
|
||||
"1 day" = "1 день";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Важно";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Неправильный логин или пароль.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Введите не менее %{minimumSearchLength} символов";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Достигнуто ограничение на размер загружаемого файла";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Переключить видимость";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Сменить состояние нескольких элементов";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Показать или скрыть подсказку";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "Shift + Пробел";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Delegát je organizátorom. Zadajte prosím iného delegáta.";
|
||||
"delegate is a participant" = "Delegát je už účastníkom.";
|
||||
"delegate is a group" = "Zadaná adresa zodpovedá skupine. Môžete ju delegovať len na jedinečnú osobu.";
|
||||
"Snooze for " = "Odložiť";
|
||||
"5 minutes" = "5 minút";
|
||||
"10 minutes" = "10 minút";
|
||||
"15 minutes" = "15 minút";
|
||||
"30 minutes" = "30 minút";
|
||||
"45 minutes" = "45 minút";
|
||||
"1 hour" = "1 hodinu";
|
||||
"1 day" = "1 deň";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "OK";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Štart";
|
||||
"Due Date" = "Splatnosť";
|
||||
"Location" = "Umiestnenie";
|
||||
"Snooze" = "Odložit";
|
||||
"Snooze for " = "Odložiť do";
|
||||
"5 minutes" = "5 minút";
|
||||
"10 minutes" = "10 minút";
|
||||
"15 minutes" = "15 minút";
|
||||
"30 minutes" = "30 minút";
|
||||
"45 minutes" = "45 minút";
|
||||
"1 hour" = "1 hodinu";
|
||||
"1 day" = "1 deň";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Dôležité";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Nesprávne uživateľské meno alebo heslo.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "Vložte minimálne %{minimumSearchLength} znakov";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Bol dosiahnutý limit pre veľkosť nahraného súboru";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Prepnúť zobrazenie";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Prepnúť rozsah položiek";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Zobraziť alebo skryť túto nápovedu";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "shift + medzerník";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -69,14 +69,6 @@
|
||||
"delegate is organizer" = "Vekil edilen kişi etkinliği düzenleyen kişidir. Lütfen başka bir vekil belirleyin edin. ";
|
||||
"delegate is a participant" = "Vekil edilen kişi zaten bu etkinliğe katılıyor.";
|
||||
"delegate is a group" = "Belirtilen adres bir gruba denk geliyor. Sadece bir kişiyi vekil tayin edebilirsiniz.";
|
||||
"Snooze for " = "Beklenecek süre";
|
||||
"5 minutes" = "5 dakika";
|
||||
"10 minutes" = "10 dakika";
|
||||
"15 minutes" = "15 dakika";
|
||||
"30 minutes" = "30 dakika";
|
||||
"45 minutes" = "45 dakika";
|
||||
"1 hour" = "1 saat";
|
||||
"1 day" = "1 gün";
|
||||
|
||||
/* common buttons */
|
||||
"OK" = "Tamam";
|
||||
@@ -89,6 +81,15 @@
|
||||
"Start" = "Başlangıç";
|
||||
"Due Date" = "Son Tarih";
|
||||
"Location" = "Konum";
|
||||
"Snooze" = "Ertele";
|
||||
"Snooze for " = "Beklenecek süre";
|
||||
"5 minutes" = "5 dakika";
|
||||
"10 minutes" = "10 dakika";
|
||||
"15 minutes" = "15 dakika";
|
||||
"30 minutes" = "30 dakika";
|
||||
"45 minutes" = "45 dakika";
|
||||
"1 hour" = "1 saat";
|
||||
"1 day" = "1 gün";
|
||||
|
||||
/* mail labels */
|
||||
"Important" = "Önemli";
|
||||
@@ -118,18 +119,27 @@
|
||||
/* Authentication failed */
|
||||
"Wrong username or password." = "Kullanıcı adı veya şifre yanlış.";
|
||||
|
||||
/* Error message display bellow search field when the search string has less than the required number of characters */
|
||||
/* Error message displayed bellow search field when the search string has less than the required number of characters */
|
||||
"Enter at least %{minimumSearchLength} characters" = "En az %{minimumSearchLength} harf girin";
|
||||
|
||||
/* Error message displayed when a file upload exceeds WOMaxUploadSize */
|
||||
"File size upload limit reached" = "Yükleme üst sınırı aşıldı";
|
||||
|
||||
/* Toggle visibility (ex: mail account in left navigation menu) */
|
||||
"Toggle visibility" = "Görünürlük geçişi";
|
||||
|
||||
/* Toggle multiple items at the same time (hotkeys cheatsheet) */
|
||||
"Toggle range of items" = "Ögeler arasında geçiş yap";
|
||||
|
||||
/* Question mark shows list of hotkeys */
|
||||
"Show or hide this help" = "Bu yardımı gizle veya göster";
|
||||
|
||||
/* Space key */
|
||||
"key_space" = "space";
|
||||
|
||||
/* Shift and space key */
|
||||
"key_shift+space" = "üst karakter + boşluk";
|
||||
|
||||
/* Up arrow key */
|
||||
"key_up" = "↑";
|
||||
|
||||
|
||||
@@ -20,12 +20,15 @@
|
||||
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGObjWeb/WOContext.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
|
||||
#import <SoObjects/SOGo/SOGoParentFolder.h>
|
||||
#import <SoObjects/SOGo/SOGoUser.h>
|
||||
#import <SoObjects/SOGo/SOGoUserSettings.h>
|
||||
|
||||
#import <SOGo/NSArray+Utilities.h>
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
|
||||
@@ -91,4 +94,49 @@
|
||||
return response;
|
||||
}
|
||||
|
||||
- (WOResponse *) saveFoldersActivationAction
|
||||
{
|
||||
NSDictionary *params;
|
||||
NSEnumerator *foldersEnumerator;
|
||||
NSMutableArray *folderSubscription;
|
||||
NSMutableDictionary *moduleSettings;
|
||||
NSString *baseFolder, *folderName;
|
||||
SOGoParentFolder *clientObject;
|
||||
SOGoUser *activeUser;
|
||||
SOGoUserSettings *us;
|
||||
WORequest *request;
|
||||
BOOL makeActive;
|
||||
|
||||
request = [context request];
|
||||
params = [[request contentAsString] objectFromJSONString];
|
||||
activeUser = [context activeUser];
|
||||
clientObject = [self clientObject];
|
||||
baseFolder = [clientObject nameInContainer];
|
||||
us = [activeUser userSettings];
|
||||
moduleSettings = [us objectForKey: baseFolder];
|
||||
if (!moduleSettings)
|
||||
moduleSettings = [NSMutableDictionary dictionary];
|
||||
[us setObject: moduleSettings forKey: baseFolder];
|
||||
folderSubscription = [moduleSettings objectForKey: @"InactiveFolders"];
|
||||
if (!folderSubscription)
|
||||
{
|
||||
folderSubscription = [NSMutableArray array];
|
||||
[moduleSettings setObject: folderSubscription forKey: @"InactiveFolders"];
|
||||
}
|
||||
|
||||
foldersEnumerator = [params keyEnumerator];
|
||||
while ((folderName = [foldersEnumerator nextObject]))
|
||||
{
|
||||
makeActive = [[params objectForKey: folderName] boolValue];
|
||||
if (makeActive)
|
||||
[folderSubscription removeObject: folderName];
|
||||
else
|
||||
[folderSubscription addObjectUniquely: folderName];
|
||||
}
|
||||
[us synchronize];
|
||||
|
||||
return [self responseWith204];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -78,6 +78,11 @@
|
||||
actionClass = "UIxParentFolderActions";
|
||||
actionName = "createFolder";
|
||||
};
|
||||
saveFoldersActivation = {
|
||||
protectedBy = "Change Images And Files";
|
||||
actionClass = "UIxParentFolderActions";
|
||||
actionName = "saveFoldersActivation";
|
||||
};
|
||||
};
|
||||
};
|
||||
SOGoFolder = {
|
||||
|
||||
@@ -153,8 +153,8 @@
|
||||
= "Bu klasöre üye olunamıyor.";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Adres defterinin erişim yetkileri";
|
||||
"For user" = "Aşağıdaki kullanıcı için";
|
||||
"Access rights to" = "Adres defteri";
|
||||
"For user" = "Erişen";
|
||||
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
|
||||
"Public Access" = "Herkese Açık Erişim";
|
||||
"This person can add cards to this addressbook."
|
||||
|
||||
@@ -49,8 +49,17 @@
|
||||
|
||||
#import "UIxContactFolderActions.h"
|
||||
|
||||
static NSArray *photoTags = nil;
|
||||
|
||||
@implementation UIxContactFolderActions
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
if (!photoTags)
|
||||
{
|
||||
photoTags = [[NSArray alloc] initWithObjects: @"jpegphoto", @"photo", @"thumbnailphoto", nil];
|
||||
}
|
||||
}
|
||||
|
||||
/* actions */
|
||||
|
||||
@@ -146,24 +155,26 @@
|
||||
|
||||
- (int) importLdifData: (NSString *) ldifData
|
||||
{
|
||||
NSMutableDictionary *entry, *encodedEntry;
|
||||
SOGoContactLDIFEntry *ldifEntry;
|
||||
NSArray *ldifContacts, *lines;
|
||||
SOGoContactGCSFolder *folder;
|
||||
NSString *key, *value;
|
||||
NSArray *ldifContacts, *lines, *components;
|
||||
NSMutableDictionary *entry;
|
||||
NSEnumerator *keyEnumerator;
|
||||
NSString *key, *uid, *line;
|
||||
NGVCard *vCard;
|
||||
NSString *uid;
|
||||
int i,j,count,linesCount;
|
||||
int rc = 0;
|
||||
id value;
|
||||
|
||||
NSRange r;
|
||||
int i, j, count, linesCount;
|
||||
int rc;
|
||||
|
||||
folder = [self clientObject];
|
||||
ldifContacts = [ldifData componentsSeparatedByString: @"\ndn"];
|
||||
count = [ldifContacts count];
|
||||
rc = 0;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
SOGoContactLDIFEntry *ldifEntry;
|
||||
NSEnumerator *keyEnumerator;
|
||||
NSMutableDictionary *encodedEntry;
|
||||
encodedEntry = [NSMutableDictionary dictionary];
|
||||
lines = [[ldifContacts objectAtIndex: i]
|
||||
componentsSeparatedByString: @"\n"];
|
||||
@@ -172,7 +183,6 @@
|
||||
linesCount = [lines count];
|
||||
for (j = 0; j < linesCount; j++)
|
||||
{
|
||||
NSString *line;
|
||||
line = [lines objectAtIndex: j];
|
||||
|
||||
/* skip embedded comment lines */
|
||||
@@ -188,17 +198,17 @@
|
||||
if (key != NULL)
|
||||
{
|
||||
value = [[encodedEntry valueForKey: key]
|
||||
stringByAppendingString: [line substringFromIndex: 1]];
|
||||
stringByAppendingString: [line substringFromIndex: 1]];
|
||||
[encodedEntry setValue: value forKey: key];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
components = [line componentsSeparatedByString: @": "];
|
||||
if ([components count] == 2)
|
||||
r = [line rangeOfString: @": "];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
key = [[components objectAtIndex: 0] lowercaseString];
|
||||
value = [components objectAtIndex: 1];
|
||||
key = [[line substringToIndex: r.location] lowercaseString];
|
||||
value = [line substringFromIndex: NSMaxRange(r)];
|
||||
|
||||
if ([key length] == 0)
|
||||
key = @"dn";
|
||||
@@ -220,8 +230,16 @@
|
||||
if ([key hasSuffix: @":"])
|
||||
{
|
||||
key = [key substringToIndex: [key length] - 1];
|
||||
value = [value stringByDecodingBase64];
|
||||
if ([photoTags containsObject: key])
|
||||
value = [value dataByDecodingBase64];
|
||||
else
|
||||
value = [value stringByDecodingBase64];
|
||||
}
|
||||
|
||||
// Standard key recognized in NGCards
|
||||
if ([photoTags containsObject: key])
|
||||
key = @"photo";
|
||||
|
||||
[entry setValue: value forKey: key];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (C) 2005-2015 Inverse inc.
|
||||
Copyright (C) 2005-2016 Inverse inc.
|
||||
|
||||
This file is part of SOGo.
|
||||
|
||||
@@ -62,73 +62,6 @@
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
/* accessors */
|
||||
|
||||
// - (NSString *) _cardStringWithLabel: (NSString *) label
|
||||
// value: (NSString *) value
|
||||
// byEscapingHTMLString: (BOOL) escapeHTML
|
||||
// asLinkScheme: (NSString *) scheme
|
||||
// withLinkAttributes: (NSString *) attrs
|
||||
// {
|
||||
// NSMutableString *cardString;
|
||||
|
||||
// cardString = [NSMutableString stringWithCapacity: 80];
|
||||
// value = [value stringByReplacingString: @"\r" withString: @""];
|
||||
// if ([value length] > 0)
|
||||
// {
|
||||
// if (escapeHTML)
|
||||
// value = [value stringByEscapingHTMLString];
|
||||
// if ([scheme length] > 0)
|
||||
// value = [NSString stringWithFormat: @"<a href=\"%@%@\" %@>%@</a>", scheme, value, attrs, value];
|
||||
|
||||
// if (label)
|
||||
// [cardString appendFormat: @"<dt>%@</dt><dd>%@</dd>\n",
|
||||
// [self labelForKey: label], value];
|
||||
// else
|
||||
// [cardString appendFormat: @"<dt></dt><dd>%@</dd>\n", value];
|
||||
// }
|
||||
|
||||
// return cardString;
|
||||
// }
|
||||
|
||||
// - (NSString *) _cardStringWithLabel: (NSString *) label
|
||||
// value: (NSString *) value
|
||||
// {
|
||||
// return [self _cardStringWithLabel: label
|
||||
// value: value
|
||||
// byEscapingHTMLString: YES
|
||||
// asLinkScheme: nil
|
||||
// withLinkAttributes: nil];
|
||||
// }
|
||||
|
||||
// - (NSString *) _cardStringWithLabel: (NSString *) label
|
||||
// value: (NSString *) value
|
||||
// asLinkScheme: (NSString *) scheme
|
||||
// {
|
||||
// return [self _cardStringWithLabel: label
|
||||
// value: value
|
||||
// byEscapingHTMLString: YES
|
||||
// asLinkScheme: scheme
|
||||
// withLinkAttributes: nil];
|
||||
// }
|
||||
|
||||
// - (NSString *) displayName
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Display Name:"
|
||||
// value: [card fn]];
|
||||
// }
|
||||
|
||||
// - (NSString *) nickName
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Nickname:"
|
||||
// value: [card nickname]];
|
||||
// }
|
||||
|
||||
// - (NSString *) fullName
|
||||
// {
|
||||
// return [card fullName];
|
||||
// }
|
||||
|
||||
- (NSArray *) _languageContactsCategories
|
||||
{
|
||||
NSArray *categoryLabels;
|
||||
@@ -168,346 +101,6 @@
|
||||
return cats;
|
||||
}
|
||||
|
||||
// - (NSString *) primaryEmail
|
||||
// {
|
||||
// NSString *email, *fn, *attrs;
|
||||
|
||||
// email = [card preferredEMail];
|
||||
// if ([email length] > 0)
|
||||
// {
|
||||
// fn = [card fn];
|
||||
// if ([fn length] > 0)
|
||||
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
|
||||
// else
|
||||
// attrs = email;
|
||||
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
|
||||
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
|
||||
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// attrs = nil;
|
||||
// }
|
||||
|
||||
// return [self _cardStringWithLabel: @"Email:"
|
||||
// value: email
|
||||
// byEscapingHTMLString: YES
|
||||
// asLinkScheme: @"mailto:"
|
||||
// withLinkAttributes: attrs];
|
||||
// }
|
||||
|
||||
// - (NSArray *) secondaryEmails
|
||||
// {
|
||||
// NSMutableArray *secondaryEmails;
|
||||
// NSString *email, *fn, *attrs;
|
||||
// NSArray *emails;
|
||||
|
||||
// emails = [card secondaryEmails];
|
||||
// secondaryEmails = [NSMutableArray array];
|
||||
// attrs = nil;
|
||||
|
||||
// // We might not have a preferred item but rather something like this:
|
||||
// // EMAIL;TYPE=work:dd@ee.com
|
||||
// // EMAIL;TYPE=home:ff@gg.com
|
||||
// //
|
||||
// // or:
|
||||
// //
|
||||
// // EMAIL;TYPE=INTERNET:a@a.com
|
||||
// // EMAIL;TYPE=INTERNET,HOME:b@b.com
|
||||
// //
|
||||
// // In this case, we always return the entry NOT matching the primaryEmail
|
||||
// if ([emails count] > 0)
|
||||
// {
|
||||
// int i;
|
||||
|
||||
// for (i = 0; i < [emails count]; i++)
|
||||
// {
|
||||
// email = [[emails objectAtIndex: i] flattenedValuesForKey: @""];
|
||||
// if ([email length])
|
||||
// {
|
||||
// fn = [card fn];
|
||||
// if ([fn length])
|
||||
// attrs = [NSString stringWithFormat: @"%@ <%@>", fn, email];
|
||||
// else
|
||||
// attrs = email;
|
||||
// attrs = [attrs stringByReplacingString: @"'" withString: @"\\'"];
|
||||
// attrs = [attrs stringByReplacingString: @"\"" withString: @"\\\""];
|
||||
// attrs = [NSString stringWithFormat: @"onclick=\"return openMailTo('%@');\"", attrs];
|
||||
|
||||
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
|
||||
// value: email
|
||||
// byEscapingHTMLString: YES
|
||||
// asLinkScheme: @"mailto:"
|
||||
// withLinkAttributes: attrs]];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// [secondaryEmails addObject: [self _cardStringWithLabel: nil
|
||||
// value: nil]];
|
||||
// }
|
||||
|
||||
|
||||
// return secondaryEmails;
|
||||
// }
|
||||
|
||||
// - (NSString *) screenName
|
||||
// {
|
||||
// NSString *screenName;
|
||||
|
||||
// screenName = [[card uniqueChildWithTag: @"x-aim"] flattenedValuesForKey: @""];
|
||||
|
||||
// return [self _cardStringWithLabel: @"Screen Name:"
|
||||
// value: screenName
|
||||
// asLinkScheme: @"aim:goim?screenname="];
|
||||
// }
|
||||
|
||||
// - (NSString *) preferredTel
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Phone Number:"
|
||||
// value: [card preferredTel] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (NSString *) preferredAddress
|
||||
// {
|
||||
// return @"";
|
||||
// }
|
||||
|
||||
// - (BOOL) hasTelephones
|
||||
// {
|
||||
// if (!phones)
|
||||
// phones = [card childrenWithTag: @"tel"];
|
||||
|
||||
// return ([phones count] > 0);
|
||||
// }
|
||||
|
||||
// - (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 _cardStringWithLabel: @"Work:" value: [card workPhone] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (NSString *) homePhone
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Home:" value: [card homePhone] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (NSString *) fax
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Fax:" value: [card fax] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (NSString *) mobile
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Mobile:" value: [card mobile] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (NSString *) pager
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Pager:" value: [card pager] asLinkScheme: @"tel:"];
|
||||
// }
|
||||
|
||||
// - (BOOL) hasHomeInfos
|
||||
// {
|
||||
// BOOL result;
|
||||
// NSArray *elements;
|
||||
|
||||
// elements = [card childrenWithTag: @"adr"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: @"home"];
|
||||
// if ([elements count] > 0)
|
||||
// {
|
||||
// result = YES;
|
||||
// homeAdr = [elements objectAtIndex: 0];
|
||||
// }
|
||||
// else
|
||||
// result = ([[card childrenWithTag: @"url"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: @"home"] count] > 0);
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// - (NSString *) homePobox
|
||||
// {
|
||||
// return [self _cardStringWithLabel: nil
|
||||
// value: [homeAdr flattenedValueAtIndex: 0
|
||||
// forKey: @""]];
|
||||
// }
|
||||
|
||||
// - (NSString *) homeExtendedAddress
|
||||
// {
|
||||
// return [self _cardStringWithLabel: nil
|
||||
// value: [homeAdr flattenedValueAtIndex: 1
|
||||
// forKey: @""]];
|
||||
// }
|
||||
|
||||
// - (NSString *) homeStreetAddress
|
||||
// {
|
||||
// return [self _cardStringWithLabel: nil
|
||||
// value: [homeAdr flattenedValueAtIndex: 2
|
||||
// forKey: @""]];
|
||||
// }
|
||||
|
||||
// - (NSString *) homeCityAndProv
|
||||
// {
|
||||
// NSString *city, *prov;
|
||||
// NSMutableString *data;
|
||||
|
||||
// city = [homeAdr flattenedValueAtIndex: 3 forKey: @""];
|
||||
// prov = [homeAdr flattenedValueAtIndex: 4 forKey: @""];
|
||||
|
||||
// data = [NSMutableString string];
|
||||
// [data appendString: city];
|
||||
// if ([city length] > 0 && [prov length] > 0)
|
||||
// [data appendString: @", "];
|
||||
// [data appendString: prov];
|
||||
|
||||
// return [self _cardStringWithLabel: nil value: data];
|
||||
// }
|
||||
|
||||
// - (NSString *) homePostalCodeAndCountry
|
||||
// {
|
||||
// NSString *postalCode, *country;
|
||||
// NSMutableString *data;
|
||||
|
||||
// postalCode = [homeAdr flattenedValueAtIndex: 5 forKey: @""];
|
||||
// country = [homeAdr flattenedValueAtIndex: 6 forKey: @""];
|
||||
|
||||
// data = [NSMutableString string];
|
||||
// [data appendString: postalCode];
|
||||
// if ([postalCode length] > 0 && [country length] > 0)
|
||||
// [data appendFormat: @", "];
|
||||
// [data appendString: country];
|
||||
|
||||
// return [self _cardStringWithLabel: nil value: data];
|
||||
// }
|
||||
|
||||
// - (NSString *) _formattedURL: (NSString *) url
|
||||
// {
|
||||
// NSRange schemaR;
|
||||
// NSString *schema, *data;
|
||||
|
||||
// if ([url length] > 0)
|
||||
// {
|
||||
// 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
|
||||
// {
|
||||
// schema = nil;
|
||||
// data = nil;
|
||||
// }
|
||||
|
||||
// return [self _cardStringWithLabel: nil
|
||||
// value: data
|
||||
// byEscapingHTMLString: YES
|
||||
// asLinkScheme: schema
|
||||
// withLinkAttributes: @"target=\"_blank\""];
|
||||
// }
|
||||
|
||||
|
||||
// - (NSString *) _urlOfType: (NSString *) aType
|
||||
// {
|
||||
// NSArray *elements;
|
||||
// NSString *url;
|
||||
|
||||
// elements = [card childrenWithTag: @"url"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: aType];
|
||||
// if ([elements count] > 0)
|
||||
// url = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
|
||||
// else
|
||||
// url = nil;
|
||||
|
||||
// return [self _formattedURL: url];
|
||||
// }
|
||||
|
||||
// - (NSString *) homeUrl
|
||||
// {
|
||||
// NSString *s;
|
||||
|
||||
// s = [self _urlOfType: @"home"];
|
||||
|
||||
// if (!s || [s length] == 0)
|
||||
// {
|
||||
// NSArray *elements;
|
||||
// NSString *workURL;
|
||||
// int i;
|
||||
|
||||
// elements = [card childrenWithTag: @"url"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: @"work"];
|
||||
// workURL = nil;
|
||||
|
||||
// if ([elements count] > 0)
|
||||
// workURL = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
|
||||
|
||||
// elements = [card childrenWithTag: @"url"];
|
||||
|
||||
// if (workURL && [elements count] > 1)
|
||||
// {
|
||||
// for (i = 0; i < [elements count]; i++)
|
||||
// {
|
||||
// if ([[[elements objectAtIndex: i] flattenedValuesForKey: @""]
|
||||
// caseInsensitiveCompare: workURL] != NSOrderedSame)
|
||||
// {
|
||||
// s = [[elements objectAtIndex: i] flattenedValuesForKey: @""];
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
// else if (!workURL && [elements count] > 0)
|
||||
// {
|
||||
// s = [[elements objectAtIndex: 0] flattenedValuesForKey: @""];
|
||||
// }
|
||||
|
||||
// if (s && [s length] > 0)
|
||||
// s = [self _formattedURL: s];
|
||||
// }
|
||||
|
||||
// return s;
|
||||
// }
|
||||
|
||||
// - (BOOL) hasWorkInfos
|
||||
// {
|
||||
// BOOL result;
|
||||
// NSArray *elements;
|
||||
|
||||
// elements = [card childrenWithTag: @"adr"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: @"work"];
|
||||
// if ([elements count] > 0)
|
||||
// {
|
||||
// result = YES;
|
||||
// workAdr = [elements objectAtIndex: 0];
|
||||
// }
|
||||
// else
|
||||
// result = (([[card childrenWithTag: @"url"
|
||||
// andAttribute: @"type"
|
||||
// havingValue: @"work"] count] > 0)
|
||||
// || [[card childrenWithTag: @"org"] count] > 0);
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// - (NSString *) workTitle
|
||||
// {
|
||||
// return [self _cardStringWithLabel: nil value: [card title]];
|
||||
// }
|
||||
|
||||
- (NSArray *) orgUnits
|
||||
{
|
||||
NSMutableArray *orgUnits;
|
||||
@@ -640,120 +233,6 @@
|
||||
return addresses;
|
||||
}
|
||||
|
||||
// - (NSString *) workService
|
||||
// {
|
||||
// NSMutableArray *orgServices;
|
||||
// NSArray *values;
|
||||
// CardElement *org;
|
||||
// NSString *service, *services;
|
||||
// NSUInteger count, max;
|
||||
|
||||
// org = [card org];
|
||||
// values = [org valuesForKey: @""];
|
||||
// max = [values count];
|
||||
// if (max > 1)
|
||||
// {
|
||||
// orgServices = [NSMutableArray arrayWithCapacity: max];
|
||||
// for (count = 1; count < max; count++)
|
||||
// {
|
||||
// service = [org flattenedValueAtIndex: count forKey: @""];
|
||||
// if ([service length] > 0)
|
||||
// [orgServices addObject: service];
|
||||
// }
|
||||
|
||||
// services = [orgServices componentsJoinedByString: @", "];
|
||||
// }
|
||||
// else
|
||||
// services = nil;
|
||||
|
||||
// return [self _cardStringWithLabel: nil value: services];
|
||||
// }
|
||||
|
||||
// - (NSString *) workUrl
|
||||
// {
|
||||
// return [self _urlOfType: @"work"];
|
||||
// }
|
||||
|
||||
// - (BOOL) hasOtherInfos
|
||||
// {
|
||||
// return ([[card note] length] > 0
|
||||
// || [[card bday] length] > 0
|
||||
// || [[card tz] length] > 0);
|
||||
// }
|
||||
|
||||
// - (NSString *) bday
|
||||
// {
|
||||
// SOGoDateFormatter *dateFormatter;
|
||||
// NSCalendarDate *date;
|
||||
// NSString *bday;
|
||||
|
||||
// date = [card birthday];
|
||||
// bday = nil;
|
||||
|
||||
// if (date)
|
||||
// {
|
||||
// dateFormatter = [[[self context] activeUser] dateFormatterInContext: context];
|
||||
// bday = [dateFormatter formattedDate: date];
|
||||
// }
|
||||
|
||||
// return bday;
|
||||
// //return [self _cardStringWithLabel: @"Birthday:" value: bday];
|
||||
// }
|
||||
|
||||
// - (NSString *) tz
|
||||
// {
|
||||
// return [self _cardStringWithLabel: @"Timezone:" value: [card tz]];
|
||||
// }
|
||||
|
||||
// - (NSArray *) notes
|
||||
// {
|
||||
// NSMutableArray *notes;
|
||||
// NSString *note;
|
||||
// NSUInteger count, max;
|
||||
|
||||
// notes = [NSMutableArray arrayWithArray: [card notes]];
|
||||
// max = [notes count];
|
||||
// for (count = 0; count < max; count++)
|
||||
// {
|
||||
// note = [notes objectAtIndex: count];
|
||||
// note = [note stringByEscapingHTMLString];
|
||||
// note = [note stringByReplacingString: @"\r\n"
|
||||
// withString: @"<br />"];
|
||||
// note = [note stringByReplacingString: @"\n"
|
||||
// withString: @"<br />"];
|
||||
|
||||
// [notes replaceObjectAtIndex: count withObject: note];
|
||||
// }
|
||||
|
||||
// return notes;
|
||||
// }
|
||||
|
||||
/* hrefs */
|
||||
|
||||
// - (NSString *) completeHrefForMethod: (NSString *) _method
|
||||
// withParameter: (NSString *) _param
|
||||
// forKey: (NSString *) _key
|
||||
// {
|
||||
// NSString *href;
|
||||
|
||||
// [self setQueryParameter:_param forKey:_key];
|
||||
// href = [self completeHrefForMethod:[self ownMethodName]];
|
||||
// [self setQueryParameter:nil forKey:_key];
|
||||
|
||||
// return href;
|
||||
// }
|
||||
|
||||
// - (NSString *)attributesTabLink {
|
||||
// return [self completeHrefForMethod:[self ownMethodName]
|
||||
// withParameter:@"attributes"
|
||||
// forKey:@"tab"];
|
||||
// }
|
||||
// - (NSString *)debugTabLink {
|
||||
// return [self completeHrefForMethod:[self ownMethodName]
|
||||
// withParameter:@"debug"
|
||||
// forKey:@"tab"];
|
||||
// }
|
||||
|
||||
/* action */
|
||||
|
||||
- (id <WOActionResults>) defaultAction
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
|
||||
[viewer setBodyInfo: info];
|
||||
[viewer setPartPath: [self childPartPath]];
|
||||
[viewer setAttachmentIds: attachmentIds];
|
||||
[renderedParts addObject: [viewer renderedPart]];
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <libxml/encoding.h>
|
||||
|
||||
#import <SoObjects/SOGo/NSString+Utilities.h>
|
||||
#import <SoObjects/Mailer/SOGoMailObject.h>
|
||||
#import <SoObjects/Mailer/SOGoMailBodyPart.h>
|
||||
|
||||
@@ -908,6 +909,8 @@ static NSData* _sanitizeContent(NSData *theData)
|
||||
NSObject <SaxXMLReader> *parser;
|
||||
NSData *preparsedContent;
|
||||
SOGoMailObject *mail;
|
||||
NSString *s;
|
||||
|
||||
xmlCharEncoding enc;
|
||||
|
||||
mail = [self clientObject];
|
||||
@@ -917,7 +920,7 @@ static NSData* _sanitizeContent(NSData *theData)
|
||||
createXMLReaderForMimeType: @"text/html"];
|
||||
|
||||
handler = [_UIxHTMLMailContentHandler new];
|
||||
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
|
||||
[handler setAttachmentIds: attachmentIds];
|
||||
|
||||
// We check if we got an unsupported charset. If so
|
||||
// we convert everything to UTF-16{LE,BE} so it passes
|
||||
@@ -926,8 +929,6 @@ static NSData* _sanitizeContent(NSData *theData)
|
||||
enc = [self _xmlCharEncoding];
|
||||
if (enc == XML_CHAR_ENCODING_ERROR)
|
||||
{
|
||||
NSString *s;
|
||||
|
||||
s = [NSString stringWithData: preparsedContent
|
||||
usingEncodingNamed: [[bodyInfo objectForKey:@"parameterList"]
|
||||
objectForKey: @"charset"]];
|
||||
@@ -954,6 +955,14 @@ static NSData* _sanitizeContent(NSData *theData)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Let's sanitize the string to make sure libxml doesn't go havoc
|
||||
if (enc == XML_CHAR_ENCODING_UTF8)
|
||||
{
|
||||
s = [[NSString alloc] initWithData: preparsedContent encoding: NSUTF8StringEncoding];
|
||||
preparsedContent = [[s safeString] dataUsingEncoding: NSUTF8StringEncoding];
|
||||
RELEASE(s);
|
||||
}
|
||||
|
||||
[handler setContentEncoding: enc];
|
||||
|
||||
[parser setContentHandler: handler];
|
||||
@@ -1038,7 +1047,7 @@ static NSData* _sanitizeContent(NSData *theData)
|
||||
encoding = @"us-ascii";
|
||||
|
||||
handler = [_UIxHTMLMailContentHandler new];
|
||||
[handler setAttachmentIds: [mail fetchFileAttachmentIds]];
|
||||
[handler setAttachmentIds: attachmentIds];
|
||||
|
||||
// We check if we got an unsupported charset. If so
|
||||
// we convert everything to UTF-16{LE,BE} so it passes
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#import <Appointments/SOGoAppointmentFolders.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <Mailer/SOGoMailBodyPart.h>
|
||||
|
||||
@@ -234,20 +235,20 @@
|
||||
- (WOResponse *) _changePartStatusAction: (NSString *) newStatus
|
||||
withDelegate: (iCalPerson *) delegate
|
||||
{
|
||||
WOResponse *response;
|
||||
SOGoAppointmentObject *eventObject;
|
||||
WOResponse *response;
|
||||
iCalEvent *chosenEvent;
|
||||
iCalAlarm *alarm;
|
||||
|
||||
chosenEvent = [self _setupChosenEventAndEventObject: &eventObject];
|
||||
if (chosenEvent)
|
||||
{
|
||||
// For invitations, we take the organizers's alarm to start with
|
||||
alarm = [[chosenEvent alarms] lastObject];
|
||||
response = (WOResponse*)[eventObject changeParticipationStatus: newStatus
|
||||
withDelegate: delegate
|
||||
alarm: nil
|
||||
alarm: alarm
|
||||
forRecurrenceId: [chosenEvent recurrenceId]];
|
||||
// if (ex)
|
||||
// response = ex; //[self responseWithStatus: 500];
|
||||
// else
|
||||
if (!response)
|
||||
response = [self responseWith204];
|
||||
}
|
||||
@@ -380,15 +381,50 @@
|
||||
|
||||
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
|
||||
{
|
||||
NSString *emailFrom;
|
||||
SOGoMailObject *mailObject;
|
||||
NGImap4EnvelopeAddress *address;
|
||||
SOGoMailObject *mailObject;
|
||||
NSString *emailFrom;
|
||||
iCalPerson *p;
|
||||
|
||||
mailObject = [[self clientObject] mailObject];
|
||||
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
|
||||
emailFrom = [address baseEMail];
|
||||
p = [event findAttendeeWithEmail: emailFrom];
|
||||
|
||||
return [event findAttendeeWithEmail: emailFrom];
|
||||
// We haven't found it yet, let's look in the identities
|
||||
// associated to this user
|
||||
if (!p)
|
||||
{
|
||||
SOGoUserManager *sm;
|
||||
NSString *uid;
|
||||
|
||||
sm = [SOGoUserManager sharedUserManager];
|
||||
uid = [sm getUIDForEmail: emailFrom];
|
||||
|
||||
if (uid)
|
||||
{
|
||||
NSArray *allEmails;
|
||||
NSString *email;
|
||||
SOGoUser *u;
|
||||
int i;
|
||||
|
||||
u = [SOGoUser userWithLogin: uid];
|
||||
allEmails = [u allEmails];
|
||||
for (i = 0; i < [allEmails count]; i++)
|
||||
{
|
||||
email = [allEmails objectAtIndex: i];
|
||||
if ([email caseInsensitiveCompare: emailFrom] == NSOrderedSame)
|
||||
continue;
|
||||
|
||||
p = [event findAttendeeWithEmail: email];
|
||||
|
||||
if (p)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserDefaults.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <Appointments/iCalEntityObject+SOGo.h>
|
||||
#import <Appointments/SOGoAppointmentFolder.h>
|
||||
#import <Appointments/SOGoAppointmentObject.h>
|
||||
@@ -470,31 +471,67 @@
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
- (iCalPerson *) storedReplyAttendee
|
||||
- (iCalPerson *) _attendeeFromMailSenderInEvent: (iCalEvent *) e
|
||||
{
|
||||
/*
|
||||
TODO: since an attendee can have multiple email addresses, maybe we
|
||||
should translate the email to an internal uid and then retrieve
|
||||
all emails addresses for matching the participant.
|
||||
|
||||
Note: -findAttendeeWithEmail: does not parse the email!
|
||||
*/
|
||||
iCalEvent *e;
|
||||
NSString *baseEmail;
|
||||
iCalPerson *p;
|
||||
|
||||
p = nil;
|
||||
|
||||
e = [self storedEvent];
|
||||
if (e)
|
||||
baseEmail = [self replySenderBaseEMail];
|
||||
|
||||
/*
|
||||
Note: -findAttendeeWithEmail: does not parse the email!
|
||||
*/
|
||||
p = [e findAttendeeWithEmail: baseEmail];
|
||||
if (!p)
|
||||
p = [e findAttendeeWithEmail:[self replySenderEMail]];
|
||||
|
||||
// We haven't found it yet, let's look in the identities
|
||||
// associated to this user
|
||||
if (!p)
|
||||
{
|
||||
p = [e findAttendeeWithEmail: [self replySenderBaseEMail]];
|
||||
if (!p)
|
||||
p = [e findAttendeeWithEmail:[self replySenderEMail]];
|
||||
SOGoUserManager *sm;
|
||||
NSString *uid;
|
||||
|
||||
sm = [SOGoUserManager sharedUserManager];
|
||||
uid = [sm getUIDForEmail: baseEmail];
|
||||
|
||||
if (uid)
|
||||
{
|
||||
NSArray *allEmails;
|
||||
NSString *email;
|
||||
SOGoUser *u;
|
||||
int i;
|
||||
|
||||
u = [SOGoUser userWithLogin: uid];
|
||||
allEmails = [u allEmails];
|
||||
for (i = 0; i < [allEmails count]; i++)
|
||||
{
|
||||
email = [allEmails objectAtIndex: i];
|
||||
if ([email caseInsensitiveCompare: baseEmail] == NSOrderedSame)
|
||||
continue;
|
||||
|
||||
p = [e findAttendeeWithEmail: email];
|
||||
|
||||
if (p)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
- (iCalPerson *) storedReplyAttendee
|
||||
{
|
||||
iCalEvent *e;
|
||||
|
||||
e = [self storedEvent];
|
||||
if (e)
|
||||
return [self _attendeeFromMailSenderInEvent: e];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL) isReplySenderAnAttendee
|
||||
{
|
||||
return ([self storedReplyAttendee] != nil);
|
||||
@@ -502,15 +539,7 @@
|
||||
|
||||
- (iCalPerson *) _emailParticipantWithEvent: (iCalEvent *) event
|
||||
{
|
||||
NSString *emailFrom;
|
||||
SOGoMailObject *mailObject;
|
||||
NGImap4EnvelopeAddress *address;
|
||||
|
||||
mailObject = [[self clientObject] mailObject];
|
||||
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
|
||||
emailFrom = [address baseEMail];
|
||||
|
||||
return [event findAttendeeWithEmail: emailFrom];
|
||||
return [self _attendeeFromMailSenderInEvent: event];
|
||||
}
|
||||
|
||||
- (BOOL) hasSenderStatusChanged
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
|
||||
[viewer setBodyInfo: info];
|
||||
[viewer setPartPath: [self childPartPath]];
|
||||
[viewer setAttachmentIds: attachmentIds];
|
||||
[renderedParts addObject: [viewer renderedPart]];
|
||||
}
|
||||
contentType = [NSString stringWithFormat: @"%@/%@",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
@class NSArray;
|
||||
@class NSData;
|
||||
@class NSFormatter;
|
||||
@class NSMutableDictionary;
|
||||
|
||||
@class SOGoMailBodyPart;
|
||||
|
||||
@@ -53,6 +54,7 @@
|
||||
NSArray *partPath;
|
||||
id bodyInfo;
|
||||
NSData *flatContent;
|
||||
NSDictionary *attachmentIds;
|
||||
}
|
||||
|
||||
/* accessors */
|
||||
@@ -66,6 +68,8 @@
|
||||
- (SOGoMailBodyPart *) clientPart;
|
||||
- (id) renderedPart;
|
||||
|
||||
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds;
|
||||
|
||||
- (NSData *)flatContent;
|
||||
- (NSData *)decodedFlatContent;
|
||||
- (NSString *)flatContentAsString;
|
||||
|
||||
@@ -42,6 +42,16 @@
|
||||
|
||||
@implementation UIxMailPartViewer
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
attachmentIds = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[flatContent release];
|
||||
@@ -153,6 +163,11 @@
|
||||
nil];
|
||||
}
|
||||
|
||||
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
|
||||
{
|
||||
attachmentIds = newAttachmentIds;
|
||||
}
|
||||
|
||||
- (NSData *) content
|
||||
{
|
||||
return [[self clientObject] fetchBLOB];
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Vítejte v SOGo Mailer. Využijte stromu složek nalevo pro procházení Vašimi mailovými účty!";
|
||||
"Read messages" = "Číst zprávy";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Napsat zprávu";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Napsat zprávu v novém okně";
|
||||
|
||||
"Share" = "Sdílet";
|
||||
"Account" = "Účet";
|
||||
"Shared Account" = "Sdílený účet";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Převést na úkol";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Stáhnout všechny přílohy";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "přílohy";
|
||||
|
||||
"Print..." = "Tisk...";
|
||||
"Delete Message" = "Smazat zprávu";
|
||||
"Delete Selected Messages" = "Smazat označené zprávy";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"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 is too big" = "Zpráva je příliš velká";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Odesílání zprávy";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!";
|
||||
"Read messages" = "Read messages";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Write a new message";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Write a message in new window";
|
||||
|
||||
"Share" = "Share";
|
||||
"Account" = "Account";
|
||||
"Shared Account" = "Shared Account";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Convert To Task";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Download all attachments";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "attachments";
|
||||
|
||||
"Print..." = "Print...";
|
||||
"Delete Message" = "Delete Message";
|
||||
"Delete Selected Messages" = "Delete Selected Messages";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"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 is too big" = "Message is too big";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Sending";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Bienvenue dans votre webmail. Utilisez l'arborescence sur votre gauche pour parcourir vos boites mails.";
|
||||
"Read messages" = "Lire les messages";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Écrire un nouveau message";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Écrire un message dans une nouvelle fenêtre";
|
||||
|
||||
"Share" = "Partage";
|
||||
"Account" = "Compte";
|
||||
"Shared Account" = "Compte partagé";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Convertir en tâche";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Télécharger les pièces jointes";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "pièces-jointes";
|
||||
|
||||
"Print..." = "Imprimer...";
|
||||
"Delete Message" = "Supprimer le message";
|
||||
"Delete Selected Messages" = "Supprimer les messages sélectionnés";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"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 is too big" = "Le message dépasse la limite permise";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Envoi en cours";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Willkommen zum SOGo Mailer. Verwenden Sie den Ordnerbaum auf der linken Seite, um in Ihrem E-Mail-Konto zu stöbern!";
|
||||
"Read messages" = "Nachrichten lesen";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Eine neue Nachricht schreiben";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Nachricht in einem neuen Fenster schreiben";
|
||||
|
||||
"Share" = "Benutzerrechte";
|
||||
"Account" = "Konto";
|
||||
"Shared Account" = "Gemeinsames Konto";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "In Aufgabe umwandeln";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Alle Anhänge herunterladen";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "Anhänge";
|
||||
|
||||
"Print..." = "Drucken...";
|
||||
"Delete Message" = "Nachricht löschen";
|
||||
"Delete Selected Messages" = "Gewählte Nachricht(en) löschen";
|
||||
@@ -280,7 +292,7 @@
|
||||
"%{0} message(s) copied" = "%{0} Nachrichten kopiert";
|
||||
|
||||
/* Notification on the number of messages successfuly movied */
|
||||
"%{0} message(s) moved" = "%{0} Nachrichten verschoben";
|
||||
"%{0} message(s) moved" = "%{0} Nachricht(en) verschoben";
|
||||
|
||||
"Please select a message." = "Sie müssen eine Nachricht auswählen.";
|
||||
"Please select a message to print." = "Sie müssen eine Nachricht zum Drucken auswählen.";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"Error while uploading the file \"%{0}\":" = "Fehler beim Hochladen der Datei \"%{0}\":";
|
||||
"There is an active file upload. Closing the window will interrupt it." = "Es wird gerade eine Datei hochgeladen. Das Schließen des Fensters wird dies abbrechen.";
|
||||
"Message is too big" = "Nachricht ist zu groß";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Sende";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Witaj w programie SOGo Mailer. Używaj drzewa folderów po lewej stronie by przeglądać swoje konta pocztowe!";
|
||||
"Read messages" = "Wiadomości przeczytane";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Napisz nową wiadomość";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Utwórz wiadomość w nowym oknie";
|
||||
|
||||
"Share" = "Udostępnianie";
|
||||
"Account" = "Konto";
|
||||
"Shared Account" = "Udostępnione konto";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Zamień na zadanie";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Pobierz wszystkie załączniki";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "załączniki";
|
||||
|
||||
"Print..." = "Drukuj...";
|
||||
"Delete Message" = "Usuń wiadomość";
|
||||
"Delete Selected Messages" = "Usuń zaznaczone wiadomości";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"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 is too big" = "Wiadomość jest zbyt duża";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Wysyłanie";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Добро пожаловать в почту SOGo! Для просмотра сообщений воспользуйтесь деревом папок слева.";
|
||||
"Read messages" = "Читать сообщения";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Составить новое сообщение";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Составить сообщение в новом окне";
|
||||
|
||||
"Share" = "Совместное использование";
|
||||
"Account" = "Пользователь";
|
||||
"Shared Account" = "Общая учетная запись";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Преобразовать в задание";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Загрузить все вложения";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "вложения";
|
||||
|
||||
"Print..." = "Печать...";
|
||||
"Delete Message" = "Удалить сообщение";
|
||||
"Delete Selected Messages" = "Удалить выделенные сообщения";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"Error while uploading the file \"%{0}\":" = "Ошибка при загрузке файла \"%{0}\":";
|
||||
"There is an active file upload. Closing the window will interrupt it." = "Сейчас выполняется загрузка файла. Закрытие окна прервет её.";
|
||||
"Message is too big" = "Слишком большое сообщение";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Отправляется";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "Vitajte v SOGo Mailer. Použite zoznam zložiek vľavo na prehliadanie Vášho emailového účtu!";
|
||||
"Read messages" = "Čítať správu";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Nápísať správu";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Napísať správu v novom okne";
|
||||
|
||||
"Share" = "Zdielať";
|
||||
"Account" = "Účet";
|
||||
"Shared Account" = "Zdielaný účet";
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Zmeniť na úlohu";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Stiahnuť všetky prílohy";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "prílohy";
|
||||
|
||||
"Print..." = "Tlačiť";
|
||||
"Delete Message" = "Zmazať správu";
|
||||
"Delete Selected Messages" = "Zmazať vybrané správy";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"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 is too big" = "Správa je príliš veľká";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Odosielam správu";
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
/* Mail account main windows */
|
||||
"Welcome to the SOGo Mailer. Use the folder tree on the left to browse your mail accounts!" = "SOGo Postalayıcısına hoş geldiniz. E-posta hesaplarınızı görmek için soldaki klasör ağacını kullanın!";
|
||||
"Read messages" = "İletileri oku";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a new message" = "Yeni ileti yaz";
|
||||
|
||||
/* Tooltip for fab button */
|
||||
"Write a message in new window" = "Yeni pencerede bir ileti yaz";
|
||||
|
||||
"Share" = "Paylaşım";
|
||||
"Account" = "Hesap";
|
||||
"Shared Account" = "Paylaşılan Hesap";
|
||||
@@ -96,7 +102,7 @@
|
||||
"To" = "Alıcı";
|
||||
"Cc" = "Karbon Kopya";
|
||||
"Bcc" = "Gizli Karbon Kopya";
|
||||
"Reply-To" = "Yanıtlama adresi";
|
||||
"Reply-To" = "Yanıt adresi";
|
||||
"Add address" = "Adresleri ekle";
|
||||
"Body" = "Gövde";
|
||||
"Open" = "Aç";
|
||||
@@ -111,7 +117,7 @@
|
||||
"bcc" = "Gizli Karbon Kopya";
|
||||
"Add a recipient" = "Bir alıcı ekleyin";
|
||||
"Edit Draft..." = "Taslağı Düzenle...";
|
||||
"Load Images" = "Resimleri yükle";
|
||||
"Load Images" = "Resimleri göster";
|
||||
"Return Receipt" = "Alındı Raporu";
|
||||
"The sender of this message has asked to be notified when you read this message. Do you with to notify the sender?" = "Bu iletiyi gönderen kişi, iletiyi okuduğunuzdan haberdar olmak istedi. Gönderene alındı raporu yollamak ister misiniz?";
|
||||
"Return Receipt (displayed) - %@"= "Alındı Raporu (görüntülendi) - %@";
|
||||
@@ -155,10 +161,10 @@
|
||||
"Untitled" = "Başlıksız";
|
||||
|
||||
/* Tree */
|
||||
"SentFolderName" = "Gönderilmiş";
|
||||
"SentFolderName" = "Giden";
|
||||
"TrashFolderName" = "Çöp";
|
||||
"InboxFolderName" = "Gelen";
|
||||
"DraftsFolderName" = "Taslaklar";
|
||||
"DraftsFolderName" = "Taslak";
|
||||
"JunkFolderName" = "İstenmeyen";
|
||||
"SieveFolderName" = "Süzgeçler";
|
||||
"Folders" = "Klasörler"; /* title line */
|
||||
@@ -193,8 +199,8 @@
|
||||
"Delegation..." = "Yetkili Tayini...";
|
||||
|
||||
/* Use This Folder menu */
|
||||
"Sent Messages" = "Gönderilmiş İletiler";
|
||||
"Drafts" = "Taslaklar";
|
||||
"Sent Messages" = "Giden İletiler";
|
||||
"Drafts" = "Taslak";
|
||||
"Deleted Messages" = "Silinmiş İletiler";
|
||||
"Junk Messages" = "İstenmeyen İletiler";
|
||||
|
||||
@@ -217,6 +223,12 @@
|
||||
/* Message view "more" menu: create a task from message */
|
||||
"Convert To Task" = "Göreve Dönüştür";
|
||||
|
||||
/* Message view "more" menu: download all attachments as a zip archive */
|
||||
"Download all attachments" = "Tüm eklentileri indir";
|
||||
|
||||
/* Filename prefix when downloading all attachments as a zip archive */
|
||||
"attachments" = "eklentiler";
|
||||
|
||||
"Print..." = "Yazdır...";
|
||||
"Delete Message" = "İletiyi Sil";
|
||||
"Delete Selected Messages" = "Seçili İletileri Sil";
|
||||
@@ -284,7 +296,7 @@
|
||||
|
||||
"Please select a message." = "Lütfen bir ileti seçin.";
|
||||
"Please select a message to print." = "Lütfen yazdırılacak iletiyi seçin.";
|
||||
"Please select only one message to print." = "Lütfen yazdırmak için sadece bir ileti seçin.";
|
||||
"Please select only one message to print." = "Yazdırmak için lütfen sadece bir ileti seçin.";
|
||||
"The message you have selected doesn't exist anymore." = "Seçtiğiniz ileti artık mevcut değil.";
|
||||
"The folder with name \"%{0}\" could not be created."
|
||||
= "\"%{0}\" adındaki klasör oluşturulamıyor.";
|
||||
@@ -315,6 +327,7 @@
|
||||
/* Error when uploading a file attachment */
|
||||
"Error while uploading the file \"%{0}\":" = " \"%{0}\" dosyasını yüklerken hata oluştu:";
|
||||
"There is an active file upload. Closing the window will interrupt it." = "Devam eden bir dosya yükleme işlemi var. Pencereyi kapanırsa yükleme işlemi duracak.";
|
||||
"Message is too big" = "İleti çok büyük";
|
||||
|
||||
/* Appears while sending the message */
|
||||
"Sending" = "Gönderiliyor";
|
||||
@@ -340,8 +353,8 @@
|
||||
"Rename" = "Yeniden Adlandır";
|
||||
"Compact" = "Sıkıştır";
|
||||
"Export" = "Dışarı Aktar";
|
||||
"Set as Drafts" = "Taslaklar klasörü olarak tanımla";
|
||||
"Set as Sent" = "Gönderilmiş klasörü olarak tanımla";
|
||||
"Set as Drafts" = "Taslak klasörü olarak tanımla";
|
||||
"Set as Sent" = "Giden klasörü olarak tanımla";
|
||||
"Set as Trash" = "Çöp klasörü olarak tanımla";
|
||||
|
||||
/* Set the folder as the one holding Junk mails */
|
||||
|
||||
@@ -661,10 +661,9 @@ static NSArray *infoKeys = nil;
|
||||
{
|
||||
NSDictionary *d;
|
||||
|
||||
d = [NSDictionary dictionaryWithObjectsAndKeys: msg, @"textStatus", nil];
|
||||
d = [NSDictionary dictionaryWithObjectsAndKeys: msg, @"message", nil];
|
||||
|
||||
return [self responseWithStatus: 500
|
||||
andString: [d jsonRepresentation]];
|
||||
return [self responseWithStatus: 500 andJSONRepresentation: d];
|
||||
}
|
||||
|
||||
/* attachment helper */
|
||||
|
||||
@@ -213,6 +213,25 @@ static NSString *mailETag = nil;
|
||||
viewer = [[context mailRenderingContext] viewerForBodyInfo: info];
|
||||
[viewer setBodyInfo: info];
|
||||
|
||||
NSMutableDictionary *attachmentIds;
|
||||
NSDictionary *attributes;
|
||||
NSString *filename;
|
||||
unsigned int count, max;
|
||||
|
||||
max = [[self attachmentAttrs] count];
|
||||
attachmentIds = [NSMutableDictionary dictionaryWithCapacity: max];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
attributes = [[self attachmentAttrs] objectAtIndex: count];
|
||||
filename = [NSString stringWithFormat: @"<%@>", [attributes objectForKey: @"filename"]];
|
||||
[attachmentIds setObject: [attributes objectForKey: @"url"]
|
||||
forKey: filename];
|
||||
if ([[attributes objectForKey: @"bodyId"] length])
|
||||
[attachmentIds setObject: [attributes objectForKey: @"url"]
|
||||
forKey: [attributes objectForKey: @"bodyId"]];
|
||||
}
|
||||
[viewer setAttachmentIds: attachmentIds];
|
||||
|
||||
return viewer;
|
||||
}
|
||||
|
||||
@@ -312,6 +331,18 @@ static NSString *mailETag = nil;
|
||||
return response;
|
||||
}
|
||||
|
||||
- (id <WOActionResults>) archiveAttachmentsAction
|
||||
{
|
||||
NSString *name;
|
||||
SOGoMailObject *co;
|
||||
|
||||
co = [self clientObject];
|
||||
name = [NSString stringWithFormat: @"%@-%@.zip",
|
||||
[self labelForKey: @"attachments"], [co nameInContainer]];
|
||||
|
||||
return [co archiveAllFilesinArchiveNamed: name];
|
||||
}
|
||||
|
||||
/* MDN */
|
||||
|
||||
- (BOOL) _userHasEMail: (NSString *) email
|
||||
|
||||
@@ -244,6 +244,11 @@
|
||||
pageName = "UIxMailView";
|
||||
actionName = "sendMDN";
|
||||
};
|
||||
archiveAttachments = {
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailView";
|
||||
actionName = "archiveAttachments";
|
||||
};
|
||||
viewsource = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailSourceView";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ChineseTaiwan */
|
||||
{
|
||||
NSLanguageName = "ChineseTaiwan";
|
||||
NSFormalName = "ChineseTaiwan";
|
||||
NSFormalName = "正體中文";
|
||||
NSLocaleCode = "zh"; /* ISO 639-1 */
|
||||
NSLanguageCode = "zho"; /* ISO 639-2 */
|
||||
NSParentContext = "";
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#import <SOGo/SOGoWebAuthenticator.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserFolder.h>
|
||||
#import <SOGo/SOGoUserProfile.h>
|
||||
#import <SOGo/SOGoSession.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||
@@ -535,14 +536,14 @@
|
||||
{
|
||||
NSString *uid;
|
||||
NSDictionary *contact;
|
||||
NSString *contactInfo, *login;
|
||||
NSString *contactInfo, *owner;
|
||||
NSMutableArray *jsonResponse;
|
||||
NSMutableDictionary *jsonLine;
|
||||
NSArray *allUsers;
|
||||
int count, max;
|
||||
BOOL activeUserIsInDomain;
|
||||
|
||||
login = [[context activeUser] login];
|
||||
owner = [[self clientObject] ownerInContext: context];
|
||||
activeUserIsInDomain = ([domain length] == 0 || [[[context activeUser] domain] isEqualToString: domain]);
|
||||
|
||||
// We sort our array - this is pretty useful for the Web
|
||||
@@ -556,8 +557,8 @@
|
||||
contact = [allUsers objectAtIndex: count];
|
||||
uid = [contact objectForKey: @"c_uid"];
|
||||
|
||||
// We do NOT return the current authenticated user
|
||||
if (!activeUserIsInDomain || ![uid isEqualToString: login])
|
||||
// We do NOT return the owner from which the search is performed
|
||||
if (!activeUserIsInDomain || ![uid isEqualToString: owner])
|
||||
{
|
||||
jsonLine = [NSMutableDictionary dictionary];
|
||||
if ([domain length])
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Text podpisu";
|
||||
"Please specify a valid sender address." = "Prosím zadejte správnou adresu odesílatele.";
|
||||
"Please specify a valid reply-to address." = "Prosím zadejte správnou adresu pro odpověď.";
|
||||
"Specify a hostname other than the local host" = "Zadefinujte jiné doménové jméno než local host";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Dodatečné parametry";
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Please enter your signature below";
|
||||
"Please specify a valid sender address." = "Please specify a valid sender address.";
|
||||
"Please specify a valid reply-to address." = "Please specify a valid reply-to address.";
|
||||
"Specify a hostname other than the local host" = "Specify a hostname other than the local host";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Additional Parameters";
|
||||
|
||||
@@ -35,16 +35,16 @@
|
||||
"contacts_category_labels" = "Ami, Client, Collègue, Concurrent, Famille, Fournisseur, Partenaire d'affaire, Presse, VIP";
|
||||
|
||||
/* vacation (auto-reply) */
|
||||
"Enable vacation auto reply" = "Activer message d'absence prolongée";
|
||||
"Enable vacation auto reply" = "Activer les réponses automatiques d'absence prolongée";
|
||||
"Enable custom auto reply subject" = "Activer un sujet personnalisé";
|
||||
"Auto reply subject" = "Sujet personnalisé";
|
||||
"You can write ${subject} to insert the original subject" = "Inscrivez ${subject} pour insérer le sujet original";
|
||||
"Auto reply message" = "Message de réponse automatique";
|
||||
"Auto reply subject" = "Modifier le sujet de la réponse automatique";
|
||||
"You can write ${subject} to insert the original subject" = "Inscrire ${subject} pour insérer le sujet original";
|
||||
"Auto reply message" = "Contenu de la réponse automatique";
|
||||
"Email addresses (separated by commas)" = "Adresses courriels (séparées par des virgules)";
|
||||
"Add default email addresses" = "Ajouter les adresses par défaut";
|
||||
"Days between responses" = "Nombre de jours entre réponses";
|
||||
"Days between responses" = "Nombre de jours entre les réponses d'absence";
|
||||
"Do not send responses to mailing lists" = "Ne pas envoyer les réponses aux listes de diffusion";
|
||||
"Enable auto reply on" = "Activer la réponse automatique le";
|
||||
"Enable auto reply on" = "Débuter la réponse automatique le";
|
||||
"Disable auto reply on" = "Désactiver la réponse automatique le";
|
||||
"Always send vacation message response" = "Toujours envoyer le message de vacances prolongées";
|
||||
"Please specify your message and your email addresses for which you want to enable auto reply."
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Introduisez votre signature ci-dessous";
|
||||
"Please specify a valid sender address." = "Veuillez définir une adresse d'expédition valide.";
|
||||
"Please specify a valid reply-to address." = "Veuillez définir une adresse de retour valide.";
|
||||
"Specify a hostname other than the local host" = "Veuillez utiliser un nom d'hôte autre que l'hôte local";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Paramètres supplémentaires";
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
"Label" = "Schlagwort";
|
||||
"New label" = "Neue Bezeichnung";
|
||||
"Show subscribed mailboxes only" = "Nur abonnierte Ordner anzeigen";
|
||||
"Synchronize only default mail folders (EAS)" = "Nur Standard-Mail-Ordner synchronisieren (EAS)";
|
||||
"Synchronize only default mail folders (EAS)" = "Nur Standard-E-Mail-Ordner synchronisieren (EAS)";
|
||||
"Sort messages by threads" = "Nachrichten nach Thema sortieren";
|
||||
"When sending mail, add unknown recipients to my" = "Unbekannte Empfänger meiner E-Mails hinzufügen zu";
|
||||
"Address Book" = "Adressbuch";
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Bitte fügen Sie die Signatur hier ein";
|
||||
"Please specify a valid sender address." = "Bitte geben Sie eine gültige Absenderadresse an.";
|
||||
"Please specify a valid reply-to address." = "Bitte geben Sie eine gültige \"Antworten An\"-Adresse (Reply-To) an.";
|
||||
"Specify a hostname other than the local host" = "Geben Sie einen anderen Servernamen an, als den Lokalen";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Zusätzliche Einstellungen";
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Wprowadź swoją sygnaturę";
|
||||
"Please specify a valid sender address." = "Wprowadź poprawny adres nadawcy.";
|
||||
"Please specify a valid reply-to address." = "Wprowadź poprawny adres \"Odpowiedź do\"";
|
||||
"Specify a hostname other than the local host" = "Podaj nazwę serwera inną niż host lokalny";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Dodatkowe parametry";
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Введите Вашу подпись ниже";
|
||||
"Please specify a valid sender address." = "Пожалуйста, укажите правильный адрес отправителя.";
|
||||
"Please specify a valid reply-to address." = "Пожалуйста, укажите правильный адрес для ответа.";
|
||||
"Specify a hostname other than the local host" = "Укажите имя хоста, отличное от localhost";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Дополнительные параметры";
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"Please enter your signature below" = "Prosím vložte svoj podpis nižšie";
|
||||
"Please specify a valid sender address." = "Prosím zvoľte platnú adresu odosielateľa";
|
||||
"Please specify a valid reply-to address." = "Prosím zvoľte platnú adresu pre odpoveď.";
|
||||
"Specify a hostname other than the local host" = "Zadajte iné doménové meno ako local host";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Dalšie parametre";
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
"Enable vacation auto reply" = "Otomatik tatil yanıtını etkinleştir";
|
||||
"Enable custom auto reply subject" = "Özelleştirilmiş otomatik yanıt konusunu etkinleştir";
|
||||
"Auto reply subject" = "Otomatik yanıt konusu";
|
||||
"You can write ${subject} to insert the original subject" = "Orjinal konuyu iliştirmek için ${subject} yazabilirsiniz";
|
||||
"You can write ${subject} to insert the original subject" = "Asıl konuyu eklemek için ${subject} yazabilirsiniz";
|
||||
"Auto reply message" = "Otomatik yanıt iletisi";
|
||||
"Email addresses (separated by commas)" = "E-posta adresleri (virgülle ayrılmış)";
|
||||
"Add default email addresses" = "Varsayılan e-posta adresi ekle";
|
||||
"Days between responses" = "Yanıtlar arasındaki gün sayısı";
|
||||
"Do not send responses to mailing lists" = "Yanıtları e-posta listelerine gönderme";
|
||||
"Enable auto reply on" = "Otomatik yanıtlamayı şu tarihlerde etkinleştir";
|
||||
"Disable auto reply on" = "Otomatik yanıtlamayı şu tarihlerde devre dışı bırak";
|
||||
"Enable auto reply on" = "Otomatik yanıtı şu tarihlerde etkinleştir";
|
||||
"Disable auto reply on" = "Otomatik yanıtı şu tarihlerde devre dışı bırak";
|
||||
"Always send vacation message response" = "Her zaman tatil yanıtı iletisi gönder";
|
||||
"Please specify your message and your email addresses for which you want to enable auto reply."
|
||||
= "Lütfen otomatik yanıtı etkinleştireceğiniz e-posta adreslerini ve yanıt iletinizi belirleyin.";
|
||||
@@ -150,14 +150,14 @@
|
||||
"Show subscribed mailboxes only" = "Sadece üye olunan e-posta klasörlerini göster";
|
||||
"Synchronize only default mail folders (EAS)" = "Sadece varsayılan e-posta klasörlerini eşitle (EAS)";
|
||||
"Sort messages by threads" = "İletileri diziler halinde sırala";
|
||||
"When sending mail, add unknown recipients to my" = "İleti gönderirken, kayıtlı olmayan alıcıları aşağıdaki adres defterime ekle";
|
||||
"When sending mail, add unknown recipients to my" = "Gönderilen iletideki alıcıları aşağıdaki defterime ekle";
|
||||
"Address Book" = "Adres Defteri";
|
||||
"Forward messages" = "Başkasına İletilen e-postayı";
|
||||
"messageforward_inline" = "İletinin İçine Ekle";
|
||||
"messageforward_attached" = "Dosya Olarak Ekle";
|
||||
"When replying to a message" = "İletiyi yanıtlarken";
|
||||
"replyplacement_above" = "Yanıtımı orjinal yazının üzerinde başlat";
|
||||
"replyplacement_below" = "Yanıtımı orjinal yazının altında başlat";
|
||||
"replyplacement_above" = "Yanıtımı asıl yazının üzerinde başlat";
|
||||
"replyplacement_below" = "Yanıtımı asıl yazının altında başlat";
|
||||
"And place my signature" = "Ve imzamı";
|
||||
"signatureplacement_above" = "yanıtımın altına yerleştir";
|
||||
"signatureplacement_below" = "yazının altına yerleştir";
|
||||
@@ -168,15 +168,15 @@
|
||||
/* Base font size for messages composed in HTML */
|
||||
"Default font size" = "Varsayılan yazı tipi boyutu";
|
||||
|
||||
"Display remote inline images" = "İleti içinindeki harici resimleri";
|
||||
"displayremoteinlineimages_never" = "Hiç bir zaman gösterme";
|
||||
"displayremoteinlineimages_always" = "Her zaman göster";
|
||||
"Auto save every" = "Yazdığım iletiyi şu aralıklarla kaydet";
|
||||
"Display remote inline images" = "İletiye gömülü harici resimleri";
|
||||
"displayremoteinlineimages_never" = "Gösterme";
|
||||
"displayremoteinlineimages_always" = "Göster";
|
||||
"Auto save every" = "Yazılan iletinin otomatik kaydedilme aralığı";
|
||||
"minutes" = "dakika";
|
||||
|
||||
/* Contact */
|
||||
"Personal Address Book" = "Kişisel Adres Defteri";
|
||||
"Collected Address Book" = "Otomatik Toplanan Adresler";
|
||||
"Collected Address Book" = "Otomatik Eklenen Adresler";
|
||||
|
||||
/* IMAP Accounts */
|
||||
"Mail Account" = "E-posta Hesabı";
|
||||
@@ -193,7 +193,8 @@
|
||||
"(Click to create)" = "(Oluşturmak için tıklayın)";
|
||||
"Please enter your signature below" = "Lütfen aşağıya imzanızı yazın";
|
||||
"Please specify a valid sender address." = "Lütfen geçeril bir gönderici adresi belirtin.";
|
||||
"Please specify a valid reply-to address." = "Lütfen geçeril bir yanıtlama adresi belirtin.";
|
||||
"Please specify a valid reply-to address." = "Lütfen geçeril bir yanıt adresi belirtin.";
|
||||
"Specify a hostname other than the local host" = "Yerel sunucu adından farklı bir sunucu adı belirtin";
|
||||
|
||||
/* Additional Parameters */
|
||||
"Additional Parameters" = "Diğer Özellikler";
|
||||
@@ -278,13 +279,13 @@
|
||||
|
||||
/* Return receipts */
|
||||
"When I receive a request for a return receipt" = "Alındı bildirim isteği aldığımda";
|
||||
"Never send a return receipt" = "Alındı bildirimi gönderme";
|
||||
"Allow return receipts for some messages" = "Bazı iletiler için alındı bildirimi gönder";
|
||||
"Never send a return receipt" = "Gönderme";
|
||||
"Allow return receipts for some messages" = "Aşağıdaki durumlarda alındı bildirimi gönder";
|
||||
"If I'm not in the To or Cc of the message" = "Eğer alıcı veya karbon kopya alanında değilsem";
|
||||
"If the sender is outside my domain" = "Eğer gönderen adresi alan adımın dışında ise";
|
||||
"In all other cases" = "diğer her koşullarda";
|
||||
"Never send" = "Hiç bir zaman gönderme";
|
||||
"Always send" = "Her zaman gönder";
|
||||
"Never send" = "Gönderme";
|
||||
"Always send" = "Gönder";
|
||||
"Ask me" = "Sor";
|
||||
|
||||
/* Filters - UIxPreferences */
|
||||
|
||||
@@ -1622,14 +1622,6 @@ static NSArray *reminderValues = nil;
|
||||
userCanChangePassword];
|
||||
}
|
||||
|
||||
//
|
||||
// Used wox by template
|
||||
//
|
||||
- (NSString *) sieveFolderEncoding
|
||||
{
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
|
||||
}
|
||||
|
||||
// - (NSString *) localeCode
|
||||
// {
|
||||
// // WARNING : NSLocaleCode is not defined in <Foundation/NSUserDefaults.h>
|
||||
@@ -1835,6 +1827,15 @@ static NSArray *reminderValues = nil;
|
||||
return [self labelForKey: item];
|
||||
}
|
||||
|
||||
- (NSDictionary *) languageLocale
|
||||
{
|
||||
WOResourceManager *rm;
|
||||
|
||||
rm = [self pageResourceManager];
|
||||
|
||||
return [rm localeForLanguageNamed: item];
|
||||
}
|
||||
|
||||
//
|
||||
// Used by wox template
|
||||
//
|
||||
|
||||
@@ -72,22 +72,22 @@
|
||||
"Forbidden" = "Yasaklanmış";
|
||||
|
||||
/* acls */
|
||||
"Access rights to" = "Takvimin erişim yetkileri";
|
||||
"For user" = "Aşağıdaki kullanıcı için";
|
||||
"Access rights to" = "Takvim";
|
||||
"For user" = "Erişen";
|
||||
"Any Authenticated User" = "Kimliği doğrulanmış her kullanıcı";
|
||||
"Public Access" = "Herkese Açık Erişim";
|
||||
"label_Public" = "Herkese Açık";
|
||||
"label_Private" = "Kişisel";
|
||||
"label_Confidential" = "Gizli";
|
||||
"label_Viewer" = "Hepsini Görüntüle";
|
||||
"label_Viewer" = "Hepsini Görüntüler";
|
||||
"label_DAndTViewer" = "Günü ve Saati Görüntüler";
|
||||
"label_Modifier" = "Değiştir";
|
||||
"label_Responder" = "Yanıtlanan Kişi";
|
||||
"label_Modifier" = "Değiştirir";
|
||||
"label_Responder" = "Yanıtlar";
|
||||
"label_None" = "Hiç biri";
|
||||
"View All" = "Hepsini Görüntüler";
|
||||
"View the Date & Time" = "Günü ve Saati Görüntüler";
|
||||
"Modify" = "Değiştirir";
|
||||
"Respond To" = "Yanıtlanan Kişi";
|
||||
"Respond To" = "Yanıtlar";
|
||||
"None" = "Hiç biri";
|
||||
"This person can create objects in my calendar."
|
||||
= "Bu kişi bu takvimimde öge oluşturabilir.";
|
||||
@@ -100,7 +100,7 @@
|
||||
"New calendar" = "Yeni Takvim";
|
||||
"Name of the Calendar" = "Takvimin Adı";
|
||||
"new" = "Yeni";
|
||||
"Print view" = "Yazdırma görünümü";
|
||||
"Print view" = "Yazdırma Görünümü";
|
||||
"edit" = "Düzenle";
|
||||
"delete" = "Sil";
|
||||
"proposal" = "Teklif";
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
<main class="view"
|
||||
layout="row" layout-fill="layout-fill"
|
||||
ui-view="administration"
|
||||
ng-controller="navController">
|
||||
<!-- administration -->
|
||||
</main>
|
||||
ng-controller="navController"><!-- administration --></main>
|
||||
|
||||
<script type="text/ng-template" id="administration.html">
|
||||
<md-sidenav id="left-sidenav" class="md-sidenav-left md-whiteframe-z1" md-component-id="left" md-is-locked-open="$mdMedia('gt-md')" layout="column">
|
||||
|
||||
<!-- Sidenav -->
|
||||
<md-sidenav class="md-sidenav-left md-whiteframe-z1" layout="column"
|
||||
md-component-id="left" md-is-locked-open="isGtMedium"
|
||||
ng-class="{ 'sg-close': leftIsClose }">
|
||||
<var:component className="UIxSidenavToolbarTemplate" />
|
||||
<md-content class="md-flex" layout="column" md-scroll-y="md-scroll-y"
|
||||
md-colors="::{ backgroundColor: 'default-background-300' }">
|
||||
|
||||
@@ -74,14 +74,10 @@
|
||||
ng-class="{ 'sg-expanded': user.uid == acl.selectedUid }">
|
||||
<a class="md-flex md-button" ng-click="acl.selectUser(user, $event)">
|
||||
<div layout="row" layout-align="start center">
|
||||
<span class="card-picture" ng-switch="user.userClass">
|
||||
<div ng-switch-when="normal-user">
|
||||
<sg-avatar-image class="md-avatar"
|
||||
sg-email="user.c_email"
|
||||
size="40"><!-- avatar --></sg-avatar-image>
|
||||
</div>
|
||||
<div ng-switch-when="normal-group" class="md-avatar sg-avatar-list"><!-- normal-group --></div>
|
||||
<div ng-switch-when="public-user" class="md-avatar sg-avatar-list"><!-- public-user --></div>
|
||||
<span class="card-picture">
|
||||
<sg-avatar-image class="md-avatar"
|
||||
sg-email="user.c_email"
|
||||
size="40">{{ user.$avatarIcon }}</sg-avatar-image>
|
||||
</span>
|
||||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead"><div>{{user.cn}}</div></div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
ng-disabled="properties.addressbook.id == 'personal'"
|
||||
ng-true-value="1"
|
||||
ng-false-value="0">
|
||||
<var:string label:value="Synchronize"/>
|
||||
<var:string label:value="Synchronize"/> (Microsoft Enterprise ActiveSync)
|
||||
</md-checkbox>
|
||||
</md-dialog-content>
|
||||
|
||||
|
||||
Loaded 100 of 237 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user