summaryrefslogtreecommitdiffstats
path: root/Bugzilla/User.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/User.pm')
-rw-r--r--Bugzilla/User.pm3948
1 files changed, 2036 insertions, 1912 deletions
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index afd310eb0..fdfc7f8d0 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -34,9 +34,9 @@ use Role::Tiny::With;
use base qw(Bugzilla::Object Exporter);
@Bugzilla::User::EXPORT = qw(is_available_username
- login_to_id user_id_to_login
- USER_MATCH_MULTIPLE USER_MATCH_FAILED USER_MATCH_SUCCESS
- MATCH_SKIP_CONFIRM
+ login_to_id user_id_to_login
+ USER_MATCH_MULTIPLE USER_MATCH_FAILED USER_MATCH_SUCCESS
+ MATCH_SKIP_CONFIRM
);
#####################################################################
@@ -47,16 +47,16 @@ use constant USER_MATCH_MULTIPLE => -1;
use constant USER_MATCH_FAILED => 0;
use constant USER_MATCH_SUCCESS => 1;
-use constant MATCH_SKIP_CONFIRM => 1;
+use constant MATCH_SKIP_CONFIRM => 1;
use constant DEFAULT_USER => {
- 'userid' => 0,
- 'realname' => '',
- 'login_name' => '',
- 'showmybugslink' => 0,
- 'disabledtext' => '',
- 'disable_mail' => 0,
- 'is_enabled' => 1,
+ 'userid' => 0,
+ 'realname' => '',
+ 'login_name' => '',
+ 'showmybugslink' => 0,
+ 'disabledtext' => '',
+ 'disable_mail' => 0,
+ 'is_enabled' => 1,
};
use constant DB_TABLE => 'profiles';
@@ -66,23 +66,24 @@ use constant DB_TABLE => 'profiles';
# Bugzilla::User used "name" for the realname field. This should be
# fixed one day.
sub DB_COLUMNS {
- my $dbh = Bugzilla->dbh;
- return (
- 'profiles.userid',
- 'profiles.login_name',
- 'profiles.realname',
- 'profiles.mybugslink AS showmybugslink',
- 'profiles.disabledtext',
- 'profiles.disable_mail',
- 'profiles.extern_id',
- 'profiles.is_enabled',
- $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date',
- 'profiles.password_change_required',
- 'profiles.password_change_reason',
- 'profiles.mfa',
- 'profiles.mfa_required_date',
- 'profiles.nickname'
+ my $dbh = Bugzilla->dbh;
+ return (
+ 'profiles.userid',
+ 'profiles.login_name',
+ 'profiles.realname',
+ 'profiles.mybugslink AS showmybugslink',
+ 'profiles.disabledtext',
+ 'profiles.disable_mail',
+ 'profiles.extern_id',
+ 'profiles.is_enabled',
+ $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date',
+ 'profiles.password_change_required',
+ 'profiles.password_change_reason',
+ 'profiles.mfa',
+ 'profiles.mfa_required_date',
+ 'profiles.nickname'
),
+ ;
}
use constant NAME_FIELD => 'login_name';
@@ -90,41 +91,41 @@ use constant ID_FIELD => 'userid';
use constant LIST_ORDER => NAME_FIELD;
use constant VALIDATORS => {
- cryptpassword => \&_check_password,
- disable_mail => \&_check_disable_mail,
- disabledtext => \&_check_disabledtext,
- login_name => \&check_login_name_for_creation,
- realname => \&_check_realname,
- nickname => \&_check_realname,
- extern_id => \&_check_extern_id,
- is_enabled => \&_check_is_enabled,
- password_change_required => \&Bugzilla::Object::check_boolean,
- password_change_reason => \&_check_password_change_reason,
- mfa => \&_check_mfa,
+ cryptpassword => \&_check_password,
+ disable_mail => \&_check_disable_mail,
+ disabledtext => \&_check_disabledtext,
+ login_name => \&check_login_name_for_creation,
+ realname => \&_check_realname,
+ nickname => \&_check_realname,
+ extern_id => \&_check_extern_id,
+ is_enabled => \&_check_is_enabled,
+ password_change_required => \&Bugzilla::Object::check_boolean,
+ password_change_reason => \&_check_password_change_reason,
+ mfa => \&_check_mfa,
};
sub UPDATE_COLUMNS {
- my $self = shift;
- my @cols = qw(
- disable_mail
- disabledtext
- login_name
- realname
- extern_id
- is_enabled
- password_change_required
- password_change_reason
- mfa
- mfa_required_date
- nickname
- );
- push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
- return @cols;
-};
+ my $self = shift;
+ my @cols = qw(
+ disable_mail
+ disabledtext
+ login_name
+ realname
+ extern_id
+ is_enabled
+ password_change_required
+ password_change_reason
+ mfa
+ mfa_required_date
+ nickname
+ );
+ push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
+ return @cols;
+}
use constant VALIDATOR_DEPENDENCIES => {
- is_enabled => [ 'disabledtext' ],
- password_change_reason => [ 'password_change_required' ],
+ is_enabled => ['disabledtext'],
+ password_change_reason => ['password_change_required'],
};
use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
@@ -132,18 +133,18 @@ use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
with 'Bugzilla::Elastic::Role::Object';
sub ES_INDEX {
- my ($class) = @_;
- sprintf("%s_%s", Bugzilla->params->{elasticsearch_index}, $class->ES_TYPE);
+ my ($class) = @_;
+ sprintf("%s_%s", Bugzilla->params->{elasticsearch_index}, $class->ES_TYPE);
}
-sub ES_TYPE { 'user' }
+sub ES_TYPE {'user'}
-sub ES_OBJECTS_AT_ONCE { 5000 }
+sub ES_OBJECTS_AT_ONCE {5000}
sub ES_SELECT_UPDATED_SQL {
- my ($class, $mtime) = @_;
+ my ($class, $mtime) = @_;
- my $sql = q{
+ my $sql = q{
SELECT DISTINCT
object_id
FROM
@@ -151,221 +152,227 @@ sub ES_SELECT_UPDATED_SQL {
WHERE
class = 'Bugzilla::User' AND at_time > FROM_UNIXTIME(?)
};
- return ($sql, [$mtime]);
+ return ($sql, [$mtime]);
}
sub ES_SELECT_ALL_SQL {
- my ($class, $last_id) = @_;
+ my ($class, $last_id) = @_;
- my $id = $class->ID_FIELD;
- my $table = $class->DB_TABLE;
+ my $id = $class->ID_FIELD;
+ my $table = $class->DB_TABLE;
- return ("SELECT $id FROM $table WHERE $id > ? AND is_enabled AND NOT disabledtext ORDER BY $id", [$last_id // 0]);
+ return (
+ "SELECT $id FROM $table WHERE $id > ? AND is_enabled AND NOT disabledtext ORDER BY $id",
+ [$last_id // 0]
+ );
}
sub ES_SETTINGS {
- return {
- number_of_shards => 2,
- analysis => {
- filter => {
- asciifolding_original => {
- type => "asciifolding",
- preserve_original => \1,
- },
- },
- analyzer => {
- autocomplete => {
- type => 'custom',
- tokenizer => 'keyword',
- filter => [ 'lowercase', 'asciifolding_original' ],
- },
- folding => {
- tokenizer => 'standard',
- filter => [ 'standard', 'lowercase', 'asciifolding_original' ],
- },
- }
- }
- };
+ return {
+ number_of_shards => 2,
+ analysis => {
+ filter => {
+ asciifolding_original => {type => "asciifolding", preserve_original => \1,},
+ },
+ analyzer => {
+ autocomplete => {
+ type => 'custom',
+ tokenizer => 'keyword',
+ filter => ['lowercase', 'asciifolding_original'],
+ },
+ folding => {
+ tokenizer => 'standard',
+ filter => ['standard', 'lowercase', 'asciifolding_original'],
+ },
+ }
+ }
+ };
}
sub ES_PROPERTIES {
- return {
- suggest_user => {
- type => 'completion',
- analyzer => 'folding',
- search_analyzer => 'folding',
- payloads => \1,
- },
- suggest_nick => {
- type => 'completion',
- analyzer => 'autocomplete',
- payloads => \1,
- },
- login => { type => 'string' },
- name => { type => 'string' },
- is_enabled => { type => 'boolean' },
- };
+ return {
+ suggest_user => {
+ type => 'completion',
+ analyzer => 'folding',
+ search_analyzer => 'folding',
+ payloads => \1,
+ },
+ suggest_nick =>
+ {type => 'completion', analyzer => 'autocomplete', payloads => \1,},
+ login => {type => 'string'},
+ name => {type => 'string'},
+ is_enabled => {type => 'boolean'},
+ };
}
sub es_document {
- my ( $self, $timestamp ) = @_;
- my $doc = {
- login => $self->login,
- name => $self->name,
- is_enabled => $self->is_enabled,
- suggest_user => {
- input => [ $self->login, $self->name ],
- output => $self->identity,
- payload => { name => $self->login, real_name => $self->name },
- },
+ my ($self, $timestamp) = @_;
+ my $doc = {
+ login => $self->login,
+ name => $self->name,
+ is_enabled => $self->is_enabled,
+ suggest_user => {
+ input => [$self->login, $self->name],
+ output => $self->identity,
+ payload => {name => $self->login, real_name => $self->name},
+ },
+ };
+ my $name = $self->name;
+ my @nicks = extract_nicks($name);
+
+ if (@nicks) {
+ $doc->{suggest_nick} = {
+ input => \@nicks,
+ output => $self->login,
+ payload => {name => $self->login, real_name => $self->name},
};
- my $name = $self->name;
- my @nicks = extract_nicks($name);
-
- if (@nicks) {
- $doc->{suggest_nick} = {
- input => \@nicks,
- output => $self->login,
- payload => { name => $self->login, real_name => $self->name },
- };
- }
+ }
- return $doc;
+ return $doc;
}
################################################################################
# Functions
################################################################################
sub new {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my ($param) = @_;
-
- my $user = { %{ DEFAULT_USER() } };
- bless ($user, $class);
- return $user unless $param;
-
- if (ref($param) eq 'HASH') {
- if (defined $param->{extern_id}) {
- $param = { condition => 'extern_id = ?' , values => [$param->{extern_id}] };
- $_[0] = $param;
- }
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
+
+ my $user = {%{DEFAULT_USER()}};
+ bless($user, $class);
+ return $user unless $param;
+
+ if (ref($param) eq 'HASH') {
+ if (defined $param->{extern_id}) {
+ $param = {condition => 'extern_id = ?', values => [$param->{extern_id}]};
+ $_[0] = $param;
}
- return $class->SUPER::new(@_);
+ }
+ return $class->SUPER::new(@_);
}
sub super_user {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my ($param) = @_;
+ my $invocant = shift;
+ my $class = ref($invocant) || $invocant;
+ my ($param) = @_;
- my $user = { %{ DEFAULT_USER() } };
- $user->{groups} = [Bugzilla::Group->get_all];
- $user->{bless_groups} = [Bugzilla::Group->get_all];
- bless $user, $class;
- return $user;
+ my $user = {%{DEFAULT_USER()}};
+ $user->{groups} = [Bugzilla::Group->get_all];
+ $user->{bless_groups} = [Bugzilla::Group->get_all];
+ bless $user, $class;
+ return $user;
}
sub _update_groups {
- my $self = shift;
- my $group_changes = shift;
- my $changes = shift;
- my $dbh = Bugzilla->dbh;
- my $user = Bugzilla->user;
-
- # Update group settings.
- my $sth_add_mapping = $dbh->prepare(
- qq{INSERT INTO user_group_map (
+ my $self = shift;
+ my $group_changes = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
+ my $user = Bugzilla->user;
+
+ # 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
+ }
+ );
+ my $sth_remove_mapping = $dbh->prepare(
+ qq{DELETE FROM user_group_map
WHERE user_id = ?
AND group_id = ?
AND isbless = ?
AND grant_type = ?
- });
+ }
+ );
- foreach my $is_bless (keys %$group_changes) {
- my ($removed, $added) = @{$group_changes->{$is_bless}};
+ foreach my $is_bless (keys %$group_changes) {
+ my ($removed, $added) = @{$group_changes->{$is_bless}};
- foreach my $group (@$removed) {
- $sth_remove_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
- Bugzilla->audit(sprintf('%s <%s> removed group %s from %s', $user->login, remote_ip(), $group->name, $self->login));
- }
- foreach my $group (@$added) {
- $sth_add_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
- Bugzilla->audit(sprintf('%s <%s> added group %s to %s', $user->login, remote_ip(), $group->name, $self->login));
- }
+ foreach my $group (@$removed) {
+ $sth_remove_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
+ Bugzilla->audit(sprintf(
+ '%s <%s> removed group %s from %s',
+ $user->login, remote_ip(), $group->name, $self->login
+ ));
+ }
+ foreach my $group (@$added) {
+ $sth_add_mapping->execute($self->id, $group->id, $is_bless, GRANT_DIRECT);
+ Bugzilla->audit(sprintf(
+ '%s <%s> added group %s to %s',
+ $user->login, remote_ip(), $group->name, $self->login
+ ));
+ }
- if (! $is_bless) {
- my $query = qq{
+ if (!$is_bless) {
+ my $query = qq{
INSERT INTO profiles_activity
(userid, who, profiles_when, fieldid, oldvalue, newvalue)
VALUES ( ?, ?, now(), ?, ?, ?)
};
- $dbh->do(
- $query, undef,
- $self->id, $user->id,
- get_field_id('bug_group'),
- join(', ', map { $_->name } @$removed),
- join(', ', map { $_->name } @$added)
- );
- }
- else {
- # XXX: should create profiles_activity entries for blesser changes.
- }
+ $dbh->do(
+ $query, undef, $self->id, $user->id,
+ get_field_id('bug_group'),
+ join(', ', map { $_->name } @$removed),
+ join(', ', map { $_->name } @$added)
+ );
+ }
+ else {
+ # XXX: should create profiles_activity entries for blesser changes.
+ }
- Bugzilla->memcached->clear_config({ key => 'user_groups.' . $self->id });
+ Bugzilla->memcached->clear_config({key => 'user_groups.' . $self->id});
- my $type = $is_bless ? 'bless_groups' : 'groups';
- $changes->{$type} = [
- [ map { $_->name } @$removed ],
- [ map { $_->name } @$added ],
- ];
- }
+ my $type = $is_bless ? 'bless_groups' : 'groups';
+ $changes->{$type} = [[map { $_->name } @$removed], [map { $_->name } @$added],];
+ }
}
sub update {
- my $self = shift;
- my $options = shift;
+ my $self = shift;
+ my $options = shift;
- my $group_changes = delete $self->{_group_changes};
+ my $group_changes = delete $self->{_group_changes};
- my $changes = $self->SUPER::update(@_);
- my $dbh = Bugzilla->dbh;
- $self->_update_groups($group_changes, $changes);
+ my $changes = $self->SUPER::update(@_);
+ my $dbh = Bugzilla->dbh;
+ $self->_update_groups($group_changes, $changes);
- if (exists $changes->{login_name}) {
- # Delete all the tokens related to the userid
- $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $self->id)
- unless $options->{keep_tokens};
- # And rederive regex groups
- $self->derive_regexp_groups();
- }
+ if (exists $changes->{login_name}) {
- if (exists $changes->{mfa} && $self->mfa eq '') {
- if (Bugzilla->user->id != $self->id) {
- Bugzilla->audit(sprintf('%s disabled 2FA for %s', Bugzilla->user->login, $self->login));
- }
- $dbh->do("DELETE FROM profile_mfa WHERE user_id = ?", undef, $self->id);
+ # Delete all the tokens related to the userid
+ $dbh->do('DELETE FROM tokens WHERE userid = ?', undef, $self->id)
+ unless $options->{keep_tokens};
+
+ # And rederive regex groups
+ $self->derive_regexp_groups();
+ }
+
+ if (exists $changes->{mfa} && $self->mfa eq '') {
+ if (Bugzilla->user->id != $self->id) {
+ Bugzilla->audit(
+ sprintf('%s disabled 2FA for %s', Bugzilla->user->login, $self->login));
}
+ $dbh->do("DELETE FROM profile_mfa WHERE user_id = ?", undef, $self->id);
+ }
- # Logout the user if necessary.
- Bugzilla->logout_user($self)
- if (!$options->{keep_session}
- && (exists $changes->{login_name}
- || exists $changes->{disabledtext}
- || exists $changes->{cryptpassword}));
+ # Logout the user if necessary.
+ Bugzilla->logout_user($self)
+ if (
+ !$options->{keep_session}
+ && ( exists $changes->{login_name}
+ || exists $changes->{disabledtext}
+ || exists $changes->{cryptpassword})
+ );
- # XXX Can update profiles_activity here as soon as it understands
- # field names like login_name.
+ # XXX Can update profiles_activity here as soon as it understands
+ # field names like login_name.
- return $changes;
+ return $changes;
}
################################################################################
@@ -373,288 +380,294 @@ sub update {
################################################################################
sub _check_disable_mail {
- my ($invocant, $value) = @_;
- return 1 if ref($invocant) && !$invocant->is_enabled;
- return $value ? 1 : 0;
+ my ($invocant, $value) = @_;
+ return 1 if ref($invocant) && !$invocant->is_enabled;
+ return $value ? 1 : 0;
}
sub _check_disabledtext { return trim($_[1]) || ''; }
# Check whether the extern_id is unique.
sub _check_extern_id {
- my ($invocant, $extern_id) = @_;
- $extern_id = trim($extern_id);
- return undef unless defined($extern_id) && $extern_id ne "";
- if (!ref($invocant) || $invocant->extern_id ne $extern_id) {
- my $existing_login = $invocant->new({ extern_id => $extern_id });
- if ($existing_login) {
- ThrowUserError( 'extern_id_exists',
- { extern_id => $extern_id,
- existing_login_name => $existing_login->login });
- }
+ my ($invocant, $extern_id) = @_;
+ $extern_id = trim($extern_id);
+ return undef unless defined($extern_id) && $extern_id ne "";
+ if (!ref($invocant) || $invocant->extern_id ne $extern_id) {
+ my $existing_login = $invocant->new({extern_id => $extern_id});
+ if ($existing_login) {
+ ThrowUserError('extern_id_exists',
+ {extern_id => $extern_id, existing_login_name => $existing_login->login});
}
- return $extern_id;
+ }
+ return $extern_id;
}
# This is public since createaccount.cgi needs to use it before issuing
# a token for account creation.
sub check_login_name_for_creation {
- my ($invocant, $name) = @_;
- $name = trim($name);
- $name || ThrowUserError('user_login_required');
- validate_email_syntax($name)
- || ThrowUserError('illegal_email_address', { addr => $name });
-
- # Check the name if it's a new user, or if we're changing the name.
- if (!ref($invocant) || $invocant->login ne $name) {
- is_available_username($name)
- || ThrowUserError('account_exists', { email => $name });
- }
+ my ($invocant, $name) = @_;
+ $name = trim($name);
+ $name || ThrowUserError('user_login_required');
+ validate_email_syntax($name)
+ || ThrowUserError('illegal_email_address', {addr => $name});
- return $name;
+ # Check the name if it's a new user, or if we're changing the name.
+ if (!ref($invocant) || $invocant->login ne $name) {
+ is_available_username($name)
+ || ThrowUserError('account_exists', {email => $name});
+ }
+
+ return $name;
}
sub _check_password {
- my ($self, $pass) = @_;
+ my ($self, $pass) = @_;
- # If the password is '*', do not encrypt it or validate it further--we
- # are creating a user who should not be able to log in using DB
- # authentication.
- return $pass if $pass eq '*';
+ # If the password is '*', do not encrypt it or validate it further--we
+ # are creating a user who should not be able to log in using DB
+ # authentication.
+ return $pass if $pass eq '*';
- Bugzilla->assert_password_is_secure($pass);
- my $cryptpassword = bz_crypt($pass);
- return $cryptpassword;
+ Bugzilla->assert_password_is_secure($pass);
+ my $cryptpassword = bz_crypt($pass);
+ return $cryptpassword;
}
sub _check_realname { return trim($_[1]) || ''; }
sub _check_is_enabled {
- my ($invocant, $is_enabled, undef, $params) = @_;
- # is_enabled is set automatically on creation depending on whether
- # disabledtext is empty (enabled) or not empty (disabled).
- # When updating the user, is_enabled is set by calling set_disabledtext().
- # Any value passed into this validator is ignored.
- my $disabledtext = ref($invocant) ? $invocant->disabledtext : $params->{disabledtext};
- return $disabledtext ? 0 : 1;
+ my ($invocant, $is_enabled, undef, $params) = @_;
+
+ # is_enabled is set automatically on creation depending on whether
+ # disabledtext is empty (enabled) or not empty (disabled).
+ # When updating the user, is_enabled is set by calling set_disabledtext().
+ # Any value passed into this validator is ignored.
+ my $disabledtext
+ = ref($invocant) ? $invocant->disabledtext : $params->{disabledtext};
+ return $disabledtext ? 0 : 1;
}
sub _check_password_change_reason {
- my ($self, $value) = @_;
- return $self->password_change_required
- ? trim($_[1]) || ''
- : '';
+ my ($self, $value) = @_;
+ return $self->password_change_required ? trim($_[1]) || '' : '';
}
sub _check_mfa {
- my ($self, $provider) = @_;
- $provider = lc($provider // '');
- return 'TOTP' if $provider eq 'totp';
- return 'Duo' if $provider eq 'duo';
-
- # you must be member of the bz_can_disable_mfa group to disable mfa for
- # other accounts.
- if ($provider eq '') {
- my $user = Bugzilla->user;
- if ($user->id != $self->id && !$user->in_group('bz_can_disable_mfa')) {
- ThrowUserError('mfa_disable_denied');
- }
+ my ($self, $provider) = @_;
+ $provider = lc($provider // '');
+ return 'TOTP' if $provider eq 'totp';
+ return 'Duo' if $provider eq 'duo';
+
+ # you must be member of the bz_can_disable_mfa group to disable mfa for
+ # other accounts.
+ if ($provider eq '') {
+ my $user = Bugzilla->user;
+ if ($user->id != $self->id && !$user->in_group('bz_can_disable_mfa')) {
+ ThrowUserError('mfa_disable_denied');
}
+ }
- return '';
+ return '';
}
################################################################################
# Mutators
################################################################################
-sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
-sub set_email_enabled { $_[0]->set('disable_mail', !$_[1]); }
-sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
-sub set_password_change_required { $_[0]->set('password_change_required', $_[1]); }
-sub set_password_change_reason { $_[0]->set('password_change_reason', $_[1]); }
+sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
+sub set_email_enabled { $_[0]->set('disable_mail', !$_[1]); }
+sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
+
+sub set_password_change_required {
+ $_[0]->set('password_change_required', $_[1]);
+}
+sub set_password_change_reason { $_[0]->set('password_change_reason', $_[1]); }
sub set_login {
- my ($self, $login) = @_;
- $self->set('login_name', $login);
- delete $self->{identity};
- delete $self->{nick};
+ my ($self, $login) = @_;
+ $self->set('login_name', $login);
+ delete $self->{identity};
+ delete $self->{nick};
}
sub _generate_nickname {
- my ($name, $login, $id) = @_;
- my ($nick) = extract_nicks($name);
- if (!$nick) {
- $nick = "";
- }
- my ($count) = Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM profiles WHERE nickname = ? AND userid != ?', undef, $nick, $id);
- if ($count) {
- $nick = "";
- }
- return $nick;
+ my ($name, $login, $id) = @_;
+ my ($nick) = extract_nicks($name);
+ if (!$nick) {
+ $nick = "";
+ }
+ my ($count)
+ = Bugzilla->dbh->selectrow_array(
+ 'SELECT COUNT(*) FROM profiles WHERE nickname = ? AND userid != ?',
+ undef, $nick, $id);
+ if ($count) {
+ $nick = "";
+ }
+ return $nick;
}
sub set_name {
- my ($self, $name) = @_;
- $self->set('realname', $name);
- delete $self->{identity};
- $self->set('nickname', _generate_nickname($name, $self->login, $self->id));
+ my ($self, $name) = @_;
+ $self->set('realname', $name);
+ delete $self->{identity};
+ $self->set('nickname', _generate_nickname($name, $self->login, $self->id));
}
sub set_nick {
- my ($self, $nick) = @_;
- $self->set('nickname', $nick);
+ my ($self, $nick) = @_;
+ $self->set('nickname', $nick);
}
sub set_password {
- my ($self, $password) = @_;
- $self->set('cryptpassword', $password);
- $self->set('password_change_required', 0);
- $self->set('password_change_reason', '');
+ my ($self, $password) = @_;
+ $self->set('cryptpassword', $password);
+ $self->set('password_change_required', 0);
+ $self->set('password_change_reason', '');
}
sub set_disabledtext {
- my ($self, $text) = @_;
- $self->set('disabledtext', $text);
- $self->set('is_enabled', trim($text) eq '' ? 0 : 1);
- $self->set('disable_mail', 1) if !$self->is_enabled;
+ my ($self, $text) = @_;
+ $self->set('disabledtext', $text);
+ $self->set('is_enabled', trim($text) eq '' ? 0 : 1);
+ $self->set('disable_mail', 1) if !$self->is_enabled;
}
sub set_mfa {
- my ($self, $value) = @_;
- $self->set('mfa', $value);
- delete $self->{mfa_provider};
+ my ($self, $value) = @_;
+ $self->set('mfa', $value);
+ delete $self->{mfa_provider};
}
sub set_mfa_required_date {
- my ($self, $value) = @_;
- $self->set('mfa_required_date', $value);
+ my ($self, $value) = @_;
+ $self->set('mfa_required_date', $value);
}
sub set_groups {
- my $self = shift;
- $self->_set_groups(GROUP_MEMBERSHIP, @_);
+ my $self = shift;
+ $self->_set_groups(GROUP_MEMBERSHIP, @_);
}
sub set_bless_groups {
- my $self = shift;
+ my $self = shift;
- # The person making the change needs to be in the editusers group
- Bugzilla->user->in_group('editusers')
- || ThrowUserError("auth_failure", {group => "editusers",
- reason => "cant_bless",
- action => "edit",
- object => "users"});
+ # The person making the change needs to be in the editusers group
+ Bugzilla->user->in_group('editusers') || ThrowUserError(
+ "auth_failure",
+ {
+ group => "editusers",
+ reason => "cant_bless",
+ action => "edit",
+ object => "users"
+ }
+ );
- $self->_set_groups(GROUP_BLESS, @_);
+ $self->_set_groups(GROUP_BLESS, @_);
}
sub _set_groups {
- my $self = shift;
- my $is_bless = shift;
- my $changes = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $is_bless = shift;
+ my $changes = shift;
+ my $dbh = Bugzilla->dbh;
- use Data::Dumper;
+ use Data::Dumper;
- # The person making the change is $user, $self is the person being changed
- my $user = Bugzilla->user;
+ # The person making the change is $user, $self is the person being changed
+ my $user = Bugzilla->user;
- # Input is a hash of arrays. Key is 'set', 'add' or 'remove'. The array
- # is a list of group ids and/or names.
+ # Input is a hash of arrays. Key is 'set', 'add' or 'remove'. The array
+ # is a list of group ids and/or names.
- # First turn the arrays into group objects.
- $changes = $self->_set_groups_to_object($changes);
+ # First turn the arrays into group objects.
+ $changes = $self->_set_groups_to_object($changes);
- # Get a list of the groups the user currently is a member of
- my $ids = $dbh->selectcol_arrayref(
- q{SELECT DISTINCT group_id
+ # Get a list of the groups the user currently is a member of
+ my $ids = $dbh->selectcol_arrayref(
+ q{SELECT DISTINCT group_id
FROM user_group_map
- WHERE user_id = ? AND isbless = ? AND grant_type = ?},
- undef, $self->id, $is_bless, GRANT_DIRECT);
-
- my $current_groups = Bugzilla::Group->new_from_list($ids);
- my $new_groups = dclone($current_groups);
-
- # Record the changes
- if (exists $changes->{set}) {
- $new_groups = $changes->{set};
-
- # We need to check the user has bless rights on the existing groups
- # If they don't, then we need to add them back to new_groups
- foreach my $group (@$current_groups) {
- if (! $user->can_bless($group->id)) {
- push @$new_groups, $group
- unless grep { $_->id eq $group->id } @$new_groups;
- }
- }
+ WHERE user_id = ? AND isbless = ? AND grant_type = ?}, undef, $self->id,
+ $is_bless, GRANT_DIRECT
+ );
+
+ my $current_groups = Bugzilla::Group->new_from_list($ids);
+ my $new_groups = dclone($current_groups);
+
+ # Record the changes
+ if (exists $changes->{set}) {
+ $new_groups = $changes->{set};
+
+ # We need to check the user has bless rights on the existing groups
+ # If they don't, then we need to add them back to new_groups
+ foreach my $group (@$current_groups) {
+ if (!$user->can_bless($group->id)) {
+ push @$new_groups, $group unless grep { $_->id eq $group->id } @$new_groups;
+ }
}
- else {
- foreach my $group (@{$changes->{remove} // []}) {
- @$new_groups = grep { $_->id ne $group->id } @$new_groups;
- }
- foreach my $group (@{$changes->{add} // []}) {
- push @$new_groups, $group
- unless grep { $_->id eq $group->id } @$new_groups;
- }
+ }
+ else {
+ foreach my $group (@{$changes->{remove} // []}) {
+ @$new_groups = grep { $_->id ne $group->id } @$new_groups;
}
-
- # Stash the changes, so self->update can actually make them
- my @diffs = diff_arrays($current_groups, $new_groups, 'id');
- if (scalar(@{$diffs[0]}) || scalar(@{$diffs[1]})) {
- $self->{_group_changes}{$is_bless} = \@diffs;
+ foreach my $group (@{$changes->{add} // []}) {
+ push @$new_groups, $group unless grep { $_->id eq $group->id } @$new_groups;
}
+ }
+
+ # Stash the changes, so self->update can actually make them
+ my @diffs = diff_arrays($current_groups, $new_groups, 'id');
+ if (scalar(@{$diffs[0]}) || scalar(@{$diffs[1]})) {
+ $self->{_group_changes}{$is_bless} = \@diffs;
+ }
}
sub _set_groups_to_object {
- my $self = shift;
- my $changes = shift;
- my $user = Bugzilla->user;
+ my $self = shift;
+ my $changes = shift;
+ my $user = Bugzilla->user;
- foreach my $key (keys %$changes) {
- # Check we were given an array
- unless (ref($changes->{$key}) eq 'ARRAY') {
- ThrowCodeError(
- 'param_invalid',
- { param => $changes->{$key}, function => $key }
- );
- }
+ foreach my $key (keys %$changes) {
- # Go through the array, and turn items into group objects
- my @groups = ();
- foreach my $value (@{$changes->{$key}}) {
- my $type = $value =~ /^\d+$/ ? 'id' : 'name';
- my $group = Bugzilla::Group->new({$type => $value});
-
- if (! $group || ! $user->can_bless($group->id)) {
- ThrowUserError('auth_failure',
- { group => $value, reason => 'cant_bless',
- action => 'edit', object => 'users' });
- }
- push @groups, $group;
- }
- $changes->{$key} = \@groups;
+ # Check we were given an array
+ unless (ref($changes->{$key}) eq 'ARRAY') {
+ ThrowCodeError('param_invalid', {param => $changes->{$key}, function => $key});
}
- return $changes;
+ # Go through the array, and turn items into group objects
+ my @groups = ();
+ foreach my $value (@{$changes->{$key}}) {
+ my $type = $value =~ /^\d+$/ ? 'id' : 'name';
+ my $group = Bugzilla::Group->new({$type => $value});
+
+ if (!$group || !$user->can_bless($group->id)) {
+ ThrowUserError('auth_failure',
+ {group => $value, reason => 'cant_bless', action => 'edit', object => 'users'});
+ }
+ push @groups, $group;
+ }
+ $changes->{$key} = \@groups;
+ }
+
+ return $changes;
}
sub update_last_seen_date {
- my $self = shift;
- return unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $date = $dbh->selectrow_array(
- 'SELECT ' . $dbh->sql_date_format('NOW()', '%Y-%m-%d'));
-
- if (!$self->last_seen_date or $date ne $self->last_seen_date) {
- $self->{last_seen_date} = $date;
- # We don't use the normal update() routine here as we only
- # want to update the last_seen_date column, not any other
- # pending changes
- $dbh->do("UPDATE profiles SET last_seen_date = ? WHERE userid = ?",
- undef, $date, $self->id);
- Bugzilla->memcached->clear({ table => 'profiles', id => $self->id });
- }
+ my $self = shift;
+ return unless $self->id;
+ my $dbh = Bugzilla->dbh;
+ my $date = $dbh->selectrow_array(
+ 'SELECT ' . $dbh->sql_date_format('NOW()', '%Y-%m-%d'));
+
+ if (!$self->last_seen_date or $date ne $self->last_seen_date) {
+ $self->{last_seen_date} = $date;
+
+ # We don't use the normal update() routine here as we only
+ # want to update the last_seen_date column, not any other
+ # pending changes
+ $dbh->do("UPDATE profiles SET last_seen_date = ? WHERE userid = ?",
+ undef, $date, $self->id);
+ Bugzilla->memcached->clear({table => 'profiles', id => $self->id});
+ }
}
################################################################################
@@ -662,201 +675,212 @@ sub update_last_seen_date {
################################################################################
# Accessors for user attributes
-sub name { $_[0]->{realname}; }
-sub login { $_[0]->{login_name}; }
-sub extern_id { $_[0]->{extern_id}; }
-sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
-sub disabledtext { $_[0]->{'disabledtext'}; }
-sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; }
+sub name { $_[0]->{realname}; }
+sub login { $_[0]->{login_name}; }
+sub extern_id { $_[0]->{extern_id}; }
+sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; }
+sub disabledtext { $_[0]->{'disabledtext'}; }
+sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; }
sub showmybugslink { $_[0]->{showmybugslink}; }
sub email_disabled { $_[0]->{disable_mail} || !$_[0]->{is_enabled}; }
-sub email_enabled { !$_[0]->email_disabled; }
+sub email_enabled { !$_[0]->email_disabled; }
sub last_seen_date { $_[0]->{last_seen_date}; }
sub password_change_required { $_[0]->{password_change_required}; }
-sub password_change_reason { $_[0]->{password_change_reason}; }
+sub password_change_reason { $_[0]->{password_change_reason}; }
sub cryptpassword {
- my $self = shift;
- # We don't store it because we never want it in the object (we
- # don't want to accidentally dump even the hash somewhere).
- my ($pw) = Bugzilla->dbh->selectrow_array(
- 'SELECT cryptpassword FROM profiles WHERE userid = ?',
- undef, $self->id);
- return $pw;
+ my $self = shift;
+
+ # We don't store it because we never want it in the object (we
+ # don't want to accidentally dump even the hash somewhere).
+ my ($pw)
+ = Bugzilla->dbh->selectrow_array(
+ 'SELECT cryptpassword FROM profiles WHERE userid = ?',
+ undef, $self->id);
+ return $pw;
}
sub set_authorizer {
- my ($self, $authorizer) = @_;
- $self->{authorizer} = $authorizer;
+ my ($self, $authorizer) = @_;
+ $self->{authorizer} = $authorizer;
}
+
sub authorizer {
- my ($self) = @_;
- if (!$self->{authorizer}) {
- require Bugzilla::Auth;
- $self->{authorizer} = new Bugzilla::Auth();
- }
- return $self->{authorizer};
+ my ($self) = @_;
+ if (!$self->{authorizer}) {
+ require Bugzilla::Auth;
+ $self->{authorizer} = new Bugzilla::Auth();
+ }
+ return $self->{authorizer};
}
sub mfa { $_[0]->{mfa} }
sub mfa_required_date {
- my $self = shift;
- return $self->{mfa_required_date} ? datetime_from($self->{mfa_required_date}, @_) : undef;
+ my $self = shift;
+ return $self->{mfa_required_date}
+ ? datetime_from($self->{mfa_required_date}, @_)
+ : undef;
}
sub mfa_provider {
- my ($self) = @_;
- my $mfa = $self->{mfa} || return undef;
- return $self->{mfa_provider} if exists $self->{mfa_provider};
- require Bugzilla::MFA;
- $self->{mfa_provider} = Bugzilla::MFA->new_from($self, $mfa);
- return $self->{mfa_provider};
+ my ($self) = @_;
+ my $mfa = $self->{mfa} || return undef;
+ return $self->{mfa_provider} if exists $self->{mfa_provider};
+ require Bugzilla::MFA;
+ $self->{mfa_provider} = Bugzilla::MFA->new_from($self, $mfa);
+ return $self->{mfa_provider};
}
sub in_mfa_group {
- my $self = shift;
- return $self->{in_mfa_group} if exists $self->{in_mfa_group};
+ my $self = shift;
+ return $self->{in_mfa_group} if exists $self->{in_mfa_group};
- my $mfa_group = Bugzilla->params->{mfa_group};
- return $self->{in_mfa_group} = ($mfa_group && $self->in_group($mfa_group));
+ my $mfa_group = Bugzilla->params->{mfa_group};
+ return $self->{in_mfa_group} = ($mfa_group && $self->in_group($mfa_group));
}
sub name_or_login {
- my $self = shift;
+ my $self = shift;
- return $self->name || $self->login;
+ return $self->name || $self->login;
}
# Generate a string to identify the user by name + login if the user
# has a name or by login only if she doesn't.
sub identity {
- my $self = shift;
+ my $self = shift;
- return "" unless $self->id;
+ return "" unless $self->id;
- if (!defined $self->{identity}) {
- $self->{identity} =
- $self->name ? $self->name . " <" . $self->login. ">" : $self->login;
- }
+ if (!defined $self->{identity}) {
+ $self->{identity}
+ = $self->name ? $self->name . " <" . $self->login . ">" : $self->login;
+ }
- return $self->{identity};
+ return $self->{identity};
}
sub nick {
- my $self = shift;
+ my $self = shift;
- return "" unless $self->id;
- return $self->{nickname} if $self->{nickname};
- return $self->{nick} //= (split(/@/, $self->login, 2))[0];
+ return "" unless $self->id;
+ return $self->{nickname} if $self->{nickname};
+ return $self->{nick} //= (split(/@/, $self->login, 2))[0];
}
sub queries {
- my $self = shift;
- return $self->{queries} if defined $self->{queries};
- return [] unless $self->id;
+ my $self = shift;
+ return $self->{queries} if defined $self->{queries};
+ return [] unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $query_ids = $dbh->selectcol_arrayref(
- 'SELECT id FROM namedqueries WHERE userid = ?', undef, $self->id);
- require Bugzilla::Search::Saved;
- $self->{queries} = Bugzilla::Search::Saved->new_from_list($query_ids);
+ my $dbh = Bugzilla->dbh;
+ my $query_ids
+ = $dbh->selectcol_arrayref('SELECT id FROM namedqueries WHERE userid = ?',
+ undef, $self->id);
+ require Bugzilla::Search::Saved;
+ $self->{queries} = Bugzilla::Search::Saved->new_from_list($query_ids);
- # We preload link_in_footer from here as this information is always requested.
- # This only works if the user object represents the current logged in user.
- Bugzilla::Search::Saved::preload($self->{queries}) if $self->id == Bugzilla->user->id;
+ # We preload link_in_footer from here as this information is always requested.
+ # This only works if the user object represents the current logged in user.
+ Bugzilla::Search::Saved::preload($self->{queries})
+ if $self->id == Bugzilla->user->id;
- return $self->{queries};
+ return $self->{queries};
}
sub queries_subscribed {
- my $self = shift;
- return $self->{queries_subscribed} if defined $self->{queries_subscribed};
- return [] unless $self->id;
-
- # Exclude the user's own queries.
- my @my_query_ids = map($_->id, @{$self->queries});
- my $query_id_string = join(',', @my_query_ids) || '-1';
-
- # Only show subscriptions that we can still actually see. If a
- # user changes the shared group of a query, our subscription
- # will remain but we won't have access to the query anymore.
- my $subscribed_query_ids = Bugzilla->dbh->selectcol_arrayref(
- "SELECT lif.namedquery_id
+ my $self = shift;
+ return $self->{queries_subscribed} if defined $self->{queries_subscribed};
+ return [] unless $self->id;
+
+ # Exclude the user's own queries.
+ my @my_query_ids = map($_->id, @{$self->queries});
+ my $query_id_string = join(',', @my_query_ids) || '-1';
+
+ # Only show subscriptions that we can still actually see. If a
+ # user changes the shared group of a query, our subscription
+ # will remain but we won't have access to the query anymore.
+ my $subscribed_query_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT lif.namedquery_id
FROM namedqueries_link_in_footer lif
INNER JOIN namedquery_group_map ngm
ON ngm.namedquery_id = lif.namedquery_id
WHERE lif.user_id = ?
AND lif.namedquery_id NOT IN ($query_id_string)
- AND " . $self->groups_in_sql,
- undef, $self->id);
- require Bugzilla::Search::Saved;
- $self->{queries_subscribed} =
- Bugzilla::Search::Saved->new_from_list($subscribed_query_ids);
- return $self->{queries_subscribed};
+ AND " . $self->groups_in_sql, undef, $self->id
+ );
+ require Bugzilla::Search::Saved;
+ $self->{queries_subscribed}
+ = Bugzilla::Search::Saved->new_from_list($subscribed_query_ids);
+ return $self->{queries_subscribed};
}
sub queries_available {
- my $self = shift;
- return $self->{queries_available} if defined $self->{queries_available};
- return [] unless $self->id;
+ my $self = shift;
+ return $self->{queries_available} if defined $self->{queries_available};
+ return [] unless $self->id;
- # Exclude the user's own queries.
- my @my_query_ids = map($_->id, @{$self->queries});
- my $query_id_string = join(',', @my_query_ids) || '-1';
+ # Exclude the user's own queries.
+ my @my_query_ids = map($_->id, @{$self->queries});
+ my $query_id_string = join(',', @my_query_ids) || '-1';
- my $avail_query_ids = Bugzilla->dbh->selectcol_arrayref(
- 'SELECT namedquery_id FROM namedquery_group_map
- WHERE ' . $self->groups_in_sql . "
- AND namedquery_id NOT IN ($query_id_string)");
- require Bugzilla::Search::Saved;
- $self->{queries_available} =
- Bugzilla::Search::Saved->new_from_list($avail_query_ids);
- return $self->{queries_available};
+ my $avail_query_ids = Bugzilla->dbh->selectcol_arrayref(
+ 'SELECT namedquery_id FROM namedquery_group_map
+ WHERE ' . $self->groups_in_sql . "
+ AND namedquery_id NOT IN ($query_id_string)"
+ );
+ require Bugzilla::Search::Saved;
+ $self->{queries_available}
+ = Bugzilla::Search::Saved->new_from_list($avail_query_ids);
+ return $self->{queries_available};
}
sub tags {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- if (!defined $self->{tags}) {
- # We must use LEFT JOIN instead of INNER JOIN as we may be
- # in the process of inserting a new tag to some bugs,
- # in which case there are no bugs with this tag yet.
- $self->{tags} = $dbh->selectall_hashref(
- 'SELECT name, id, COUNT(bug_id) AS bug_count
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{tags}) {
+
+ # We must use LEFT JOIN instead of INNER JOIN as we may be
+ # in the process of inserting a new tag to some bugs,
+ # in which case there are no bugs with this tag yet.
+ $self->{tags} = $dbh->selectall_hashref(
+ 'SELECT name, id, COUNT(bug_id) AS bug_count
FROM tag
LEFT JOIN bug_tag ON bug_tag.tag_id = tag.id
- WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'),
- 'name', undef, $self->id);
- }
- return $self->{tags};
+ WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'), 'name', undef,
+ $self->id
+ );
+ }
+ return $self->{tags};
}
sub bugs_ignored {
- my ($self) = @_;
- my $dbh = Bugzilla->dbh;
- if (!defined $self->{'bugs_ignored'}) {
- $self->{'bugs_ignored'} = $dbh->selectall_arrayref(
- 'SELECT bugs.bug_id AS id,
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
+ if (!defined $self->{'bugs_ignored'}) {
+ $self->{'bugs_ignored'} = $dbh->selectall_arrayref(
+ 'SELECT bugs.bug_id AS id,
bugs.bug_status AS status,
bugs.short_desc AS summary
FROM bugs
INNER JOIN email_bug_ignore
ON bugs.bug_id = email_bug_ignore.bug_id
- WHERE user_id = ?',
- { Slice => {} }, $self->id);
- # Go ahead and load these into the visible bugs cache
- # to speed up can_see_bug checks later
- $self->visible_bugs([ map { $_->{'id'} } @{ $self->{'bugs_ignored'} } ]);
- }
- return $self->{'bugs_ignored'};
+ WHERE user_id = ?', {Slice => {}}, $self->id
+ );
+
+ # Go ahead and load these into the visible bugs cache
+ # to speed up can_see_bug checks later
+ $self->visible_bugs([map { $_->{'id'} } @{$self->{'bugs_ignored'}}]);
+ }
+ return $self->{'bugs_ignored'};
}
sub is_bug_ignored {
- my ($self, $bug_id) = @_;
- return (grep {$_->{'id'} == $bug_id} @{$self->bugs_ignored}) ? 1 : 0;
+ my ($self, $bug_id) = @_;
+ return (grep { $_->{'id'} == $bug_id } @{$self->bugs_ignored}) ? 1 : 0;
}
##########################
@@ -864,280 +888,288 @@ sub is_bug_ignored {
##########################
sub recent_searches {
- my $self = shift;
- $self->{recent_searches} ||=
- Bugzilla::Search::Recent->match({ user_id => $self->id });
- return $self->{recent_searches};
+ my $self = shift;
+ $self->{recent_searches}
+ ||= Bugzilla::Search::Recent->match({user_id => $self->id});
+ return $self->{recent_searches};
}
sub recent_search_containing {
- my ($self, $bug_id) = @_;
- my $searches = $self->recent_searches;
+ my ($self, $bug_id) = @_;
+ my $searches = $self->recent_searches;
- foreach my $search (@$searches) {
- return $search if grep($_ == $bug_id, @{ $search->bug_list });
- }
+ foreach my $search (@$searches) {
+ return $search if grep($_ == $bug_id, @{$search->bug_list});
+ }
- return undef;
+ return undef;
}
sub recent_search_for {
- my ($self, $bug) = @_;
- my $params = Bugzilla->input_params;
- my $cgi = Bugzilla->cgi;
-
- if ($self->id) {
- # First see if there's a list_id parameter in the query string.
- my $list_id = $params->{list_id};
- if (!$list_id) {
- # If not, check for "list_id" in the query string of the referer.
- my $referer = $cgi->referer;
- if ($referer) {
- my $uri = URI->new($referer);
- if ($uri->path =~ /buglist\.cgi$/) {
- $list_id = $uri->query_param('list_id')
- || $uri->query_param('regetlastlist');
- }
- }
+ my ($self, $bug) = @_;
+ my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+
+ if ($self->id) {
+
+ # First see if there's a list_id parameter in the query string.
+ my $list_id = $params->{list_id};
+ if (!$list_id) {
+
+ # If not, check for "list_id" in the query string of the referer.
+ my $referer = $cgi->referer;
+ if ($referer) {
+ my $uri = URI->new($referer);
+ if ($uri->path =~ /buglist\.cgi$/) {
+ $list_id = $uri->query_param('list_id') || $uri->query_param('regetlastlist');
}
+ }
+ }
- if ($list_id && $list_id ne 'cookie') {
- # If we got a bad list_id (either some other user's or an expired
- # one) don't crash, just don't return that list.
- my $search = Bugzilla::Search::Recent->check_quietly(
- { id => $list_id });
- return $search if $search;
- }
+ if ($list_id && $list_id ne 'cookie') {
- # If there's no list_id, see if the current bug's id is contained
- # in any of the user's saved lists.
- my $search = $self->recent_search_containing($bug->id);
- return $search if $search;
+ # If we got a bad list_id (either some other user's or an expired
+ # one) don't crash, just don't return that list.
+ my $search = Bugzilla::Search::Recent->check_quietly({id => $list_id});
+ return $search if $search;
}
- # Finally (or always, if we're logged out), if there's a BUGLIST cookie
- # and the selected bug is in the list, then return the cookie as a fake
- # Search::Recent object.
- if (my $list = $cgi->cookie('BUGLIST')) {
- # Also split on colons, which was used as a separator in old cookies.
- my @bug_ids = split(/[:-]/, $list);
- if (grep { $_ == $bug->id } @bug_ids) {
- my $search = Bugzilla::Search::Recent->new_from_cookie(\@bug_ids);
- return $search;
- }
+ # If there's no list_id, see if the current bug's id is contained
+ # in any of the user's saved lists.
+ my $search = $self->recent_search_containing($bug->id);
+ return $search if $search;
+ }
+
+ # Finally (or always, if we're logged out), if there's a BUGLIST cookie
+ # and the selected bug is in the list, then return the cookie as a fake
+ # Search::Recent object.
+ if (my $list = $cgi->cookie('BUGLIST')) {
+
+ # Also split on colons, which was used as a separator in old cookies.
+ my @bug_ids = split(/[:-]/, $list);
+ if (grep { $_ == $bug->id } @bug_ids) {
+ my $search = Bugzilla::Search::Recent->new_from_cookie(\@bug_ids);
+ return $search;
}
+ }
- return undef;
+ return undef;
}
sub save_last_search {
- my ($self, $params) = @_;
- my ($bug_ids, $order, $vars, $list_id) =
- @$params{qw(bugs order vars list_id)};
-
- my $cgi = Bugzilla->cgi;
- if ($order) {
- $cgi->send_cookie(-name => 'LASTORDER',
- -value => $order,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
- }
+ my ($self, $params) = @_;
+ my ($bug_ids, $order, $vars, $list_id) = @$params{qw(bugs order vars list_id)};
+
+ my $cgi = Bugzilla->cgi;
+ if ($order) {
+ $cgi->send_cookie(
+ -name => 'LASTORDER',
+ -value => $order,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
+ }
- return if !@$bug_ids;
-
- my $search;
- if ($self->id) {
- on_main_db {
- if ($list_id) {
- $search = Bugzilla::Search::Recent->check_quietly({ id => $list_id });
- }
-
- if ($search) {
- if (join(',', @{$search->bug_list}) ne join(',', @$bug_ids)) {
- $search->set_bug_list($bug_ids);
- }
- if (!$search->list_order || $order ne $search->list_order) {
- $search->set_list_order($order);
- }
- $search->update();
- }
- else {
- # If we already have an existing search with a totally
- # identical bug list, then don't create a new one. This
- # prevents people from writing over their whole
- # recent-search list by just refreshing a saved search
- # (which doesn't have list_id in the header) over and over.
- my $list_string = join(',', @$bug_ids);
- my $existing_search = Bugzilla::Search::Recent->match({
- user_id => $self->id, bug_list => $list_string });
-
- if (!scalar(@$existing_search)) {
- $search = Bugzilla::Search::Recent->create({
- user_id => $self->id,
- bug_list => $bug_ids,
- list_order => $order });
- }
- else {
- $search = $existing_search->[0];
- }
- }
- };
- delete $self->{recent_searches};
- }
- # Logged-out users use a cookie to store a single last search. We don't
- # override that cookie with the logged-in user's latest search, because
- # if they did one search while logged out and another while logged in,
- # they may still want to navigate through the search they made while
- # logged out.
- else {
- my $bug_list = join('-', @$bug_ids);
- if (length($bug_list) < 4000) {
- $cgi->send_cookie(-name => 'BUGLIST',
- -value => $bug_list,
- -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
+ return if !@$bug_ids;
+
+ my $search;
+ if ($self->id) {
+ on_main_db {
+ if ($list_id) {
+ $search = Bugzilla::Search::Recent->check_quietly({id => $list_id});
+ }
+
+ if ($search) {
+ if (join(',', @{$search->bug_list}) ne join(',', @$bug_ids)) {
+ $search->set_bug_list($bug_ids);
+ }
+ if (!$search->list_order || $order ne $search->list_order) {
+ $search->set_list_order($order);
+ }
+ $search->update();
+ }
+ else {
+ # If we already have an existing search with a totally
+ # identical bug list, then don't create a new one. This
+ # prevents people from writing over their whole
+ # recent-search list by just refreshing a saved search
+ # (which doesn't have list_id in the header) over and over.
+ my $list_string = join(',', @$bug_ids);
+ my $existing_search = Bugzilla::Search::Recent->match(
+ {user_id => $self->id, bug_list => $list_string});
+
+ if (!scalar(@$existing_search)) {
+ $search
+ = Bugzilla::Search::Recent->create({
+ user_id => $self->id, bug_list => $bug_ids, list_order => $order
+ });
}
else {
- $cgi->remove_cookie('BUGLIST');
- $vars->{'toolong'} = 1;
+ $search = $existing_search->[0];
}
+ }
+ };
+ delete $self->{recent_searches};
+ }
+
+ # Logged-out users use a cookie to store a single last search. We don't
+ # override that cookie with the logged-in user's latest search, because
+ # if they did one search while logged out and another while logged in,
+ # they may still want to navigate through the search they made while
+ # logged out.
+ else {
+ my $bug_list = join('-', @$bug_ids);
+ if (length($bug_list) < 4000) {
+ $cgi->send_cookie(
+ -name => 'BUGLIST',
+ -value => $bug_list,
+ -expires => 'Fri, 01-Jan-2038 00:00:00 GMT'
+ );
}
- return $search;
+ else {
+ $cgi->remove_cookie('BUGLIST');
+ $vars->{'toolong'} = 1;
+ }
+ }
+ return $search;
}
sub settings {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{'settings'} if (defined $self->{'settings'});
+ return $self->{'settings'} if (defined $self->{'settings'});
- # IF the user is logged in
- # THEN get the user's settings
- # ELSE get default settings
- if ($self->id) {
- $self->{'settings'} = get_all_settings($self->id);
- } else {
- $self->{'settings'} = get_defaults();
- }
+ # IF the user is logged in
+ # THEN get the user's settings
+ # ELSE get default settings
+ if ($self->id) {
+ $self->{'settings'} = get_all_settings($self->id);
+ }
+ else {
+ $self->{'settings'} = get_defaults();
+ }
- return $self->{'settings'};
+ return $self->{'settings'};
}
sub setting {
- my ($self, $name) = @_;
- return $self->settings->{$name}->{'value'};
+ my ($self, $name) = @_;
+ return $self->settings->{$name}->{'value'};
}
sub timezone {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{timezone}) {
- my $tz = $self->setting('timezone');
- if ($tz eq 'local') {
- # The user wants the local timezone of the server.
- $self->{timezone} = Bugzilla->local_timezone;
- }
- else {
- $self->{timezone} = DateTime::TimeZone->new(name => $tz);
- }
+ if (!defined $self->{timezone}) {
+ my $tz = $self->setting('timezone');
+ if ($tz eq 'local') {
+
+ # The user wants the local timezone of the server.
+ $self->{timezone} = Bugzilla->local_timezone;
+ }
+ else {
+ $self->{timezone} = DateTime::TimeZone->new(name => $tz);
}
- return $self->{timezone};
+ }
+ return $self->{timezone};
}
sub flush_queries_cache {
- my $self = shift;
+ my $self = shift;
- delete $self->{queries};
- delete $self->{queries_subscribed};
- delete $self->{queries_available};
+ delete $self->{queries};
+ delete $self->{queries_subscribed};
+ delete $self->{queries_available};
}
sub groups {
- my $self = shift;
+ my $self = shift;
- return $self->{groups} if defined $self->{groups};
- return [] unless $self->id;
+ return $self->{groups} if defined $self->{groups};
+ return [] unless $self->id;
- my $user_groups_key = "user_groups." . $self->id;
- my $groups = Bugzilla->memcached->get_config({
- key => $user_groups_key
- });
+ my $user_groups_key = "user_groups." . $self->id;
+ my $groups = Bugzilla->memcached->get_config({key => $user_groups_key});
- if (!$groups) {
- my $dbh = Bugzilla->dbh;
- my $groups_to_check = $dbh->selectcol_arrayref(
- "SELECT DISTINCT group_id
+ if (!$groups) {
+ my $dbh = Bugzilla->dbh;
+ my $groups_to_check = $dbh->selectcol_arrayref(
+ "SELECT DISTINCT group_id
FROM user_group_map
- WHERE user_id = ? AND isbless = 0", undef, $self->id);
-
- my $grant_type_key = 'group_grant_type_' . GROUP_MEMBERSHIP;
- my $membership_rows = Bugzilla->memcached->get_config({
- key => $grant_type_key,
- });
- if (!$membership_rows) {
- $membership_rows = $dbh->selectall_arrayref(
- "SELECT DISTINCT grantor_id, member_id
+ WHERE user_id = ? AND isbless = 0", undef, $self->id
+ );
+
+ my $grant_type_key = 'group_grant_type_' . GROUP_MEMBERSHIP;
+ my $membership_rows
+ = Bugzilla->memcached->get_config({key => $grant_type_key,});
+ if (!$membership_rows) {
+ $membership_rows = $dbh->selectall_arrayref(
+ "SELECT DISTINCT grantor_id, member_id
FROM group_group_map
- WHERE grant_type = " . GROUP_MEMBERSHIP);
- Bugzilla->memcached->set_config({
- key => $grant_type_key,
- data => $membership_rows,
- });
- }
+ WHERE grant_type = " . GROUP_MEMBERSHIP
+ );
+ Bugzilla->memcached->set_config({
+ key => $grant_type_key, data => $membership_rows,
+ });
+ }
- my %group_membership;
- foreach my $row (@$membership_rows) {
- my ($grantor_id, $member_id) = @$row;
- push (@{ $group_membership{$member_id} }, $grantor_id);
- }
+ my %group_membership;
+ foreach my $row (@$membership_rows) {
+ my ($grantor_id, $member_id) = @$row;
+ push(@{$group_membership{$member_id}}, $grantor_id);
+ }
- # Let's walk the groups hierarchy tree (using FIFO)
- # On the first iteration it's pre-filled with direct groups
- # membership. Later on, each group can add its own members into the
- # FIFO. Circular dependencies are eliminated by checking
- # $checked_groups{$member_id} hash values.
- # As a result, %groups will have all the groups we are the member of.
- my %checked_groups;
- my %groups;
- while (scalar(@$groups_to_check) > 0) {
- # Pop the head group from FIFO
- my $member_id = shift @$groups_to_check;
-
- # Skip the group if we have already checked it
- if (!$checked_groups{$member_id}) {
- # Mark group as checked
- $checked_groups{$member_id} = 1;
-
- # Add all its members to the FIFO check list
- # %group_membership contains arrays of group members
- # for all groups. Accessible by group number.
- my $members = $group_membership{$member_id};
- my @new_to_check = grep(!$checked_groups{$_}, @$members);
- push(@$groups_to_check, @new_to_check);
-
- $groups{$member_id} = 1;
- }
- }
- $groups = [ keys %groups ];
+ # Let's walk the groups hierarchy tree (using FIFO)
+ # On the first iteration it's pre-filled with direct groups
+ # membership. Later on, each group can add its own members into the
+ # FIFO. Circular dependencies are eliminated by checking
+ # $checked_groups{$member_id} hash values.
+ # As a result, %groups will have all the groups we are the member of.
+ my %checked_groups;
+ my %groups;
+ while (scalar(@$groups_to_check) > 0) {
+
+ # Pop the head group from FIFO
+ my $member_id = shift @$groups_to_check;
+
+ # Skip the group if we have already checked it
+ if (!$checked_groups{$member_id}) {
- Bugzilla->memcached->set_config({
- key => $user_groups_key,
- data => $groups,
- });
+ # Mark group as checked
+ $checked_groups{$member_id} = 1;
+
+ # Add all its members to the FIFO check list
+ # %group_membership contains arrays of group members
+ # for all groups. Accessible by group number.
+ my $members = $group_membership{$member_id};
+ my @new_to_check = grep(!$checked_groups{$_}, @$members);
+ push(@$groups_to_check, @new_to_check);
+
+ $groups{$member_id} = 1;
+ }
}
+ $groups = [keys %groups];
+
+ Bugzilla->memcached->set_config({key => $user_groups_key, data => $groups,});
+ }
- $self->{groups} = Bugzilla::Group->new_from_list($groups);
- return $self->{groups};
+ $self->{groups} = Bugzilla::Group->new_from_list($groups);
+ return $self->{groups};
}
sub force_bug_dissociation {
- my ($self, $nobody, $groups, $timestamp) = @_;
- my $dbh = Bugzilla->dbh;
- my $auto_user = Bugzilla->user;
- $timestamp //= $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
-
- my $group_marks = join(", ", ('?') x @$groups);
- my $user_id = $self->id;
- my @params = ($user_id, $user_id, $user_id, $user_id,
- map { blessed $_ ? $_->id : $_ } @$groups);
- my $bugs = $dbh->selectall_arrayref(qq{
+ my ($self, $nobody, $groups, $timestamp) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $auto_user = Bugzilla->user;
+ $timestamp //= $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+
+ my $group_marks = join(", ", ('?') x @$groups);
+ my $user_id = $self->id;
+ my @params = (
+ $user_id, $user_id, $user_id, $user_id,
+ map { blessed $_ ? $_->id : $_ } @$groups
+ );
+ my $bugs = $dbh->selectall_arrayref(
+ qq{
SELECT
bugs.bug_id,
bugs.reporter_accessible,
@@ -1157,99 +1189,103 @@ sub force_bug_dissociation {
OR match_assignee
OR match_qa_contact
OR match_cc
- }, { Slice => {} }, @params);
-
- my @reporter_bugs = map { $_->{bug_id} } grep { $_->{match_reporter} } @$bugs;
- my @assignee_bugs = map { $_->{bug_id} } grep { $_->{match_assignee} } @$bugs;
- my @qa_bugs = map { $_->{bug_id} } grep { $_->{match_qa_contact} } @$bugs;
- my @cc_bugs = map { $_->{bug_id} } grep { $_->{match_cc} } @$bugs;
-
- # Reporter - set reporter_accessible to false
- my $reporter_accessible_field_id = get_field_id('reporter_accessible');
- foreach my $bug_id (@reporter_bugs) {
- $dbh->do(
- q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, ?)},
- undef, $bug_id, $auto_user->id, $timestamp, $reporter_accessible_field_id, 1, 0);
- $dbh->do(
- q{UPDATE bugs SET reporter_accessible = 0, delta_ts = ?, lastdiffed = ?
- WHERE bug_id = ?},
- undef, $timestamp, $timestamp, $bug_id);
- }
-
- # Assignee
- my $assigned_to_field_id = get_field_id('assigned_to');
- foreach my $bug_id (@assignee_bugs) {
- $dbh->do(
- q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, ?)},
- undef, $bug_id, $auto_user->id, $timestamp, $assigned_to_field_id,
- $self->login, $auto_user->login);
- $dbh->do(
- q{UPDATE bugs SET assigned_to = ?, delta_ts = ?, lastdiffed = ?
- WHERE bug_id = ?},
- undef, $nobody->id, $timestamp, $timestamp, $bug_id);
- }
-
- # QA Contact
- my $qa_field_id = get_field_id('qa_contact');
- foreach my $bug_id (@qa_bugs) {
- $dbh->do(
- q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, '')},
- undef, $bug_id, $auto_user->id, $timestamp, $qa_field_id, $self->login);
- $dbh->do(
- q{UPDATE bugs SET qa_contact = NULL, delta_ts = ?, lastdiffed = ?
- WHERE bug_id = ?},
- undef, $timestamp, $timestamp, $bug_id);
- }
-
- # CC list
- my $cc_field_id = get_field_id('cc');
- foreach my $bug_id (@cc_bugs) {
- $dbh->do(
- q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
- VALUES (?, ?, ?, ?, ?, '')},
- undef, $bug_id, $auto_user->id, $timestamp, $cc_field_id, $self->login);
- $dbh->do(q{DELETE FROM cc WHERE bug_id = ? AND who = ?},
- undef, $bug_id, $self->id);
- }
+ }, {Slice => {}}, @params
+ );
+
+ my @reporter_bugs = map { $_->{bug_id} } grep { $_->{match_reporter} } @$bugs;
+ my @assignee_bugs = map { $_->{bug_id} } grep { $_->{match_assignee} } @$bugs;
+ my @qa_bugs = map { $_->{bug_id} } grep { $_->{match_qa_contact} } @$bugs;
+ my @cc_bugs = map { $_->{bug_id} } grep { $_->{match_cc} } @$bugs;
+
+ # Reporter - set reporter_accessible to false
+ my $reporter_accessible_field_id = get_field_id('reporter_accessible');
+ foreach my $bug_id (@reporter_bugs) {
+ $dbh->do(
+ q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?)}, undef, $bug_id, $auto_user->id, $timestamp,
+ $reporter_accessible_field_id, 1, 0
+ );
+ $dbh->do(
+ q{UPDATE bugs SET reporter_accessible = 0, delta_ts = ?, lastdiffed = ?
+ WHERE bug_id = ?}, undef, $timestamp, $timestamp, $bug_id
+ );
+ }
+
+ # Assignee
+ my $assigned_to_field_id = get_field_id('assigned_to');
+ foreach my $bug_id (@assignee_bugs) {
+ $dbh->do(
+ q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, ?)}, undef, $bug_id, $auto_user->id, $timestamp,
+ $assigned_to_field_id, $self->login, $auto_user->login
+ );
+ $dbh->do(
+ q{UPDATE bugs SET assigned_to = ?, delta_ts = ?, lastdiffed = ?
+ WHERE bug_id = ?}, undef, $nobody->id, $timestamp, $timestamp, $bug_id
+ );
+ }
+
+ # QA Contact
+ my $qa_field_id = get_field_id('qa_contact');
+ foreach my $bug_id (@qa_bugs) {
+ $dbh->do(
+ q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, '')}, undef, $bug_id, $auto_user->id, $timestamp,
+ $qa_field_id, $self->login
+ );
+ $dbh->do(
+ q{UPDATE bugs SET qa_contact = NULL, delta_ts = ?, lastdiffed = ?
+ WHERE bug_id = ?}, undef, $timestamp, $timestamp, $bug_id
+ );
+ }
+
+ # CC list
+ my $cc_field_id = get_field_id('cc');
+ foreach my $bug_id (@cc_bugs) {
+ $dbh->do(
+ q{INSERT INTO bugs_activity (bug_id, who, bug_when, fieldid, removed, added)
+ VALUES (?, ?, ?, ?, ?, '')}, undef, $bug_id, $auto_user->id, $timestamp,
+ $cc_field_id, $self->login
+ );
+ $dbh->do(q{DELETE FROM cc WHERE bug_id = ? AND who = ?},
+ undef, $bug_id, $self->id);
+ }
- if (@reporter_bugs || @assignee_bugs || @qa_bugs || @cc_bugs) {
- $self->clear_last_statistics_ts();
+ if (@reporter_bugs || @assignee_bugs || @qa_bugs || @cc_bugs) {
+ $self->clear_last_statistics_ts();
- # It's complex to determine which items now need to be flushed from memcached.
- # As this is expected to be a rare event, we just flush the entire cache.
- Bugzilla->memcached->clear_all();
- }
+ # It's complex to determine which items now need to be flushed from memcached.
+ # As this is expected to be a rare event, we just flush the entire cache.
+ Bugzilla->memcached->clear_all();
+ }
- return $bugs;
+ return $bugs;
}
sub last_visited {
- my ($self) = @_;
+ my ($self) = @_;
- return Bugzilla::BugUserLastVisit->match({ user_id => $self->id });
+ return Bugzilla::BugUserLastVisit->match({user_id => $self->id});
}
sub is_involved_in_bug {
- my ($self, $bug) = @_;
- my $user_id = $self->id;
- my $user_login = $self->login;
+ my ($self, $bug) = @_;
+ my $user_id = $self->id;
+ my $user_login = $self->login;
- return unless $user_id;
- return 1 if $user_id == $bug->assigned_to->id;
- return 1 if $user_id == $bug->reporter->id;
+ return unless $user_id;
+ return 1 if $user_id == $bug->assigned_to->id;
+ return 1 if $user_id == $bug->reporter->id;
- if (Bugzilla->params->{'useqacontact'} and $bug->qa_contact) {
- return 1 if $user_id == $bug->qa_contact->id;
- }
+ if (Bugzilla->params->{'useqacontact'} and $bug->qa_contact) {
+ return 1 if $user_id == $bug->qa_contact->id;
+ }
- # BMO - Bug mentors are considered involved with the bug
- return 1 if $bug->is_mentor($self);
+ # BMO - Bug mentors are considered involved with the bug
+ return 1 if $bug->is_mentor($self);
- return unless $bug->cc;
- return any { $user_login eq $_ } @{ $bug->cc };
+ return unless $bug->cc;
+ return any { $user_login eq $_ } @{$bug->cc};
}
# It turns out that calling ->id on objects a few hundred thousand
@@ -1257,228 +1293,239 @@ sub is_involved_in_bug {
# when profiling xt/search.t.) So we cache the group ids separately from
# groups for functions that need the group ids.
sub _group_ids {
- my ($self) = @_;
- $self->{group_ids} ||= [map { $_->id } @{ $self->groups }];
- return $self->{group_ids};
+ my ($self) = @_;
+ $self->{group_ids} ||= [map { $_->id } @{$self->groups}];
+ return $self->{group_ids};
}
sub groups_as_string {
- my $self = shift;
- my $ids = $self->_group_ids;
- return scalar(@$ids) ? join(',', @$ids) : '-1';
+ my $self = shift;
+ my $ids = $self->_group_ids;
+ return scalar(@$ids) ? join(',', @$ids) : '-1';
}
sub groups_in_sql {
- my ($self, $field) = @_;
- $field ||= 'group_id';
- my $ids = $self->_group_ids;
- $ids = [-1] if !scalar @$ids;
- return Bugzilla->dbh->sql_in($field, $ids);
+ my ($self, $field) = @_;
+ $field ||= 'group_id';
+ my $ids = $self->_group_ids;
+ $ids = [-1] if !scalar @$ids;
+ return Bugzilla->dbh->sql_in($field, $ids);
}
sub groups_owned {
- my $self = shift;
- return $self->{groups_owned} //= Bugzilla::Group->match({ owner_user_id => $self->id });
+ my $self = shift;
+ return $self->{groups_owned}
+ //= Bugzilla::Group->match({owner_user_id => $self->id});
}
sub bless_groups {
- my $self = shift;
+ my $self = shift;
- return $self->{'bless_groups'} if defined $self->{'bless_groups'};
- return [] unless $self->id;
+ return $self->{'bless_groups'} if defined $self->{'bless_groups'};
+ return [] unless $self->id;
- if ($self->in_group('admin')) {
- # Users having admin permissions may bless all groups.
- $self->{'bless_groups'} = [Bugzilla::Group->get_all];
- return $self->{'bless_groups'};
- }
+ if ($self->in_group('admin')) {
- if (Bugzilla->params->{usevisibilitygroups}
- && !$self->visible_groups_inherited) {
- return [];
- }
+ # Users having admin permissions may bless all groups.
+ $self->{'bless_groups'} = [Bugzilla::Group->get_all];
+ return $self->{'bless_groups'};
+ }
+
+ if (Bugzilla->params->{usevisibilitygroups} && !$self->visible_groups_inherited)
+ {
+ return [];
+ }
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- # Get all groups for the user where they have direct bless privileges.
- my $query = "
+ # Get all groups for the user where they have direct bless privileges.
+ my $query = "
SELECT DISTINCT group_id
FROM user_group_map
WHERE user_id = ?
AND isbless = 1";
- if (Bugzilla->params->{usevisibilitygroups}) {
- $query .= " AND "
- . $dbh->sql_in('group_id', $self->visible_groups_inherited);
- }
-
- # Get all groups for the user where they are a member of a group that
- # inherits bless privs.
- my @group_ids = map { $_->id } @{ $self->groups };
- if (@group_ids) {
- $query .= "
+ if (Bugzilla->params->{usevisibilitygroups}) {
+ $query .= " AND " . $dbh->sql_in('group_id', $self->visible_groups_inherited);
+ }
+
+ # Get all groups for the user where they are a member of a group that
+ # inherits bless privs.
+ my @group_ids = map { $_->id } @{$self->groups};
+ if (@group_ids) {
+ $query .= "
UNION
SELECT DISTINCT grantor_id
FROM group_group_map
WHERE grant_type = " . GROUP_BLESS . "
AND " . $dbh->sql_in('member_id', \@group_ids);
- if (Bugzilla->params->{usevisibilitygroups}) {
- $query .= " AND "
- . $dbh->sql_in('grantor_id', $self->visible_groups_inherited);
- }
+ if (Bugzilla->params->{usevisibilitygroups}) {
+ $query .= " AND " . $dbh->sql_in('grantor_id', $self->visible_groups_inherited);
}
+ }
- my $ids = $dbh->selectcol_arrayref($query, undef, $self->id);
- return $self->{bless_groups} = Bugzilla::Group->new_from_list($ids);
+ my $ids = $dbh->selectcol_arrayref($query, undef, $self->id);
+ return $self->{bless_groups} = Bugzilla::Group->new_from_list($ids);
}
sub in_group {
- my ($self, $group, $product_id) = @_;
- $group = $group->name if blessed $group;
- $self->{in_group} //= { map { $_->name => $_ } @{ $self->groups } };
+ my ($self, $group, $product_id) = @_;
+ $group = $group->name if blessed $group;
+ $self->{in_group} //= {map { $_->name => $_ } @{$self->groups}};
- if ($self->{in_group}{$group}) {
- return 1;
- }
- elsif ($product_id && detaint_natural($product_id)) {
- # Make sure $group exists on a per-product basis.
- return 0 unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
-
- $self->{"product_$product_id"} = {} unless exists $self->{"product_$product_id"};
- if (!defined $self->{"product_$product_id"}->{$group}) {
- my $dbh = Bugzilla->dbh;
- my $in_group = $dbh->selectrow_array(
- "SELECT 1
+ if ($self->{in_group}{$group}) {
+ return 1;
+ }
+ elsif ($product_id && detaint_natural($product_id)) {
+
+ # Make sure $group exists on a per-product basis.
+ return 0 unless (grep { $_ eq $group } PER_PRODUCT_PRIVILEGES);
+
+ $self->{"product_$product_id"} = {}
+ unless exists $self->{"product_$product_id"};
+ if (!defined $self->{"product_$product_id"}->{$group}) {
+ my $dbh = Bugzilla->dbh;
+ my $in_group = $dbh->selectrow_array(
+ "SELECT 1
FROM group_control_map
WHERE product_id = ?
AND $group != 0
- AND " . $self->groups_in_sql . ' ' .
- $dbh->sql_limit(1),
- undef, $product_id);
+ AND "
+ . $self->groups_in_sql . ' ' . $dbh->sql_limit(1), undef, $product_id
+ );
- $self->{"product_$product_id"}->{$group} = $in_group ? 1 : 0;
- }
- return $self->{"product_$product_id"}->{$group};
+ $self->{"product_$product_id"}->{$group} = $in_group ? 1 : 0;
}
- # If we come here, then the user is not in the requested group.
- return 0;
+ return $self->{"product_$product_id"}->{$group};
+ }
+
+ # If we come here, then the user is not in the requested group.
+ return 0;
}
sub in_group_id {
- my ($self, $id) = @_;
- return grep($_->id == $id, @{ $self->groups }) ? 1 : 0;
+ my ($self, $id) = @_;
+ return grep($_->id == $id, @{$self->groups}) ? 1 : 0;
}
# This is a helper to get all groups which have an icon to be displayed
# besides the name of the commenter.
sub groups_with_icon {
- my $self = shift;
+ my $self = shift;
- return $self->{groups_with_icon} //= [grep { $_->icon_url } @{ $self->groups }];
+ return $self->{groups_with_icon} //= [grep { $_->icon_url } @{$self->groups}];
}
sub get_products_by_permission {
- my ($self, $group) = @_;
- # Make sure $group exists on a per-product basis.
- return [] unless (grep {$_ eq $group} PER_PRODUCT_PRIVILEGES);
+ my ($self, $group) = @_;
- my $product_ids = Bugzilla->dbh->selectcol_arrayref(
- "SELECT DISTINCT product_id
+ # Make sure $group exists on a per-product basis.
+ return [] unless (grep { $_ eq $group } PER_PRODUCT_PRIVILEGES);
+
+ my $product_ids = Bugzilla->dbh->selectcol_arrayref(
+ "SELECT DISTINCT product_id
FROM group_control_map
WHERE $group != 0
- AND " . $self->groups_in_sql);
+ AND " . $self->groups_in_sql
+ );
- # No need to go further if the user has no "special" privs.
- return [] unless scalar(@$product_ids);
- my %product_map = map { $_ => 1 } @$product_ids;
+ # No need to go further if the user has no "special" privs.
+ return [] unless scalar(@$product_ids);
+ my %product_map = map { $_ => 1 } @$product_ids;
- # We will restrict the list to products the user can see.
- my $selectable_products = $self->get_selectable_products;
- my @products = grep { $product_map{$_->id} } @$selectable_products;
- return \@products;
+ # We will restrict the list to products the user can see.
+ my $selectable_products = $self->get_selectable_products;
+ my @products = grep { $product_map{$_->id} } @$selectable_products;
+ return \@products;
}
sub can_see_user {
- my ($self, $otherUser) = @_;
- my $query;
+ my ($self, $otherUser) = @_;
+ my $query;
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- # If the user can see no groups, then no users are visible either.
- my $visibleGroups = $self->visible_groups_as_string() || return 0;
- $query = qq{SELECT COUNT(DISTINCT userid)
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+
+ # If the user can see no groups, then no users are visible either.
+ my $visibleGroups = $self->visible_groups_as_string() || 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)
+ }
+ else {
+ $query = qq{SELECT COUNT(userid)
FROM profiles
WHERE userid = ?
};
- }
- return Bugzilla->dbh->selectrow_array($query, undef, $otherUser->id);
+ }
+ return Bugzilla->dbh->selectrow_array($query, undef, $otherUser->id);
}
sub can_edit_product {
- my ($self, $prod_id) = @_;
- my $dbh = Bugzilla->dbh;
+ my ($self, $prod_id) = @_;
+ my $dbh = Bugzilla->dbh;
- my $has_external_groups =
- $dbh->selectrow_array('SELECT 1
+ my $has_external_groups = $dbh->selectrow_array(
+ 'SELECT 1
FROM group_control_map
WHERE product_id = ?
AND canedit != 0
- AND group_id NOT IN(' . $self->groups_as_string . ')',
- undef, $prod_id);
+ AND group_id NOT IN('
+ . $self->groups_as_string . ')', undef, $prod_id
+ );
- return !$has_external_groups;
+ return !$has_external_groups;
}
sub can_see_bug {
- my ($self, $bug_id) = @_;
- return @{ $self->visible_bugs([$bug_id]) } ? 1 : 0;
+ my ($self, $bug_id) = @_;
+ return @{$self->visible_bugs([$bug_id])} ? 1 : 0;
}
sub visible_bugs {
- my ($self, $bugs) = @_;
- # Allow users to pass in Bug objects and bug ids both.
- my @bug_ids = map { blessed $_ ? $_->id : $_ } @$bugs;
-
- # We only check the visibility of bugs that we haven't
- # checked yet.
- # Bugzilla::Bug->update automatically removes updated bugs
- # from the cache to force them to be checked again.
- my $visible_cache = $self->{_visible_bugs_cache} ||= {};
- my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
-
- if (@check_ids) {
- my $dbh = Bugzilla->dbh;
- my $user_id = $self->id;
-
- foreach my $id (@check_ids) {
- my $orig_id = $id;
- detaint_natural($id)
- || ThrowCodeError('param_must_be_numeric', { param => $orig_id,
- function => 'Bugzilla::User->visible_bugs'});
- }
+ my ($self, $bugs) = @_;
- my $sth;
- # Speed up the can_see_bug case.
- if (scalar(@check_ids) == 1) {
- $sth = $self->{_sth_one_visible_bug};
- }
- $sth ||= $dbh->prepare(
- # This checks for groups that the bug is in that the user
- # *isn't* in. Then, in the Perl code below, we check if
- # the user can otherwise access the bug (for example, by being
- # the assignee or QA Contact).
- #
- # The DISTINCT exists because the bug could be in *several*
- # groups that the user isn't in, but they will all return the
- # same result for bug_group_map.bug_id (so DISTINCT filters
- # out duplicate rows).
- "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
+ # Allow users to pass in Bug objects and bug ids both.
+ my @bug_ids = map { blessed $_ ? $_->id : $_ } @$bugs;
+
+ # We only check the visibility of bugs that we haven't
+ # checked yet.
+ # Bugzilla::Bug->update automatically removes updated bugs
+ # from the cache to force them to be checked again.
+ my $visible_cache = $self->{_visible_bugs_cache} ||= {};
+ my @check_ids = grep(!exists $visible_cache->{$_}, @bug_ids);
+
+ if (@check_ids) {
+ my $dbh = Bugzilla->dbh;
+ my $user_id = $self->id;
+
+ foreach my $id (@check_ids) {
+ my $orig_id = $id;
+ detaint_natural($id)
+ || ThrowCodeError('param_must_be_numeric',
+ {param => $orig_id, function => 'Bugzilla::User->visible_bugs'});
+ }
+
+ my $sth;
+
+ # Speed up the can_see_bug case.
+ if (scalar(@check_ids) == 1) {
+ $sth = $self->{_sth_one_visible_bug};
+ }
+ $sth ||= $dbh->prepare(
+
+ # This checks for groups that the bug is in that the user
+ # *isn't* in. Then, in the Perl code below, we check if
+ # the user can otherwise access the bug (for example, by being
+ # the assignee or QA Contact).
+ #
+ # The DISTINCT exists because the bug could be in *several*
+ # groups that the user isn't in, but they will all return the
+ # same result for bug_group_map.bug_id (so DISTINCT filters
+ # out duplicate rows).
+ "SELECT DISTINCT bugs.bug_id, reporter, assigned_to, qa_contact,
reporter_accessible, cclist_accessible, cc.who,
bug_group_map.bug_id
FROM bugs
@@ -1488,1107 +1535,1173 @@ sub visible_bugs {
LEFT JOIN bug_group_map
ON bugs.bug_id = bug_group_map.bug_id
AND bug_group_map.group_id NOT IN ("
- . $self->groups_as_string . ')
+ . $self->groups_as_string . ')
WHERE bugs.bug_id IN (' . join(',', ('?') x @check_ids) . ')
- AND creation_ts IS NOT NULL ');
- if (scalar(@check_ids) == 1) {
- $self->{_sth_one_visible_bug} = $sth;
- }
+ AND creation_ts IS NOT NULL '
+ );
+ if (scalar(@check_ids) == 1) {
+ $self->{_sth_one_visible_bug} = $sth;
+ }
- $sth->execute(@check_ids);
- my $use_qa_contact = Bugzilla->params->{'useqacontact'};
- while (my $row = $sth->fetchrow_arrayref) {
- my ($bug_id, $reporter, $owner, $qacontact, $reporter_access,
- $cclist_access, $isoncclist, $missinggroup) = @$row;
- $visible_cache->{$bug_id} ||=
- ((($reporter == $user_id) && $reporter_access)
- || ($use_qa_contact
- && $qacontact && ($qacontact == $user_id))
- || ($owner == $user_id)
- || ($isoncclist && $cclist_access)
- || !$missinggroup) ? 1 : 0;
- }
+ $sth->execute(@check_ids);
+ my $use_qa_contact = Bugzilla->params->{'useqacontact'};
+ while (my $row = $sth->fetchrow_arrayref) {
+ my ($bug_id, $reporter, $owner, $qacontact, $reporter_access, $cclist_access,
+ $isoncclist, $missinggroup)
+ = @$row;
+ $visible_cache->{$bug_id}
+ ||= ((($reporter == $user_id) && $reporter_access)
+ || ($use_qa_contact && $qacontact && ($qacontact == $user_id))
+ || ($owner == $user_id)
+ || ($isoncclist && $cclist_access)
+ || !$missinggroup) ? 1 : 0;
}
+ }
- return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
+ return [grep { $visible_cache->{blessed $_ ? $_->id : $_} } @$bugs];
}
sub clear_product_cache {
- my $self = shift;
- delete $self->{enterable_products};
- delete $self->{selectable_products};
- delete $self->{selectable_classifications};
+ my $self = shift;
+ delete $self->{enterable_products};
+ delete $self->{selectable_products};
+ delete $self->{selectable_classifications};
}
sub can_see_product {
- my ($self, $product_name) = @_;
+ my ($self, $product_name) = @_;
- return any { $_->name eq $product_name } @{$self->get_selectable_products};
+ return any { $_->name eq $product_name } @{$self->get_selectable_products};
}
sub get_selectable_products {
- my $self = shift;
- my $class_id = shift;
- my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id;
-
- if (!defined $self->{selectable_products}) {
- my $query = "SELECT id " .
- " FROM products " .
- "LEFT JOIN group_control_map " .
- "ON group_control_map.product_id = products.id " .
- " AND group_control_map.membercontrol = " . CONTROLMAPMANDATORY .
- " AND group_id NOT IN(" . $self->groups_as_string . ") " .
- " WHERE group_id IS NULL " .
- "ORDER BY name";
-
- my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query);
- $self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids);
- }
-
- # Restrict the list of products to those being in the classification, if any.
- if ($class_restricted) {
- return [grep {$_->classification_id == $class_id} @{$self->{selectable_products}}];
- }
- # If we come here, then we want all selectable products.
- return $self->{selectable_products};
+ my $self = shift;
+ my $class_id = shift;
+ my $class_restricted = Bugzilla->params->{'useclassification'} && $class_id;
+
+ if (!defined $self->{selectable_products}) {
+ my $query
+ = "SELECT id "
+ . " FROM products "
+ . "LEFT JOIN group_control_map "
+ . "ON group_control_map.product_id = products.id "
+ . " AND group_control_map.membercontrol = "
+ . CONTROLMAPMANDATORY
+ . " AND group_id NOT IN("
+ . $self->groups_as_string . ") "
+ . " WHERE group_id IS NULL "
+ . "ORDER BY name";
+
+ my $prod_ids = Bugzilla->dbh->selectcol_arrayref($query);
+ $self->{selectable_products} = Bugzilla::Product->new_from_list($prod_ids);
+ }
+
+ # Restrict the list of products to those being in the classification, if any.
+ if ($class_restricted) {
+ return [grep { $_->classification_id == $class_id }
+ @{$self->{selectable_products}}];
+ }
+
+ # If we come here, then we want all selectable products.
+ return $self->{selectable_products};
}
sub get_selectable_classifications {
- my ($self) = @_;
+ my ($self) = @_;
- if (!defined $self->{selectable_classifications}) {
- my $products = $self->get_selectable_products;
- my %class_ids = map { $_->classification_id => 1 } @$products;
+ if (!defined $self->{selectable_classifications}) {
+ my $products = $self->get_selectable_products;
+ my %class_ids = map { $_->classification_id => 1 } @$products;
- $self->{selectable_classifications} = Bugzilla::Classification->new_from_list([keys %class_ids]);
- }
- return $self->{selectable_classifications};
+ $self->{selectable_classifications}
+ = Bugzilla::Classification->new_from_list([keys %class_ids]);
+ }
+ return $self->{selectable_classifications};
}
sub can_enter_product {
- my ($self, $input, $warn) = @_;
- my $dbh = Bugzilla->dbh;
- $warn ||= 0;
-
- $input = trim($input) if !ref $input;
- if (!defined $input or $input eq '') {
- return unless $warn == THROW_ERROR;
- ThrowUserError('object_not_specified',
- { class => 'Bugzilla::Product' });
- }
+ my ($self, $input, $warn) = @_;
+ my $dbh = Bugzilla->dbh;
+ $warn ||= 0;
- if (!scalar @{ $self->get_enterable_products }) {
- return unless $warn == THROW_ERROR;
- ThrowUserError('no_products');
- }
+ $input = trim($input) if !ref $input;
+ if (!defined $input or $input eq '') {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('object_not_specified', {class => 'Bugzilla::Product'});
+ }
- my $product = blessed($input) ? $input
- : new Bugzilla::Product({ name => $input });
- my $can_enter =
- $product && grep($_->name eq $product->name,
- @{ $self->get_enterable_products });
+ if (!scalar @{$self->get_enterable_products}) {
+ return unless $warn == THROW_ERROR;
+ ThrowUserError('no_products');
+ }
- return $product if $can_enter;
+ my $product
+ = blessed($input) ? $input : new Bugzilla::Product({name => $input});
+ my $can_enter = $product
+ && grep($_->name eq $product->name, @{$self->get_enterable_products});
- return 0 unless $warn == THROW_ERROR;
+ return $product if $can_enter;
- # Check why access was denied. These checks are slow,
- # but that's fine, because they only happen if we fail.
+ return 0 unless $warn == THROW_ERROR;
- # We don't just use $product->name for error messages, because if it
- # changes case from $input, then that's a clue that the product does
- # exist but is hidden.
- my $name = blessed($input) ? $input->name : $input;
+ # Check why access was denied. These checks are slow,
+ # but that's fine, because they only happen if we fail.
- # The product could not exist or you could be denied...
- if (!$product || !$product->user_has_access($self)) {
- ThrowUserError('entry_access_denied', { product => $name });
- }
- # It could be closed for bug entry...
- elsif (!$product->is_active) {
- ThrowUserError('product_disabled', { product => $product });
- }
- # It could have no components...
- elsif (!@{$product->components}
- || !grep { $_->is_active } @{$product->components})
- {
- ThrowUserError('missing_component', { product => $product });
- }
- # It could have no versions...
- elsif (!@{$product->versions}
- || !grep { $_->is_active } @{$product->versions})
- {
- ThrowUserError ('missing_version', { product => $product });
- }
+ # We don't just use $product->name for error messages, because if it
+ # changes case from $input, then that's a clue that the product does
+ # exist but is hidden.
+ my $name = blessed($input) ? $input->name : $input;
+
+ # The product could not exist or you could be denied...
+ if (!$product || !$product->user_has_access($self)) {
+ ThrowUserError('entry_access_denied', {product => $name});
+ }
+
+ # It could be closed for bug entry...
+ elsif (!$product->is_active) {
+ ThrowUserError('product_disabled', {product => $product});
+ }
+
+ # It could have no components...
+ elsif (!@{$product->components}
+ || !grep { $_->is_active } @{$product->components})
+ {
+ ThrowUserError('missing_component', {product => $product});
+ }
+
+ # It could have no versions...
+ elsif (!@{$product->versions} || !grep { $_->is_active } @{$product->versions})
+ {
+ ThrowUserError('missing_version', {product => $product});
+ }
- die "can_enter_product reached an unreachable location.";
+ die "can_enter_product reached an unreachable location.";
}
sub get_enterable_products {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (defined $self->{enterable_products}) {
- return $self->{enterable_products};
- }
+ if (defined $self->{enterable_products}) {
+ return $self->{enterable_products};
+ }
- # All products which the user has "Entry" access to.
- my $enterable_ids = $dbh->selectcol_arrayref(
- 'SELECT products.id FROM products
+ # All products which the user has "Entry" access to.
+ my $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT products.id FROM products
LEFT JOIN group_control_map
ON group_control_map.product_id = products.id
AND group_control_map.entry != 0
AND group_id NOT IN (' . $self->groups_as_string . ')
WHERE group_id IS NULL
- AND products.isactive = 1');
-
- if (scalar @$enterable_ids) {
- # And all of these products must have at least one component
- # and one version.
- $enterable_ids = $dbh->selectcol_arrayref(
- 'SELECT DISTINCT products.id FROM products
- WHERE ' . $dbh->sql_in('products.id', $enterable_ids) .
- ' AND products.id IN (SELECT DISTINCT components.product_id
+ AND products.isactive = 1'
+ );
+
+ if (scalar @$enterable_ids) {
+
+ # And all of these products must have at least one component
+ # and one version.
+ $enterable_ids = $dbh->selectcol_arrayref(
+ 'SELECT DISTINCT products.id FROM products
+ WHERE '
+ . $dbh->sql_in('products.id', $enterable_ids)
+ . ' AND products.id IN (SELECT DISTINCT components.product_id
FROM components
WHERE components.isactive = 1)
AND products.id IN (SELECT DISTINCT versions.product_id
FROM versions
- WHERE versions.isactive = 1)');
- }
+ WHERE versions.isactive = 1)'
+ );
+ }
- $self->{enterable_products} =
- Bugzilla::Product->new_from_list($enterable_ids);
- return $self->{enterable_products};
+ $self->{enterable_products} = Bugzilla::Product->new_from_list($enterable_ids);
+ return $self->{enterable_products};
}
sub can_access_product {
- my ($self, $product) = @_;
- my $product_name = blessed($product) ? $product->name : $product;
- return scalar(grep {$_->name eq $product_name} @{$self->get_accessible_products});
+ my ($self, $product) = @_;
+ my $product_name = blessed($product) ? $product->name : $product;
+ return
+ scalar(grep { $_->name eq $product_name } @{$self->get_accessible_products});
}
sub get_accessible_products {
- my $self = shift;
+ my $self = shift;
- # Map the objects into a hash using the ids as keys
- my %products = map { $_->id => $_ }
- @{$self->get_selectable_products},
- @{$self->get_enterable_products};
+ # Map the objects into a hash using the ids as keys
+ my %products = map { $_->id => $_ } @{$self->get_selectable_products},
+ @{$self->get_enterable_products};
- return [ sort { $a->name cmp $b->name } values %products ];
+ return [sort { $a->name cmp $b->name } values %products];
}
sub check_can_admin_product {
- my ($self, $product_name) = @_;
+ my ($self, $product_name) = @_;
- # First make sure the product name is valid.
- my $product = Bugzilla::Product->check($product_name);
+ # First make sure the product name is valid.
+ my $product = Bugzilla::Product->check($product_name);
- ($self->in_group('editcomponents', $product->id)
- && $self->can_see_product($product->name))
- || ThrowUserError('product_admin_denied', {product => $product->name});
+ ( $self->in_group('editcomponents', $product->id)
+ && $self->can_see_product($product->name))
+ || ThrowUserError('product_admin_denied', {product => $product->name});
- # Return the validated product object.
- return $product;
+ # Return the validated product object.
+ return $product;
}
sub check_can_admin_flagtype {
- my ($self, $flagtype_id) = @_;
-
- my $flagtype = Bugzilla::FlagType->check({ id => $flagtype_id });
- my $can_fully_edit = 1;
-
- if (!$self->in_group('editcomponents')) {
- my $products = $self->get_products_by_permission('editcomponents');
- # You need editcomponents privs for at least one product to have
- # a chance to edit the flagtype.
- scalar(@$products)
- || ThrowUserError('auth_failure', {group => 'editcomponents',
- action => 'edit',
- object => 'flagtypes'});
- my $can_admin = 0;
- my $i = $flagtype->inclusions_as_hash;
- my $e = $flagtype->exclusions_as_hash;
-
- # If there is at least one product for which the user doesn't have
- # editcomponents privs, then don't allow him to do everything with
- # this flagtype, independently of whether this product is in the
- # exclusion list or not.
- my %product_ids;
- map { $product_ids{$_->id} = 1 } @$products;
- $can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
-
- unless ($e->{0}->{0}) {
- foreach my $product (@$products) {
- my $id = $product->id;
- next if $e->{$id}->{0};
- # If we are here, the product has not been explicitly excluded.
- # Check whether it's explicitly included, or at least one of
- # its components.
- $can_admin = ($i->{0}->{0} || $i->{$id}->{0}
- || scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
- last if $can_admin;
- }
- }
- $can_admin || ThrowUserError('flag_type_not_editable', { flagtype => $flagtype });
- }
- return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
+ my ($self, $flagtype_id) = @_;
+
+ my $flagtype = Bugzilla::FlagType->check({id => $flagtype_id});
+ my $can_fully_edit = 1;
+
+ if (!$self->in_group('editcomponents')) {
+ my $products = $self->get_products_by_permission('editcomponents');
+
+ # You need editcomponents privs for at least one product to have
+ # a chance to edit the flagtype.
+ scalar(@$products)
+ || ThrowUserError('auth_failure',
+ {group => 'editcomponents', action => 'edit', object => 'flagtypes'});
+ my $can_admin = 0;
+ my $i = $flagtype->inclusions_as_hash;
+ my $e = $flagtype->exclusions_as_hash;
+
+ # If there is at least one product for which the user doesn't have
+ # editcomponents privs, then don't allow him to do everything with
+ # this flagtype, independently of whether this product is in the
+ # exclusion list or not.
+ my %product_ids;
+ map { $product_ids{$_->id} = 1 } @$products;
+ $can_fully_edit = 0 if grep { !$product_ids{$_} } keys %$i;
+
+ unless ($e->{0}->{0}) {
+ foreach my $product (@$products) {
+ my $id = $product->id;
+ next if $e->{$id}->{0};
+
+ # If we are here, the product has not been explicitly excluded.
+ # Check whether it's explicitly included, or at least one of
+ # its components.
+ $can_admin
+ = ( $i->{0}->{0}
+ || $i->{$id}->{0}
+ || scalar(grep { !$e->{$id}->{$_} } keys %{$i->{$id}}));
+ last if $can_admin;
+ }
+ }
+ $can_admin || ThrowUserError('flag_type_not_editable', {flagtype => $flagtype});
+ }
+ return wantarray ? ($flagtype, $can_fully_edit) : $flagtype;
}
sub can_change_flag {
- my ($self, $flag_type, $old_status, $new_status) = @_;
+ my ($self, $flag_type, $old_status, $new_status) = @_;
- # "old_status:new_status" => [OR conditions
- state $flag_transitions = {
- 'X:-' => ['grant_group'],
- 'X:+' => ['grant_group'],
- 'X:?' => ['request_group'],
+ # "old_status:new_status" => [OR conditions
+ state $flag_transitions = {
+ 'X:-' => ['grant_group'],
+ 'X:+' => ['grant_group'],
+ 'X:?' => ['request_group'],
- '?:X' => ['request_group', 'is_setter'],
- '?:-' => ['grant_group'],
- '?:+' => ['grant_group'],
+ '?:X' => ['request_group', 'is_setter'],
+ '?:-' => ['grant_group'],
+ '?:+' => ['grant_group'],
- '+:X' => ['grant_group'],
- '+:-' => ['grant_group'],
- '+:?' => ['grant_group'],
+ '+:X' => ['grant_group'],
+ '+:-' => ['grant_group'],
+ '+:?' => ['grant_group'],
- '-:X' => ['grant_group'],
- '-:+' => ['grant_group'],
- '-:?' => ['grant_group'],
- };
+ '-:X' => ['grant_group'],
+ '-:+' => ['grant_group'],
+ '-:?' => ['grant_group'],
+ };
- return 1 if $new_status eq $old_status;
+ return 1 if $new_status eq $old_status;
- my $action = "$old_status:$new_status";
- my %bool = (
- request_group => $self->can_request_flag($flag_type),
- grant_group => $self->can_set_flag($flag_type),
- is_setter => $self->id == Bugzilla->user->id,
- );
+ my $action = "$old_status:$new_status";
+ my %bool = (
+ request_group => $self->can_request_flag($flag_type),
+ grant_group => $self->can_set_flag($flag_type),
+ is_setter => $self->id == Bugzilla->user->id,
+ );
- my $cond = $flag_transitions->{$action};
- if ($cond) {
- if (any { $bool{ $_ } } @$cond) {
- return 1;
- }
- else {
- return 0;
- }
+ my $cond = $flag_transitions->{$action};
+ if ($cond) {
+ if (any { $bool{$_} } @$cond) {
+ return 1;
}
else {
- warn "unknown flag transition blocked: $action";
- return 0;
+ return 0;
}
+ }
+ else {
+ warn "unknown flag transition blocked: $action";
+ return 0;
+ }
}
sub can_request_flag {
- my ($self, $flag_type) = @_;
+ my ($self, $flag_type) = @_;
- return ($self->can_set_flag($flag_type)
- || !$flag_type->request_group_id
- || $self->in_group_id($flag_type->request_group_id)) ? 1 : 0;
+ return ($self->can_set_flag($flag_type)
+ || !$flag_type->request_group_id
+ || $self->in_group_id($flag_type->request_group_id)) ? 1 : 0;
}
sub can_set_flag {
- my ($self, $flag_type) = @_;
+ my ($self, $flag_type) = @_;
- return (!$flag_type->grant_group_id
- || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
+ return (!$flag_type->grant_group_id
+ || $self->in_group_id($flag_type->grant_group_id)) ? 1 : 0;
}
sub can_unset_flag {
- my ($self, $flag_type, $flag_status) = @_;
- return 1 if !$flag_type->grant_group_id;
- return 1 if ($flag_status ne '+' && $flag_status ne '-');
- return $self->in_group_id($flag_type->grant_group_id) ? 1 : 0;
+ my ($self, $flag_type, $flag_status) = @_;
+ return 1 if !$flag_type->grant_group_id;
+ return 1 if ($flag_status ne '+' && $flag_status ne '-');
+ return $self->in_group_id($flag_type->grant_group_id) ? 1 : 0;
}
# visible_groups_inherited returns a reference to a list of all the groups
# whose members are visible to this user.
sub visible_groups_inherited {
- my $self = shift;
- return $self->{visible_groups_inherited} if defined $self->{visible_groups_inherited};
- return [] unless $self->id;
- my @visgroups = @{$self->visible_groups_direct};
- @visgroups = @{Bugzilla::Group->flatten_group_membership(@visgroups)};
- $self->{visible_groups_inherited} = \@visgroups;
- return $self->{visible_groups_inherited};
+ my $self = shift;
+ return $self->{visible_groups_inherited}
+ if defined $self->{visible_groups_inherited};
+ return [] unless $self->id;
+ my @visgroups = @{$self->visible_groups_direct};
+ @visgroups = @{Bugzilla::Group->flatten_group_membership(@visgroups)};
+ $self->{visible_groups_inherited} = \@visgroups;
+ return $self->{visible_groups_inherited};
}
# visible_groups_direct returns a reference to a list of all the groups that
# are visible to this user.
sub visible_groups_direct {
- my $self = shift;
- my @visgroups = ();
- return $self->{visible_groups_direct} if defined $self->{visible_groups_direct};
- return [] unless $self->id;
+ my $self = shift;
+ my @visgroups = ();
+ return $self->{visible_groups_direct} if defined $self->{visible_groups_direct};
+ return [] unless $self->id;
- my $dbh = Bugzilla->dbh;
- my $sth;
+ my $dbh = Bugzilla->dbh;
+ my $sth;
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $sth = $dbh->prepare("SELECT DISTINCT grantor_id
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $sth = $dbh->prepare(
+ "SELECT DISTINCT grantor_id
FROM group_group_map
WHERE " . $self->groups_in_sql('member_id') . "
- AND grant_type=" . GROUP_VISIBLE);
- }
- else {
- # All groups are visible if usevisibilitygroups is off.
- $sth = $dbh->prepare('SELECT id FROM groups');
- }
- $sth->execute();
+ AND grant_type=" . GROUP_VISIBLE
+ );
+ }
+ else {
+ # All groups are visible if usevisibilitygroups is off.
+ $sth = $dbh->prepare('SELECT id FROM groups');
+ }
+ $sth->execute();
- while (my ($row) = $sth->fetchrow_array) {
- push @visgroups,$row;
- }
- $self->{visible_groups_direct} = \@visgroups;
+ while (my ($row) = $sth->fetchrow_array) {
+ push @visgroups, $row;
+ }
+ $self->{visible_groups_direct} = \@visgroups;
- return $self->{visible_groups_direct};
+ return $self->{visible_groups_direct};
}
sub visible_groups_as_string {
- my $self = shift;
- return join(', ', @{$self->visible_groups_inherited()});
+ my $self = shift;
+ return join(', ', @{$self->visible_groups_inherited()});
}
# This function defines the groups a user may share a query with.
# More restrictive sites may want to build this reference to a list of group IDs
# from bless_groups instead of mirroring visible_groups_inherited, perhaps.
sub queryshare_groups {
- my $self = shift;
- my @queryshare_groups;
-
- return $self->{queryshare_groups} if defined $self->{queryshare_groups};
-
- if ($self->in_group(Bugzilla->params->{'querysharegroup'})) {
- # We want to be allowed to share with groups we're in only.
- # If usevisibilitygroups is on, then we need to restrict this to groups
- # we may see.
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- foreach(@{$self->visible_groups_inherited()}) {
- next unless $self->in_group_id($_);
- push(@queryshare_groups, $_);
- }
- }
- else {
- @queryshare_groups = @{ $self->_group_ids };
- }
+ my $self = shift;
+ my @queryshare_groups;
+
+ return $self->{queryshare_groups} if defined $self->{queryshare_groups};
+
+ if ($self->in_group(Bugzilla->params->{'querysharegroup'})) {
+
+ # We want to be allowed to share with groups we're in only.
+ # If usevisibilitygroups is on, then we need to restrict this to groups
+ # we may see.
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ foreach (@{$self->visible_groups_inherited()}) {
+ next unless $self->in_group_id($_);
+ push(@queryshare_groups, $_);
+ }
}
+ else {
+ @queryshare_groups = @{$self->_group_ids};
+ }
+ }
- return $self->{queryshare_groups} = \@queryshare_groups;
+ return $self->{queryshare_groups} = \@queryshare_groups;
}
sub queryshare_groups_as_string {
- my $self = shift;
- return join(', ', @{$self->queryshare_groups()});
+ my $self = shift;
+ return join(', ', @{$self->queryshare_groups()});
}
sub derive_regexp_groups {
- my ($self) = @_;
+ my ($self) = @_;
- my $id = $self->id;
- return unless $id;
+ my $id = $self->id;
+ return unless $id;
- my $dbh = Bugzilla->dbh;
+ my $dbh = Bugzilla->dbh;
- my $sth;
+ my $sth;
- # add derived records for any matching regexps
+ # add derived records for any matching regexps
- $sth = $dbh->prepare("SELECT id, userregexp, user_group_map.group_id
+ $sth = $dbh->prepare(
+ "SELECT id, userregexp, user_group_map.group_id
FROM groups
LEFT JOIN user_group_map
ON groups.id = user_group_map.group_id
AND user_group_map.user_id = ?
- AND user_group_map.grant_type = ?");
- $sth->execute($id, GRANT_REGEXP);
+ AND user_group_map.grant_type = ?"
+ );
+ $sth->execute($id, GRANT_REGEXP);
- my $group_insert = $dbh->prepare(q{INSERT INTO user_group_map
+ my $group_insert = $dbh->prepare(
+ q{INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
- VALUES (?, ?, 0, ?)});
- my $group_delete = $dbh->prepare(q{DELETE FROM user_group_map
+ VALUES (?, ?, 0, ?)}
+ );
+ my $group_delete = $dbh->prepare(
+ q{DELETE FROM user_group_map
WHERE user_id = ?
AND group_id = ?
AND isbless = 0
- AND grant_type = ?});
- while (my ($group, $regexp, $present) = $sth->fetchrow_array()) {
- if (($regexp ne '') && ($self->login =~ m/$regexp/i)) {
- $group_insert->execute($id, $group, GRANT_REGEXP) unless $present;
- } else {
- $group_delete->execute($id, $group, GRANT_REGEXP) if $present;
- }
+ AND grant_type = ?}
+ );
+ while (my ($group, $regexp, $present) = $sth->fetchrow_array()) {
+ if (($regexp ne '') && ($self->login =~ m/$regexp/i)) {
+ $group_insert->execute($id, $group, GRANT_REGEXP) unless $present;
+ }
+ else {
+ $group_delete->execute($id, $group, GRANT_REGEXP) if $present;
}
+ }
- Bugzilla->memcached->clear_config({ key => "user_groups.$id" });
+ Bugzilla->memcached->clear_config({key => "user_groups.$id"});
}
sub product_responsibilities {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- return $self->{'product_resp'} if defined $self->{'product_resp'};
- return [] unless $self->id;
+ return $self->{'product_resp'} if defined $self->{'product_resp'};
+ return [] unless $self->id;
- my $list = $dbh->selectall_arrayref('SELECT components.product_id, components.id
+ my $list = $dbh->selectall_arrayref(
+ 'SELECT components.product_id, components.id
FROM components
LEFT JOIN component_cc
ON components.id = component_cc.component_id
WHERE components.initialowner = ?
OR components.initialqacontact = ?
OR component_cc.user_id = ?',
- {Slice => {}}, ($self->id, $self->id, $self->id));
-
- unless ($list) {
- $self->{'product_resp'} = [];
- return $self->{'product_resp'};
- }
+ {Slice => {}}, ($self->id, $self->id, $self->id)
+ );
- my @prod_ids = map {$_->{'product_id'}} @$list;
- my $products = Bugzilla::Product->new_from_list(\@prod_ids);
- # We cannot |use| it, because Component.pm already |use|s User.pm.
- require Bugzilla::Component;
- my @comp_ids = map {$_->{'id'}} @$list;
- my $components = Bugzilla::Component->new_from_list(\@comp_ids);
-
- my @prod_list;
- # @$products is already sorted alphabetically.
- foreach my $prod (@$products) {
- # We use @components instead of $prod->components because we only want
- # components where the user is either the default assignee or QA contact.
- push(@prod_list, {product => $prod,
- components => [grep {$_->product_id == $prod->id} @$components]});
- }
- $self->{'product_resp'} = \@prod_list;
+ unless ($list) {
+ $self->{'product_resp'} = [];
return $self->{'product_resp'};
+ }
+
+ my @prod_ids = map { $_->{'product_id'} } @$list;
+ my $products = Bugzilla::Product->new_from_list(\@prod_ids);
+
+ # We cannot |use| it, because Component.pm already |use|s User.pm.
+ require Bugzilla::Component;
+ my @comp_ids = map { $_->{'id'} } @$list;
+ my $components = Bugzilla::Component->new_from_list(\@comp_ids);
+
+ my @prod_list;
+
+ # @$products is already sorted alphabetically.
+ foreach my $prod (@$products) {
+
+ # We use @components instead of $prod->components because we only want
+ # components where the user is either the default assignee or QA contact.
+ push(
+ @prod_list,
+ {
+ product => $prod,
+ components => [grep { $_->product_id == $prod->id } @$components]
+ }
+ );
+ }
+ $self->{'product_resp'} = \@prod_list;
+ return $self->{'product_resp'};
}
sub can_bless {
- my $self = shift;
+ my $self = shift;
- if (!scalar(@_)) {
- # If we're called without an argument, just return
- # whether or not we can bless at all.
- return scalar(@{ $self->bless_groups }) ? 1 : 0;
- }
+ if (!scalar(@_)) {
- # Otherwise, we're checking a specific group
- my $group_id = shift;
- return grep($_->id == $group_id, @{ $self->bless_groups }) ? 1 : 0;
+ # If we're called without an argument, just return
+ # whether or not we can bless at all.
+ return scalar(@{$self->bless_groups}) ? 1 : 0;
+ }
+
+ # Otherwise, we're checking a specific group
+ my $group_id = shift;
+ return grep($_->id == $group_id, @{$self->bless_groups}) ? 1 : 0;
}
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.
- my ($str, $limit, $exclude_disabled) = @_;
- my $user = Bugzilla->user;
- my $dbh = Bugzilla->dbh;
- $str = trim($str);
+ # 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.
- my @users = ();
- return \@users if $str =~ /^\s*$/;
+ # $str contains the string to match, while $limit contains the
+ # maximum number of records to retrieve.
+ my ($str, $limit, $exclude_disabled) = @_;
+ my $user = Bugzilla->user;
+ my $dbh = Bugzilla->dbh;
- # The search order is wildcards, then exact match, then substring search.
- # Wildcard matching is skipped if there is no '*', and exact matches will
- # not (?) have a '*' in them. If any search comes up with something, the
- # ones following it will not execute.
+ $str = trim($str);
- # first try wildcards
- my $wildstr = $str;
+ my @users = ();
+ return \@users if $str =~ /^\s*$/;
- # Do not do wildcards if there is no '*' in the string.
- if ($wildstr =~ s/\*/\%/g && $user->id) {
- # Build the query.
- trick_taint($wildstr);
- my $query = "SELECT DISTINCT userid FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "INNER JOIN user_group_map
- ON user_group_map.user_id = profiles.userid ";
- }
- $query .= "WHERE ("
- . $dbh->sql_istrcmp('login_name', '?', "LIKE") . " OR " .
- $dbh->sql_istrcmp('realname', '?', "LIKE") . ") ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "AND isbless = 0 " .
- "AND group_id IN(" .
- join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
- }
- $query .= " AND is_enabled = 1 " if $exclude_disabled;
- $query .= $dbh->sql_limit($limit) if $limit;
+ # The search order is wildcards, then exact match, then substring search.
+ # Wildcard matching is skipped if there is no '*', and exact matches will
+ # not (?) have a '*' in them. If any search comes up with something, the
+ # ones following it will not execute.
- # Execute the query, retrieve the results, and make them into
- # User objects.
- my $user_ids = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
- @users = @{Bugzilla::User->new_from_list($user_ids)};
- }
- else { # try an exact match
- # Exact matches don't care if a user is disabled.
- trick_taint($str);
- my $user_id = $dbh->selectrow_array('SELECT userid FROM profiles
- WHERE ' . $dbh->sql_istrcmp('login_name', '?'),
- undef, $str);
-
- push(@users, new Bugzilla::User($user_id)) if $user_id;
+ # first try wildcards
+ my $wildstr = $str;
+
+ # Do not do wildcards if there is no '*' in the string.
+ if ($wildstr =~ s/\*/\%/g && $user->id) {
+
+ # Build the query.
+ trick_taint($wildstr);
+ my $query = "SELECT DISTINCT userid FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= "INNER JOIN user_group_map
+ ON user_group_map.user_id = profiles.userid ";
}
+ $query
+ .= "WHERE ("
+ . $dbh->sql_istrcmp('login_name', '?', "LIKE") . " OR "
+ . $dbh->sql_istrcmp('realname', '?', "LIKE") . ") ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= "AND isbless = 0 "
+ . "AND group_id IN("
+ . join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
+ }
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
+ $query .= $dbh->sql_limit($limit) if $limit;
+
+ # Execute the query, retrieve the results, and make them into
+ # User objects.
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($wildstr, $wildstr));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
+ }
+ else { # try an exact match
+ # Exact matches don't care if a user is disabled.
+ trick_taint($str);
+ my $user_id = $dbh->selectrow_array(
+ 'SELECT userid FROM profiles
+ WHERE '
+ . $dbh->sql_istrcmp('login_name', '?'), undef, $str
+ );
+
+ push(@users, new Bugzilla::User($user_id)) if $user_id;
+ }
- # then try substring search
- if (!scalar(@users) && length($str) >= 3 && $user->id) {
- trick_taint($str);
+ # then try substring search
+ if (!scalar(@users) && length($str) >= 3 && $user->id) {
+ trick_taint($str);
- my $query = "SELECT DISTINCT userid FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "INNER JOIN user_group_map
+ my $query = "SELECT DISTINCT userid FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= "INNER JOIN user_group_map
ON user_group_map.user_id = profiles.userid ";
- }
- $query .= " WHERE (" .
- $dbh->sql_iposition('?', 'login_name') . " > 0" . " OR " .
- $dbh->sql_iposition('?', 'realname') . " > 0) ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= " AND isbless = 0" .
- " AND group_id IN(" .
- join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
- }
- $query .= " AND is_enabled = 1 " if $exclude_disabled;
- $query .= $dbh->sql_limit($limit) if $limit;
- my $user_ids = $dbh->selectcol_arrayref($query, undef, ($str, $str));
- @users = @{Bugzilla::User->new_from_list($user_ids)};
}
- return \@users;
+ $query
+ .= " WHERE ("
+ . $dbh->sql_iposition('?', 'login_name') . " > 0" . " OR "
+ . $dbh->sql_iposition('?', 'realname')
+ . " > 0) ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= " AND isbless = 0"
+ . " AND group_id IN("
+ . join(', ', (-1, @{$user->visible_groups_inherited})) . ") ";
+ }
+ $query .= " AND is_enabled = 1 " if $exclude_disabled;
+ $query .= $dbh->sql_limit($limit) if $limit;
+ my $user_ids = $dbh->selectcol_arrayref($query, undef, ($str, $str));
+ @users = @{Bugzilla::User->new_from_list($user_ids)};
+ }
+ return \@users;
}
sub match_field {
- my $fields = shift; # arguments as a hash
- my $data = shift || Bugzilla->input_params; # hash to look up fields in
- my $behavior = shift || 0; # A constant that tells us how to act
- my $matches = {}; # the values sent to the template
- my $matchsuccess = 1; # did the match fail?
- my $need_confirm = 0; # whether to display confirmation screen
- my $match_multiple = 0; # whether we ever matched more than one user
- my @non_conclusive_fields; # fields which don't have a unique user.
-
- my $params = Bugzilla->params;
-
- # prepare default form values
-
- # Fields can be regular expressions matching multiple form fields
- # (f.e. "requestee-(\d+)"), so expand each non-literal field
- # into the list of form fields it matches.
- my $expanded_fields = {};
- foreach my $field_pattern (keys %{$fields}) {
- # Check if the field has any non-word characters. Only those fields
- # can be regular expressions, so don't expand the field if it doesn't
- # have any of those characters.
- if ($field_pattern =~ /^\w+$/) {
- $expanded_fields->{$field_pattern} = $fields->{$field_pattern};
- }
- else {
- my @field_names = grep(/$field_pattern/, keys %$data);
-
- foreach my $field_name (@field_names) {
- $expanded_fields->{$field_name} =
- { type => $fields->{$field_pattern}->{'type'} };
-
- # The field is a requestee field; in order for its name
- # to show up correctly on the confirmation page, we need
- # to find out the name of its flag type.
- if ($field_name =~ /^requestee(_type)?-(\d+)$/) {
- my $flag_type;
- if ($1) {
- require Bugzilla::FlagType;
- $flag_type = new Bugzilla::FlagType($2);
- }
- else {
- require Bugzilla::Flag;
- my $flag = new Bugzilla::Flag($2);
- $flag_type = $flag->type if $flag;
- }
- if ($flag_type) {
- $expanded_fields->{$field_name}->{'flag_type'} = $flag_type;
- }
- else {
- # No need to look for a valid requestee if the flag(type)
- # has been deleted (may occur in race conditions).
- delete $expanded_fields->{$field_name};
- delete $data->{$field_name};
- }
- }
- }
+ my $fields = shift; # arguments as a hash
+ my $data = shift || Bugzilla->input_params; # hash to look up fields in
+ my $behavior = shift || 0; # A constant that tells us how to act
+ my $matches = {}; # the values sent to the template
+ my $matchsuccess = 1; # did the match fail?
+ my $need_confirm = 0; # whether to display confirmation screen
+ my $match_multiple = 0; # whether we ever matched more than one user
+ my @non_conclusive_fields; # fields which don't have a unique user.
+
+ my $params = Bugzilla->params;
+
+ # prepare default form values
+
+ # Fields can be regular expressions matching multiple form fields
+ # (f.e. "requestee-(\d+)"), so expand each non-literal field
+ # into the list of form fields it matches.
+ my $expanded_fields = {};
+ foreach my $field_pattern (keys %{$fields}) {
+
+ # Check if the field has any non-word characters. Only those fields
+ # can be regular expressions, so don't expand the field if it doesn't
+ # have any of those characters.
+ if ($field_pattern =~ /^\w+$/) {
+ $expanded_fields->{$field_pattern} = $fields->{$field_pattern};
+ }
+ else {
+ my @field_names = grep(/$field_pattern/, keys %$data);
+
+ foreach my $field_name (@field_names) {
+ $expanded_fields->{$field_name} = {type => $fields->{$field_pattern}->{'type'}};
+
+ # The field is a requestee field; in order for its name
+ # to show up correctly on the confirmation page, we need
+ # to find out the name of its flag type.
+ if ($field_name =~ /^requestee(_type)?-(\d+)$/) {
+ my $flag_type;
+ if ($1) {
+ require Bugzilla::FlagType;
+ $flag_type = new Bugzilla::FlagType($2);
+ }
+ else {
+ require Bugzilla::Flag;
+ my $flag = new Bugzilla::Flag($2);
+ $flag_type = $flag->type if $flag;
+ }
+ if ($flag_type) {
+ $expanded_fields->{$field_name}->{'flag_type'} = $flag_type;
+ }
+ else {
+ # No need to look for a valid requestee if the flag(type)
+ # has been deleted (may occur in race conditions).
+ delete $expanded_fields->{$field_name};
+ delete $data->{$field_name};
+ }
}
+ }
}
- $fields = $expanded_fields;
+ }
+ $fields = $expanded_fields;
- foreach my $field (keys %{$fields}) {
- next unless defined $data->{$field};
+ foreach my $field (keys %{$fields}) {
+ next unless defined $data->{$field};
- #Concatenate login names, so that we have a common way to handle them.
- my $raw_field;
- if (ref $data->{$field}) {
- $raw_field = join(",", @{$data->{$field}});
- }
- else {
- $raw_field = $data->{$field};
- }
- $raw_field = clean_text($raw_field || '');
-
- # Now we either split $raw_field by spaces/commas and put the list
- # into @queries, or in the case of fields which only accept single
- # entries, we simply use the verbatim text.
- my @queries;
- if ($fields->{$field}->{'type'} eq 'single') {
- @queries = ($raw_field);
- # We will repopulate it later if a match is found, else it must
- # be set to an empty string so that the field remains defined.
- $data->{$field} = '';
- }
- elsif ($fields->{$field}->{'type'} eq 'multi') {
- @queries = split(/[,;]+/, $raw_field);
- # We will repopulate it later if a match is found, else it must
- # be undefined.
- delete $data->{$field};
- }
- else {
- # bad argument
- ThrowCodeError('bad_arg',
- { argument => $fields->{$field}->{'type'},
- function => 'Bugzilla::User::match_field',
- });
- }
+ #Concatenate login names, so that we have a common way to handle them.
+ my $raw_field;
+ if (ref $data->{$field}) {
+ $raw_field = join(",", @{$data->{$field}});
+ }
+ else {
+ $raw_field = $data->{$field};
+ }
+ $raw_field = clean_text($raw_field || '');
- # Tolerate fields that do not exist (in case you specify
- # e.g. the QA contact, and it's currently not in use).
- next unless (defined $raw_field && $raw_field ne '');
+ # Now we either split $raw_field by spaces/commas and put the list
+ # into @queries, or in the case of fields which only accept single
+ # entries, we simply use the verbatim text.
+ my @queries;
+ if ($fields->{$field}->{'type'} eq 'single') {
+ @queries = ($raw_field);
- my $limit = 0;
- if ($params->{'maxusermatches'}) {
- $limit = $params->{'maxusermatches'} + 1;
- }
+ # We will repopulate it later if a match is found, else it must
+ # be set to an empty string so that the field remains defined.
+ $data->{$field} = '';
+ }
+ elsif ($fields->{$field}->{'type'} eq 'multi') {
+ @queries = split(/[,;]+/, $raw_field);
- my @logins;
- for my $query (@queries) {
- $query = trim($query);
- next if $query eq '';
-
- my $users = match(
- $query, # match string
- $limit, # match limit
- 1 # exclude_disabled
- );
-
- # here is where it checks for multiple matches
- if (scalar(@{$users}) == 1) { # exactly one match
- push(@logins, @{$users}[0]->login);
-
- # skip confirmation for exact matches
- next if (lc(@{$users}[0]->login) eq lc($query));
-
- $matches->{$field}->{$query}->{'status'} = 'success';
- $need_confirm = 1 if $params->{'confirmuniqueusermatch'};
-
- }
- elsif ((scalar(@{$users}) > 1)
- && ($params->{'maxusermatches'} != 1)) {
- $need_confirm = 1;
- $match_multiple = 1;
- push(@non_conclusive_fields, $field);
-
- if (($params->{'maxusermatches'})
- && (scalar(@{$users}) > $params->{'maxusermatches'}))
- {
- $matches->{$field}->{$query}->{'status'} = 'trunc';
- pop @{$users}; # take the last one out
- }
- else {
- $matches->{$field}->{$query}->{'status'} = 'success';
- }
-
- }
- else {
- # everything else fails
- $matchsuccess = 0; # fail
- push(@non_conclusive_fields, $field);
- $matches->{$field}->{$query}->{'status'} = 'fail';
- $need_confirm = 1; # confirmation screen shows failures
- }
-
- $matches->{$field}->{$query}->{'users'} = $users;
+ # We will repopulate it later if a match is found, else it must
+ # be undefined.
+ delete $data->{$field};
+ }
+ else {
+ # bad argument
+ ThrowCodeError(
+ 'bad_arg',
+ {
+ argument => $fields->{$field}->{'type'},
+ function => 'Bugzilla::User::match_field',
}
+ );
+ }
+
+ # Tolerate fields that do not exist (in case you specify
+ # e.g. the QA contact, and it's currently not in use).
+ next unless (defined $raw_field && $raw_field ne '');
+
+ my $limit = 0;
+ if ($params->{'maxusermatches'}) {
+ $limit = $params->{'maxusermatches'} + 1;
+ }
+
+ my @logins;
+ for my $query (@queries) {
+ $query = trim($query);
+ next if $query eq '';
+
+ my $users = match(
+ $query, # match string
+ $limit, # match limit
+ 1 # exclude_disabled
+ );
- # If no match or more than one match has been found for a field
- # expecting only one match (type eq "single"), we set it back to ''
- # so that the caller of this function can still check whether this
- # field was defined or not (and it was if we came here).
- if ($fields->{$field}->{'type'} eq 'single') {
- $data->{$field} = $logins[0] || '';
+ # here is where it checks for multiple matches
+ if (scalar(@{$users}) == 1) { # exactly one match
+ push(@logins, @{$users}[0]->login);
+
+ # skip confirmation for exact matches
+ next if (lc(@{$users}[0]->login) eq lc($query));
+
+ $matches->{$field}->{$query}->{'status'} = 'success';
+ $need_confirm = 1 if $params->{'confirmuniqueusermatch'};
+
+ }
+ elsif ((scalar(@{$users}) > 1) && ($params->{'maxusermatches'} != 1)) {
+ $need_confirm = 1;
+ $match_multiple = 1;
+ push(@non_conclusive_fields, $field);
+
+ if ( ($params->{'maxusermatches'})
+ && (scalar(@{$users}) > $params->{'maxusermatches'}))
+ {
+ $matches->{$field}->{$query}->{'status'} = 'trunc';
+ pop @{$users}; # take the last one out
}
- elsif (scalar @logins) {
- $data->{$field} = \@logins;
+ else {
+ $matches->{$field}->{$query}->{'status'} = 'success';
}
+
+ }
+ else {
+ # everything else fails
+ $matchsuccess = 0; # fail
+ push(@non_conclusive_fields, $field);
+ $matches->{$field}->{$query}->{'status'} = 'fail';
+ $need_confirm = 1; # confirmation screen shows failures
+ }
+
+ $matches->{$field}->{$query}->{'users'} = $users;
}
- my $retval;
- if (!$matchsuccess) {
- $retval = USER_MATCH_FAILED;
+ # If no match or more than one match has been found for a field
+ # expecting only one match (type eq "single"), we set it back to ''
+ # so that the caller of this function can still check whether this
+ # field was defined or not (and it was if we came here).
+ if ($fields->{$field}->{'type'} eq 'single') {
+ $data->{$field} = $logins[0] || '';
}
- elsif ($match_multiple) {
- $retval = USER_MATCH_MULTIPLE;
- }
- else {
- $retval = USER_MATCH_SUCCESS;
+ elsif (scalar @logins) {
+ $data->{$field} = \@logins;
}
+ }
- # Skip confirmation if we were told to, or if we don't need to confirm.
- if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm) {
- return wantarray ? ($retval, \@non_conclusive_fields) : $retval;
- }
+ my $retval;
+ if (!$matchsuccess) {
+ $retval = USER_MATCH_FAILED;
+ }
+ elsif ($match_multiple) {
+ $retval = USER_MATCH_MULTIPLE;
+ }
+ else {
+ $retval = USER_MATCH_SUCCESS;
+ }
- my $template = Bugzilla->template;
- my $cgi = Bugzilla->cgi;
- my $vars = {};
+ # Skip confirmation if we were told to, or if we don't need to confirm.
+ if ($behavior == MATCH_SKIP_CONFIRM || !$need_confirm) {
+ return wantarray ? ($retval, \@non_conclusive_fields) : $retval;
+ }
- $vars->{'script'} = $cgi->url(-relative => 1); # for self-referencing URLs
- $vars->{'fields'} = $fields; # fields being matched
- $vars->{'matches'} = $matches; # matches that were made
- $vars->{'matchsuccess'} = $matchsuccess; # continue or fail
- $vars->{'matchmultiple'} = $match_multiple;
+ my $template = Bugzilla->template;
+ my $cgi = Bugzilla->cgi;
+ my $vars = {};
- print $cgi->header();
+ $vars->{'script'} = $cgi->url(-relative => 1); # for self-referencing URLs
+ $vars->{'fields'} = $fields; # fields being matched
+ $vars->{'matches'} = $matches; # matches that were made
+ $vars->{'matchsuccess'} = $matchsuccess; # continue or fail
+ $vars->{'matchmultiple'} = $match_multiple;
- $template->process("global/confirm-user-match.html.tmpl", $vars)
- || ThrowTemplateError($template->error());
- exit;
+ print $cgi->header();
+
+ $template->process("global/confirm-user-match.html.tmpl", $vars)
+ || ThrowTemplateError($template->error());
+ exit;
}
# Changes in some fields automatically trigger events. The field names are
# from the fielddefs table.
our %names_to_events = (
- 'resolution' => EVT_OPENED_CLOSED,
- 'keywords' => EVT_KEYWORD,
- 'cc' => EVT_CC,
- 'bug_severity' => EVT_PROJ_MANAGEMENT,
- 'priority' => EVT_PROJ_MANAGEMENT,
- 'bug_status' => EVT_PROJ_MANAGEMENT,
- 'target_milestone' => EVT_PROJ_MANAGEMENT,
- 'attachments.description' => EVT_ATTACHMENT_DATA,
- 'attachments.mimetype' => EVT_ATTACHMENT_DATA,
- 'attachments.ispatch' => EVT_ATTACHMENT_DATA,
- 'dependson' => EVT_DEPEND_BLOCK,
- 'blocked' => EVT_DEPEND_BLOCK,
- 'product' => EVT_COMPONENT,
- 'component' => EVT_COMPONENT);
+ 'resolution' => EVT_OPENED_CLOSED,
+ 'keywords' => EVT_KEYWORD,
+ 'cc' => EVT_CC,
+ 'bug_severity' => EVT_PROJ_MANAGEMENT,
+ 'priority' => EVT_PROJ_MANAGEMENT,
+ 'bug_status' => EVT_PROJ_MANAGEMENT,
+ 'target_milestone' => EVT_PROJ_MANAGEMENT,
+ 'attachments.description' => EVT_ATTACHMENT_DATA,
+ 'attachments.mimetype' => EVT_ATTACHMENT_DATA,
+ 'attachments.ispatch' => EVT_ATTACHMENT_DATA,
+ 'dependson' => EVT_DEPEND_BLOCK,
+ 'blocked' => EVT_DEPEND_BLOCK,
+ 'product' => EVT_COMPONENT,
+ 'component' => EVT_COMPONENT
+);
# Returns true if the user wants mail for a given bug change.
# Note: the "+" signs before the constants suppress bareword quoting.
sub wants_bug_mail {
- my $self = shift;
- my ($bug, $relationship, $fieldDiffs, $comments, $dep_mail, $changer) = @_;
-
- # is_silent_user is true if the username is mentioned in the param `silent_users`
- return 0 if $changer && $changer->is_silent_user;
-
- # Make a list of the events which have happened during this bug change,
- # from the point of view of this user.
- my %events;
- foreach my $change (@$fieldDiffs) {
- my $fieldName = $change->{field_name};
- # A change to any of the above fields sets the corresponding event
- if (defined($names_to_events{$fieldName})) {
- $events{$names_to_events{$fieldName}} = 1;
- }
- else {
- # Catch-all for any change not caught by a more specific event
- $events{+EVT_OTHER} = 1;
- }
+ my $self = shift;
+ my ($bug, $relationship, $fieldDiffs, $comments, $dep_mail, $changer) = @_;
- # If the user is in a particular role and the value of that role
- # changed, we need the ADDED_REMOVED event.
- if (($fieldName eq "assigned_to" && $relationship == REL_ASSIGNEE) ||
- ($fieldName eq "qa_contact" && $relationship == REL_QA))
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
+ # is_silent_user is true if the username is mentioned in the param `silent_users`
+ return 0 if $changer && $changer->is_silent_user;
- if ($fieldName eq "cc") {
- my $login = $self->login;
- my $inold = ($change->{old} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
- my $innew = ($change->{new} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
- if ($inold != $innew)
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
- }
- }
+ # Make a list of the events which have happened during this bug change,
+ # from the point of view of this user.
+ my %events;
+ foreach my $change (@$fieldDiffs) {
+ my $fieldName = $change->{field_name};
- if (!$bug->lastdiffed) {
- # Notify about new bugs.
- $events{+EVT_BUG_CREATED} = 1;
-
- # You role is new if the bug itself is.
- # Only makes sense for the assignee, QA contact and the CC list.
- if ($relationship == REL_ASSIGNEE
- || $relationship == REL_QA
- || $relationship == REL_CC)
- {
- $events{+EVT_ADDED_REMOVED} = 1;
- }
- }
-
- if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
- $events{+EVT_ATTACHMENT} = 1;
+ # A change to any of the above fields sets the corresponding event
+ if (defined($names_to_events{$fieldName})) {
+ $events{$names_to_events{$fieldName}} = 1;
}
- elsif (defined($$comments[0])) {
- $events{+EVT_COMMENT} = 1;
+ else {
+ # Catch-all for any change not caught by a more specific event
+ $events{+EVT_OTHER} = 1;
}
- # Dependent changed bugmails must have an event to ensure the bugmail is
- # emailed.
- if ($dep_mail) {
- $events{+EVT_DEPEND_BLOCK} = 1;
+ # If the user is in a particular role and the value of that role
+ # changed, we need the ADDED_REMOVED event.
+ if ( ($fieldName eq "assigned_to" && $relationship == REL_ASSIGNEE)
+ || ($fieldName eq "qa_contact" && $relationship == REL_QA))
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
}
- my @event_list = keys %events;
+ if ($fieldName eq "cc") {
+ my $login = $self->login;
+ my $inold = ($change->{old} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ my $innew = ($change->{new} =~ /^(.*,\s*)?\Q$login\E(,.*)?$/);
+ if ($inold != $innew) {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
+ }
+ }
- my $wants_mail = $self->wants_mail(\@event_list, $relationship);
+ if (!$bug->lastdiffed) {
- # The negative events are handled separately - they can't be incorporated
- # into the first wants_mail call, because they are of the opposite sense.
- #
- # We do them separately because if _any_ of them are set, we don't want
- # the mail.
- if ($wants_mail && $changer && ($self->id == $changer->id)) {
- $wants_mail &= $self->wants_mail([EVT_CHANGED_BY_ME], $relationship);
- }
+ # Notify about new bugs.
+ $events{+EVT_BUG_CREATED} = 1;
- if ($wants_mail && $bug->bug_status eq 'UNCONFIRMED') {
- $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
+ # You role is new if the bug itself is.
+ # Only makes sense for the assignee, QA contact and the CC list.
+ if ( $relationship == REL_ASSIGNEE
+ || $relationship == REL_QA
+ || $relationship == REL_CC)
+ {
+ $events{+EVT_ADDED_REMOVED} = 1;
+ }
+ }
+
+ if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) {
+ $events{+EVT_ATTACHMENT} = 1;
+ }
+ elsif (defined($$comments[0])) {
+ $events{+EVT_COMMENT} = 1;
+ }
+
+ # Dependent changed bugmails must have an event to ensure the bugmail is
+ # emailed.
+ if ($dep_mail) {
+ $events{+EVT_DEPEND_BLOCK} = 1;
+ }
+
+ my @event_list = keys %events;
+
+ my $wants_mail = $self->wants_mail(\@event_list, $relationship);
+
+ # The negative events are handled separately - they can't be incorporated
+ # into the first wants_mail call, because they are of the opposite sense.
+ #
+ # We do them separately because if _any_ of them are set, we don't want
+ # the mail.
+ if ($wants_mail && $changer && ($self->id == $changer->id)) {
+ $wants_mail &= $self->wants_mail([EVT_CHANGED_BY_ME], $relationship);
+ }
+
+ if ($wants_mail && $bug->bug_status eq 'UNCONFIRMED') {
+ $wants_mail &= $self->wants_mail([EVT_UNCONFIRMED], $relationship);
+ }
+
+ # BMO: add a hook to allow custom bugmail filtering
+ Bugzilla::Hook::process(
+ "user_wants_mail",
+ {
+ user => $self,
+ wants_mail => \$wants_mail,
+ bug => $bug,
+ relationship => $relationship,
+ fieldDiffs => $fieldDiffs,
+ comments => $comments,
+ dep_mail => $dep_mail,
+ changer => $changer,
}
+ );
- # BMO: add a hook to allow custom bugmail filtering
- Bugzilla::Hook::process("user_wants_mail", {
- user => $self,
- wants_mail => \$wants_mail,
- bug => $bug,
- relationship => $relationship,
- fieldDiffs => $fieldDiffs,
- comments => $comments,
- dep_mail => $dep_mail,
- changer => $changer,
- });
-
- return $wants_mail;
+ return $wants_mail;
}
# Returns true if the user wants mail for a given set of events.
sub wants_mail {
- my $self = shift;
- my ($events, $relationship) = @_;
+ my $self = shift;
+ my ($events, $relationship) = @_;
- # Don't send any mail, ever, if account is disabled
- # XXX Temporary Compatibility Change 1 of 2:
- # This code is disabled for the moment to make the behaviour like the old
- # system, which sent bugmail to disabled accounts.
- # return 0 if $self->{'disabledtext'};
+ # Don't send any mail, ever, if account is disabled
+ # XXX Temporary Compatibility Change 1 of 2:
+ # This code is disabled for the moment to make the behaviour like the old
+ # system, which sent bugmail to disabled accounts.
+ # return 0 if $self->{'disabledtext'};
- # No mail if there are no events
- return 0 if !scalar(@$events);
+ # No mail if there are no events
+ return 0 if !scalar(@$events);
- # If a relationship isn't given, default to REL_ANY.
- if (!defined($relationship)) {
- $relationship = REL_ANY;
- }
+ # If a relationship isn't given, default to REL_ANY.
+ if (!defined($relationship)) {
+ $relationship = REL_ANY;
+ }
- # Skip DB query if relationship is explicit
- return 1 if $relationship == REL_GLOBAL_WATCHER;
+ # Skip DB query if relationship is explicit
+ return 1 if $relationship == REL_GLOBAL_WATCHER;
- my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
- return $wants_mail ? 1 : 0;
+ my $wants_mail = grep { $self->mail_settings->{$relationship}{$_} } @$events;
+ return $wants_mail ? 1 : 0;
}
sub mail_settings {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
-
- if (!defined $self->{'mail_settings'}) {
- my $data =
- $dbh->selectall_arrayref('SELECT relationship, event FROM email_setting
- WHERE user_id = ?', undef, $self->id);
- my %mail;
- # The hash is of the form $mail{$relationship}{$event} = 1.
- $mail{$_->[0]}{$_->[1]} = 1 foreach @$data;
-
- $self->{'mail_settings'} = \%mail;
- }
- return $self->{'mail_settings'};
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+
+ if (!defined $self->{'mail_settings'}) {
+ my $data = $dbh->selectall_arrayref(
+ 'SELECT relationship, event FROM email_setting
+ WHERE user_id = ?', undef, $self->id
+ );
+ my %mail;
+
+ # The hash is of the form $mail{$relationship}{$event} = 1.
+ $mail{$_->[0]}{$_->[1]} = 1 foreach @$data;
+
+ $self->{'mail_settings'} = \%mail;
+ }
+ return $self->{'mail_settings'};
}
sub has_audit_entries {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
- if (!exists $self->{'has_audit_entries'}) {
- $self->{'has_audit_entries'} =
- $dbh->selectrow_array('SELECT 1 FROM audit_log WHERE user_id = ? ' .
- $dbh->sql_limit(1), undef, $self->id);
- }
- return $self->{'has_audit_entries'};
+ if (!exists $self->{'has_audit_entries'}) {
+ $self->{'has_audit_entries'}
+ = $dbh->selectrow_array(
+ 'SELECT 1 FROM audit_log WHERE user_id = ? ' . $dbh->sql_limit(1),
+ undef, $self->id);
+ }
+ return $self->{'has_audit_entries'};
}
sub is_insider {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'is_insider'}) {
- my $insider_group = Bugzilla->params->{'insidergroup'};
- $self->{'is_insider'} =
- ($insider_group && $self->in_group($insider_group)) ? 1 : 0;
- }
- return $self->{'is_insider'};
+ if (!defined $self->{'is_insider'}) {
+ my $insider_group = Bugzilla->params->{'insidergroup'};
+ $self->{'is_insider'}
+ = ($insider_group && $self->in_group($insider_group)) ? 1 : 0;
+ }
+ return $self->{'is_insider'};
}
sub is_global_watcher {
- my $self = shift;
+ my $self = shift;
- if (!exists $self->{'is_global_watcher'}) {
- my @watchers = split(/\s*,\s*/, Bugzilla->params->{'globalwatchers'});
- $self->{'is_global_watcher'} = (any { $_ eq $self->login } @watchers) ? 1 : 0;
- }
- return $self->{'is_global_watcher'};
+ if (!exists $self->{'is_global_watcher'}) {
+ my @watchers = split(/\s*,\s*/, Bugzilla->params->{'globalwatchers'});
+ $self->{'is_global_watcher'} = (any { $_ eq $self->login } @watchers) ? 1 : 0;
+ }
+ return $self->{'is_global_watcher'};
}
sub is_silent_user {
- my $self = shift;
+ my $self = shift;
- if (!exists $self->{'is_silent_user'}) {
- my @users = split(/\s*,\s*/, Bugzilla->params->{'silent_users'});
- $self->{'is_silent_user'} = (any { $self->login eq $_ } @users) ? 1 : 0;
- }
+ if (!exists $self->{'is_silent_user'}) {
+ my @users = split(/\s*,\s*/, Bugzilla->params->{'silent_users'});
+ $self->{'is_silent_user'} = (any { $self->login eq $_ } @users) ? 1 : 0;
+ }
- return $self->{'is_silent_user'};
+ return $self->{'is_silent_user'};
}
sub is_timetracker {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'is_timetracker'}) {
- my $tt_group = Bugzilla->params->{'timetrackinggroup'};
- $self->{'is_timetracker'} =
- ($tt_group && $self->in_group($tt_group)) ? 1 : 0;
- }
- return $self->{'is_timetracker'};
+ if (!defined $self->{'is_timetracker'}) {
+ my $tt_group = Bugzilla->params->{'timetrackinggroup'};
+ $self->{'is_timetracker'} = ($tt_group && $self->in_group($tt_group)) ? 1 : 0;
+ }
+ return $self->{'is_timetracker'};
}
sub can_tag_comments {
- my $self = shift;
+ my $self = shift;
- if (!defined $self->{'can_tag_comments'}) {
- my $group = Bugzilla->params->{'comment_taggers_group'};
- $self->{'can_tag_comments'} =
- ($group && $self->in_group($group)) ? 1 : 0;
- }
- return $self->{'can_tag_comments'};
+ if (!defined $self->{'can_tag_comments'}) {
+ my $group = Bugzilla->params->{'comment_taggers_group'};
+ $self->{'can_tag_comments'} = ($group && $self->in_group($group)) ? 1 : 0;
+ }
+ return $self->{'can_tag_comments'};
}
sub get_userlist {
- my $self = shift;
-
- return $self->{'userlist'} if defined $self->{'userlist'};
-
- my $dbh = Bugzilla->dbh;
- my $query = "SELECT DISTINCT login_name, realname,";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= " COUNT(group_id) ";
- } else {
- $query .= " 1 ";
- }
- $query .= "FROM profiles ";
- if (Bugzilla->params->{'usevisibilitygroups'}) {
- $query .= "LEFT JOIN user_group_map " .
- "ON user_group_map.user_id = userid AND isbless = 0 " .
- "AND group_id IN(" .
- join(', ', (-1, @{$self->visible_groups_inherited})) . ")";
- }
- $query .= " WHERE is_enabled = 1 ";
- $query .= $dbh->sql_group_by('userid', 'login_name, realname');
-
- my $sth = $dbh->prepare($query);
- $sth->execute;
-
- my @userlist;
- while (my($login, $name, $visible) = $sth->fetchrow_array) {
- push @userlist, {
- login => $login,
- identity => $name ? "$name <$login>" : $login,
- visible => $visible,
- };
- }
- @userlist = sort { lc $$a{'identity'} cmp lc $$b{'identity'} } @userlist;
-
- $self->{'userlist'} = \@userlist;
- return $self->{'userlist'};
+ my $self = shift;
+
+ return $self->{'userlist'} if defined $self->{'userlist'};
+
+ my $dbh = Bugzilla->dbh;
+ my $query = "SELECT DISTINCT login_name, realname,";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query .= " COUNT(group_id) ";
+ }
+ else {
+ $query .= " 1 ";
+ }
+ $query .= "FROM profiles ";
+ if (Bugzilla->params->{'usevisibilitygroups'}) {
+ $query
+ .= "LEFT JOIN user_group_map "
+ . "ON user_group_map.user_id = userid AND isbless = 0 "
+ . "AND group_id IN("
+ . join(', ', (-1, @{$self->visible_groups_inherited})) . ")";
+ }
+ $query .= " WHERE is_enabled = 1 ";
+ $query .= $dbh->sql_group_by('userid', 'login_name, realname');
+
+ my $sth = $dbh->prepare($query);
+ $sth->execute;
+
+ my @userlist;
+ while (my ($login, $name, $visible) = $sth->fetchrow_array) {
+ push @userlist,
+ {
+ login => $login,
+ identity => $name ? "$name <$login>" : $login,
+ visible => $visible,
+ };
+ }
+ @userlist = sort { lc $$a{'identity'} cmp lc $$b{'identity'} } @userlist;
+
+ $self->{'userlist'} = \@userlist;
+ return $self->{'userlist'};
}
sub create {
- my ($class, $params) = @_;
- my $dbh = Bugzilla->dbh;
-
- $dbh->bz_start_transaction();
- $params->{nickname} = _generate_nickname($params->{realname}, $params->{login_name}, 0);
- my $user = $class->SUPER::create($params);
-
- # Turn on all email for the new user
- require Bugzilla::BugMail;
- my %relationships = Bugzilla::BugMail::relationships();
- foreach my $rel (keys %relationships) {
- foreach my $event (POS_EVENTS, NEG_EVENTS) {
- # These "exceptions" define the default email preferences.
- #
- # We enable mail unless the change was made by the user, or it's
- # just a CC list addition and the user is not the reporter.
- next if ($event == EVT_CHANGED_BY_ME);
- next if (($event == EVT_CC) && ($rel != REL_REPORTER));
-
- $dbh->do('INSERT INTO email_setting (user_id, relationship, event)
- VALUES (?, ?, ?)', undef, ($user->id, $rel, $event));
- }
- }
-
- foreach my $event (GLOBAL_EVENTS) {
- $dbh->do('INSERT INTO email_setting (user_id, relationship, event)
- VALUES (?, ?, ?)', undef, ($user->id, REL_ANY, $event));
- }
+ my ($class, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ $dbh->bz_start_transaction();
+ $params->{nickname}
+ = _generate_nickname($params->{realname}, $params->{login_name}, 0);
+ my $user = $class->SUPER::create($params);
+
+ # Turn on all email for the new user
+ require Bugzilla::BugMail;
+ my %relationships = Bugzilla::BugMail::relationships();
+ foreach my $rel (keys %relationships) {
+ foreach my $event (POS_EVENTS, NEG_EVENTS) {
+
+ # These "exceptions" define the default email preferences.
+ #
+ # We enable mail unless the change was made by the user, or it's
+ # just a CC list addition and the user is not the reporter.
+ next if ($event == EVT_CHANGED_BY_ME);
+ next if (($event == EVT_CC) && ($rel != REL_REPORTER));
+
+ $dbh->do(
+ 'INSERT INTO email_setting (user_id, relationship, event)
+ VALUES (?, ?, ?)', undef, ($user->id, $rel, $event)
+ );
+ }
+ }
+
+ foreach my $event (GLOBAL_EVENTS) {
+ $dbh->do(
+ 'INSERT INTO email_setting (user_id, relationship, event)
+ VALUES (?, ?, ?)', undef, ($user->id, REL_ANY, $event)
+ );
+ }
- $user->derive_regexp_groups();
+ $user->derive_regexp_groups();
- # Add the creation date to the profiles_activity table.
- # $who is the user who created the new user account, i.e. either an
- # admin or the new user himself.
- my $who = Bugzilla->user->id || $user->id;
- my $creation_date_fieldid = get_field_id('creation_ts');
+ # Add the creation date to the profiles_activity table.
+ # $who is the user who created the new user account, i.e. either an
+ # admin or the new user himself.
+ my $who = Bugzilla->user->id || $user->id;
+ my $creation_date_fieldid = get_field_id('creation_ts');
- $dbh->do('INSERT INTO profiles_activity
+ $dbh->do(
+ 'INSERT INTO profiles_activity
(userid, who, profiles_when, fieldid, newvalue)
- VALUES (?, ?, NOW(), ?, NOW())',
- undef, ($user->id, $who, $creation_date_fieldid));
+ VALUES (?, ?, NOW(), ?, NOW())', undef,
+ ($user->id, $who, $creation_date_fieldid)
+ );
- $dbh->bz_commit_transaction();
+ $dbh->bz_commit_transaction();
- # Return the newly created user account.
- return $user;
+ # Return the newly created user account.
+ return $user;
}
sub check_required_create_fields {
- my ($invocant, $params) = @_;
- my $class = ref($invocant) || $invocant;
- # ensure disabled users also have their email disabled
- $params->{disable_mail} = 1 if
- exists $params->{disabledtext}
- && defined($params->{disabledtext})
- && trim($params->{disabledtext}) ne '';
- $class->SUPER::check_required_create_fields($params);
+ my ($invocant, $params) = @_;
+ my $class = ref($invocant) || $invocant;
+
+ # ensure disabled users also have their email disabled
+ $params->{disable_mail} = 1
+ if exists $params->{disabledtext}
+ && defined($params->{disabledtext})
+ && trim($params->{disabledtext}) ne '';
+ $class->SUPER::check_required_create_fields($params);
}
###########################
@@ -2596,44 +2709,45 @@ sub check_required_create_fields {
###########################
sub account_is_locked_out {
- my $self = shift;
- my $login_failures = scalar @{ $self->account_ip_login_failures };
- return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0;
+ my $self = shift;
+ my $login_failures = scalar @{$self->account_ip_login_failures};
+ return $login_failures >= MAX_LOGIN_ATTEMPTS ? 1 : 0;
}
sub note_login_failure {
- my $self = shift;
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- Bugzilla->dbh->do("INSERT INTO login_failure (user_id, ip_addr, login_time)
- VALUES (?, ?, LOCALTIMESTAMP(0))",
- undef, $self->id, $ip_addr);
- delete $self->{account_ip_login_failures};
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do(
+ "INSERT INTO login_failure (user_id, ip_addr, login_time)
+ VALUES (?, ?, LOCALTIMESTAMP(0))", undef, $self->id, $ip_addr
+ );
+ delete $self->{account_ip_login_failures};
}
sub clear_login_failures {
- my $self = shift;
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- Bugzilla->dbh->do(
- 'DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?',
- undef, $self->id, $ip_addr);
- delete $self->{account_ip_login_failures};
+ my $self = shift;
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ Bugzilla->dbh->do('DELETE FROM login_failure WHERE user_id = ? AND ip_addr = ?',
+ undef, $self->id, $ip_addr);
+ delete $self->{account_ip_login_failures};
}
sub account_ip_login_failures {
- my $self = shift;
- my $dbh = Bugzilla->dbh;
- my $time = $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-',
- LOGIN_LOCKOUT_INTERVAL, 'MINUTE');
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref(
- "SELECT login_time, ip_addr, user_id FROM login_failure
+ my $self = shift;
+ my $dbh = Bugzilla->dbh;
+ my $time = $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', LOGIN_LOCKOUT_INTERVAL,
+ 'MINUTE');
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ $self->{account_ip_login_failures} ||= Bugzilla->dbh->selectall_arrayref(
+ "SELECT login_time, ip_addr, user_id FROM login_failure
WHERE user_id = ? AND login_time > $time
AND ip_addr = ?
- ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr);
- return $self->{account_ip_login_failures};
+ ORDER BY login_time", {Slice => {}}, $self->id, $ip_addr
+ );
+ return $self->{account_ip_login_failures};
}
###############
@@ -2641,116 +2755,126 @@ sub account_ip_login_failures {
###############
sub is_available_username {
- my ($username, $old_username) = @_;
+ my ($username, $old_username) = @_;
- if(login_to_id($username) != 0) {
- return 0;
- }
-
- my $dbh = Bugzilla->dbh;
- # $username is safe because it is only used in SELECT placeholders.
- trick_taint($username);
- # Reject if the new login is part of an email change which is
- # still in progress
- #
- # substring/locate stuff: bug 165221; this used to use regexes, but that
- # was unsafe and required weird escaping; using substring to pull out
- # the new/old email addresses and sql_position() to find the delimiter (':')
- # is cleaner/safer
- my $eventdata = $dbh->selectrow_array(
- "SELECT eventdata
+ if (login_to_id($username) != 0) {
+ return 0;
+ }
+
+ my $dbh = Bugzilla->dbh;
+
+ # $username is safe because it is only used in SELECT placeholders.
+ trick_taint($username);
+
+ # Reject if the new login is part of an email change which is
+ # still in progress
+ #
+ # substring/locate stuff: bug 165221; this used to use regexes, but that
+ # was unsafe and required weird escaping; using substring to pull out
+ # the new/old email addresses and sql_position() to find the delimiter (':')
+ # is cleaner/safer
+ my $eventdata = $dbh->selectrow_array(
+ "SELECT eventdata
FROM tokens
WHERE (tokentype = 'emailold'
- AND SUBSTRING(eventdata, 1, (" .
- $dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?)
+ AND SUBSTRING(eventdata, 1, ("
+ . $dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?)
OR (tokentype = 'emailnew'
- AND SUBSTRING(eventdata, (" .
- $dbh->sql_position(q{':'}, 'eventdata') . "+ 1), LENGTH(eventdata)) = ?)",
- undef, ($username, $username));
-
- if ($eventdata) {
- # Allow thru owner of token
- if($old_username && ($eventdata eq "$old_username:$username")) {
- return 1;
- }
- return 0;
+ AND SUBSTRING(eventdata, ("
+ . $dbh->sql_position(q{':'}, 'eventdata')
+ . "+ 1), LENGTH(eventdata)) = ?)",
+ undef, ($username, $username)
+ );
+
+ if ($eventdata) {
+
+ # Allow thru owner of token
+ if ($old_username && ($eventdata eq "$old_username:$username")) {
+ return 1;
}
+ return 0;
+ }
- return 1;
+ return 1;
}
sub check_account_creation_enabled {
- my $self = shift;
+ my $self = shift;
- # If we're using e.g. LDAP for login, then we can't create a new account.
- $self->authorizer->user_can_create_account
- || ThrowUserError('auth_cant_create_account');
+ # If we're using e.g. LDAP for login, then we can't create a new account.
+ $self->authorizer->user_can_create_account
+ || ThrowUserError('auth_cant_create_account');
- Bugzilla->params->{'createemailregexp'}
- || ThrowUserError('account_creation_disabled');
+ Bugzilla->params->{'createemailregexp'}
+ || ThrowUserError('account_creation_disabled');
}
sub check_and_send_account_creation_confirmation {
- my ($self, $login) = @_;
+ my ($self, $login) = @_;
- $login = $self->check_login_name_for_creation($login);
- my $creation_regexp = Bugzilla->params->{'createemailregexp'};
+ $login = $self->check_login_name_for_creation($login);
+ my $creation_regexp = Bugzilla->params->{'createemailregexp'};
- if ($login !~ /$creation_regexp/i) {
- ThrowUserError('account_creation_restricted');
- }
+ if ($login !~ /$creation_regexp/i) {
+ ThrowUserError('account_creation_restricted');
+ }
- # BMO - add a hook to allow extra validation prior to account creation.
- Bugzilla::Hook::process("user_verify_login", { login => $login });
+ # BMO - add a hook to allow extra validation prior to account creation.
+ Bugzilla::Hook::process("user_verify_login", {login => $login});
- # Create and send a token for this new account.
- require Bugzilla::Token;
- Bugzilla::Token::issue_new_user_account_token($login);
+ # Create and send a token for this new account.
+ require Bugzilla::Token;
+ Bugzilla::Token::issue_new_user_account_token($login);
}
# This is used in a few performance-critical areas where we don't want to
# do check() and pull all the user data from the database.
sub login_to_id {
- my ($login, $throw_error) = @_;
- my $dbh = Bugzilla->dbh;
- my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
-
- # We cache lookups because this function showed up as taking up a
- # significant amount of time in profiles of xt/search.t. However,
- # for users that don't exist, we re-do the check every time, because
- # otherwise we break is_available_username.
- my $user_id;
- if (defined $cache->{$login}) {
- $user_id = $cache->{$login};
- }
- else {
- # No need to validate $login -- it will be used by the following SELECT
- # statement only, so it's safe to simply trick_taint.
- trick_taint($login);
- $user_id = $dbh->selectrow_array(
- "SELECT userid FROM profiles
- WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login);
- $cache->{$login} = $user_id;
- }
-
- if ($user_id) {
- return $user_id;
- } elsif ($throw_error) {
- ThrowUserError('invalid_username', { name => $login });
- } else {
- return 0;
- }
+ my ($login, $throw_error) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
+
+ # We cache lookups because this function showed up as taking up a
+ # significant amount of time in profiles of xt/search.t. However,
+ # for users that don't exist, we re-do the check every time, because
+ # otherwise we break is_available_username.
+ my $user_id;
+ if (defined $cache->{$login}) {
+ $user_id = $cache->{$login};
+ }
+ else {
+ # No need to validate $login -- it will be used by the following SELECT
+ # statement only, so it's safe to simply trick_taint.
+ trick_taint($login);
+ $user_id = $dbh->selectrow_array(
+ "SELECT userid FROM profiles
+ WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login
+ );
+ $cache->{$login} = $user_id;
+ }
+
+ if ($user_id) {
+ return $user_id;
+ }
+ elsif ($throw_error) {
+ ThrowUserError('invalid_username', {name => $login});
+ }
+ else {
+ return 0;
+ }
}
sub user_id_to_login {
- my $user_id = shift;
- my $dbh = Bugzilla->dbh;
+ my $user_id = shift;
+ my $dbh = Bugzilla->dbh;
- return '' unless ($user_id && detaint_natural($user_id));
+ return '' unless ($user_id && detaint_natural($user_id));
- my $login = $dbh->selectrow_array('SELECT login_name FROM profiles
- WHERE userid = ?', undef, $user_id);
- return $login || '';
+ my $login = $dbh->selectrow_array(
+ 'SELECT login_name FROM profiles
+ WHERE userid = ?', undef, $user_id
+ );
+ return $login || '';
}
1;