diff options
Diffstat (limited to 'Bugzilla')
-rw-r--r-- | Bugzilla/BugMail.pm | 33 | ||||
-rw-r--r-- | Bugzilla/Error.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Flag.pm | 40 | ||||
-rw-r--r-- | Bugzilla/FlagType.pm | 15 | ||||
-rw-r--r-- | Bugzilla/Search.pm | 21 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 5 | ||||
-rw-r--r-- | Bugzilla/User.pm | 502 |
7 files changed, 505 insertions, 114 deletions
diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index da25b6a70..16b44d6ff 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -111,14 +111,6 @@ sub Send($;$) { # require abuse we do. GetVersionTable(); - # Since any email recipients must be rederived if the user has not - # been rederived since the most recent group change, figure out when that - # is once and determine the need to rederive users using the same DB - # access that gets the user's email address each time a person is - # processed. - SendSQL("SELECT MAX(last_changed) FROM groups"); - ($last_changed) = FetchSQLData(); - # Make sure to clean up _all_ package vars here. Yuck... $nametoexclude = $recipients->{'changer'} || ""; @{$force{'CClist'}} = (exists $recipients->{'cc'} && @@ -710,19 +702,13 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { return; } - - SendSQL("SELECT userid, (refreshed_when > " . SqlQuote($last_changed) . - ") FROM profiles WHERE login_name = " . SqlQuote($person)); - my ($userid, $current) = (FetchSQLData()); + # This routine should really get passed a userid + # This rederives groups as a side effect + my $user = Bugzilla::User->new_from_login($person); + my $userid = $user->id; $seen{$person} = 1; - detaint_natural($userid); - - if (!$current) { - DeriveGroup($userid); - } - # if this person doesn't have permission to see info on this bug, # return. # @@ -732,12 +718,11 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { # quietly disappear from their radar. # return unless CanSeeBug($id, $userid); - # Drop any non-insiders if the comment is private - return if (Param("insidergroup") && + return if (Param("insidergroup") && ($anyprivate != 0) && - (!UserInGroup(Param("insidergroup"), $userid))); + (!$user->groups->{Param("insidergroup")})); # We shouldn't send changedmail if this is a dependency mail, and any of # the depending bugs is not visible to the user. @@ -761,8 +746,8 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { } # Don't send estimated_time if user not in the group, or not enabled if ($f ne 'estimated_time' || - UserInGroup(Param('timetrackinggroup'), $userid)) { - + $user->groups->{Param('timetrackinggroup')}) { + my $desc = $fielddescription{$f}; $head .= FormatDouble($desc, $value); } @@ -781,7 +766,7 @@ sub NewProcessOnePerson ($$$$$$$$$$$$$) { ($diff->{'fieldname'} eq 'estimated_time' || $diff->{'fieldname'} eq 'remaining_time' || $diff->{'fieldname'} eq 'work_time')) { - if (UserInGroup(Param("timetrackinggroup"), $userid)) { + if ($user->groups->{Param("timetrackinggroup")}) { $add_diff = 1; } } else { diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index 485646274..a23d6db15 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -34,9 +34,6 @@ sub ThrowUserError { $vars->{error} = $error; - # Need to do this until User.pm goes in, so that the footer is correct - $vars->{user} = $::vars->{user}; - Bugzilla->dbh->do("UNLOCK TABLES") if $unlock_tables; print Bugzilla->cgi->header(); diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm index f8eb8a4a4..4a1752cd0 100644 --- a/Bugzilla/Flag.pm +++ b/Bugzilla/Flag.pm @@ -177,18 +177,14 @@ sub validate { if ($requestee_email ne $flag->{'requestee'}->{'email'}) { # We know the requestee exists because we ran # Bugzilla::User::match_field before getting here. - # ConfirmGroup makes sure their group settings - # are up-to-date or calls DeriveGroups to update them. - my $requestee_id = &::DBname_to_id($requestee_email); - &::ConfirmGroup($requestee_id); + my $requestee = Bugzilla::User->new_from_login($requestee_email); # Throw an error if the user can't see the bug. - if (!&::CanSeeBug($bug_id, $requestee_id)) + if (!&::CanSeeBug($bug_id, $requestee->id)) { ThrowUserError("flag_requestee_unauthorized", { flag_type => $flag->{'type'}, - requestee => - new Bugzilla::User($requestee_id), + requestee => $requestee, bug_id => $bug_id, attach_id => $flag->{target}->{attachment}->{id} }); @@ -198,13 +194,12 @@ sub validate { # the requestee isn't in the group of insiders who can see it. if ($flag->{target}->{attachment}->{exists} && $data->{'isprivate'} - && &::Param("insidergroup") - && !&::UserInGroup(&::Param("insidergroup"), $requestee_id)) + && Param("insidergroup") + && !$requestee->in_group(Param("insidergroup"))) { ThrowUserError("flag_requestee_unauthorized_attachment", { flag_type => $flag->{'type'}, - requestee => - new Bugzilla::User($requestee_id), + requestee => $requestee, bug_id => $bug_id, attach_id => $flag->{target}->{attachment}->{id} }); @@ -236,7 +231,7 @@ sub process { my @old_summaries; foreach my $flag (@$flags) { my $summary = $flag->{'type'}->{'name'} . $flag->{'status'}; - $summary .= "($flag->{'requestee'}->{'email'})" if $flag->{'requestee'}; + $summary .= "(" . $flag->{'requestee'}->login . ")" if $flag->{'requestee'}; push(@old_summaries, $summary); } @@ -275,7 +270,7 @@ sub process { my @new_summaries; foreach my $flag (@$flags) { my $summary = $flag->{'type'}->{'name'} . $flag->{'status'}; - $summary .= "($flag->{'requestee'}->{'email'})" if $flag->{'requestee'}; + $summary .= "(" . $flag->{'requestee'}->login . ")" if $flag->{'requestee'}; push(@new_summaries, $summary); } @@ -307,7 +302,7 @@ sub create { # Insert a record for the flag into the flags table. my $attach_id = $flag->{'target'}->{'attachment'}->{'id'} || "NULL"; - my $requestee_id = $flag->{'requestee'} ? $flag->{'requestee'}->{'id'} : "NULL"; + my $requestee_id = $flag->{'requestee'} ? $flag->{'requestee'}->id : "NULL"; &::SendSQL("INSERT INTO flags (id, type_id, bug_id, attach_id, requestee_id, setter_id, status, @@ -317,7 +312,7 @@ sub create { $flag->{'target'}->{'bug'}->{'id'}, $attach_id, $requestee_id, - $flag->{'setter'}->{'id'}, + " . $flag->{'setter'}->id . ", '$flag->{'status'}', $timestamp, $timestamp)"); @@ -380,7 +375,7 @@ sub modify { # the flag isn't specifically requestable || $status ne "?" # or the flag isn't being requested || ($flag->{'requestee'} # or the requestee hasn't changed - && ($requestee_email eq $flag->{'requestee'}->{'email'}))); + && ($requestee_email eq $flag->{'requestee'}->login))); # Since the status is validated, we know it's safe, but it's still # tainted, so we have to detaint it before using it in a query. @@ -568,14 +563,15 @@ sub notify { { my @new_cc_list; foreach my $cc (split(/[, ]+/, $flag->{'type'}->{'cc_list'})) { - my $user_id = &::DBname_to_id($cc) || next; - # re-derive permissions if necessary - &::ConfirmGroup($user_id, TABLES_ALREADY_LOCKED); + my $ccuser = Bugzilla::User->new_from_login($cc, + TABLES_ALREADY_LOCKED) + || next; + next if $flag->{'target'}->{'bug'}->{'restricted'} - && !&::CanSeeBug($flag->{'target'}->{'bug'}->{'id'}, $user_id); + && !&::CanSeeBug($flag->{'target'}->{'bug'}->{'id'}, $ccuser->id); next if $flag->{'target'}->{'attachment'}->{'isprivate'} && Param("insidergroup") - && !&::UserInGroup(Param("insidergroup"), $user_id); + && !$ccuser->in_group(Param("insidergroup")); push(@new_cc_list, $cc); } $flag->{'type'}->{'cc_list'} = join(", ", @new_cc_list); @@ -646,7 +642,7 @@ sub perlify_record { id => $id , type => Bugzilla::FlagType::get($type_id) , target => GetTarget($bug_id, $attach_id) , - requestee => new Bugzilla::User($requestee_id) , + requestee => $requestee_id ? new Bugzilla::User($requestee_id) : undef, setter => new Bugzilla::User($setter_id) , status => $status , }; diff --git a/Bugzilla/FlagType.pm b/Bugzilla/FlagType.pm index 523f60190..7fbe1f142 100644 --- a/Bugzilla/FlagType.pm +++ b/Bugzilla/FlagType.pm @@ -219,20 +219,17 @@ sub validate { && trim($data->{"requestee_type-$id"})) { my $requestee_email = trim($data->{"requestee_type-$id"}); - my $requestee_id = &::DBname_to_id($requestee_email); # We know the requestee exists because we ran # Bugzilla::User::match_field before getting here. - # ConfirmGroup makes sure their group settings - # are up-to-date or calls DeriveGroups to update them. - &::ConfirmGroup($requestee_id); + my $requestee = Bugzilla::User->new_from_login($requestee_email); # Throw an error if the user can't see the bug. - if (!&::CanSeeBug($bug_id, $requestee_id)) + if (!&::CanSeeBug($bug_id, $requestee->id)) { ThrowUserError("flag_requestee_unauthorized", { flag_type => $flag_type, - requestee => new Bugzilla::User($requestee_id), + requestee => $requestee, bug_id => $bug_id, attach_id => $attach_id }); } @@ -240,13 +237,13 @@ sub validate { # Throw an error if the target is a private attachment and # the requestee isn't in the group of insiders who can see it. if ($attach_id - && &::Param("insidergroup") + && Param("insidergroup") && $data->{'isprivate'} - && !&::UserInGroup(&::Param("insidergroup"), $requestee_id)) + && !$requestee->in_group(Param("insidergroup"))) { ThrowUserError("flag_requestee_unauthorized_attachment", { flag_type => $flag_type, - requestee => new Bugzilla::User($requestee_id), + requestee => $requestee, bug_id => $bug_id, attach_id => $attach_id }); } diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 45c26fdf2..df2ab58e3 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -926,28 +926,31 @@ sub init { # Make sure we create a legal SQL query. @andlist = ("1 = 1") if !@andlist; + my $user = Bugzilla->user; + my $query = "SELECT " . join(', ', @fields) . " FROM $suppstring" . " LEFT JOIN bug_group_map " . " ON bug_group_map.bug_id = bugs.bug_id "; - if (defined @{$::vars->{user}{groupids}} && @{$::vars->{user}{groupids}} > 0) { - $query .= " AND bug_group_map.group_id NOT IN (" . join(',', @{$::vars->{user}{groupids}}) . ") "; - } + if ($user) { + if (%{$user->groups}) { + $query .= " AND bug_group_map.group_id NOT IN (" . join(',', values(%{$user->groups})) . ") "; + } - if ($::vars->{user}{userid}) { - $query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = $::userid "; + $query .= " LEFT JOIN cc ON cc.bug_id = bugs.bug_id AND cc.who = " . $user->id; } $query .= " WHERE " . join(' AND ', (@wherepart, @andlist)) . " AND ((bug_group_map.group_id IS NULL)"; - if ($::vars->{user}{userid}) { - $query .= " OR (bugs.reporter_accessible = 1 AND bugs.reporter = $::userid) " . + if ($user) { + my $userid = $user->id; + $query .= " OR (bugs.reporter_accessible = 1 AND bugs.reporter = $userid) " . " OR (bugs.cclist_accessible = 1 AND cc.who IS NOT NULL) " . - " OR (bugs.assigned_to = $::userid) "; + " OR (bugs.assigned_to = $userid) "; if (Param('useqacontact')) { - $query .= "OR (bugs.qa_contact = $::userid) "; + $query .= "OR (bugs.qa_contact = $userid) "; } } diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index e596af226..7c084ecb9 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -256,7 +256,10 @@ sub create { # Generic linear search function 'lsearch' => \&Bugzilla::Util::lsearch, - # UserInGroup - you probably want to cache this + # Currently logged in user, if any + 'user' => sub { return Bugzilla->user; }, + + # UserInGroup. Deprecated - use the user.* functions instead 'UserInGroup' => \&::UserInGroup, # SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index fde9d336b..f5df92063 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -19,6 +19,8 @@ # # Contributor(s): Myk Melez <myk@mozilla.org> # Erik Stambaugh <not_erik@dasbistro.com> +# Bradley Baetz <bbaetz@acm.org> +# Joel Peshkin <bugreport@peshkin.net> ################################################################################ # Module Initialization @@ -30,57 +32,311 @@ use strict; # This module implements utilities for dealing with Bugzilla users. package Bugzilla::User; +use Bugzilla::Config; +use Bugzilla::Util; + ################################################################################ # Functions ################################################################################ -my $user_cache = {}; sub new { - # Returns a hash of information about a particular user. + my $invocant = shift; + return $invocant->_create("userid=?", @_); +} +# This routine is sort of evil. Nothing except the login stuff should +# be dealing with addresses as an input, and they can get the id as a +# side effect of the other sql they have to do anyway. +# Bugzilla::BugMail still does this, probably as a left over from the +# pre-id days. Provide this as a helper, but don't document it, and hope +# that it can go away. +# The request flag stuff also does this, but it really should be passing +# in the id its already had to validate (or the User.pm object, of course) +sub new_from_login { + my $invocant = shift; + return $invocant->_create("login_name=?", @_); +} + +# Internal helper for the above |new| methods +# $cond is a string (including a placeholder ?) for the search +# requirement for the profiles table +sub _create { my $invocant = shift; my $class = ref($invocant) || $invocant; - - my $exists = 1; - my ($id, $name, $email) = @_; - - return undef if !$id; - return $user_cache->{$id} if exists($user_cache->{$id}); - - my $self = { 'id' => $id }; - - bless($self, $class); - - if (!$name && !$email) { - &::PushGlobalSQLState(); - &::SendSQL("SELECT 1, realname, login_name FROM profiles WHERE userid = $id"); - ($exists, $name, $email) = &::FetchSQLData(); - &::PopGlobalSQLState(); + + my $cond = shift; + my $val = shift; + + # We're checking for validity here, so any value is OK + trick_taint($val); + + my $tables_locked_for_derive_groups = shift; + + my $dbh = Bugzilla->dbh; + + my ($id, + $login, + $name, + $mybugslink) = $dbh->selectrow_array(qq{SELECT userid, + login_name, + realname, + mybugslink + FROM profiles + WHERE $cond}, + undef, + $val); + + return undef unless defined $id; + + my $self = { id => $id, + name => $name, + login => $login, + showmybugslink => $mybugslink, + }; + + bless ($self, $class); + + # Now update any old group information if needed + my $result = $dbh->selectrow_array(q{SELECT 1 + FROM profiles, groups + WHERE userid=? + AND profiles.refreshed_when <= + groups.last_changed}, + undef, + $id); + + if ($result) { + $self->derive_groups($tables_locked_for_derive_groups); } - - $self->{'name'} = $name; - $self->{'email'} = $email || "__UNKNOWN__"; - $self->{'exists'} = $exists; - - # Generate a string to identify the user by name + email if the user - # has a name or by email only if she doesn't. - $self->{'identity'} = $name ? "$name <$email>" : $email; - - # Generate a user "nickname" -- i.e. a shorter, not-necessarily-unique name - # by which to identify the user. Currently the part of the user's email - # address before the at sign (@), but that could change, especially if we - # implement usernames not dependent on email address. - my @email_components = split("@", $email); - $self->{'nick'} = $email_components[0]; - - $user_cache->{$id} = $self; - + return $self; } +# Accessors for user attributes +sub id { $_[0]->{id}; } +sub login { $_[0]->{login}; } +sub email { $_[0]->{login}; } +sub name { $_[0]->{name}; } +sub showmybugslink { $_[0]->{showmybugslink}; } + +# Generate a string to identify the user by name + email if the user +# has a name or by email only if she doesn't. +sub identity { + my $self = shift; + + if (!defined $self->{identity}) { + $self->{identity} = + $self->{name} ? "$self->{name} <$self->{login}>" : $self->{login}; + } + + return $self->{identity}; +} + +sub nick { + my $self = shift; + + if (!defined $self->{nick}) { + $self->{nick} = (split(/@/, $self->{login}, 2))[0]; + } + + return $self->{nick}; +} + +sub queries { + my $self = shift; + + return $self->{queries} if defined $self->{queries}; + + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare(q{ SELECT name, query, linkinfooter + FROM namedqueries + WHERE userid=? + ORDER BY UPPER(name)}); + $sth->execute($self->{id}); + + my @queries; + while (my $row = $sth->fetch) { + push (@queries, { + name => $row->[0], + query => $row->[1], + linkinfooter => $row->[2], + }); + } + $self->{queries} = \@queries; + + return $self->{queries}; +} + +sub flush_queries_cache { + my $self = shift; + + delete $self->{queries}; +} + +sub groups { + my $self = shift; + + return $self->{groups} if defined $self->{groups}; + + my $dbh = Bugzilla->dbh; + my $groups = $dbh->selectcol_arrayref(q{SELECT DISTINCT groups.name, group_id + FROM groups, user_group_map + WHERE groups.id=user_group_map.group_id + AND user_id=? + AND isbless=0}, + { Columns=>[1,2] }, + $self->{id}); + + # The above gives us an arrayref [name, id, name, id, ...] + # Convert that into a hashref + my %groups = @$groups; + $self->{groups} = \%groups; + + return $self->{groups}; +} + +sub in_group { + my ($self, $group) = @_; + + # If we already have the info, just return it. + return defined($self->{groups}->{$group}) if defined $self->{groups}; + + # Otherwise, go check for it + + my $dbh = Bugzilla->dbh; + + my $res = $dbh->selectrow(q{SELECT 1 + FROM groups, user_group_map + WHERE groups.id=user_group_map.group_id + AND user_group_map.user_id=? + AND isbless=0 + AND groups.name=?}, + undef, + $self->id, + $group); + + return defined($res); +} + +sub derive_groups { + my ($self, $already_locked) = @_; + + my $id = $self->id; + + my $dbh = Bugzilla->dbh; + + my $sth; + + $dbh->do(q{LOCK TABLES profiles WRITE, + user_group_map WRITE, + group_group_map READ, + groups READ}) unless $already_locked; + + # avoid races, we are only up to date as of the BEGINNING of this process + my $time = $dbh->selectrow_array("SELECT NOW()"); + + # first remove any old derived stuff for this user + $dbh->do(q{DELETE FROM user_group_map + WHERE user_id = ? + AND isderived = 1}, + undef, + $id); + + my %groupidsadded = (); + # add derived records for any matching regexps + + $sth = $dbh->prepare("SELECT id, userregexp FROM groups WHERE userregexp != ''"); + $sth->execute; + + my $group_insert; + while (my $row = $sth->fetch) { + if ($self->{login} =~ m/$row->[1]/i) { + $group_insert ||= $dbh->prepare(q{INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES (?, ?, 0, 1)}); + $groupidsadded{$row->[0]} = 1; + $group_insert->execute($id, $row->[0]); + } + } + + # Get a list of the groups of which the user is a member. + my %groupidschecked = (); + + my @groupidstocheck = @{$dbh->selectcol_arrayref(q{SELECT group_id + FROM user_group_map + WHERE user_id=?}, + undef, + $id)}; + + # Each group needs to be checked for inherited memberships once. + my $group_sth; + while (@groupidstocheck) { + my $group = shift @groupidstocheck; + if (!defined($groupidschecked{"$group"})) { + $groupidschecked{"$group"} = 1; + $group_sth ||= $dbh->prepare(q{SELECT grantor_id + FROM group_group_map + WHERE member_id=? + AND isbless=0}); + $group_sth->execute($group); + while (my $groupid = $group_sth->fetchrow_array) { + if (!defined($groupidschecked{"$groupid"})) { + push(@groupidstocheck,$groupid); + } + if (!$groupidsadded{$groupid}) { + $groupidsadded{$groupid} = 1; + $group_insert ||= $dbh->prepare(q{INSERT INTO user_group_map + (user_id, group_id, isbless, isderived) + VALUES (?, ?, 0, 1)}); + $group_insert->execute($id, $groupid); + } + } + } + } + + $dbh->do(q{UPDATE profiles + SET refreshed_when = ? + WHERE userid=?}, + undef, + $time, + $id); + $dbh->do("UNLOCK TABLES") unless $already_locked; +} + +sub can_bless { + my $self = shift; + + return $self->{can_bless} if defined $self->{can_bless}; + + my $dbh = Bugzilla->dbh; + # First check if the user can explicitly bless a group + my $res = $dbh->selectrow_arrayref(q{SELECT 1 + FROM user_group_map + WHERE user_id=? + AND isbless=1}, + undef, + $self->{id}); + if (!$res) { + # Now check if user is a member of a group that can bless a group + $res = $dbh->selectrow_arrayref(q{SELECT 1 + FROM user_group_map, group_group_map + WHERE user_group_map.user_id=? + AND user_group_map.group_id=member_id + AND group_group_map.isbless=1}, + undef, + $self->{id}); + } + + $self->{can_bless} = $res ? 1 : 0; + + return $self->{can_bless}; +} + sub match { # Generates a list of users whose login name (email address) or real name # matches a substring or wildcard. + # This is also called if matches are disabled (for error checking), but + # in this case only the exact match code will end up running. # $str contains the string to match, while $limit contains the # maximum number of records to retrieve. @@ -99,7 +355,8 @@ sub match { my $wildstr = $str; - if ($wildstr =~ s/\*/\%/g) { # don't do wildcards if no '*' in the string + if ($wildstr =~ s/\*/\%/g && # don't do wildcards if no '*' in the string + Param('usermatchmode') ne 'off') { # or if we only want exact matches # Build the query. my $sqlstr = &::SqlQuote($wildstr); @@ -159,7 +416,7 @@ sub match { # order @users by alpha - @users = sort { uc($a->{'email'}) cmp uc($b->{'email'}) } @users; + @users = sort { uc($a->login) cmp uc($b->login) } @users; return \@users; } @@ -251,9 +508,6 @@ sub match_field { } $fields = $expanded_fields; - # Skip all of this if the option has been turned off - return 1 if (&::Param('usermatchmode') eq 'off'); - for my $field (keys %{$fields}) { # Tolerate fields that do not exist. @@ -312,14 +566,14 @@ sub match_field { # skip confirmation for exact matches if ((scalar(@{$users}) == 1) - && (@{$users}[0]->{'email'} eq $query)) + && (@{$users}[0]->{'login'} eq $query)) { # delimit with spaces if necessary if ($vars->{'form'}->{$field}) { $vars->{'form'}->{$field} .= " "; } - $vars->{'form'}->{$field} .= @{$users}[0]->{'email'}; - push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'email'}; + $vars->{'form'}->{$field} .= @{$users}[0]->{'login'}; + push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'login'}; next; } @@ -333,8 +587,8 @@ sub match_field { if ($vars->{'form'}->{$field}) { $vars->{'form'}->{$field} .= " "; } - $vars->{'form'}->{$field} .= @{$users}[0]->{'email'}; - push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'email'}; + $vars->{'form'}->{$field} .= @{$users}[0]->{'login'}; + push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'login'}; $need_confirm = 1 if &::Param('confirmuniqueusermatch'); } @@ -443,3 +697,159 @@ sub email_prefs { } 1; + +__END__ + +=head1 NAME + +Bugzilla::User - Object for a Bugzilla user + +=head1 SYNOPSIS + + use Bugzilla::User; + + my $user = new Bugzilla::User($id); + +=head1 DESCRIPTION + +This package handles Bugzilla users. Data obtained from here is read-only; +there is currently no way to modify a user from this package. + +Note that the currently logged in user (if any) is available via +L<Bugzilla-E<gt>user|Bugzilla/"user">. + +=head1 METHODS + +=over 4 + +=item C<new($userid)> + +Creates a new C<Bugzilla::User> object for the given user id. Returns +C<undef> if no matching user is found. + +=begin undocumented + +=item C<new_from_login($login)> + +Creates a new C<Bugzilla::User> object given the provided login. Returns +C<undef> if no matching user is found. + +This routine should not be required in general; most scripts should be using +userids instead. + +This routine and C<new> both take an extra optional argument, which is +passed as the argument to C<derive_groups> to avoid locking. See that +routine's documentation for details. + +=end undocumented + +=item C<id> + +Returns the userid for this user. + +=item C<login> + +Returns the login name for this user. + +=item C<email> + +Returns the user's email address. Currently this is the same value as the +login. + +=item C<name> + +Returns the 'real' name for this user, if any. + +=item C<showmybugslink> + +Returns C<1> if the user has set his preference to show the 'My Bugs' link in +the page footer, and C<0> otherwise. + +=item C<identity> + +Retruns a string for the identity of the user. This will be of the form +C<name E<lt>emailE<gt>> if the user has specified a name, and C<email> +otherwise. + +=item C<nick> + +Returns a user "nickname" -- i.e. a shorter, not-necessarily-unique name by +which to identify the user. Currently the part of the user's email address +before the at sign (@), but that could change, especially if we implement +usernames not dependent on email address. + +=item C<queries> + +Returns an array of the user's named queries, sorted in a case-insensitive +order by name. Each entry is a hash with three keys: + +=over + +=item * + +name - The name of the query + +=item * + +query - The text for the query + +=item * + +linkinfooter - Whether or not the query should be displayed in the footer. + +=back + +=item C<flush_queries_cache> + +Some code modifies the set of stored queries. Because C<Bugzilla::User> does +not handle these modifications, but does cache the result of calling C<queries> +internally, such code must call this method to flush the cached result. + +=item C<groups> + +Returns a hashref of group names for groups the user is a member of. The keys +are the names of the groups, whilst the values are the respective group ids. +(This is so that a set of all groupids for groups the user is in can be +obtained by C<values(%{$user->groups})>.) + +=item C<in_group> + +Determines whether or not a user is in the given group. This method is mainly +intended for cases where we are not looking at the currently logged in user, +and only need to make a quick check for the group, where calling C<groups> +and getting all of the groups would be overkill. + +=item C<derive_groups> + +Bugzilla allows for group inheritance. When data about the user (or any of the +groups) changes, the database must be updated. Handling updated groups is taken +care of by the constructor. However, when updating the email address, the +user may be placed into different groups, based on a new email regexp. This +method should be called in such a case to force reresolution of these groups. + +=begin undocumented + +This routine takes an optional argument. If true, then this routine will not +lock the tables, but will rely on the caller to ahve done so itsself. + +This is required because mysql will only execute a query if all of the tables +are locked, or if none of them are, not a mixture. If the caller has already +done some locking, then this routine would fail. Thus the caller needs to lock +all the tables required by this method, and then C<derive_groups> won't do +any locking. + +This is a really ugly solution, and when Bugzilla supports transactions +instead of using the explicit table locking we were forced to do when thats +all MySQL supported, this will go away. + +=end undocumented + +=item C<can_bless> + +Returns C<1> if the user can bless at least one group. Otherwise returns C<0>. + +=back + +=head1 SEE ALSO + +L<Bugzilla|Bugzilla> |