Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / editusers.cgi
blob924cd6f8a868fb522101fdcde1ecd2e73144b9a5
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
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/
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.
14 # The Original Code is the Bugzilla Bug Tracking System.
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>
23 use strict;
24 use lib qw(. lib);
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;
38 my $user = Bugzilla->login(LOGIN_REQUIRED);
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 = {};
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"});
55 print $cgi->header();
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');
63 # Prefill template vars with data used in all or nearly all templates
64 $vars->{'editusers'} = $editusers;
65 mirrorListSelectionValues();
67 ###########################################################################
68 if ($action eq 'search') {
69 # Allow to restrict the search to any group the user is allowed to bless.
70 $vars->{'restrictablegroups'} = $user->bless_groups();
71 $template->process('admin/users/search.html.tmpl', $vars)
72 || ThrowTemplateError($template->error());
74 ###########################################################################
75 } elsif ($action eq 'list') {
76 my $matchvalue = $cgi->param('matchvalue') || '';
77 my $matchstr = $cgi->param('matchstr');
78 my $matchtype = $cgi->param('matchtype');
79 my $grouprestrict = $cgi->param('grouprestrict') || '0';
80 my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
81 'FROM profiles';
82 my @bindValues;
83 my $nextCondition;
84 my $visibleGroups;
86 # If a group ID is given, make sure it is a valid one.
87 my $group;
88 if ($grouprestrict) {
89 $group = new Bugzilla::Group(scalar $cgi->param('groupid'));
90 $group || ThrowUserError('invalid_group_ID');
93 if (!$editusers && Bugzilla->params->{'usevisibilitygroups'}) {
94 # Show only users in visible groups.
95 $visibleGroups = $user->visible_groups_as_string();
97 if ($visibleGroups) {
98 $query .= qq{, user_group_map AS ugm
99 WHERE ugm.user_id = profiles.userid
100 AND ugm.isbless = 0
101 AND ugm.group_id IN ($visibleGroups)
103 $nextCondition = 'AND';
105 } else {
106 $visibleGroups = 1;
107 if ($grouprestrict eq '1') {
108 $query .= qq{, user_group_map AS ugm
109 WHERE ugm.user_id = profiles.userid
110 AND ugm.isbless = 0
112 $nextCondition = 'AND';
114 else {
115 $nextCondition = 'WHERE';
119 if (!$visibleGroups) {
120 $vars->{'users'} = {};
122 else {
123 # Handle selection by login name, real name, or userid.
124 if (defined($matchtype)) {
125 $query .= " $nextCondition ";
126 my $expr = "";
127 if ($matchvalue eq 'userid') {
128 if ($matchstr) {
129 my $stored_matchstr = $matchstr;
130 detaint_natural($matchstr)
131 || ThrowUserError('illegal_user_id', {userid => $stored_matchstr});
133 $expr = "profiles.userid";
134 } elsif ($matchvalue eq 'realname') {
135 $expr = "profiles.realname";
136 } else {
137 $expr = "profiles.login_name";
139 if ($matchtype eq 'regexp') {
140 $query .= $dbh->sql_regexp($expr, '?');
141 $matchstr = '.' unless $matchstr;
142 } elsif ($matchtype eq 'notregexp') {
143 $query .= $dbh->sql_not_regexp($expr, '?');
144 $matchstr = '.' unless $matchstr;
145 } elsif ($matchtype eq 'exact') {
146 $query .= $expr . ' = ?';
147 $matchstr = '.' unless $matchstr;
148 } else { # substr or unknown
149 $query .= $dbh->sql_istrcmp($expr, '?', 'LIKE');
150 $matchstr = "%$matchstr%";
152 $nextCondition = 'AND';
153 # We can trick_taint because we use the value in a SELECT only,
154 # using a placeholder.
155 trick_taint($matchstr);
156 push(@bindValues, $matchstr);
159 # Handle selection by group.
160 if ($grouprestrict eq '1') {
161 my $grouplist = join(',',
162 @{Bugzilla::User->flatten_group_membership($group->id)});
163 $query .= " $nextCondition ugm.group_id IN($grouplist) ";
165 $query .= ' ORDER BY profiles.login_name';
167 $vars->{'users'} = $dbh->selectall_arrayref($query,
168 {'Slice' => {}},
169 @bindValues);
173 if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) {
174 my $match_user_id = $vars->{'users'}[0]->{'userid'};
175 my $match_user = check_user($match_user_id);
176 edit_processing($match_user);
177 } else {
178 $template->process('admin/users/list.html.tmpl', $vars)
179 || ThrowTemplateError($template->error());
182 ###########################################################################
183 } elsif ($action eq 'add') {
184 $editusers || ThrowUserError("auth_failure", {group => "editusers",
185 action => "add",
186 object => "users"});
188 $vars->{'token'} = issue_session_token('add_user');
190 $template->process('admin/users/create.html.tmpl', $vars)
191 || ThrowTemplateError($template->error());
193 ###########################################################################
194 } elsif ($action eq 'new') {
195 $editusers || ThrowUserError("auth_failure", {group => "editusers",
196 action => "add",
197 object => "users"});
199 check_token_data($token, 'add_user');
201 my $new_user = Bugzilla::User->create({
202 login_name => scalar $cgi->param('login'),
203 cryptpassword => scalar $cgi->param('password'),
204 realname => scalar $cgi->param('name'),
205 disabledtext => scalar $cgi->param('disabledtext'),
206 disable_mail => scalar $cgi->param('disable_mail')});
208 userDataToVars($new_user->id);
210 delete_token($token);
212 # We already display the updated page. We have to recreate a token now.
213 $vars->{'token'} = issue_session_token('edit_user');
214 $vars->{'message'} = 'account_created';
215 $template->process('admin/users/edit.html.tmpl', $vars)
216 || ThrowTemplateError($template->error());
218 ###########################################################################
219 } elsif ($action eq 'edit') {
220 my $otherUser = check_user($otherUserID, $otherUserLogin);
221 edit_processing($otherUser);
223 ###########################################################################
224 } elsif ($action eq 'update') {
225 check_token_data($token, 'edit_user');
226 my $otherUser = check_user($otherUserID, $otherUserLogin);
227 $otherUserID = $otherUser->id;
229 # Lock tables during the check+update session.
230 $dbh->bz_start_transaction();
232 $editusers || $user->can_see_user($otherUser)
233 || ThrowUserError('auth_failure', {reason => "not_visible",
234 action => "modify",
235 object => "user"});
237 $vars->{'loginold'} = $otherUser->login;
239 # Update profiles table entry; silently skip doing this if the user
240 # is not authorized.
241 my %changes;
242 if ($editusers) {
243 $otherUser->set_login($cgi->param('login'));
244 $otherUser->set_name($cgi->param('name'));
245 $otherUser->set_password($cgi->param('password'))
246 if $cgi->param('password');
247 $otherUser->set_disabledtext($cgi->param('disabledtext'));
248 $otherUser->set_disable_mail($cgi->param('disable_mail'));
249 %changes = %{$otherUser->update()};
252 # Update group settings.
253 my $sth_add_mapping = $dbh->prepare(
254 qq{INSERT INTO user_group_map (
255 user_id, group_id, isbless, grant_type
256 ) VALUES (
257 ?, ?, ?, ?
260 my $sth_remove_mapping = $dbh->prepare(
261 qq{DELETE FROM user_group_map
262 WHERE user_id = ?
263 AND group_id = ?
264 AND isbless = ?
265 AND grant_type = ?
268 my @groupsAddedTo;
269 my @groupsRemovedFrom;
270 my @groupsGrantedRightsToBless;
271 my @groupsDeniedRightsToBless;
273 # Regard only groups the user is allowed to bless and skip all others
274 # silently.
275 # XXX: checking for existence of each user_group_map entry
276 # would allow to display a friendlier error message on page reloads.
277 userDataToVars($otherUserID);
278 my $permissions = $vars->{'permissions'};
279 foreach (@{$user->bless_groups()}) {
280 my $id = $$_{'id'};
281 my $name = $$_{'name'};
283 # Change memberships.
284 my $groupid = $cgi->param("group_$id") || 0;
285 if ($groupid != $permissions->{$id}->{'directmember'}) {
286 if (!$groupid) {
287 $sth_remove_mapping->execute(
288 $otherUserID, $id, 0, GRANT_DIRECT);
289 push(@groupsRemovedFrom, $name);
290 } else {
291 $sth_add_mapping->execute(
292 $otherUserID, $id, 0, GRANT_DIRECT);
293 push(@groupsAddedTo, $name);
297 # Only members of the editusers group may change bless grants.
298 # Skip silently if this is not the case.
299 if ($editusers) {
300 my $groupid = $cgi->param("bless_$id") || 0;
301 if ($groupid != $permissions->{$id}->{'directbless'}) {
302 if (!$groupid) {
303 $sth_remove_mapping->execute(
304 $otherUserID, $id, 1, GRANT_DIRECT);
305 push(@groupsDeniedRightsToBless, $name);
306 } else {
307 $sth_add_mapping->execute(
308 $otherUserID, $id, 1, GRANT_DIRECT);
309 push(@groupsGrantedRightsToBless, $name);
314 if (@groupsAddedTo || @groupsRemovedFrom) {
315 $dbh->do(qq{INSERT INTO profiles_activity (
316 userid, who,
317 profiles_when, fieldid,
318 oldvalue, newvalue
319 ) VALUES (
320 ?, ?, now(), ?, ?, ?
323 undef,
324 ($otherUserID, $userid,
325 get_field_id('bug_group'),
326 join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
328 # XXX: should create profiles_activity entries for blesser changes.
330 $dbh->bz_commit_transaction();
332 # XXX: userDataToVars may be off when editing ourselves.
333 userDataToVars($otherUserID);
334 delete_token($token);
336 $vars->{'message'} = 'account_updated';
337 $vars->{'changed_fields'} = [keys %changes];
338 $vars->{'groups_added_to'} = \@groupsAddedTo;
339 $vars->{'groups_removed_from'} = \@groupsRemovedFrom;
340 $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
341 $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
342 # We already display the updated page. We have to recreate a token now.
343 $vars->{'token'} = issue_session_token('edit_user');
345 $template->process('admin/users/edit.html.tmpl', $vars)
346 || ThrowTemplateError($template->error());
348 ###########################################################################
349 } elsif ($action eq 'del') {
350 my $otherUser = check_user($otherUserID, $otherUserLogin);
351 $otherUserID = $otherUser->id;
353 Bugzilla->params->{'allowuserdeletion'}
354 || ThrowUserError('users_deletion_disabled');
355 $editusers || ThrowUserError('auth_failure', {group => "editusers",
356 action => "delete",
357 object => "users"});
358 $vars->{'otheruser'} = $otherUser;
360 # Find other cross references.
361 $vars->{'attachments'} = $dbh->selectrow_array(
362 'SELECT COUNT(*) FROM attachments WHERE submitter_id = ?',
363 undef, $otherUserID);
364 $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
365 qq{SELECT COUNT(*)
366 FROM bugs
367 WHERE assigned_to = ? OR qa_contact = ?},
368 undef, ($otherUserID, $otherUserID));
369 $vars->{'reporter'} = $dbh->selectrow_array(
370 'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
371 undef, $otherUserID);
372 $vars->{'cc'} = $dbh->selectrow_array(
373 'SELECT COUNT(*) FROM cc WHERE who = ?',
374 undef, $otherUserID);
375 $vars->{'bugs_activity'} = $dbh->selectrow_array(
376 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
377 undef, $otherUserID);
378 $vars->{'component_cc'} = $dbh->selectrow_array(
379 'SELECT COUNT(*) FROM component_cc WHERE user_id = ?',
380 undef, $otherUserID);
381 $vars->{'email_setting'} = $dbh->selectrow_array(
382 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
383 undef, $otherUserID);
384 $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
385 'SELECT COUNT(*) FROM flags WHERE requestee_id = ?',
386 undef, $otherUserID);
387 $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
388 'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
389 undef, $otherUserID);
390 $vars->{'longdescs'} = $dbh->selectrow_array(
391 'SELECT COUNT(*) FROM longdescs WHERE who = ?',
392 undef, $otherUserID);
393 my $namedquery_ids = $dbh->selectcol_arrayref(
394 'SELECT id FROM namedqueries WHERE userid = ?',
395 undef, $otherUserID);
396 $vars->{'namedqueries'} = scalar(@$namedquery_ids);
397 if (scalar(@$namedquery_ids)) {
398 $vars->{'namedquery_group_map'} = $dbh->selectrow_array(
399 'SELECT COUNT(*) FROM namedquery_group_map WHERE namedquery_id IN' .
400 ' (' . join(', ', @$namedquery_ids) . ')');
402 else {
403 $vars->{'namedquery_group_map'} = 0;
405 $vars->{'profile_setting'} = $dbh->selectrow_array(
406 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
407 undef, $otherUserID);
408 $vars->{'profiles_activity'} = $dbh->selectrow_array(
409 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
410 undef, ($otherUserID, $otherUserID));
411 $vars->{'quips'} = $dbh->selectrow_array(
412 'SELECT COUNT(*) FROM quips WHERE userid = ?',
413 undef, $otherUserID);
414 $vars->{'series'} = $dbh->selectrow_array(
415 'SELECT COUNT(*) FROM series WHERE creator = ?',
416 undef, $otherUserID);
417 $vars->{'votes'} = $dbh->selectrow_array(
418 'SELECT COUNT(*) FROM votes WHERE who = ?',
419 undef, $otherUserID);
420 $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
421 'SELECT COUNT(*) FROM watch WHERE watched = ?',
422 undef, $otherUserID);
423 $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
424 'SELECT COUNT(*) FROM watch WHERE watcher = ?',
425 undef, $otherUserID);
426 $vars->{'whine_events'} = $dbh->selectrow_array(
427 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
428 undef, $otherUserID);
429 $vars->{'whine_schedules'} = $dbh->selectrow_array(
430 qq{SELECT COUNT(distinct eventid)
431 FROM whine_schedules
432 WHERE mailto = ?
433 AND mailto_type = ?
435 undef, ($otherUserID, MAILTO_USER));
436 $vars->{'token'} = issue_session_token('delete_user');
438 $template->process('admin/users/confirm-delete.html.tmpl', $vars)
439 || ThrowTemplateError($template->error());
441 ###########################################################################
442 } elsif ($action eq 'delete') {
443 check_token_data($token, 'delete_user');
444 my $otherUser = check_user($otherUserID, $otherUserLogin);
445 $otherUserID = $otherUser->id;
447 # Cache for user accounts.
448 my %usercache = (0 => new Bugzilla::User());
449 my %updatedbugs;
451 # Lock tables during the check+removal session.
452 # XXX: if there was some change on these tables after the deletion
453 # confirmation checks, we may do something here we haven't warned
454 # about.
455 $dbh->bz_start_transaction();
457 Bugzilla->params->{'allowuserdeletion'}
458 || ThrowUserError('users_deletion_disabled');
459 $editusers || ThrowUserError('auth_failure',
460 {group => "editusers",
461 action => "delete",
462 object => "users"});
463 @{$otherUser->product_responsibilities()}
464 && ThrowUserError('user_has_responsibility');
466 Bugzilla->logout_user($otherUser);
468 # Get the named query list so we can delete namedquery_group_map entries.
469 my $namedqueries_as_string = join(', ', @{$dbh->selectcol_arrayref(
470 'SELECT id FROM namedqueries WHERE userid = ?', undef, $otherUserID)});
472 # Get the timestamp for LogActivityEntry.
473 my $timestamp = $dbh->selectrow_array('SELECT NOW()');
475 # When we update a bug_activity entry, we update the bug timestamp, too.
476 my $sth_set_bug_timestamp =
477 $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
479 # Reference removals which need LogActivityEntry.
480 my $statement_flagupdate = 'UPDATE flags set requestee_id = NULL
481 WHERE bug_id = ?
482 AND attach_id %s
483 AND requestee_id = ?';
484 my $sth_flagupdate_attachment =
485 $dbh->prepare(sprintf($statement_flagupdate, '= ?'));
486 my $sth_flagupdate_bug =
487 $dbh->prepare(sprintf($statement_flagupdate, 'IS NULL'));
489 my $buglist = $dbh->selectall_arrayref('SELECT DISTINCT bug_id, attach_id
490 FROM flags
491 WHERE requestee_id = ?',
492 undef, $otherUserID);
494 foreach (@$buglist) {
495 my ($bug_id, $attach_id) = @$_;
496 my @old_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
497 if ($attach_id) {
498 $sth_flagupdate_attachment->execute($bug_id, $attach_id, $otherUserID);
500 else {
501 $sth_flagupdate_bug->execute($bug_id, $otherUserID);
503 my @new_summaries = Bugzilla::Flag->snapshot($bug_id, $attach_id);
504 # Let update_activity do all the dirty work, including setting
505 # the bug timestamp.
506 Bugzilla::Flag::update_activity($bug_id, $attach_id, $timestamp,
507 \@old_summaries, \@new_summaries);
508 $updatedbugs{$bug_id} = 1;
511 # Simple deletions in referred tables.
512 $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
513 $otherUserID);
514 $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
515 $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
516 $dbh->do('DELETE FROM namedqueries_link_in_footer WHERE user_id = ?', undef,
517 $otherUserID);
518 if ($namedqueries_as_string) {
519 $dbh->do('DELETE FROM namedquery_group_map WHERE namedquery_id IN ' .
520 "($namedqueries_as_string)");
522 $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
523 $otherUserID);
524 $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
525 ($otherUserID, $otherUserID));
526 $dbh->do('UPDATE quips SET userid = NULL where userid = ?', undef, $otherUserID);
527 $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
528 $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
529 $otherUserID);
530 $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
531 $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
532 ($otherUserID, $otherUserID));
534 # Deletions in referred tables which need LogActivityEntry.
535 $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc
536 WHERE who = ?',
537 undef, $otherUserID);
538 $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
539 foreach my $bug_id (@$buglist) {
540 LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
541 $timestamp);
542 $sth_set_bug_timestamp->execute($timestamp, $bug_id);
543 $updatedbugs{$bug_id} = 1;
546 # Even more complex deletions in referred tables.
547 my $id;
549 # 1) Series
550 my $sth_seriesid = $dbh->prepare(
551 'SELECT series_id FROM series WHERE creator = ?');
552 my $sth_deleteSeries = $dbh->prepare(
553 'DELETE FROM series WHERE series_id = ?');
554 my $sth_deleteSeriesData = $dbh->prepare(
555 'DELETE FROM series_data WHERE series_id = ?');
557 $sth_seriesid->execute($otherUserID);
558 while ($id = $sth_seriesid->fetchrow_array()) {
559 $sth_deleteSeriesData->execute($id);
560 $sth_deleteSeries->execute($id);
563 # 2) Whines
564 my $sth_whineidFromEvents = $dbh->prepare(
565 'SELECT id FROM whine_events WHERE owner_userid = ?');
566 my $sth_deleteWhineEvent = $dbh->prepare(
567 'DELETE FROM whine_events WHERE id = ?');
568 my $sth_deleteWhineQuery = $dbh->prepare(
569 'DELETE FROM whine_queries WHERE eventid = ?');
570 my $sth_deleteWhineSchedule = $dbh->prepare(
571 'DELETE FROM whine_schedules WHERE eventid = ?');
573 $dbh->do('DELETE FROM whine_schedules WHERE mailto = ? AND mailto_type = ?',
574 undef, ($otherUserID, MAILTO_USER));
576 $sth_whineidFromEvents->execute($otherUserID);
577 while ($id = $sth_whineidFromEvents->fetchrow_array()) {
578 $sth_deleteWhineQuery->execute($id);
579 $sth_deleteWhineSchedule->execute($id);
580 $sth_deleteWhineEvent->execute($id);
583 # 3) Bugs
584 # 3.1) fall back to the default assignee
585 $buglist = $dbh->selectall_arrayref(
586 'SELECT bug_id, initialowner
587 FROM bugs
588 INNER JOIN components ON components.id = bugs.component_id
589 WHERE assigned_to = ?', undef, $otherUserID);
591 my $sth_updateAssignee = $dbh->prepare(
592 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
594 foreach my $bug (@$buglist) {
595 my ($bug_id, $default_assignee_id) = @$bug;
596 $sth_updateAssignee->execute($default_assignee_id,
597 $timestamp, $bug_id);
598 $updatedbugs{$bug_id} = 1;
599 $default_assignee_id ||= 0;
600 $usercache{$default_assignee_id} ||=
601 new Bugzilla::User($default_assignee_id);
602 LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
603 $usercache{$default_assignee_id}->login,
604 $userid, $timestamp);
607 # 3.2) fall back to the default QA contact
608 $buglist = $dbh->selectall_arrayref(
609 'SELECT bug_id, initialqacontact
610 FROM bugs
611 INNER JOIN components ON components.id = bugs.component_id
612 WHERE qa_contact = ?', undef, $otherUserID);
614 my $sth_updateQAcontact = $dbh->prepare(
615 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
617 foreach my $bug (@$buglist) {
618 my ($bug_id, $default_qa_contact_id) = @$bug;
619 $sth_updateQAcontact->execute($default_qa_contact_id,
620 $timestamp, $bug_id);
621 $updatedbugs{$bug_id} = 1;
622 $default_qa_contact_id ||= 0;
623 $usercache{$default_qa_contact_id} ||=
624 new Bugzilla::User($default_qa_contact_id);
625 LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
626 $usercache{$default_qa_contact_id}->login,
627 $userid, $timestamp);
630 # Finally, remove the user account itself.
631 $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
633 $dbh->bz_commit_transaction();
634 delete_token($token);
636 $vars->{'message'} = 'account_deleted';
637 $vars->{'otheruser'}{'login'} = $otherUser->login;
638 $vars->{'restrictablegroups'} = $user->bless_groups();
639 $template->process('admin/users/search.html.tmpl', $vars)
640 || ThrowTemplateError($template->error());
642 # Send mail about what we've done to bugs.
643 # The deleted user is not notified of the changes.
644 foreach (keys(%updatedbugs)) {
645 Bugzilla::BugMail::Send($_, {'changer' => $user->login} );
648 ###########################################################################
649 } elsif ($action eq 'activity') {
650 my $otherUser = check_user($otherUserID, $otherUserLogin);
652 $vars->{'profile_changes'} = $dbh->selectall_arrayref(
653 "SELECT profiles.login_name AS who, " .
654 $dbh->sql_date_format('profiles_activity.profiles_when') . " AS activity_when,
655 fielddefs.description AS what,
656 profiles_activity.oldvalue AS removed,
657 profiles_activity.newvalue AS added
658 FROM profiles_activity
659 INNER JOIN profiles ON profiles_activity.who = profiles.userid
660 INNER JOIN fielddefs ON fielddefs.id = profiles_activity.fieldid
661 WHERE profiles_activity.userid = ?
662 ORDER BY profiles_activity.profiles_when",
663 {'Slice' => {}},
664 $otherUser->id);
666 $vars->{'otheruser'} = $otherUser;
668 $template->process("account/profile-activity.html.tmpl", $vars)
669 || ThrowTemplateError($template->error());
671 ###########################################################################
672 } else {
673 $vars->{'action'} = $action;
674 ThrowCodeError('action_unrecognized', $vars);
677 exit;
679 ###########################################################################
680 # Helpers
681 ###########################################################################
683 # Try to build a user object using its ID, else its login name, and throw
684 # an error if the user does not exist.
685 sub check_user {
686 my ($otherUserID, $otherUserLogin) = @_;
688 my $otherUser;
689 my $vars = {};
691 if ($otherUserID) {
692 $otherUser = Bugzilla::User->new($otherUserID);
693 $vars->{'user_id'} = $otherUserID;
695 elsif ($otherUserLogin) {
696 $otherUser = new Bugzilla::User({ name => $otherUserLogin });
697 $vars->{'user_login'} = $otherUserLogin;
699 ($otherUser && $otherUser->id) || ThrowCodeError('invalid_user', $vars);
701 return $otherUser;
704 # Copy incoming list selection values from CGI params to template variables.
705 sub mirrorListSelectionValues {
706 my $cgi = Bugzilla->cgi;
707 if (defined($cgi->param('matchtype'))) {
708 foreach ('matchvalue', 'matchstr', 'matchtype', 'grouprestrict', 'groupid') {
709 $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
714 # Retrieve user data for the user editing form. User creation and user
715 # editing code rely on this to call derive_groups().
716 sub userDataToVars {
717 my $otheruserid = shift;
718 my $otheruser = new Bugzilla::User($otheruserid);
719 my $query;
720 my $user = Bugzilla->user;
721 my $dbh = Bugzilla->dbh;
723 my $grouplist = $otheruser->groups_as_string;
725 $vars->{'otheruser'} = $otheruser;
726 $vars->{'groups'} = $user->bless_groups();
728 $vars->{'permissions'} = $dbh->selectall_hashref(
729 qq{SELECT id,
730 COUNT(directmember.group_id) AS directmember,
731 COUNT(regexpmember.group_id) AS regexpmember,
732 (CASE WHEN (groups.id IN ($grouplist)
733 AND COUNT(directmember.group_id) = 0
734 AND COUNT(regexpmember.group_id) = 0
735 ) THEN 1 ELSE 0 END)
736 AS derivedmember,
737 COUNT(directbless.group_id) AS directbless
738 FROM groups
739 LEFT JOIN user_group_map AS directmember
740 ON directmember.group_id = id
741 AND directmember.user_id = ?
742 AND directmember.isbless = 0
743 AND directmember.grant_type = ?
744 LEFT JOIN user_group_map AS regexpmember
745 ON regexpmember.group_id = id
746 AND regexpmember.user_id = ?
747 AND regexpmember.isbless = 0
748 AND regexpmember.grant_type = ?
749 LEFT JOIN user_group_map AS directbless
750 ON directbless.group_id = id
751 AND directbless.user_id = ?
752 AND directbless.isbless = 1
753 AND directbless.grant_type = ?
754 } . $dbh->sql_group_by('id'),
755 'id', undef,
756 ($otheruserid, GRANT_DIRECT,
757 $otheruserid, GRANT_REGEXP,
758 $otheruserid, GRANT_DIRECT));
760 # Find indirect bless permission.
761 $query = qq{SELECT groups.id
762 FROM groups, group_group_map AS ggm
763 WHERE groups.id = ggm.grantor_id
764 AND ggm.member_id IN ($grouplist)
765 AND ggm.grant_type = ?
766 } . $dbh->sql_group_by('id');
767 foreach (@{$dbh->selectall_arrayref($query, undef,
768 (GROUP_BLESS))}) {
769 # Merge indirect bless permissions into permission variable.
770 $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
774 sub edit_processing {
775 my $otherUser = shift;
776 my $user = Bugzilla->user;
777 my $template = Bugzilla->template;
779 $user->in_group('editusers') || $user->can_see_user($otherUser)
780 || ThrowUserError('auth_failure', {reason => "not_visible",
781 action => "modify",
782 object => "user"});
784 userDataToVars($otherUser->id);
785 $vars->{'token'} = issue_session_token('edit_user');
787 $template->process('admin/users/edit.html.tmpl', $vars)
788 || ThrowTemplateError($template->error());