From 2a5d8de847971f7b5476ad6026fcbfcb68813354 Mon Sep 17 00:00:00 2001 From: "travis%sedsystems.ca" <> Date: Tue, 1 Mar 2005 04:41:42 +0000 Subject: Bug 119485 : Templatise editusers.cgi Patch by Marc Schumann r=GavinS, mkanat a=justdave --- Bugzilla/User.pm | 40 +- CGI.pl | 2 +- defparams.pl | 10 +- editusers.cgi | 1468 +++++++++----------- globals.pl | 6 +- skins/standard/admin.css | 28 + skins/standard/editusers.css | 52 + template/en/default/admin/groups/delete.html.tmpl | 2 +- .../default/admin/users/confirm-delete.html.tmpl | 404 ++++++ template/en/default/admin/users/create.html.tmpl | 57 + template/en/default/admin/users/edit.html.tmpl | 154 ++ template/en/default/admin/users/list.html.tmpl | 98 ++ .../default/admin/users/listselectvars.html.tmpl | 34 + template/en/default/admin/users/search.html.tmpl | 70 + template/en/default/admin/users/userdata.html.tmpl | 79 ++ template/en/default/filterexceptions.pl | 25 + template/en/default/global/code-error.html.tmpl | 6 +- template/en/default/global/messages.html.tmpl | 72 +- template/en/default/global/user-error.html.tmpl | 14 + 19 files changed, 1812 insertions(+), 809 deletions(-) create mode 100644 skins/standard/admin.css create mode 100644 skins/standard/editusers.css create mode 100644 template/en/default/admin/users/confirm-delete.html.tmpl create mode 100644 template/en/default/admin/users/create.html.tmpl create mode 100644 template/en/default/admin/users/edit.html.tmpl create mode 100644 template/en/default/admin/users/list.html.tmpl create mode 100644 template/en/default/admin/users/listselectvars.html.tmpl create mode 100644 template/en/default/admin/users/search.html.tmpl create mode 100644 template/en/default/admin/users/userdata.html.tmpl 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 + +Returns the disable text of the user, if any. + =item C Some code modifies the set of stored queries. Because C does @@ -1254,12 +1266,18 @@ called "statically," just like a normal procedural function. =item C -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 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 -# Dave Miller -# Joe Robins -# Dan Mosedale -# Joel Peshkin -# Erik Stambaugh -# -# Direct any questions on this source code to -# -# Holger Schurig +# Contributor(s): Marc Schumann 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{\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{$value\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 " Login name:\n"; - EmitElement("user", $user); - - print "\n"; - print " Real name:\n"; - EmitElement("realname", $realname); - - if ($editall) { - print "\n"; - print " Password:\n"; - print qq| -
- (enter new password to change) - - |; - print "\n"; - - print " Disable text:\n"; - print " \n"; - print " \n"; - print "\n"; - print " If non-empty, then the account will\n"; - print "be disabled, and this text should explain why.\n"; - } - - - if($user ne "") { - print "Group Access:"; - 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 "\n"; - print "\n\n"; - } - print "\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 "\n"; - print "\n"; - print "\n"; - if ($editall) { - $blchecked = ($blchecked) ? "CHECKED" : ""; - print "\n"; - } - $checked = ($checked) ? "CHECKED" : ""; - print "\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 "
Can turn this bit on for other users
|User is a member of these groups
"; - print "[" if $derivedbless; - print ""; - print "]" if $derivedbless; - print ""; - print '[' if ($isderived); - print '*' if ($isregexp); - print ""; - print ']' if ($isderived); - print '*' if ($isregexp); - print ""; - print ucfirst($name) . ": $description
\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 index"); - if($editall) { - push(@links, - "add 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 "

\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 = 'edit more users'; -my $candelete = Param('allowuserdeletion'); - -my $dbh = Bugzilla->dbh; - -# -# action='' -> Ask for match string for users. -# - -unless ($action) { - PutHeader("Select match string"); - print qq{ -

- -List users with login name matching: - - -
- -
-}; - 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 = " - - -"; - if ($candelete) { - $header .= "\n"; - } - $header .= "\n"; - print $header; - while ( MoreSQLData() ) { - $count++; - if ($count % 100 == 0) { - print "
Edit user ...Real nameAction
$header"; - } - my ($user, $realname, $disabledtext) = FetchSQLData(); - my $s = ""; - my $e = ""; - if ($disabledtext) { - $s = ''; - $e = ''; + 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) : "missing"); - print "\n"; - print " $s", html_quote($user), "$e\n"; - print " $s$realname$e\n"; - if ($candelete) { - print " Delete\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 ""; - } - if ($editall) { - print "\n"; - my $span = $candelete ? 3 : 2; - print qq{ - - add a new user - -}; - print ""; } - print "\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 "
\n"; - print "\n"; - - EmitFormElements(0, '', '', ''); - - print "
\n
\n"; - print "\n"; - print "\n"; - print "
"; - - 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 "Back 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 "Back and try again.\n"; - PutTrailer($localtrailer); - exit; - } - if (!is_available_username($user)) { - print "The user '$user' does already exist. Please press\n"; - print "Back 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.
\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 " . - "edit this user."; - print "

\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) : "missing"); - - PutHeader("Delete user $user"); - print "\n"; - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - print "\n"; - print " \n"; - print " \n"; - - - # 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 "
\n"; - } else { - print "\n"; - print " \n"; - print " \n" 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 "
\n"; - } else { - print "\n"; - print " \n"; - print " \n" if $found; - - print "
PartValue
Login name:$user
Real name:$realname
Group set:"; - 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 "
\n" if $found; - print ucfirst $name; - $found = 1; - } - print "none" unless $found; - print "
Initial owner:"; - } - my ($product, $component) = FetchSQLData(); - print "$product: $component"; - $found = 1; - $nodelete = 'initial bug owner'; - } - print "
Initial QA contact:"; - } - my ($product, $component) = FetchSQLData(); - print "$product: $component"; - $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 "
\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 "

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 "

Confirmation

\n"; - print "

Do you really want to delete this user?

\n"; + $dbh->bz_unlock_tables(); - print "

\n"; - print "\n"; - print "\n"; - print "\n"; - print "
"; + $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.
\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 "
\n"; - print "\n"; - - EmitFormElements($thisuserid, $user, $realname, $disabledtext); - - print "
\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "\n"; - print "
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 '*').

- 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. -
"; - - print "

"; - if ($candelete) { - print "
\n"; - print "\n"; - print "\n"; - print "\n"; - print "
"; + 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|edit user again|; - 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 "Back 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
\n"; - push(@grpadd, $name); - } else { - print "Dropped user from group $name
\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
\n"; - } else { - print "Revoked user's permission to bless group $name
\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.
\n"; - } else { - print "Did not update password: $passworderror
\n"; - } - } - if ($editall && $realname ne $realnameold) { - SendSQL("UPDATE profiles - SET realname=" . SqlQuote($realname) . " - WHERE login_name=" . SqlQuote($userold)); - print 'Updated real name to ' . html_quote($realname) . ".
\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.
\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 ' . html_quote($user) . ".
\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.
\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 + */ + +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 + */ + +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 @@

One or more users belong to this group. You cannot delete this group while there are users in it. -
Show +
Show me which users - Remove all users from this group for me.

[% 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 + #%] + +[%# 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' + } +%] + + + + + + + + + + + + + + + [% IF product_responsibilities.size %] + + + + + [% END %] +
Login name:[% otheruser.login FILTER html %]
Real name:[% otheruser.name FILTER html %]
Group set: + [% IF groups.size %] +
    + [% FOREACH group = groups %] +
  • [% group.name FILTER html %]
  • + [% END %] +
+ [% ELSE %] + None + [% END %] +
Product responsibilities: + +
+ +[% IF product_responsibilities.size %] +

+ You can't delete this user at this time because + [%+ otheruser.login FILTER html %] has got responsibilities for at least + one product. +

+

+ [% IF editcomponents %] + Change this by clicking the product editing links above, + [% ELSE %] + For now, you can + [% END %] +[% ELSE %] + +

Confirmation

+ + [% IF bugs || bug_activity || cc || flags.requestee || flags.setter || + longdescs || namedqueries || profiles_activity || series || votes || + watch.watched || watch.watcher || whine_events || whine_schedules %] +
    + [% IF bugs %] +
  • + [% otheruser.login FILTER html %] + is + related to + [% IF bugs == 1 %] + [%+ terms.abug %] + [% ELSE %] + [%+ bugs %] [%+ terms.bugs %] + [% END %], 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. +
  • + [% END %] + [% IF bugs_activity %] +
  • + [% 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. +
  • + [% END %] + [% IF cc %] +
  • + [% otheruser.login FILTER html %] + is + on the CC list of + [% IF cc == 1 %] + [%+ terms.abug %] + [% ELSE %] + [%+ cc %] [%+ terms.bugs %] + [% END %]. + If you delete the user account, it will be removed from these CC + lists. +
  • + [% END %] + [% IF flags.requestee %] +
  • + [% otheruser.login FILTER html %] has been + asked + to set + [% IF flags.requestee == 1 %] + a flag + [% ELSE %] + [% flags.requestee %] flags + [% END %]. + If you delete the user account, + [% IF flags.requestee == 1 %] + this flag + [% ELSE %] + these flags + [% END %] + will change to be unspecifically requested. +
  • + [% END %] + [% IF flags.setter %] +
  • + [% otheruser.login FILTER html %] has + set + or requested + [% IF flags.setter == 1 %] + a flag + [% ELSE %] + [%+ flags.setter %] flags + [% END %]. + 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. +
  • + [% END %] + [% IF longdescs %] +
  • + [% otheruser.login FILTER html %] has + commented + [% IF longdescs == 1 %] + once on [% terms.abug %] + [% ELSE %] + [%+ longdescs %] times on [% terms.bugs %] + [% END %]. + 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. +
  • + [% END %] + [% IF namedqueries %] +
  • + [% 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. +
  • + [% END %] + [% IF profiles_activity %] +
  • + [% 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. +
  • + [% END %] + [% IF series %] +
  • + [% 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. +
  • + [% END %] + [% IF votes %] +
  • + [% 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. +
  • + [% END %] + [% IF watch.watched || watch.watcher %] +
  • + [% 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. +
  • + [% END %] + [% IF whine_events || whine_schedules %] +
  • + [% 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. +
  • + [% END %] +
+

+ Please be aware of the consequences of this before continuing. +

+ [% END %] + +

Do you really want to delete this user account?

+ +
+

+ + + + [% INCLUDE listselectionhiddenfields %] +

+
+ +

If you do not want to delete the user account at this time, +[% END %] + + edit the user, + go + back + to the user list, + [% IF editusers %] + add + a new user, + [% END %] + or find other users. +

+ +[% 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 + #%] + +[%# 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 +%] + +
+ + [% PROCESS admin/users/userdata.html.tmpl + editform = 0 + editusers = editusers + otheruser = [] + %] +
+

+ + + [% INCLUDE listselectionhiddenfields %] +

+
+ +

+ You can also find a user + [% IF listselectionvalues %], + or + go + back to the user list + [% END %]. +

+ +[% 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 + #%] + +[%# 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 +%] + +
+ + [% PROCESS admin/users/userdata.html.tmpl + editform = 1 + editusers = editusers + otheruser = otheruser + %] + [% IF groups.size %] + + + + + [% END %] +
Group access: + + + [% IF editusers %] + + [% END %] + + + [% IF editusers %] + + [% END %] + + + [% FOREACH group = groups %] + [% perms = permissions.${group.id} %] + + [% IF editusers %] + + [% END %] + + + + [% END %] +
+ Can turn these [% terms.bits %] on for other users +
|User is a member of these groups
+ [% '[' IF perms.indirectbless %] + [% %] + [% ']' IF perms.indirectbless %] + [% %] + [% '[' IF perms.derivedmember %] + [% '*' IF perms.regexpmember %] + [%%] + [% '*' IF perms.regexpmember %] + [% ']' IF perms.derivedmember %] + [% %] + +
+
+ +

+ + + + [% INCLUDE listselectionhiddenfields %] +

+
+

+ 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 '*'). +

+[% IF editusers %] +

+ 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. +

+[% END %] + +[% IF Param('allowuserdeletion') && editusers %] +
+

+ + + + [% INCLUDE listselectionhiddenfields %] +

+
+[% END %] + +

+ You can also + [% IF editusers %] + add + a new user + [% IF listselectionvalues %], + [% END %] + [% END %] + [% IF listselectionvalues %] + go + back + to the user list, + [% END %] + [% IF editusers OR listselectionvalues %] + or + [% END %] + find other users. +

+ +[% 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 + #%] + +[%# 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&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' _ + '&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 = 'missing' %] + [% END %] + [% IF thisuser.disabledtext %] + [% thisuser.login_name = "$thisuser.login_name" %] + [% thisuser.realname = "$thisuser.realname" %] + [% END %] +[% END %] + +

[% users.size %] user[% "s" UNLESS users.size == 1 %] found.

+ +[% PROCESS admin/table.html.tmpl + columns = columns + data = users +%] + +

+ If you do not wish to modify a user account at this time, you can + find other users + [% IF editusers %] + or + add + a new user + [% END %]. +

+ +[% 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 + #%] + +[%# INTERFACE: + # + # listselectionvalues: selection values to recreate the current user list. + #%] + +[% BLOCK listselectionurlparams %] + [% FOREACH field = listselectionvalues.keys %]& + [% field FILTER url_quote %]= + [% listselectionvalues.$field FILTER url_quote %] + [% END %] +[% END %] + +[% BLOCK listselectionhiddenfields %] + [% FOREACH field = listselectionvalues.keys %] + + [% 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 + #%] + +[%# 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 +%] + +
+ +

+ + +

+ +[% IF restrictablegroups.size %] +

+ +

+[% END %] +
+ +[% IF editusers %] +

+ You can also add a new user + [%- IF listselectionvalues %], + or + show + the user list again + [%- END %]. +

+[% 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 + #%] + +[%# 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 + #%] + + + + + [% IF editusers %] + + [% IF editform %] + + [% END %] + [% ELSE %] + [% otheruser.login FILTER html %] + [% END %] + + + + + + [% IF editusers %] + + [% IF editform %] + + [% END %] + [% ELSE %] + [% otheruser.name FILTER html %] + [% END %] + + +[% IF editusers %] + + + + + [% IF editform %]
+ (Enter new password to change.) + [% END %] + + + + + +
+ (If non-empty, then the account will be disabled, and this text should + explain why.) + [% IF editform %] + + [% END %] + + +[% 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 [% id FILTER html %] couldn't be found. - + + [% ELSIF error == "invalid_user_id" %] + [% title = "Invalid User ID" %] + There is no user account with ID [% userid FILTER html %]. + [% 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 %]: +
    + [% FOREACH field = changed_fields %] +
  • + [% 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 %] +
  • + [% END %] + [% IF groups_added_to.size %] +
  • + The account has been added to the + [%+ groups_added_to.join(', ') FILTER html %] + group[% 's' IF groups_added_to.size > 1 %]. +
  • + [% END %] + [% IF groups_removed_from.size %] +
  • + The account has been removed from the + [%+ groups_removed_from.join(', ') FILTER html %] + group[% 's' IF groups_removed_from.size > 1 %]. +
  • + [% END %] + [% IF groups_granted_rights_to_bless.size %] +
  • + 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 %]. +
  • + [% END %] + [% IF groups_denied_rights_to_bless.size %] +
  • + 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 %]. +
  • + [% END %] +
+ [% 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. -- cgit v1.2.3-24-g4f1b