Unverified Commit 3803f0fb authored by IKEDA Soji's avatar IKEDA Soji Committed by GitHub
Browse files

Merge pull request #1251 from ikedas/add+del-commands by ikedas

sympa.pl: Adding a new option --add, and expand --add and --del options
parents ce354529 676e14c7
......@@ -36,6 +36,7 @@ check_SCRIPTS = \
t/Message_smime.t \
t/Message_urlize.t \
t/Regexps.t \
t/Request_Handler_add+del.t \
t/Scenario.t \
t/Tools_Data.t \
t/Tools_File.t \
......
......@@ -158,6 +158,9 @@ message_hook privileged_owner read
personalization owner,privileged_owner read
default_owner_options owner read
default_editor_options owner read
default privileged_owner write
default owner write
default editor read
......
......@@ -284,6 +284,12 @@
[%~ ELSIF report_entry == 'unable_to_rename_list' ~%]
[%|loc(report_param.listname,report_param.new_listname)%]Unable to rename list '%1' to '%2'.[%END%]
[%~ ELSIF report_entry == 'unable_to_add_to_database' ~%]
[%|loc(report_param.email)%]Attempts to add some users in database failed.[%END%]
[%~ ELSIF report_entry == 'unable_to_delete_from_database' ~%]
[%|loc(report_param.email)%]Attempts to delete some users in database failed.[%END%]
[%~ END ~%]
[%############################~%]
......
......@@ -4606,36 +4606,54 @@ sub _review_user {
();
}
} keys %in;
my $new_users = [grep { $_ and $_->{email} }
@{($new_admin || {})->{$role} || []}];
foreach my $email (@del_users) {
next if grep { $email eq $_->{email} } @$new_users;
$list->delete_list_admin($role, $email);
}
foreach
my $user (@{(ref $new_users eq 'ARRAY') ? $new_users : []}) {
my $email = $user->{email};
if (grep { $email eq $_ } @del_users) {
; #FIXME: Update user?
} elsif ($list->add_list_admin($role, $user)) {
# Notify the new list owner/editor
Sympa::send_notify_to_user(
$list,
'added_as_listadmin',
$email,
{ admin_type => $role,
delegator => $param->{'user'}{'email'}
}
);
add_stash('notice', 'user_notified',
{notified_user => $email});
} else {
#FIXME: Report error
}
my @new_users = grep { $_ and $_->{email} }
@{($new_admin || {})->{$role} || []};
my $stash = [];
my $processed = 0;
my $spindle;
my @add = grep {
my $email = $_->{email};
!grep { $email eq $_ } @del_users;
} @new_users;
$spindle = Sympa::Spindle::ProcessRequest->new(
context => $list,
action => 'add',
role => $role,
email => [map { $_->{email} } @add],
gecos => [map { $_->{gecos} } @add],
sender => $param->{'user'}{'email'},
quiet => $param->{'quiet'},
scenario_context => {skip => 1},
stash => $stash,
);
$spindle and $processed += $spindle->spin;
my @del = grep {
my $email = $_;
!grep { $email eq $_->{email} } @new_users;
} @del_users;
$spindle = Sympa::Spindle::ProcessRequest->new(
context => $list,
action => 'del',
role => $role,
email => [@del],
sender => $param->{'user'}{'email'},
quiet => $param->{'quiet'},
scenario_context => {skip => 1},
stash => $stash,
);
$spindle and $processed += $spindle->spin;
foreach my $report (@$stash) {
add_stash(@{$report}[1 .. 3]);
}
unless (@$stash) {
add_stash('notice', 'performed');
}
 
if ($list->get_family and (@del_users or @{$new_users || []})) {
# FIXME: Required?
if ($list->get_family and $processed) {
$list->update_config_changes('param', $role);
}
}
......@@ -6856,10 +6874,32 @@ sub do_import {
#);
#return $next_action unless $next_action eq '1';
 
unless ($list->is_subscription_allowed) {
wwslog('info', 'List %s not open', $list);
add_stash('user', 'list_not_open',
{'status' => $list->{'admin'}{'status'}});
return $in{'previous_action'} || 'review';
}
my (@emails, @dnames);
foreach (split /\r\n|\r|\n/, $content) {
next unless /\S/;
next if /\A\s*#/; #FIXME: email address can contain '#'
my ($email, $dname) = m{\A\s*(\S+)(?:\s+(.*))?\s*\z};
push @emails, $email;
push @dnames, (length($dname // '') ? $dname : undef);
}
unless (@emails) {
add_stash('user', 'no_email');
return $in{'previous_action'} || 'review';
}
my $spindle = Sympa::Spindle::ProcessRequest->new(
context => $list,
action => 'import',
dump => $content,
action => 'add',
email => [@emails],
gecos => [@dnames],
sender => $param->{'user'}{'email'},
quiet => $param->{'quiet'},
md5_check => 1,
......@@ -6910,6 +6950,13 @@ sub do_add {
);
return $next_action unless $next_action eq '1';
 
unless ($list->is_subscription_allowed) {
wwslog('info', 'List %s not open', $list);
add_stash('user', 'list_not_open',
{'status' => $list->{'admin'}{'status'}});
return $in{'previous_action'} || 'review';
}
my $stash = [];
my $processed = 0;
foreach my $email (@emails) {
......@@ -7019,6 +7066,13 @@ sub do_del {
);
return $next_action unless $next_action eq '1';
 
unless ($list->is_subscription_allowed) {
wwslog('info', 'List %s not open', $list);
add_stash('user', 'list_not_open',
{'status' => $list->{'admin'}{'status'}});
return $in{'previous_action'} || 'review';
}
my $stash = [];
my $processed = 0;
foreach my $email (@emails) {
......@@ -16672,16 +16726,16 @@ sub do_delete_account {
for my $list (sort keys %{$param->{'which'}}) {
my $l = Sympa::List->new($list, $robot);
# Unsubscribe
$l->delete_list_member('users' => [$email])
$l->delete_list_member([$email])
if $param->{'which'}->{$list}->{'is_subscriber'};
# Remove from the editors
$l->delete_list_admin('editor', $email)
$l->delete_list_admin('editor', [$email])
if $param->{'which'}->{$list}->{'is_editor'};
# Remove from the owners
if ($param->{'which'}->{$list}->{'is_owner'}) {
my @admins = $l->get_admins('owner');
if (scalar(@admins) > 1) {
$l->delete_list_admin('owner', $email);
$l->delete_list_admin('owner', [$email]);
 
# Don't let a list without a privileged admin
my @privileged_admins =
......
......@@ -6,8 +6,8 @@
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2017, 2018, 2019 The Sympa Community. See the AUTHORS.md file at
# the top-level directory of this distribution and at
# Copyright 2017, 2018, 2019, 2021 The Sympa Community. See the
# AUTHORS.md file at the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
......@@ -100,7 +100,6 @@ nobase_modules_DATA = \
Sympa/Request/Handler/global_set.pm \
Sympa/Request/Handler/global_signoff.pm \
Sympa/Request/Handler/help.pm \
Sympa/Request/Handler/import.pm \
Sympa/Request/Handler/include.pm \
Sympa/Request/Handler/index.pm \
Sympa/Request/Handler/info.pm \
......
......@@ -926,7 +926,7 @@ our %pinfo = (
default_user_options => {
context => [qw(list)],
order => 20.06,
order => 20.06_01,
group => 'sending',
gettext_id => "Subscription profile",
gettext_comment => 'Default profile for the subscribers of the list.',
......@@ -958,6 +958,74 @@ our %pinfo = (
}
},
},
default_owner_options => {
context => [qw(list domain site)],
order => 20.06_02,
group => 'sending',
gettext_id => "Owner profile",
gettext_comment => 'Default profile for the owners of the list.',
format => {
profile => {
context => [qw(list domain site)],
order => 1,
gettext_id => "profile",
format => ['privileged', 'normal'],
occurrence => '1',
default => 'normal'
},
reception => {
context => [qw(list domain site)],
order => 2,
gettext_id => "reception mode",
gettext_comment => 'Mail reception mode.',
format => ['mail', 'nomail'],
field_type => 'reception',
occurrence => '1',
default => 'mail'
},
visibility => {
context => [qw(list domain site)],
order => 3,
gettext_id => "visibility",
gettext_comment => 'Visibility of the owner.',
format => ['conceal', 'noconceal'],
field_type => 'visibility',
occurrence => '1',
default => 'noconceal'
},
},
not_before => '6.2.67b.2',
},
default_editor_options => {
context => [qw(list domain site)],
order => 20.06_03,
group => 'sending',
gettext_id => "Moderator profile",
gettext_comment => 'Default profile for the moderators of the list.',
format => {
reception => {
context => [qw(list domain site)],
order => 1,
gettext_id => "reception mode",
gettext_comment => 'Mail reception mode.',
format => ['mail', 'nomail'],
field_type => 'reception',
occurrence => '1',
default => 'mail'
},
visibility => {
context => [qw(list domain site)],
order => 2,
gettext_id => "visibility",
gettext_comment => 'Visibility of the moderator.',
format => ['conceal', 'noconceal'],
field_type => 'visibility',
occurrence => '1',
default => 'noconceal'
},
},
not_before => '6.2.67b.2',
},
msg_topic => {
context => [qw(list)],
......@@ -2750,6 +2818,31 @@ our %pinfo = (
format => '.*',
occurrence => '0-1'
},
reception => {
context => [qw(list)],
order => 3,
gettext_id => "reception mode",
gettext_comment => 'Mail reception mode.',
format => [
'mail', 'notice', 'digest', 'digestplain',
'summary', 'nomail', 'txt', 'urlize',
'not_me'
],
synonym => {'html' => 'mail'},
field_type => 'reception',
occurrence => '0-1', # See default_user_options
not_before => '6.2.67b.2',
},
visibility => {
context => [qw(list)],
order => 4,
gettext_id => "visibility",
gettext_comment => 'Visibility of the subscriber.',
format => ['conceal', 'noconceal'],
field_type => 'visibility',
occurrence => '0-1', # See default_user_options
not_before => '6.2.67b.2',
}
},
occurrence => '0-n'
},
......@@ -2759,6 +2852,7 @@ our %pinfo = (
order => 60.02_1,
group => 'data_source',
gettext_id => 'Owners defined in an external data source',
not_before => '4.2b.5',
format => {
source => {
context => [qw(list)],
......@@ -2771,6 +2865,7 @@ our %pinfo = (
context => [qw(list)],
order => 2,
gettext_id => 'data source parameters',
not_before => '5.0a',
format => '.*',
occurrence => '0-1'
},
......@@ -2779,24 +2874,22 @@ our %pinfo = (
order => 3,
gettext_id => 'profile',
format => ['privileged', 'normal'],
occurrence => '1',
default => 'normal'
occurrence => '0-1', # See default_owner_options
},
reception => {
context => [qw(list)],
order => 4,
gettext_id => 'reception mode',
format => ['mail', 'nomail'],
occurrence => '1',
default => 'mail'
occurrence => '0-1', # See default_owner_options
},
visibility => {
context => [qw(list)],
order => 5,
gettext_id => "visibility",
not_before => '5.4a.6',
format => ['conceal', 'noconceal'],
occurrence => '1',
default => 'noconceal'
occurrence => '0-1', # See default_owner_options
},
},
occurrence => '0-n'
......@@ -2807,6 +2900,7 @@ our %pinfo = (
order => 60.02_2,
group => 'data_source',
gettext_id => 'Moderators defined in an external data source',
not_before => '4.2b.5',
format => {
source => {
context => [qw(list)],
......@@ -2819,6 +2913,7 @@ our %pinfo = (
context => [qw(list)],
order => 2,
gettext_id => 'data source parameters',
not_before => '5.0a',
format => '.*',
occurrence => '0-1'
},
......@@ -2827,16 +2922,15 @@ our %pinfo = (
order => 3,
gettext_id => 'reception mode',
format => ['mail', 'nomail'],
occurrence => '1',
default => 'mail'
occurrence => '0-1', # See default_editor_options
},
visibility => {
context => [qw(list)],
order => 5,
gettext_id => "visibility",
not_before => '5.4a.6',
format => ['conceal', 'noconceal'],
occurrence => '1',
default => 'noconceal'
occurrence => '0-1', # See default_editor_options
}
},
occurrence => '0-n'
......
......@@ -61,48 +61,22 @@ sub new {
die 'bug in logic. Ask developer' unless ref $list eq 'Sympa::List';
}
# Get default user options.
my ($defopts, @required);
if ($options{default_user_options}) {
$defopts = $options{default_user_options};
@required = qw(reception visibility);
} elsif ($role eq 'member') {
$defopts = $list->{'admin'}{'default_user_options'};
@required = qw(reception visibility);
} elsif ($role eq 'owner') {
my @keys = qw(visibility reception profile info);
@{$defopts}{@keys} = @options{@keys};
@required = qw(reception visibility profile);
} elsif ($role eq 'editor') {
my @keys = qw(visibility reception info);
@{$defopts}{@keys} = @options{@keys};
@required = qw(reception visibility);
}
# Complement required attributes.
#FIXME: check not only existence but also validity of values
if (@required) {
my $defdefs = {
reception => 'mail',
visibility => 'noconceal',
profile => 'normal',
};
my @missing =
grep { not(defined $defopts->{$_} and length $defopts->{$_}) }
@required;
@{$defopts}{@missing} = @{$defdefs}{@missing} if @missing;
# Get default user options from data source definition.
my %defopts;
if (grep { $role eq $_ } qw(member owner editor)) {
%defopts =
map { ($_ => $options{$_}) }
grep { defined $options{$_} }
keys %{$list->get_default_user_options(role => $role)};
}
my @defkeys = sort keys %{$defopts || {}};
my @defvals = @{$defopts || {}}{@defkeys} if @defkeys;
#FIXME: consider boundaries of Unicode characters (or grapheme clusters)
$options{name} = substr $options{name}, 0, 50
if $options{name} and 50 < length $options{name};
$options{name} = Sympa::Tools::Text::clip($options{name}, 50)
if 50 < length($options{name} // '');
my $self = $type->_new(
%options,
_role => $role,
_defkeys => [@defkeys],
_defvals => [@defvals],
_role => $role,
default_user_options => {%defopts},
);
$self->{_external} = not($self->isa('Sympa::DataSource::List')
and [split /\@/, $self->{listname}, 2]->[1] eq $list->{'domain'})
......
......@@ -511,15 +511,23 @@ sub insert_delete_exclusion {
## Insert: family, user and date
## Add dummy list_exclusion column to satisfy constraint.
my $sdm;
my $sdm = Sympa::DatabaseManager->instance;
unless (
$sdm = Sympa::DatabaseManager->instance
$sdm
and $sdm->do_prepared_query(
q{INSERT INTO exclusion_table
(list_exclusion, family_exclusion, robot_exclusion,
user_exclusion, date_exclusion)
VALUES (?, ?, ?, ?, ?)},
sprintf('family:%s', $name), $name, $robot_id, $email, $date
SELECT ?, ?, ?, ?, ?
FROM dual
WHERE NOT EXISTS (
SELECT 1
FROM exclusion_table
WHERE family_exclusion = ? AND robot_exclusion = ? AND
user_exclusion = ?
)},
sprintf('family:%s', $name), $name, $robot_id, $email, $date,
$name, $robot_id, $email
)
) {
$log->syslog('err', 'Unable to exclude user %s from family %s',
......
This diff is collapsed.
......@@ -55,90 +55,89 @@ sub _twist {
my $request = shift;
my $list = $request->{context};
my $which = $list->{'name'};
my $robot = $list->{'domain'};
my $sender = $request->{sender};
my $email = $request->{email};
my $comment = $request->{gecos};
my $role = $request->{role} || 'member';
my $ca = $request->{custom_attribute};
die 'bug in logic. Ask developer'
unless grep { $role eq $_ } qw(member owner editor);
$language->set_lang($list->{'admin'}{'lang'});
unless (Sympa::Tools::Text::valid_email($email)) {
$self->add_stash($request, 'user', 'incorrect_email',
{'email' => $email});
$log->syslog('err',
'ADD command rejected; incorrect email "%s"', $email);
'request "add" rejected; incorrect email "%s"', $email);
return undef;
}
if (Sympa::Tools::Domains::is_blocklisted($email)) {
$self->add_stash($request, 'user', 'blocklisted_domain',
{'email' => $email});
$log->syslog('err',
'ADD command rejected; blocklisted domain for "%s"', $email);
return undef;
}
if ($list->is_list_member($email)) {
$self->add_stash($request, 'user', 'already_subscriber',
{'email' => $email, 'listname' => $list->{'name'}});
$log->syslog('err',
'ADD command rejected; user "%s" already member of list "%s"',
$email, $which);
return undef;
}
unless ($request->{force}) {
# If a list is not 'open' and allow_subscribe_if_pending has been set
# to 'off' returns undef.
unless (
$list->{'admin'}{'status'} eq 'open'
or Conf::get_robot_conf($list->{'domain'},
'allow_subscribe_if_pending') eq 'on'
) {
my @stash;
if ($role eq 'member') {
unless ($request->{force} or $list->is_subscription_allowed) {
$log->syslog('info', 'List %s not open', $list);
$self->add_stash($request, 'user', 'list_not_open',
{'status' => $list->{'admin'}{'status'}});
$log->syslog('info', 'List %s not open', $list);
return undef;
}
}
if (Sympa::Tools::Domains::is_blocklisted($email)) {
$self->add_stash($request, 'user', 'blocklisted_domain',
{'email' => $email});
$log->syslog('err',
'request "add" rejected; blocklisted domain for "%s"',
$email);
return undef;
}
my $u;
my $defaults = $list->get_default_user_options();
%{$u} = %{$defaults};
$u->{'email'} = $email;
$u->{'gecos'} = $comment;
$u->{'date'} = $u->{'update_date'} = time;
$u->{custom_attribute} = $ca if $ca;
$list->add_list_member($u);
if (defined $list->{'add_outcome'}{'errors'}) {
if (defined $list->{'add_outcome'}{'errors'}
{'max_list_members_exceeded'}) {
$self->add_stash($request, 'user', 'max_list_members_exceeded',
{max_list_members => $list->{'admin'}{'max_list_members'}});
} else {
my $error =
sprintf 'Unable to add user %s in list %s : %s',
$u, $list->get_id,
$list->{'add_outcome'}{'errors'}{'error_message'};
$list->add_list_member(
{email => $email, gecos => $comment, custom_attribute => $ca},
stash => \@stash);
} else {
$list->add_list_admin(
$role,
{email => $email, gecos => $comment},
stash => \@stash
);
}
foreach my $report (@stash) {
$self->add_stash($request, @$report);
if ($report->[0] eq 'intern') {
Sympa::send_notify_to_listmaster(
$list,
'mail_intern_error',
{ error => $error,
{ error => $report->[1], #FIXME: Update listmaster tt2
who => $sender,
action => 'Command process',
}
);