diff options
Diffstat (limited to 'Bugzilla/Product.pm')
-rw-r--r-- | Bugzilla/Product.pm | 1230 |
1 files changed, 647 insertions, 583 deletions
diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index 0c0cb458d..1f80db451 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -39,32 +39,32 @@ use constant IS_CONFIG => 1; use constant DB_TABLE => 'products'; use constant DB_COLUMNS => qw( - id - name - classification_id - description - isactive - defaultmilestone - allows_unconfirmed + id + name + classification_id + description + isactive + defaultmilestone + allows_unconfirmed ); use constant UPDATE_COLUMNS => qw( - name - description - defaultmilestone - isactive - allows_unconfirmed + name + description + defaultmilestone + isactive + allows_unconfirmed ); 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, + isactive => \&Bugzilla::Object::check_boolean, + create_series => \&Bugzilla::Object::check_boolean }; ############################### @@ -72,258 +72,283 @@ use constant VALIDATORS => { ############################### 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(@_); + + # 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 $product = $class->insert_create_data($params); - Bugzilla->user->clear_product_cache(); + my $product = $class->insert_create_data($params); + Bugzilla->user->clear_product_cache(); - # 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 }); + # 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}); - # Create groups and series for the new product, if requested. - $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'}; - $product->_create_series() if $create_series; + # Create groups and series for the new product, if requested. + $product->_create_bug_group() if Bugzilla->params->{'makeproductgroups'}; + $product->_create_series() if $create_series; - Bugzilla::Hook::process('product_end_of_create', { product => $product }); + Bugzilla::Hook::process('product_end_of_create', {product => $product}); - $dbh->bz_commit_transaction(); - Bugzilla->memcached->clear_config(); - return $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)) { + 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; + + # 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 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 +357,94 @@ 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}); + } + else { + ThrowUserError('product_name_diff_in_case', + {'product' => $name, 'existing_product' => $product->name}); } - return $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; + $url = trim($url || ''); + return $url; } ##################################### @@ -431,393 +459,429 @@ 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_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 }; - } - - # 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}; + 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}}); } - - 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'; - } - - # 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, + 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) { + + # 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 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 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,29 +889,29 @@ 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'}; } ############################### #### 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); - - if (!$params->{allow_inaccessible} - && !Bugzilla->user->can_access_product($product)) - { - ThrowUserError('product_access_denied', $params); - } - return $product; + 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; } 1; |