Unverified Commit 1d6b19ef authored by IKEDA Soji's avatar IKEDA Soji Committed by GitHub
Browse files

Merge pull request #275 from ikedas/issue-11_trial4 by ikedas

#11: Spurious error on duplicate keys with admin sync, and changes on configuration for owners/moderators
parents 91bfffa6 d7bab5fe
<!-- edit.tt2 -->
<h2>
[% IF role == 'owner' ~%]
[%|loc%]Owner[%END%]
[%~ ELSIF role == 'editor' ~%]
[%|loc%]Moderator[%END%]
[%~ ELSE ~%]
[% RETURN %]
[%~ END %]
</h2>
<form action="[% path_cgi %]" method="post">
<fieldset>
[% SET pV = config_values.${role}.0 ~%]
<input type="hidden" name="previous_action" value="[% previous_action %]" />
<input type="hidden" name="list" value="[% list %]" />
<input type="hidden" name="role" value="[% role %]" />
<input type="hidden" name="email" value="[% pV.email %]" />
<div class="row">
<div class="columns">
<label for="email">[%|loc%]Email:[%END%] </label>
[% pV.email %]
</div>
<div class="columns">
<label for="gecos">[%|loc%]Name:[%END%] </label>
<input type="text" name="single_param.[% role %].0.gecos" id="gecos"
value="[% pV.gecos %]" size="25" />
</div>
[% IF is_privileged_owner ~%]
<div class="columns">
<label for="info">[%|loc%]private information[%END%] </label>
<input type="text" name="single_param.[% role %].0.info" id="info"
value="[% pV.info %]" size="30" />
</div>
[%~ END %]
[% IF role == 'owner' ~%]
<div class="columns">
<label for="profile">[%|loc%]profile[%END%] </label>
[% IF is_listmaster ~%]
<select name="single_param.[% role %].0.profile" id="profile">
[% FOREACH r = ['normal', 'privileged'] ~%]
<option value="[% r %]"
[%~ IF r == pV.profile %] selected="selected"[% END ~%]
>
[%~ r | optdesc ~%]
</option>
[% END %]
</select>
[%~ ELSE ~%]
[% pV.profile | optdesc %]
[%~ END %]
</div>
[%~ END %]
<div class="columns">
<label for="reception">[%|loc%]Receiving:[%END%] </label>
<select name="single_param.[% role %].0.reception" id="reception">
[% FOREACH r = ['mail', 'nomail'] ~%]
<option value="[% r %]"
[%~ IF r == pV.reception %] selected="selected"[% END ~%]
>
[%~ r | optdesc ~%]
</option>
[% END %]
</select>
</div>
<div class="columns">
<label for="visibility">[%|loc%]Visibility:[%END%] </label>
<select id="visibility" name="single_param.[% role %].0.visibility">
[% FOREACH r = ['noconceal', 'conceal'] ~%]
<option value="[% r %]"
[%~ IF r == pV.visibility %] selected="selected"[% END ~%]
>
[%~ r | optdesc ~%]
</option>
[% END %]
</select>
</div>
<div class="columns">
<label>[%|loc%]Delegated since:[%END%] </label>
[% pV.date | optdesc('unixtime') %]
</div>
<div class="columns">
<label>[%|loc%]Last update:[%END%] </label>
[% pV.update_date | optdesc('unixtime') %]
</div>
[% IF is_privileged_owner ~%]
<input type="hidden" name="submit" value="submit" />
<div class="columns">
<input class="MainMenuLinks" type="submit" name="action_edit"
value="[%|loc%]Update[%END%]" />
</div>
[%~ END %]
</div>
</fieldset>
</form>
<!-- end edit.tt2 -->
......@@ -56,7 +56,12 @@
<div class="item_content">
<a class="item_title" href="[% 'admin' | url_rel([list]) %]"><i class="fa fa-wrench fa-3x pull-left fa-border"></i> [%|loc%]List Configuration[%END%]</a>
<ul class="fa-ul">
<li><i class="fa-li fa fa-arrow-right"></i><a href="[% 'edit_list_request' | url_rel([list,'description']) %]">[%|loc%]Modify owners or moderators (editors)[%END%]</a></li>
<li>
<i class="fa-li fa fa-arrow-right"></i>
<a href="[% 'review' | url_rel([list,'owner']) %]">
[%|loc%]Modify owners or moderators (editors)[%END%]
</a>
</li>
<li><i class="fa-li fa fa-arrow-right"></i><a href="[% 'edit_list_request' | url_rel([list,'description']) %]">[%|loc%]Modify list subject and visibility[%END%]</a></li>
<li><i class="fa-li fa fa-arrow-right"></i><a href="[% 'edit_list_request' | url_rel([list,'sending']) %]">[%|loc%]Change who can post to this list[%END%]</a></li>
<li><i class="fa-li fa fa-arrow-right"></i><a href="[% 'edit_list_request' | url_rel([list,'command']) %]">[%|loc%]Change who can (un)subscribe and view list information[%END%]</a></li>
......
......@@ -43,7 +43,10 @@
[% IF is_priv %]
<span>
<a href="[% 'edit_list_request' | url_rel([list,'description'],{},'owner') %]"><i class="fa fa-pencil-square fa-lg" title="[%|loc%](Edit)[%END%]"></i></a>
<a href="[% 'review' | url_rel([list,'owner']) %]">
<i class="fa fa-pencil-square fa-lg" title="[%|loc%](Edit)[%END%]">
</i>
</a>
</span>
[% END %]
</span>
......@@ -78,7 +81,10 @@
[% IF is_priv %]
<span>
<a href="[% 'edit_list_request' | url_rel([list,'description'],{},'editor') %]"><i class="fa fa-pencil-square fa-lg" title="[%|loc%](Edit)[%END%]"></i></a>
<a href="[% 'review' | url_rel([list,'editor']) %]">
<i class="fa fa-pencil-square fa-lg" title="[%|loc%](Edit)[%END%]">
</i>
</a>
</span>
[% END %]
</span>
......
......@@ -141,10 +141,30 @@
<li class="[% class %]"><a href="[% 'edit_list_request' | url_rel([list,'other']) %]" >[%|loc%]Miscellaneous[%END%]</a></li>
</ul>
</li>
[% IF is_owner %]
[% IF action == 'review' %][% SET class = 'active' %][% ELSE %][% SET class = '' %][% END %]
<li class="[% class %]"><a href="[% 'review' | url_rel([list]) %]">[%|loc%]Manage Subscribers[%END%]</a></li>
[% END %]
[% IF is_owner ~%]
<li class="has-dropdown [% IF action == 'review' %]active[%END%]">
<a href="#">
[%|loc%]Users[%END%]
</a>
<ul class="dropdown">
<li [% IF page.match('^\d+$') %]class="active"[%END%]>
<a href="[% 'review' | url_rel([list]) %]" >
[%|loc%]Subscribers[%END%]
</a>
</li>
<li [% IF page == 'owner' %]class="active"[%END%]>
<a href="[% 'review' | url_rel([list,'owner']) %]" >
[%|loc%]Owners[%END%]
</a>
</li>
<li [% IF page == 'editor' %]class="active"[%END%]>
<a href="[% 'review' | url_rel([list,'editor']) %]" >
[%|loc%]Editors[%END%]
</a>
</li>
</ul>
</li>
[%~ END %]
[% IF conf.use_blacklist != 'none' %]
[% IF action == 'blacklist' %][% SET class = 'active' %][% ELSE %][% SET class = '' %][% END %]
<li class="[% class %]"><a href="[% 'blacklist' | url_rel([list]) %]" >[%|loc%]Blacklist[%END%]</a></li>
......
<!-- review.tt2 -->
[% IF page.match('^\d*$') ~%]
[% PROCESS ReviewMembers ~%]
[%~ ELSIF page == 'owner' ~%]
[% PROCESS ReviewUsers
role = page
users = owners %]
[%~ ELSIF page == 'editor' ~%]
[% PROCESS ReviewUsers
role = page
users = editors %]
[%~ END %]
[%~ BLOCK ReviewMembers # (members) ~%]
[% IF is_owner %]
<h2>[%|loc%]Manage list members[%END%] <a href="[% 'nomenu/help/admin' | url_rel %]#manage_members" title="[%|loc%]Open in a new window[%END%]" onclick="window.open('','wws_help','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=no,width=400,height=200')" target="wws_help"><i class="fa fa-question-circle" title="[%|loc%]Help[%END%]"></i></a></h2>
......@@ -206,4 +221,206 @@
[% END %]
[% END %]
[%~ END #ReviewMembers %]
[%~ BLOCK ReviewUsers # (role,users) ~%]
<h2>
[% IF role == 'owner' ~%]
[%|loc%]Owners[%END%]
[%~ ELSIF role == 'editor' ~%]
[%|loc%]Moderators[%END%]
[%~ END %]
</h2>
<form action="[% path_cgi %]" method="POST">
<fieldset role="table">
<input type="hidden" name="list" value="[% list %]" />
<input type="hidden" name="action" value="review" />
<input type="hidden" name="page" value="[% role %]" />
<div class="row" role="row">
<div class="small-10 medium-11 columns">
<div class="small-6 medium-4 columns" role="columnheader">
<label>[%|loc%]Email[%END%]</label>
</div>
<div class="small-6 medium-4 columns" role="columnheader">
<label>[%|loc%]Name[%END%]</label>
</div>
<div class="medium-2 columns show-for-medium-up" role="columnheader">
<label>[%|loc%]Reception[%END%]</label>
</div>
<div class="medium-2 columns show-for-medium-up" role="columnheader">
<label>[%|loc%]Visibility[%END%]</label>
</div>
[% IF is_privileged_owner ~%]
<div class="columns" role="columnheader">
<label>[%|loc%]private information[%END%]</label>
</div>
[%~ END %]
<div class="columns" role="separator">
<hr>
</div>
</div>
[% IF is_privileged_owner ~%]
<div class="small-2 medium-1 columns" role="columnheader">
<label title="[%|loc%]Delete[%END%]">
<i class="fa fa-user-times"></i>
</label>
</div>
[%~ END %]
</div>
[% IF users.size() ~%]
[% SET idx = 0 ~%]
[% FOREACH u = users ~%]
<div class="row" role="row">
<div class="small-10 medium-11 columns" id="item.user.[% idx %]">
<div class="small-6 medium-4 columns" role="cell">
<span class="show-for-medium-up">
[%~ IF role == 'owner' && u.profile == 'privileged' ~%]
<i class="fa fa-fw fa-star"
title="[%|loc%]Privileged owner[%END%]"></i>
[%~ ELSIF role == 'owner' ~%]
<i class="fa fa-fw" title="[%|loc%]Owner[%END%]"></i>
[%~ ELSIF role == 'editor' ~%]
<i class="fa fa-fw" title="[%|loc%]Moderator[%END%]"></i>
[%~ END %]
</span>
[% IF is_privileged_owner ~%]
<a href="[% 'ajax/edit' | url_rel([list,role],{email=>u.email,previous_action=>action}) %]"
data-reveal-id="edit" data-reveal-ajax="true"
class="MainMenuLinks">[% u.email %]</a>
[%~ ELSE ~%]
[% u.email %]
[%~ END %]
</div>
<div class="small-6 medium-4 columns" role="cell">
[% u.gecos || '&nbsp;' %]
</div>
<div class="medium-2 columns show-for-medium-up" role="cell">
[% u.reception | optdesc %]
</div>
<div class="medium-2 columns show-for-medium-up" role="cell">
[% u.visibility | optdesc %]
</div>
[% IF is_privileged_owner ~%]
<div class="columns" role="cell">
[% u.info %]
</div>
[%~ END %]
<div class="columns" role="separator">
<hr>
</div>
</div>
[% IF is_privileged_owner ~%]
<div class="small-2 medium-1 columns" role="cell">
[% IF u.subscribed ~%]
<input type="checkbox" name="del_emails"
id="del.user.[% idx %]"
class="fadeIfChecked" data-selector="#item\.user\.[% idx %]"
title="[%|loc%]Delete[%END%]"
value="[% u.email %]" />
[%~ ELSIF u.included ~%]
&nbspo;
[%~ END %]
</div>
[%~ END %]
</div>
[%~ SET idx = idx + 1 %]
[%~ END %]
[%~ ELSE ~%]
<p
class="small-12 medium-8 medium-centered columns alert-box info text-center">
[% IF role == 'owner' ~%]
[%|loc%]List has no owners[%END%]
[%~ ELSIF role == 'editor' ~%]
[%|loc%]List has no moderators[%END%]
[%~ END %]
</p>
[%~ END %]
[% IF is_privileged_owner ~%]
<h3>
[% IF role == 'owner' ~%]
[%|loc%]Add owners[%END%]
[%~ ELSIF role == 'editor' ~%]
[%|loc%]Add momderators[%END%]
[%~ END %]
</h3>
<div class="row" id="item.user.0.new" role="row">
<div class="small-10 medium-11 columns">
[% IF role == 'owner' ~%]
<div class="columns show-for-medium-up" role="cell">
<input type="checkbox"
name="single_param.user.0.profile" id="param.user.0.profile"
value="privileged" />
<label for="param.user.0.profile">[% 'privileged' | optdesc %]</label>
</div>
[%~ END %]
<div class="small-6 medium-4 columns" role="cell">
<label for="param.user.0.email">[%|loc%]Email[%END%]
<input type="text"
name="single_param.user.0.email" id="param.user.0.email" />
</div>
<div class="small-6 medium-4 columns" role="cell">
<label for="param.user.0.gecos">[%|loc%]Name[%END%]
<input name="single_param.user.0gecos" id="param.user.0.gecos" />
</div>
<div class="medium-2 columns show-for-medium-up" role="cell">
<input type="checkbox"
name="single_param.user.0.reception" id="param.user.0.reception"
value="nomail" />
<label for="param.user.0.reception">[% 'nomail' | optdesc %]</label>
</div>
<div class="medium-2 columns show-for-medium-up" role="cell">
<input type="checkbox"
name="single_param.user.0.visibility" id="param.user.0.visibility"
value="conceal" />
<label for="param.user.0.visibility">[% 'conceal' | optdesc %]</label>
</div>
<div class="columns" role="cell">
<label for="param.user.0.info">[%|loc%]private information[%END%]</label>
<input type="text"
name="single_param.user.0.info" id="param.user.0.info" />
</div>
</div>
</div>
[%~ END %]
<input class="MainMenuLInks" type="submit" name="action_review"
value="[%|loc%]Update[%END%]" />
</fieldset>
</form>
[%# AJAX modal dialog ~%]
<div id="edit" class="reveal-modal medium" data-reveal
aria-labelledby="[%|loc%]View user[%END%]" aria-hidden="true"
role="dialog">
[%# empty div that will display a content by AJAX. ~%]
</div>
[%~ END#ReviewUsers ~%]
<!-- end review.tt2 -->
......@@ -51,6 +51,21 @@
</pre>
</div>
<h2>[%|loc%]Users[%END%]</h2>
<ul>
<li>
<a href="[% 'review' | url_rel([list,'owner']) %]">
[%|loc%]Owners[%END%]
</a>
</li>
<li>
<a href="[% 'review' | url_rel([list,'editor']) %]">
[%|loc%]Moderators[%END%]
</a>
</li>
</ul>
<div class="block">
<h2>[%|loc%]Configuration file[%END%]</h2>
[% IF is_listmaster ~%]
......
......@@ -248,6 +248,7 @@ our %comm = (
'edit_config' => 'do_edit_config',
#XXX'submit_list' => 'do_submit_list',
'editsubscriber' => 'do_editsubscriber',
'edit' => 'do_edit',
'viewbounce' => 'do_viewbounce',
'redirect' => 'do_redirect',
'rename_list_request' => 'do_rename_list_request',
......@@ -401,6 +402,7 @@ our %action_args = (
# ['list', 'email', 'previous_action', 'custom_attribute'],
#'editsubscriber' => ['list', 'email', 'previous_action'],
'editsubscriber' => ['list'],
'edit' => ['list', 'role'],
#'viewbounce' => ['list', 'email', '@file'],
'viewbounce' => ['list', 'dir', '@file'],
#'resetbounce' => ['list', 'email'],
......@@ -556,6 +558,7 @@ our %required_args = (
'distribute' => ['param.list', 'param.user.email', 'id|idspam'],
'add_frommod' => ['param.list', 'param.user.email', 'id'],
'dump_scenario' => ['param.list', 'pname'],
'edit' => ['param.list', 'param.user.email', 'role', 'email'],
'edit_list' => ['param.user.email', 'param.list'],
'edit_list_request' => ['param.user.email', 'param.list'],
'edit_template' => ['webormail'],
......@@ -663,6 +666,7 @@ our %required_privileges = (
'distribute' => ['editor', 'owner', 'listmaster'],
'add_frommod' => ['editor', 'owner'],
'dump_scenario' => ['listmaster'],
'edit' => ['editor', 'owner', 'listmaster'],
'edit_list' => ['owner'],
'edit_list_request' => ['owner'],
'edit_template' => ['listmaster'],
......@@ -739,6 +743,7 @@ my %action_type = (
'savefile' => 'admin',
'rebuildallarc' => 'admin', #FIXME: serveradmin?
'reviewbouncing' => 'admin',
'edit' => 'admin',
'edit_list_request' => 'admin',
'edit_list' => 'admin',
'editsubscriber' => 'admin',
......@@ -749,8 +754,8 @@ my %action_type = (
'd_admin' => 'admin',
'd_reject_shared' => 'admin',
'd_install_shared' => 'admin',
'export_member' => 'admin',
'dump_scenario' => 'admin',
'export_member' => 'admin',
'open_list' => 'admin',
'remind' => 'admin',
#'subindex' => 'admin',
......@@ -826,7 +831,7 @@ our %in_regexp = (
'blacklist' => '.*',
 
## Integer
'page' => '\d+',
'page' => '\d+|owner|editor',
'size' => '\d+',
 
## Free data
......@@ -903,6 +908,7 @@ our %in_regexp = (
'new_email' => Sympa::Regexps::email(),
'sender' => Sympa::Regexps::email(),
'fromaddr' => Sympa::Regexps::email(),
'del_emails' => '.*',
'to' => '(([\w\-\_\.\/\+\=\']+|\".*\")\s[\w\-]+(\.[\w\-]+)+(,?))*',
'automatic_list_part_*' => '[\w\-\.\+]*',
 
......@@ -958,6 +964,9 @@ our %in_regexp = (
 
## Authentication/moderation key
'authkey' => '\w+',
# Role
'role' => 'member|editor|owner',
);
 
## Regexp applied on incoming parameters (%in)
......@@ -4757,6 +4766,75 @@ sub do_subscriber_count {
## Subscribers' list
sub do_review {
wwslog('info', '(%s)', $in{'page'});
$param->{'page'} = $in{'page'} || 1;
if ($param->{'page'} eq 'owner') {
return _review_user('owner');
} elsif ($in{'page'} eq 'editor') {
return _review_user('editor');
} else {
return _review_member();
}
}
# List of owners / editors
sub _review_user {
wwslog('info', '(%s)', @_);
my $role = shift;
# Access control
return undef
unless Sympa::is_listmaster($list, $param->{'user'}{'email'})
or $list->is_admin('owner', $param->{'user'}{'email'});
# Delete/add users.
my @del_users = grep {$_} map { Sympa::Tools::Text::canonic_email($_) }
split /\0/, $in{'del_emails'};
my $new_users = [
grep { $_ and $_->{email} }
@{(_deserialize_changes() || {})->{user} || []}
];
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?
} else {
unless ($list->add_list_admin($role, $user)) {
#FIXME: Report error
} else {
# Notify the new list owner/editor
Sympa::send_notify_to_user(
$list,
'added_as_listadmin',
$email,
{ admin_type => $role,
delegator => $param->{'user'}{'email'}
}
);
Sympa::WWW::Report::notice_report_web('user_notified',
{'notified_user' => $email},
$param->{'action'});
}
}
}
# Users list
my $users =
[grep { $_->{role} eq $role } @{$list->get_current_admins || []}];
foreach my $user (@$users) {
$user->{sources} = $list->get_datasource_name($user->{id})
if $user->{id};
}
$param->{($role eq 'owner') ? 'owners' : 'editors'} = $users;
return 1;
}
sub _review_member {
my $record;
my @users;
my $size;
......@@ -4899,6 +4977,83 @@ sub do_review {
return 1;
}
 
sub do_edit {
wwslog('info', '(%s, %s)', $in{'role'}, $in{'email'});
my $role = $in{'role'};
my $email = $in{'email'};
$param->{'role'} = $role;
$param->{'page'} = $role; # For review action
my @keys = qw(gecos profile info reception visibility);
#FIXME: Provide config schema including privileges by Family.
my $schema = {};
my ($role_p, $priv_p) = $list->may_edit($role, $param->{'user'}{'email'});
foreach my $key (@keys) {
my ($role, $priv) =
$list->may_edit("$role.$key", $param->{'user'}{'email'});
my $pitem = $schema->{$key} = {};
$priv = $priv_p
if not $priv
or ($priv_p and $priv_p lt $priv);
$pitem->{privilege} = $priv
if not $pitem->{privilege}
or ($priv and $priv lt $pitem->{privilege});
$pitem->{privilege} ||= 'hidden'; # Implicit default
}
# Initial access. show current value.
unless ($in{'submit'}) {
$param->{'config_schema'} = $schema;
my ($user) =
grep { $_ and $_->{email} eq $email and $_->{role} eq $role }
@{$list->get_current_admins || []};
$param->{'config_values'} = {$role => [$user]} if $user;
$param->{'previous_action'} = $in{'previous_action'} || 'review';
return 1;
} else {
# Start parsing the data sent by the edition form.
my $new_config = _deserialize_changes();
my $new = $new_config->{$role}->[0] || {} if $new_config;
my $values = {};
foreach my $key (@keys) {
next unless $schema->{$key}{privilege}
and $schema->{$key}{privilege} eq 'write';
if ($key eq 'gecos') {
$values->{gecos} = (defined $new->{gecos}) ? $new->{gecos} : ''
if exists $new->{gecos};
} elsif ($key eq 'reception') {
$values->{reception} = $new->{reception}
if $new->{reception}
and grep {$new->{reception} eq $_} qw(mail nomail);
} elsif ($key eq 'visibility') {
$values->{visibility} = $new->{visibility}
if $new->{visibility}
and grep {$new->{visibility} eq $_}
qw(conceal noconceal);
} elsif ($key eq 'profile') {
$values->{profile} = $new->{profile}
if $new->{profile}