Milestone.pm 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. # -*- Mode: perl; indent-tabs-mode: nil -*-
  2. #
  3. # The contents of this file are subject to the Mozilla Public
  4. # License Version 1.1 (the "License"); you may not use this file
  5. # except in compliance with the License. You may obtain a copy of
  6. # the License at http://www.mozilla.org/MPL/
  7. #
  8. # Software distributed under the License is distributed on an "AS
  9. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10. # implied. See the License for the specific language governing
  11. # rights and limitations under the License.
  12. #
  13. # The Original Code is the Bugzilla Bug Tracking System.
  14. #
  15. # Contributor(s): Tiago R. Mello <timello@async.com.br>
  16. # Max Kanat-Alexander <mkanat@bugzilla.org>
  17. # Frédéric Buclin <LpSolit@gmail.com>
  18. use strict;
  19. package Bugzilla::Milestone;
  20. use base qw(Bugzilla::Object);
  21. use Bugzilla::Constants;
  22. use Bugzilla::Util;
  23. use Bugzilla::Error;
  24. ################################
  25. ##### Initialization #####
  26. ################################
  27. use constant DEFAULT_SORTKEY => 0;
  28. use constant DB_TABLE => 'milestones';
  29. use constant NAME_FIELD => 'value';
  30. use constant LIST_ORDER => 'sortkey, value';
  31. use constant DB_COLUMNS => qw(
  32. id
  33. value
  34. product_id
  35. sortkey
  36. );
  37. use constant REQUIRED_CREATE_FIELDS => qw(
  38. name
  39. product
  40. );
  41. use constant UPDATE_COLUMNS => qw(
  42. value
  43. sortkey
  44. );
  45. use constant VALIDATORS => {
  46. product => \&_check_product,
  47. sortkey => \&_check_sortkey,
  48. };
  49. use constant UPDATE_VALIDATORS => {
  50. value => \&_check_value,
  51. };
  52. ################################
  53. sub new {
  54. my $class = shift;
  55. my $param = shift;
  56. my $dbh = Bugzilla->dbh;
  57. my $product;
  58. if (ref $param) {
  59. $product = $param->{product};
  60. my $name = $param->{name};
  61. if (!defined $product) {
  62. ThrowCodeError('bad_arg',
  63. {argument => 'product',
  64. function => "${class}::new"});
  65. }
  66. if (!defined $name) {
  67. ThrowCodeError('bad_arg',
  68. {argument => 'name',
  69. function => "${class}::new"});
  70. }
  71. my $condition = 'product_id = ? AND value = ?';
  72. my @values = ($product->id, $name);
  73. $param = { condition => $condition, values => \@values };
  74. }
  75. unshift @_, $param;
  76. return $class->SUPER::new(@_);
  77. }
  78. sub run_create_validators {
  79. my $class = shift;
  80. my $params = $class->SUPER::run_create_validators(@_);
  81. my $product = delete $params->{product};
  82. $params->{product_id} = $product->id;
  83. $params->{value} = $class->_check_value($params->{name}, $product);
  84. delete $params->{name};
  85. return $params;
  86. }
  87. sub update {
  88. my $self = shift;
  89. my $changes = $self->SUPER::update(@_);
  90. if (exists $changes->{value}) {
  91. my $dbh = Bugzilla->dbh;
  92. # The milestone value is stored in the bugs table instead of its ID.
  93. $dbh->do('UPDATE bugs SET target_milestone = ?
  94. WHERE target_milestone = ? AND product_id = ?',
  95. undef, ($self->name, $changes->{value}->[0], $self->product_id));
  96. # The default milestone also stores the value instead of the ID.
  97. $dbh->do('UPDATE products SET defaultmilestone = ?
  98. WHERE id = ? AND defaultmilestone = ?',
  99. undef, ($self->name, $self->product_id, $changes->{value}->[0]));
  100. }
  101. return $changes;
  102. }
  103. sub remove_from_db {
  104. my $self = shift;
  105. my $dbh = Bugzilla->dbh;
  106. # The default milestone cannot be deleted.
  107. if ($self->name eq $self->product->default_milestone) {
  108. ThrowUserError('milestone_is_default', { milestone => $self });
  109. }
  110. if ($self->bug_count) {
  111. # We don't want to delete bugs when deleting a milestone.
  112. # Bugs concerned are reassigned to the default milestone.
  113. my $bug_ids =
  114. $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
  115. WHERE product_id = ? AND target_milestone = ?',
  116. undef, ($self->product->id, $self->name));
  117. my $timestamp = $dbh->selectrow_array('SELECT NOW()');
  118. $dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
  119. WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
  120. undef, ($self->product->default_milestone, $timestamp));
  121. require Bugzilla::Bug;
  122. import Bugzilla::Bug qw(LogActivityEntry);
  123. foreach my $bug_id (@$bug_ids) {
  124. LogActivityEntry($bug_id, 'target_milestone',
  125. $self->name,
  126. $self->product->default_milestone,
  127. Bugzilla->user->id, $timestamp);
  128. }
  129. }
  130. $dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
  131. }
  132. ################################
  133. # Validators
  134. ################################
  135. sub _check_value {
  136. my ($invocant, $name, $product) = @_;
  137. $name = trim($name);
  138. $name || ThrowUserError('milestone_blank_name');
  139. if (length($name) > MAX_MILESTONE_SIZE) {
  140. ThrowUserError('milestone_name_too_long', {name => $name});
  141. }
  142. $product = $invocant->product if (ref $invocant);
  143. my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
  144. if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
  145. ThrowUserError('milestone_already_exists', { name => $milestone->name,
  146. product => $product->name });
  147. }
  148. return $name;
  149. }
  150. sub _check_sortkey {
  151. my ($invocant, $sortkey) = @_;
  152. # Keep a copy in case detaint_signed() clears the sortkey
  153. my $stored_sortkey = $sortkey;
  154. if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
  155. ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
  156. }
  157. return $sortkey;
  158. }
  159. sub _check_product {
  160. my ($invocant, $product) = @_;
  161. return Bugzilla->user->check_can_admin_product($product->name);
  162. }
  163. ################################
  164. # Methods
  165. ################################
  166. sub set_name { $_[0]->set('value', $_[1]); }
  167. sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
  168. sub bug_count {
  169. my $self = shift;
  170. my $dbh = Bugzilla->dbh;
  171. if (!defined $self->{'bug_count'}) {
  172. $self->{'bug_count'} = $dbh->selectrow_array(q{
  173. SELECT COUNT(*) FROM bugs
  174. WHERE product_id = ? AND target_milestone = ?},
  175. undef, $self->product_id, $self->name) || 0;
  176. }
  177. return $self->{'bug_count'};
  178. }
  179. ################################
  180. ##### Accessors ######
  181. ################################
  182. sub name { return $_[0]->{'value'}; }
  183. sub product_id { return $_[0]->{'product_id'}; }
  184. sub sortkey { return $_[0]->{'sortkey'}; }
  185. sub product {
  186. my $self = shift;
  187. require Bugzilla::Product;
  188. $self->{'product'} ||= new Bugzilla::Product($self->product_id);
  189. return $self->{'product'};
  190. }
  191. 1;
  192. __END__
  193. =head1 NAME
  194. Bugzilla::Milestone - Bugzilla product milestone class.
  195. =head1 SYNOPSIS
  196. use Bugzilla::Milestone;
  197. my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
  198. my $name = $milestone->name;
  199. my $product_id = $milestone->product_id;
  200. my $product = $milestone->product;
  201. my $sortkey = $milestone->sortkey;
  202. my $milestone = Bugzilla::Milestone->create(
  203. { name => $name, product => $product, sortkey => $sortkey });
  204. $milestone->set_name($new_name);
  205. $milestone->set_sortkey($new_sortkey);
  206. $milestone->update();
  207. $milestone->remove_from_db;
  208. =head1 DESCRIPTION
  209. Milestone.pm represents a Product Milestone object.
  210. =head1 METHODS
  211. =over
  212. =item C<new({name => $name, product => $product})>
  213. Description: The constructor is used to load an existing milestone
  214. by passing a product object and a milestone name.
  215. Params: $product - a Bugzilla::Product object.
  216. $name - the name of a milestone (string).
  217. Returns: A Bugzilla::Milestone object.
  218. =item C<name()>
  219. Description: Name (value) of the milestone.
  220. Params: none.
  221. Returns: The name of the milestone.
  222. =item C<product_id()>
  223. Description: ID of the product the milestone belongs to.
  224. Params: none.
  225. Returns: The ID of a product.
  226. =item C<product()>
  227. Description: The product object of the product the milestone belongs to.
  228. Params: none.
  229. Returns: A Bugzilla::Product object.
  230. =item C<sortkey()>
  231. Description: Sortkey of the milestone.
  232. Params: none.
  233. Returns: The sortkey of the milestone.
  234. =item C<bug_count()>
  235. Description: Returns the total of bugs that belong to the milestone.
  236. Params: none.
  237. Returns: Integer with the number of bugs.
  238. =item C<set_name($new_name)>
  239. Description: Changes the name of the milestone.
  240. Params: $new_name - new name of the milestone (string). This name
  241. must be unique within the product.
  242. Returns: Nothing.
  243. =item C<set_sortkey($new_sortkey)>
  244. Description: Changes the sortkey of the milestone.
  245. Params: $new_sortkey - new sortkey of the milestone (signed integer).
  246. Returns: Nothing.
  247. =item C<update()>
  248. Description: Writes the new name and/or the new sortkey into the DB.
  249. Params: none.
  250. Returns: A hashref with changes made to the milestone object.
  251. =item C<remove_from_db()>
  252. Description: Deletes the current milestone from the DB. The object itself
  253. is not destroyed.
  254. Params: none.
  255. Returns: Nothing.
  256. =back
  257. =head1 CLASS METHODS
  258. =over
  259. =item C<create({name => $name, product => $product, sortkey => $sortkey})>
  260. Description: Create a new milestone for the given product.
  261. Params: $name - name of the new milestone (string). This name
  262. must be unique within the product.
  263. $product - a Bugzilla::Product object.
  264. $sortkey - the sortkey of the new milestone (signed integer)
  265. Returns: A Bugzilla::Milestone object.
  266. =back