diff --git a/ChangeLog b/ChangeLog index f1e91e804..d3275311c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2010-03-02 Wolfgang Sourdeau + * UI/PreferencesUI/UIxPreferences.m (-nameLabel, -colorLabel): + removed useless methods. + + * SoObjects/SOGo/SOGoUserDefaults.m (-sieveFilters) + (-setSieveFilters:): new accessors. + + * SoObjects/SOGo/SOGoDomainDefaults.m (-sieveScriptEnabled): new + accessor. + + * SoObjects/Mailer/SOGoSieveConverter.m: new class for producing + sieve scripts from user defaults. + + * UI/PreferencesUI/UIxFilterEditor.m: new template class for + editing sieve scripts. + * UI/WebServerResources/generic.js (_): new gettextable equivalent of "getLabel". diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index 7e13e2505..828d708bd 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -33,6 +33,8 @@ Mailer_OBJC_FILES += \ SOGoMailForward.m \ SOGoMailReply.m \ \ + SOGoSieveConverter.m \ + \ EOQualifier+MailDAV.m \ NSData+Mail.m \ NSString+Mail.m diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 6b0edb895..9455f42d9 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -50,8 +50,10 @@ #import "SOGoMailManager.h" #import "SOGoMailNamespace.h" #import "SOGoSentFolder.h" +#import "SOGoSieveConverter.h" #import "SOGoTrashFolder.h" + #import "SOGoMailAccount.h" @implementation SOGoMailAccount @@ -232,23 +234,25 @@ static NSString *sieveScriptName = @"sogo"; - (BOOL) updateFilters { - NSMutableString *header, *script; + NSMutableArray *requirements; + NSMutableString *script, *header; NGInternetSocketAddress *address; NSDictionary *result, *values; SOGoUserDefaults *ud; SOGoDomainDefaults *dd; NGSieveClient *client; - NSString *v, *password; + NSString *filterScript, *v, *password; + SOGoSieveConverter *converter; BOOL b; dd = [[context activeUser] domainDefaults]; - if (!([dd vacationEnabled] || [dd forwardEnabled])) + if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled])) return YES; + requirements = [NSMutableArray arrayWithCapacity: 15]; ud = [[context activeUser] userDefaults]; b = NO; - header = [NSMutableString stringWithString: @"require ["]; script = [NSMutableString string]; // Right now, we handle Sieve filters here and only for vacation @@ -274,8 +278,8 @@ static NSString *sieveScriptName = @"sogo"; if (days == 0) days = 7; - [header appendString: @"\"vacation\""]; - + [requirements addObjectUniquely: @"vacation"]; + // Skip mailing lists if (ignore) [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*\" ) {"]; @@ -315,9 +319,27 @@ static NSString *sieveScriptName = @"sogo"; [script appendString: @"keep;\r\n"]; } - if ([header compare: @"require ["] != NSOrderedSame) + converter = [SOGoSieveConverter sieveConverterForUser: [context activeUser]]; + filterScript = [converter sieveScriptWithRequirements: requirements]; + if (filterScript) { - [header appendString: @"];\r\n"]; + if ([filterScript length]) + { + b = YES; + [script appendString: filterScript]; + } + } + else + { + [self errorWithFormat: @"Sieve generation failure: %@", + [converter lastScriptError]]; + return NO; + } + + if ([requirements count]) + { + header = [NSString stringWithFormat: @"require [\"%@\"];\r\n", + [requirements componentsJoinedByString: @"\",\""]]; [script insertString: header atIndex: 0]; } diff --git a/SoObjects/Mailer/SOGoSieveConverter.h b/SoObjects/Mailer/SOGoSieveConverter.h new file mode 100644 index 000000000..3a8b2359b --- /dev/null +++ b/SoObjects/Mailer/SOGoSieveConverter.h @@ -0,0 +1,49 @@ +/* SOGoSieveConverter.h - this file is part of SOGo + * + * Copyright (C) 2010 Wolfgang Sourdeau + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOSIEVECONVERTER_H +#define SOGOSIEVECONVERTER_H + +#import + +@class NSDictionary; +@class NSMutableArray; +@class NSString; + +@class SOGoUser; + +@interface SOGoSieveConverter : NSObject +{ + SOGoUser *user; + NSMutableArray *requirements; + NSString *scriptError; +} + ++ (id) sieveConverterForUser: (SOGoUser *) user; +- (id) initForUser: (SOGoUser *) newUser; + +- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements; +- (NSString *) lastScriptError; + +@end + +#endif /* SOGOSIEVECONVERTER_H */ diff --git a/SoObjects/Mailer/SOGoSieveConverter.m b/SoObjects/Mailer/SOGoSieveConverter.m new file mode 100644 index 000000000..54e275575 --- /dev/null +++ b/SoObjects/Mailer/SOGoSieveConverter.m @@ -0,0 +1,610 @@ +/* SOGoSieveConverter.m - this file is part of SOGo + * + * Copyright (C) 2010 Wolfgang Sourdeau + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import "SOGoMailAccounts.h" + +#import "SOGoSieveConverter.h" + +typedef enum { + UIxFilterFieldTypeAddress, + UIxFilterFieldTypeHeader, + UIxFilterFieldTypeSize, +} UIxFilterFieldType; + +static NSArray *sieveOperators = nil; +static NSArray *sieveSizeOperators = nil; +static NSMutableDictionary *fieldTypes = nil; +static NSDictionary *sieveFields = nil; +static NSDictionary *sieveFlags = nil; +static NSDictionary *operatorRequirements = nil; +static NSDictionary *methodRequirements = nil; + +@interface NSString (SOGoSieveExtension) + +- (NSString *) asSieveQuotedString; + +@end + +@implementation NSString (SOGoSieveExtension) + +- (NSString *) _asSingleLineSieveQuotedString +{ + NSString *escapedString; + + escapedString = [[self stringByReplacingString: @"\\" + withString: @"\\\\"] + stringByReplacingString: @"\"" + withString: @"\\\""]; + + return [NSString stringWithFormat: @"\"%@\"", escapedString]; +} + +- (NSString *) _asMultiLineSieveQuotedString +{ + NSArray *lines; + NSMutableArray *newLines; + NSString *line, *newText; + int count, max; + + lines = [self componentsSeparatedByString: @"\n"]; + max = [lines count]; + newLines = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + line = [lines objectAtIndex: count]; + if ([line length] > 0 && [line characterAtIndex: 0] == '.') + [newLines addObject: [NSString stringWithFormat: @".%@", line]]; + else + [newLines addObject: line]; + } + + newText = [NSString stringWithFormat: @"\r\n%@\r\n.\r\n", + [newLines componentsJoinedByString: @"\n"]]; + + return newText; +} + +- (NSString *) asSieveQuotedString +{ + NSRange nlRange; + + nlRange = [self rangeOfString: @"\n"]; + + return ((nlRange.length > 0) + ? [self _asMultiLineSieveQuotedString] + : [self _asSingleLineSieveQuotedString]); +} + +@end + +@implementation SOGoSieveConverter + ++ (void) initialize +{ + NSArray *fields; + + if (!sieveOperators) + { + sieveOperators = [NSArray arrayWithObjects: @"is", @"contains", + @"matches", @"regex", + @"over", @"under", nil]; + [sieveOperators retain]; + } + if (!sieveSizeOperators) + { + sieveSizeOperators = [NSArray arrayWithObjects: @"over", @"under", nil]; + [sieveSizeOperators retain]; + } + if (!fieldTypes) + { + fieldTypes = [NSMutableDictionary new]; + fields = [NSArray arrayWithObjects: @"to", @"cc", @"to_or_cc", @"from", + nil]; + [fieldTypes setObject: [NSNumber + numberWithInt: UIxFilterFieldTypeAddress] + forKeys: fields]; + fields = [NSArray arrayWithObjects: @"header", @"subject", nil]; + [fieldTypes setObject: [NSNumber + numberWithInt: UIxFilterFieldTypeHeader] + forKeys: fields]; + [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeSize] + forKey: @"size"]; + } + if (!sieveFields) + { + sieveFields + = [NSDictionary dictionaryWithObjectsAndKeys: + @"\"to\"", @"to", + @"\"cc\"", @"cc", + @"[\"to\", \"cc\"]", @"to_or_cc", + @"\"from\"", @"from", + @"\"subject\"", @"subject", + nil]; + [sieveFields retain]; + } + if (!sieveFlags) + { + sieveFlags + = [NSDictionary dictionaryWithObjectsAndKeys: + @"\\Answered", @"answered", + @"\\Deleted", @"deleted", + @"\\Draft", @"draft", + @"\\Flagged", @"flagged", + @"Junk", @"junk", + @"NotJunk", @"not_junk", + @"\\Seen", @"seen", + @"$Label1", @"label1", + @"$Label2", @"label2", + @"$Label3", @"label3", + @"$Label4", @"label4", + @"$Label5", @"label5", + nil]; + [sieveFlags retain]; + } + if (!operatorRequirements) + { + operatorRequirements + = [NSDictionary dictionaryWithObjectsAndKeys: + @"regex", @"regex", + nil]; + [operatorRequirements retain]; + } + if (!methodRequirements) + { + methodRequirements + = [NSDictionary dictionaryWithObjectsAndKeys: + @"imapflags", @"addflag", + @"imapflags", @"removeflag", + @"imapflags", @"flag", + @"vacation", @"vacation", + @"notify", @"notify", + @"fileinto", @"fileinto", + @"reject", @"reject", + @"regex", @"regex", + nil]; + [methodRequirements retain]; + } +} + ++ (id) sieveConverterForUser: (SOGoUser *) newUser +{ + SOGoSieveConverter *newConverter; + + newConverter = [[self alloc] initForUser: newUser]; + [newConverter autorelease]; + + return newConverter; +} + +- (id) init +{ + if ((self = [super init])) + { + user = nil; + requirements = nil; + scriptError = nil; + } + + return self; +} + +- (id) initForUser: (SOGoUser *) newUser +{ + if ((self = [self init])) + { + ASSIGN (user, newUser); + } + + return self; +} + +- (void) dealloc +{ + [user release]; + [requirements release]; + [scriptError release]; + [super dealloc]; +} + +- (BOOL) _saveFilters +{ + return YES; +} + +- (BOOL) _extractRuleField: (NSString **) field + fromRule: (NSDictionary *) rule + andType: (UIxFilterFieldType *) type +{ + NSNumber *fieldType; + NSString *jsonField, *customHeader; + + jsonField = [rule objectForKey: @"field"]; + if (jsonField) + { + fieldType = [fieldTypes objectForKey: jsonField]; + if (fieldType) + { + *type = [fieldType intValue]; + if ([jsonField isEqualToString: @"header"]) + { + customHeader = [rule objectForKey: @"custom_header"]; + if ([customHeader length]) + *field = [customHeader asSieveQuotedString]; + else + scriptError = (@"Pseudo-header field 'header' without" + @" 'custom_header' parameter."); + } + else if ([jsonField isEqualToString: @"size"]) + *field = nil; + else + *field = [sieveFields objectForKey: jsonField]; + } + else + scriptError + = [NSString stringWithFormat: @"Rule based on unknown field '%@'", + *field]; + } + else + scriptError = @"Rule without any specified field."; + + return (scriptError == nil); +} + +- (BOOL) _extractRuleOperator: (NSString **) operator + fromRule: (NSDictionary *) rule + isNot: (BOOL *) isNot +{ + NSString *jsonOperator, *baseOperator, *requirement; + int baseLength; + + jsonOperator = [rule objectForKey: @"operator"]; + if (jsonOperator) + { + *isNot = [jsonOperator hasSuffix: @"_not"]; + if (*isNot) + { + baseLength = [jsonOperator length] - 4; + baseOperator + = [jsonOperator substringWithRange: NSMakeRange (0, baseLength)]; + } + else + baseOperator = jsonOperator; + + if ([sieveOperators containsObject: baseOperator]) + { + requirement = [operatorRequirements objectForKey: baseOperator]; + if (requirement) + [requirements addObjectUniquely: requirement]; + *operator = baseOperator; + } + else + scriptError = [NSString stringWithFormat: + @"Rule has unknown operator '%@'", + baseOperator]; + } + else + scriptError = @"Rule without any specified operator"; + + return (scriptError == nil); +} + +- (BOOL) _validateRuleOperator: (NSString *) operator + withFieldType: (UIxFilterFieldType) type +{ + BOOL rc; + + if (type == UIxFilterFieldTypeSize) + rc = [sieveSizeOperators containsObject: operator]; + else + rc = (![sieveSizeOperators containsObject: operator] + && [sieveOperators containsObject: operator]); + + return rc; +} + +- (BOOL) _extractRuleValue: (NSString **) value + fromRule: (NSDictionary *) rule + withFieldType: (UIxFilterFieldType) type +{ + NSString *extractedValue; + + extractedValue = [rule objectForKey: @"value"]; + if (extractedValue) + { + if (type == UIxFilterFieldTypeSize) + *value = [NSString stringWithFormat: @"%d", + [extractedValue intValue]]; + else + *value = [extractedValue asSieveQuotedString]; + } + else + scriptError = @"Rule lacks a 'value' parameter"; + + return (scriptError == nil); +} + +- (NSString *) _composeSieveRuleOnField: (NSString *) field + withType: (UIxFilterFieldType) type + operator: (NSString *) operator + revert: (BOOL) revert + andValue: (NSString *) value +{ + NSMutableString *sieveRule; + + sieveRule = [NSMutableString stringWithCapacity: 100]; + if (revert) + [sieveRule appendString: @"not "]; + if (type == UIxFilterFieldTypeAddress) + [sieveRule appendString: @"address "]; + else if (type == UIxFilterFieldTypeHeader) + [sieveRule appendString: @"header "]; + else if (type == UIxFilterFieldTypeSize) + [sieveRule appendString: @"size "]; + [sieveRule appendFormat: @":%@ ", operator]; + if (type == UIxFilterFieldTypeSize) + [sieveRule appendFormat: @"%@K", value]; + else + [sieveRule appendFormat: @"%@ %@", field, value]; + + return sieveRule; +} + +- (NSString *) _extractSieveRule: (NSDictionary *) rule +{ + NSString *field, *operator, *value; + UIxFilterFieldType type; + BOOL isNot; + + return (([self _extractRuleField: &field fromRule: rule andType: &type] + && [self _extractRuleOperator: &operator fromRule: rule + isNot: &isNot] + && [self _validateRuleOperator: operator + withFieldType: type] + && [self _extractRuleValue: &value fromRule: rule + withFieldType: type]) + ? [self _composeSieveRuleOnField: field + withType: type + operator: operator + revert: isNot + andValue: value] + : nil); +} + +- (NSArray *) _extractSieveRules: (NSArray *) rules +{ + NSMutableArray *sieveRules; + NSString *sieveRule; + int count, max; + + max = [rules count]; + if (max) + { + sieveRules = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; !scriptError && count < max; count++) + { + sieveRule = [self _extractSieveRule: [rules objectAtIndex: count]]; + if (sieveRule) + [sieveRules addObject: sieveRule]; + } + } + else + sieveRules = nil; + + return sieveRules; +} + +- (NSString *) _extractSieveAction: (NSDictionary *) action +{ + NSString *sieveAction, *method, *requirement, *argument, + *flag, *mailbox; + SOGoDomainDefaults *dd; + + method = [action objectForKey: @"method"]; + if (method) + { + argument = [action objectForKey: @"argument"]; + if ([method isEqualToString: @"discard"] + || [method isEqualToString: @"keep"] + || [method isEqualToString: @"stop"]) + sieveAction = method; + else + { + if (argument) + { + if ([method isEqualToString: @"addflag"]) + { + flag = [sieveFlags objectForKey: argument]; + if (flag) + sieveAction = [NSString stringWithFormat: @"%@ %@", + method, [flag asSieveQuotedString]]; + else + scriptError + = [NSString stringWithFormat: + @"Action with invalid flag argument '%@'", + argument]; + } + else if ([method isEqualToString: @"fileinto"]) + { + dd = [user domainDefaults]; + mailbox + = [[argument componentsSeparatedByString: @"/"] + componentsJoinedByString: [dd imapFolderSeparator]]; + sieveAction = [NSString stringWithFormat: @"%@ %@", + method, [mailbox asSieveQuotedString]]; + } + else if ([method isEqualToString: @"redirect"]) + sieveAction = [NSString stringWithFormat: @"%@ %@", + method, [argument asSieveQuotedString]]; + else if ([method isEqualToString: @"reject"]) + sieveAction = [NSString stringWithFormat: @"%@ text: %@", + method, [argument asSieveQuotedString]]; + else + scriptError + = [NSString stringWithFormat: @"Action has unknown method '%@'", + method]; + } + else + scriptError = @"Action missing 'argument' parameter"; + } + if (method) + { + requirement = [methodRequirements objectForKey: method]; + if (requirement) + [requirements addObjectUniquely: requirement]; + } + } + else + { + scriptError = @"Action missing 'method' parameter"; + sieveAction = nil; + } + + return sieveAction; +} + +- (NSArray *) _extractSieveActions: (NSArray *) actions +{ + NSMutableArray *sieveActions; + NSString *sieveAction; + int count, max; + + max = [actions count]; + sieveActions = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; !scriptError && count < max; count++) + { + sieveAction = [self _extractSieveAction: [actions objectAtIndex: count]]; + if (!scriptError) + [sieveActions addObject: sieveAction]; + } + + return sieveActions; +} + +- (NSString *) _convertScriptToSieve: (NSDictionary *) newScript +{ + NSMutableString *sieveText; + NSString *match; + NSArray *sieveRules, *sieveActions; + + sieveText = [NSMutableString stringWithCapacity: 1024]; + match = [newScript objectForKey: @"match"]; + if ([match isEqualToString: @"allmessages"]) + match = nil; + if (match) + { + if ([match isEqualToString: @"all"] || [match isEqualToString: @"any"]) + { + sieveRules + = [self _extractSieveRules: [newScript objectForKey: @"rules"]]; + if (sieveRules) + [sieveText appendFormat: @"if %@of (%@) {\r\n", + match, + [sieveRules componentsJoinedByString: @", "]]; + else + scriptError = [NSString stringWithFormat: + @"Test '%@' used without any" + @" specified rule", + match]; + } + else + scriptError = [NSString stringWithFormat: @"Bad test: %@", + match]; + } + sieveActions = [self _extractSieveActions: + [newScript objectForKey: @"actions"]]; + if ([sieveActions count]) + [sieveText appendFormat: @" %@;\r\n", + [sieveActions componentsJoinedByString: @";\r\n "]]; + + if (match) + [sieveText appendFormat: @"}\r\n"]; + + return sieveText; +} + +- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements +{ + NSMutableString *sieveScript; + NSString *sieveText; + NSArray *scripts; + int count, max; + BOOL previousWasConditional; + NSDictionary *currentScript; + + sieveScript = [NSMutableString stringWithCapacity: 8192]; + + ASSIGN (requirements, newRequirements); + [scriptError release]; + scriptError = nil; + + scripts = [[user userDefaults] sieveFilters]; + max = [scripts count]; + if (max) + { + previousWasConditional = NO; + for (count = 0; !scriptError && count < max; count++) + { + currentScript = [scripts objectAtIndex: count]; + if ([[currentScript objectForKey: @"active"] boolValue]) + { + sieveText = [self _convertScriptToSieve: currentScript]; + if ([sieveText hasPrefix: @"if"]) + { + if (previousWasConditional) + [sieveScript appendFormat: @"els"]; + else + previousWasConditional = YES; + } + else + previousWasConditional = NO; + [sieveScript appendString: sieveText]; + } + } + } + + [scriptError retain]; + [requirements release]; + requirements = nil; + + if (scriptError) + sieveScript = nil; + + return sieveScript; +} + +- (NSString *) lastScriptError +{ + return scriptError; +} + +@end diff --git a/SoObjects/SOGo/NSDictionary+Utilities.h b/SoObjects/SOGo/NSDictionary+Utilities.h index 15d5940e4..c8e08ea7c 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.h +++ b/SoObjects/SOGo/NSDictionary+Utilities.h @@ -42,6 +42,8 @@ @interface NSMutableDictionary (SOGoDictionaryUtilities) +- (void) setObject: (id) object + forKeys: (NSArray *) keys; - (void) setObjects: (NSArray *) objects forKeys: (NSArray *) keys; diff --git a/SoObjects/SOGo/NSDictionary+Utilities.m b/SoObjects/SOGo/NSDictionary+Utilities.m index b81cfe0a7..25a066ec8 100644 --- a/SoObjects/SOGo/NSDictionary+Utilities.m +++ b/SoObjects/SOGo/NSDictionary+Utilities.m @@ -184,6 +184,17 @@ @implementation NSMutableDictionary (SOGoDictionaryUtilities) +- (void) setObject: (id) object + forKeys: (NSArray *) keys +{ + unsigned int count, max; + + max = [keys count]; + for (count = 0; count < max; count++) + [self setObject: object + forKey: [keys objectAtIndex: count]]; +} + - (void) setObjects: (NSArray *) objects forKeys: (NSArray *) keys { diff --git a/SoObjects/SOGo/SOGoDefaults.plist b/SoObjects/SOGo/SOGoDefaults.plist index b0d0b8c21..79a190501 100644 --- a/SoObjects/SOGo/SOGoDefaults.plist +++ b/SoObjects/SOGo/SOGoDefaults.plist @@ -4,6 +4,8 @@ WOLogFile = "/var/log/sogo/sogo.log"; WOPidFile = "/var/run/sogo/sogo.pid"; + NGImap4ConnectionStringSeparator = "."; + SOGoZipPath = "/usr/bin/zip"; WOUseRelativeURLs = YES; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.h b/SoObjects/SOGo/SOGoDomainDefaults.h index 6677e887c..f5281b18f 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.h +++ b/SoObjects/SOGo/SOGoDomainDefaults.h @@ -43,6 +43,7 @@ - (NSString *) imapFolderSeparator; - (BOOL) imapAclConformsToIMAPExt; - (BOOL) forceIMAPLoginWithEmail; +- (BOOL) sieveScriptsEnabled; - (BOOL) forwardEnabled; - (BOOL) vacationEnabled; - (NSString *) mailingMechanism; diff --git a/SoObjects/SOGo/SOGoDomainDefaults.m b/SoObjects/SOGo/SOGoDomainDefaults.m index 7878dcc20..7cde35a26 100644 --- a/SoObjects/SOGo/SOGoDomainDefaults.m +++ b/SoObjects/SOGo/SOGoDomainDefaults.m @@ -155,6 +155,11 @@ return [self boolForKey: @"SOGoForceIMAPLoginWithEmail"]; } +- (BOOL) sieveScriptsEnabled +{ + return [self boolForKey: @"SOGoSieveScriptsEnabled"]; +} + - (BOOL) forwardEnabled { return [self boolForKey: @"SOGoForwardEnabled"]; diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index f09b6fd80..a23901adf 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -140,6 +140,9 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (void) setRemindWithASound: (BOOL) newValue; - (BOOL) remindWithASound; +- (void) setSieveFilters: (NSArray *) newValue; +- (NSArray *) sieveFilters; + - (void) setVacationOptions: (NSDictionary *) newValue; - (NSDictionary *) vacationOptions; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 6fa8f8ec8..35a75c3d3 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -565,6 +565,16 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self boolForKey: @"SOGoRemindWithASound"]; } +- (void) setSieveFilters: (NSArray *) newValue +{ + [self setObject: newValue forKey: @"SOGoSieveFilters"]; +} + +- (NSArray *) sieveFilters +{ + return [self arrayForKey: @"SOGoSieveFilters"]; +} + - (void) setVacationOptions: (NSDictionary *) newValue { [self setObject: newValue forKey: @"Vacation"]; diff --git a/UI/PreferencesUI/GNUmakefile b/UI/PreferencesUI/GNUmakefile index 1c7000357..0ee32942d 100644 --- a/UI/PreferencesUI/GNUmakefile +++ b/UI/PreferencesUI/GNUmakefile @@ -13,6 +13,8 @@ PreferencesUI_OBJC_FILES = \ \ UIxJSONPreferences.m \ UIxPreferences.m \ + UIxFilterEditor.m \ + \ UIxAdditionalPreferences.m PreferencesUI_RESOURCE_FILES += \ diff --git a/UI/PreferencesUI/GNUmakefile.preamble b/UI/PreferencesUI/GNUmakefile.preamble new file mode 100644 index 000000000..e074677b0 --- /dev/null +++ b/UI/PreferencesUI/GNUmakefile.preamble @@ -0,0 +1,8 @@ +# compile settings + +SUBMINOR_VERSION:=1 + +ADDITIONAL_CPPFLAGS += \ + -DSOGO_MAJOR_VERSION=$(MAJOR_VERSION) \ + -DSOGO_MINOR_VERSION=$(MINOR_VERSION) \ + -DSOGO_SUBMINOR_VERSION=$(SUBMINOR_VERSION) diff --git a/UI/PreferencesUI/UIxFilterEditor.m b/UI/PreferencesUI/UIxFilterEditor.m new file mode 100644 index 000000000..5ee488b9a --- /dev/null +++ b/UI/PreferencesUI/UIxFilterEditor.m @@ -0,0 +1,104 @@ +/* UIxFilterEditor.m - this file is part of SOGo + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import + +#import +#import + +#import + +@interface UIxFilterEditor : UIxComponent +{ + NSString *filterId; +} + +@end + +@implementation UIxFilterEditor + +/* convert and save */ +- (BOOL) _validateFilterId +{ + NSCharacterSet *digits; + BOOL rc; + + digits = [NSCharacterSet decimalDigitCharacterSet]; + rc = ([filterId isEqualToString: @"new"] + || [[filterId stringByTrimmingCharactersInSet: digits] length] == 0); + + return rc; +} + +- (id ) defaultAction +{ + id result; + WORequest *request; + + request = [context request]; + ASSIGN (filterId, [request formValueForKey: @"filter"]); + if (filterId) + { + if ([self _validateFilterId]) + result = self; + else + result = [self responseWithStatus: 403 + andString: @"Bad value for 'filter'"]; + } + else + result = [self responseWithStatus: 403 + andString: @"Missing 'filter' parameter"]; + + return result; +} + +- (NSString *) filterId +{ + return filterId; +} + +- (NSString *) firstMailAccount +{ + NSArray *accounts; + NSDictionary *account; + NSString *login, *accountName; + SOGoUser *ownerUser; + + login = [[self clientObject] nameInContainer]; + ownerUser = [SOGoUser userWithLogin: login]; + + accounts = [ownerUser mailAccounts]; + if ([accounts count] > 0) + { + account = [accounts objectAtIndex: 0]; + accountName = [[account objectForKey: @"name"] asCSSIdentifier]; + } + else + accountName = @""; + + return accountName; +} + +@end diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index 326f02bb9..dd8b2549f 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -42,6 +42,7 @@ SOGoUserDefaults *userDefaults; NSCalendarDate *today; NSArray *daysOfWeek, *daysBetweenResponsesList; + NSArray *sieveFilters; NSMutableDictionary *vacationOptions, *forwardOptions; BOOL hasChanged, composeMessageTypeHasChanged; } diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index db129102e..7beabbc9a 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -21,10 +21,11 @@ */ #import +#import +#import #import #import #import /* for locale strings */ -#import #import #import @@ -35,6 +36,7 @@ #import #import #import +#import #import #import #import @@ -83,6 +85,13 @@ composeMessageTypeHasChanged = NO; dd = [user domainDefaults]; + if ([dd sieveScriptsEnabled]) + { + sieveFilters = [[userDefaults sieveFilters] copy]; + if (!sieveFilters) + sieveFilters = [NSArray new]; + } + if ([dd vacationEnabled]) { vacationOptions = [[userDefaults vacationOptions] mutableCopy]; @@ -107,6 +116,7 @@ [item release]; [user release]; [userDefaults release]; + [sieveFilters release]; [vacationOptions release]; [calendarCategories release]; [calendarCategoriesColors release]; @@ -661,11 +671,54 @@ /* mail autoreply (vacation) */ +- (BOOL) isSieveScriptsEnabled +{ + return [[user domainDefaults] sieveScriptsEnabled]; +} + +- (NSString *) sieveCapabilities +{ +#warning this should be deduced from the server + static NSArray *capabilities = nil; + + if (!capabilities) + { + capabilities = [NSArray arrayWithObjects: @"fileinto", @"reject", + @"envelope", @"vacation", @"imapflags", + @"notify", @"subaddress", @"relational", + @"comparator-i;ascii-numeric", @"regex", nil]; + [capabilities retain]; + } + + return [[NSDictionary dictionary] + jsonStringForArray: capabilities + withIndentLevel: 0]; +} + - (BOOL) isVacationEnabled { return [[user domainDefaults] vacationEnabled]; } +- (void) setSieveFiltersValue: (NSString *) newValue +{ + NSScanner *jsonScanner; + + if ([newValue hasPrefix: @"["]) + { + jsonScanner = [NSScanner scannerWithString: newValue]; + [jsonScanner scanJSONArray: &sieveFilters]; + [sieveFilters retain]; + } +} + +- (NSString *) sieveFiltersValue +{ + return [[NSDictionary dictionary] + jsonStringForArray: sieveFilters + withIndentLevel: 0]; +} + - (void) setEnableVacation: (BOOL) enableVacation { [vacationOptions setObject: [NSNumber numberWithBool: enableVacation] @@ -884,6 +937,8 @@ id folder; dd = [[context activeUser] domainDefaults]; + if ([dd sieveScriptsEnabled]) + [userDefaults setSieveFilters: sieveFilters]; if ([dd vacationEnabled]) [userDefaults setVacationOptions: vacationOptions]; if ([dd forwardEnabled]) @@ -897,7 +952,6 @@ account = [folder lookupName: [[mailAccounts objectForKey: @"name"] asCSSIdentifier] inContext: context acquire: NO]; - [account updateFilters]; if (composeMessageTypeHasChanged) @@ -947,16 +1001,6 @@ userCanChangePassword]; } -- (NSString *) nameLabel -{ - return [self labelForKey: @"Name"]; -} - -- (NSString *) colorLabel -{ - return [self labelForKey: @"Color"]; -} - - (NSArray *) languageCategories { NSArray *categoryLabels; diff --git a/UI/PreferencesUI/product.plist b/UI/PreferencesUI/product.plist index ccb4bc782..1866d2905 100644 --- a/UI/PreferencesUI/product.plist +++ b/UI/PreferencesUI/product.plist @@ -19,6 +19,10 @@ protectedBy = "View"; pageName = "UIxPreferences"; }; + editFilter = { + protectedBy = "View"; + pageName = "UIxFilterEditor"; + }; identities = { protectedBy = "View"; pageName = "UIxIdentities"; diff --git a/UI/Templates/PreferencesUI/UIxFilterEditor.wox b/UI/Templates/PreferencesUI/UIxFilterEditor.wox new file mode 100644 index 000000000..87cfeec15 --- /dev/null +++ b/UI/Templates/PreferencesUI/UIxFilterEditor.wox @@ -0,0 +1,64 @@ + + + + +
+
+
+
+
+ + +
+ + + +
+
+
+ +
+
+
diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index 291419557..d621b4a69 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -101,11 +101,11 @@ > @@ -173,15 +173,45 @@ const:id="composeMessagesType" string="itemComposeMessagesText" selection="userComposeMessagesType"/> +
+ +
+ + + +
+ + + -
- -