* Wed May 07 2025 Brian Read <brianr@koozali.org> 11.0.0-3.sme

- Missed lex file last time, plus extra code for new record [SME:13000]
- Get validation working.
- and remove rule.
This commit is contained in:
Brian Read 2025-05-09 16:56:19 +01:00
parent 54a0eb3665
commit 6d5df54c78
7 changed files with 254 additions and 148 deletions

View File

@ -54,31 +54,36 @@ my $pdb;
sub validate_RULES {
my $c = shift;
my $ms_data = shift; #Data hash as parameter
# Validation for each field
my $ret = "";
## Validation for each field
if (! TRUE) #validate $c->param('basis')
{$ret .= 'Validation for basis failed';}
if (! TRUE) #validate $c->param('criterion')
{$ret .= 'Validation for criterion failed';}
if (! TRUE) #validate $c->param('basis2')
{$ret .= 'Validation for basis2 failed';}
if (! TRUE) #validate $c->param('criterion2')
{$ret .= 'Validation for criterion2 failed';}
if (! TRUE) #validate $c->param('action')
{$ret .= 'Validation for action failed';}
if (! TRUE) #validate $c->param('deliver')
{$ret .= 'Validation for deliver failed';}
if (! TRUE) #validate $c->param('folder')
{$ret .= 'Validation for folder failed';}
if (! TRUE) #validate $c->param('copy')
{$ret .= 'Validation for copy failed';}
if (! TRUE) #validate $c->param('action2')
{$ret .= 'Validation for action2 failed';}
if (! TRUE) #validate $c->param('deliver2')
{$ret .= 'Validation for deliver2 failed';}
if (! TRUE) #validate $c->param('key')
{$ret .= 'Validation for key failed';}
#if (! TRUE) #validate $c->param('basis')
#{$ret .= 'Validation for basis failed';}
#if (! TRUE) #validate $c->param('criterion')
#{$ret .= 'Validation for criterion failed';}
#if (! TRUE) #validate $c->param('basis2')
#{$ret .= 'Validation for basis2 failed';}
#if (! TRUE) #validate $c->param('criterion2')
#{$ret .= 'Validation for criterion2 failed';}
#if (! TRUE) #validate $c->param('action')
#{$ret .= 'Validation for action failed';}
#if (! TRUE) #validate $c->param('deliver')
#{$ret .= 'Validation for deliver failed';}
#if (! TRUE) #validate $c->param('folder')
#{$ret .= 'Validation for folder failed';}
#if (! TRUE) #validate $c->param('copy')
#{$ret .= 'Validation for copy failed';}
#if (! TRUE) #validate $c->param('action2')
#{$ret .= 'Validation for action2 failed';}
#if (! TRUE) #validate $c->param('deliver2')
#{$ret .= 'Validation for deliver2 failed';}
#if (! TRUE) #validate $c->param('key')
#{$ret .= 'Validation for key failed';}
#Check parameters
$ret = $c->nonblankWithSort();
$ret = $c->nonblankWithForward() unless $ret ne 'ok';
$ret = $c->nonblankWithForward2() unless $ret ne 'ok';
if ($ret eq "") {$ret = 'ok';}
return $ret;
}
@ -117,13 +122,26 @@ sub get_data_for_panel_RULES {
my $pdb = esmith::ConfigDB->open('processmail') or die "Could not open processmail DB\n";
my $PanelUser = $c->get_panel_user();
my $key = $c->param('Selected');
# Get the rule object from the DB
my $rule = $pdb->get($key);
return () unless $rule;
unless ($rule) {
# Get the list of property names (fields) from the database metadata
my @fields = $pdb->props; # If you want all possible properties for the DB
my $len = scalar @fields;
# Or, for a specific record: my @fields = $rule->props if $rule;
my %empty_rule = map { $_ => "" } @fields;
return (
topmessage => $c->l('ms_new_record'),
%empty_rule,
);
}
# Use the new routine to get rule data
my $rule_info = get_rule_from_db($c, $rule);
# and add heading message
$rule_info->{topmessage} = $c->l('ms_You_can_change_the_order');
# Add/override any fields specific to the panel as needed
my %ret = (
@ -136,11 +154,12 @@ sub get_data_for_panel_RULES {
sub get_data_for_panel_REMOVE {
# Return a hash with the fields required which will be loaded into the shared data
my $c = shift;
my $pdb = esmith::ConfigDB->open('processmail') or die "Could not open processmail DB\n";
my $PanelUser = $c->get_panel_user();
my $key = $c->param('Selected');
my %ret = (
'Data1'=>'Data for REMOVE', #Example
# fields from Inputs in REMOVE $fields['REMOVE']
'RemoveRule'=>'RemoveRule contents',
'RemoveRule'=>"Db entries:\n------------\n".$c->esmith_db_record_to_multiline($pdb,$key)."\n\nProcmail Rule:\n--------------".$c->get_procmail_rule($pdb,$key),
);
return %ret;
}
@ -238,7 +257,7 @@ sub get_getAllRules {
sub get_selected_RULES {
my $c = shift;
my $selected = shift; #Parameter is name of selected row.
my $selected = shift; #'Parameter is name of selected row.
my $is_new_record = shift; #Indicates new record required (defaults)
my %ret = {};
return %ret;
@ -308,18 +327,23 @@ sub get_getAllRules {
sub perform_REMOVE {
my $c = shift;
my $ms_data = shift; #Data hash as parameter
my $ret = "";
my $db = $cdb; #maybe one of the others
my $dbkey = 'ChangeThis';
# To make it write to DB as comment, delete this (regex) string in each if statement "TRUE\) \#copy or perform with value: .* e.g."
if (! TRUE) #copy or perform with value: RemoveRule e.g. $db->set_prop($dbkey,'RemoveRule',$c->param('RemoveRule'),type=>'service'))
{$ret .= 'Perform/save failed for RemoveRule';}
if ($ret eq "") {$ret = 'ok';}
return $ret;
my $ms_data = shift;
return $c->remove_rule()
}
sub remove_rule {
my $c = shift;
my $rule = $c->param('Selected');
my $pdb = esmith::ConfigDB->open('processmail') or die "Could not open processmail DB\n";
my $PanelUser = $c->get_panel_user();
my $rec = $pdb->get($rule); # || return "Rule:$rule not found";
$rec->delete;
unless ( system ("/sbin/e-smith/signal-event mailsorting-conf $PanelUser") == 0 )
{ return $self->error('ERROR_UPDATING'); }
return 'ok';
}
sub create_link{
# WIP
@ -442,13 +466,16 @@ sub save_rule
my $PanelUser = $c->get_panel_user();
my $rule = $c->param ('key') || '';
#my $q =
if (($rule eq '') || ($rule eq 'new'))
{
my $random = int(rand(999999));
$rule = $PanelUser.$random;
$pdb->new_record($rule, { type => "$PanelUser" });
}
} elsif (! defined $pdb->get($rule)){
#check if the key specified exists in the DB, if not then create it
$pdb->new_record($rule, { type => "$PanelUser" });
}
my %filtered;
$filtered{criterion} .= $c->param ('criterion') || '';
$filtered{criterion2} .= $c->param ('criterion2') || '';
@ -492,111 +519,167 @@ sub save_rule
return "ok";
}
#
# This are for the future enhanced version.
#
sub describe_procmail_rule {
my ($rule) = @_;
my @lines = grep { /\S/ } map { s/^\s+|\s+$//gr } split /\n/, $rule;
my ($header, @conditions, $action);
$header = shift @lines if $lines[0] =~ /^:0/;
while (@lines && $lines[0] =~ /^\*/) {
push @conditions, shift @lines;
sub esmith_db_record_to_multiline {
my $c = shift;
my ($db, $key) = @_;
my $record = $db->get($key) or return "No record found for key: $key";
# Get all property (field) names from the DB's metadata
my @fields = $record->props;
my @lines;
foreach my $field (@fields) {
my $value = $record->prop($field);
next unless defined $value && $value ne '';
push @lines, "$field: $value";
}
$action = join(' ', @lines);
my $desc = "";
if ($header) {
my $flags = $header; $flags =~ s/^:0//; $flags =~ s/:$//; $flags =~ s/\s+//g;
$desc .= "Recipe flags: " . ($flags ? $flags : "none") . ". ";
}
if (@conditions) {
$desc .= "Matches if ";
my @cond_descs;
foreach my $cond (@conditions) {
$cond =~ s/^\*\s*//;
if ($cond =~ /^\^(.*?):\s*(.*)/) {
push @cond_descs, "the header '$1' contains '$2'";
} elsif ($cond =~ /^\//) {
push @cond_descs, "the body matches regex '$cond'";
} else {
push @cond_descs, "the message matches '$cond'";
}
}
$desc .= join(" AND ", @cond_descs) . ". ";
} else {
$desc .= "Matches all messages. ";
}
if ($action) {
if ($action =~ m{^/}) {
$desc .= "Delivers to file or mailbox '$action'.";
} elsif ($action =~ /^[A-Za-z0-9_\-]+$/) {
$desc .= "Delivers to folder '$action'.";
} elsif ($action =~ /^\!/) {
$desc .= "Forwards to address '" . (split ' ', $action, 2)[1] . "'.";
} elsif ($action =~ /^\|/) {
$desc .= "Pipes message to command '" . (substr $action, 1) . "'.";
} else {
$desc .= "Performs action: '$action'.";
}
} else {
$desc .= "No action specified.";
}
return $desc;
return join("\n", @lines);
}
sub construct_procmail_rule {
my ($entry) = @_;
sub get_esmith_db_record_hashref {
my $c = shift;
my ($db, $key) = @_;
my $record = $db->get($key) or return undef;
my %props = $record->props; # returns a hash (not a hashref)
return \%props; # return a hashref
}
# Start the recipe header
my $flags = '';
$flags .= 'c' if ($entry->{copy} && $entry->{copy} eq 'yes');
my $header = ":0$flags:";
sub get_procmail_rule {
my $c = shift;
# Logic taken from template expansion
my ($pdb, $key) = @_;
# Build conditions
my @conditions;
if ($entry->{basis} && $entry->{criterion}) {
my $cond = ($entry->{basis} eq 'Subject')
? "^Subject:.*$entry->{criterion}"
: ($entry->{basis} eq 'headers')
? $entry->{criterion}
: "^$entry->{basis}:.*$entry->{criterion}";
push @conditions, "* $cond";
}
if ($entry->{basis2} && $entry->{criterion2}) {
my $cond2 = ($entry->{basis2} eq 'Subject')
? "^Subject:.*$entry->{criterion2}"
: ($entry->{basis2} eq 'headers')
? $entry->{criterion2}
: "^$entry->{basis2}:.*$entry->{criterion2}";
push @conditions, "* $cond2";
my $basis = $pdb->get_prop($key, "basis") || '';
my $criterion = $pdb->get_prop( $key, "criterion") || '';
my $basis2 = $pdb->get_prop( $key, "basis2") || '';
my $secondtest_orig = $pdb->get_prop( $key, "basis2") || '';
my $criterion2 = $pdb->get_prop( $key, "criterion2") || '';
my $deliver = $pdb->get_prop( $key, "deliver") || '';
my $deliver2 = $pdb->get_prop( $key, "deliver2") || '';
my $copy = $pdb->get_prop( $key, "copy") || '';
my $action = $pdb->get_prop( $key, "action") || '';
my $action2 = $pdb->get_prop( $key, "action2") || '';
# Process basis fields
foreach my $b (\$basis, \$basis2) {
$$b = $c->process_basis($$b);
}
# Build action
my $action = '';
if ($entry->{action} && $entry->{action} eq 'delete') {
$action = "/dev/null";
} elsif ($entry->{deliver}) {
$action = $entry->{deliver};
} else {
$action = "INBOX"; # Default action
# Handle spaces in deliver addresses
unless (($zarafa1 eq 'enabled') || ($zarafa2 eq 'enabled')) {
$_ =~ s/ /\\ /g for ($deliver, $deliver2);
}
# Build delivery paths
$deliver = $c->build_delivery_path($action, $deliver, $USERNAME);
$deliver2 = $c->build_delivery_path($action2, $deliver2, $USERNAME) if $action2;
# Construct second test line
my $secondtest = $secondtest_orig ? "* $basis2$criterion2\n" : '';
# Build rule string
my $rule = "\n";
if ($copy eq 'no') {
$rule .= ":0\n* $basis$criterion\n$secondtest$deliver\n";
}
elsif ($copy eq 'yes' && $action2 eq 'inbox') {
$rule .= ":0 c\n* $basis$criterion\n$secondtest$deliver\n";
}
else {
$rule .= ":0\n* $basis$criterion\n$secondtest\{\n"
. " :0 c\n $deliver\n\n :0\n $deliver2\n\}\n";
}
# Assemble the recipe
my $rule = $header . "\n";
$rule .= join("\n", @conditions) . "\n" if @conditions;
$rule .= "$action\n";
return $rule;
}
sub get_esmith_db_property {
# as a hashref
my ($db, $key, $prop) = @_;
my $record = $db->get($key) or return undef;
my $value = $record->prop($prop);
return $value;
sub process_basis {
my ($c,$basis) = @_;
return '' if $basis eq 'headers';
if ($basis !~ /^[<>]$/) {
return $basis eq 'TO_' ? "^$basis" : "^${basis}.*";
}
return "$basis ";
}
sub build_delivery_path {
my ($c,$action, $deliver, $user) = @_;
return '' unless $deliver;
if ($action =~ /sort|create/) {
return ($zarafa1 eq 'enabled' || $zarafa2 eq 'enabled')
? ($deliver eq 'junkmail'
? "| zarafa-dagent -j $user"
: "| zarafa-dagent $user -C -F 'Inbox\\$deliver'")
: "\$MAILDIR/.$deliver/";
}
elsif ($action eq 'forward') {
return "! $deliver";
}
elsif ($action eq 'delete') {
return '/dev/null';
}
return $deliver;
}
sub nonblankWithForward
{
my $c = shift;
my $action = $c->param ('action') || '';
my $deliver = $c->param ('deliver') || '';
if ( $action eq 'sort')
{
return "ms_ERROR_SHOULD_BE_EMPTY" unless not $deliver;
return 'ok';
}
elsif ( $action ne 'delete')
{
if (not $deliver)
{ return "ms_ERROR_FORWARD_NO_EMAIL"; }
elsif ( $deliver =~ /^\s+$/ )
{ return "ms_ERROR_FORWARD_NO_EMAIL"; }
else
{ return 'ok'; }
}
else { return 'ok'; }
}
sub nonblankWithForward2
{
my $c = shift;
my $copy = $c->param ('copy') || '';
my $action = $c->param ('action2') || '';
my $deliver = $c->param ('deliver2') || '';
if (( $action eq 'forward') && ( $copy eq 'yes'))
{
if (not $deliver)
{ return "ms_ERROR_FORWARD_NO_EMAIL"; }
elsif ( $deliver =~ /^\s+$/ )
{ return "ms_ERROR_FORWARD_NO_EMAIL"; }
else
{ return 'ok'; }
}
else { return 'ok'; }
}
sub nonblankWithSort
{
my $c = shift;
my $action = $c->param ('action') || '';
my $folder = $c->param ('folder') || '';
if ( $action eq 'sort')
{
return "ms_ERROR_SORT_NO_FOLDER" if not $folder;
return 'ok';
}
else { return 'ok'; }
}

View File

@ -168,11 +168,10 @@ sub do_update {
}
if ($ret ne "ok"){
$c->stash(error => $c->l($ret));
$c->do_display($thispanel);
} else {
#Do whatever is needed, including writing values to the DB
if ($trt eq 'TABLE'){
#do whatever is required ...
$ret = $c->perform_TABLE(\%ms_data);

View File

@ -3,17 +3,17 @@
#
'ms_Modify_your_rule.' => 'Modify your rule',
'ms_REMOVE_panel_action_was_successful' => 'REMOVE panel action was successful',
'ms_criterion' => 'Criterion',
'ms_criterion' => 'The rule',
'ms_APPLY' => 'Apply',
'ms_RULES_panel_action_was_successful' => 'RULES panel action was successful',
'ms_Rule_contents' => 'Rule contents',
'ms_Add_new_rule' => 'Add new rule',
'ms_action2' => 'action',
'ms_You_are_about_to_remove' => 'You are about to remove a rule',
'ms_You_can_change_the_order' => 'You can change the order in which rules are evaluated by altering the numeric value in order of rule execution Priority is determined by asci value And must be unique beg rule user101 goes first Then user then user',
'ms_You_can_change_the_order' => 'You can change the order in which rules are evaluated by altering the numeric value in order of rule execution Priority is determined by asci value and must be unique. eg rule user101 goes first, then user5 then user99',
'ms_Remove' => 'Remove',
'ms_The_Rule' => 'The Rule',
'ms_key' => 'Key',
'ms_key' => 'Order of rule execution',
'ms_Rules_are_executed_as_email' => 'Rules are executed as email arrives in your mailbox on the server And are independent of your email client Your current rules If you have any are listed below',
'ms_2nd_Rule' => '2nd Rule',
'ms_Action' => 'Action',
@ -22,20 +22,32 @@
'ms_Match_Against' => 'Match Against',
'ms_2nd_Match' => '2nd Match',
'ms_Copy' => 'Copy',
'ms_criterion2' => 'criterion',
'ms_criterion2' => 'The rule',
'ms_deliver2' => 'deliver',
'ms_basis2' => 'basis',
'ms_Mail_sorting_rules' => 'Mail sorting rules',
'ms_User_Name' => 'User Name',
'ms_folder' => 'Folder',
'ms_action' => 'Action',
'ms_folder' => 'Folder (if sorting)',
'ms_action' => '2nd Action',
'ms_copy' => 'Copy',
'ms_basis' => 'Basis',
'ms_basis' => '"2nd Match',
'ms_Account' => 'Account',
'ms_Serial_Number' => 'Serial Number',
'ms_Manage_mailsortingModifyRule_settings:' => 'Manage mailsortingModifyRule settings',
'ms_Manage_mailsortingModifyRule_settings:' => 'Manage mailsorting - Modify the Rule settings',
'ms_Destination' => 'Destination',
'ms_Modify' => 'Modify',
'ms_Process_Mail' => 'Process Mail',
'ms_deliver' => 'Deliver',
'ms_Current_rules' => 'Current rules',
'ms_deliver_email' => 'Email address (if forwarding)',
'ms_Current_rules' => 'Current rules',
'ms_Modify_your_rule.' => 'Modify your rule.',
'ms_Match_against' => 'Match against',
'ms_the_rule' => 'The rule',
'ms_2nd_Match' => '2nd Match',
'ms_2nd_rule' => '2nd rule',
'ms_Action' => 'Action',
'ms_folder_if_sorting' => 'folder (if sorting)',
'ms_copy' => 'copy',
'ms_2nd_action' => '2nd action',
'ms_deliver2_email' => '2nd action Delivery email (if forwarding)',
'ms_key' => 'Order of rule execution',
'ms_new_record' => 'Select the part of the email to be tested. You can match against part of an email address, header, subject, or the email size. Size is in bytes. Delete, sort or Forward any matches. The second match is optional, but if used both rules must match'

View File

@ -16,6 +16,9 @@
%= form_for "mailsortingu" => (method => 'POST') => begin
% param 'trt' => $ms_data->{trt} unless param 'trt';
%= hidden_field 'trt' => $ms_data->{trt}
%# die("here");
%= hidden_field 'Selected' => $c->param('Selected')
%# Inputs etc in here.
<h1 class='head'><%=l('ms_Mail_sorting_rules')%></h1>
@ -26,13 +29,13 @@
%=l('ms_Rule_contents')
</span><span class=data>
% param 'RemoveRule' => $ms_data->{RemoveRule} unless param 'RemoveRule';
%= text_area 'RemoveRule', cols=>40, rows=>10, Readonly=>'true','
%= text_area 'RemoveRule', cols=>60, rows=>20, Readonly=>'true'
</span><br>
<span class='data'>
%= submit_button l('ms_Remove'), class => 'action subm95'
%= submit_button l('ms_Remove'), class => 'action subm05'
</span>
%# Probably finally by a submit.
%end
</div>

View File

@ -28,7 +28,7 @@
</p>
<p class='paragraph para2'>
%=l('ms_You_can_change_the_order')
%= $ms_data->{topmessage}; #l('ms_You_can_change_the_order')
</p>
<p><span class=label>

View File

@ -13,6 +13,10 @@
</pre>
% }
% my $btn = l('ms_APPLY');
%# flag selection undefined
% $c->param(Selected => undef);
%= form_for "mailsortingu" => (method => 'POST') => begin
% param 'trt' => $ms_data->{trt} unless param 'trt';
%= hidden_field 'trt' => $ms_data->{trt}

View File

@ -6,7 +6,7 @@ Summary: Lets users configure procmail or maildrop rules.
%define name smeserver-mailsorting
Name: %{name}
%define version 11.0.0
%define release 2
%define release 3
Version: %{version}
Release: %{release}%{?dist}
License: GPL
@ -32,6 +32,11 @@ SME Server enhancement to enable procmail or maildrop filtering for users.
Optionally provides user panels where users can create mail rules for themselves
%changelog
* Wed May 07 2025 Brian Read <brianr@koozali.org> 11.0.0-3.sme
- Missed lex file last time, plus extra code for new record [SME:13000]
- Get validation working.
- and remove rule.
* Mon May 05 2025 Brian Read <brianr@koozali.org> 11.0.0-2.sme
- Add first go at SM2 panels [SME: 13000]