summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla/User.pm40
-rw-r--r--CGI.pl2
-rw-r--r--defparams.pl10
-rwxr-xr-xeditusers.cgi1468
-rw-r--r--globals.pl6
-rw-r--r--skins/standard/admin.css28
-rw-r--r--skins/standard/editusers.css52
-rw-r--r--template/en/default/admin/groups/delete.html.tmpl2
-rw-r--r--template/en/default/admin/users/confirm-delete.html.tmpl404
-rw-r--r--template/en/default/admin/users/create.html.tmpl57
-rw-r--r--template/en/default/admin/users/edit.html.tmpl154
-rw-r--r--template/en/default/admin/users/list.html.tmpl98
-rw-r--r--template/en/default/admin/users/listselectvars.html.tmpl34
-rw-r--r--template/en/default/admin/users/search.html.tmpl70
-rw-r--r--template/en/default/admin/users/userdata.html.tmpl79
-rw-r--r--template/en/default/filterexceptions.pl25
-rw-r--r--template/en/default/global/code-error.html.tmpl6
-rw-r--r--template/en/default/global/messages.html.tmpl72
-rw-r--r--template/en/default/global/user-error.html.tmpl14
19 files changed, 1812 insertions, 809 deletions
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index f87f021b9..b17b638d1 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -86,6 +86,7 @@ sub _create {
'name' => '',
'login' => '',
'showmybugslink' => 0,
+ 'disabledtext' => '',
'flags' => {},
};
bless ($self, $class);
@@ -101,9 +102,11 @@ sub _create {
my ($id,
$login,
$name,
+ $disabledtext,
$mybugslink) = $dbh->selectrow_array(qq{SELECT userid,
login_name,
realname,
+ disabledtext,
mybugslink
FROM profiles
WHERE $cond},
@@ -115,6 +118,7 @@ sub _create {
$self->{'id'} = $id;
$self->{'name'} = $name;
$self->{'login'} = $login;
+ $self->{'disabledtext'} = $disabledtext;
$self->{'showmybugslink'} = $mybugslink;
# Now update any old group information if needed
@@ -951,12 +955,14 @@ sub get_userlist {
return $self->{'userlist'};
}
-sub insert_new_user ($$) {
- my ($username, $realname) = (@_);
+sub insert_new_user ($$;$$) {
+ my ($username, $realname, $password, $disabledtext) = (@_);
my $dbh = Bugzilla->dbh;
- # Generate a new random password for the user.
- my $password = &::GenerateRandomPassword();
+ $disabledtext ||= '';
+
+ # If not specified, generate a new random password for the user.
+ $password ||= &::GenerateRandomPassword();
my $cryptpassword = bz_crypt($password);
# XXX - These should be moved into ValidateNewUser or CheckEmailSyntax
@@ -966,10 +972,12 @@ sub insert_new_user ($$) {
# Insert the new user record into the database.
$dbh->do("INSERT INTO profiles
- (login_name, realname, cryptpassword, emailflags)
- VALUES (?, ?, ?, ?)",
+ (login_name, realname, cryptpassword, emailflags,
+ disabledtext)
+ VALUES (?, ?, ?, ?, ?)",
undef,
- ($username, $realname, $cryptpassword, DEFAULT_EMAIL_SETTINGS));
+ ($username, $realname, $cryptpassword, DEFAULT_EMAIL_SETTINGS,
+ $disabledtext));
# Return the password to the calling code so it can be included
# in an email sent to the user.
@@ -1039,7 +1047,7 @@ Bugzilla::User - Object for a Bugzilla user
my $user = new Bugzilla::User($id);
# Class Functions
- $random_password = insert_new_user($username, $realname);
+ $password = insert_new_user($username, $realname, $password, $disabledtext);
=head1 DESCRIPTION
@@ -1132,6 +1140,10 @@ linkinfooter - Whether or not the query should be displayed in the footer.
=back
+=item C<disabledtext>
+
+Returns the disable text of the user, if any.
+
=item C<flush_queries_cache>
Some code modifies the set of stored queries. Because C<Bugzilla::User> does
@@ -1254,12 +1266,18 @@ called "statically," just like a normal procedural function.
=item C<insert_new_user>
-Creates a new user in the database with a random password.
+Creates a new user in the database.
Params: $username (scalar, string) - The login name for the new user.
$realname (scalar, string) - The full name for the new user.
-
-Returns: The password that we randomly generated for this user, in plain text.
+ $password (scalar, string) - Optional. The password for the new user;
+ if not given, a random password will be
+ generated.
+ $disabledtext (scalar, string) - Optional. The disable text for the new
+ user; if not given, it will be empty.
+
+Returns: The password for this user, in plain text, so it can be included
+ in an e-mail sent to the user.
=item C<is_available_username>
diff --git a/CGI.pl b/CGI.pl
index ff2db2feb..69ec8f64f 100644
--- a/CGI.pl
+++ b/CGI.pl
@@ -213,7 +213,7 @@ sub CheckEmailSyntax {
my ($addr) = (@_);
my $match = Param('emailregexp');
if ($addr !~ /$match/ || $addr =~ /[\\\(\)<>&,;:"\[\] \t\r\n]/) {
- ThrowUserError("illegal_email_address", { addr => $addr });
+ ThrowUserError("illegal_email_address", { addr => $addr }, 'abort');
}
}
diff --git a/defparams.pl b/defparams.pl
index 169db1232..d5c46c9f4 100644
--- a/defparams.pl
+++ b/defparams.pl
@@ -1061,10 +1061,12 @@ Reason: %reason%
{
name => 'allowuserdeletion',
- desc => 'The pages to edit users can also let you delete a user. But there ' .
- 'is no code that goes and cleans up any references to that user in ' .
- 'other tables, so such deletions are kinda scary. So, you have to ' .
- 'turn on this option before any such deletions will ever happen.',
+ desc => 'The pages to edit users can also let you delete a user. ' .
+ 'Bugzilla will issue a warning in case you\'d run into ' .
+ 'inconsistencies when you\'re about to do so, ' .
+ 'but such deletions remain kinda scary. ' .
+ 'So, you have to turn on this option before any such deletions ' .
+ 'will ever happen.',
type => 'b',
default => 0
},
diff --git a/editusers.cgi b/editusers.cgi
index 4c2a8a193..87a8b69bd 100755
--- a/editusers.cgi
+++ b/editusers.cgi
@@ -11,23 +11,9 @@
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
-# The Original Code is mozilla.org code.
+# The Original Code is the Bugzilla Bug Tracking System.
#
-# The Initial Developer of the Original Code is Holger
-# Schurig. Portions created by Holger Schurig are
-# Copyright (C) 1999 Holger Schurig. All
-# Rights Reserved.
-#
-# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
-# Dave Miller <justdave@syndicomm.com>
-# Joe Robins <jmrobins@tgix.com>
-# Dan Mosedale <dmose@mozilla.org>
-# Joel Peshkin <bugreport@peshkin.net>
-# Erik Stambaugh <erik@dasbistro.com>
-#
-# Direct any questions on this source code to
-#
-# Holger Schurig <holgerschurig@nikocity.de>
+# Contributor(s): Marc Schumann <wurblzap@gmail.com>
use strict;
use lib ".";
@@ -35,827 +21,735 @@ use lib ".";
require "CGI.pl";
require "globals.pl";
-use Bugzilla;
+use vars qw( $vars );
+
use Bugzilla::User;
use Bugzilla::Constants;
use Bugzilla::Auth;
-# Shut up misguided -w warnings about "used only once". "use vars" just
-# doesn't work for me.
-
-sub sillyness {
- my $zz;
- $zz = $::userid;
-}
-
-my $editall;
-
-
-
-# TestUser: just returns if the specified user does exists
-# CheckUser: same check, optionally emit an error text
-
-sub TestUser ($)
-{
- my $user = shift;
-
- # does the product exist?
- SendSQL("SELECT login_name
- FROM profiles
- WHERE login_name=" . SqlQuote($user));
- return FetchOneColumn();
-}
-
-sub CheckUser ($)
-{
- my $user = shift;
-
- # do we have a user?
- unless ($user) {
- print "Sorry, you haven't specified a user.";
- PutTrailer();
- exit;
- }
-
- unless (TestUser $user) {
- print "Sorry, user '$user' does not exist.";
- PutTrailer();
- exit;
- }
-}
+Bugzilla->login(LOGIN_REQUIRED);
+my $cgi = Bugzilla->cgi();
+my $template = Bugzilla->template();
+my $dbh = Bugzilla->dbh;
+my $user = Bugzilla->user();
+my $userid = $user->id();
+my $editusers = UserInGroup('editusers');
+my $action = $cgi->param('action') || 'search';
+
+# Reject access if there is no sense in continuing.
+$editusers
+ || Bugzilla->user->can_bless()
+ || ThrowUserError("auth_failure", {group => "editusers",
+ reason => "cant_bless",
+ action => "edit",
+ object => "users"});
+print Bugzilla->cgi->header();
-sub EmitElement ($$)
-{
- my ($name, $value) = (@_);
- $value = value_quote($value);
- if ($editall) {
- print qq{<TD><INPUT SIZE=64 MAXLENGTH=255 NAME="$name" VALUE="$value"></TD>\n};
+$vars->{'editusers'} = $editusers;
+mirrorListSelectionValues();
+
+###########################################################################
+if ($action eq 'search') {
+ # Allow to restrict the search to any group the user is allowed to bless.
+ $vars->{'restrictablegroups'} = groupsUserMayBless($user, 'id', 'name');
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'list') {
+ my $matchstr = $cgi->param('matchstr');
+ my $matchtype = $cgi->param('matchtype');
+ my $grouprestrict = $cgi->param('grouprestrict') || '0';
+ my $groupid = $cgi->param('groupid');
+ my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
+ 'FROM profiles';
+ my @bindValues;
+ my $nextCondition;
+
+ if (Param('usevisibilitygroups')) {
+ # Show only users in visible groups.
+ my $visibleGroups = visibleGroupsAsString();
+ $query .= qq{, user_group_map AS ugm
+ WHERE ugm.user_id = profiles.userid
+ AND ugm.isbless = 0
+ AND ugm.group_id IN ($visibleGroups)
+ };
+ $nextCondition = 'AND';
} else {
- print qq{<TD>$value<INPUT TYPE=HIDDEN NAME="$name" VALUE="$value"></TD>\n};
+ if ($grouprestrict eq '1') {
+ $query .= ', user_group_map AS ugm';
+ }
+ $nextCondition = 'WHERE';
}
-}
-
-#
-# Displays the form to edit a user parameters
-#
-
-sub EmitFormElements ($$$$)
-{
- my ($user_id, $user, $realname, $disabledtext) = @_;
-
- print " <TH ALIGN=\"right\">Login name:</TH>\n";
- EmitElement("user", $user);
-
- print "</TR><TR>\n";
- print " <TH ALIGN=\"right\">Real name:</TH>\n";
- EmitElement("realname", $realname);
-
- if ($editall) {
- print "</TR><TR>\n";
- print " <TH ALIGN=\"right\">Password:</TH>\n";
- print qq|
- <TD><INPUT TYPE="PASSWORD" SIZE="16" MAXLENGTH="16" NAME="password" VALUE=""><br>
- (enter new password to change)
- </TD>
- |;
- print "</TR><TR>\n";
-
- print " <TH ALIGN=\"right\">Disable text:</TH>\n";
- print " <TD ROWSPAN=2><TEXTAREA NAME=\"disabledtext\" ROWS=10 COLS=60>" .
- value_quote($disabledtext) . "</TEXTAREA>\n";
- print " </TD>\n";
- print "</TR><TR>\n";
- print " <TD VALIGN=\"top\">If non-empty, then the account will\n";
- print "be disabled, and this text should explain why.</TD>\n";
- }
-
-
- if($user ne "") {
- print "</TR><TR><TH VALIGN=TOP ALIGN=RIGHT>Group Access:</TH><TD><TABLE><TR>";
- SendSQL("SELECT groups.id, groups.name, groups.description, " .
- "MAX(CASE WHEN grant_type = " . GRANT_DIRECT . " THEN 1 ELSE 0 END)," .
- "MAX(CASE WHEN grant_type = " . GRANT_DERIVED . " THEN 1 ELSE 0 END)," .
- "MAX(CASE WHEN grant_type = " . GRANT_REGEXP . " THEN 1 ELSE 0 END)" .
- "FROM groups " .
- "LEFT JOIN user_group_map " .
- "ON user_group_map.group_id = groups.id " .
- "AND isbless = 0 " .
- "AND user_id = $user_id " .
- "GROUP BY groups.name ");
- if (MoreSQLData()) {
- if ($editall) {
- print "<TD COLSPAN=3 ALIGN=LEFT><B>Can turn this bit on for other users</B></TD>\n";
- print "</TR><TR>\n<TD ALIGN=CENTER><B>|</B></TD>\n";
- }
- print "<TD COLSPAN=2 ALIGN=LEFT><B>User is a member of these groups</B></TD>\n";
- while (MoreSQLData()) {
- my ($groupid, $name, $description, $checked, $isderived, $isregexp) = FetchSQLData();
- next unless ($editall || Bugzilla->user->can_bless($name));
- PushGlobalSQLState();
- SendSQL("SELECT user_id " .
- "FROM user_group_map " .
- "WHERE isbless = 1 " .
- "AND user_id = $user_id " .
- "AND group_id = $groupid");
- my ($blchecked) = FetchSQLData() ? 1 : 0;
- SendSQL("SELECT grantor_id FROM user_group_map,
- group_group_map
- WHERE $groupid = grantor_id
- AND user_group_map.user_id = $user_id
- AND user_group_map.isbless = 0
- AND group_group_map.grant_type = " . GROUP_BLESS . "
- AND user_group_map.group_id = member_id");
- my $derivedbless = FetchOneColumn();
- PopGlobalSQLState();
- print "</TR><TR";
- print ' bgcolor=#cccccc' if ($isderived || $isregexp);
- print ">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"oldgroup_$groupid\" VALUE=\"$checked\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"oldbless_$groupid\" VALUE=\"$blchecked\">\n";
- if ($editall) {
- $blchecked = ($blchecked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER>";
- print "[" if $derivedbless;
- print "<INPUT TYPE=CHECKBOX NAME=\"bless_$groupid\" $blchecked VALUE=\"$groupid\">";
- print "]" if $derivedbless;
- print "</TD>\n";
- }
- $checked = ($checked) ? "CHECKED" : "";
- print "<TD ALIGN=CENTER>";
- print '[' if ($isderived);
- print '*' if ($isregexp);
- print "<INPUT TYPE=CHECKBOX NAME=\"group_$groupid\" $checked VALUE=\"$groupid\">";
- print ']' if ($isderived);
- print '*' if ($isregexp);
- print "</TD><TD><B>";
- print ucfirst($name) . "</B>: $description</TD>\n";
- }
+ # Selection by user name.
+ if (defined($matchtype)) {
+ $query .= " $nextCondition profiles.login_name ";
+ if ($matchtype eq 'regexp') {
+ $query .= $dbh->sql_regexp . ' ?';
+ $matchstr = '.' unless $matchstr;
+ } elsif ($matchtype eq 'notregexp') {
+ $query .= $dbh->sql_not_regexp . ' ?';
+ $matchstr = '.' unless $matchstr;
+ } else { # substr or unknown
+ $query .= 'like ?';
+ $matchstr = "%$matchstr%";
}
- print "</TR></TABLE></TD>\n";
+ $nextCondition = 'AND';
+ # We can trick_taint because we use the value in a SELECT only, using
+ # a placeholder.
+ trick_taint($matchstr);
+ push(@bindValues, $matchstr);
}
-}
-
-
-#
-# Displays a text like "a.", "a or b.", "a, b or c.", "a, b, c or d."
-#
-
-sub PutTrailer (@)
-{
- my (@links) = ("Back to the <a href=\"./\">index</a>");
- if($editall) {
- push(@links,
- "<a href=\"editusers.cgi?action=add\">add</a> a new user");
+ # Selection by group.
+ if ($grouprestrict eq '1') {
+ $query .= " $nextCondition profiles.userid = ugm.user_id " .
+ 'AND ugm.group_id = ?';
+ # We can trick_taint because we use the value in a SELECT only, using
+ # a placeholder.
+ trick_taint($groupid);
+ push(@bindValues, $groupid);
}
- push(@links, @_);
-
- my $count = $#links;
- my $num = 0;
- print "<P>\n";
- foreach (@links) {
- print $_;
- if ($num == $count) {
- print ".\n";
+ $query .= ' ORDER BY profiles.login_name';
+
+ $vars->{'users'} = $dbh->selectall_arrayref($query,
+ {'Slice' => {}},
+ @bindValues);
+ $template->process('admin/users/list.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'add') {
+ $editusers || ThrowUserError("auth_failure", {group => "editusers",
+ action => "add",
+ object => "users"});
+
+ $template->process('admin/users/create.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'new') {
+ $editusers || ThrowUserError("auth_failure", {group => "editusers",
+ action => "add",
+ object => "users"});
+
+ my $login = $cgi->param('login');
+ my $password = $cgi->param('password');
+
+ # Cleanups
+ my $realname = trim($cgi->param('name') || '');
+ my $disabledtext = trim($cgi->param('disabledtext') || '');
+
+ # Lock tables during the check+creation session.
+ $dbh->bz_lock_tables('profiles WRITE',
+ 'profiles_activity WRITE',
+ 'namedqueries READ',
+ 'whine_queries READ',
+ 'tokens READ');
+
+ # Validity checks
+ $login || ThrowUserError('user_login_required');
+ CheckEmailSyntax($login);
+ is_available_username($login) || ThrowUserError('account_exists',
+ {'email' => $login});
+ ValidatePassword($password);
+
+ # Login and password are validated now, and realname and disabledtext
+ # are allowed to contain anything
+ trick_taint($login);
+ trick_taint($realname);
+ trick_taint($password);
+ trick_taint($disabledtext);
+
+ insert_new_user($login, $realname, $password, $disabledtext);
+ my $userid = $dbh->bz_last_key('profiles', 'userid');
+ $dbh->bz_unlock_tables();
+ userDataToVars($userid);
+
+ $vars->{'message'} = 'account_created';
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'edit') {
+ my $otherUser = new Bugzilla::User($cgi->param('userid'))
+ || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')});
+ my $otherUserID = $otherUser->id();
+
+ canSeeUser($otherUserID)
+ || ThrowUserError('auth_failure', {reason => "not_visible",
+ action => "modify",
+ object => "user"});
+
+ userDataToVars($otherUserID);
+
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'update') {
+ my $otherUser = new Bugzilla::User($cgi->param('userid'))
+ || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')});
+ my $otherUserID = $otherUser->id();
+ my $logoutNeeded = 0;
+ my @changedFields;
+
+ # Lock tables during the check+update session.
+ $dbh->bz_lock_tables('profiles WRITE',
+ 'profiles_activity WRITE',
+ 'fielddefs READ',
+ 'namedqueries READ',
+ 'whine_queries READ',
+ 'tokens WRITE',
+ 'logincookies WRITE',
+ 'groups READ',
+ 'user_group_map WRITE',
+ 'group_group_map READ');
+
+ canSeeUser($otherUserID)
+ || ThrowUserError('auth_failure', {reason => "not_visible",
+ action => "modify",
+ object => "user"},
+ 'abort');
+
+ # Cleanups
+ my $login = trim($cgi->param('login') || '');
+ my $loginold = $cgi->param('loginold') || '';
+ my $realname = trim($cgi->param('name') || '');
+ my $realnameold = $cgi->param('nameold') || '';
+ my $password = $cgi->param('password') || '';
+ my $disabledtext = trim($cgi->param('disabledtext') || '');
+ my $disabledtextold = $cgi->param('disabledtextold') || '';
+
+ # Update profiles table entry; silently skip doing this if the user
+ # is not authorized.
+ if ($editusers) {
+ my @values;
+
+ if ($login ne $loginold) {
+ # Validate, then trick_taint.
+ $login || ThrowUserError('user_login_required', undef, 'abort');
+ CheckEmailSyntax($login);
+ is_available_username($login) || ThrowUserError('account_exists',
+ {'email' => $login},
+ 'abort');
+ trick_taint($login);
+ push(@changedFields, 'login_name');
+ push(@values, $login);
+ $logoutNeeded = 1;
+
+ # Since we change the login, silently delete any tokens.
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', {}, $otherUserID);
}
- elsif ($num == $count-1) {
- print " or ";
+ if ($realname ne $realnameold) {
+ # The real name may be anything; we use a placeholder for our
+ # INSERT, and we rely on displaying code to FILTER html.
+ trick_taint($realname);
+ push(@changedFields, 'realname');
+ push(@values, $realname);
}
- else {
- print ", ";
+ if ($password) {
+ # Validate, then trick_taint.
+ ValidatePassword($password) if $password;
+ trick_taint($password);
+ push(@changedFields, 'cryptpassword');
+ push(@values, bz_crypt($password));
+ $logoutNeeded = 1;
}
- $num++;
- }
- PutFooter();
-}
-
-
-
-#
-# Preliminary checks:
-#
-
-Bugzilla->login(LOGIN_REQUIRED);
-
-print Bugzilla->cgi->header();
-
-$editall = UserInGroup("editusers");
-
-$editall
- || Bugzilla->user->can_bless
- || ThrowUserError("auth_failure", {group => "editusers",
- reason => "cant_bless",
- action => "edit",
- object => "users"});
-
-
-#
-# often used variables
-#
-my $user = trim($::FORM{user} || '');
-my $action = trim($::FORM{action} || '');
-my $localtrailer = '<a href="editusers.cgi?">edit more users</a>';
-my $candelete = Param('allowuserdeletion');
-
-my $dbh = Bugzilla->dbh;
-
-#
-# action='' -> Ask for match string for users.
-#
-
-unless ($action) {
- PutHeader("Select match string");
- print qq{
-<FORM METHOD=GET ACTION="editusers.cgi">
-<INPUT TYPE=HIDDEN NAME="action" VALUE="list">
-List users with login name matching:
-<INPUT SIZE=32 NAME="matchstr">
-<SELECT NAME="matchtype">
-<OPTION VALUE="substr" SELECTED>case-insensitive substring
-<OPTION VALUE="regexp">case-sensitive regexp
-<OPTION VALUE="notregexp">not (case-sensitive regexp)
-</SELECT>
-<BR>
-<INPUT TYPE=SUBMIT VALUE="Submit">
-</FORM>
-};
- PutTrailer();
- exit;
-}
-
-
-#
-# action='list' -> Show nice list of matching users
-#
-
-if ($action eq 'list') {
- PutHeader("Select user");
- my $query = "";
- my $matchstr = $::FORM{'matchstr'};
- if (exists $::FORM{'matchtype'}) {
- $query = "SELECT login_name,realname,disabledtext " .
- "FROM profiles WHERE login_name ";
- if ($::FORM{'matchtype'} eq 'substr') {
- $query .= "like";
- $matchstr = '%' . $matchstr . '%';
- } elsif ($::FORM{'matchtype'} eq 'regexp') {
- $query .= $dbh->sql_regexp();
- $matchstr = '.'
- unless $matchstr;
- } elsif ($::FORM{'matchtype'} eq 'notregexp') {
- $query .= $dbh->sql_not_regexp();
- $matchstr = '.'
- unless $matchstr;
- } else {
- die "Unknown match type";
- }
- $query .= SqlQuote($matchstr) . " ORDER BY login_name";
- } elsif (exists $::FORM{'group'}) {
- detaint_natural($::FORM{'group'});
- $query = "SELECT DISTINCT login_name,realname,disabledtext " .
- "FROM profiles, user_group_map WHERE profiles.userid = user_group_map.user_id
- AND group_id=" . $::FORM{'group'} . " ORDER BY login_name";
- } else {
- die "Missing parameters";
- }
-
- SendSQL($query);
- my $count = 0;
- my $header = "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0><TR BGCOLOR=\"#6666FF\">
-<TH ALIGN=\"left\">Edit user ...</TH>
-<TH ALIGN=\"left\">Real name</TH>
-";
- if ($candelete) {
- $header .= "<TH ALIGN=\"left\">Action</TH>\n";
- }
- $header .= "</TR>\n";
- print $header;
- while ( MoreSQLData() ) {
- $count++;
- if ($count % 100 == 0) {
- print "</table>$header";
- }
- my ($user, $realname, $disabledtext) = FetchSQLData();
- my $s = "";
- my $e = "";
- if ($disabledtext) {
- $s = '<span class="bz_inactive">';
- $e = '</span>';
+ if ($disabledtext ne $disabledtextold) {
+ # The disable text may be anything; we use a placeholder for our
+ # INSERT, and we rely on displaying code to FILTER html.
+ trick_taint($disabledtext);
+ push(@changedFields, 'disabledtext');
+ push(@values, $disabledtext);
+ $logoutNeeded = 1;
}
- $realname = ($realname ? html_quote($realname) : "<FONT COLOR=\"red\">missing</FONT>");
- print "<TR>\n";
- print " <TD VALIGN=\"top\"><A HREF=\"editusers.cgi?action=edit&user=", url_quote($user), "\"><B>$s", html_quote($user), "$e</B></A></TD>\n";
- print " <TD VALIGN=\"top\">$s$realname$e</TD>\n";
- if ($candelete) {
- print " <TD VALIGN=\"top\"><A HREF=\"editusers.cgi?action=del&user=", url_quote($user), "\">Delete</A></TD>\n";
+ if (@changedFields) {
+ push (@values, $otherUserID);
+ $logoutNeeded && Bugzilla->logout_user_by_id($otherUserID);
+ $dbh->do('UPDATE profiles SET ' .
+ join(' = ?,', @changedFields).' = ? ' .
+ 'WHERE userid = ?',
+ undef, @values);
+ # FIXME: should create profiles_activity entries.
}
- print "</TR>";
- }
- if ($editall) {
- print "<TR>\n";
- my $span = $candelete ? 3 : 2;
- print qq{
-<TD VALIGN="top" COLSPAN=$span ALIGN="right">
- <A HREF=\"editusers.cgi?action=add\">add a new user</A>
-</TD>
-};
- print "</TR>";
}
- print "</TABLE>\n";
- print "$count users found.\n";
-
- PutTrailer($localtrailer);
- exit;
-}
-
-
-
-
-#
-# action='add' -> present form for parameters for new user
-#
-# (next action will be 'new')
-#
-
-if ($action eq 'add') {
- $editall || ThrowUserError("auth_failure", {group => "editusers",
- action => "add",
- object => "users"});
- PutHeader("Add user");
- print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
- print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
-
- EmitFormElements(0, '', '', '');
-
- print "</TR></TABLE>\n<HR>\n";
- print "<INPUT TYPE=SUBMIT VALUE=\"Add\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"new\">\n";
- print "</FORM>";
-
- my $other = $localtrailer;
- $other =~ s/more/other/;
- PutTrailer($other);
- exit;
-}
+ # Update group settings.
+ my $sth_add_mapping = $dbh->prepare(
+ qq{INSERT INTO user_group_map (
+ user_id, group_id, isbless, grant_type
+ ) VALUES (
+ ?, ?, ?, ?
+ )
+ });
+ my $sth_remove_mapping = $dbh->prepare(
+ qq{DELETE FROM user_group_map
+ WHERE user_id = ?
+ AND group_id = ?
+ AND isbless = ?
+ AND grant_type = ?
+ });
+
+ # We need the group names, too -- for display and for profiles_activity.
+ my $groups = $dbh->selectall_hashref('SELECT id, name FROM groups', 'id');
+ my @groupsAddedTo;
+ my @groupsRemovedFrom;
+ my @groupsGrantedRightsToBless;
+ my @groupsDeniedRightsToBless;
+
+ # Regard only groups the user is allowed to bless and skip all others
+ # silently.
+ # FIXME: checking for existence of each user_group_map entry
+ # would allow to display a friendlier error message on page reloads.
+ foreach (@{groupsUserMayBless($user, 'id')}) {
+ my $id = $$_{'id'};
+
+ # Change memberships.
+ my $oldgroupid = $cgi->param("oldgroup_$id") || '0';
+ my $groupid = $cgi->param("group_$id") || '0';
+ if ($groupid ne $oldgroupid) {
+ if ($groupid eq '0') {
+ $sth_remove_mapping->execute(
+ $otherUserID, $id, 0, GRANT_DIRECT);
+ push(@groupsRemovedFrom, $$groups{$id}{'name'});
+ } else {
+ $sth_add_mapping->execute(
+ $otherUserID, $id, 0, GRANT_DIRECT);
+ push(@groupsAddedTo, $$groups{$id}{'name'});
+ }
+ }
-
-#
-# action='new' -> add user entered in the 'action=add' screen
-#
-
-if ($action eq 'new') {
- $editall || ThrowUserError("auth_failure", {group => "editusers",
- action => "add",
- object => "users"});
-
- # Cleanups and valididy checks
- my $realname = trim($::FORM{realname} || '');
- # We don't trim the password since that could falsely lead the user
- # to believe a password with a space was accepted even though a space
- # is an illegal character in a Bugzilla password.
- my $password = $::FORM{'password'};
- my $disabledtext = trim($::FORM{disabledtext} || '');
- my $emailregexp = Param("emailregexp");
-
- PutHeader("Adding new user");
- unless ($user) {
- print "You must enter a name for the new user. Please press\n";
- print "<b>Back</b> and try again.\n";
- PutTrailer($localtrailer);
- exit;
- }
- unless ($user =~ m/$emailregexp/) {
- print "The user name entered must be a valid e-mail address. Please press\n";
- print "<b>Back</b> and try again.\n";
- PutTrailer($localtrailer);
- exit;
- }
- if (!is_available_username($user)) {
- print "The user '$user' does already exist. Please press\n";
- print "<b>Back</b> and try again.\n";
- PutTrailer($localtrailer);
- exit;
+ # Only members of the editusers group may change bless grants.
+ # Skip silently if this is not the case.
+ if ($editusers) {
+ my $oldgroupid = $cgi->param("oldbless_$id") || '0';
+ my $groupid = $cgi->param("bless_$id") || '0';
+ if ($groupid ne $oldgroupid) {
+ if ($groupid eq '0') {
+ $sth_remove_mapping->execute(
+ $otherUserID, $id, 1, GRANT_DIRECT);
+ push(@groupsDeniedRightsToBless, $$groups{$id}{'name'});
+ } else {
+ $sth_add_mapping->execute(
+ $otherUserID, $id, 1, GRANT_DIRECT);
+ push(@groupsGrantedRightsToBless, $$groups{$id}{'name'});
+ }
+ }
+ }
}
- my $passworderror = ValidatePassword($password);
- if ( $passworderror ) {
- print $passworderror;
- PutTrailer($localtrailer);
- exit;
+ if (@groupsAddedTo || @groupsRemovedFrom) {
+ $dbh->do(qq{INSERT INTO profiles_activity (
+ userid, who,
+ profiles_when, fieldid,
+ oldvalue, newvalue
+ ) VALUES (
+ ?, ?, now(), ?, ?, ?
+ )
+ },
+ undef,
+ ($otherUserID, $userid,
+ GetFieldID('bug_group'),
+ join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
+ $dbh->do('UPDATE profiles SET refreshed_when=? WHERE userid = ?',
+ undef, ('1900-01-01 00:00:00', $otherUserID));
}
-
- # Add the new user
- SendSQL("INSERT INTO profiles ( " .
- "login_name, cryptpassword, realname, " .
- "emailflags, disabledtext" .
- " ) VALUES ( " .
- SqlQuote($user) . "," .
- SqlQuote(bz_crypt($password)) . "," .
- SqlQuote($realname) . "," .
- SqlQuote(Bugzilla::Constants::DEFAULT_EMAIL_SETTINGS) . "," .
- SqlQuote($disabledtext) . ")" );
-
- #+++ send e-mail away
-
- print "OK, done.<br>\n";
- my $newuserid = $dbh->bz_last_key('profiles', 'userid');
-
- my $changeduser = new Bugzilla::User($newuserid);
- $changeduser->derive_groups();
- print "To change ${user}'s permissions, go back and " .
- "<a href=\"editusers.cgi?action=edit&user=" . url_quote($user) .
- "\">edit</a> this user.";
- print "<p>\n";
- PutTrailer($localtrailer);
- exit;
-
-}
-
-
-
-#
-# action='del' -> ask if user really wants to delete
-#
-# (next action would be 'delete')
-#
-
-if ($action eq 'del') {
- $candelete || ThrowUserError("users_deletion_disabled");
- $editall || ThrowUserError("auth_failure", {group => "editusers",
+ # FIXME: should create profiles_activity entries for blesser changes.
+
+ $dbh->bz_unlock_tables();
+
+ # FIXME: userDataToVars may be off when editing ourselves.
+ userDataToVars($otherUserID);
+
+ $vars->{'message'} = 'account_updated';
+ $vars->{'loginold'} = $loginold;
+ $vars->{'changed_fields'} = \@changedFields;
+ $vars->{'groups_added_to'} = \@groupsAddedTo;
+ $vars->{'groups_removed_from'} = \@groupsRemovedFrom;
+ $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
+ $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
+ $template->process('admin/users/edit.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'del') {
+ my $otherUser = new Bugzilla::User($cgi->param('userid'))
+ || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')});
+ my $otherUserID = $otherUser->id();
+
+ Param('allowuserdeletion') || ThrowUserError('users_deletion_disabled');
+ $editusers || ThrowUserError('auth_failure', {group => "editusers",
+ action => "delete",
+ object => "users"});
+ canSeeUser($otherUserID) || ThrowUserError('auth_failure',
+ {reason => "not_visible",
action => "delete",
- object => "users"});
- CheckUser($user);
-
- # display some data about the user
- SendSQL("SELECT userid, realname FROM profiles
- WHERE login_name=" . SqlQuote($user));
- my ($thisuserid, $realname) =
- FetchSQLData();
- $realname = ($realname ? html_quote($realname) : "<FONT COLOR=\"red\">missing</FONT>");
-
- PutHeader("Delete user $user");
- print "<TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>\n";
- print "<TR BGCOLOR=\"#6666FF\">\n";
- print " <TH VALIGN=\"top\" ALIGN=\"left\">Part</TH>\n";
- print " <TH VALIGN=\"top\" ALIGN=\"left\">Value</TH>\n";
-
- print "</TR><TR>\n";
- print " <TD VALIGN=\"top\">Login name:</TD>\n";
- print " <TD VALIGN=\"top\">$user</TD>\n";
-
- print "</TR><TR>\n";
- print " <TD VALIGN=\"top\">Real name:</TD>\n";
- print " <TD VALIGN=\"top\">$realname</TD>\n";
-
- print "</TR><TR>\n";
- print " <TD VALIGN=\"top\">Group set:</TD>\n";
- print " <TD VALIGN=\"top\">";
- SendSQL("SELECT name
- FROM groups, user_group_map
- WHERE groups.id = user_group_map.group_id
- AND user_group_map.user_id = $thisuserid
- AND isbless = 0
- ORDER BY name");
- my $found = 0;
- while ( MoreSQLData() ) {
- my ($name) = FetchSQLData();
- print "<br>\n" if $found;
- print ucfirst $name;
- $found = 1;
- }
- print "none" unless $found;
- print "</TD>\n</TR>";
-
-
- # Check if the user is an initialowner
- my $nodelete = '';
-
- SendSQL("SELECT products.name, components.name " .
- "FROM products, components " .
- "WHERE products.id = components.product_id " .
- " AND initialowner=" . login_to_id($user));
- $found = 0;
- while (MoreSQLData()) {
- if ($found) {
- print "<BR>\n";
- } else {
- print "<TR>\n";
- print " <TD VALIGN=\"top\">Initial owner:</TD>\n";
- print " <TD VALIGN=\"top\">";
- }
- my ($product, $component) = FetchSQLData();
- print "<a href=\"editcomponents.cgi?product=", url_quote($product),
- "&component=", url_quote($component),
- "&action=edit\">$product: $component</a>";
- $found = 1;
- $nodelete = 'initial bug owner';
- }
- print "</TD>\n</TR>" if $found;
-
-
- # Check if the user is an initialqacontact
-
- SendSQL("SELECT products.name, components.name " .
- "FROM products, components " .
- "WHERE products.id = components.product_id " .
- " AND initialqacontact=" . login_to_id($user));
- $found = 0;
- while (MoreSQLData()) {
- if ($found) {
- print "<BR>\n";
- } else {
- print "<TR>\n";
- print " <TD VALIGN=\"top\">Initial QA contact:</TD>\n";
- print " <TD VALIGN=\"top\">";
- }
- my ($product, $component) = FetchSQLData();
- print "<a href=\"editcomponents.cgi?product=", url_quote($product),
- "&component=", url_quote($component),
- "&action=edit\">$product: $component</a>";
- $found = 1;
- $nodelete = 'initial QA contact';
+ object => "user"});
+
+ $vars->{'otheruser'} = $otherUser;
+ $vars->{'editcomponents'} = UserInGroup('editcomponents');
+
+ # If the user is initial owner or initial QA contact of a component,
+ # then no deletion is possible.
+ $vars->{'product_responsibilities'} = productResponsibilities($otherUserID);
+
+ # Find other cross references.
+ $vars->{'bugs'} = $dbh->selectrow_array(
+ qq{SELECT COUNT(*)
+ FROM bugs
+ WHERE assigned_to = ? OR
+ qa_contact = ? OR
+ reporter = ?
+ },
+ undef, ($otherUserID, $otherUserID, $otherUserID));
+ $vars->{'cc'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM cc WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'bugs_activity'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
+ undef, $otherUserID);
+ $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
+ undef, $otherUserID);
+ $vars->{'groups'} = $dbh->selectall_arrayref(
+ qq{SELECT name
+ FROM groups, user_group_map
+ WHERE id = group_id
+ AND user_id = ?
+ AND isbless = 0
+ ORDER BY name
+ },
+ {'Slice' => {}}, $otherUserID);
+ $vars->{'longdescs'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM longdescs WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'namedqueries'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM namedqueries WHERE userid = ?',
+ undef, $otherUserID);
+ $vars->{'profiles_activity'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
+ undef, ($otherUserID, $otherUserID));
+ $vars->{'series'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM series WHERE creator = ?',
+ undef, $otherUserID);
+ $vars->{'votes'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM votes WHERE who = ?',
+ undef, $otherUserID);
+ $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM watch WHERE watched = ?',
+ undef, $otherUserID);
+ $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM watch WHERE watcher = ?',
+ undef, $otherUserID);
+ $vars->{'whine_events'} = $dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
+ undef, $otherUserID);
+ $vars->{'whine_schedules'} = $dbh->selectrow_array(
+ qq{SELECT COUNT(distinct eventid)
+ FROM whine_schedules
+ WHERE mailto = ?
+ AND mailto_type = ?
+ },
+ undef, ($otherUserID, MAILTO_USER));
+
+ $template->process('admin/users/confirm-delete.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
+
+###########################################################################
+} elsif ($action eq 'delete') {
+ my $otherUser = new Bugzilla::User($cgi->param('userid'))
+ || ThrowCodeError('invalid_user_id', {'userid' => $cgi->param('userid')});
+ my $otherUserID = $otherUser->id();
+ my $otherUserLogin = $otherUser->login();
+
+ # Lock tables during the check+removal session.
+ # FIXME: if there was some change on these tables after the deletion
+ # confirmation checks, we may do something here we haven't warned
+ # about.
+ $dbh->bz_lock_tables('products READ',
+ 'components READ',
+ 'logincookies WRITE',
+ 'profiles WRITE',
+ 'profiles_activity WRITE',
+ 'groups READ',
+ 'user_group_map WRITE',
+ 'group_group_map READ',
+ 'flags WRITE',
+ 'cc WRITE',
+ 'namedqueries WRITE',
+ 'tokens WRITE',
+ 'votes WRITE',
+ 'watch WRITE',
+ 'series WRITE',
+ 'series_data WRITE',
+ 'whine_schedules WRITE',
+ 'whine_queries WRITE',
+ 'whine_events WRITE');
+
+ Param('allowuserdeletion')
+ || ThrowUserError('users_deletion_disabled', undef, 'abort');
+ $editusers || ThrowUserError('auth_failure',
+ {group => "editusers",
+ action => "delete",
+ object => "users"},
+ 'abort');
+ canSeeUser($otherUserID) || ThrowUserError('auth_failure',
+ {reason => "not_visible",
+ action => "delete",
+ object => "user"},
+ 'abort');
+ productResponsibilities($otherUserID)
+ && ThrowUserError('user_has_responsibility', undef, 'abort');
+
+ Bugzilla->logout_user_by_id($otherUserID);
+
+ # Reference removals.
+ $dbh->do('UPDATE flags set requestee_id = NULL WHERE requestee_id = ?',
+ undef, $otherUserID);
+
+ # Simple deletions in referred tables.
+ $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
+ ($otherUserID, $otherUserID));
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
+ $otherUserID);
+ $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
+ $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
+ ($otherUserID, $otherUserID));
+
+ # More complex deletions in referred tables.
+ my $id;
+
+ # 1) Series
+ my $sth_seriesid = $dbh->prepare(
+ 'SELECT series_id FROM series WHERE creator = ?');
+ my $sth_deleteSeries = $dbh->prepare(
+ 'DELETE FROM series WHERE series_id = ?');
+ my $sth_deleteSeriesData = $dbh->prepare(
+ 'DELETE FROM series_data WHERE series_id = ?');
+
+ $sth_seriesid->execute($otherUserID);
+ while ($id = $sth_seriesid->fetchrow_array()) {
+ $sth_deleteSeriesData->execute($id);
+ $sth_deleteSeries->execute($id);
}
- print "</TD>\n</TR>" if $found;
-
- print "</TABLE>\n";
+ # 2) Whines
+ my $sth_whineidFromSchedules = $dbh->prepare(
+ qq{SELECT eventid FROM whine_schedules
+ WHERE mailto = ? AND mailto_type = ?});
+ my $sth_whineidFromEvents = $dbh->prepare(
+ 'SELECT id FROM whine_events WHERE owner_userid = ?');
+ my $sth_deleteWhineEvent = $dbh->prepare(
+ 'DELETE FROM whine_events WHERE id = ?');
+ my $sth_deleteWhineQuery = $dbh->prepare(
+ 'DELETE FROM whine_queries WHERE eventid = ?');
+ my $sth_deleteWhineSchedule = $dbh->prepare(
+ 'DELETE FROM whine_schedules WHERE eventid = ?');
+
+ $sth_whineidFromSchedules->execute($otherUserID, MAILTO_USER);
+ while ($id = $sth_whineidFromSchedules->fetchrow_array()) {
+ $sth_deleteWhineQuery->execute($id);
+ $sth_deleteWhineSchedule->execute($id);
+ $sth_deleteWhineEvent->execute($id);
+ }
- if ($nodelete) {
- print "<P>You can't delete this user because '$user' is an $nodelete ",
- "for at least one product.";
- PutTrailer($localtrailer);
- exit;
+ $sth_whineidFromEvents->execute($otherUserID);
+ while ($id = $sth_whineidFromEvents->fetchrow_array()) {
+ $sth_deleteWhineQuery->execute($id);
+ $sth_deleteWhineSchedule->execute($id);
+ $sth_deleteWhineEvent->execute($id);
}
+ # Finally, remove the user account itself.
+ $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
- print "<H2>Confirmation</H2>\n";
- print "<P>Do you really want to delete this user?<P>\n";
+ $dbh->bz_unlock_tables();
- print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
- print "<INPUT TYPE=SUBMIT VALUE=\"Yes, delete\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"delete\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"user\" VALUE=\"$user\">\n";
- print "</FORM>";
+ $vars->{'message'} = 'account_deleted';
+ $vars->{'otheruser'}{'login'} = $otherUserLogin;
+ $vars->{'restrictablegroups'} = groupsUserMayBless($user, 'id', 'name');
+ $template->process('admin/users/search.html.tmpl', $vars)
+ || ThrowTemplateError($template->error());
- PutTrailer($localtrailer);
- exit;
+###########################################################################
+} else {
+ $vars->{'action'} = $action;
+ ThrowCodeError('action_unrecognized', $vars);
}
+exit;
+###########################################################################
+# Helpers
+###########################################################################
-#
-# action='delete' -> really delete the user
-#
-
-if ($action eq 'delete') {
- $candelete || ThrowUserError("users_deletion_disabled");
- $editall || ThrowUserError("auth_failure", {group => "editusers",
- action => "delete",
- object => "users"});
- CheckUser($user);
-
- SendSQL("SELECT userid
- FROM profiles
- WHERE login_name=" . SqlQuote($user));
- my $userid = FetchOneColumn();
-
- Bugzilla->logout_user_by_id($userid);
- SendSQL("DELETE FROM profiles
- WHERE login_name=" . SqlQuote($user));
- SendSQL("DELETE FROM user_group_map
- WHERE user_id=" . $userid);
-
- PutHeader("Deleting user");
- print "User deleted.<BR>\n";
- PutTrailer($localtrailer);
- exit;
+# Copy incoming list selection values from CGI params to template variables.
+sub mirrorListSelectionValues {
+ if (defined($cgi->param('matchtype'))) {
+ foreach ('matchstr', 'matchtype', 'grouprestrict', 'groupid') {
+ $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
+ }
+ }
}
+# Give a list of IDs of groups the user can see.
+sub visibleGroupsAsString {
+ return join(', ', @{$user->visible_groups_direct()});
+}
+# Give a list of IDs of groups the user may bless.
+sub groupsUserMayBless {
+ my $user = shift;
+ my $fieldList = join(', ', @_);
+ my $query;
+ my $connector;
+ my @bindValues;
-#
-# action='edit' -> present the user edit from
-#
-# (next action would be 'update')
-#
-
-if ($action eq 'edit') {
- PutHeader("Edit user $user");
- CheckUser($user);
-
- # get data of user
- SendSQL("SELECT userid, realname, disabledtext
- FROM profiles
- WHERE login_name=" . SqlQuote($user));
- my ($thisuserid, $realname, $disabledtext) = FetchSQLData();
+ $user->derive_groups(1);
- if ($thisuserid > 0) {
- # Force groups to be up to date
- my $changeduser = new Bugzilla::User($thisuserid);
- $changeduser->derive_groups();
- }
- print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
- print "<TABLE BORDER=0 CELLPADDING=4 CELLSPACING=0><TR>\n";
-
- EmitFormElements($thisuserid, $user, $realname, $disabledtext);
-
- print "</TR></TABLE>\n";
- print "<INPUT TYPE=HIDDEN NAME=\"userold\" VALUE=\"$user\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"realnameold\" VALUE=\"$realname\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"disabledtextold\" VALUE=\"" .
- value_quote($disabledtext) . "\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"update\">\n";
- print "<INPUT TYPE=SUBMIT VALUE=\"Update\">\n";
- print "<BR>User is a member of any groups shown with a check or grey bar.
- A grey bar indicates indirect membership, either derived from other
- groups (marked with square brackets) or via regular expression
- (marked with '*').<p>
- Square brackets around the bless checkbox indicate the ability
- to bless users (grant them membership in the group) as a result
- of membership in another group.
- <BR>";
-
- print "</FORM>";
- if ($candelete) {
- print "<FORM METHOD=POST ACTION=editusers.cgi>\n";
- print "<INPUT TYPE=SUBMIT VALUE=\"Delete User\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"action\" VALUE=\"del\">\n";
- print "<INPUT TYPE=HIDDEN NAME=\"user\" VALUE=\"$user\">\n";
- print "</FORM>";
+ if ($editusers) {
+ $query = "SELECT DISTINCT $fieldList FROM groups";
+ $connector = 'WHERE';
+ } else {
+ $query = qq{SELECT DISTINCT $fieldList
+ FROM groups, user_group_map AS ugm
+ LEFT JOIN group_group_map AS ggm
+ ON ggm.member_id = ugm.group_id
+ AND ggm.grant_type = ?
+ WHERE user_id = ?
+ AND ((id = group_id AND isbless = 1) OR
+ (id = grantor_id))
+ };
+ @bindValues = (GROUP_BLESS, $userid);
+ $connector = 'AND';
}
- my $x = $localtrailer;
- $x =~ s/more/other/;
- PutTrailer($x);
- exit;
-}
-
-#
-# action='update' -> update the user
-#
-
-if ($action eq 'update') {
- my $userold = trim($::FORM{userold} || '');
- my $realname = trim($::FORM{realname} || '');
- my $realnameold = trim($::FORM{realnameold} || '');
- my $password = $::FORM{password} || '';
- my $disabledtext = trim($::FORM{disabledtext} || '');
- my $disabledtextold = trim($::FORM{disabledtextold} || '');
- my @localtrailers = ($localtrailer);
- $localtrailer = qq|<a href="editusers.cgi?action=edit&user=XXX">edit user again</a>|;
- PutHeader("Updating user $userold" . ($realnameold && " ($realnameold)"));
-
- CheckUser($userold);
- SendSQL("SELECT userid FROM profiles
- WHERE login_name=" . SqlQuote($userold));
- my ($thisuserid) = FetchSQLData();
-
- my $emailregexp = Param("emailregexp");
- unless ($user =~ m/$emailregexp/) {
- print "The user name entered must be a valid e-mail address. Please press\n";
- print "<b>Back</b> and try again.\n";
- PutTrailer($localtrailer);
- exit;
+ # If visibilitygroups are used, restrict the set of groups.
+ if (Param('usevisibilitygroups')) {
+ my $visibleGroups = visibleGroupsAsString();
+ $query .= " $connector id in ($visibleGroups)";
}
- my @grpadd = ();
- my @grpdel = ();
- my $chggrp = 0;
- SendSQL("SELECT id, name FROM groups");
- while (my ($groupid, $name) = FetchSQLData()) {
- next unless ($editall || Bugzilla->user->can_bless($name));
- if ($::FORM{"oldgroup_$groupid"} != ($::FORM{"group_$groupid"} ? 1 : 0)) {
- # group membership changed
- PushGlobalSQLState();
- $chggrp = 1;
- SendSQL("DELETE FROM user_group_map
- WHERE user_id = $thisuserid
- AND group_id = $groupid
- AND isbless = 0
- AND grant_type = " . GRANT_DIRECT);
- if ($::FORM{"group_$groupid"}) {
- SendSQL("INSERT INTO user_group_map
- (user_id, group_id, isbless, grant_type)
- VALUES ($thisuserid, $groupid, 0," . GRANT_DIRECT . ")");
- print "Added user to group $name<BR>\n";
- push(@grpadd, $name);
- } else {
- print "Dropped user from group $name<BR>\n";
- push(@grpdel, $name);
- }
- PopGlobalSQLState();
- }
- if ($editall && ($::FORM{"oldbless_$groupid"} != ($::FORM{"bless_$groupid"} ? 1 : 0))) {
- # group membership changed
- PushGlobalSQLState();
- SendSQL("DELETE FROM user_group_map
- WHERE user_id = $thisuserid
- AND group_id = $groupid
- AND isbless = 1
- AND grant_type = " . GRANT_DIRECT);
- if ($::FORM{"bless_$groupid"}) {
- SendSQL("INSERT INTO user_group_map
- (user_id, group_id, isbless, grant_type)
- VALUES ($thisuserid, $groupid, 1," . GRANT_DIRECT . ")");
- print "Granted user permission to bless group $name<BR>\n";
- } else {
- print "Revoked user's permission to bless group $name<BR>\n";
- }
- PopGlobalSQLState();
-
- }
- }
- my $fieldid = GetFieldID("bug_group");
- if ($chggrp) {
- SendSQL("INSERT INTO profiles_activity " .
- "(userid, who, profiles_when, fieldid, oldvalue, newvalue) " .
- "VALUES " . "($thisuserid, $::userid, now(), $fieldid, " .
- SqlQuote(join(", ",@grpdel)) . ", " .
- SqlQuote(join(", ",@grpadd)) . ")");
- SendSQL("UPDATE profiles SET refreshed_when='1900-01-01 00:00:00' " .
- "WHERE userid = $thisuserid");
- }
+ $query .= ' ORDER BY name';
+ return $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues);
+}
- # Update the database with the user's new password if they changed it.
- if ( $editall && $password ) {
- my $passworderror = ValidatePassword($password);
- if ( !$passworderror ) {
- my $cryptpassword = SqlQuote(bz_crypt($password));
- my $loginname = SqlQuote($userold);
- SendSQL("UPDATE profiles
- SET cryptpassword = $cryptpassword
- WHERE login_name = $loginname");
- SendSQL("SELECT userid
- FROM profiles
- WHERE login_name=" . SqlQuote($userold));
- my $userid = FetchOneColumn();
- Bugzilla->logout_user_by_id($userid);
- print "Updated password.<BR>\n";
- } else {
- print "Did not update password: $passworderror<br>\n";
- }
- }
- if ($editall && $realname ne $realnameold) {
- SendSQL("UPDATE profiles
- SET realname=" . SqlQuote($realname) . "
- WHERE login_name=" . SqlQuote($userold));
- print 'Updated real name to <q>' . html_quote($realname) . "</q>.<BR>\n";
- }
- if ($editall && $disabledtext ne $disabledtextold) {
- SendSQL("UPDATE profiles
- SET disabledtext=" . SqlQuote($disabledtext) . "
- WHERE login_name=" . SqlQuote($userold));
- SendSQL("SELECT userid
- FROM profiles
- WHERE login_name=" . SqlQuote($userold));
- my $userid = FetchOneColumn();
- Bugzilla->logout_user_by_id($userid);
- print "Updated disabled text.<BR>\n";
+# Determine whether the user can see a user. (Checks for existence, too.)
+sub canSeeUser {
+ my $otherUserID = shift;
+ my $query;
+
+ if (Param('usevisibilitygroups')) {
+ my $visibleGroups = visibleGroupsAsString();
+ $query = qq{SELECT COUNT(DISTINCT userid)
+ FROM profiles, user_group_map
+ WHERE userid = ?
+ AND user_id = userid
+ AND isbless = 0
+ AND group_id IN ($visibleGroups)
+ };
+ } else {
+ $query = qq{SELECT COUNT(userid)
+ FROM profiles
+ WHERE userid = ?
+ };
}
- if ($editall && $user ne $userold) {
- unless ($user) {
- print "Sorry, I can't delete the user's name.";
- $userold = url_quote($userold);
- $localtrailer =~ s/XXX/$userold/;
- push @localtrailers, $localtrailer;
- PutTrailer(@localtrailers);
- exit;
- }
- if (TestUser($user)) {
- print "Sorry, user name '$user' is already in use.";
- $userold = url_quote($userold);
- $localtrailer =~ s/XXX/$userold/;
- push @localtrailers, $localtrailer;
- PutTrailer($localtrailer);
- exit;
- }
-
- SendSQL("UPDATE profiles
- SET login_name=" . SqlQuote($user) . "
- WHERE login_name=" . SqlQuote($userold));
+ return $dbh->selectrow_array($query, undef, $otherUserID);
+}
- print q|Updated user's name to <a href="mailto:| .
- url_quote($user) . '">' . html_quote($user) . "</a>.<BR>\n";
+# Retrieve product responsibilities, usable for both display and verification.
+sub productResponsibilities {
+ my $userid = shift;
+ my $h = $dbh->selectall_arrayref(
+ qq{SELECT products.name AS productname,
+ components.name AS componentname,
+ initialowner,
+ initialqacontact
+ FROM products, components
+ WHERE products.id = components.product_id
+ AND ? IN (initialowner, initialqacontact)
+ },
+ {'Slice' => {}}, $userid);
+
+ if (@$h) {
+ return $h;
+ } else {
+ return undef;
}
- my $changeduser = new Bugzilla::User($thisuserid);
- $changeduser->derive_groups();
-
- $user = url_quote($user);
- $localtrailer =~ s/XXX/$user/;
- push @localtrailers, $localtrailer;
- PutTrailer(@localtrailers);
- exit;
}
-
-
-#
-# No valid action found
-#
-
-PutHeader("Error");
-print "I don't have a clue what you want.<BR>\n";
+# Retrieve user data for the user editing form. User creation and user
+# editing code rely on this to call derive_groups().
+sub userDataToVars {
+ my $userid = shift;
+ my $user = new Bugzilla::User($userid);
+ my $query;
+
+ $user->derive_groups();
+
+ $vars->{'otheruser'} = $user;
+ $vars->{'groups'} = groupsUserMayBless($user, 'id', 'name', 'description');
+ $vars->{'disabledtext'} = $dbh->selectrow_array(
+ 'SELECT disabledtext FROM profiles WHERE userid = ?', undef, $userid);
+
+ $vars->{'permissions'} = $dbh->selectall_hashref(
+ qq{SELECT id,
+ COUNT(directmember.group_id) AS directmember,
+ COUNT(regexpmember.group_id) AS regexpmember,
+ COUNT(derivedmember.group_id) AS derivedmember,
+ COUNT(directbless.group_id) AS directbless
+ FROM groups
+ LEFT JOIN user_group_map AS directmember
+ ON directmember.group_id = id
+ AND directmember.user_id = ?
+ AND directmember.isbless = 0
+ AND directmember.grant_type = ?
+ LEFT JOIN user_group_map AS regexpmember
+ ON regexpmember.group_id = id
+ AND regexpmember.user_id = ?
+ AND regexpmember.isbless = 0
+ AND regexpmember.grant_type = ?
+ LEFT JOIN user_group_map AS derivedmember
+ ON derivedmember.group_id = id
+ AND derivedmember.user_id = ?
+ AND derivedmember.isbless = 0
+ AND derivedmember.grant_type = ?
+ LEFT JOIN user_group_map AS directbless
+ ON directbless.group_id = id
+ AND directbless.user_id = ?
+ AND directbless.isbless = 1
+ AND directbless.grant_type = ?
+ GROUP BY id
+ },
+ 'id', undef,
+ ($userid, GRANT_DIRECT,
+ $userid, GRANT_REGEXP,
+ $userid, GRANT_DERIVED,
+ $userid, GRANT_DIRECT));
+
+ # Find indirect bless permission.
+ $query = qq{SELECT groups.id
+ FROM groups, user_group_map AS ugm, group_group_map AS ggm
+ WHERE ugm.user_id = ?
+ AND groups.id = ggm.grantor_id
+ AND ggm.member_id = ugm.group_id
+ AND ugm.isbless = 0
+ AND ggm.grant_type = ?
+ GROUP BY id
+ };
+ foreach (@{$dbh->selectall_arrayref($query, undef, ($userid, GROUP_BLESS))}) {
+ # Merge indirect bless permissions into permission variable.
+ $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
+ }
+}
diff --git a/globals.pl b/globals.pl
index 721f3bc43..e71493f6b 100644
--- a/globals.pl
+++ b/globals.pl
@@ -613,11 +613,11 @@ sub ValidatePassword {
my ($password, $matchpassword) = @_;
if (length($password) < 3) {
- ThrowUserError("password_too_short");
+ ThrowUserError("password_too_short", undef, 'abort');
} elsif (length($password) > 16) {
- ThrowUserError("password_too_long");
+ ThrowUserError("password_too_long", undef, 'abort');
} elsif ((defined $matchpassword) && ($password ne $matchpassword)) {
- ThrowUserError("passwords_dont_match");
+ ThrowUserError("passwords_dont_match", undef, 'abort');
}
}
diff --git a/skins/standard/admin.css b/skins/standard/admin.css
new file mode 100644
index 000000000..1f266b792
--- /dev/null
+++ b/skins/standard/admin.css
@@ -0,0 +1,28 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+ul.warningmessages {
+ background-color: white;
+ border-style: solid;
+ border-width: 1px;
+ border-color: yellow;
+ padding: 1ex 1ex 1ex 4ex;
+}
+
+p.areyoureallyreallysure {
+ color: red;
+ font-size: 120%;
+ font-weight: bold;
+}
diff --git a/skins/standard/editusers.css b/skins/standard/editusers.css
new file mode 100644
index 000000000..a5bf4581f
--- /dev/null
+++ b/skins/standard/editusers.css
@@ -0,0 +1,52 @@
+/* The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Bugzilla Bug Tracking System.
+ *
+ * Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ */
+
+table.main {
+ border-spacing: 1em;
+}
+table.main tr {
+ vertical-align: top;
+ border-top: solid thin black;
+}
+table.main th {
+ text-align: right;
+ white-space: nowrap;
+}
+table.main th,
+table.main td {
+ padding: 0;
+}
+table.main ul {
+ list-style-type: none;
+ padding-left: 0
+}
+
+table.groups {
+ border-spacing: 1px;
+}
+table.groups tr.indirect {
+ background-color: #cccccc;
+}
+table.groups th {
+ text-align: left;
+ padding: 0 0 0 1ex;
+}
+table.groups td {
+ padding: 2px;
+}
+table.groups td.checkbox {
+ text-align: center;
+ white-space: nowrap;
+}
diff --git a/template/en/default/admin/groups/delete.html.tmpl b/template/en/default/admin/groups/delete.html.tmpl
index 842e2c6f1..2c4b70862 100644
--- a/template/en/default/admin/groups/delete.html.tmpl
+++ b/template/en/default/admin/groups/delete.html.tmpl
@@ -56,7 +56,7 @@
<p><b>One or more users belong to this group. You cannot delete
this group while there are users in it.</b>
- <br><a href="editusers.cgi?action=list&group=[% gid FILTER html %]">Show
+ <br><a href="editusers.cgi?action=list&group=[% gid FILTER html %]&grouprestrict=1">Show
me which users</a> - <input type="checkbox" name="removeusers">Remove
all users from this group for me.</p>
[% END %]
diff --git a/template/en/default/admin/users/confirm-delete.html.tmpl b/template/en/default/admin/users/confirm-delete.html.tmpl
new file mode 100644
index 000000000..ece5de7e0
--- /dev/null
+++ b/template/en/default/admin/users/confirm-delete.html.tmpl
@@ -0,0 +1,404 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user
+ # list.
+ # editusers: is viewing user member of editusers?
+ # editcomponents: is viewing user member of editcomponents?
+ # otheruser: Bugzilla::User object of the viewed user.
+ # groups: array of Group names the viewed user is a member
+ # of.
+ # product_responsibilities: list of hashes, one entry per Bugzilla component.
+ # productname: Name of the product.
+ # componentname: Name of the component.
+ # initialowner: User ID of initial owner.
+ # initialqacontact: User ID of initial QA contact.
+ # bugs: number of bugs the viewed user has a role in
+ # bug_activity: number of bugs the viewed user has activity
+ # entries on
+ # cc number of bugs the viewed user is cc list member
+ # of
+ # flags.requestee: number of flags the viewed user is being asked for
+ # flags.setter: number of flags the viewed user has set
+ # longdescs: number of bug comments the viewed user has written
+ # namedqueries: number of named queries the user has created
+ # profiles_activity: number of named queries the user has created
+ # series: number of series the viewed user has created
+ # votes: number of bugs the viewed user has voted on
+ # watch.watched: number of users the viewed user is being watched
+ # by
+ # watch.watcher: number of users the viewed user is watching
+ # whine_events: number of whine events the viewed user has created
+ # whine_schedules: number of whine schedules the viewed user has
+ # created
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Confirm deletion of user $otheruser.login"
+ style_urls = ['skins/standard/admin.css',
+ 'skins/standard/editusers.css']
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+[% responsibilityterms = {
+ 'initialowner' => 'Initial Owner',
+ 'initialqacontact' => 'Initial QA Contact'
+ }
+%]
+
+<table class="main">
+ <tr>
+ <th>Login name:</th>
+ <td>[% otheruser.login FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>Real name:</th>
+ <td>[% otheruser.name FILTER html %]</td>
+ </tr>
+ <tr>
+ <th>Group set:</th>
+ <td>
+ [% IF groups.size %]
+ <ul>
+ [% FOREACH group = groups %]
+ <li>[% group.name FILTER html %]</li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ None
+ [% END %]
+ </td>
+ </tr>
+ [% IF product_responsibilities.size %]
+ <tr>
+ <th>Product responsibilities:</th>
+ <td>
+ <ul>
+ [% FOREACH component = product_responsibilities %]
+ <li>
+ [% andstring = '' %]
+ [% FOREACH responsibility = ['initialowner', 'initialqacontact'] %]
+ [% IF component.$responsibility == userid %]
+ [% andstring %] [% responsibilityterms.$responsibility %]
+ [% andstring = ' and ' %]
+ [% END %]
+ [% END %]
+ for
+ [% IF editcomponents %]
+ <a href="editcomponents.cgi?action=edit&amp;product=
+ [% component.productname FILTER url_quote %]&amp;component=
+ [% component.componentname FILTER url_quote %]">
+ [% END %]
+ [%+ component.productname FILTER html %]:
+ [% component.componentname FILTER html %]
+ [% IF editcomponents %]
+ </a>
+ [% END %]
+ </li>
+ [% END %]
+ </ul>
+ </td>
+ </tr>
+ [% END %]
+</table>
+
+[% IF product_responsibilities.size %]
+ <p>
+ You can't delete this user at this time because
+ [%+ otheruser.login FILTER html %] has got responsibilities for at least
+ one product.
+ </p>
+ <p>
+ [% IF editcomponents %]
+ Change this by clicking the product editing links above,
+ [% ELSE %]
+ For now, you can
+ [% END %]
+[% ELSE %]
+
+ <h2>Confirmation</h2>
+
+ [% IF bugs || bug_activity || cc || flags.requestee || flags.setter ||
+ longdescs || namedqueries || profiles_activity || series || votes ||
+ watch.watched || watch.watcher || whine_events || whine_schedules %]
+ <ul class="warningmessages">
+ [% IF bugs %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?emailassigned_to1=1&amp;emailreporter1=1&amp;emailqa_contact1=1&amp;emailtype1=exact&amp;email1=[% otheruser.login FILTER url_quote %]">is
+ related to
+ [% IF bugs == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ bugs %] [%+ terms.bugs %]
+ [% END %]</a>, by having reported, being assigned to or being
+ the QA contact.
+ If you delete the user account, the [% terms.bugs %] table in the
+ database will be inconsistent, resulting in
+ [% IF bugs == 1 %]
+ this [% terms.bug %]
+ [% ELSE %]
+ these [% terms.bugs %]
+ [% END %]
+ not appearing in [% terms.bug %] lists any more.
+ </li>
+ [% END %]
+ [% IF bugs_activity %]
+ <li>
+ [% otheruser.login FILTER html %] has made
+ [% IF bugs_activity == 1 %]
+ a change on [% terms.abug %]
+ [% ELSE %]
+ changes on [% terms.bugs %]
+ [% END %].
+ If you delete the user account, the [% terms.bugs %] activity table in
+ the database will be inconsistent, resulting in
+ [% IF bugs_activity == 1 %]
+ this change
+ [% ELSE %]
+ these changes
+ [% END %]
+ not showing up in [% terms.bug %] activity logs any more.
+ </li>
+ [% END %]
+ [% IF cc %]
+ <li>
+ [% otheruser.login FILTER html %]
+ <a href="buglist.cgi?emailcc1=1&amp;emailtype1=exact&amp;email1=[% otheruser.login FILTER url_quote %]">is
+ on the CC list of
+ [% IF cc == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ cc %] [%+ terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, it will be removed from these CC
+ lists.
+ </li>
+ [% END %]
+ [% IF flags.requestee %]
+ <li>
+ [% otheruser.login FILTER html %] has been
+ <a href="buglist.cgi?field0-0-0=requestees.login_name&amp;type0-0-0=equals&amp;value0-0-0=[% otheruser.login FILTER url_quote %]">asked
+ to set
+ [% IF flags.requestee == 1 %]
+ a flag
+ [% ELSE %]
+ [% flags.requestee %] flags
+ [% END %]</a>.
+ If you delete the user account,
+ [% IF flags.requestee == 1 %]
+ this flag
+ [% ELSE %]
+ these flags
+ [% END %]
+ will change to be unspecifically requested.
+ </li>
+ [% END %]
+ [% IF flags.setter %]
+ <li>
+ [% otheruser.login FILTER html %] has
+ <a href="buglist.cgi?field0-0-0=setters.login_name&amp;type0-0-0=equals&amp;value0-0-0=[% otheruser.login FILTER url_quote %]">set
+ or requested
+ [% IF flags.setter == 1 %]
+ a flag
+ [% ELSE %]
+ [%+ flags.setter %] flags
+ [% END %]</a>.
+ If you delete the user account, the flags table in the database
+ will be inconsistent, resulting in
+ [% IF flags.setter == 1 %]
+ this flag
+ [% ELSE %]
+ these flags
+ [% END %]
+ not displaying correctly any more.
+ </li>
+ [% END %]
+ [% IF longdescs %]
+ <li>
+ [% otheruser.login FILTER html %] has
+ <a href="buglist.cgi?emaillongdesc1=1&amp;emailtype1=exact&amp;email1=[% otheruser.login FILTER url_quote %]">commented
+ [% IF longdescs == 1 %]
+ once on [% terms.abug %]
+ [% ELSE %]
+ [%+ longdescs %] times on [% terms.bugs %]
+ [% END %]</a>.
+ If you delete the user account, the comments table in the database
+ will be inconsistent, resulting in
+ [% IF longdescs == 1 %]
+ this comment
+ [% ELSE %]
+ these comments
+ [% END %]
+ not being visible any more.
+ </li>
+ [% END %]
+ [% IF namedqueries %]
+ <li>
+ [% otheruser.login FILTER html %]
+ has
+ [% IF namedqueries == 1 %]
+ a named query
+ [% ELSE %]
+ [%+ namedqueries %] named queries
+ [% END %].
+ [% IF namedqueries == 1 %]
+ This named query
+ [% ELSE %]
+ These named queries
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ [% IF profiles_activity %]
+ <li>
+ [% otheruser.login FILTER html %] has made
+ [% IF bugs_activity == 1 %]
+ a change on a other user's profile
+ [% ELSE %]
+ changes on other users' profiles
+ [% END %].
+ If you delete the user account, the user profiles activity table in
+ the database will be inconsistent.
+ </li>
+ [% END %]
+ [% IF series %]
+ <li>
+ [% otheruser.login FILTER html %] has created
+ [% IF series == 1 %]
+ a series
+ [% ELSE %]
+ [%+ series %] series
+ [% END %].
+ [% IF series == 1 %]
+ This series
+ [% ELSE %]
+ These series
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ [% IF votes %]
+ <li>
+ [% otheruser.login FILTER html %] has voted on
+ [% IF votes == 1 %]
+ [%+ terms.abug %]
+ [% ELSE %]
+ [%+ votes %] [%+ terms.bugs %]
+ [% END %].
+ If you delete the user account,
+ [% IF votes == 1 %]
+ this vote
+ [% ELSE %]
+ these votes
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ [% IF watch.watched || watch.watcher %]
+ <li>
+ [% otheruser.login FILTER html %]
+ [% IF watch.watched %]
+ is being watched by
+ [% IF watch.watched == 1 %]
+ a user
+ [% ELSE %]
+ [%+ watch.watched %] users
+ [% END %]
+ [% END %]
+ [% IF watch.watcher %]
+ [%+ 'and' IF watch.watched %]
+ watches
+ [% IF watch.watcher == 1 %]
+ a user
+ [% ELSE %]
+ [%+ watch.watcher %] users
+ [% END %]
+ [% END %].
+ [% IF watch.watched + watch.watcher == 1 %]
+ This watching
+ [% ELSE %]
+ These watchings
+ [% END %]
+ will cease along with the deletion of the user account.
+ </li>
+ [% END %]
+ [% IF whine_events || whine_schedules %]
+ <li>
+ [% otheruser.login FILTER html %]
+ [% IF whine_events %]
+ has scheduled
+ [% IF whine_events == 1 %]
+ a whine
+ [% ELSE %]
+ [%+ whine_events %] whines
+ [% END %]
+ [% END %]
+ [% IF whine_schedules %]
+ [%+ 'and' IF whine_events %]
+ is on the receiving end of
+ [% IF whine_schedules == 1 %]
+ a whine
+ [% ELSE %]
+ [%+ whine_schedules %] whines
+ [% END %]
+ [% END %].
+ [% IF whine_events + whine_schedules == 1 %]
+ This whine
+ [% ELSE %]
+ These whines
+ [% END %]
+ will be deleted along with the user account.
+ </li>
+ [% END %]
+ </ul>
+ <p class="areyoureallyreallysure">
+ Please be aware of the consequences of this before continuing.
+ </p>
+ [% END %]
+
+ <p>Do you really want to delete this user account?</p>
+
+ <form method="post" action="editusers.cgi">
+ <p>
+ <input type="submit" value="Yes, delete" />
+ <input type="hidden" name="action" value="delete" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ [% INCLUDE listselectionhiddenfields %]
+ </p>
+ </form>
+
+ <p>If you do not want to delete the user account at this time,
+[% END %]
+
+ <a href="editusers.cgi?action=edit&amp;userid=[% otheruser.id %]
+ [% INCLUDE listselectionurlparams %]">edit the user</a>,
+ go
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
+ to the user list</a>,
+ [% IF editusers %]
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>,
+ [% END %]
+ or <a href="editusers.cgi">find other users</a>.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/create.html.tmpl b/template/en/default/admin/users/create.html.tmpl
new file mode 100644
index 000000000..42aefbdb7
--- /dev/null
+++ b/template/en/default/admin/users/create.html.tmpl
@@ -0,0 +1,57 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Add user"
+ style_urls = ['skins/standard/editusers.css']
+ onload = "document.forms['f'].login.focus()"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form name="f" method="post" action="editusers.cgi">
+<table class="main">
+ [% PROCESS admin/users/userdata.html.tmpl
+ editform = 0
+ editusers = editusers
+ otheruser = []
+ %]
+</table>
+<p>
+ <input type="submit" value="Add" />
+ <input type="hidden" name="action" value="new" />
+ [% INCLUDE listselectionhiddenfields %]
+</p>
+</form>
+
+<p>
+ You can also <a href="editusers.cgi">find a user</a>
+ [% IF listselectionvalues %],
+ or
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">go
+ back to the user list</a>
+ [% END %].
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/edit.html.tmpl b/template/en/default/admin/users/edit.html.tmpl
new file mode 100644
index 000000000..53ea17f21
--- /dev/null
+++ b/template/en/default/admin/users/edit.html.tmpl
@@ -0,0 +1,154 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # message: message tag specifying a global/messages.html.tmpl
+ # message
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ # otheruser: Bugzilla::User object of viewed user.
+ # groups: array of group information (name, grant type,
+ # canbless) for viewed user.
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Edit user $login"
+ message = message
+ style_urls = ['skins/standard/editusers.css']
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form method="post" action="editusers.cgi">
+<table class="main">
+ [% PROCESS admin/users/userdata.html.tmpl
+ editform = 1
+ editusers = editusers
+ otheruser = otheruser
+ %]
+ [% IF groups.size %]
+ <tr>
+ <th>Group access:</th>
+ <td>
+ <table class="groups">
+ <tr>
+ [% IF editusers %]
+ <th colspan="3">
+ Can turn these [% terms.bits %] on for other users
+ </th>
+ [% END %]
+ </tr>
+ <tr>
+ [% IF editusers %]
+ <td style="text-align: center; font-weight: bold">|</td>
+ [% END %]
+ <th colspan="2">User is a member of these groups</th>
+ </tr>
+ [% FOREACH group = groups %]
+ [% perms = permissions.${group.id} %]
+ <tr class="[% 'in' IF perms.regexpmember || perms.derivedmember %]direct">
+ [% IF editusers %]
+ <td class="checkbox">
+ [% '[' IF perms.indirectbless %]
+ [% %]<input type="checkbox"
+ name="bless_[% group.id %]"
+ value="1"
+ [% ' checked="checked"' IF perms.directbless %] />
+ [% ']' IF perms.indirectbless %]
+ [% %]<input type="hidden" name="oldbless_[% group.id %]"
+ value="[% perms.directbless %]" /></td>
+ [% END %]
+ <td class="checkbox">
+ [% '[' IF perms.derivedmember %]
+ [% '*' IF perms.regexpmember %]
+ [%%]<input type="checkbox"
+ id="group_[% group.id %]"
+ name="group_[% group.id %]"
+ value="1"
+ [% ' checked="checked"' IF perms.directmember %] />
+ [% '*' IF perms.regexpmember %]
+ [% ']' IF perms.derivedmember %]
+ [% %]<input type="hidden" name="oldgroup_[% group.id %]"
+ value="[% perms.directmember %]" /></td>
+ <td class="groupname">
+ <label for="group_[% group.id %]">
+ <strong>[% group.name FILTER html %]:</strong>
+ [%+ group.description FILTER html %]
+ </label>
+ </td>
+ </tr>
+ [% END %]
+ </table>
+ </td>
+ </tr>
+ [% END %]
+</table>
+
+<p>
+ <input type="submit" value="Update" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ <input type="hidden" name="action" value="update" />
+ [% INCLUDE listselectionhiddenfields %]
+</p>
+</form>
+<p>
+ User is a member of any groups shown with a check or grey bar.
+ A grey bar indicates indirect membership, either derived from other
+ groups (marked with square brackets) or via regular expression
+ (marked with '*').
+</p>
+[% IF editusers %]
+ <p>
+ Square brackets around the bless checkbox indicate the ability
+ to bless users (grant them membership in the group) as a result
+ of membership in another group.
+ </p>
+[% END %]
+
+[% IF Param('allowuserdeletion') && editusers %]
+ <form method="post" action="editusers.cgi">
+ <p>
+ <input type="submit" value="Delete User" />
+ <input type="hidden" name="action" value="del" />
+ <input type="hidden" name="userid" value="[% otheruser.id %]" />
+ [% INCLUDE listselectionhiddenfields %]
+ </p>
+ </form>
+[% END %]
+
+<p>
+ You can also
+ [% IF editusers %]
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>
+ [% IF listselectionvalues %],
+ [% END %]
+ [% END %]
+ [% IF listselectionvalues %]
+ go
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">back
+ to the user list</a>,
+ [% END %]
+ [% IF editusers OR listselectionvalues %]
+ or
+ [% END %]
+ <a href="editusers.cgi">find other users</a>.
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/list.html.tmpl b/template/en/default/admin/users/list.html.tmpl
new file mode 100644
index 000000000..9cbd7530d
--- /dev/null
+++ b/template/en/default/admin/users/list.html.tmpl
@@ -0,0 +1,98 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ # editusers: is viewing user member of editusers?
+ # users: list of user information (id, login_name, realname,
+ # disabledtext).
+ #%]
+
+[% PROCESS global/header.html.tmpl
+ title = "Select user"
+ style_urls = ['skins/standard/editusers.css']
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+[% listselectionurlparams = INCLUDE listselectionurlparams %]
+
+[% columns =
+ [{name => 'login_name'
+ heading => 'Edit user...'
+ contentlink => 'editusers.cgi?action=edit&amp;userid=%%userid%%' _
+ listselectionurlparams
+ allow_html_content => 1
+ }
+ {name => 'realname'
+ heading => 'Real name'
+ allow_html_content => 1
+ }
+ ]
+%]
+
+[% IF Param('allowuserdeletion') && editusers %]
+ [% columns.push({heading => 'Action'
+ content => 'Delete'
+ contentlink => 'editusers.cgi?action=del' _
+ '&amp;userid=%%userid%%' _
+ listselectionurlparams
+ }
+ )
+ %]
+[% END %]
+
+[% FOREACH thisuser = users %]
+ [%# We FILTER html here because we need admin/table.html.tmpl to accept HTML
+ # for styling, so we cannot let admin/table.html.tmpl do the FILTER.
+ #%]
+ [% thisuser.login_name = BLOCK %]
+ [% thisuser.login_name FILTER html %]
+ [% END %]
+ [% IF thisuser.realname %]
+ [% thisuser.realname = BLOCK %]
+ [% thisuser.realname FILTER html %]
+ [% END %]
+ [% ELSE %]
+ [% SET thisuser.realname = '<span style="color: red">missing</span>' %]
+ [% END %]
+ [% IF thisuser.disabledtext %]
+ [% thisuser.login_name = "<span class=\"bz_inactive\">$thisuser.login_name</span>" %]
+ [% thisuser.realname = "<span class=\"bz_inactive\">$thisuser.realname</span>" %]
+ [% END %]
+[% END %]
+
+<p>[% users.size %] user[% "s" UNLESS users.size == 1 %] found.</p>
+
+[% PROCESS admin/table.html.tmpl
+ columns = columns
+ data = users
+%]
+
+<p>
+ If you do not wish to modify a user account at this time, you can
+ <a href="editusers.cgi">find other users</a>
+ [% IF editusers %]
+ or
+ <a href="editusers.cgi?action=add[% INCLUDE listselectionurlparams %]">add
+ a new user</a>
+ [% END %].
+</p>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/listselectvars.html.tmpl b/template/en/default/admin/users/listselectvars.html.tmpl
new file mode 100644
index 000000000..781e85a7d
--- /dev/null
+++ b/template/en/default/admin/users/listselectvars.html.tmpl
@@ -0,0 +1,34 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # listselectionvalues: selection values to recreate the current user list.
+ #%]
+
+[% BLOCK listselectionurlparams %]
+ [% FOREACH field = listselectionvalues.keys %]&amp;
+ [% field FILTER url_quote %]=
+ [% listselectionvalues.$field FILTER url_quote %]
+ [% END %]
+[% END %]
+
+[% BLOCK listselectionhiddenfields %]
+ [% FOREACH field = listselectionvalues.keys %]
+ <input type="hidden" name="[% field FILTER html %]"
+ value="[% listselectionvalues.$field FILTER html %]" />
+ [% END %]
+[% END %]
diff --git a/template/en/default/admin/users/search.html.tmpl b/template/en/default/admin/users/search.html.tmpl
new file mode 100644
index 000000000..748670a68
--- /dev/null
+++ b/template/en/default/admin/users/search.html.tmpl
@@ -0,0 +1,70 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # editusers: is viewing user member of editusers?
+ # restrictablegroups: list of groups visible to the user:
+ # id: group id
+ # name: group name
+ #%]
+
+
+[% PROCESS global/header.html.tmpl
+ title = "Search users"
+ style_urls = ['skins/standard/editusers.css']
+ onload = "document.forms['f'].matchstr.focus()"
+%]
+
+[% PROCESS admin/users/listselectvars.html.tmpl
+ listselectionvalues = listselectionvalues
+%]
+
+<form name="f" method="get" action="editusers.cgi">
+<input type="hidden" name="action" value="list" />
+<p><label for="matchstr">List users with login name matching</label>
+<input size="32" name="matchstr" id="matchstr" />
+<select name="matchtype">
+ <option value="substr" selected="selected">case-insensitive substring</option>
+ <option value="regexp">case-insensitive regexp</option>
+ <option value="notregexp">not (case-insensitive regexp)</option>
+</select>
+<input type="submit" value="Search" /></p>
+
+[% IF restrictablegroups.size %]
+ <p><input type="checkbox" name="grouprestrict" value="1" id="grouprestrict" />
+ <label for="grouprestrict">Restrict to users belonging to group</label>
+ <select name="groupid"
+ onchange="document.forms['f'].grouprestrict.checked=true">
+ [% FOREACH group = restrictablegroups %]
+ <option value="[% group.id FILTER html %]">[% group.name FILTER html %]</option>
+ [% END %]
+ </select></p>
+[% END %]
+</form>
+
+[% IF editusers %]
+ <p>
+ You can also <a href="editusers.cgi?action=add">add a new user</a>
+ [%- IF listselectionvalues %],
+ or
+ <a href="editusers.cgi?action=list[% INCLUDE listselectionurlparams %]">show
+ the user list again</a>
+ [%- END %].
+ </p>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/users/userdata.html.tmpl b/template/en/default/admin/users/userdata.html.tmpl
new file mode 100644
index 000000000..43ee627f1
--- /dev/null
+++ b/template/en/default/admin/users/userdata.html.tmpl
@@ -0,0 +1,79 @@
+[%# 1.0@bugzilla.org %]
+[%# The contents of this file are subject to the Mozilla Public
+ # License Version 1.1 (the "License"); you may not use this file
+ # except in compliance with the License. You may obtain a copy of
+ # the License at http://www.mozilla.org/MPL/
+ #
+ # Software distributed under the License is distributed on an "AS
+ # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ # implied. See the License for the specific language governing
+ # rights and limitations under the License.
+ #
+ # The Original Code is the Bugzilla Bug Tracking System.
+ #
+ # Contributor(s): Marc Schumann <wurblzap@gmail.com>
+ #%]
+
+[%# INTERFACE:
+ #
+ # editform: is this an edit form? (It's a create form otherwise)
+ # editusers: is viewing user member of editusers?
+ # otheruser: Bugzilla::User object of user to edit
+ #%]
+
+<tr>
+ <th><label for="login">Login name:</label></th>
+ <td>
+ [% IF editusers %]
+ <input size="64" maxlength="255" name="login"
+ id="login" value="[% otheruser.login FILTER html %]" />
+ [% IF editform %]
+ <input type="hidden" name="loginold"
+ value="[% otheruser.login FILTER html %]" />
+ [% END %]
+ [% ELSE %]
+ [% otheruser.login FILTER html %]
+ [% END %]
+ </td>
+</tr>
+<tr>
+ <th><label for="name">Real name:</label></th>
+ <td>
+ [% IF editusers %]
+ <input size="64" maxlength="255" name="name"
+ id="name" value="[% otheruser.name FILTER html %]" />
+ [% IF editform %]
+ <input type="hidden" name="nameold"
+ value="[% otheruser.name FILTER html %]" />
+ [% END %]
+ [% ELSE %]
+ [% otheruser.name FILTER html %]
+ [% END %]
+ </td>
+</tr>
+[% IF editusers %]
+ <tr>
+ <th><label for="password">Password:</label></th>
+ <td>
+ <input type="password" size="16" maxlength="16" name="password"
+ id="password" value="" />
+ [% IF editform %]<br />
+ (Enter new password to change.)
+ [% END %]
+ </td>
+ </tr>
+ <tr>
+ <th><label for="disabledtext">Disable text:</label></th>
+ <td>
+ <textarea name="disabledtext" rows="10"
+ id="disabledtext"
+ cols="60">[% otheruser.disabledtext FILTER html %]</textarea><br />
+ (If non-empty, then the account will be disabled, and this text should
+ explain why.)
+ [% IF editform %]
+ <input type="hidden" name="disabledtextold"
+ value="[% otheruser.disabledtext FILTER html %]" />
+ [% END %]
+ </td>
+ </tr>
+[% END %]
diff --git a/template/en/default/filterexceptions.pl b/template/en/default/filterexceptions.pl
index 94a4168e2..bcbbcc8a5 100644
--- a/template/en/default/filterexceptions.pl
+++ b/template/en/default/filterexceptions.pl
@@ -581,6 +581,31 @@
'deleted_bug_count'
],
+'admin/users/confirm-delete.html.tmpl' => [
+ 'andstring',
+ 'responsibilityterms.$responsibility',
+ 'bugs',
+ 'cc',
+ 'flags.requestee',
+ 'flags.setter',
+ 'longdescs',
+ 'namedqueries',
+ 'votes',
+ 'series',
+ 'watch.watched',
+ 'watch.watcher',
+ 'whine_events',
+ 'whine_schedules',
+ 'otheruser.id'
+],
+
+'admin/users/edit.html.tmpl' => [
+ 'otheruser.id',
+ 'group.id',
+ 'perms.directbless',
+ 'perms.directmember',
+],
+
'admin/components/edit.html.tmpl' => [
'bug_count'
],
diff --git a/template/en/default/global/code-error.html.tmpl b/template/en/default/global/code-error.html.tmpl
index 3a2a9606e..259be667c 100644
--- a/template/en/default/global/code-error.html.tmpl
+++ b/template/en/default/global/code-error.html.tmpl
@@ -185,7 +185,11 @@
[% ELSIF error == "invalid_keyword_id" %]
The keyword ID <em>[% id FILTER html %]</em> couldn't be
found.
-
+
+ [% ELSIF error == "invalid_user_id" %]
+ [% title = "Invalid User ID" %]
+ There is no user account with ID <em>[% userid FILTER html %]</em>.
+
[% ELSIF error == "missing_bug_id" %]
No [% terms.bug %] ID was given.
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
index ba1476300..5ed2eecf8 100644
--- a/template/en/default/global/messages.html.tmpl
+++ b/template/en/default/global/messages.html.tmpl
@@ -30,7 +30,77 @@
[% message_tag = message %]
[% message = BLOCK %]
- [% IF message_tag == "buglist_adding_field" %]
+ [% IF message_tag == "account_created" %]
+ [% title = "User $otheruser.login created" %]
+ A new user account [% otheruser.login FILTER html %] has been created
+ successfully.
+ [% IF groups.size %]
+ You may want to edit the group settings now, using the form below.
+ [% END %]
+
+ [% ELSIF message_tag == "account_updated" %]
+ [% IF changed_fields.size
+ + groups_added_to.size + groups_removed_from.size
+ + groups_granted_rights_to_bless.size + groups_denied_rights_to_bless.size %]
+ [% title = "User $loginold updated" %]
+ The following changes have been made to the user account
+ [%+ loginold FILTER html %]:
+ <ul>
+ [% FOREACH field = changed_fields %]
+ <li>
+ [% IF field == 'login_name' %]
+ The login is now [% otheruser.login FILTER html %].
+ [% ELSIF field == 'realname' %]
+ The real name has been updated.
+ [% ELSIF field == 'cryptpassword' %]
+ A new password has been set.
+ [% ELSIF field == 'disabledtext' %]
+ The disable text has been modified.
+ [% END %]
+ </li>
+ [% END %]
+ [% IF groups_added_to.size %]
+ <li>
+ The account has been added to the
+ [%+ groups_added_to.join(', ') FILTER html %]
+ group[% 's' IF groups_added_to.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_removed_from.size %]
+ <li>
+ The account has been removed from the
+ [%+ groups_removed_from.join(', ') FILTER html %]
+ group[% 's' IF groups_removed_from.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_granted_rights_to_bless.size %]
+ <li>
+ The account has been granted rights to bless the
+ [%+ groups_granted_rights_to_bless.join(', ') FILTER html %]
+ group[% 's' IF groups_granted_rights_to_bless.size > 1 %].
+ </li>
+ [% END %]
+ [% IF groups_denied_rights_to_bless.size %]
+ <li>
+ The account has been denied rights to bless the
+ [%+ groups_denied_rights_to_bless.join(', ') FILTER html %]
+ group[% 's' IF groups_denied_rights_to_bless.size > 1 %].
+ </li>
+ [% END %]
+ </ul>
+ [% ELSE %]
+ [% title = "User $otheruser.login not changed" %]
+ You didn't request any change on the user account
+ [%+ otheruser.login FILTER html %].
+ [% END %]
+ [%# changed_fields.join(', ') %]
+
+ [% ELSIF message_tag == "account_deleted" %]
+ [% title = "User $otheruser.login deleted" %]
+ The user account [% otheruser.login FILTER html %] has been deleted
+ successfully.
+
+ [% ELSIF message_tag == "buglist_adding_field" %]
[% title = "Adding field to search page..." %]
[% link = "Click here if the page does not redisplay automatically." %]
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index e0d43b5b1..eba6e97c6 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -108,6 +108,8 @@
[% IF group %] and [% END %]
[% IF reason == "cant_bless" %]
you don't have permissions to put people in or out of any group,
+ [% ELSIF reason == "not_visible" %]
+ there are visibility restrictions on certain user groups,
[% END %]
[% END %]
@@ -146,6 +148,8 @@
products
[% ELSIF object == "reports" %]
whine reports
+ [% ELSIF object == "user" %]
+ the user you specified
[% ELSIF object == "users" %]
users
[% ELSIF object == "versions" %]
@@ -1046,6 +1050,16 @@
[% title = "Deletion not activated" %]
Sorry, the deletion of user accounts is not allowed.
+ [% ELSIF error == "user_has_responsibility" %]
+ [% title = "Can't Delete User Account" %]
+ The user you want to delete is set up for roles as initial [% terms.bug %]
+ owner or QA contact for at least one component.
+ For this reason, you cannot delete the account at this time.
+
+ [% ELSIF error == "user_login_required" %]
+ [% title = "Login Name Required" %]
+ You must enter a login name for the new user.
+
[% ELSIF error == "votes_must_be_nonnegative" %]
[% title = "Votes Must Be Non-negative" %]
Only use non-negative numbers for your [% terms.bug %] votes.