Series.pm 9.46 KB
Newer Older
1 2 3
# 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/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8

# This module implements a series - a set of data to be plotted on a chart.
9 10 11 12 13 14
#
# This Series is in the database if and only if self->{'series_id'} is defined. 
# Note that the series being in the database does not mean that the fields of 
# this object are the same as the DB entries, as the object may have been 
# altered.

15 16
package Bugzilla::Series;

17 18 19
use 5.10.1;
use strict;

20
use Bugzilla::Error;
21 22
use Bugzilla::Util;

23 24 25 26 27
# This is a hack so that we can re-use the rename_field_value
# code from Bugzilla::Search::Saved.
use constant DB_TABLE => 'series';
use constant ID_FIELD => 'series_id';

28 29 30 31 32 33 34 35
sub new {
    my $invocant = shift;
    my $class = ref($invocant) || $invocant;
  
    # Create a ref to an empty hash and bless it
    my $self = {};
    bless($self, $class);

36 37
    my $arg_count = scalar(@_);
    
38 39 40 41 42
    # new() can return undef if you pass in a series_id and the user doesn't 
    # have sufficient permissions. If you create a new series in this way,
    # you need to check for an undef return, and act appropriately.
    my $retval = $self;

43 44 45
    # There are three ways of creating Series objects. Two (CGI and Parameters)
    # are for use when creating a new series. One (Database) is for retrieving
    # information on existing series.
46
    if ($arg_count == 1) {
47
        if (ref($_[0])) {
48
            # We've been given a CGI object to create a new Series from.
49 50
            # This series may already exist - external code needs to check
            # before it calls writeToDatabase().
51
            $self->initFromCGI($_[0]);
52 53
        }
        else {
54 55
            # We've been given a series_id, which should represent an existing
            # Series.
56
            $retval = $self->initFromDatabase($_[0]);
57 58
        }
    }
59
    elsif ($arg_count >= 6 && $arg_count <= 8) {
60
        # We've been given a load of parameters to create a new Series from.
61
        # Currently, undef is always passed as the first parameter; this allows
62 63
        # you to call writeToDatabase() unconditionally.
        # XXX - You cannot set category_id and subcategory_id from here.
64
        $self->initFromParameters(@_);
65 66
    }
    else {
67
        die("Bad parameters passed in - invalid number of args: $arg_count");
68 69
    }

70
    return $retval;
71 72 73
}

sub initFromDatabase {
74 75 76 77
    my ($self, $series_id) = @_;
    my $dbh = Bugzilla->dbh;
    my $user = Bugzilla->user;

78 79
    detaint_natural($series_id) 
      || ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
80 81 82

    my $grouplist = $user->groups_as_string;

83 84
    my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
        "cc2.name, series.name, series.creator, series.frequency, " .
85
        "series.query, series.is_public, series.category, series.subcategory " .
86
        "FROM series " .
87
        "INNER JOIN series_categories AS cc1 " .
88
        "    ON series.category = cc1.id " .
89
        "INNER JOIN series_categories AS cc2 " .
90 91 92
        "    ON series.subcategory = cc2.id " .
        "LEFT JOIN category_group_map AS cgm " .
        "    ON series.category = cgm.category_id " .
93 94 95 96 97
        "    AND cgm.group_id NOT IN($grouplist) " .
        "WHERE series.series_id = ? " .
        "    AND (creator = ? OR (is_public = 1 AND cgm.category_id IS NULL))",
        undef, ($series_id, $user->id));

98 99
    if (@series) {
        $self->initFromParameters(@series);
100
        return $self;
101 102
    }
    else {
103
        return undef;
104 105 106 107
    }
}

sub initFromParameters {
108
    # Pass undef as the first parameter if you are creating a new series.
109 110 111
    my $self = shift;

    ($self->{'series_id'}, $self->{'category'},  $self->{'subcategory'},
112 113 114
     $self->{'name'}, $self->{'creator_id'}, $self->{'frequency'},
     $self->{'query'}, $self->{'public'}, $self->{'category_id'},
     $self->{'subcategory_id'}) = @_;
115 116 117 118

    # If the first parameter is undefined, check if this series already
    # exists and update it series_id accordingly
    $self->{'series_id'} ||= $self->existsInDatabase();
119 120
}

121 122 123 124 125 126 127
sub initFromCGI {
    my $self = shift;
    my $cgi = shift;

    $self->{'series_id'} = $cgi->param('series_id') || undef;
    if (defined($self->{'series_id'})) {
        detaint_natural($self->{'series_id'})
128
          || ThrowCodeError("invalid_series_id", 
129 130 131 132 133
                               { 'series_id' => $self->{'series_id'} });
    }
    
    $self->{'category'} = $cgi->param('category')
      || $cgi->param('newcategory')
134
      || ThrowUserError("missing_category");
135 136 137

    $self->{'subcategory'} = $cgi->param('subcategory')
      || $cgi->param('newsubcategory')
138
      || ThrowUserError("missing_subcategory");
139 140

    $self->{'name'} = $cgi->param('name')
141
      || ThrowUserError("missing_name");
142

143
    $self->{'creator_id'} = Bugzilla->user->id;
144 145 146

    $self->{'frequency'} = $cgi->param('frequency');
    detaint_natural($self->{'frequency'})
147
      || ThrowUserError("missing_frequency");
148 149 150 151

    $self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
                                        "category", "subcategory", "name",
                                        "frequency", "public", "query_format");
152
    trick_taint($self->{'query'});
153
                                        
154 155 156 157
    $self->{'public'} = $cgi->param('public') ? 1 : 0;
    
    # Change 'admin' here and in series.html.tmpl, or remove the check
    # completely, if you want to change who can make series public.
158
    $self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
159 160 161
}

sub writeToDatabase {
162 163 164
    my $self = shift;

    my $dbh = Bugzilla->dbh;
165
    $dbh->bz_start_transaction();
166 167 168 169

    my $category_id = getCategoryID($self->{'category'});
    my $subcategory_id = getCategoryID($self->{'subcategory'});

170 171 172 173 174 175 176
    my $exists;
    if ($self->{'series_id'}) { 
        $exists = 
            $dbh->selectrow_array("SELECT series_id FROM series
                                   WHERE series_id = $self->{'series_id'}");
    }
    
177
    # Is this already in the database?                              
178
    if ($exists) {
179 180 181 182
        # Update existing series
        my $dbh = Bugzilla->dbh;
        $dbh->do("UPDATE series SET " .
                 "category = ?, subcategory = ?," .
183
                 "name = ?, frequency = ?, is_public = ?  " .
184 185
                 "WHERE series_id = ?", undef,
                 $category_id, $subcategory_id, $self->{'name'},
186 187
                 $self->{'frequency'}, $self->{'public'}, 
                 $self->{'series_id'});
188 189 190 191
    }
    else {
        # Insert the new series into the series table
        $dbh->do("INSERT INTO series (creator, category, subcategory, " .
192
                 "name, frequency, query, is_public) VALUES " . 
193
                 "(?, ?, ?, ?, ?, ?, ?)", undef,
194
                 $self->{'creator_id'}, $category_id, $subcategory_id, $self->{'name'},
195
                 $self->{'frequency'}, $self->{'query'}, $self->{'public'});
196 197 198 199 200

        # Retrieve series_id
        $self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
                                                     "FROM series");
        $self->{'series_id'}
201
          || ThrowCodeError("missing_series_id", { 'series' => $self });
202 203
    }
    
204
    $dbh->bz_commit_transaction();
205 206
}

207
# Check whether a series with this name, category and subcategory exists in
208
# the DB and, if so, returns its series_id.
209 210 211 212 213 214 215 216
sub existsInDatabase {
    my $self = shift;
    my $dbh = Bugzilla->dbh;

    my $category_id = getCategoryID($self->{'category'});
    my $subcategory_id = getCategoryID($self->{'subcategory'});
    
    trick_taint($self->{'name'});
217
    my $series_id = $dbh->selectrow_array("SELECT series_id " .
218 219 220 221
                              "FROM series WHERE category = $category_id " .
                              "AND subcategory = $subcategory_id AND name = " .
                              $dbh->quote($self->{'name'}));
                              
222
    return($series_id);
223 224
}

225 226 227 228 229 230 231 232 233 234 235
# Get a category or subcategory IDs, creating the category if it doesn't exist.
sub getCategoryID {
    my ($category) = @_;
    my $category_id;
    my $dbh = Bugzilla->dbh;

    # This seems for the best idiom for "Do A. Then maybe do B and A again."
    while (1) {
        # We are quoting this to put it in the DB, so we can remove taint
        trick_taint($category);

236
        $category_id = $dbh->selectrow_array("SELECT id " .
237 238
                                      "from series_categories " .
                                      "WHERE name =" . $dbh->quote($category));
239 240

        last if defined($category_id);
241 242 243 244 245 246 247 248

        $dbh->do("INSERT INTO series_categories (name) " .
                 "VALUES (" . $dbh->quote($category) . ")");
    }

    return $category_id;
}

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
##########
# Methods
##########
sub id   { return $_[0]->{'series_id'}; }
sub name { return $_[0]->{'name'}; }

sub creator {
    my $self = shift;

    if (!$self->{creator} && $self->{creator_id}) {
        require Bugzilla::User;
        $self->{creator} = new Bugzilla::User($self->{creator_id});
    }
    return $self->{creator};
}

sub remove_from_db {
    my $self = shift;
    my $dbh = Bugzilla->dbh;

    $dbh->do('DELETE FROM series WHERE series_id = ?', undef, $self->id);
}

272
1;
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

=head1 B<Methods in need of POD>

=over

=item creator

=item existsInDatabase

=item name

=item getCategoryID

=item initFromParameters

=item initFromCGI

=item initFromDatabase

=item remove_from_db

=item writeToDatabase

=item id

=back