summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/Releases/lib/Schedule.pm')
-rw-r--r--extensions/Releases/lib/Schedule.pm436
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