Skip to content

Commit

Permalink
add rewrite-subject flag to email-edit form; hide spam-related settin…
Browse files Browse the repository at this point in the history
…gs if 'bypass_spam' is activated; add possibility to disable rejection of spam-mails, refs #1282

Signed-off-by: Michael Kaufmann <[email protected]>
  • Loading branch information
d00p committed Sep 28, 2024
1 parent dda4c7a commit 4ce7396
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 23 deletions.
3 changes: 2 additions & 1 deletion install/froxlor.sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
`iscatchall` tinyint(1) unsigned NOT NULL default '0',
`description` varchar(255) NOT NULL DEFAULT '',
`spam_tag_level` float(4,1) NOT NULL DEFAULT 7.0,
`rewrite_subject` tinyint(1) NOT NULL default '1',
`spam_kill_level` float(4,1) NOT NULL DEFAULT 14.0,
`bypass_spam` tinyint(1) NOT NULL default '0',
`policy_greylist` tinyint(1) NOT NULL default '1',
Expand Down Expand Up @@ -730,7 +731,7 @@
('panel', 'settings_mode', '0'),
('panel', 'menu_collapsed', '1'),
('panel', 'version', '2.2.1'),
('panel', 'db_version', '202408140');
('panel', 'db_version', '202409280');
DROP TABLE IF EXISTS `panel_tasks`;
Expand Down
9 changes: 9 additions & 0 deletions install/updates/froxlor/update_2.2.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,12 @@
Update::showUpdateStep("Updating from 2.2.0 to 2.2.1", false);
Froxlor::updateToVersion('2.2.1');
}

if (Froxlor::isDatabaseVersion('202408140')) {

Update::showUpdateStep("Adding new rewrite-subject field to email table");
Database::query("ALTER TABLE `" . TABLE_MAIL_VIRTUAL . "` ADD `rewrite_subject` tinyint(1) NOT NULL default '1' AFTER `spam_tag_level`;");
Update::lastStepStatus(0);

Froxlor::updateToDbVersion('202409280');
}
24 changes: 19 additions & 5 deletions lib/Froxlor/Api/Commands/Emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class Emails extends ApiCommand implements ResourceEntity
* domain-name for the email-address
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
Expand Down Expand Up @@ -85,7 +87,8 @@ public function add()

// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, '7.0');
$spam_kill_level = $this->getParam('spam_kill_level', true, '14.0');
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, 1);
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', true, '14.0');
$bypass_spam = $this->getBoolParam('bypass_spam', true, 0);
$policy_greylist = $this->getBoolParam('policy_greylist', true, 1);
$iscatchall = $this->getBoolParam('iscatchall', true, 0);
Expand Down Expand Up @@ -155,8 +158,10 @@ public function add()
}
}

$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1})?$/', '', [7.0], true);
if ($spam_kill_level > -1) {
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1})?$/', '', [14.0], true);
}
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);

$stmt = Database::prepare("
Expand All @@ -165,6 +170,7 @@ public function add()
`email` = :email,
`email_full` = :email_full,
`spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
Expand All @@ -177,6 +183,7 @@ public function add()
"email" => $email,
"email_full" => $email_full,
"spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
Expand Down Expand Up @@ -250,6 +257,8 @@ public function get()
* optional, required when called as admin (if $customerid is not specified)
* @param float $spam_tag_level
* optional, score which is required to tag emails as spam, default: 7.0
* @param bool $rewrite_subject
* optional, whether to add ***SPAM*** to the email's subject if applicable, default true
* @param float $spam_kill_level
* optional, score which is required to discard emails, default: 14.0
* @param boolean $bypass_spam
Expand Down Expand Up @@ -283,7 +292,8 @@ public function update()

// parameters
$spam_tag_level = $this->getParam('spam_tag_level', true, $result['spam_tag_level']);
$spam_kill_level = $this->getParam('spam_kill_level', true, $result['spam_kill_level']);
$rewrite_subject = $this->getBoolParam('rewrite_subject', true, $result['rewrite_subject']);
$spam_kill_level = $this->getUlParam('spam_kill_level', 'spam_kill_level_ul', true, $result['spam_kill_level']);
$bypass_spam = $this->getBoolParam('bypass_spam', true, $result['bypass_spam']);
$policy_greylist = $this->getBoolParam('policy_greylist', true, $result['policy_greylist']);
$iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']);
Expand Down Expand Up @@ -327,13 +337,16 @@ public function update()
}

$spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true);
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
if ($spam_kill_level > -1) {
$spam_kill_level = Validate::validate($spam_kill_level, 'spam_kill_level', '/^\d{1,}(\.\d{1,2})?$/', '', [14.0], true);
}
$description = Validate::validate(trim($description), 'description', Validate::REGEX_DESC_TEXT, '', [], true);

$stmt = Database::prepare("
UPDATE `" . TABLE_MAIL_VIRTUAL . "` SET
`email` = :email ,
`spam_tag_level` = :spam_tag_level,
`rewrite_subject` = :rewrite_subject,
`spam_kill_level` = :spam_kill_level,
`bypass_spam` = :bypass_spam,
`policy_greylist` = :policy_greylist,
Expand All @@ -344,6 +357,7 @@ public function update()
$params = [
"email" => $email,
"spam_tag_level" => $spam_tag_level,
"rewrite_subject" => $rewrite_subject,
"spam_kill_level" => $spam_kill_level,
"bypass_spam" => $bypass_spam,
"policy_greylist" => $policy_greylist,
Expand Down
6 changes: 4 additions & 2 deletions lib/Froxlor/Cron/Mail/Rspamd.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private function generateEmailAddrConfig(array $email): void
$this->logger->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'Generating antispam config for ' . $email['email']);

$email['spam_tag_level'] = floatval($email['spam_tag_level']);
$email['spam_kill_level'] = floatval($email['spam_kill_level']);
$email['spam_kill_level'] = $email['spam_kill_level'] == -1 ? "null" : floatval($email['spam_kill_level']);
$email_id = md5($email['email']);

$this->frx_settings_file .= '# Email: ' . $email['email'] . "\n";
Expand All @@ -185,7 +185,9 @@ private function generateEmailAddrConfig(array $email): void
$this->frx_settings_file .= ' apply {' . "\n";
$this->frx_settings_file .= ' actions {' . "\n";
$this->frx_settings_file .= ' "add header" = ' . $email['spam_tag_level'] . ';' . "\n";
$this->frx_settings_file .= ' rewrite_subject = ' . ($email['spam_tag_level'] + 0.01) . ';' . "\n";
if ((int)$email['rewrite_subject'] == 1) {
$this->frx_settings_file .= ' rewrite_subject = ' . ($email['spam_tag_level'] + 0.01) . ';' . "\n";
}
$this->frx_settings_file .= ' reject = ' . $email['spam_kill_level'] . ';' . "\n";
if ($type == 'rcpt' && (int)$email['policy_greylist'] == 0) {
$this->frx_settings_file .= ' greylist = null;' . "\n";
Expand Down
2 changes: 1 addition & 1 deletion lib/Froxlor/Froxlor.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class Froxlor
const VERSION = '2.2.1';

// Database version (YYYYMMDDC where C is a daily counter)
const DBVERSION = '202408140';
const DBVERSION = '202409280';

// Distribution branding-tag (used for Debian etc.)
const BRANDING = '';
Expand Down
33 changes: 21 additions & 12 deletions lib/formfields/customer/email/formfield.emails_edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,27 +96,36 @@
'value' => '1',
'checked' => (int)$result['iscatchall'],
],
'bypass_spam' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.bypass_spam'),
'type' => 'checkbox',
'value' => '1',
'checked' => (int)$result['bypass_spam'],
],
'spam_tag_level' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.spam_tag_level'),
'type' => 'text',
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
'value' => $result['spam_tag_level']
'type' => 'number',
'min' => 0,
'step' => 0.1,
'value' => $result['spam_tag_level'],
],
'spam_rewrite_subject' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.rewrite_subject'),
'type' => 'checkbox',
'value' => '1',
'checked' => (int)$result['rewrite_subject'],
],
'spam_kill_level' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.spam_kill_level'),
'type' => 'text',
'string_regexp' => '/^\d{1,}(\.\d{1,2})?$/',
'desc' => lng('panel.use_checkbox_to_disable'),
'type' => 'textul',
'step' => 0.1,
'value' => $result['spam_kill_level']
],
'bypass_spam' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.bypass_spam'),
'type' => 'checkbox',
'value' => '1',
'checked' => (int)$result['bypass_spam'],
],
'policy_greylist' => [
'visible' => Settings::Get('antispam.activated') == '1',
'label' => lng('antispam.policy_greylist'),
Expand Down
5 changes: 5 additions & 0 deletions lng/de.lng.php
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,10 @@
'title' => 'Spam Level',
'description' => 'Erforderliche Punktzahl zum Markieren einer E-Mail als Spam<br/>Standard: 7.0'
],
'rewrite_subject' => [
'title' => 'Betreff ändern',
'description' => 'Dem E-Mail Betreff <strong>***SPAM***</strong> hinzufügen, sofern zutreffend',
],
'spam_kill_level' => [
'title' => 'Ablehnungs Level',
'description' => 'Erforderliche Punktzahl für das Ablehnen einer E-Mail<br/>Standard: 14.0'
Expand Down Expand Up @@ -1262,6 +1266,7 @@
'upload_import' => 'Hochladen und importieren',
'profile' => 'Mein Profil',
'use_checkbox_for_unlimited' => 'Der Wert "0" deaktiviert die Resource. Die Checkbox rechts erlaubt "unlimitierte" Nutzung.',
'use_checkbox_to_disable' => 'Zum Deaktivieren, klicke die Checkbox auf der rechten Seite des Eingabefeldes',
],
'phpfpm' => [
'vhost_httpuser' => 'Lokaler Benutzer für PHP-FPM (Froxlor-Vhost)',
Expand Down
5 changes: 5 additions & 0 deletions lng/en.lng.php
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,10 @@
'title' => 'Spam tag level',
'description' => 'Score that is required to mark an email as spam<br/>Default: 7.0'
],
'rewrite_subject' => [
'title' => 'Rewrite subject',
'description' => 'Whether to add <strong>***SPAM***</strong> to the email subject if applicable',
],
'spam_kill_level' => [
'title' => 'Spam kill level',
'description' => 'Score that is required to discard an email entirely<br/>Default: 14.0'
Expand Down Expand Up @@ -1377,6 +1381,7 @@
'upload_import' => 'Upload and import',
'profile' => 'My profile',
'use_checkbox_for_unlimited' => 'The value "0" deactivates this resource. The checkbox on the right allows "unlimited" usage.',
'use_checkbox_to_disable' => 'To disable, activate the checkbox on the right of the input field',
],
'phpfpm' => [
'vhost_httpuser' => 'Local user to use for PHP-FPM (Froxlor vHost)',
Expand Down
33 changes: 33 additions & 0 deletions templates/Froxlor/assets/js/jquery/emails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export default function () {
$(function () {

/**
* bypass spam - hide unnecessary/unused sections
*/
if ($('#id') && $('#bypass_spam').is(':checked')) {
$('#spam_tag_level').closest('.row').addClass('d-none');
$('#spam_rewrite_subject').closest('.row').addClass('d-none');
$('#spam_kill_level').closest('.row').addClass('d-none');
$('#policy_greylist').closest('.row').addClass('d-none');
}

/**
* toggle show/hide of sections in case of bypass spam flag
*/
$('#bypass_spam').on('click', function () {
if ($(this).is(':checked')) {
// hide unnecessary sections
$('#spam_tag_level').closest('.row').addClass('d-none');
$('#spam_rewrite_subject').closest('.row').addClass('d-none');
$('#spam_kill_level').closest('.row').addClass('d-none');
$('#policy_greylist').closest('.row').addClass('d-none');
} else {
// show sections
$('#spam_tag_level').closest('.row').removeClass('d-none');
$('#spam_rewrite_subject').closest('.row').removeClass('d-none');
$('#spam_kill_level').closest('.row').removeClass('d-none');
$('#policy_greylist').closest('.row').removeClass('d-none');
}
})
});
}
6 changes: 4 additions & 2 deletions templates/Froxlor/form/formfields.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
{% endif %}
{% if field.label.description is defined and field.label.description is not empty %}<br><small>{{ field.label.description|raw }}</small>
{% endif %}
{% if field.desc is defined and field.desc is not empty %}<br><small>{{ field.desc|raw }}</small>
{% endif %}
{% if field.requires_reconf is defined and field.requires_reconf is not empty %}
<div class="bg-info bg-opacity-25 rounded p-2 mt-2 d-flex align-items-center" role="alert">
<i class="fa-solid fa-circle-exclamation me-2"></i><p class="mb-0">{{ lng('serversettings.requires_reconfiguration', [field.requires_reconf|join(', ')])|raw }}</p>
Expand Down Expand Up @@ -170,7 +172,7 @@
{% if field.next_to is defined %}
<div class="input-group">
{% endif %}
<input type="{{ field.type }}" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
<input type="{{ field.type }}" {% if (field.visible is defined and field.visible == false) or (field.disabled is defined and field.disabled == true) %} disabled {% endif %} {% if field.type == 'number' and field.min is defined %} min="{{ field.min }}" {% endif %} {% if field.type == 'number' and field.max is defined %} max="{{ field.max }}" {% endif %} {% if field.type == 'number' and field.step is not empty %} step="{{ field.step }}" {% endif %} {% if field.type != 'number' and field.maxlength is defined %} maxlength="{{ field.maxlength }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{{ field.value|raw }}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %} {% if field.type == 'file' and field.accept is defined %} accept="{{ field.accept }}" {% endif %} {% if field.pattern is defined %} pattern="{{ field.pattern }}" {% endif %}/>
{% if field.type == 'hidden' and field.display is defined %}
<input type="text" readonly class="form-control-plaintext" value="{{ field.display|raw }}">
{% endif %}
Expand Down Expand Up @@ -207,7 +209,7 @@
{% endfor %}
{% endif %}
<div class="input-group">
<input type="number" min="0" {% if max is not empty %} max="{{ max }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{% if field.value >= 0 %}{{ field.value }}{% endif %}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %}/>
<input type="number" min="0" {% if max is not empty %} max="{{ max }}" {% endif %}{% if field.step is not empty %} step="{{ field.step }}" {% endif %} id="{{ id }}" name="{{ id }}" value="{% if field.value >= 0 %}{{ field.value }}{% endif %}" class="form-control {% if field.valid is defined and field.valid == false %}is-invalid{% endif %}" {% if field.mandatory is defined and field.mandatory %} required {% endif %} {% if field.readonly is defined and field.readonly %} readonly {% endif %} {% if field.autocomplete is defined %} autocomplete="{{ field.autocomplete }}" {% endif %} {% if field.placeholder is defined %} placeholder="{{ field.placeholder }}" {% endif %}/>
<div class="input-group-text">
<input class="form-check-input mt-0" type="checkbox" name="{{ id }}_ul" value="1" {% if field.value == -1 %} checked="checked" {% endif %}>
</div>
Expand Down

0 comments on commit 4ce7396

Please sign in to comment.