2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / BugsSite / editusers.cgi
blob5d74374b60e5e0bada5c4c9d77faa8ba7d8288ee
1 #!/usr/bin/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 # Frédéric Buclin <LpSolit@gmail.com>
19 use strict;
20 use lib ".";
22 require "CGI.pl";
23 require "globals.pl";
25 use vars qw( $vars );
27 use Bugzilla;
28 use Bugzilla::User;
29 use Bugzilla::Flag;
30 use Bugzilla::Config;
31 use Bugzilla::Constants;
32 use Bugzilla::Util;
34 my $user = Bugzilla->login(LOGIN_REQUIRED);
36 my $cgi = Bugzilla->cgi;
37 my $template = Bugzilla->template;
38 my $dbh = Bugzilla->dbh;
39 my $userid = $user->id;
40 my $editusers = $user->in_group('editusers');
42 # Reject access if there is no sense in continuing.
43 $editusers
44 || $user->can_bless()
45 || ThrowUserError("auth_failure", {group => "editusers",
46 reason => "cant_bless",
47 action => "edit",
48 object => "users"});
50 print $cgi->header();
52 # Common CGI params
53 my $action = $cgi->param('action') || 'search';
54 my $otherUserID = $cgi->param('userid');
55 my $otherUserLogin = $cgi->param('user');
57 # Prefill template vars with data used in all or nearly all templates
58 $vars->{'editusers'} = $editusers;
59 mirrorListSelectionValues();
61 ###########################################################################
62 if ($action eq 'search') {
63 # Allow to restrict the search to any group the user is allowed to bless.
64 $vars->{'restrictablegroups'} = $user->bless_groups();
65 $template->process('admin/users/search.html.tmpl', $vars)
66 || ThrowTemplateError($template->error());
68 ###########################################################################
69 } elsif ($action eq 'list') {
70 my $matchstr = $cgi->param('matchstr');
71 my $matchtype = $cgi->param('matchtype');
72 my $grouprestrict = $cgi->param('grouprestrict') || '0';
73 my $groupid = $cgi->param('groupid');
74 my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
75 'FROM profiles';
76 my @bindValues;
77 my $nextCondition;
78 my $visibleGroups;
80 # If a group ID is given, make sure it is a valid one.
81 if ($grouprestrict) {
82 (detaint_natural($groupid) && GroupIdToName($groupid))
83 || ThrowUserError('invalid_group_ID');
86 if (!$editusers && Param('usevisibilitygroups')) {
87 # Show only users in visible groups.
88 $visibleGroups = visibleGroupsAsString();
90 if ($visibleGroups) {
91 $query .= qq{, user_group_map AS ugm
92 WHERE ugm.user_id = profiles.userid
93 AND ugm.isbless = 0
94 AND ugm.group_id IN ($visibleGroups)
96 $nextCondition = 'AND';
98 } else {
99 $visibleGroups = 1;
100 if ($grouprestrict eq '1') {
101 $query .= ', user_group_map AS ugm';
103 $nextCondition = 'WHERE';
106 if (!$visibleGroups) {
107 $vars->{'users'} = {};
109 else {
110 # Handle selection by user name.
111 if (defined($matchtype)) {
112 $query .= " $nextCondition profiles.login_name ";
113 if ($matchtype eq 'regexp') {
114 $query .= $dbh->sql_regexp . ' ?';
115 $matchstr = '.' unless $matchstr;
116 } elsif ($matchtype eq 'notregexp') {
117 $query .= $dbh->sql_not_regexp . ' ?';
118 $matchstr = '.' unless $matchstr;
119 } else { # substr or unknown
120 $query .= 'like ?';
121 $matchstr = "%$matchstr%";
123 $nextCondition = 'AND';
124 # We can trick_taint because we use the value in a SELECT only,
125 # using a placeholder.
126 trick_taint($matchstr);
127 push(@bindValues, $matchstr);
130 # Handle selection by group.
131 if ($grouprestrict eq '1') {
132 $query .= " $nextCondition profiles.userid = ugm.user_id " .
133 'AND ugm.group_id = ?';
134 # We can trick_taint because we use the value in a SELECT only,
135 # using a placeholder.
136 push(@bindValues, $groupid);
138 $query .= ' ORDER BY profiles.login_name';
140 $vars->{'users'} = $dbh->selectall_arrayref($query,
141 {'Slice' => {}},
142 @bindValues);
145 $template->process('admin/users/list.html.tmpl', $vars)
146 || ThrowTemplateError($template->error());
148 ###########################################################################
149 } elsif ($action eq 'add') {
150 $editusers || ThrowUserError("auth_failure", {group => "editusers",
151 action => "add",
152 object => "users"});
154 $template->process('admin/users/create.html.tmpl', $vars)
155 || ThrowTemplateError($template->error());
157 ###########################################################################
158 } elsif ($action eq 'new') {
159 $editusers || ThrowUserError("auth_failure", {group => "editusers",
160 action => "add",
161 object => "users"});
163 my $login = $cgi->param('login');
164 my $password = $cgi->param('password');
165 my $realname = trim($cgi->param('name') || '');
166 my $disabledtext = trim($cgi->param('disabledtext') || '');
168 # Lock tables during the check+creation session.
169 $dbh->bz_lock_tables('profiles WRITE',
170 'profiles_activity WRITE',
171 'email_setting WRITE',
172 'namedqueries READ',
173 'whine_queries READ',
174 'tokens READ');
176 # Validity checks
177 $login || ThrowUserError('user_login_required');
178 CheckEmailSyntax($login);
179 is_available_username($login) || ThrowUserError('account_exists',
180 {'email' => $login});
181 ValidatePassword($password);
183 # Login and password are validated now, and realname and disabledtext
184 # are allowed to contain anything
185 trick_taint($login);
186 trick_taint($realname);
187 trick_taint($password);
188 trick_taint($disabledtext);
190 insert_new_user($login, $realname, $password, $disabledtext);
191 my $userid = $dbh->bz_last_key('profiles', 'userid');
192 $dbh->bz_unlock_tables();
193 userDataToVars($userid);
195 $vars->{'message'} = 'account_created';
196 $template->process('admin/users/edit.html.tmpl', $vars)
197 || ThrowTemplateError($template->error());
199 ###########################################################################
200 } elsif ($action eq 'edit') {
201 my $otherUser = check_user($otherUserID, $otherUserLogin);
202 $otherUserID = $otherUser->id;
204 $editusers || canSeeUser($otherUserID)
205 || ThrowUserError('auth_failure', {reason => "not_visible",
206 action => "modify",
207 object => "user"});
209 userDataToVars($otherUserID);
211 $template->process('admin/users/edit.html.tmpl', $vars)
212 || ThrowTemplateError($template->error());
214 ###########################################################################
215 } elsif ($action eq 'update') {
216 my $otherUser = check_user($otherUserID, $otherUserLogin);
217 $otherUserID = $otherUser->id;
219 my $logoutNeeded = 0;
220 my @changedFields;
222 # Lock tables during the check+update session.
223 $dbh->bz_lock_tables('profiles WRITE',
224 'profiles_activity WRITE',
225 'fielddefs READ',
226 'namedqueries READ',
227 'whine_queries READ',
228 'tokens WRITE',
229 'logincookies WRITE',
230 'groups READ',
231 'user_group_map WRITE',
232 'user_group_map AS ugm READ',
233 'group_group_map READ',
234 'group_group_map AS ggm READ');
236 $editusers || canSeeUser($otherUserID)
237 || ThrowUserError('auth_failure', {reason => "not_visible",
238 action => "modify",
239 object => "user"});
241 # Cleanups
242 my $loginold = $cgi->param('loginold') || '';
243 my $realnameold = $cgi->param('nameold') || '';
244 my $disabledtextold = $cgi->param('disabledtextold') || '';
246 my $login = $cgi->param('login');
247 my $password = $cgi->param('password');
248 my $realname = trim($cgi->param('name') || '');
249 my $disabledtext = trim($cgi->param('disabledtext') || '');
251 # Update profiles table entry; silently skip doing this if the user
252 # is not authorized.
253 if ($editusers) {
254 my @values;
256 if ($login ne $loginold) {
257 # Validate, then trick_taint.
258 $login || ThrowUserError('user_login_required');
259 CheckEmailSyntax($login);
260 is_available_username($login) || ThrowUserError('account_exists',
261 {'email' => $login});
262 trick_taint($login);
263 push(@changedFields, 'login_name');
264 push(@values, $login);
265 $logoutNeeded = 1;
267 # Since we change the login, silently delete any tokens.
268 $dbh->do('DELETE FROM tokens WHERE userid = ?', {}, $otherUserID);
270 if ($realname ne $realnameold) {
271 # The real name may be anything; we use a placeholder for our
272 # INSERT, and we rely on displaying code to FILTER html.
273 trick_taint($realname);
274 push(@changedFields, 'realname');
275 push(@values, $realname);
277 if ($password) {
278 # Validate, then trick_taint.
279 ValidatePassword($password) if $password;
280 trick_taint($password);
281 push(@changedFields, 'cryptpassword');
282 push(@values, bz_crypt($password));
283 $logoutNeeded = 1;
285 if ($disabledtext ne $disabledtextold) {
286 # The disable text may be anything; we use a placeholder for our
287 # INSERT, and we rely on displaying code to FILTER html.
288 trick_taint($disabledtext);
289 push(@changedFields, 'disabledtext');
290 push(@values, $disabledtext);
291 $logoutNeeded = 1;
293 if (@changedFields) {
294 push (@values, $otherUserID);
295 $logoutNeeded && Bugzilla->logout_user($otherUser);
296 $dbh->do('UPDATE profiles SET ' .
297 join(' = ?,', @changedFields).' = ? ' .
298 'WHERE userid = ?',
299 undef, @values);
300 # XXX: should create profiles_activity entries.
304 # Update group settings.
305 my $sth_add_mapping = $dbh->prepare(
306 qq{INSERT INTO user_group_map (
307 user_id, group_id, isbless, grant_type
308 ) VALUES (
309 ?, ?, ?, ?
312 my $sth_remove_mapping = $dbh->prepare(
313 qq{DELETE FROM user_group_map
314 WHERE user_id = ?
315 AND group_id = ?
316 AND isbless = ?
317 AND grant_type = ?
320 my @groupsAddedTo;
321 my @groupsRemovedFrom;
322 my @groupsGrantedRightsToBless;
323 my @groupsDeniedRightsToBless;
325 # Regard only groups the user is allowed to bless and skip all others
326 # silently.
327 # XXX: checking for existence of each user_group_map entry
328 # would allow to display a friendlier error message on page reloads.
329 foreach (@{$user->bless_groups()}) {
330 my $id = $$_{'id'};
331 my $name = $$_{'name'};
333 # Change memberships.
334 my $oldgroupid = $cgi->param("oldgroup_$id") || '0';
335 my $groupid = $cgi->param("group_$id") || '0';
336 if ($groupid ne $oldgroupid) {
337 if ($groupid eq '0') {
338 $sth_remove_mapping->execute(
339 $otherUserID, $id, 0, GRANT_DIRECT);
340 push(@groupsRemovedFrom, $name);
341 } else {
342 $sth_add_mapping->execute(
343 $otherUserID, $id, 0, GRANT_DIRECT);
344 push(@groupsAddedTo, $name);
348 # Only members of the editusers group may change bless grants.
349 # Skip silently if this is not the case.
350 if ($editusers) {
351 my $oldgroupid = $cgi->param("oldbless_$id") || '0';
352 my $groupid = $cgi->param("bless_$id") || '0';
353 if ($groupid ne $oldgroupid) {
354 if ($groupid eq '0') {
355 $sth_remove_mapping->execute(
356 $otherUserID, $id, 1, GRANT_DIRECT);
357 push(@groupsDeniedRightsToBless, $name);
358 } else {
359 $sth_add_mapping->execute(
360 $otherUserID, $id, 1, GRANT_DIRECT);
361 push(@groupsGrantedRightsToBless, $name);
366 if (@groupsAddedTo || @groupsRemovedFrom) {
367 $dbh->do(qq{INSERT INTO profiles_activity (
368 userid, who,
369 profiles_when, fieldid,
370 oldvalue, newvalue
371 ) VALUES (
372 ?, ?, now(), ?, ?, ?
375 undef,
376 ($otherUserID, $userid,
377 GetFieldID('bug_group'),
378 join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
379 $dbh->do('UPDATE profiles SET refreshed_when=? WHERE userid = ?',
380 undef, ('1900-01-01 00:00:00', $otherUserID));
382 # XXX: should create profiles_activity entries for blesser changes.
384 $dbh->bz_unlock_tables();
386 # XXX: userDataToVars may be off when editing ourselves.
387 userDataToVars($otherUserID);
389 $vars->{'message'} = 'account_updated';
390 $vars->{'loginold'} = $loginold;
391 $vars->{'changed_fields'} = \@changedFields;
392 $vars->{'groups_added_to'} = \@groupsAddedTo;
393 $vars->{'groups_removed_from'} = \@groupsRemovedFrom;
394 $vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
395 $vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
396 $template->process('admin/users/edit.html.tmpl', $vars)
397 || ThrowTemplateError($template->error());
399 ###########################################################################
400 } elsif ($action eq 'del') {
401 my $otherUser = check_user($otherUserID, $otherUserLogin);
402 $otherUserID = $otherUser->id;
404 Param('allowuserdeletion') || ThrowUserError('users_deletion_disabled');
405 $editusers || ThrowUserError('auth_failure', {group => "editusers",
406 action => "delete",
407 object => "users"});
408 $vars->{'otheruser'} = $otherUser;
409 $vars->{'editcomponents'} = UserInGroup('editcomponents');
411 # Find other cross references.
412 $vars->{'assignee_or_qa'} = $dbh->selectrow_array(
413 qq{SELECT COUNT(*)
414 FROM bugs
415 WHERE assigned_to = ? OR qa_contact = ?},
416 undef, ($otherUserID, $otherUserID));
417 $vars->{'reporter'} = $dbh->selectrow_array(
418 'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
419 undef, $otherUserID);
420 $vars->{'cc'} = $dbh->selectrow_array(
421 'SELECT COUNT(*) FROM cc WHERE who = ?',
422 undef, $otherUserID);
423 $vars->{'bugs_activity'} = $dbh->selectrow_array(
424 'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
425 undef, $otherUserID);
426 $vars->{'email_setting'} = $dbh->selectrow_array(
427 'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
428 undef, $otherUserID);
429 $vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
430 'SELECT COUNT(*) FROM flags WHERE requestee_id = ? AND is_active = 1',
431 undef, $otherUserID);
432 $vars->{'flags'}{'setter'} = $dbh->selectrow_array(
433 'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
434 undef, $otherUserID);
435 $vars->{'longdescs'} = $dbh->selectrow_array(
436 'SELECT COUNT(*) FROM longdescs WHERE who = ?',
437 undef, $otherUserID);
438 $vars->{'namedqueries'} = $dbh->selectrow_array(
439 'SELECT COUNT(*) FROM namedqueries WHERE userid = ?',
440 undef, $otherUserID);
441 $vars->{'profile_setting'} = $dbh->selectrow_array(
442 'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
443 undef, $otherUserID);
444 $vars->{'profiles_activity'} = $dbh->selectrow_array(
445 'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
446 undef, ($otherUserID, $otherUserID));
447 $vars->{'series'} = $dbh->selectrow_array(
448 'SELECT COUNT(*) FROM series WHERE creator = ?',
449 undef, $otherUserID);
450 $vars->{'votes'} = $dbh->selectrow_array(
451 'SELECT COUNT(*) FROM votes WHERE who = ?',
452 undef, $otherUserID);
453 $vars->{'watch'}{'watched'} = $dbh->selectrow_array(
454 'SELECT COUNT(*) FROM watch WHERE watched = ?',
455 undef, $otherUserID);
456 $vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
457 'SELECT COUNT(*) FROM watch WHERE watcher = ?',
458 undef, $otherUserID);
459 $vars->{'whine_events'} = $dbh->selectrow_array(
460 'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
461 undef, $otherUserID);
462 $vars->{'whine_schedules'} = $dbh->selectrow_array(
463 qq{SELECT COUNT(distinct eventid)
464 FROM whine_schedules
465 WHERE mailto = ?
466 AND mailto_type = ?
468 undef, ($otherUserID, MAILTO_USER));
470 $template->process('admin/users/confirm-delete.html.tmpl', $vars)
471 || ThrowTemplateError($template->error());
473 ###########################################################################
474 } elsif ($action eq 'delete') {
475 my $otherUser = check_user($otherUserID, $otherUserLogin);
476 $otherUserID = $otherUser->id;
478 # Cache for user accounts.
479 my %usercache = (0 => new Bugzilla::User());
480 my %updatedbugs;
482 # Lock tables during the check+removal session.
483 # XXX: if there was some change on these tables after the deletion
484 # confirmation checks, we may do something here we haven't warned
485 # about.
486 $dbh->bz_lock_tables('bugs WRITE',
487 'bugs_activity WRITE',
488 'attachments READ',
489 'fielddefs READ',
490 'products READ',
491 'components READ',
492 'logincookies WRITE',
493 'profiles WRITE',
494 'profiles_activity WRITE',
495 'email_setting WRITE',
496 'profile_setting WRITE',
497 'groups READ',
498 'bug_group_map READ',
499 'user_group_map WRITE',
500 'group_group_map READ',
501 'flags WRITE',
502 'flagtypes READ',
503 'cc WRITE',
504 'namedqueries WRITE',
505 'tokens WRITE',
506 'votes WRITE',
507 'watch WRITE',
508 'series WRITE',
509 'series_data WRITE',
510 'whine_schedules WRITE',
511 'whine_queries WRITE',
512 'whine_events WRITE');
514 Param('allowuserdeletion')
515 || ThrowUserError('users_deletion_disabled');
516 $editusers || ThrowUserError('auth_failure',
517 {group => "editusers",
518 action => "delete",
519 object => "users"});
520 @{$otherUser->product_responsibilities()}
521 && ThrowUserError('user_has_responsibility');
523 Bugzilla->logout_user($otherUser);
525 # Get the timestamp for LogActivityEntry.
526 my $timestamp = $dbh->selectrow_array('SELECT NOW()');
528 # When we update a bug_activity entry, we update the bug timestamp, too.
529 my $sth_set_bug_timestamp =
530 $dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
532 # Reference removals which need LogActivityEntry.
533 my $statement_flagupdate = 'UPDATE flags set requestee_id = NULL
534 WHERE bug_id = ?
535 AND attach_id %s
536 AND requestee_id = ?';
537 my $sth_flagupdate_attachment =
538 $dbh->prepare(sprintf($statement_flagupdate, '= ?'));
539 my $sth_flagupdate_bug =
540 $dbh->prepare(sprintf($statement_flagupdate, 'IS NULL'));
542 my $buglist = $dbh->selectall_arrayref('SELECT DISTINCT bug_id, attach_id
543 FROM flags
544 WHERE requestee_id = ?',
545 undef, $otherUserID);
547 foreach (@$buglist) {
548 my ($bug_id, $attach_id) = @$_;
549 my @old_summaries = Bugzilla::Flag::snapshot($bug_id, $attach_id);
550 if ($attach_id) {
551 $sth_flagupdate_attachment->execute($bug_id, $attach_id, $otherUserID);
553 else {
554 $sth_flagupdate_bug->execute($bug_id, $otherUserID);
556 my @new_summaries = Bugzilla::Flag::snapshot($bug_id, $attach_id);
557 # Let update_activity do all the dirty work, including setting
558 # the bug timestamp.
559 Bugzilla::Flag::update_activity($bug_id, $attach_id,
560 $dbh->quote($timestamp),
561 \@old_summaries, \@new_summaries);
562 $updatedbugs{$bug_id} = 1;
565 # Simple deletions in referred tables.
566 $dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
567 $otherUserID);
568 $dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
569 $dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
570 $dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
571 $otherUserID);
572 $dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
573 ($otherUserID, $otherUserID));
574 $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
575 $dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
576 $otherUserID);
577 $dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
578 $dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
579 ($otherUserID, $otherUserID));
581 # Deletions in referred tables which need LogActivityEntry.
582 $buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc
583 WHERE who = ?',
584 undef, $otherUserID);
585 $dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
586 foreach my $bug_id (@$buglist) {
587 LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
588 $timestamp);
589 $sth_set_bug_timestamp->execute($timestamp, $bug_id);
590 $updatedbugs{$bug_id} = 1;
593 # Even more complex deletions in referred tables.
594 my $id;
596 # 1) Series
597 my $sth_seriesid = $dbh->prepare(
598 'SELECT series_id FROM series WHERE creator = ?');
599 my $sth_deleteSeries = $dbh->prepare(
600 'DELETE FROM series WHERE series_id = ?');
601 my $sth_deleteSeriesData = $dbh->prepare(
602 'DELETE FROM series_data WHERE series_id = ?');
604 $sth_seriesid->execute($otherUserID);
605 while ($id = $sth_seriesid->fetchrow_array()) {
606 $sth_deleteSeriesData->execute($id);
607 $sth_deleteSeries->execute($id);
610 # 2) Whines
611 my $sth_whineidFromSchedules = $dbh->prepare(
612 qq{SELECT eventid FROM whine_schedules
613 WHERE mailto = ? AND mailto_type = ?});
614 my $sth_whineidFromEvents = $dbh->prepare(
615 'SELECT id FROM whine_events WHERE owner_userid = ?');
616 my $sth_deleteWhineEvent = $dbh->prepare(
617 'DELETE FROM whine_events WHERE id = ?');
618 my $sth_deleteWhineQuery = $dbh->prepare(
619 'DELETE FROM whine_queries WHERE eventid = ?');
620 my $sth_deleteWhineSchedule = $dbh->prepare(
621 'DELETE FROM whine_schedules WHERE eventid = ?');
623 $sth_whineidFromSchedules->execute($otherUserID, MAILTO_USER);
624 while ($id = $sth_whineidFromSchedules->fetchrow_array()) {
625 $sth_deleteWhineQuery->execute($id);
626 $sth_deleteWhineSchedule->execute($id);
627 $sth_deleteWhineEvent->execute($id);
630 $sth_whineidFromEvents->execute($otherUserID);
631 while ($id = $sth_whineidFromEvents->fetchrow_array()) {
632 $sth_deleteWhineQuery->execute($id);
633 $sth_deleteWhineSchedule->execute($id);
634 $sth_deleteWhineEvent->execute($id);
637 # 3) Bugs
638 # 3.1) fall back to the default assignee
639 $buglist = $dbh->selectall_arrayref(
640 'SELECT bug_id, initialowner
641 FROM bugs
642 INNER JOIN components ON components.id = bugs.component_id
643 WHERE assigned_to = ?', undef, $otherUserID);
645 my $sth_updateAssignee = $dbh->prepare(
646 'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
648 foreach my $bug (@$buglist) {
649 my ($bug_id, $default_assignee_id) = @$bug;
650 $sth_updateAssignee->execute($default_assignee_id,
651 $timestamp, $bug_id);
652 $updatedbugs{$bug_id} = 1;
653 $default_assignee_id ||= 0;
654 $usercache{$default_assignee_id} ||=
655 new Bugzilla::User($default_assignee_id);
656 LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
657 $usercache{$default_assignee_id}->login,
658 $userid, $timestamp);
661 # 3.2) fall back to the default QA contact
662 $buglist = $dbh->selectall_arrayref(
663 'SELECT bug_id, initialqacontact
664 FROM bugs
665 INNER JOIN components ON components.id = bugs.component_id
666 WHERE qa_contact = ?', undef, $otherUserID);
668 my $sth_updateQAcontact = $dbh->prepare(
669 'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
671 foreach my $bug (@$buglist) {
672 my ($bug_id, $default_qa_contact_id) = @$bug;
673 $sth_updateQAcontact->execute($default_qa_contact_id,
674 $timestamp, $bug_id);
675 $updatedbugs{$bug_id} = 1;
676 $default_qa_contact_id ||= 0;
677 $usercache{$default_qa_contact_id} ||=
678 new Bugzilla::User($default_qa_contact_id);
679 LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
680 $usercache{$default_qa_contact_id}->login,
681 $userid, $timestamp);
684 # Finally, remove the user account itself.
685 $dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
687 $dbh->bz_unlock_tables();
689 $vars->{'message'} = 'account_deleted';
690 $vars->{'otheruser'}{'login'} = $otherUser->login;
691 $vars->{'restrictablegroups'} = $user->bless_groups();
692 $template->process('admin/users/search.html.tmpl', $vars)
693 || ThrowTemplateError($template->error());
695 # Send mail about what we've done to bugs.
696 # The deleted user is not notified of the changes.
697 foreach (keys(%updatedbugs)) {
698 Bugzilla::BugMail::Send($_);
701 ###########################################################################
702 } else {
703 $vars->{'action'} = $action;
704 ThrowCodeError('action_unrecognized', $vars);
707 exit;
709 ###########################################################################
710 # Helpers
711 ###########################################################################
713 # Try to build a user object using its ID, else its login name, and throw
714 # an error if the user does not exist.
715 sub check_user {
716 my ($otherUserID, $otherUserLogin) = @_;
718 my $otherUser;
719 my $vars = {};
721 if ($otherUserID) {
722 $otherUser = Bugzilla::User->new($otherUserID);
723 $vars->{'user_id'} = $otherUserID;
725 elsif ($otherUserLogin) {
726 $otherUser = Bugzilla::User->new_from_login($otherUserLogin);
727 $vars->{'user_login'} = $otherUserLogin;
729 ($otherUser && $otherUser->id) || ThrowCodeError('invalid_user', $vars);
731 return $otherUser;
734 # Copy incoming list selection values from CGI params to template variables.
735 sub mirrorListSelectionValues {
736 if (defined($cgi->param('matchtype'))) {
737 foreach ('matchstr', 'matchtype', 'grouprestrict', 'groupid') {
738 $vars->{'listselectionvalues'}{$_} = $cgi->param($_);
743 # Give a list of IDs of groups the user can see.
744 sub visibleGroupsAsString {
745 return join(', ', @{$user->visible_groups_direct()});
748 # Determine whether the user can see a user. (Checks for existence, too.)
749 sub canSeeUser {
750 my $otherUserID = shift;
751 my $query;
753 if (Param('usevisibilitygroups')) {
754 # If the user can see no groups, then no users are visible either.
755 my $visibleGroups = visibleGroupsAsString() || return 0;
757 $query = qq{SELECT COUNT(DISTINCT userid)
758 FROM profiles, user_group_map
759 WHERE userid = ?
760 AND user_id = userid
761 AND isbless = 0
762 AND group_id IN ($visibleGroups)
764 } else {
765 $query = qq{SELECT COUNT(userid)
766 FROM profiles
767 WHERE userid = ?
770 return $dbh->selectrow_array($query, undef, $otherUserID);
773 # Retrieve user data for the user editing form. User creation and user
774 # editing code rely on this to call derive_groups().
775 sub userDataToVars {
776 my $otheruserid = shift;
777 my $otheruser = new Bugzilla::User($otheruserid);
778 my $query;
779 my $dbh = Bugzilla->dbh;
781 $otheruser->derive_groups();
783 $vars->{'otheruser'} = $otheruser;
784 $vars->{'groups'} = $user->bless_groups();
786 $vars->{'permissions'} = $dbh->selectall_hashref(
787 qq{SELECT id,
788 COUNT(directmember.group_id) AS directmember,
789 COUNT(regexpmember.group_id) AS regexpmember,
790 COUNT(derivedmember.group_id) AS derivedmember,
791 COUNT(directbless.group_id) AS directbless
792 FROM groups
793 LEFT JOIN user_group_map AS directmember
794 ON directmember.group_id = id
795 AND directmember.user_id = ?
796 AND directmember.isbless = 0
797 AND directmember.grant_type = ?
798 LEFT JOIN user_group_map AS regexpmember
799 ON regexpmember.group_id = id
800 AND regexpmember.user_id = ?
801 AND regexpmember.isbless = 0
802 AND regexpmember.grant_type = ?
803 LEFT JOIN user_group_map AS derivedmember
804 ON derivedmember.group_id = id
805 AND derivedmember.user_id = ?
806 AND derivedmember.isbless = 0
807 AND derivedmember.grant_type = ?
808 LEFT JOIN user_group_map AS directbless
809 ON directbless.group_id = id
810 AND directbless.user_id = ?
811 AND directbless.isbless = 1
812 AND directbless.grant_type = ?
813 } . $dbh->sql_group_by('id'),
814 'id', undef,
815 ($otheruserid, GRANT_DIRECT,
816 $otheruserid, GRANT_REGEXP,
817 $otheruserid, GRANT_DERIVED,
818 $otheruserid, GRANT_DIRECT));
820 # Find indirect bless permission.
821 $query = qq{SELECT groups.id
822 FROM groups, user_group_map AS ugm, group_group_map AS ggm
823 WHERE ugm.user_id = ?
824 AND groups.id = ggm.grantor_id
825 AND ggm.member_id = ugm.group_id
826 AND ugm.isbless = 0
827 AND ggm.grant_type = ?
828 } . $dbh->sql_group_by('id');
829 foreach (@{$dbh->selectall_arrayref($query, undef,
830 ($otheruserid, GROUP_BLESS))}) {
831 # Merge indirect bless permissions into permission variable.
832 $vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;