Series.pm 9.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
21
#                 Lance Larsh <lance.larsh@oracle.com>
22 23 24 25 26

use strict;
use lib ".";

# This module implements a series - a set of data to be plotted on a chart.
27 28 29 30 31 32
#
# 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.

33 34
package Bugzilla::Series;

35
use Bugzilla::Error;
36 37 38
use Bugzilla::Util;
use Bugzilla::User;

39 40
use constant PUBLIC_USER_ID => 0;

41 42 43 44 45 46 47 48
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);

49 50
    my $arg_count = scalar(@_);
    
51 52 53 54 55
    # 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;

56 57 58
    # 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.
59
    if ($arg_count == 1) {
60
        if (ref($_[0])) {
61
            # We've been given a CGI object to create a new Series from.
62 63
            # This series may already exist - external code needs to check
            # before it calls writeToDatabase().
64
            $self->initFromCGI($_[0]);
65 66
        }
        else {
67 68
            # We've been given a series_id, which should represent an existing
            # Series.
69
            $retval = $self->initFromDatabase($_[0]);
70 71
        }
    }
72
    elsif ($arg_count >= 6 && $arg_count <= 8) {
73
        # We've been given a load of parameters to create a new Series from.
74 75
        # Currently, undef is always passed as the first parameter; this allows
        # you to call writeToDatabase() unconditionally. 
76
        $self->initFromParameters(@_);
77 78
    }
    else {
79
        die("Bad parameters passed in - invalid number of args: $arg_count");
80 81
    }

82
    return $retval;
83 84 85 86 87 88
}

sub initFromDatabase {
    my $self = shift;
    my $series_id = shift;
    
89 90
    detaint_natural($series_id) 
      || ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
91 92 93 94
    
    my $dbh = Bugzilla->dbh;
    my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
        "cc2.name, series.name, series.creator, series.frequency, " .
95
        "series.query, series.is_public " .
96 97
        "FROM series " .
        "LEFT JOIN series_categories AS cc1 " .
98
        "    ON series.category = cc1.id " .
99
        "LEFT JOIN series_categories AS cc2 " .
100 101 102 103 104 105 106 107
        "    ON series.subcategory = cc2.id " .
        "LEFT JOIN category_group_map AS cgm " .
        "    ON series.category = cgm.category_id " .
        "LEFT JOIN user_group_map AS ugm " .
        "    ON cgm.group_id = ugm.group_id " .
        "    AND ugm.user_id = " . Bugzilla->user->id .
        "    AND isbless = 0 " .
        "WHERE series.series_id = $series_id AND " .
108
        "(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
109
        "(ugm.group_id IS NOT NULL)) " . 
110 111
        $dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
                           'series.name, series.creator, series.frequency, ' .
112
                           'series.query, series.is_public'));
113 114 115
    
    if (@series) {
        $self->initFromParameters(@series);
116
        return $self;
117 118
    }
    else {
119
        return undef;
120 121 122 123
    }
}

sub initFromParameters {
124
    # Pass undef as the first parameter if you are creating a new series.
125 126 127 128
    my $self = shift;

    ($self->{'series_id'}, $self->{'category'},  $self->{'subcategory'},
     $self->{'name'}, $self->{'creator'}, $self->{'frequency'},
129
     $self->{'query'}, $self->{'public'}) = @_;
130 131
}

132 133 134 135 136 137 138
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'})
139
          || ThrowCodeError("invalid_series_id", 
140 141 142 143 144
                               { 'series_id' => $self->{'series_id'} });
    }
    
    $self->{'category'} = $cgi->param('category')
      || $cgi->param('newcategory')
145
      || ThrowUserError("missing_category");
146 147 148

    $self->{'subcategory'} = $cgi->param('subcategory')
      || $cgi->param('newsubcategory')
149
      || ThrowUserError("missing_subcategory");
150 151

    $self->{'name'} = $cgi->param('name')
152
      || ThrowUserError("missing_name");
153 154 155 156 157

    $self->{'creator'} = Bugzilla->user->id;

    $self->{'frequency'} = $cgi->param('frequency');
    detaint_natural($self->{'frequency'})
158
      || ThrowUserError("missing_frequency");
159 160 161 162

    $self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
                                        "category", "subcategory", "name",
                                        "frequency", "public", "query_format");
163
    trick_taint($self->{'query'});
164
                                        
165 166 167 168
    $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.
169
    $self->{'public'} = 0 unless UserInGroup('admin');
170 171 172
}

sub writeToDatabase {
173 174 175
    my $self = shift;

    my $dbh = Bugzilla->dbh;
176
    $dbh->bz_lock_tables('series_categories WRITE', 'series WRITE');
177 178 179 180

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

181 182 183 184 185 186 187
    my $exists;
    if ($self->{'series_id'}) { 
        $exists = 
            $dbh->selectrow_array("SELECT series_id FROM series
                                   WHERE series_id = $self->{'series_id'}");
    }
    
188
    # Is this already in the database?                              
189
    if ($exists) {
190 191 192 193
        # Update existing series
        my $dbh = Bugzilla->dbh;
        $dbh->do("UPDATE series SET " .
                 "category = ?, subcategory = ?," .
194
                 "name = ?, frequency = ?, is_public = ?  " .
195 196
                 "WHERE series_id = ?", undef,
                 $category_id, $subcategory_id, $self->{'name'},
197 198
                 $self->{'frequency'}, $self->{'public'}, 
                 $self->{'series_id'});
199 200 201 202
    }
    else {
        # Insert the new series into the series table
        $dbh->do("INSERT INTO series (creator, category, subcategory, " .
203
                 "name, frequency, query, is_public) VALUES " . 
204
                 "($self->{'creator'}, " . 
205 206
                 "$category_id, $subcategory_id, " .
                 $dbh->quote($self->{'name'}) . ", $self->{'frequency'}," .
207
                 $dbh->quote($self->{'query'}) . ", $self->{'public'})");
208 209 210 211 212

        # Retrieve series_id
        $self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
                                                     "FROM series");
        $self->{'series_id'}
213
          || ThrowCodeError("missing_series_id", { 'series' => $self });
214 215
    }
    
216
    $dbh->bz_unlock_tables();
217 218
}

219
# Check whether a series with this name, category and subcategory exists in
220
# the DB and, if so, returns its series_id.
221 222 223 224 225 226 227 228
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'});
229
    my $series_id = $dbh->selectrow_array("SELECT series_id " .
230 231 232 233
                              "FROM series WHERE category = $category_id " .
                              "AND subcategory = $subcategory_id AND name = " .
                              $dbh->quote($self->{'name'}));
                              
234
    return($series_id);
235 236
}

237 238 239 240 241 242 243 244 245 246 247
# 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);

248
        $category_id = $dbh->selectrow_array("SELECT id " .
249 250
                                      "from series_categories " .
                                      "WHERE name =" . $dbh->quote($category));
251 252

        last if defined($category_id);
253 254 255 256 257 258 259 260 261

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

    return $category_id;
}

1;