diff options
Diffstat (limited to 'extensions/Releases/lib/Schedule.pm')
-rw-r--r-- | extensions/Releases/lib/Schedule.pm | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/extensions/Releases/lib/Schedule.pm b/extensions/Releases/lib/Schedule.pm new file mode 100644 index 000000000..995f38942 --- /dev/null +++ b/extensions/Releases/lib/Schedule.pm @@ -0,0 +1,436 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use strict; +use warnings; +use 5.10.1; + +package Bugzilla::Extension::Releases::Schedule; + +use base qw(Bugzilla::Object); + +use Bugzilla::Constants; +use Bugzilla::Util; +use Bugzilla::Error; +use Bugzilla::User; +use Date::Parse; +use Bugzilla::Extension::Releases::Release; +use Bugzilla::Extension::Releases::Util; + +############################### +#### Initialization #### +############################### + +use constant DB_TABLE => 'rh_release_schedule'; + +use constant DB_COLUMNS => qw( + id + release_id +); + +use constant REQUIRED_CREATE_FIELDS => qw( + release_id +); + +use constant UPDATE_COLUMNS => qw( + release_id +); + +use constant VALIDATORS => + {release_id => \&_check_release_id, phases => \&_check_phases,}; + +use constant UPDATE_VALIDATORS => {release_id => \&_check_release_id,}; + +use constant VALID_PHASES => (qw( + planning + development + testing + launch + maintenance + )); + +############################### + +sub new { + my $class = shift; + my $param = shift; + + my $release; + if (ref $param) { + if ($param->{'release'}) { + $release = Bugzilla::Extension::Releases::Release->new( + {release => $param->{'release'}}); + } + elsif ($param->{'release_id'}) { + $release = Bugzilla::Extension::Releases::Release->new( + {release_id => $param->{'release_id'}}); + } + else { + ThrowCodeError('bad_arg', {argument => 'release', function => "${class}::new"}); + } + my $condition = 'release_id = ?'; + my @values = ($release->id); + $param = {condition => $condition, values => \@values}; + } + + return $class->SUPER::new($param); +} + +sub create { + my $class = shift; + my $dbh = Bugzilla->dbh; + + $dbh->bz_start_transaction(); + + $class->check_required_create_fields(@_); + my $params = $class->run_create_validators(@_); + my $phases = delete $params->{phases}; + + my $schedule = $class->insert_create_data($params); + + # We still have to fill out the phase list + if ($phases) { + $schedule->set_phases($phases); + $schedule->_update_phases($phases); + } + + $dbh->bz_commit_transaction(); + + return $schedule; +} + +sub update { + my $self = shift; + my $changes = $self->SUPER::update(@_); + + # Update the component_cc table if necessary. + if (defined $self->{phases}) { + my $diff = $self->_update_phases($self->{phases}); + $changes->{phases} = $diff if defined $diff; + } + + return $changes; +} + +sub remove_from_db { + my $self = shift; + my $dbh = Bugzilla->dbh; + + $dbh->bz_start_transaction(); + + $dbh->do('DELETE FROM rh_release_schedule_phase WHERE schedule_id = ?', + undef, $self->id); + $dbh->do('DELETE FROM rh_release_schedule WHERE id = ?', undef, $self->id); + + $dbh->bz_commit_transaction(); + + return; +} + +################################ +#### Validators #### +################################ + +sub _check_release_id { + my ($invocant, $release_id) = @_; + $release_id || ThrowCodeError('param_required', {param => 'release_id'}); + detaint_natural($release_id); + return $release_id; +} + +sub _check_phases { + my ($invocant, $phases) = @_; + + # Make sure schedule is an array of hash references each in the format of + # { phase => {date_start => '2009-01-08', date_end => '2009-03-01'} } + ref($phases) =~ /HASH/ || ThrowCodeError( + 'release_schedule_invalid_schedule_data', + {msg => "'phases' should be a hash reference"} + ); + + foreach my $phase (keys %{$phases}) { + ref($phases->{$phase}) =~ /HASH/ + || ThrowCodeError('release_schedule_invalid_schedule_data', + {msg => "Each phase in the 'phases' hash should " . "be a hash reference"}); + + grep($_ eq $phase, VALID_PHASES) + || ThrowCodeError('release_schedule_invalid_phase', {value => $phase}); + + validate_date($phases->{$phase}{date_start}) + || ThrowUserError('illegal_date', + {date => $phases->{$phase}{date_start}, format => 'YYYY-MM-DD'}); + + validate_date($phases->{$phase}{date_end}) + || ThrowUserError('illegal_date', + {date => $phases->{$phase}{date_end}, format => 'YYYY-MM-DD'}); + + # Date end must be greater or than date start or equal to + if ( + str2time($phases->{$phase}{date_end}) < str2time($phases->{$phase}{date_start})) + { + ThrowCodeError( + 'release_schedule_invalid_schedule_data', + { + msg => "End date '" + . $phases->{$phase}{date_end} + . "' must be greater than start date '" + . $phases->{$phase}{date_start} . "'" + } + ); + } + } + + return $phases; +} + +############################### +#### Methods #### +############################### + +sub _update_phases { + my $self = shift; + my $dbh = Bugzilla->dbh; + my %changes = (added => {}, updated => {}, removed => {}); + + my %old_phases; + my $sth = $dbh->prepare( + 'SELECT phase, ' + . $dbh->sql_date_format("date_start", "%Y-%m-%d") . ', ' + . $dbh->sql_date_format("date_end", "%Y-%m-%d") . ' + FROM rh_release_schedule_phase + WHERE schedule_id = ?' + ); + $sth->execute($self->id); + while (my ($phase, $date_start, $date_end) = $sth->fetchrow_array()) { + $old_phases{$phase} = {date_start => $date_start, date_end => $date_end}; + } + + foreach my $phase (keys %{$self->{phases}}) { + if (!exists $old_phases{$phase}) { + $changes{added}{"$phase start"} = ["", $self->{phases}{$phase}{date_start}]; + $changes{added}{"$phase end"} = ["", $self->{phases}{$phase}{date_end}]; + } + elsif ($self->{phases}{$phase}{date_start} ne $old_phases{$phase}{date_start}) { + $changes{updated}{"$phase start"} + = [$old_phases{$phase}{date_start}, $self->{phases}{$phase}{date_start}]; + } + elsif ($self->{phases}{$phase}{date_end} ne $old_phases{$phase}{date_end}) { + $changes{updated}{"$phase end"} + = [$old_phases{$phase}{date_end}, $self->{phases}{$phase}{date_end}]; + } + } + + foreach my $phase (keys %old_phases) { + if (!exists $self->{phases}{$phase}) { + $changes{removed}{"$phase start"} = [$old_phases{$phase}{date_start}, ""]; + $changes{removed}{"$phase end"} = [$old_phases{$phase}{date_end}, ""]; + } + } + + $dbh->bz_start_transaction(); + + $dbh->do('DELETE FROM rh_release_schedule_phase WHERE schedule_id = ?', + undef, $self->id); + + $sth = $dbh->prepare( + 'INSERT INTO rh_release_schedule_phase + (schedule_id, phase, date_start, date_end) VALUES (?, ?, ?, ?)' + ); + + foreach my $phase (keys %{$self->{phases}}) { + trick_taint($self->{phases}{$phase}{date_start}); + trick_taint($self->{phases}{$phase}{date_end}); + $sth->execute( + $self->id, $phase, + $self->{phases}{$phase}{date_start}, + $self->{phases}{$phase}{date_end} + ); + } + + my $timestamp = $dbh->selectrow_array('SELECT NOW()'); + + foreach my $type ("added", "updated", "removed") { + foreach my $key (keys %{$changes{$type}}) { + trick_taint($changes{$type}{$key}[0]); + trick_taint($changes{$type}{$key}[1]); + log_activity({ + act_when => $timestamp, + act_type => 'schedule', + release_id => $self->release_id, + who => Bugzilla->user->id, + what => $key, + removed => $changes{$type}{$key}[0], + added => $changes{$type}{$key}[1] + }); + } + } + + $dbh->bz_commit_transaction(); + + return \%changes; +} + +sub set_release_id { $_[0]->set('release_id', $_[1]); return; } + +sub set_phases { + my ($self, $phases) = @_; + $self->{phases} = $self->_check_phases($phases); + return $self->{phases}; +} + +############################### +#### Accessors #### +############################### + +sub id { return $_[0]->{'id'}; } +sub release_id { return $_[0]->{'release_id'}; } + +sub release { + my ($self) = @_; + return $self->{'release'} if $self->{'release'}; + $self->{'release'} + = Bugzilla::Extension::Releases::Release->new($self->release_id); + return $self->{'release'}; +} + +sub phases { + my $self = shift; + my $dbh = Bugzilla->dbh; + + if (!defined $self->{'phases'}) { + my %phases; + my $sth = $dbh->prepare( + 'SELECT phase, ' + . $dbh->sql_date_format("date_start", "%Y-%m-%d") . ', ' + . $dbh->sql_date_format("date_end", "%Y-%m-%d") . ' + FROM rh_release_schedule_phase + WHERE schedule_id = ?' + ); + $sth->execute($self->id); + while (my ($phase, $date_start, $date_end) = $sth->fetchrow_array()) { + $phases{$phase} = {date_start => $date_start, date_end => $date_end}; + } + $self->{phases} = \%phases; + } + + return $self->{phases}; +} + +############################### +#### Subroutines #### +############################### + +sub get_legal_phases { + return VALID_PHASES; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Extension::Releases::Schedule - Red Hat Bugzilla Release Schedule class. + +=head1 SYNOPSIS + + use Bugzilla::Extension::Releases::Schedule; + + my $schedule = Bugzilla::Extension::Releases::Schedule->new($schedule_id); + my $schedule = Bugzilla::Extension::Releases::Schedule->new({ release => $release_name }); + my $schedule = Bugzilla::Extension::Releases::Schedule->new({ release_id => $release_id }); + + my $schedule = Bugzilla::Extension::Releases::Schedule->check({ release => $release_name }); + my $schedule = Bugzilla::Extension::Releases::Schedule->check({ release_id => $release_id }); + + my $schedule = + Bugzilla::Extension::Releases::Release->create({ release => $release_name, + phases => $phases }); + + $schedule->set_phases($new_phases); + $schedule->set_release_id($release->id); + + $schedule->update(); + + $schedule->remove_from_db; + +=head1 DESCRIPTION + +Release.pm represents a Red Hat Bugzilla Release object. + +=head1 METHODS + +=over + +=item C<new($param)> + + Description: The constructor is used to load an existing Release Schedule + by passing a release ID or a hash with the release + name. + + Params: $param - If you pass an integer, the integer is the + release ID from the database that we want to + read in. If you pass in a hash with the 'release' + key, then the value of the release + key is the release name. + + Returns: A Release Schedule object. + +=item C<set_release_id> + + Description: Changes the release id of the schedule. + + Params: $new_release_id - new release_id of the schedule (integer). + + Returns: Nothing. + +=item C<set_phases> + + Description: Changes the schedule phases of the release. + + Params: $new_phases - a hash reference containing phase names which are references + to other hashes containing their start and end dates. + NOTE: This is a make exact type operation. Any phases not passed in will be removed. + { development => { date_start => 'YYYY-MM-DD', date_end => 'YYYY-MM-DD' } } + + Returns: Nothing. + +=item C<update()> + + Description: Write changes made to the schedule into the DB. + + Params: none. + + Returns: A hashref with changes made to the schedule object. + +=item C<remove_from_db()> + + Description: Deletes the current full schedule from the DB. The object itself + is not destroyed. + + Params: none. + + Returns: Nothing. + +=back + +=head1 CLASS METHODS + +=over + +=item C<create(\%params)> + + Description: Create a new schedule. + + Params: The hashref must have the following keys: + release - release name for the new schedule (string). + phases - a hash reference containing phase names which are references + to other hashes containing their start and end dates (hash) + { development => { date_start => 'YYYY-MM-DD', date_end => 'YYYY-MM-DD' } } + + Returns: A Release Schedule object. + +=back + +=cut |