Unreviewed. Fix individual benchmark description urls to go to in-depth.html instead...
[WebKit-https.git] / Websites / bugs.webkit.org / editusers.cgi
1 #!/usr/bin/perl -T
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 #
6 # This Source Code Form is "Incompatible With Secondary Licenses", as
7 # defined by the Mozilla Public License, v. 2.0.
8
9 use 5.10.1;
10 use strict;
11 use warnings;
12
13 use lib qw(. lib);
14
15 use Bugzilla;
16 use Bugzilla::Constants;
17 use Bugzilla::Util;
18 use Bugzilla::Error;
19 use Bugzilla::User;
20 use Bugzilla::Bug;
21 use Bugzilla::BugMail;
22 use Bugzilla::Flag;
23 use Bugzilla::Field;
24 use Bugzilla::Group;
25 use Bugzilla::Token;
26 use Bugzilla::Mailer;
27
28 my $user = Bugzilla->login(LOGIN_REQUIRED);
29
30 my $cgi       = Bugzilla->cgi;
31 my $template  = Bugzilla->template;
32 my $dbh       = Bugzilla->dbh;
33 my $userid    = $user->id;
34 my $editusers = $user->in_group('editusers');
35 local our $vars     = {};
36
37 # Reject access if there is no sense in continuing.
38 $editusers
39     || $user->can_bless()
40     || ThrowUserError("auth_failure", {group  => "editusers",
41                                        reason => "cant_bless",
42                                        action => "edit",
43                                        object => "users"});
44
45 print $cgi->header();
46
47 # Common CGI params
48 my $action         = $cgi->param('action') || 'search';
49 my $otherUserID    = $cgi->param('userid');
50 my $otherUserLogin = $cgi->param('user');
51 my $token          = $cgi->param('token');
52
53 # Prefill template vars with data used in all or nearly all templates
54 $vars->{'editusers'} = $editusers;
55 mirrorListSelectionValues();
56
57 Bugzilla::Hook::process('admin_editusers_action',
58     { vars => $vars, user => $user, action => $action });
59
60 ###########################################################################
61 if ($action eq 'search') {
62     # Allow to restrict the search to any group the user is allowed to bless.
63     $vars->{'restrictablegroups'} = $user->bless_groups();
64     $template->process('admin/users/search.html.tmpl', $vars)
65        || ThrowTemplateError($template->error());
66
67 ###########################################################################
68 } elsif ($action eq 'list') {
69     my $matchvalue    = $cgi->param('matchvalue') || '';
70     my $matchstr      = trim($cgi->param('matchstr'));
71     my $matchtype     = $cgi->param('matchtype');
72     my $grouprestrict = $cgi->param('grouprestrict') || '0';
73     # 0 = disabled only, 1 = enabled only, 2 = everyone
74     my $is_enabled    = $cgi->param('is_enabled') // 2;
75     my $query = 'SELECT DISTINCT userid, login_name, realname, is_enabled, ' .
76                 $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date ' .
77                 'FROM profiles';
78     my @bindValues;
79     my $nextCondition;
80     my $visibleGroups;
81
82     # If a group ID is given, make sure it is a valid one.
83     my $group;
84     if ($grouprestrict) {
85         $group = new Bugzilla::Group(scalar $cgi->param('groupid'));
86         $group || ThrowUserError('invalid_group_ID');
87     }
88
89     if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
90         # Show only users in visible groups.
91         $visibleGroups = $user->visible_groups_as_string();
92
93         if ($visibleGroups) {
94             $query .= qq{, user_group_map AS ugm
95                          WHERE ugm.user_id = profiles.userid
96                            AND ugm.isbless = 0
97                            AND ugm.group_id IN ($visibleGroups)
98                         };
99             $nextCondition = 'AND';
100         }
101     } else {
102         $visibleGroups = 1;
103         if ($grouprestrict eq '1') {
104             $query .= qq{, user_group_map AS ugm
105                          WHERE ugm.user_id = profiles.userid
106                            AND ugm.isbless = 0
107                         };
108             $nextCondition = 'AND';
109         }
110         else {
111             $nextCondition = 'WHERE';
112         }
113     }
114
115     if (!$visibleGroups) {
116         $vars->{'users'} = {};
117     }
118     else {
119         # Handle selection by login name, real name, or userid.
120         if (defined($matchtype)) {
121             $query .= " $nextCondition ";
122             my $expr = "";
123             if ($matchvalue eq 'userid') {
124                 if ($matchstr) {
125                     my $stored_matchstr = $matchstr;
126                     detaint_natural($matchstr) 
127                         || ThrowUserError('illegal_user_id', {userid => $stored_matchstr});
128                 }
129                 $expr = "profiles.userid";
130             } elsif ($matchvalue eq 'realname') {
131                 $expr = "profiles.realname";
132             } else {
133                 $expr = "profiles.login_name";
134             }
135
136             if ($matchtype =~ /^(regexp|notregexp|exact)$/) {
137                 $matchstr ||= '.';
138             }
139             else {
140                 $matchstr = '' unless defined $matchstr;
141             }
142             # We can trick_taint because we use the value in a SELECT only,
143             # using a placeholder.
144             trick_taint($matchstr);
145
146             if ($matchtype eq 'regexp') {
147                 $query .= $dbh->sql_regexp($expr, '?', 0, $dbh->quote($matchstr));
148             } elsif ($matchtype eq 'notregexp') {
149                 $query .= $dbh->sql_not_regexp($expr, '?', 0, $dbh->quote($matchstr));
150             } elsif ($matchtype eq 'exact') {
151                 $query .= $expr . ' = ?';
152             } else { # substr or unknown
153                 $query .= $dbh->sql_iposition('?', $expr) . ' > 0';
154             }
155             $nextCondition = 'AND';
156             push(@bindValues, $matchstr);
157         }
158
159         # Handle selection by group.
160         if ($grouprestrict eq '1') {
161             my $grouplist = join(',',
162                 @{Bugzilla::Group->flatten_group_membership($group->id)});
163             $query .= " $nextCondition ugm.group_id IN($grouplist) ";
164         }
165
166         detaint_natural($is_enabled);
167         if ($is_enabled && ($is_enabled == 0 || $is_enabled == 1)) {
168             $query .= " $nextCondition profiles.is_enabled = ?";
169             $nextCondition = 'AND';
170             push(@bindValues, $is_enabled);
171         }
172
173         $query .= ' ORDER BY profiles.login_name';
174
175         $vars->{'users'} = $dbh->selectall_arrayref($query,
176                                                     {'Slice' => {}},
177                                                     @bindValues);
178
179     }
180
181     if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
182         my $match_user_id = $vars->{'users'}[0]->{'userid'};
183         my $match_user = check_user($match_user_id);
184         edit_processing($match_user);
185     } else {
186         $template->process('admin/users/list.html.tmpl', $vars)
187             || ThrowTemplateError($template->error());
188     }
189
190 ###########################################################################
191 } elsif ($action eq 'add') {
192     $editusers || ThrowUserError("auth_failure", {group  => "editusers",
193                                                   action => "add",
194                                                   object => "users"});
195
196     $vars->{'token'} = issue_session_token('add_user');
197
198     $template->process('admin/users/create.html.tmpl', $vars)
199        || ThrowTemplateError($template->error());
200
201 ###########################################################################
202 } elsif ($action eq 'new') {
203     $editusers || ThrowUserError("auth_failure", {group  => "editusers",
204                                                   action => "add",
205                                                   object => "users"});
206
207     check_token_data($token, 'add_user');
208
209     # When e.g. the 'Env' auth method is used, the password field
210     # is not displayed. In that case, set the password to *.
211     my $password = $cgi->param('password');
212     $password = '*' if !defined $password;
213
214     my $new_user = Bugzilla::User->create({
215         login_name    => scalar $cgi->param('login'),
216         cryptpassword => $password,
217         realname      => scalar $cgi->param('name'),
218         disabledtext  => scalar $cgi->param('disabledtext'),
219         disable_mail  => scalar $cgi->param('disable_mail'),
220         extern_id     => scalar $cgi->param('extern_id'),
221         });
222
223     userDataToVars($new_user->id);
224
225     delete_token($token);
226
227     if ($cgi->param('notify_user')) {
228         $vars->{'new_user'} = $new_user;
229         my $message;
230       
231         $template->process('email/new-user-details.txt.tmpl', $vars, \$message)
232             || ThrowTemplateError($template->error());
233         MessageToMTA($message);
234     }
235
236     # We already display the updated page. We have to recreate a token now.
237     $vars->{'token'} = issue_session_token('edit_user');
238     $vars->{'message'} = 'account_created';
239     $template->process('admin/users/edit.html.tmpl', $vars)
240        || ThrowTemplateError($template->error());
241
242 ###########################################################################
243 } elsif ($action eq 'edit') {
244     my $otherUser = check_user($otherUserID, $otherUserLogin);
245     edit_processing($otherUser);
246
247 ###########################################################################
248 } elsif ($action eq 'update') {
249     check_token_data($token, 'edit_user');
250     my $otherUser = check_user($otherUserID, $otherUserLogin);
251     $otherUserID = $otherUser->id;
252
253     # Lock tables during the check+update session.
254     $dbh->bz_start_transaction();
255
256     $editusers || $user->can_see_user($otherUser)
257         || ThrowUserError('auth_failure', {reason => "not_visible",
258                                            action => "modify",
259                                            object => "user"});
260
261     $vars->{'loginold'} = $otherUser->login;
262
263     # Update groups
264     my @group_ids = grep { s/group_// } keys %{ Bugzilla->cgi->Vars };
265     $otherUser->set_groups({ set => \@group_ids });
266
267     # Update profiles table entry; silently skip doing this if the user
268     # is not authorized.
269     my $changes = {};
270     if ($editusers) {
271         $otherUser->set_login($cgi->param('login'));
272         $otherUser->set_name($cgi->param('name'));
273         $otherUser->set_password($cgi->param('password'))
274             if $cgi->param('password');
275         $otherUser->set_disabledtext($cgi->param('disabledtext'));
276         $otherUser->set_disable_mail($cgi->param('disable_mail'));
277         $otherUser->set_extern_id($cgi->param('extern_id'))
278             if defined($cgi->param('extern_id'));
279
280         # Update bless groups
281         my @bless_ids = grep { s/bless_// } keys %{ Bugzilla->cgi->Vars };
282         $otherUser->set_bless_groups({ set => \@bless_ids });
283     }
284     $changes = $otherUser->update();
285
286     $dbh->bz_commit_transaction();
287
288     # XXX: userDataToVars may be off when editing ourselves.
289     userDataToVars($otherUserID);
290     delete_token($token);
291
292     $vars->{'message'} = 'account_updated';
293     $vars->{'changes'} = \%$changes;
294     # We already display the updated page. We have to recreate a token now.
295     $vars->{'token'} = issue_session_token('edit_user');
296
297     $template->process('admin/users/edit.html.tmpl', $vars)
298        || ThrowTemplateError($template->error());
299
300 ###########################################################################
301 } elsif ($action eq 'del') {
302     my $otherUser = check_user($otherUserID, $otherUserLogin);
303     $otherUserID = $otherUser->id;
304
305     Bugzilla->params->{'allowuserdeletion'} 
306         || ThrowUserError('users_deletion_disabled');
307     $editusers || ThrowUserError('auth_failure', {group  => "editusers",
308                                                   action => "delete",
309                                                   object => "users"});
310     $vars->{'otheruser'}      = $otherUser;
311
312     # Find other cross references.
313     $vars->{'attachments'} = $dbh->selectrow_array(
314         'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
315         undef, $otherUserID);
316     $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
317         qq{SELECT COUNT(*)
318            FROM bugs
319            WHERE assigned_to = ? OR qa_contact = ?},
320         undef, ($otherUserID, $otherUserID));
321     $vars->{'reporter'} = $dbh->selectrow_array(
322         'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
323         undef, $otherUserID);
324     $vars->{'cc'} = $dbh->selectrow_array(
325         'SELECT COUNT(*) FROM cc WHERE who = ?',
326         undef, $otherUserID);
327     $vars->{'bugs_activity'} = $dbh->selectrow_array(
328         'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
329         undef, $otherUserID);
330     $vars->{'component_cc'} = $dbh->selectrow_array(
331         'SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
332         undef, $otherUserID);
333     $vars->{'email_setting'} = $dbh->selectrow_array(
334         'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
335         undef, $otherUserID);
336     $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
337         'SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
338         undef, $otherUserID);
339     $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
340         'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
341         undef, $otherUserID);
342     $vars->{'longdescs'} = $dbh->selectrow_array(
343         'SELECT COUNT(*) FROM longdescs WHERE who = ?',
344         undef, $otherUserID);
345     my $namedquery_ids = $dbh->selectcol_arrayref(
346         'SELECT id FROM namedqueries WHERE userid = ?',
347         undef, $otherUserID);
348     $vars->{'namedqueries'} = scalar(@$namedquery_ids);
349     if (scalar(@$namedquery_ids)) {
350         $vars->{'namedquery_group_map'} = $dbh->selectrow_array(
351             'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' .
352             ' (' . join(', ', @$namedquery_ids) . ')');
353     }
354     else {
355         $vars->{'namedquery_group_map'} = 0;
356     }
357     $vars->{'profile_setting'} = $dbh->selectrow_array(
358         'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
359         undef, $otherUserID);
360     $vars->{'profiles_activity'} = $dbh->selectrow_array(
361         'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
362         undef, ($otherUserID, $otherUserID));
363     $vars->{'quips'} = $dbh->selectrow_array(
364         'SELECT COUNT(*) FROM quips WHERE userid = ?',
365         undef, $otherUserID);
366     $vars->{'series'} = $dbh->selectrow_array(
367         'SELECT COUNT(*) FROM series WHERE creator = ?',
368         undef, $otherUserID);
369     $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
370         'SELECT COUNT(*) FROM watch WHERE watched = ?',
371         undef, $otherUserID);
372     $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
373         'SELECT COUNT(*) FROM watch WHERE watcher = ?',
374         undef, $otherUserID);
375     $vars->{'whine_events'} = $dbh->selectrow_array(
376         'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
377         undef, $otherUserID);
378     $vars->{'whine_schedules'} = $dbh->selectrow_array(
379         qq{SELECT COUNT(distinct eventid)
380            FROM whine_schedules
381            WHERE mailto = ?
382            AND mailto_type = ?
383           },
384         undef, ($otherUserID, MAILTO_USER));
385     $vars->{'token'} = issue_session_token('delete_user');
386
387     $template->process('admin/users/confirm-delete.html.tmpl', $vars)
388        || ThrowTemplateError($template->error());
389
390 ###########################################################################
391 } elsif ($action eq 'delete') {
392     check_token_data($token, 'delete_user');
393     my $otherUser = check_user($otherUserID, $otherUserLogin);
394     $otherUserID = $otherUser->id;
395
396     # Cache for user accounts.
397     my %usercache = (0 => new Bugzilla::User());
398     my %updatedbugs;
399
400     # Lock tables during the check+removal session.
401     # XXX: if there was some change on these tables after the deletion
402     #      confirmation checks, we may do something here we haven't warned
403     #      about.
404     $dbh->bz_start_transaction();
405
406     Bugzilla->params->{'allowuserdeletion'}
407         || ThrowUserError('users_deletion_disabled');
408     $editusers || ThrowUserError('auth_failure',
409                                  {group  => "editusers",
410                                   action => "delete",
411                                   object => "users"});
412     @{$otherUser->product_responsibilities()}
413         && ThrowUserError('user_has_responsibility');
414
415     Bugzilla->logout_user($otherUser);
416
417     # Get the named query list so we can delete namedquery_group_map entries.
418     my $namedqueries_as_string = join(', ', @{$dbh->selectcol_arrayref(
419         'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID)});
420
421     # Get the timestamp for LogActivityEntry.
422     my $timestamp = $dbh->selectrow_array('SELECT NOW()');
423
424     # When we update a bug_activity entry, we update the bug timestamp, too.
425     my $sth_set_bug_timestamp =
426         $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
427
428     # Flags
429     my $flag_ids =
430       $dbh->selectcol_arrayref('SELECT id FROM flags WHERE requestee_id = ?',
431                                 undef, $otherUserID);
432
433     my $flags = Bugzilla::Flag->new_from_list($flag_ids);
434
435     $dbh->do('UPDATE flags SET requestee_id = NULL, modification_date = ?
436               WHERE requestee_id = ?', undef, ($timestamp, $otherUserID));
437
438     # We want to remove the requestee but leave the requester alone,
439     # so we have to log these changes manually.
440     my %bugs;
441     push(@{$bugs{$_->bug_id}->{$_->attach_id || 0}}, $_) foreach @$flags;
442     foreach my $bug_id (keys %bugs) {
443         foreach my $attach_id (keys %{$bugs{$bug_id}}) {
444             my @old_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
445             $_->_set_requestee() foreach @{$bugs{$bug_id}->{$attach_id}};
446             my @new_summaries = Bugzilla::Flag->snapshot($bugs{$bug_id}->{$attach_id});
447             my ($removed, $added) =
448                 Bugzilla::Flag->update_activity(\@old_summaries, \@new_summaries);
449             LogActivityEntry($bug_id, 'flagtypes.name', $removed, $added,
450                 $userid, $timestamp, undef, $attach_id);
451         }
452         $sth_set_bug_timestamp->execute($timestamp, $bug_id);
453         $updatedbugs{$bug_id} = 1;
454     }
455
456     # Simple deletions in referred tables.
457     $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
458              $otherUserID);
459     $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
460     $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
461     $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?', undef,
462              $otherUserID);
463     if ($namedqueries_as_string) {
464         $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN ' .
465                  "($namedqueries_as_string)");
466     }
467     $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
468              $otherUserID);
469     $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
470              ($otherUserID, $otherUserID));
471     $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
472     $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
473              $otherUserID);
474     $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
475              ($otherUserID, $otherUserID));
476
477     # Deletions in referred tables which need LogActivityEntry.
478     my $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc WHERE who = ?',
479                                             undef, $otherUserID);
480     $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
481     foreach my $bug_id (@$buglist) {
482         LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
483                          $timestamp);
484         $sth_set_bug_timestamp->execute($timestamp, $bug_id);
485         $updatedbugs{$bug_id} = 1;
486     }
487
488     # Even more complex deletions in referred tables.
489     my $id;
490
491     # 1) Series
492     my $sth_seriesid = $dbh->prepare(
493            'SELECT series_id FROM series WHERE creator = ?');
494     my $sth_deleteSeries = $dbh->prepare(
495            'DELETE FROM series WHERE series_id = ?');
496     my $sth_deleteSeriesData = $dbh->prepare(
497            'DELETE FROM series_data WHERE series_id = ?');
498
499     $sth_seriesid->execute($otherUserID);
500     while ($id = $sth_seriesid->fetchrow_array()) {
501         $sth_deleteSeriesData->execute($id);
502         $sth_deleteSeries->execute($id);
503     }
504
505     # 2) Whines
506     my $sth_whineidFromEvents = $dbh->prepare(
507            'SELECT id FROM whine_events WHERE owner_userid = ?');
508     my $sth_deleteWhineEvent = $dbh->prepare(
509            'DELETE FROM whine_events WHERE id = ?');
510     my $sth_deleteWhineQuery = $dbh->prepare(
511            'DELETE FROM whine_queries WHERE eventid = ?');
512     my $sth_deleteWhineSchedule = $dbh->prepare(
513            'DELETE FROM whine_schedules WHERE eventid = ?');
514
515     $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?',
516              undef, ($otherUserID, MAILTO_USER));
517
518     $sth_whineidFromEvents->execute($otherUserID);
519     while ($id = $sth_whineidFromEvents->fetchrow_array()) {
520         $sth_deleteWhineQuery->execute($id);
521         $sth_deleteWhineSchedule->execute($id);
522         $sth_deleteWhineEvent->execute($id);
523     }
524
525     # 3) Bugs
526     # 3.1) fall back to the default assignee
527     $buglist = $dbh->selectall_arrayref(
528         'SELECT bug_id, initialowner
529          FROM bugs
530          INNER JOIN components ON components.id = bugs.component_id
531          WHERE assigned_to = ?', undef, $otherUserID);
532
533     my $sth_updateAssignee = $dbh->prepare(
534         'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
535
536     foreach my $bug (@$buglist) {
537         my ($bug_id, $default_assignee_id) = @$bug;
538         $sth_updateAssignee->execute($default_assignee_id,
539                                      $timestamp, $bug_id);
540         $updatedbugs{$bug_id} = 1;
541         $default_assignee_id ||= 0;
542         $usercache{$default_assignee_id} ||=
543             new Bugzilla::User($default_assignee_id);
544         LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
545                          $usercache{$default_assignee_id}->login,
546                          $userid, $timestamp);
547     }
548
549     # 3.2) fall back to the default QA contact
550     $buglist = $dbh->selectall_arrayref(
551         'SELECT bug_id, initialqacontact
552          FROM bugs
553          INNER JOIN components ON components.id = bugs.component_id
554          WHERE qa_contact = ?', undef, $otherUserID);
555
556     my $sth_updateQAcontact = $dbh->prepare(
557         'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
558
559     foreach my $bug (@$buglist) {
560         my ($bug_id, $default_qa_contact_id) = @$bug;
561         $sth_updateQAcontact->execute($default_qa_contact_id,
562                                       $timestamp, $bug_id);
563         $updatedbugs{$bug_id} = 1;
564         $default_qa_contact_id ||= 0;
565         $usercache{$default_qa_contact_id} ||=
566             new Bugzilla::User($default_qa_contact_id);
567         LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
568                          $usercache{$default_qa_contact_id}->login,
569                          $userid, $timestamp);
570     }
571
572     # Finally, remove the user account itself.
573     $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
574
575     $dbh->bz_commit_transaction();
576     delete_token($token);
577
578     # It's complex to determine which items now need to be flushed from
579     # memcached.  As user deletion is expected to be a rare event, we just
580     # flush the entire cache when a user is deleted.
581     Bugzilla->memcached->clear_all();
582
583     $vars->{'message'} = 'account_deleted';
584     $vars->{'otheruser'}{'login'} = $otherUser->login;
585     $vars->{'restrictablegroups'} = $user->bless_groups();
586     $template->process('admin/users/search.html.tmpl', $vars)
587        || ThrowTemplateError($template->error());
588
589     # Send mail about what we've done to bugs.
590     # The deleted user is not notified of the changes.
591     foreach (keys(%updatedbugs)) {
592         Bugzilla::BugMail::Send($_, {'changer' => $user} );
593     }
594
595 ###########################################################################
596 } elsif ($action eq 'activity') {
597     my $otherUser = check_user($otherUserID, $otherUserLogin);
598
599     $vars->{'profile_changes'} = $dbh->selectall_arrayref(
600         "SELECT profiles.login_name AS who, " .
601                 $dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when,
602                 fielddefs.name AS what,
603                 profiles_activity.oldvalue AS removed,
604                 profiles_activity.newvalue AS added
605          FROM profiles_activity
606          INNER JOIN profiles ON profiles_activity.who = profiles.userid
607          INNER JOIN fielddefs ON fielddefs.id = profiles_activity.fieldid
608          WHERE profiles_activity.userid = ?
609          ORDER BY profiles_activity.profiles_when",
610         {'Slice' => {}},
611         $otherUser->id);
612
613     $vars->{'otheruser'} = $otherUser;
614
615     $template->process("account/profile-activity.html.tmpl", $vars)
616         || ThrowTemplateError($template->error());
617
618 ###########################################################################
619 } else {
620     ThrowUserError('unknown_action', {action => $action});
621 }
622
623 exit;
624
625 ###########################################################################
626 # Helpers
627 ###########################################################################
628
629 # Try to build a user object using its ID, else its login name, and throw
630 # an error if the user does not exist.
631 sub check_user {
632     my ($otherUserID, $otherUserLogin) = @_;
633
634     my $otherUser;
635     my $vars = {};
636
637     if ($otherUserID) {
638         $otherUser = Bugzilla::User->new($otherUserID);
639         $vars->{'user_id'} = $otherUserID;
640     }
641     elsif ($otherUserLogin) {
642         $otherUser = new Bugzilla::User({ name => $otherUserLogin });
643         $vars->{'user_login'} = $otherUserLogin;
644     }
645     ($otherUser && $otherUser->id) || ThrowUserError('invalid_user', $vars);
646
647     return $otherUser;
648 }
649
650 # Copy incoming list selection values from CGI params to template variables.
651 sub mirrorListSelectionValues {
652     my $cgi = Bugzilla->cgi;
653     if (defined($cgi->param('matchtype'))) {
654         foreach ('matchvalue', 'matchstr', 'matchtype',
655                  'grouprestrict', 'groupid', 'is_enabled')
656         {
657             $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
658         }
659     }
660 }
661
662 # Retrieve user data for the user editing form. User creation and user
663 # editing code rely on this to call derive_groups().
664 sub userDataToVars {
665     my $otheruserid = shift;
666     my $otheruser = new Bugzilla::User($otheruserid);
667     my $query;
668     my $user = Bugzilla->user;
669     my $dbh = Bugzilla->dbh;
670
671     my $grouplist = $otheruser->groups_as_string;
672
673     $vars->{'otheruser'} = $otheruser;
674     $vars->{'groups'} = $user->bless_groups();
675
676     $vars->{'permissions'} = $dbh->selectall_hashref(
677         qq{SELECT id,
678                   COUNT(directmember.group_id) AS directmember,
679                   COUNT(regexpmember.group_id) AS regexpmember,
680                   (CASE WHEN (groups.id IN ($grouplist)
681                               AND COUNT(directmember.group_id) = 0
682                               AND COUNT(regexpmember.group_id) = 0
683                              ) THEN 1 ELSE 0 END) 
684                       AS derivedmember,
685                   COUNT(directbless.group_id) AS directbless
686            FROM groups
687            LEFT JOIN user_group_map AS directmember
688                   ON directmember.group_id = id
689                  AND directmember.user_id = ?
690                  AND directmember.isbless = 0
691                  AND directmember.grant_type = ?
692            LEFT JOIN user_group_map AS regexpmember
693                   ON regexpmember.group_id = id
694                  AND regexpmember.user_id = ?
695                  AND regexpmember.isbless = 0
696                  AND regexpmember.grant_type = ?
697            LEFT JOIN user_group_map AS directbless
698                   ON directbless.group_id = id
699                  AND directbless.user_id = ?
700                  AND directbless.isbless = 1
701                  AND directbless.grant_type = ?
702           } . $dbh->sql_group_by('id'),
703         'id', undef,
704         ($otheruserid, GRANT_DIRECT,
705          $otheruserid, GRANT_REGEXP,
706          $otheruserid, GRANT_DIRECT));
707
708     # Find indirect bless permission.
709     $query = qq{SELECT groups.id
710                 FROM groups, group_group_map AS ggm
711                 WHERE groups.id = ggm.grantor_id
712                   AND ggm.member_id IN ($grouplist)
713                   AND ggm.grant_type = ?
714                } . $dbh->sql_group_by('id');
715     foreach (@{$dbh->selectall_arrayref($query, undef,
716                                         (GROUP_BLESS))}) {
717         # Merge indirect bless permissions into permission variable.
718         $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
719     }
720 }
721
722 sub edit_processing {
723     my $otherUser = shift;
724     my $user = Bugzilla->user;
725     my $template = Bugzilla->template;
726
727     $user->in_group('editusers') || $user->can_see_user($otherUser)
728         || ThrowUserError('auth_failure', {reason => "not_visible",
729                                            action => "modify",
730                                            object => "user"});
731
732     userDataToVars($otherUser->id);
733     $vars->{'token'} = issue_session_token('edit_user');
734
735     $template->process('admin/users/edit.html.tmpl', $vars)
736        || ThrowTemplateError($template->error());
737 }