Skip to content

Commit

Permalink
feat(mail): enable autoreply on specific days or at a specific time
Browse files Browse the repository at this point in the history
Fixes #5328
  • Loading branch information
cgx committed Dec 2, 2021
1 parent 6807d1d commit 2ecd441
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 113 deletions.
118 changes: 92 additions & 26 deletions SoObjects/SOGo/SOGoSieveManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSTimeZone.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>

Expand Down Expand Up @@ -946,14 +947,14 @@ - (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount
dateCapability || [[values objectForKey: @"endDate"] intValue] > now))
{
NSCalendarDate *startDate, *endDate;
NSMutableArray *allConditions;
NSMutableArray *allConditions, *timeConditions;
NSMutableString *vacation_script;
NSArray *addresses;
NSString *text, *templateFilePath, *customSubject;
NSArray *addresses, *weekdays;
NSString *text, *templateFilePath, *customSubject, *weekday, *startTime, *endTime, *timeCondition, *timeZone;
SOGoTextTemplateFile *templateFile;

BOOL ignore, alwaysSend, useCustomSubject, discardMails;
int days, i;
int days, i, seconds;

allConditions = [NSMutableArray array];
days = [[values objectForKey: @"daysBetweenResponse"] intValue];
Expand Down Expand Up @@ -1009,30 +1010,95 @@ - (NSException *) updateFiltersForAccount: (SOGoMailAccount *) theAccount
[allConditions addObject: @"not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\""];
}

// Start date of auto-reply
if ([dd vacationPeriodEnabled] &&
[[values objectForKey: @"startDateEnabled"] boolValue] &&
dateCapability)
if ([dd vacationPeriodEnabled] && 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"]]];
}
// Start date of auto-reply
if ([[values objectForKey: @"startDateEnabled"] boolValue])
{
[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 ([dd vacationPeriodEnabled] &&
[[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"]]];
// End date of auto-reply
if ([[values objectForKey: @"endDateEnabled"] boolValue])
{
[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"]]];
}

seconds = [[ud timeZone] secondsFromGMT];
timeZone = [NSString stringWithFormat: @"%.2i%02i", seconds/60/60, seconds/60%60];
timeConditions = [NSMutableArray array];
startTime = endTime = nil;

// Start auto-reply from a specific time
if ([[values objectForKey: @"startTimeEnabled"] boolValue])
{
startTime = [values objectForKey: @"startTime"];
timeCondition = [NSString stringWithFormat: @"date :value \"ge\" :zone \"%@\" \"date\" \"time\" \"%@:00\"",
timeZone, startTime];
[timeConditions addObject: timeCondition];
}

// Stop auto-reply at a specific time
if ([[values objectForKey: @"endTimeEnabled"] boolValue])
{
endTime = [values objectForKey: @"endTime"];
timeCondition = [NSString stringWithFormat: @"date :value \"lt\" :zone \"%@\" \"date\" \"time\" \"%@:00\"",
timeZone, endTime];
[timeConditions addObject: timeCondition];
}

if (startTime && endTime)
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
if ([startTime compare: endTime] == NSOrderedAscending)
{
// Start time is before end time:
// >= startTime && < endTime
[allConditions addObjectsFromArray: timeConditions];
}
else
{
// End time is before start time:
// >= startTime || < endTime
timeCondition = [NSString stringWithFormat: @"anyof ( %@ )", [timeConditions componentsJoinedByString: @", "]];
[allConditions addObject: timeCondition];
}
}
else if ([timeConditions count])
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
[allConditions addObjectsFromArray: timeConditions];
}

// Auto-reply during specific days
if ([[values objectForKey: @"weekdaysEnabled"] boolValue])
{
[req addObjectUniquely: @"date"];
[req addObjectUniquely: @"relational"];
weekdays = [values objectForKey: @"days"];
NSMutableString *currentWeekday = [NSMutableString stringWithString: @"currentdate :is \"weekday\" ["];
for (i = 0; i < [weekdays count]; i++)
{
if (i > 0)
[currentWeekday appendString: @", "];
weekday = [weekdays objectAtIndex: i];
[currentWeekday appendString: [weekday asSieveQuotedString]];
}
[currentWeekday appendString: @"]"];
[allConditions addObject: currentWeekday];
}
}

// Apply conditions
Expand Down
12 changes: 10 additions & 2 deletions SoObjects/SOGo/SOGoUserFolder.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
Copyright (C) 2007-2016 Inverse inc.
Copyright (C) 2007-2021 Inverse inc.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Expand All @@ -18,6 +17,7 @@
02111-1307, USA.
*/

#import <Foundation/NSTimeZone.h>

#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoSecurityManager.h>
Expand Down Expand Up @@ -674,6 +674,14 @@ - (NSString *) davResourceId
return [NSString stringWithFormat: @"urn:uuid:%@", nameInContainer];
}

- (NSString *) davCalendarTimeZone
{
NSTimeZone *tz = [[[context activeUser] userDefaults] timeZone];
int secs = [tz secondsFromGMT];

return [NSString stringWithFormat: @"%02i%02i", secs / 60 / 60, abs(secs % 60)];
}

// - (NSException *) setDavSignature: (NSString *) newSignature
// {
// SOGoUserDefaults *ud;
Expand Down
4 changes: 3 additions & 1 deletion Tests/lib/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class TestUtility {
url: `${this.webdav.serverUrl}/SOGo/dav/${login}/`,
props: [
{ name: 'displayname', namespace: DAVNamespace.DAV },
{ name: 'calendar-timezone', namespace: DAVNamespace.CALDAV },
{ name: 'calendar-user-address-set', namespace: DAVNamespace.CALDAV }
],
depth: '0',
Expand All @@ -33,7 +34,8 @@ class TestUtility {

let displayname = response.props.displayname || ''
let email = response.props.calendarUserAddressSet.href[0]
this.userInfo[login] = { displayname: displayname, email: email }
let timezone = response.props.calendarTimezone || ''
this.userInfo[login] = { displayname, email, timezone }
}
return this.userInfo[login]
}
Expand Down
42 changes: 41 additions & 1 deletion Tests/spec/SieveSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,23 @@ describe('Sieve', function() {
// kill existing filters
await prefs.setOrCreate('SOGoSieveFilters', [], ['defaults'])
// vacation filters
await prefs.setOrCreate('enabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('autoReplyText', '', ['defaults', 'Vacation'])
await prefs.setOrCreate('customSubjectEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('customSubject', '', ['defaults', 'Vacation'])
await prefs.setOrCreate('autoReplyEmailAddresses', [], ['defaults', 'Vacation'])
await prefs.setOrCreate('daysBetweenResponse', 7, ['defaults', 'Vacation'])
await prefs.setOrCreate('ignoreLists', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('startDateEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('startDate', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('startTimeEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('startTime', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('endDateEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('endDate', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('enabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('endTimeEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('endTime', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('weekdaysEnabled', 0, ['defaults', 'Vacation'])
await prefs.setOrCreate('days', [], ['defaults', 'Vacation'])
// forwarding filters
await prefs.setOrCreate('forwardAddress', [], ['defaults', 'Forward'])
await prefs.setOrCreate('keepCopy', 0, ['defaults', 'Forward'])
Expand Down Expand Up @@ -100,6 +108,38 @@ describe('Sieve', function() {
.toBe(sieveVacationIgnoreLists)
})

it('enable vacation script - activation constraints', async function() {
const vacationMsg = 'vacation test - activation constraints'
const daysInterval = 2
const mailaddr = user.email.replace(/mailto:/, '')
const now = new Date()
const tomorrow = new Date(now.getTime() + 1000*60*60*24)
const startDate = tomorrow.getFullYear() + '-' + [
'0' + (tomorrow.getMonth() + 1),
'0' + tomorrow.getDate()
].map(component => component.slice(-2)).join('-')
const startTime = '17:00'
const timezone = (user.timezone < 0 ? '-':'') + ('000' + Math.abs(user.timezone)).slice(-4)
const sieveVacationConstraints = `require ["vacation","date","relational"];\r\nif allof ( currentdate :value "ge" "date" "${startDate}", date :value "ge" :zone "${timezone}" "date" "time" "${startTime}:00" ) { vacation :days ${daysInterval} :addresses ["${mailaddr}"] text:\r\n${vacationMsg}\r\n.\r\n;\r\n}\r\n`
let vacation

vacation = await prefs.get('Vacation')
vacation.enabled = 1
await prefs.setNoSave('autoReplyText', vacationMsg)
await prefs.setNoSave('daysBetweenResponse', daysInterval)
await prefs.setNoSave('autoReplyEmailAddresses', [mailaddr])
await prefs.setNoSave('startDateEnabled', 1)
await prefs.setNoSave('startDate', tomorrow.getTime() / 1000)
await prefs.setNoSave('startTimeEnabled', 1)
await prefs.setNoSave('startTime', startTime)
await prefs.save()

const createdScript = await _getSogoSieveScript()
expect(createdScript)
.withContext(`sogo Sieve script`)
.toBe(sieveVacationConstraints)
})

it('enable simple forwarding', async function() {
const redirectMailaddr = 'nonexistent@inverse.com'
const sieveSimpleForward = `redirect "${redirectMailaddr}";\r\n`
Expand Down
Loading

0 comments on commit 2ecd441

Please sign in to comment.