diff --git a/data/assets/templates/quarantine.tpl b/data/assets/templates/quarantine.tpl index 8fa88c54b..d6ff0c58c 100644 --- a/data/assets/templates/quarantine.tpl +++ b/data/assets/templates/quarantine.tpl @@ -1,69 +1,175 @@ - - - + + + + + + + + + + Quarantine notification + - - -

Hi {{username}}!
- {% if counter == 1 %} - There is 1 new message waiting in quarantine:
- {% else %} - There are {{counter}} new messages waiting in quarantine:
- {% endif %} - - {% if quarantine_acl == 1 %}{% endif %} - {% for line in meta|reverse %} + + +
+ {% if counter == 1 %}1 new message is waiting in your quarantine.{% else %}{{ counter }} new messages are waiting in your quarantine.{% endif %} +  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ +
+ +
SubjectSenderScoreActionArrived onActions
- - - - {% if line.action == "reject" %} - - {% else %} - - {% endif %} - - {% if quarantine_acl == 1 %} - {% if line.action == "reject" %} - - {% else %} - - {% endif %} - {% endif %} + - {% endfor %} - -

- + + diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index a9f17705d..6354b053e 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1,4 +1,29 @@ compile-time default ($MAILBOX_DEFAULT_ATTRIBUTES in vars.inc.php). +// $key is either 'notification' or 'category'. +function get_quarantine_default($key) { + global $redis, $MAILBOX_DEFAULT_ATTRIBUTES; + $redis_keys = array( + 'notification' => 'Q_DEF_NOTIFICATION', + 'category' => 'Q_DEF_CATEGORY' + ); + $attr_keys = array( + 'notification' => 'quarantine_notification', + 'category' => 'quarantine_category' + ); + if (!isset($redis_keys[$key])) { + return null; + } + try { + $value = $redis->Get($redis_keys[$key]); + } + catch (RedisException $e) { + $value = false; + } + return ($value !== false && $value !== '') ? $value : $MAILBOX_DEFAULT_ATTRIBUTES[$attr_keys[$key]]; +} function mailbox($_action, $_type, $_data = null, $_extra = null) { global $pdo; global $redis; @@ -1109,8 +1134,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']); $dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']); $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; - $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); - $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval(get_quarantine_default('notification')); + $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval(get_quarantine_default('category')); // Validate quarantine_category if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { $_SESSION['return'][] = array( @@ -1740,8 +1765,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); $attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']); - $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); - $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval(get_quarantine_default('notification')); + $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval(get_quarantine_default('category')); // Validate quarantine_category if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) { $_SESSION['return'][] = array( diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index 5df86ef33..acbd493dc 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -6,6 +6,7 @@ function quarantine($_action, $_data = null) { global $pdo; global $redis; global $lang; + global $MAILBOX_DEFAULT_ATTRIBUTES; $_data_log = $_data; switch ($_action) { case 'quick_delete': @@ -22,7 +23,7 @@ function quarantine($_action, $_data = null) { return false; } $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` - WHERE `qhash` = :hash + WHERE `qhash` = :hash AND user_acl.quarantine = 1 AND rcpt IN (SELECT username FROM mailbox)'); $stmt->execute(array(':hash' => $hash)); @@ -65,7 +66,7 @@ function quarantine($_action, $_data = null) { return false; } $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt` - WHERE `qhash` = :hash + WHERE `qhash` = :hash AND `user_acl`.`quarantine` = 1 AND `username` IN (SELECT `username` FROM `mailbox`)'); $stmt->execute(array(':hash' => $hash)); @@ -331,6 +332,9 @@ function quarantine($_action, $_data = null) { $max_age = 365; } $exclude_domains = (array)$_data['exclude_domains']; + // Server-wide default quarantine notification settings for new mailboxes + $def_notification = (in_array($_data['def_notification'], array('never', 'hourly', 'daily', 'weekly'))) ? $_data['def_notification'] : 'never'; + $def_category = (in_array($_data['def_category'], array('add_header', 'reject', 'all'))) ? $_data['def_category'] : 'reject'; try { $redis->Set('Q_RETENTION_SIZE', intval($retention_size)); $redis->Set('Q_MAX_SIZE', intval($max_size)); @@ -343,6 +347,8 @@ function quarantine($_action, $_data = null) { $redis->Set('Q_REDIRECT', $redirect); $redis->Set('Q_SUBJ', $subject); $redis->Set('Q_HTML', $html); + $redis->Set('Q_DEF_NOTIFICATION', $def_notification); + $redis->Set('Q_DEF_CATEGORY', $def_category); } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -358,6 +364,55 @@ function quarantine($_action, $_data = null) { 'msg' => 'saved_settings' ); } + // Force the server-wide default onto all existing mailboxes (overwrites user overrides) + elseif ($_data['action'] == 'apply_existing') { + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } + // Resolve the default to apply: prefer the values submitted with the form + // (the settings form is merged into this request), fall back to the stored default. + $def_notification = (in_array($_data['def_notification'], array('never', 'hourly', 'daily', 'weekly'))) ? $_data['def_notification'] : get_quarantine_default('notification'); + $def_category = (in_array($_data['def_category'], array('add_header', 'reject', 'all'))) ? $_data['def_category'] : get_quarantine_default('category'); + try { + // Keep the stored default in sync with what we are forcing onto mailboxes + $redis->Set('Q_DEF_NOTIFICATION', $def_notification); + $redis->Set('Q_DEF_CATEGORY', $def_category); + $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, + '$.quarantine_notification', :quarantine_notification, + '$.quarantine_category', :quarantine_category)"); + $stmt->execute(array( + ':quarantine_notification' => $def_notification, + ':quarantine_category' => $def_category + )); + $affected = $stmt->rowCount(); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('redis_error', $e) + ); + return false; + } + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('mysql_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => array('quarantine_defaults_applied', $affected) + ); + } // Release item elseif ($_data['action'] == 'release' || $_data['action'] == 'learnham') { if (!is_array($_data['id'])) { @@ -791,6 +846,8 @@ function quarantine($_action, $_data = null) { if (empty($settings['html_tmpl'])) { $settings['html_tmpl'] = htmlspecialchars(file_get_contents("/tpls/quarantine.tpl")); } + $settings['def_notification'] = $redis->Get('Q_DEF_NOTIFICATION') ?: $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']; + $settings['def_category'] = $redis->Get('Q_DEF_CATEGORY') ?: $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']; } catch (RedisException $e) { $_SESSION['return'][] = array( @@ -833,7 +890,7 @@ function quarantine($_action, $_data = null) { ))); return false; } - $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash'); + $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash'); $stmt->execute(array(':hash' => $hash)); return $stmt->fetch(PDO::FETCH_ASSOC); break; diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 873d7b98d..e440b9838 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -315,6 +315,11 @@ "private_key": "Private key", "quarantine": "Quarantine", "quarantine_bcc": "Send a copy of all notifications (BCC) to this recipient:
Leave empty to disable. Unsigned, unchecked mail. Should be delivered internally only.", + "quarantine_defaults_apply": "Apply default to all existing mailboxes", + "quarantine_defaults_apply_confirm": "This will overwrite the quarantine notification interval and category of ALL existing mailboxes with the default selected above. User overrides will be lost. Continue?", + "quarantine_defaults_apply_info": "Applies the default to all existing mailboxes and overwrites individual user settings. New mailboxes always inherit the default above.", + "quarantine_defaults_category": "Default notification category for new mailboxes", + "quarantine_defaults_notification": "Default notification interval for new mailboxes", "quarantine_exclude_domains": "Exclude domains and alias-domains", "quarantine_max_age": "Maximum age in days
Value must be equal to or greater than 1 day.", "quarantine_max_score": "Discard notification if spam score of a mail is higher than this value:
Defaults to 9999.0", @@ -1190,6 +1195,7 @@ "pushover_settings_edited": "Pushover settings successfully set, please verify credentials.", "qlearn_spam": "Message ID %s was learned as spam and deleted", "queue_command_success": "Queue command completed successfully", + "quarantine_defaults_applied": "Default quarantine notification settings applied to %s existing mailbox(es)", "recipient_map_entry_deleted": "Recipient map ID %s has been deleted", "recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved", "recovery_email_sent": "Recovery email sent to %s", diff --git a/data/web/templates/admin/tab-config-quarantine.twig b/data/web/templates/admin/tab-config-quarantine.twig index be2d59a51..ce17d9aa8 100644 --- a/data/web/templates/admin/tab-config-quarantine.twig +++ b/data/web/templates/admin/tab-config-quarantine.twig @@ -62,6 +62,34 @@
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +

{{ lang.admin.quarantine_defaults_apply_info|raw }}

+
+
+