]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 119485 : Templatise editusers.cgi
authortravis%sedsystems.ca <>
Tue, 1 Mar 2005 04:41:42 +0000 (04:41 +0000)
committertravis%sedsystems.ca <>
Tue, 1 Mar 2005 04:41:42 +0000 (04:41 +0000)
Patch by Marc Schumann <wurblzap@gmail.com>   r=GavinS, mkanat  a=justdave

19 files changed:
Bugzilla/User.pm
CGI.pl
defparams.pl
editusers.cgi
globals.pl
skins/standard/admin.css [new file with mode: 0644]
skins/standard/editusers.css [new file with mode: 0644]
template/en/default/admin/groups/delete.html.tmpl
template/en/default/admin/users/confirm-delete.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/create.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/edit.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/list.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/listselectvars.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/search.html.tmpl [new file with mode: 0644]
template/en/default/admin/users/userdata.html.tmpl [new file with mode: 0644]
template/en/default/filterexceptions.pl
template/en/default/global/code-error.html.tmpl
template/en/default/global/messages.html.tmpl
template/en/default/global/user-error.html.tmpl

index f87f021b900880f9ec62f7b59a37e5552b10ffcd..b17b638d1a84d4cda217442ed2677b0849477beb 100644 (file)
@@ -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 ff2db2febb44b594058253da8701963d8738a951..69ec8f64f8be7ac9107b98f23a2e7f7f153c4906 100644 (file)
--- 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');
     }
 }
 
index 169db12327c090d7bbe00743f6b4708379b0d9d4..d5c46c9f4834703dcb2c6ad8ec82ef9b98e5e0e7 100644 (file)
@@ -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
   },
index 4c2a8a19357d2b1841ea5c70a4c99da0a4d9ad9d..87a8b69bde97e04605ffeb5d67d5905de8b5c530 100755 (executable)
 # 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;
+    }
+}
index 721f3bc4317a0bb7951b70ff8b0aeb4f0ff10482..e71493f6b90612bf2bd99140f1ca5189aa22af9e 100644 (file)
@@ -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 (file)
index 0000000..1f266b7
--- /dev/null
@@ -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 (file)
index 0000000..a5bf458
--- /dev/null
@@ -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;
+}
index 842e2c6f166b66194cbbaa6c941095c9041ce8f1..2c4b7086299a9e1f58dbf429c52291e664014f13 100644 (file)
@@ -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 (file)
index 0000000..ece5de7
--- /dev/null
@@ -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 (file)
index 0000000..42aefbd
--- /dev/null
@@ -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 (file)
index 0000000..53ea17f
--- /dev/null
@@ -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 (file)
index 0000000..9cbd753
--- /dev/null
@@ -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 (file)
index 0000000..781e85a
--- /dev/null
@@ -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 (file)
index 0000000..748670a
--- /dev/null
@@ -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 (file)
index 0000000..43ee627
--- /dev/null
@@ -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 %]
index 94a4168e27c6c18244737048e1ba353b8814d85e..bcbbcc8a5d89da184bca5974be3c1d3474fa52fc 100644 (file)
   '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'
 ],
index 3a2a9606e1e969a062210df50e9973d8ccfbdb54..259be667c8383d48fa8cedfc8956a1368642bf7b 100644 (file)
   [% 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.
     
index ba1476300ed9174579b7d8f58addb884e50512e3..5ed2eecf8dece1708b5b0e084a08c2faa81b1b27 100644 (file)
 [% 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." %]
 
index e0d43b5b14cddde43c40fd07ca00b74c6a42013b..eba6e97c644040099f6a5f8345371f641cbe63c4 100644 (file)
       [% 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 %]
 
       products
     [% ELSIF object == "reports" %]
       whine reports
+    [% ELSIF object == "user" %]
+      the user you specified
     [% ELSIF object == "users" %]
       users
     [% ELSIF object == "versions" %]
     [% 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.