blob: 5d74374b60e5e0bada5c4c9d77faa8ba7d8288ee [file] [log] [blame]
#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Frédéric Buclin <LpSolit@gmail.com>
use strict;
use lib ".";
require "CGI.pl";
require "globals.pl";
use vars qw( $vars );
use Bugzilla;
use Bugzilla::User;
use Bugzilla::Flag;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $dbh = Bugzilla->dbh;
my $userid = $user->id;
my $editusers = $user->in_group('editusers');
# Reject access if there is no sense in continuing.
$editusers
|| $user->can_bless()
|| ThrowUserError("auth_failure", {group => "editusers",
reason => "cant_bless",
action => "edit",
object => "users"});
print $cgi->header();
# Common CGI params
my $action = $cgi->param('action') || 'search';
my $otherUserID = $cgi->param('userid');
my $otherUserLogin = $cgi->param('user');
# Prefill template vars with data used in all or nearly all templates
$vars->{'editusers'} = $editusers;
mirrorListSelectionValues();
###########################################################################
if ($action eq 'search') {
# Allow to restrict the search to any group the user is allowed to bless.
$vars->{'restrictablegroups'} = $user->bless_groups();
$template->process('admin/users/search.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'list') {
my $matchstr = $cgi->param('matchstr');
my $matchtype = $cgi->param('matchtype');
my $grouprestrict = $cgi->param('grouprestrict') || '0';
my $groupid = $cgi->param('groupid');
my $query = 'SELECT DISTINCT userid, login_name, realname, disabledtext ' .
'FROM profiles';
my @bindValues;
my $nextCondition;
my $visibleGroups;
# If a group ID is given, make sure it is a valid one.
if ($grouprestrict) {
(detaint_natural($groupid) && GroupIdToName($groupid))
|| ThrowUserError('invalid_group_ID');
}
if (!$editusers && Param('usevisibilitygroups')) {
# Show only users in visible groups.
$visibleGroups = visibleGroupsAsString();
if ($visibleGroups) {
$query .= qq{, user_group_map AS ugm
WHERE ugm.user_id = profiles.userid
AND ugm.isbless = 0
AND ugm.group_id IN ($visibleGroups)
};
$nextCondition = 'AND';
}
} else {
$visibleGroups = 1;
if ($grouprestrict eq '1') {
$query .= ', user_group_map AS ugm';
}
$nextCondition = 'WHERE';
}
if (!$visibleGroups) {
$vars->{'users'} = {};
}
else {
# Handle selection by user name.
if (defined($matchtype)) {
$query .= " $nextCondition profiles.login_name ";
if ($matchtype eq 'regexp') {
$query .= $dbh->sql_regexp . ' ?';
$matchstr = '.' unless $matchstr;
} elsif ($matchtype eq 'notregexp') {
$query .= $dbh->sql_not_regexp . ' ?';
$matchstr = '.' unless $matchstr;
} else { # substr or unknown
$query .= 'like ?';
$matchstr = "%$matchstr%";
}
$nextCondition = 'AND';
# We can trick_taint because we use the value in a SELECT only,
# using a placeholder.
trick_taint($matchstr);
push(@bindValues, $matchstr);
}
# Handle selection by group.
if ($grouprestrict eq '1') {
$query .= " $nextCondition profiles.userid = ugm.user_id " .
'AND ugm.group_id = ?';
# We can trick_taint because we use the value in a SELECT only,
# using a placeholder.
push(@bindValues, $groupid);
}
$query .= ' ORDER BY profiles.login_name';
$vars->{'users'} = $dbh->selectall_arrayref($query,
{'Slice' => {}},
@bindValues);
}
$template->process('admin/users/list.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'add') {
$editusers || ThrowUserError("auth_failure", {group => "editusers",
action => "add",
object => "users"});
$template->process('admin/users/create.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'new') {
$editusers || ThrowUserError("auth_failure", {group => "editusers",
action => "add",
object => "users"});
my $login = $cgi->param('login');
my $password = $cgi->param('password');
my $realname = trim($cgi->param('name') || '');
my $disabledtext = trim($cgi->param('disabledtext') || '');
# Lock tables during the check+creation session.
$dbh->bz_lock_tables('profiles WRITE',
'profiles_activity WRITE',
'email_setting WRITE',
'namedqueries READ',
'whine_queries READ',
'tokens READ');
# Validity checks
$login || ThrowUserError('user_login_required');
CheckEmailSyntax($login);
is_available_username($login) || ThrowUserError('account_exists',
{'email' => $login});
ValidatePassword($password);
# Login and password are validated now, and realname and disabledtext
# are allowed to contain anything
trick_taint($login);
trick_taint($realname);
trick_taint($password);
trick_taint($disabledtext);
insert_new_user($login, $realname, $password, $disabledtext);
my $userid = $dbh->bz_last_key('profiles', 'userid');
$dbh->bz_unlock_tables();
userDataToVars($userid);
$vars->{'message'} = 'account_created';
$template->process('admin/users/edit.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'edit') {
my $otherUser = check_user($otherUserID, $otherUserLogin);
$otherUserID = $otherUser->id;
$editusers || canSeeUser($otherUserID)
|| ThrowUserError('auth_failure', {reason => "not_visible",
action => "modify",
object => "user"});
userDataToVars($otherUserID);
$template->process('admin/users/edit.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'update') {
my $otherUser = check_user($otherUserID, $otherUserLogin);
$otherUserID = $otherUser->id;
my $logoutNeeded = 0;
my @changedFields;
# Lock tables during the check+update session.
$dbh->bz_lock_tables('profiles WRITE',
'profiles_activity WRITE',
'fielddefs READ',
'namedqueries READ',
'whine_queries READ',
'tokens WRITE',
'logincookies WRITE',
'groups READ',
'user_group_map WRITE',
'user_group_map AS ugm READ',
'group_group_map READ',
'group_group_map AS ggm READ');
$editusers || canSeeUser($otherUserID)
|| ThrowUserError('auth_failure', {reason => "not_visible",
action => "modify",
object => "user"});
# Cleanups
my $loginold = $cgi->param('loginold') || '';
my $realnameold = $cgi->param('nameold') || '';
my $disabledtextold = $cgi->param('disabledtextold') || '';
my $login = $cgi->param('login');
my $password = $cgi->param('password');
my $realname = trim($cgi->param('name') || '');
my $disabledtext = trim($cgi->param('disabledtext') || '');
# Update profiles table entry; silently skip doing this if the user
# is not authorized.
if ($editusers) {
my @values;
if ($login ne $loginold) {
# Validate, then trick_taint.
$login || ThrowUserError('user_login_required');
CheckEmailSyntax($login);
is_available_username($login) || ThrowUserError('account_exists',
{'email' => $login});
trick_taint($login);
push(@changedFields, 'login_name');
push(@values, $login);
$logoutNeeded = 1;
# Since we change the login, silently delete any tokens.
$dbh->do('DELETE FROM tokens WHERE userid = ?', {}, $otherUserID);
}
if ($realname ne $realnameold) {
# The real name may be anything; we use a placeholder for our
# INSERT, and we rely on displaying code to FILTER html.
trick_taint($realname);
push(@changedFields, 'realname');
push(@values, $realname);
}
if ($password) {
# Validate, then trick_taint.
ValidatePassword($password) if $password;
trick_taint($password);
push(@changedFields, 'cryptpassword');
push(@values, bz_crypt($password));
$logoutNeeded = 1;
}
if ($disabledtext ne $disabledtextold) {
# The disable text may be anything; we use a placeholder for our
# INSERT, and we rely on displaying code to FILTER html.
trick_taint($disabledtext);
push(@changedFields, 'disabledtext');
push(@values, $disabledtext);
$logoutNeeded = 1;
}
if (@changedFields) {
push (@values, $otherUserID);
$logoutNeeded && Bugzilla->logout_user($otherUser);
$dbh->do('UPDATE profiles SET ' .
join(' = ?,', @changedFields).' = ? ' .
'WHERE userid = ?',
undef, @values);
# XXX: should create profiles_activity entries.
}
}
# Update group settings.
my $sth_add_mapping = $dbh->prepare(
qq{INSERT INTO user_group_map (
user_id, group_id, isbless, grant_type
) VALUES (
?, ?, ?, ?
)
});
my $sth_remove_mapping = $dbh->prepare(
qq{DELETE FROM user_group_map
WHERE user_id = ?
AND group_id = ?
AND isbless = ?
AND grant_type = ?
});
my @groupsAddedTo;
my @groupsRemovedFrom;
my @groupsGrantedRightsToBless;
my @groupsDeniedRightsToBless;
# Regard only groups the user is allowed to bless and skip all others
# silently.
# XXX: checking for existence of each user_group_map entry
# would allow to display a friendlier error message on page reloads.
foreach (@{$user->bless_groups()}) {
my $id = $$_{'id'};
my $name = $$_{'name'};
# Change memberships.
my $oldgroupid = $cgi->param("oldgroup_$id") || '0';
my $groupid = $cgi->param("group_$id") || '0';
if ($groupid ne $oldgroupid) {
if ($groupid eq '0') {
$sth_remove_mapping->execute(
$otherUserID, $id, 0, GRANT_DIRECT);
push(@groupsRemovedFrom, $name);
} else {
$sth_add_mapping->execute(
$otherUserID, $id, 0, GRANT_DIRECT);
push(@groupsAddedTo, $name);
}
}
# Only members of the editusers group may change bless grants.
# Skip silently if this is not the case.
if ($editusers) {
my $oldgroupid = $cgi->param("oldbless_$id") || '0';
my $groupid = $cgi->param("bless_$id") || '0';
if ($groupid ne $oldgroupid) {
if ($groupid eq '0') {
$sth_remove_mapping->execute(
$otherUserID, $id, 1, GRANT_DIRECT);
push(@groupsDeniedRightsToBless, $name);
} else {
$sth_add_mapping->execute(
$otherUserID, $id, 1, GRANT_DIRECT);
push(@groupsGrantedRightsToBless, $name);
}
}
}
}
if (@groupsAddedTo || @groupsRemovedFrom) {
$dbh->do(qq{INSERT INTO profiles_activity (
userid, who,
profiles_when, fieldid,
oldvalue, newvalue
) VALUES (
?, ?, now(), ?, ?, ?
)
},
undef,
($otherUserID, $userid,
GetFieldID('bug_group'),
join(', ', @groupsRemovedFrom), join(', ', @groupsAddedTo)));
$dbh->do('UPDATE profiles SET refreshed_when=? WHERE userid = ?',
undef, ('1900-01-01 00:00:00', $otherUserID));
}
# XXX: should create profiles_activity entries for blesser changes.
$dbh->bz_unlock_tables();
# XXX: userDataToVars may be off when editing ourselves.
userDataToVars($otherUserID);
$vars->{'message'} = 'account_updated';
$vars->{'loginold'} = $loginold;
$vars->{'changed_fields'} = \@changedFields;
$vars->{'groups_added_to'} = \@groupsAddedTo;
$vars->{'groups_removed_from'} = \@groupsRemovedFrom;
$vars->{'groups_granted_rights_to_bless'} = \@groupsGrantedRightsToBless;
$vars->{'groups_denied_rights_to_bless'} = \@groupsDeniedRightsToBless;
$template->process('admin/users/edit.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'del') {
my $otherUser = check_user($otherUserID, $otherUserLogin);
$otherUserID = $otherUser->id;
Param('allowuserdeletion') || ThrowUserError('users_deletion_disabled');
$editusers || ThrowUserError('auth_failure', {group => "editusers",
action => "delete",
object => "users"});
$vars->{'otheruser'} = $otherUser;
$vars->{'editcomponents'} = UserInGroup('editcomponents');
# Find other cross references.
$vars->{'assignee_or_qa'} = $dbh->selectrow_array(
qq{SELECT COUNT(*)
FROM bugs
WHERE assigned_to = ? OR qa_contact = ?},
undef, ($otherUserID, $otherUserID));
$vars->{'reporter'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM bugs WHERE reporter = ?',
undef, $otherUserID);
$vars->{'cc'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM cc WHERE who = ?',
undef, $otherUserID);
$vars->{'bugs_activity'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM bugs_activity WHERE who = ?',
undef, $otherUserID);
$vars->{'email_setting'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM email_setting WHERE user_id = ?',
undef, $otherUserID);
$vars->{'flags'}{'requestee'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM flags WHERE requestee_id = ? AND is_active = 1',
undef, $otherUserID);
$vars->{'flags'}{'setter'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM flags WHERE setter_id = ?',
undef, $otherUserID);
$vars->{'longdescs'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM longdescs WHERE who = ?',
undef, $otherUserID);
$vars->{'namedqueries'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM namedqueries WHERE userid = ?',
undef, $otherUserID);
$vars->{'profile_setting'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM profile_setting WHERE user_id = ?',
undef, $otherUserID);
$vars->{'profiles_activity'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM profiles_activity WHERE who = ? AND userid != ?',
undef, ($otherUserID, $otherUserID));
$vars->{'series'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM series WHERE creator = ?',
undef, $otherUserID);
$vars->{'votes'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM votes WHERE who = ?',
undef, $otherUserID);
$vars->{'watch'}{'watched'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM watch WHERE watched = ?',
undef, $otherUserID);
$vars->{'watch'}{'watcher'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM watch WHERE watcher = ?',
undef, $otherUserID);
$vars->{'whine_events'} = $dbh->selectrow_array(
'SELECT COUNT(*) FROM whine_events WHERE owner_userid = ?',
undef, $otherUserID);
$vars->{'whine_schedules'} = $dbh->selectrow_array(
qq{SELECT COUNT(distinct eventid)
FROM whine_schedules
WHERE mailto = ?
AND mailto_type = ?
},
undef, ($otherUserID, MAILTO_USER));
$template->process('admin/users/confirm-delete.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
###########################################################################
} elsif ($action eq 'delete') {
my $otherUser = check_user($otherUserID, $otherUserLogin);
$otherUserID = $otherUser->id;
# Cache for user accounts.
my %usercache = (0 => new Bugzilla::User());
my %updatedbugs;
# Lock tables during the check+removal session.
# XXX: if there was some change on these tables after the deletion
# confirmation checks, we may do something here we haven't warned
# about.
$dbh->bz_lock_tables('bugs WRITE',
'bugs_activity WRITE',
'attachments READ',
'fielddefs READ',
'products READ',
'components READ',
'logincookies WRITE',
'profiles WRITE',
'profiles_activity WRITE',
'email_setting WRITE',
'profile_setting WRITE',
'groups READ',
'bug_group_map READ',
'user_group_map WRITE',
'group_group_map READ',
'flags WRITE',
'flagtypes READ',
'cc WRITE',
'namedqueries WRITE',
'tokens WRITE',
'votes WRITE',
'watch WRITE',
'series WRITE',
'series_data WRITE',
'whine_schedules WRITE',
'whine_queries WRITE',
'whine_events WRITE');
Param('allowuserdeletion')
|| ThrowUserError('users_deletion_disabled');
$editusers || ThrowUserError('auth_failure',
{group => "editusers",
action => "delete",
object => "users"});
@{$otherUser->product_responsibilities()}
&& ThrowUserError('user_has_responsibility');
Bugzilla->logout_user($otherUser);
# Get the timestamp for LogActivityEntry.
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
# When we update a bug_activity entry, we update the bug timestamp, too.
my $sth_set_bug_timestamp =
$dbh->prepare('UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
# Reference removals which need LogActivityEntry.
my $statement_flagupdate = 'UPDATE flags set requestee_id = NULL
WHERE bug_id = ?
AND attach_id %s
AND requestee_id = ?';
my $sth_flagupdate_attachment =
$dbh->prepare(sprintf($statement_flagupdate, '= ?'));
my $sth_flagupdate_bug =
$dbh->prepare(sprintf($statement_flagupdate, 'IS NULL'));
my $buglist = $dbh->selectall_arrayref('SELECT DISTINCT bug_id, attach_id
FROM flags
WHERE requestee_id = ?',
undef, $otherUserID);
foreach (@$buglist) {
my ($bug_id, $attach_id) = @$_;
my @old_summaries = Bugzilla::Flag::snapshot($bug_id, $attach_id);
if ($attach_id) {
$sth_flagupdate_attachment->execute($bug_id, $attach_id, $otherUserID);
}
else {
$sth_flagupdate_bug->execute($bug_id, $otherUserID);
}
my @new_summaries = Bugzilla::Flag::snapshot($bug_id, $attach_id);
# Let update_activity do all the dirty work, including setting
# the bug timestamp.
Bugzilla::Flag::update_activity($bug_id, $attach_id,
$dbh->quote($timestamp),
\@old_summaries, \@new_summaries);
$updatedbugs{$bug_id} = 1;
}
# Simple deletions in referred tables.
$dbh->do('DELETE FROM email_setting WHERE user_id = ?', undef,
$otherUserID);
$dbh->do('DELETE FROM logincookies WHERE userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM namedqueries WHERE userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM profile_setting WHERE user_id = ?', undef,
$otherUserID);
$dbh->do('DELETE FROM profiles_activity WHERE userid = ? OR who = ?', undef,
($otherUserID, $otherUserID));
$dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $otherUserID);
$dbh->do('DELETE FROM user_group_map WHERE user_id = ?', undef,
$otherUserID);
$dbh->do('DELETE FROM votes WHERE who = ?', undef, $otherUserID);
$dbh->do('DELETE FROM watch WHERE watcher = ? OR watched = ?', undef,
($otherUserID, $otherUserID));
# Deletions in referred tables which need LogActivityEntry.
$buglist = $dbh->selectcol_arrayref('SELECT bug_id FROM cc
WHERE who = ?',
undef, $otherUserID);
$dbh->do('DELETE FROM cc WHERE who = ?', undef, $otherUserID);
foreach my $bug_id (@$buglist) {
LogActivityEntry($bug_id, 'cc', $otherUser->login, '', $userid,
$timestamp);
$sth_set_bug_timestamp->execute($timestamp, $bug_id);
$updatedbugs{$bug_id} = 1;
}
# Even more complex deletions in referred tables.
my $id;
# 1) Series
my $sth_seriesid = $dbh->prepare(
'SELECT series_id FROM series WHERE creator = ?');
my $sth_deleteSeries = $dbh->prepare(
'DELETE FROM series WHERE series_id = ?');
my $sth_deleteSeriesData = $dbh->prepare(
'DELETE FROM series_data WHERE series_id = ?');
$sth_seriesid->execute($otherUserID);
while ($id = $sth_seriesid->fetchrow_array()) {
$sth_deleteSeriesData->execute($id);
$sth_deleteSeries->execute($id);
}
# 2) Whines
my $sth_whineidFromSchedules = $dbh->prepare(
qq{SELECT eventid FROM whine_schedules
WHERE mailto = ? AND mailto_type = ?});
my $sth_whineidFromEvents = $dbh->prepare(
'SELECT id FROM whine_events WHERE owner_userid = ?');
my $sth_deleteWhineEvent = $dbh->prepare(
'DELETE FROM whine_events WHERE id = ?');
my $sth_deleteWhineQuery = $dbh->prepare(
'DELETE FROM whine_queries WHERE eventid = ?');
my $sth_deleteWhineSchedule = $dbh->prepare(
'DELETE FROM whine_schedules WHERE eventid = ?');
$sth_whineidFromSchedules->execute($otherUserID, MAILTO_USER);
while ($id = $sth_whineidFromSchedules->fetchrow_array()) {
$sth_deleteWhineQuery->execute($id);
$sth_deleteWhineSchedule->execute($id);
$sth_deleteWhineEvent->execute($id);
}
$sth_whineidFromEvents->execute($otherUserID);
while ($id = $sth_whineidFromEvents->fetchrow_array()) {
$sth_deleteWhineQuery->execute($id);
$sth_deleteWhineSchedule->execute($id);
$sth_deleteWhineEvent->execute($id);
}
# 3) Bugs
# 3.1) fall back to the default assignee
$buglist = $dbh->selectall_arrayref(
'SELECT bug_id, initialowner
FROM bugs
INNER JOIN components ON components.id = bugs.component_id
WHERE assigned_to = ?', undef, $otherUserID);
my $sth_updateAssignee = $dbh->prepare(
'UPDATE bugs SET assigned_to = ?, delta_ts = ? WHERE bug_id = ?');
foreach my $bug (@$buglist) {
my ($bug_id, $default_assignee_id) = @$bug;
$sth_updateAssignee->execute($default_assignee_id,
$timestamp, $bug_id);
$updatedbugs{$bug_id} = 1;
$default_assignee_id ||= 0;
$usercache{$default_assignee_id} ||=
new Bugzilla::User($default_assignee_id);
LogActivityEntry($bug_id, 'assigned_to', $otherUser->login,
$usercache{$default_assignee_id}->login,
$userid, $timestamp);
}
# 3.2) fall back to the default QA contact
$buglist = $dbh->selectall_arrayref(
'SELECT bug_id, initialqacontact
FROM bugs
INNER JOIN components ON components.id = bugs.component_id
WHERE qa_contact = ?', undef, $otherUserID);
my $sth_updateQAcontact = $dbh->prepare(
'UPDATE bugs SET qa_contact = ?, delta_ts = ? WHERE bug_id = ?');
foreach my $bug (@$buglist) {
my ($bug_id, $default_qa_contact_id) = @$bug;
$sth_updateQAcontact->execute($default_qa_contact_id,
$timestamp, $bug_id);
$updatedbugs{$bug_id} = 1;
$default_qa_contact_id ||= 0;
$usercache{$default_qa_contact_id} ||=
new Bugzilla::User($default_qa_contact_id);
LogActivityEntry($bug_id, 'qa_contact', $otherUser->login,
$usercache{$default_qa_contact_id}->login,
$userid, $timestamp);
}
# Finally, remove the user account itself.
$dbh->do('DELETE FROM profiles WHERE userid = ?', undef, $otherUserID);
$dbh->bz_unlock_tables();
$vars->{'message'} = 'account_deleted';
$vars->{'otheruser'}{'login'} = $otherUser->login;
$vars->{'restrictablegroups'} = $user->bless_groups();
$template->process('admin/users/search.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
# Send mail about what we've done to bugs.
# The deleted user is not notified of the changes.
foreach (keys(%updatedbugs)) {
Bugzilla::BugMail::Send($_);
}
###########################################################################
} else {
$vars->{'action'} = $action;
ThrowCodeError('action_unrecognized', $vars);
}
exit;
###########################################################################
# Helpers
###########################################################################
# Try to build a user object using its ID, else its login name, and throw
# an error if the user does not exist.
sub check_user {
my ($otherUserID, $otherUserLogin) = @_;
my $otherUser;
my $vars = {};
if ($otherUserID) {
$otherUser = Bugzilla::User->new($otherUserID);
$vars->{'user_id'} = $otherUserID;
}
elsif ($otherUserLogin) {
$otherUser = Bugzilla::User->new_from_login($otherUserLogin);
$vars->{'user_login'} = $otherUserLogin;
}
($otherUser && $otherUser->id) || ThrowCodeError('invalid_user', $vars);
return $otherUser;
}
# Copy incoming list selection values from CGI params to template variables.
sub mirrorListSelectionValues {
if (defined($cgi->param('matchtype'))) {
foreach ('matchstr', 'matchtype', 'grouprestrict', 'groupid') {
$vars->{'listselectionvalues'}{$_} = $cgi->param($_);
}
}
}
# Give a list of IDs of groups the user can see.
sub visibleGroupsAsString {
return join(', ', @{$user->visible_groups_direct()});
}
# Determine whether the user can see a user. (Checks for existence, too.)
sub canSeeUser {
my $otherUserID = shift;
my $query;
if (Param('usevisibilitygroups')) {
# If the user can see no groups, then no users are visible either.
my $visibleGroups = visibleGroupsAsString() || return 0;
$query = qq{SELECT COUNT(DISTINCT userid)
FROM profiles, user_group_map
WHERE userid = ?
AND user_id = userid
AND isbless = 0
AND group_id IN ($visibleGroups)
};
} else {
$query = qq{SELECT COUNT(userid)
FROM profiles
WHERE userid = ?
};
}
return $dbh->selectrow_array($query, undef, $otherUserID);
}
# Retrieve user data for the user editing form. User creation and user
# editing code rely on this to call derive_groups().
sub userDataToVars {
my $otheruserid = shift;
my $otheruser = new Bugzilla::User($otheruserid);
my $query;
my $dbh = Bugzilla->dbh;
$otheruser->derive_groups();
$vars->{'otheruser'} = $otheruser;
$vars->{'groups'} = $user->bless_groups();
$vars->{'permissions'} = $dbh->selectall_hashref(
qq{SELECT id,
COUNT(directmember.group_id) AS directmember,
COUNT(regexpmember.group_id) AS regexpmember,
COUNT(derivedmember.group_id) AS derivedmember,
COUNT(directbless.group_id) AS directbless
FROM groups
LEFT JOIN user_group_map AS directmember
ON directmember.group_id = id
AND directmember.user_id = ?
AND directmember.isbless = 0
AND directmember.grant_type = ?
LEFT JOIN user_group_map AS regexpmember
ON regexpmember.group_id = id
AND regexpmember.user_id = ?
AND regexpmember.isbless = 0
AND regexpmember.grant_type = ?
LEFT JOIN user_group_map AS derivedmember
ON derivedmember.group_id = id
AND derivedmember.user_id = ?
AND derivedmember.isbless = 0
AND derivedmember.grant_type = ?
LEFT JOIN user_group_map AS directbless
ON directbless.group_id = id
AND directbless.user_id = ?
AND directbless.isbless = 1
AND directbless.grant_type = ?
} . $dbh->sql_group_by('id'),
'id', undef,
($otheruserid, GRANT_DIRECT,
$otheruserid, GRANT_REGEXP,
$otheruserid, GRANT_DERIVED,
$otheruserid, GRANT_DIRECT));
# Find indirect bless permission.
$query = qq{SELECT groups.id
FROM groups, user_group_map AS ugm, group_group_map AS ggm
WHERE ugm.user_id = ?
AND groups.id = ggm.grantor_id
AND ggm.member_id = ugm.group_id
AND ugm.isbless = 0
AND ggm.grant_type = ?
} . $dbh->sql_group_by('id');
foreach (@{$dbh->selectall_arrayref($query, undef,
($otheruserid, GROUP_BLESS))}) {
# Merge indirect bless permissions into permission variable.
$vars->{'permissions'}{${$_}[0]}{'indirectbless'} = 1;
}
}