diff options
author | Jeff Fearn <jfearn@redhat.com> | 2020-05-07 11:41:39 +1000 |
---|---|---|
committer | Jeff Fearn <jfearn@redhat.com> | 2020-05-07 11:41:39 +1000 |
commit | 353af77714fd29aeab330255808636a9088eecc2 (patch) | |
tree | ecaadcb99519d2b53a96e7dfe9661b498156827a /Bugzilla/Product.pm | |
parent | Bumped version to 5.0.4 (diff) | |
download | bugzilla-353af77714fd29aeab330255808636a9088eecc2.tar.gz bugzilla-353af77714fd29aeab330255808636a9088eecc2.tar.bz2 bugzilla-353af77714fd29aeab330255808636a9088eecc2.zip |
Bug 478886 - Open Source Red Hat BugzillaRelease-5.0.4-rh44
Import Red Hat Bugzilla changes.
Diffstat (limited to 'Bugzilla/Product.pm')
-rw-r--r-- | Bugzilla/Product.pm | 1496 |
1 files changed, 913 insertions, 583 deletions
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index 0c0cb458d..682d3fd50 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -19,6 +19,7 @@ use Bugzilla::Error; use Bugzilla::Group; use Bugzilla::Version; use Bugzilla::Milestone; +use Bugzilla::Release; use Bugzilla::Field; use Bugzilla::Status; use Bugzilla::Install::Requirements; @@ -38,292 +39,388 @@ use constant IS_CONFIG => 1; use constant DB_TABLE => 'products'; +## REDHAT EXTENSION BEGIN 706784 739424 829154 +# Extension is the last six values only use constant DB_COLUMNS => qw( - id - name - classification_id - description - isactive - defaultmilestone - allows_unconfirmed + id + name + classification_id + description + isactive + defaultmilestone + allows_unconfirmed + defaultrelease + multiple_components + multiple_target_releases + multiple_versions + always_private + allows_on_dev + allows_no_clear_acks + rule_group + report_group ); +## REDHAT EXTENSION END 706784 739424 829154 +## REDHAT EXTENSION BEGIN 706784 739424 829154 +# Extension is the last six values only use constant UPDATE_COLUMNS => qw( - name - description - defaultmilestone - isactive - allows_unconfirmed + name + description + defaultmilestone + isactive + allows_unconfirmed + defaultrelease + multiple_components + multiple_target_releases + multiple_versions + always_private + allows_on_dev + allows_no_clear_acks + rule_group + report_group ); +## REDHAT EXTENSION END 706784 739424 829154 use constant VALIDATORS => { - allows_unconfirmed => \&Bugzilla::Object::check_boolean, - classification => \&_check_classification, - name => \&_check_name, - description => \&_check_description, - version => \&_check_version, - defaultmilestone => \&_check_default_milestone, - isactive => \&Bugzilla::Object::check_boolean, - create_series => \&Bugzilla::Object::check_boolean + allows_unconfirmed => \&Bugzilla::Object::check_boolean, + classification => \&_check_classification, + name => \&_check_name, + description => \&_check_description, + version => \&_check_version, + defaultmilestone => \&_check_default_milestone, + defaultrelease => \&_check_default_release, + isactive => \&Bugzilla::Object::check_boolean, + create_series => \&Bugzilla::Object::check_boolean }; +## REDHAT EXTENSION BEGIN 706784 +VALIDATORS->{multiple_components} = \&Bugzilla::Object::check_boolean; +VALIDATORS->{multiple_target_releases} = \&Bugzilla::Object::check_boolean; +VALIDATORS->{multiple_versions} = \&Bugzilla::Object::check_boolean; +## REDHAT EXTENSION END 706784 +## REDHAT EXTENSION BEGIN 829154 +VALIDATORS->{always_private} = \&Bugzilla::Object::check_boolean; +## REDHAT EXTENSION END 829154 + +VALIDATORS->{allows_on_dev} = \&Bugzilla::Object::check_boolean; +VALIDATORS->{allows_no_clear_acks} = \&Bugzilla::Object::check_boolean; + +VALIDATORS->{rule_group} = \&Bugzilla::_check_rule_group; +VALIDATORS->{report_group} = \&Bugzilla::_check_group; + ############################### #### Constructors ##### ############################### sub create { - my $class = shift; - my $dbh = Bugzilla->dbh; + my $class = shift; + my $dbh = Bugzilla->dbh; - $dbh->bz_start_transaction(); + $dbh->bz_start_transaction(); - $class->check_required_create_fields(@_); + $class->check_required_create_fields(@_); - my $params = $class->run_create_validators(@_); - # Some fields do not exist in the DB as is. - if (defined $params->{classification}) { - $params->{classification_id} = delete $params->{classification}; - } - my $version = delete $params->{version}; - my $create_series = delete $params->{create_series}; + my $params = $class->run_create_validators(@_); - my $product = $class->insert_create_data($params); - Bugzilla->user->clear_product_cache(); + # Some fields do not exist in the DB as is. + if (defined $params->{classification}) { + $params->{classification_id} = delete $params->{classification}; + } + my $version = delete $params->{version}; + my $create_series = delete $params->{create_series}; - # Add the new version and milestone into the DB as valid values. - Bugzilla::Version->create({ value => $version, product => $product }); - Bugzilla::Milestone->create({ value => $product->default_milestone, - product => $product }); + my $product = $class->insert_create_data($params); + Bugzilla->user->clear_product_cache(); - # Create groups and series for the new product, if requested. - $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'}; - $product->_create_series() if $create_series; + # Add the new version and milestone into the DB as valid values. + Bugzilla::Version->create({value => $version, product => $product}); + Bugzilla::Milestone->create( + {value => $product->default_milestone, product => $product}); + Bugzilla::Release->create( + {value => $product->default_release, product => $product}); - Bugzilla::Hook::process('product_end_of_create', { product => $product }); + # Create groups and series for the new product, if requested. + $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'}; + $product->_create_series() if $create_series; - $dbh->bz_commit_transaction(); - Bugzilla->memcached->clear_config(); - return $product; + Bugzilla::Hook::process('product_end_of_create', {product => $product}); + + $dbh->bz_commit_transaction(); + Bugzilla->memcached->clear_config(); + return $product; } # This is considerably faster than calling new_from_list three times # for each product in the list, particularly with hundreds or thousands # of products. sub preload { - my ($products, $preload_flagtypes) = @_; - my %prods = map { $_->id => $_ } @$products; - my @prod_ids = keys %prods; - return unless @prod_ids; - - # We cannot |use| it due to a dependency loop with Bugzilla::User. - require Bugzilla::Component; - foreach my $field (qw(component version milestone)) { - my $classname = "Bugzilla::" . ucfirst($field); - my $objects = $classname->match({ product_id => \@prod_ids }); - - # Now populate the products with this set of objects. - foreach my $obj (@$objects) { - my $product_id = $obj->product_id; - $prods{$product_id}->{"${field}s"} ||= []; - push(@{$prods{$product_id}->{"${field}s"}}, $obj); - } - } - if ($preload_flagtypes) { - $_->flag_types foreach @$products; + my ($products, $preload_flagtypes) = @_; + my %prods = map { $_->id => $_ } @$products; + my @prod_ids = keys %prods; + return unless @prod_ids; + + # We cannot |use| it due to a dependency loop with Bugzilla::User. + require Bugzilla::Component; + foreach my $field (qw(component version milestone release)) { + my $classname = "Bugzilla::" . ucfirst($field); + my $objects = $classname->match({product_id => \@prod_ids}); + + # Now populate the products with this set of objects. + foreach my $obj (@$objects) { + my $product_id = $obj->product_id; + $prods{$product_id}->{"${field}s"} ||= []; + push(@{$prods{$product_id}->{"${field}s"}}, $obj); } + } + if ($preload_flagtypes) { + $_->flag_types foreach @$products; + } } sub update { - my $self = shift; - my $dbh = Bugzilla->dbh; - - # Don't update the DB if something goes wrong below -> transaction. - $dbh->bz_start_transaction(); - my ($changes, $old_self) = $self->SUPER::update(@_); - - # Also update group settings. - if ($self->{check_group_controls}) { - require Bugzilla::Bug; - import Bugzilla::Bug qw(LogActivityEntry); - - my $old_settings = $old_self->group_controls; - my $new_settings = $self->group_controls; - my $timestamp = $dbh->selectrow_array('SELECT NOW()'); - - foreach my $gid (keys %$new_settings) { - my $old_setting = $old_settings->{$gid} || {}; - my $new_setting = $new_settings->{$gid}; - # If all new settings are 0 for a given group, we delete the entry - # from group_control_map, so we have to track it here. - my $all_zero = 1; - my @fields; - my @values; - - foreach my $field ('entry', 'membercontrol', 'othercontrol', 'canedit', - 'editcomponents', 'editbugs', 'canconfirm') - { - my $old_value = $old_setting->{$field}; - my $new_value = $new_setting->{$field}; - $all_zero = 0 if $new_value; - next if (defined $old_value && $old_value == $new_value); - push(@fields, $field); - # The value has already been validated. - detaint_natural($new_value); - push(@values, $new_value); - } - # Is there anything to update? - next unless scalar @fields; - - if ($all_zero) { - $dbh->do('DELETE FROM group_control_map - WHERE product_id = ? AND group_id = ?', - undef, $self->id, $gid); - } - else { - if (exists $old_setting->{group}) { - # There is already an entry in the DB. - my $set_fields = join(', ', map {"$_ = ?"} @fields); - $dbh->do("UPDATE group_control_map SET $set_fields - WHERE product_id = ? AND group_id = ?", - undef, (@values, $self->id, $gid)); - } - else { - # No entry yet. - my $fields = join(', ', @fields); - # +2 because of the product and group IDs. - my $qmarks = join(',', ('?') x (scalar @fields + 2)); - $dbh->do("INSERT INTO group_control_map (product_id, group_id, $fields) - VALUES ($qmarks)", undef, ($self->id, $gid, @values)); - } - } - - # If the group is mandatory, restrict all bugs to it. - if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY) { - my $bug_ids = - $dbh->selectcol_arrayref('SELECT bugs.bug_id + my $self = shift; + my $dbh = Bugzilla->dbh; + + my @colnames = qw(NA Shown Default Mandatory); ## REDHAT EXTENSION 1214523 - Record ACL changes + + ## REDHAT EXTENSION START 829154 + # Setting a bug as always private with no group control means you cannot edit it + my $group_controls = $self->group_controls; + my @member_control + = grep { $group_controls->{$_}{membercontrol} } keys %$group_controls; + if ($self->always_private and scalar(@member_control) == 0) { + ThrowUserError('product_cannot_be_private'); + } + ## REDHAT EXTENSION END 829154 + + # Don't update the DB if something goes wrong below -> transaction. + $dbh->bz_start_transaction(); + my ($changes, $old_self) = $self->SUPER::update(@_); + + # Also update group settings. + if ($self->{check_group_controls}) { + require Bugzilla::Bug; + import Bugzilla::Bug qw(LogActivityEntry); + + my $old_settings = $old_self->group_controls; + my $new_settings = $self->group_controls; + my $timestamp = $dbh->selectrow_array('SELECT NOW()'); + + foreach my $gid (keys %$new_settings) { + my $old_setting = $old_settings->{$gid} || {}; + my $new_setting = $new_settings->{$gid}; + + # If all new settings are 0 for a given group, we delete the entry + # from group_control_map, so we have to track it here. + my $all_zero = 1; + my @fields; + my @values; + + my %mapchanges; ## REDHAT EXTENSION 1214523 - Record ACL changes + + foreach my $field ( + 'entry', 'membercontrol', 'othercontrol', 'canedit', + 'editcomponents', 'editbugs', 'canconfirm' + ) + { + my $old_value = $old_setting->{$field} // 0; + my $new_value = $new_setting->{$field} // 0; + $all_zero = 0 if $new_value; + next + if ((defined $old_value && $old_value == $new_value) + || (!$old_value && !$new_value)); + push(@fields, $field); + + # The value has already been validated. + detaint_natural($new_value); + push(@values, $new_value); + ## REDHAT EXTENSION 1214523 START - Record ACL changes + if ($field eq 'membercontrol' || $field eq 'othercontrol') { + $changes->{'group_controls'}->{$new_setting->{group}->name}->{$field} + = [$colnames[$old_value], $colnames[$new_value]]; + } + else { + $old_value //= ''; + $changes->{'group_controls'}->{$new_setting->{group}->name}->{$field} + = [$old_value, $new_value]; + } + ## REDHAT EXTENSION 1214523 END + } + + # Is there anything to update? + next unless scalar @fields; + + $self->audit_log(\%mapchanges); ## REDHAT EXTENSION 1214523 + + if ($all_zero) { + $dbh->do( + 'DELETE FROM group_control_map + WHERE product_id = ? AND group_id = ?', undef, $self->id, $gid + ); + } + else { + if (exists $old_setting->{group}) { + + # There is already an entry in the DB. + my $set_fields = join(', ', map {"$_ = ?"} @fields); + $dbh->do( + "UPDATE group_control_map SET $set_fields + WHERE product_id = ? AND group_id = ?", undef, + (@values, $self->id, $gid) + ); + } + else { + # No entry yet. + my $fields = join(', ', @fields); + + # +2 because of the product and group IDs. + my $qmarks = join(',', ('?') x (scalar @fields + 2)); + $dbh->do( + "INSERT INTO group_control_map (product_id, group_id, $fields) + VALUES ($qmarks)", undef, ($self->id, $gid, @values) + ); + } + } + + # If the group is mandatory, restrict all bugs to it. + if ($new_setting->{membercontrol} == CONTROLMAPMANDATORY) { + my $bug_ids = $dbh->selectcol_arrayref( + 'SELECT bugs.bug_id FROM bugs LEFT JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id AND group_id = ? WHERE product_id = ? AND bug_group_map.bug_id IS NULL', - undef, $gid, $self->id); - - if (scalar @$bug_ids) { - my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) - VALUES (?, ?)'); - - foreach my $bug_id (@$bug_ids) { - $sth->execute($bug_id, $gid); - # Add this change to the bug history. - LogActivityEntry($bug_id, 'bug_group', '', - $new_setting->{group}->name, - Bugzilla->user->id, $timestamp); - } - push(@{$changes->{'_group_controls'}->{'now_mandatory'}}, - {name => $new_setting->{group}->name, - bug_count => scalar @$bug_ids}); - } - } - # If the group can no longer be used to restrict bugs, remove them. - elsif ($new_setting->{membercontrol} == CONTROLMAPNA) { - my $bug_ids = - $dbh->selectcol_arrayref('SELECT bugs.bug_id + undef, $gid, $self->id + ); + + if (scalar @$bug_ids) { + my $sth = $dbh->prepare( + 'INSERT INTO bug_group_map (bug_id, group_id) + VALUES (?, ?)' + ); + + foreach my $bug_id (@$bug_ids) { + $sth->execute($bug_id, $gid); + + # Add this change to the bug history. + LogActivityEntry($bug_id, 'bug_group', '', $new_setting->{group}->name, + Bugzilla->user->id, $timestamp); + } + push( + @{$changes->{'_group_controls'}->{'now_mandatory'}}, + {name => $new_setting->{group}->name, bug_count => scalar @$bug_ids} + ); + } + } + + # If the group can no longer be used to restrict bugs, remove them. + elsif ($new_setting->{membercontrol} == CONTROLMAPNA) { + my $bug_ids = $dbh->selectcol_arrayref( + 'SELECT bugs.bug_id FROM bugs INNER JOIN bug_group_map ON bug_group_map.bug_id = bugs.bug_id WHERE product_id = ? AND group_id = ?', - undef, $self->id, $gid); - - if (scalar @$bug_ids) { - $dbh->do('DELETE FROM bug_group_map WHERE group_id = ? AND ' . - $dbh->sql_in('bug_id', $bug_ids), undef, $gid); - - # Add this change to the bug history. - foreach my $bug_id (@$bug_ids) { - LogActivityEntry($bug_id, 'bug_group', - $old_setting->{group}->name, '', - Bugzilla->user->id, $timestamp); - } - push(@{$changes->{'_group_controls'}->{'now_na'}}, - {name => $old_setting->{group}->name, - bug_count => scalar @$bug_ids}); - } - } + undef, $self->id, $gid + ); + + if (scalar @$bug_ids) { + $dbh->do( + 'DELETE FROM bug_group_map WHERE group_id = ? AND ' + . $dbh->sql_in('bug_id', $bug_ids), + undef, $gid + ); + + # Add this change to the bug history. + foreach my $bug_id (@$bug_ids) { + LogActivityEntry($bug_id, 'bug_group', $old_setting->{group}->name, + '', Bugzilla->user->id, $timestamp); + } + push( + @{$changes->{'_group_controls'}->{'now_na'}}, + {name => $old_setting->{group}->name, bug_count => scalar @$bug_ids} + ); } - - delete $self->{groups_available}; - delete $self->{groups_mandatory}; + } } - $dbh->bz_commit_transaction(); - # Changes have been committed. - delete $self->{check_group_controls}; - Bugzilla->user->clear_product_cache(); - Bugzilla->memcached->clear_config(); - return $changes; + delete $self->{groups_available}; + delete $self->{groups_mandatory}; + } + $dbh->bz_commit_transaction(); + + # Changes have been committed. + delete $self->{check_group_controls}; + Bugzilla->user->clear_product_cache(); + Bugzilla->memcached->clear_config(); + + return $changes; } sub remove_from_db { - my ($self, $params) = @_; - my $user = Bugzilla->user; - my $dbh = Bugzilla->dbh; - - $dbh->bz_start_transaction(); - - $self->_check_if_controller(); - - if ($self->bug_count) { - if (Bugzilla->params->{'allowbugdeletion'}) { - require Bugzilla::Bug; - foreach my $bug_id (@{$self->bug_ids}) { - # Note that we allow the user to delete bugs they can't see, - # which is okay, because they're deleting the whole Product. - my $bug = new Bugzilla::Bug($bug_id); - $bug->remove_from_db(); - } - } - else { - ThrowUserError('product_has_bugs', { nb => $self->bug_count }); - } + my ($self, $params) = @_; + my $user = Bugzilla->user; + my $dbh = Bugzilla->dbh; + + $dbh->bz_start_transaction(); + + $self->_check_if_controller(); + + if ($self->bug_count) { + if (Bugzilla->params->{'allowbugdeletion'}) { + require Bugzilla::Bug; + foreach my $bug_id (@{$self->bug_ids}) { + + # Note that we allow the user to delete bugs they can't see, + # which is okay, because they're deleting the whole Product. + my $bug = new Bugzilla::Bug($bug_id); + $bug->remove_from_db(); + } } + else { + ThrowUserError('product_has_bugs', {nb => $self->bug_count}); + } + } - if ($params->{delete_series}) { - my $series_ids = - $dbh->selectcol_arrayref('SELECT series_id + if ($params->{delete_series}) { + my $series_ids = $dbh->selectcol_arrayref( + 'SELECT series_id FROM series INNER JOIN series_categories ON series_categories.id = series.category - WHERE series_categories.name = ?', - undef, $self->name); + WHERE series_categories.name = ?', undef, + $self->name + ); - if (scalar @$series_ids) { - $dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids)); - } + if (scalar @$series_ids) { + $dbh->do('DELETE FROM series WHERE ' . $dbh->sql_in('series_id', $series_ids)); + } - # If no subcategory uses this product name, completely purge it. - my $in_use = - $dbh->selectrow_array('SELECT 1 + # If no subcategory uses this product name, completely purge it. + my $in_use = $dbh->selectrow_array( + 'SELECT 1 FROM series INNER JOIN series_categories ON series_categories.id = series.subcategory - WHERE series_categories.name = ? ' . - $dbh->sql_limit(1), - undef, $self->name); - if (!$in_use) { - $dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name); - } + WHERE series_categories.name = ? ' + . $dbh->sql_limit(1), undef, $self->name + ); + if (!$in_use) { + $dbh->do('DELETE FROM series_categories WHERE name = ?', undef, $self->name); } + } - $self->SUPER::remove_from_db(); + $self->SUPER::remove_from_db(); - $dbh->bz_commit_transaction(); - Bugzilla->memcached->clear_config(); + $dbh->bz_commit_transaction(); + Bugzilla->memcached->clear_config(); - # We have to delete these internal variables, else we get - # the old lists of products and classifications again. - delete $user->{selectable_products}; - delete $user->{selectable_classifications}; + # We have to delete these internal variables, else we get + # the old lists of products and classifications again. + delete $user->{selectable_products}; + delete $user->{selectable_classifications}; } @@ -332,91 +429,135 @@ sub remove_from_db { ############################### sub _check_classification { - my ($invocant, $classification_name) = @_; - - my $classification_id = 1; - if (Bugzilla->params->{'useclassification'}) { - my $classification = Bugzilla::Classification->check($classification_name); - $classification_id = $classification->id; - } - return $classification_id; + my ($invocant, $classification_name) = @_; + + my $classification_id = 1; + if (Bugzilla->params->{'useclassification'}) { + my $classification = Bugzilla::Classification->check($classification_name); + $classification_id = $classification->id; + } + return $classification_id; } sub _check_name { - my ($invocant, $name) = @_; + my ($invocant, $name) = @_; - $name = trim($name); - $name || ThrowUserError('product_blank_name'); + $name = trim($name); + $name || ThrowUserError('product_blank_name'); - if (length($name) > MAX_PRODUCT_SIZE) { - ThrowUserError('product_name_too_long', {'name' => $name}); - } + if (length($name) > MAX_PRODUCT_SIZE) { + ThrowUserError('product_name_too_long', {'name' => $name}); + } - my $product = new Bugzilla::Product({name => $name}); - if ($product && (!ref $invocant || $product->id != $invocant->id)) { - # Check for exact case sensitive match: - if ($product->name eq $name) { - ThrowUserError('product_name_already_in_use', {'product' => $product->name}); - } - else { - ThrowUserError('product_name_diff_in_case', {'product' => $name, - 'existing_product' => $product->name}); - } + my $product = new Bugzilla::Product({name => $name}); + if ($product && (!ref $invocant || $product->id != $invocant->id)) { + + # Check for exact case sensitive match: + if ($product->name eq $name) { + ThrowUserError('product_name_already_in_use', {'product' => $product->name}); } - return $name; + else { + ThrowUserError('product_name_diff_in_case', + {'product' => $name, 'existing_product' => $product->name}); + } + } + return $name; } sub _check_description { - my ($invocant, $description) = @_; + my ($invocant, $description) = @_; - $description = trim($description); - $description || ThrowUserError('product_must_have_description'); - return $description; + $description = trim($description); + $description || ThrowUserError('product_must_have_description'); + return $description; } sub _check_version { - my ($invocant, $version) = @_; + my ($invocant, $version) = @_; - $version = trim($version); - $version || ThrowUserError('product_must_have_version'); - # We will check the version length when Bugzilla::Version->create will do it. - return $version; + $version = trim($version); + $version || ThrowUserError('product_must_have_version'); + + # We will check the version length when Bugzilla::Version->create will do it. + return $version; } sub _check_default_milestone { - my ($invocant, $milestone) = @_; + my ($invocant, $milestone) = @_; - # Do nothing if target milestones are not in use. - unless (Bugzilla->params->{'usetargetmilestone'}) { - return (ref $invocant) ? $invocant->default_milestone : '---'; - } + # Do nothing if target milestones are not in use. + unless (Bugzilla->params->{'usetargetmilestone'}) { + return (ref $invocant) ? $invocant->default_milestone : '---'; + } - $milestone = trim($milestone); + $milestone = trim($milestone); - if (ref $invocant) { - # The default milestone must be one of the existing milestones. - my $mil_obj = new Bugzilla::Milestone({name => $milestone, product => $invocant}); + if (ref $invocant) { - $mil_obj || ThrowUserError('product_must_define_defaultmilestone', - {product => $invocant->name, - milestone => $milestone}); - } - else { - $milestone ||= '---'; - } - return $milestone; + # The default milestone must be one of the existing milestones. + my $mil_obj + = new Bugzilla::Milestone({name => $milestone, product => $invocant}); + + $mil_obj || ThrowUserError('product_must_define_defaultmilestone', + {product => $invocant->name, milestone => $milestone}); + } + else { + $milestone ||= '---'; + } + return $milestone; } sub _check_milestone_url { - my ($invocant, $url) = @_; + my ($invocant, $url) = @_; - # Do nothing if target milestones are not in use. - unless (Bugzilla->params->{'usetargetmilestone'}) { - return (ref $invocant) ? $invocant->milestone_url : ''; - } + # Do nothing if target milestones are not in use. + unless (Bugzilla->params->{'usetargetmilestone'}) { + return (ref $invocant) ? $invocant->milestone_url : ''; + } + + $url = trim($url || ''); + return $url; +} + +sub _check_default_release { + my ($invocant, $release) = @_; + + # Do nothing if target releases are not in use. + unless (Bugzilla->params->{'usetargetrelease'}) { + return (ref $invocant) ? $invocant->default_release : '---'; + } + + $release = trim($release); + + if (ref $invocant) { + + # The default release must be one of the existing active releases. + my $rel_obj = new Bugzilla::Release({name => $release, product => $invocant}); - $url = trim($url || ''); - return $url; + $rel_obj || ThrowUserError('product_must_define_defaultrelease', + {product => $invocant->name, release => $release}); + + $rel_obj->is_active || ThrowUserError( + 'product_must_define_active_defaultrelease', + {product => $invocant->name, release => $release} + ); + } + else { + $release ||= '---'; + } + return $release; +} + +sub _check_release_url { + my ($invocant, $url) = @_; + + # Do nothing if target releases are not in use. + unless (Bugzilla->params->{'usetargetrelease'}) { + return (ref $invocant) ? $invocant->release_url : ''; + } + + $url = trim($url || ''); + return $url; } ##################################### @@ -431,393 +572,466 @@ use constant is_default => 0; ############################### sub _create_bug_group { - my $self = shift; - my $dbh = Bugzilla->dbh; - - my $group_name = $self->name; - while (new Bugzilla::Group({name => $group_name})) { - $group_name .= '_'; - } - my $group_description = get_text('bug_group_description', {product => $self}); - - my $group = Bugzilla::Group->create({name => $group_name, - description => $group_description, - isbuggroup => 1}); - - # Associate the new group and new product. - $dbh->do('INSERT INTO group_control_map + my $self = shift; + my $dbh = Bugzilla->dbh; + + my $group_name = $self->name; + while (new Bugzilla::Group({name => $group_name})) { + $group_name .= '_'; + } + my $group_description = get_text('bug_group_description', {product => $self}); + + my $group + = Bugzilla::Group->create({ + name => $group_name, description => $group_description, isbuggroup => 1 + }); + + # Associate the new group and new product. + $dbh->do( + 'INSERT INTO group_control_map (group_id, product_id, membercontrol, othercontrol) - VALUES (?, ?, ?, ?)', - undef, ($group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA)); + VALUES (?, ?, ?, ?)', undef, + ($group->id, $self->id, CONTROLMAPDEFAULT, CONTROLMAPNA) + ); } sub _create_series { - my $self = shift; - - my @series; - # We do every status, every resolution, and an "opened" one as well. - foreach my $bug_status (@{get_legal_field_values('bug_status')}) { - push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]); - } - - foreach my $resolution (@{get_legal_field_values('resolution')}) { - next if !$resolution; - push(@series, [$resolution, "resolution=" . url_quote($resolution)]); - } - - my @openedstatuses = BUG_STATE_OPEN; - my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses); - push(@series, [get_text('series_all_open'), $query]); - - foreach my $sdata (@series) { - my $series = new Bugzilla::Series(undef, $self->name, - get_text('series_subcategory'), - $sdata->[0], Bugzilla->user->id, 1, - $sdata->[1] . "&product=" . url_quote($self->name), 1); - $series->writeToDatabase(); - } + my $self = shift; + + my @series; + + # We do every status, every resolution, and an "opened" one as well. + foreach my $bug_status (@{get_legal_field_values('bug_status')}) { + push(@series, [$bug_status, "bug_status=" . url_quote($bug_status)]); + } + + foreach my $resolution (@{get_legal_field_values('resolution')}) { + next if !$resolution; + push(@series, [$resolution, "resolution=" . url_quote($resolution)]); + } + + my @openedstatuses = BUG_STATE_OPEN; + my $query = join("&", map { "bug_status=" . url_quote($_) } @openedstatuses); + push(@series, [get_text('series_all_open'), $query]); + + foreach my $sdata (@series) { + my $series + = new Bugzilla::Series(undef, $self->name, get_text('series_subcategory'), + $sdata->[0], Bugzilla->user->id, 1, + $sdata->[1] . "&product=" . url_quote($self->name), 1); + $series->writeToDatabase(); + } } -sub set_name { $_[0]->set('name', $_[1]); } -sub set_description { $_[0]->set('description', $_[1]); } -sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); } -sub set_is_active { $_[0]->set('isactive', $_[1]); } +sub set_name { $_[0]->set('name', $_[1]); } +sub set_description { $_[0]->set('description', $_[1]); } +sub set_default_milestone { $_[0]->set('defaultmilestone', $_[1]); } +sub set_is_active { $_[0]->set('isactive', $_[1]); } sub set_allows_unconfirmed { $_[0]->set('allows_unconfirmed', $_[1]); } +sub set_default_release { $_[0]->set('defaultrelease', $_[1]); } +## REDHAT EXTENSION BEGIN 706784, 1380351 Disable MultipleValues extension +sub set_multiple_components { return; } +sub set_multiple_target_releases { return; } +sub set_multiple_versions { return; } +## REDHAT EXTENSION END 706784 +## REDHAT EXTENSION BEGIN 829154 +sub set_always_private { $_[0]->set('always_private', $_[1]); } +## REDHAT EXTENSION END 829154 sub set_group_controls { - my ($self, $group, $settings) = @_; - - $group->is_active_bug_group - || ThrowUserError('product_illegal_group', {group => $group}); - - scalar(keys %$settings) - || ThrowCodeError('product_empty_group_controls', {group => $group}); - - # We store current settings for this group. - my $gs = $self->group_controls->{$group->id}; - # If there is no entry for this group yet, create a default hash. - unless (defined $gs) { - $gs = { entry => 0, - membercontrol => CONTROLMAPNA, - othercontrol => CONTROLMAPNA, - canedit => 0, - editcomponents => 0, - editbugs => 0, - canconfirm => 0, - group => $group }; + my ($self, $group, $settings) = @_; + + $group->is_active_bug_group + || ThrowUserError('product_illegal_group', {group => $group}); + + scalar(keys %$settings) + || ThrowCodeError('product_empty_group_controls', {group => $group}); + + # We store current settings for this group. + my $gs = $self->group_controls->{$group->id}; + + # If there is no entry for this group yet, create a default hash. + unless (defined $gs) { + $gs = { + entry => 0, + membercontrol => CONTROLMAPNA, + othercontrol => CONTROLMAPNA, + canedit => 0, + editcomponents => 0, + editbugs => 0, + canconfirm => 0, + group => $group + }; + } + + # Both settings must be defined, or none of them can be updated. + if (defined $settings->{membercontrol} && defined $settings->{othercontrol}) { + + # Legality of control combination is a function of + # membercontrol\othercontrol + # NA SH DE MA + # NA + - - - + # SH + + + + + # DE + - + + + # MA - - - + + foreach my $field ('membercontrol', 'othercontrol') { + my ($is_legal) + = grep { $settings->{$field} == $_ } + (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY); + defined $is_legal || ThrowCodeError('product_illegal_group_control', + {field => $field, value => $settings->{$field}}); } - - # Both settings must be defined, or none of them can be updated. - if (defined $settings->{membercontrol} && defined $settings->{othercontrol}) { - # Legality of control combination is a function of - # membercontrol\othercontrol - # NA SH DE MA - # NA + - - - - # SH + + + + - # DE + - + + - # MA - - - + - foreach my $field ('membercontrol', 'othercontrol') { - my ($is_legal) = grep { $settings->{$field} == $_ } - (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY); - defined $is_legal || ThrowCodeError('product_illegal_group_control', - { field => $field, value => $settings->{$field} }); - } - unless ($settings->{membercontrol} == $settings->{othercontrol} - || $settings->{membercontrol} == CONTROLMAPSHOWN - || ($settings->{membercontrol} == CONTROLMAPDEFAULT - && $settings->{othercontrol} != CONTROLMAPSHOWN)) - { - ThrowUserError('illegal_group_control_combination', {groupname => $group->name}); - } - $gs->{membercontrol} = $settings->{membercontrol}; - $gs->{othercontrol} = $settings->{othercontrol}; - } - - foreach my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm') { - next unless defined $settings->{$field}; - $gs->{$field} = $settings->{$field} ? 1 : 0; + unless ( + $settings->{membercontrol} == $settings->{othercontrol} + || $settings->{membercontrol} == CONTROLMAPSHOWN + || ( $settings->{membercontrol} == CONTROLMAPDEFAULT + && $settings->{othercontrol} != CONTROLMAPSHOWN) + ) + { + ThrowUserError('illegal_group_control_combination', + {groupname => $group->name}); } - $self->{group_controls}->{$group->id} = $gs; - $self->{check_group_controls} = 1; + $gs->{membercontrol} = $settings->{membercontrol}; + $gs->{othercontrol} = $settings->{othercontrol}; + } + + foreach + my $field ('entry', 'canedit', 'editcomponents', 'editbugs', 'canconfirm') + { + next unless defined $settings->{$field}; + $gs->{$field} = $settings->{$field} ? 1 : 0; + } + $self->{group_controls}->{$group->id} = $gs; + $self->{check_group_controls} = 1; } sub components { - my $self = shift; - my $dbh = Bugzilla->dbh; + my $self = shift; + my $dbh = Bugzilla->dbh; - if (!defined $self->{components}) { - my $ids = $dbh->selectcol_arrayref(q{ + if (!defined $self->{components}) { + my $ids = $dbh->selectcol_arrayref( + q{ SELECT id FROM components WHERE product_id = ? - ORDER BY name}, undef, $self->id); + ORDER BY name}, undef, $self->id + ); - require Bugzilla::Component; - $self->{components} = Bugzilla::Component->new_from_list($ids); - } - return $self->{components}; + require Bugzilla::Component; + $self->{components} = Bugzilla::Component->new_from_list($ids); + } + return $self->{components}; } -sub group_controls { - my ($self, $full_data) = @_; - my $dbh = Bugzilla->dbh; - - # By default, we don't return groups which are not listed in - # group_control_map. If $full_data is true, then we also - # return groups whose settings could be set for the product. - my $where_or_and = 'WHERE'; - my $and_or_where = 'AND'; - if ($full_data) { - $where_or_and = 'AND'; - $and_or_where = 'WHERE'; - } +sub component_count { + my $self = shift; + my $dbh = Bugzilla->dbh; + if (!defined $self->{component_count}) { + $self->{component_count} + = $dbh->selectrow_array( + "SELECT COUNT(id) FROM components WHERE product_id = ?", + undef, $self->id); + } + return $self->{component_count}; +} - # If $full_data is true, we collect all the data in all cases, - # even if the cache is already populated. - # $full_data is never used except in the very special case where - # all configurable bug groups are displayed to administrators, - # so we don't care about collecting all the data again in this case. - if (!defined $self->{group_controls} || $full_data) { - # Include name to the list, to allow us sorting data more easily. - my $query = qq{SELECT id, name, entry, membercontrol, othercontrol, - canedit, editcomponents, editbugs, canconfirm +sub group_controls { + my ($self, $full_data) = @_; + my $dbh = Bugzilla->dbh; + + # By default, we don't return groups which are not listed in + # group_control_map. If $full_data is true, then we also + # return groups whose settings could be set for the product. + my $where_or_and = 'WHERE'; + my $and_or_where = 'AND'; + if ($full_data) { + $where_or_and = 'AND'; + $and_or_where = 'WHERE'; + } + + # If $full_data is true, we collect all the data in all cases, + # even if the cache is already populated. + # $full_data is never used except in the very special case where + # all configurable bug groups are displayed to administrators, + # so we don't care about collecting all the data again in this case. + if (!defined $self->{group_controls} || $full_data) { + ## REDHAT EXTENSION 881990: Add description for sorting + # Include name and description to the list, to allow us sorting data more easily. + my $query = qq{SELECT id, name, entry, membercontrol, othercontrol, + canedit, editcomponents, editbugs, canconfirm, description FROM groups LEFT JOIN group_control_map ON id = group_id $where_or_and product_id = ? $and_or_where isbuggroup = 1}; - $self->{group_controls} = - $dbh->selectall_hashref($query, 'id', undef, $self->id); - - # For each group ID listed above, create and store its group object. - my @gids = keys %{$self->{group_controls}}; - my $groups = Bugzilla::Group->new_from_list(\@gids); - $self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups; - } - - # We never cache bug counts, for the same reason as above. - if ($full_data) { - my $counts = - $dbh->selectall_arrayref('SELECT group_id, COUNT(bugs.bug_id) AS bug_count + $self->{group_controls} + = $dbh->selectall_hashref($query, 'id', undef, $self->id); + + # For each group ID listed above, create and store its group object. + my @gids = keys %{$self->{group_controls}}; + my $groups = Bugzilla::Group->new_from_list(\@gids); + $self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups; + } + + # We never cache bug counts, for the same reason as above. + if ($full_data) { + my $counts = $dbh->selectall_arrayref( + 'SELECT group_id, COUNT(bugs.bug_id) AS bug_count FROM bug_group_map INNER JOIN bugs ON bugs.bug_id = bug_group_map.bug_id - WHERE bugs.product_id = ? ' . - $dbh->sql_group_by('group_id'), - {'Slice' => {}}, $self->id); - foreach my $data (@$counts) { - $self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count}; - } + WHERE bugs.product_id = ? ' + . $dbh->sql_group_by('group_id'), {'Slice' => {}}, $self->id + ); + foreach my $data (@$counts) { + $self->{group_controls}->{$data->{group_id}}->{bug_count} = $data->{bug_count}; } - return $self->{group_controls}; + } + return $self->{group_controls}; } sub groups_available { - my ($self) = @_; - return $self->{groups_available} if defined $self->{groups_available}; - my $dbh = Bugzilla->dbh; - my $shown = CONTROLMAPSHOWN; - my $default = CONTROLMAPDEFAULT; - my %member_groups = @{ $dbh->selectcol_arrayref( - "SELECT group_id, membercontrol + my ($self) = @_; + return $self->{groups_available} if defined $self->{groups_available}; + my $dbh = Bugzilla->dbh; + my $shown = CONTROLMAPSHOWN; + my $default = CONTROLMAPDEFAULT; + my %member_groups = @{ + $dbh->selectcol_arrayref( + "SELECT group_id, membercontrol FROM group_control_map INNER JOIN groups ON group_control_map.group_id = groups.id WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ? AND (membercontrol = $shown OR membercontrol = $default) - AND " . Bugzilla->user->groups_in_sql(), - {Columns=>[1,2]}, $self->id) }; - # We don't need to check the group membership here, because we only - # add these groups to the list below if the group isn't already listed - # for membercontrol. - my %other_groups = @{ $dbh->selectcol_arrayref( - "SELECT group_id, othercontrol + AND " . Bugzilla->user->groups_in_sql(), {Columns => [1, 2]}, + $self->id + ) + }; + + # We don't need to check the group membership here, because we only + # add these groups to the list below if the group isn't already listed + # for membercontrol. + my %other_groups = @{ + $dbh->selectcol_arrayref( + "SELECT group_id, othercontrol FROM group_control_map INNER JOIN groups ON group_control_map.group_id = groups.id WHERE isbuggroup = 1 AND isactive = 1 AND product_id = ? - AND (othercontrol = $shown OR othercontrol = $default)", - {Columns=>[1,2]}, $self->id) }; - - # If the user is a member, then we use the membercontrol value. - # Otherwise, we use the othercontrol value. - my %all_groups = %member_groups; - foreach my $id (keys %other_groups) { - if (!defined $all_groups{$id}) { - $all_groups{$id} = $other_groups{$id}; - } + AND (othercontrol = $shown OR othercontrol = $default)", + {Columns => [1, 2]}, $self->id + ) + }; + + # If the user is a member, then we use the membercontrol value. + # Otherwise, we use the othercontrol value. + my %all_groups = %member_groups; + foreach my $id (keys %other_groups) { + if (!defined $all_groups{$id}) { + $all_groups{$id} = $other_groups{$id}; } + } - my $available = Bugzilla::Group->new_from_list([keys %all_groups]); - foreach my $group (@$available) { - $group->{is_default} = 1 if $all_groups{$group->id} == $default; - } + my $available = Bugzilla::Group->new_from_list([keys %all_groups]); + foreach my $group (@$available) { + $group->{is_default} = 1 if $all_groups{$group->id} == $default; + } - $self->{groups_available} = $available; - return $self->{groups_available}; + $self->{groups_available} = $available; + return $self->{groups_available}; } sub groups_mandatory { - my ($self) = @_; - return $self->{groups_mandatory} if $self->{groups_mandatory}; - my $groups = Bugzilla->user->groups_as_string; - my $mandatory = CONTROLMAPMANDATORY; - # For membercontrol we don't check group_id IN, because if membercontrol - # is Mandatory, the group is Mandatory for everybody, regardless of their - # group membership. - my $ids = Bugzilla->dbh->selectcol_arrayref( - "SELECT group_id + my ($self) = @_; + return $self->{groups_mandatory} if $self->{groups_mandatory}; + my $groups = Bugzilla->user->groups_as_string; + my $mandatory = CONTROLMAPMANDATORY; + + # For membercontrol we don't check group_id IN, because if membercontrol + # is Mandatory, the group is Mandatory for everybody, regardless of their + # group membership. + my $ids = Bugzilla->dbh->selectcol_arrayref( + "SELECT group_id FROM group_control_map INNER JOIN groups ON group_control_map.group_id = groups.id WHERE product_id = ? AND isactive = 1 AND (membercontrol = $mandatory OR (othercontrol = $mandatory - AND group_id NOT IN ($groups)))", - undef, $self->id); - $self->{groups_mandatory} = Bugzilla::Group->new_from_list($ids); - return $self->{groups_mandatory}; + AND group_id NOT IN ($groups)))", undef, $self->id + ); + $self->{groups_mandatory} = Bugzilla::Group->new_from_list($ids); + return $self->{groups_mandatory}; } # We don't just check groups_valid, because we want to know specifically # if this group can be validly set by the currently-logged-in user. sub group_is_settable { - my ($self, $group) = @_; + my ($self, $group) = @_; - return 0 unless ($group->is_active && $group->is_bug_group); + return 0 unless ($group->is_active && $group->is_bug_group); - my $is_mandatory = grep { $group->id == $_->id } - @{ $self->groups_mandatory }; - my $is_available = grep { $group->id == $_->id } - @{ $self->groups_available }; - return ($is_mandatory or $is_available) ? 1 : 0; + my $is_mandatory = grep { $group->id == $_->id } @{$self->groups_mandatory}; + my $is_available = grep { $group->id == $_->id } @{$self->groups_available}; + return ($is_mandatory or $is_available) ? 1 : 0; } sub group_is_valid { - my ($self, $group) = @_; - return grep($_->id == $group->id, @{ $self->groups_valid }) ? 1 : 0; + my ($self, $group) = @_; + return grep($_->id == $group->id, @{$self->groups_valid}) ? 1 : 0; } sub groups_valid { - my ($self) = @_; - return $self->{groups_valid} if defined $self->{groups_valid}; - - # Note that we don't check OtherControl below, because there is no - # valid NA/* combination. - my $ids = Bugzilla->dbh->selectcol_arrayref( - "SELECT DISTINCT group_id + my ($self) = @_; + return $self->{groups_valid} if defined $self->{groups_valid}; + + # Note that we don't check OtherControl below, because there is no + # valid NA/* combination. + my $ids = Bugzilla->dbh->selectcol_arrayref( + "SELECT DISTINCT group_id FROM group_control_map AS gcm INNER JOIN groups ON gcm.group_id = groups.id WHERE product_id = ? AND isbuggroup = 1 - AND membercontrol != " . CONTROLMAPNA, undef, $self->id); - $self->{groups_valid} = Bugzilla::Group->new_from_list($ids); - return $self->{groups_valid}; + AND membercontrol != " . CONTROLMAPNA, undef, $self->id + ); + $self->{groups_valid} = Bugzilla::Group->new_from_list($ids); + return $self->{groups_valid}; } sub versions { - my $self = shift; - my $dbh = Bugzilla->dbh; + my $self = shift; + my $dbh = Bugzilla->dbh; - if (!defined $self->{versions}) { - my $ids = $dbh->selectcol_arrayref(q{ + if (!defined $self->{versions}) { + my $ids = $dbh->selectcol_arrayref( + q{ SELECT id FROM versions - WHERE product_id = ?}, undef, $self->id); + WHERE product_id = ?}, undef, $self->id + ); - $self->{versions} = Bugzilla::Version->new_from_list($ids); - } - return $self->{versions}; + $self->{versions} = Bugzilla::Version->new_from_list($ids); + } + return $self->{versions}; } sub milestones { - my $self = shift; - my $dbh = Bugzilla->dbh; + my $self = shift; + my $dbh = Bugzilla->dbh; - if (!defined $self->{milestones}) { - my $ids = $dbh->selectcol_arrayref(q{ + if (!defined $self->{milestones}) { + my $ids = $dbh->selectcol_arrayref( + q{ SELECT id FROM milestones - WHERE product_id = ?}, undef, $self->id); - - $self->{milestones} = Bugzilla::Milestone->new_from_list($ids); - } - return $self->{milestones}; + WHERE product_id = ?}, undef, $self->id + ); + + $self->{milestones} = Bugzilla::Milestone->new_from_list($ids); + } + return $self->{milestones}; +} + +sub releases { + my $self = shift; + my $dbh = Bugzilla->dbh; + + if (!defined $self->{releases}) { + my $ids = $dbh->selectcol_arrayref( + q{ + SELECT id FROM releases + WHERE product_id = ?}, undef, $self->id + ); + + $self->{releases} = Bugzilla::Release->new_from_list($ids); + } + return $self->{releases}; } sub bug_count { - my $self = shift; - my $dbh = Bugzilla->dbh; + my $self = shift; + my $dbh = Bugzilla->dbh; - if (!defined $self->{'bug_count'}) { - $self->{'bug_count'} = $dbh->selectrow_array(qq{ + if (!defined $self->{'bug_count'}) { + $self->{'bug_count'} = $dbh->selectrow_array( + qq{ SELECT COUNT(bug_id) FROM bugs - WHERE product_id = ?}, undef, $self->id); + WHERE product_id = ?}, undef, $self->id + ); - } - return $self->{'bug_count'}; + } + return $self->{'bug_count'}; } sub bug_ids { - my $self = shift; - my $dbh = Bugzilla->dbh; - - if (!defined $self->{'bug_ids'}) { - $self->{'bug_ids'} = - $dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs - WHERE product_id = ?}, - undef, $self->id); - } - return $self->{'bug_ids'}; + my $self = shift; + my $dbh = Bugzilla->dbh; + + if (!defined $self->{'bug_ids'}) { + $self->{'bug_ids'} = $dbh->selectcol_arrayref( + q{SELECT bug_id FROM bugs + WHERE product_id = ?}, undef, $self->id + ); + } + return $self->{'bug_ids'}; } sub user_has_access { - my ($self, $user) = @_; + my ($self, $user) = @_; - return Bugzilla->dbh->selectrow_array( - 'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END + return Bugzilla->dbh->selectrow_array( + 'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END 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 (' . $user->groups_as_string . ') - WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1), - undef, $self->id); + WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1), undef, $self->id + ); } sub flag_types { - my $self = shift; - - return $self->{'flag_types'} if defined $self->{'flag_types'}; - - # We cache flag types to avoid useless calls to get_clusions(). - my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {}; - $self->{flag_types} = {}; - my $prod_id = $self->id; - my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id }); - - foreach my $type ('bug', 'attachment') { - my @flags = grep { $_->target_type eq $type } @$flagtypes; - $self->{flag_types}->{$type} = \@flags; - - # Also populate component flag types, while we are here. - foreach my $comp (@{$self->components}) { - $comp->{flag_types} ||= {}; - my $comp_id = $comp->id; - - foreach my $flag (@flags) { - my $flag_id = $flag->id; - $cache->{$flag_id} ||= $flag; - my $i = $cache->{$flag_id}->inclusions_as_hash; - my $e = $cache->{$flag_id}->exclusions_as_hash; - my $included = $i->{0}->{0} || $i->{0}->{$comp_id} - || $i->{$prod_id}->{0} || $i->{$prod_id}->{$comp_id}; - my $excluded = $e->{0}->{0} || $e->{0}->{$comp_id} - || $e->{$prod_id}->{0} || $e->{$prod_id}->{$comp_id}; - push(@{$comp->{flag_types}->{$type}}, $flag) if ($included && !$excluded); - } - } + my $self = shift; + + return $self->{'flag_types'} if defined $self->{'flag_types'}; + + # We cache flag types to avoid useless calls to get_clusions(). + my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {}; + $self->{flag_types} = {}; + my $prod_id = $self->id; + my $flagtypes = Bugzilla::FlagType::match({product_id => $prod_id}); + + foreach my $type ('bug', 'attachment') { + my @flags = grep { $_->target_type eq $type } @$flagtypes; + $self->{flag_types}->{$type} = \@flags; + + # Also populate component flag types, while we are here. + foreach my $comp (@{$self->components}) { + $comp->{flag_types} ||= {}; + my $comp_id = $comp->id; + + foreach my $flag (@flags) { + my $flag_id = $flag->id; + $cache->{$flag_id} ||= $flag; + my $i = $cache->{$flag_id}->inclusions_as_hash; + my $e = $cache->{$flag_id}->exclusions_as_hash; + my $included + = $i->{0}->{0} + || $i->{0}->{$comp_id} + || $i->{$prod_id}->{0} + || $i->{$prod_id}->{$comp_id}; + my $excluded + = $e->{0}->{0} + || $e->{0}->{$comp_id} + || $e->{$prod_id}->{0} + || $e->{$prod_id}->{$comp_id}; + push(@{$comp->{flag_types}->{$type}}, $flag) if ($included && !$excluded); + } } - return $self->{'flag_types'}; + } + return $self->{'flag_types'}; } sub classification { - my $self = shift; - $self->{'classification'} ||= - new Bugzilla::Classification({ id => $self->classification_id, cache => 1 }); - return $self->{'classification'}; + my $self = shift; + $self->{'classification'} ||= new Bugzilla::Classification( + {id => $self->classification_id, cache => 1}); + return $self->{'classification'}; } ############################### @@ -825,30 +1039,103 @@ sub classification { ############################### sub allows_unconfirmed { return $_[0]->{'allows_unconfirmed'}; } -sub description { return $_[0]->{'description'}; } -sub is_active { return $_[0]->{'isactive'}; } -sub default_milestone { return $_[0]->{'defaultmilestone'}; } -sub classification_id { return $_[0]->{'classification_id'}; } +sub description { return $_[0]->{'description'}; } +sub is_active { return $_[0]->{'isactive'}; } +sub default_milestone { return $_[0]->{'defaultmilestone'}; } +sub classification_id { return $_[0]->{'classification_id'}; } +sub default_release { return $_[0]->{'defaultrelease'}; } +## REDHAT EXTENSION BEGIN 706784 +sub multiple_components { return $_[0]->{'multiple_components'}; } +sub multiple_target_releases { return $_[0]->{'multiple_target_releases'}; } +sub multiple_versions { return $_[0]->{'multiple_versions'}; } +## REDHAT EXTENSION END 706784 +## REDHAT EXTENSION BEGIN 829154 +sub always_private { return $_[0]->{'always_private'}; } +## REDHAT EXTENSION END 829154 ############################### #### Subroutines ###### ############################### sub check { - my ($class, $params) = @_; - $params = { name => $params } if !ref $params; - if (!$params->{allow_inaccessible}) { - $params->{_error} = 'product_access_denied'; - } - my $product = $class->SUPER::check($params); + my ($class, $params) = @_; + $params = {name => $params} if !ref $params; + if (!$params->{allow_inaccessible}) { + $params->{_error} = 'product_access_denied'; + } + my $product = $class->SUPER::check($params); + + if ( !$params->{allow_inaccessible} + && !Bugzilla->user->can_access_product($product)) + { + ThrowUserError('product_access_denied', $params); + } + return $product; +} - if (!$params->{allow_inaccessible} - && !Bugzilla->user->can_access_product($product)) - { - ThrowUserError('product_access_denied', $params); - } - return $product; +## REDHAT EXTENSION START 951975 +sub private_product_check { + my $self = shift; + + # Get a list of available groups for the product + my $gc = $self->group_controls; + my @group_ids + = grep { $gc->{$_}{membercontrol} || $gc->{$_}{othercontrol} } keys %$gc; + + ThrowUserError('product_cannot_be_private') if scalar(@group_ids) == 0; + + my $groups_map = join(',', @group_ids); + + # Return the number of bugs that aren't / wouldn't be restricted + return Bugzilla->dbh->selectrow_array( + qq{ + SELECT COUNT(*) + FROM bugs + WHERE product_id = ? + AND bug_id NOT IN ( + SELECT bug_id FROM bug_group_map + WHERE group_id IN ($groups_map) + ) + }, undef, $self->id + ); +} + + +sub private_product_set { + my $self = shift; + + # Get the new group name, and turn it into an object + my $new_group_name = shift; + my $new_group = Bugzilla::Group->new({name => $new_group_name}); + ThrowUserError('object_does_not_exist', + {class => 'Bugzilla::Group', name => $new_group_name}) + if not $new_group; + + # Check that this group is available for these bugs + my $gc = $self->group_controls; + my @group_ids + = grep { $gc->{$_}{membercontrol} || $gc->{$_}{othercontrol} } keys %$gc; + ThrowUserError('group_restriction_not_allowed', + {name => $new_group_name, product => $self->name}) + unless grep { $_ == $new_group->id } @group_ids; + + my $groups_map = join(',', @group_ids); + + # Add the group to any bugs that don't have a group restriction + return Bugzilla->dbh->do( + qq{ + INSERT INTO bug_group_map (bug_id, group_id) + SELECT bug_id, ? + FROM bugs + WHERE product_id = ? + AND bug_id NOT IN ( + SELECT bug_id FROM bug_group_map + WHERE group_id IN ($groups_map) + ) + }, undef, $new_group->id, $self->id + ); } +## REDHAT EXTENSION END 951975 1; @@ -868,6 +1155,7 @@ Bugzilla::Product - Bugzilla product class. my @components = $product->components(); my $groups_controls = $product->group_controls(); my @milestones = $product->milestones(); + my @releases = $product->releases(); my @versions = $product->versions(); my $bugcount = $product->bug_count(); my $bug_ids = $product->bug_ids(); @@ -880,6 +1168,7 @@ Bugzilla::Product - Bugzilla product class. my $description = $product->description; my isactive = $product->is_active; my $defaultmilestone = $product->default_milestone; + my $defaultrelease = $product->default_release; my $classificationid = $product->classification_id; my $allows_unconfirmed = $product->allows_unconfirmed; @@ -1005,6 +1294,14 @@ a group is valid in a particular product.) Returns: An array of Bugzilla::Milestone objects. +=item C<releases> + + Description: Returns all valid releases for that product. + + Params: none. + + Returns: An array of Bugzilla::Release objects. + =item C<bug_count()> Description: Returns the total of bugs that belong to the product. @@ -1068,6 +1365,17 @@ products and their components are also preloaded. This function is not exported, so must be called like C<Bugzilla::Product::preload($products)>. +=item C<private_product_check> + +This is a Red Hat customisation. Returns the number of bugs a product +has that have no group restrictions. In the case where group restrictions +are changing, looks at the new list + +=item C<private_product_set> + +This is a Red Hat customisation. Given a group name will add the group to +all bugs that match the above criteria. + =back =head1 SEE ALSO @@ -1108,4 +1416,26 @@ L<Bugzilla::Object> =item update +=item always_private + +=item set_multiple_components + +=item set_always_private + +=item default_release + +=item multiple_versions + +=item component_count + +=item set_multiple_target_releases + +=item set_default_release + +=item multiple_components + +=item set_multiple_versions + +=item multiple_target_releases + =back |