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