Series.pm 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. # The Initial Developer of the Original Code is Netscape Communications
  16. # Corporation. Portions created by Netscape are
  17. # Copyright (C) 1998 Netscape Communications Corporation. All
  18. # Rights Reserved.
  19. #
  20. # Contributor(s): Gervase Markham <gerv@gerv.net>
  21. # Lance Larsh <lance.larsh@oracle.com>
  22. use strict;
  23. # This module implements a series - a set of data to be plotted on a chart.
  24. #
  25. # This Series is in the database if and only if self->{'series_id'} is defined.
  26. # Note that the series being in the database does not mean that the fields of
  27. # this object are the same as the DB entries, as the object may have been
  28. # altered.
  29. package Bugzilla::Series;
  30. use Bugzilla::Error;
  31. use Bugzilla::Util;
  32. use Bugzilla::User;
  33. sub new {
  34. my $invocant = shift;
  35. my $class = ref($invocant) || $invocant;
  36. # Create a ref to an empty hash and bless it
  37. my $self = {};
  38. bless($self, $class);
  39. my $arg_count = scalar(@_);
  40. # new() can return undef if you pass in a series_id and the user doesn't
  41. # have sufficient permissions. If you create a new series in this way,
  42. # you need to check for an undef return, and act appropriately.
  43. my $retval = $self;
  44. # There are three ways of creating Series objects. Two (CGI and Parameters)
  45. # are for use when creating a new series. One (Database) is for retrieving
  46. # information on existing series.
  47. if ($arg_count == 1) {
  48. if (ref($_[0])) {
  49. # We've been given a CGI object to create a new Series from.
  50. # This series may already exist - external code needs to check
  51. # before it calls writeToDatabase().
  52. $self->initFromCGI($_[0]);
  53. }
  54. else {
  55. # We've been given a series_id, which should represent an existing
  56. # Series.
  57. $retval = $self->initFromDatabase($_[0]);
  58. }
  59. }
  60. elsif ($arg_count >= 6 && $arg_count <= 8) {
  61. # We've been given a load of parameters to create a new Series from.
  62. # Currently, undef is always passed as the first parameter; this allows
  63. # you to call writeToDatabase() unconditionally.
  64. $self->initFromParameters(@_);
  65. }
  66. else {
  67. die("Bad parameters passed in - invalid number of args: $arg_count");
  68. }
  69. return $retval;
  70. }
  71. sub initFromDatabase {
  72. my $self = shift;
  73. my $series_id = shift;
  74. detaint_natural($series_id)
  75. || ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
  76. my $dbh = Bugzilla->dbh;
  77. my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
  78. "cc2.name, series.name, series.creator, series.frequency, " .
  79. "series.query, series.is_public " .
  80. "FROM series " .
  81. "LEFT JOIN series_categories AS cc1 " .
  82. " ON series.category = cc1.id " .
  83. "LEFT JOIN series_categories AS cc2 " .
  84. " ON series.subcategory = cc2.id " .
  85. "LEFT JOIN category_group_map AS cgm " .
  86. " ON series.category = cgm.category_id " .
  87. "LEFT JOIN user_group_map AS ugm " .
  88. " ON cgm.group_id = ugm.group_id " .
  89. " AND ugm.user_id = " . Bugzilla->user->id .
  90. " AND isbless = 0 " .
  91. "WHERE series.series_id = $series_id AND " .
  92. "(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
  93. "(ugm.group_id IS NOT NULL)) " .
  94. $dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
  95. 'series.name, series.creator, series.frequency, ' .
  96. 'series.query, series.is_public'));
  97. if (@series) {
  98. $self->initFromParameters(@series);
  99. return $self;
  100. }
  101. else {
  102. return undef;
  103. }
  104. }
  105. sub initFromParameters {
  106. # Pass undef as the first parameter if you are creating a new series.
  107. my $self = shift;
  108. ($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
  109. $self->{'name'}, $self->{'creator'}, $self->{'frequency'},
  110. $self->{'query'}, $self->{'public'}) = @_;
  111. # If the first parameter is undefined, check if this series already
  112. # exists and update it series_id accordingly
  113. $self->{'series_id'} ||= $self->existsInDatabase();
  114. }
  115. sub initFromCGI {
  116. my $self = shift;
  117. my $cgi = shift;
  118. $self->{'series_id'} = $cgi->param('series_id') || undef;
  119. if (defined($self->{'series_id'})) {
  120. detaint_natural($self->{'series_id'})
  121. || ThrowCodeError("invalid_series_id",
  122. { 'series_id' => $self->{'series_id'} });
  123. }
  124. $self->{'category'} = $cgi->param('category')
  125. || $cgi->param('newcategory')
  126. || ThrowUserError("missing_category");
  127. $self->{'subcategory'} = $cgi->param('subcategory')
  128. || $cgi->param('newsubcategory')
  129. || ThrowUserError("missing_subcategory");
  130. $self->{'name'} = $cgi->param('name')
  131. || ThrowUserError("missing_name");
  132. $self->{'creator'} = Bugzilla->user->id;
  133. $self->{'frequency'} = $cgi->param('frequency');
  134. detaint_natural($self->{'frequency'})
  135. || ThrowUserError("missing_frequency");
  136. $self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
  137. "category", "subcategory", "name",
  138. "frequency", "public", "query_format");
  139. trick_taint($self->{'query'});
  140. $self->{'public'} = $cgi->param('public') ? 1 : 0;
  141. # Change 'admin' here and in series.html.tmpl, or remove the check
  142. # completely, if you want to change who can make series public.
  143. $self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
  144. }
  145. sub writeToDatabase {
  146. my $self = shift;
  147. my $dbh = Bugzilla->dbh;
  148. $dbh->bz_start_transaction();
  149. my $category_id = getCategoryID($self->{'category'});
  150. my $subcategory_id = getCategoryID($self->{'subcategory'});
  151. my $exists;
  152. if ($self->{'series_id'}) {
  153. $exists =
  154. $dbh->selectrow_array("SELECT series_id FROM series
  155. WHERE series_id = $self->{'series_id'}");
  156. }
  157. # Is this already in the database?
  158. if ($exists) {
  159. # Update existing series
  160. my $dbh = Bugzilla->dbh;
  161. $dbh->do("UPDATE series SET " .
  162. "category = ?, subcategory = ?," .
  163. "name = ?, frequency = ?, is_public = ? " .
  164. "WHERE series_id = ?", undef,
  165. $category_id, $subcategory_id, $self->{'name'},
  166. $self->{'frequency'}, $self->{'public'},
  167. $self->{'series_id'});
  168. }
  169. else {
  170. # Insert the new series into the series table
  171. $dbh->do("INSERT INTO series (creator, category, subcategory, " .
  172. "name, frequency, query, is_public) VALUES " .
  173. "(?, ?, ?, ?, ?, ?, ?)", undef,
  174. $self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
  175. $self->{'frequency'}, $self->{'query'}, $self->{'public'});
  176. # Retrieve series_id
  177. $self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
  178. "FROM series");
  179. $self->{'series_id'}
  180. || ThrowCodeError("missing_series_id", { 'series' => $self });
  181. }
  182. $dbh->bz_commit_transaction();
  183. }
  184. # Check whether a series with this name, category and subcategory exists in
  185. # the DB and, if so, returns its series_id.
  186. sub existsInDatabase {
  187. my $self = shift;
  188. my $dbh = Bugzilla->dbh;
  189. my $category_id = getCategoryID($self->{'category'});
  190. my $subcategory_id = getCategoryID($self->{'subcategory'});
  191. trick_taint($self->{'name'});
  192. my $series_id = $dbh->selectrow_array("SELECT series_id " .
  193. "FROM series WHERE category = $category_id " .
  194. "AND subcategory = $subcategory_id AND name = " .
  195. $dbh->quote($self->{'name'}));
  196. return($series_id);
  197. }
  198. # Get a category or subcategory IDs, creating the category if it doesn't exist.
  199. sub getCategoryID {
  200. my ($category) = @_;
  201. my $category_id;
  202. my $dbh = Bugzilla->dbh;
  203. # This seems for the best idiom for "Do A. Then maybe do B and A again."
  204. while (1) {
  205. # We are quoting this to put it in the DB, so we can remove taint
  206. trick_taint($category);
  207. $category_id = $dbh->selectrow_array("SELECT id " .
  208. "from series_categories " .
  209. "WHERE name =" . $dbh->quote($category));
  210. last if defined($category_id);
  211. $dbh->do("INSERT INTO series_categories (name) " .
  212. "VALUES (" . $dbh->quote($category) . ")");
  213. }
  214. return $category_id;
  215. }
  216. 1;