RSS

(root)/bmo/4.2 : /showdependencygraph.cgi (revision 8095)

To get this branch, use:
bzr branch /bmo/4.2
Line Revision Contents
1 2107
#!/usr/bin/perl -wT
2 258
# -*- Mode: perl; indent-tabs-mode: nil -*-
3
#
4 371
# The contents of this file are subject to the Mozilla Public
5
# License Version 1.1 (the "License"); you may not use this file
6
# except in compliance with the License. You may obtain a copy of
7
# the License at http://www.mozilla.org/MPL/
8
#
9
# Software distributed under the License is distributed on an "AS
10
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11
# implied. See the License for the specific language governing
12
# rights and limitations under the License.
13
#
14 258
# The Original Code is the Bugzilla Bug Tracking System.
15 371
#
16 258
# The Initial Developer of the Original Code is Netscape Communications
17 371
# Corporation. Portions created by Netscape are
18
# Copyright (C) 1998 Netscape Communications Corporation. All
19
# Rights Reserved.
20
#
21 258
# Contributor(s): Terry Weissman <terry@mozilla.org>
22 1375
#                 Gervase Markham <gerv@gerv.net>
23 258
24
use strict;
25
26 5304
use lib qw(. lib);
27 1243
28 2137
use File::Temp;
29 4296
30 2161
use Bugzilla;
31 4296
use Bugzilla::Constants;
32 7249
use Bugzilla::Install::Filesystem;
33 3027
use Bugzilla::Util;
34 4296
use Bugzilla::Error;
35 3531
use Bugzilla::Bug;
36 5285
use Bugzilla::Status;
37 2137
38 2537
Bugzilla->login();
39 899
40 2161
my $cgi = Bugzilla->cgi;
41 3762
my $template = Bugzilla->template;
42
my $vars = {};
43 2040
# Connect to the shadow database if this installation is using one to improve
44
# performance.
45 3813
my $dbh = Bugzilla->switch_to_shadow_db();
46 2040
47 4329
local our (%seen, %edgesdone, %bugtitles);
48 2355
49
# CreateImagemap: This sub grabs a local filename as a parameter, reads the 
50
# dot-generated image map datafile residing in that file and turns it into
51
# an HTML map element. THIS SUB IS ONLY USED FOR LOCAL DOT INSTALLATIONS.
52
# The map datafile won't necessarily contain the bug summaries, so we'll
53
# pull possible HTML titles from the %bugtitles hash (filled elsewhere
54
# in the code)
55
56
# The dot mapdata lines have the following format (\nsummary is optional):
57
# rectangle (LEFTX,TOPY) (RIGHTX,BOTTOMY) URLBASE/show_bug.cgi?id=BUGNUM BUGNUM[\nSUMMARY]
58 258
59 1495
sub CreateImagemap {
60
    my $mapfilename = shift;
61
    my $map = "<map name=\"imagemap\">\n";
62 7051
    my $default = "";
63 1495
64
    open MAP, "<$mapfilename";
65
    while(my $line = <MAP>) {
66
        if($line =~ /^default ([^ ]*)(.*)$/) {
67 1593
            $default = qq{<area alt="" shape="default" href="$1">\n};
68 1495
        }
69 2355
70 5065
        if ($line =~ /^rectangle \((.*),(.*)\) \((.*),(.*)\) (http[^ ]*) (\d+)(\\n.*)?$/) {
71 5061
            my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
72 2355
73
            # Pick up bugid from the mapdata label field. Getting the title from
74
            # bugtitle hash instead of mapdata allows us to get the summary even
75
            # when showsummary is off, and also gives us status and resolution.
76 5280
            my $bugtitle = html_quote(clean_text($bugtitles{$bugid}));
77 2355
            $map .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" } .
78
                    qq{title="$bugtitle" href="$url" } .
79
                    qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
80 1495
        }
81
    }
82
    close MAP;
83
84
    $map .= "$default</map>";
85
    return $map;
86
}
87
88 258
sub AddLink {
89 2137
    my ($blocked, $dependson, $fh) = (@_);
90 258
    my $key = "$blocked,$dependson";
91
    if (!exists $edgesdone{$key}) {
92
        $edgesdone{$key} = 1;
93 6707
        print $fh "$dependson -> $blocked\n";
94 258
        $seen{$blocked} = 1;
95
        $seen{$dependson} = 1;
96
    }
97
}
98
99 7467
ThrowCodeError("missing_bug_id") if !defined $cgi->param('id');
100
101 4637
# The list of valid directions. Some are not proposed in the dropdrown
102 5143
# menu despite the fact that they are valid.
103 4637
my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
104
105 5143
my $rankdir = $cgi->param('rankdir') || 'TB';
106 4637
# Make sure the submitted 'rankdir' value is valid.
107 7138
if (!grep { $_ eq $rankdir } @valid_rankdirs) {
108 5143
    $rankdir = 'TB';
109 4637
}
110
111 5017
my $display = $cgi->param('display') || 'tree';
112 4296
my $webdotdir = bz_locations()->{'webdotdir'};
113 263
114 2137
my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
115
                                           SUFFIX => '.dot',
116 7249
                                           DIR => $webdotdir,
117
                                           UNLINK => 1);
118
119
chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename
120
    or warn install_string('chmod_failed', { path => $filename,
121
                                             error => $! });
122
123 4317
my $urlbase = Bugzilla->params->{'urlbase'};
124 1375
125 2137
print $fh "digraph G {";
126
print $fh qq{
127 3578
graph [URL="${urlbase}query.cgi", rankdir=$rankdir]
128 258
node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
129
};
130 1375
131
my %baselist;
132
133 7467
foreach my $i (split('[\s,]+', $cgi->param('id'))) {
134
    my $bug = Bugzilla::Bug->check($i);
135
    $baselist{$bug->id} = 1;
136
}
137
138
my @stack = keys(%baselist);
139
140
if ($display eq 'web') {
141
    my $sth = $dbh->prepare(q{SELECT blocked, dependson
142
                                FROM dependencies
143
                               WHERE blocked = ? OR dependson = ?});
144
145
    foreach my $id (@stack) {
146
        my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
147
        foreach my $dependency (@$dependencies) {
148
            my ($blocked, $dependson) = @$dependency;
149
            if ($blocked != $id && !exists $seen{$blocked}) {
150
                push @stack, $blocked;
151
            }
152
            if ($dependson != $id && !exists $seen{$dependson}) {
153
                push @stack, $dependson;
154
            }
155
            AddLink($blocked, $dependson, $fh);
156
        }
157
    }
158
}
159
# This is the default: a tree instead of a spider web.
160
else {
161
    my @blocker_stack = @stack;
162
    foreach my $id (@blocker_stack) {
163
        my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
164
        foreach my $blocker_id (@$blocker_ids) {
165
            push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
166
            AddLink($id, $blocker_id, $fh);
167
        }
168
    }
169
    my @dependent_stack = @stack;
170
    foreach my $id (@dependent_stack) {
171
        my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
172
        foreach my $dep_bug_id (@$dep_bug_ids) {
173
            push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
174
            AddLink($dep_bug_id, $id, $fh);
175
        }
176
    }
177
}
178
179
foreach my $k (keys(%baselist)) {
180
    $seen{$k} = 1;
181 1375
}
182
183 3769
my $sth = $dbh->prepare(
184
              q{SELECT bug_status, resolution, short_desc
185
                  FROM bugs
186
                 WHERE bugs.bug_id = ?});
187 1375
foreach my $k (keys(%seen)) {
188 2355
    # Retrieve bug information from the database
189 3769
    my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
190 2355
191
    # Resolution and summary are shown only if user can see the bug
192 2748
    if (!Bugzilla->user->can_see_bug($k)) {
193 2355
        $resolution = $summary = '';
194 1375
    }
195 2355
196 3725
    $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
197 2355
198 1375
    my @params;
199
200 2533
    if ($summary ne "" && $cgi->param('showsummary')) {
201 7051
        # Wide characters cause GraphViz to die.
202
        if (Bugzilla->params->{'utf8'}) {
203
            utf8::encode($summary) if utf8::is_utf8($summary);
204
        }
205 1375
        $summary =~ s/([\\\"])/\\$1/g;
206
        push(@params, qq{label="$k\\n$summary"});
207
    }
208
209
    if (exists $baselist{$k}) {
210
        push(@params, "shape=box");
211
    }
212
213 4065
    if (is_open_state($stat)) {
214 1375
        push(@params, "color=green");
215
    }
216
217
    if (@params) {
218 2137
        print $fh "$k [" . join(',', @params) . "]\n";
219 1375
    } else {
220 2137
        print $fh "$k\n";
221 1375
    }
222 2355
223
    # Push the bug tooltip texts into a global hash so that 
224
    # CreateImagemap sub (used with local dot installations) can
225
    # use them later on.
226
    $bugtitles{$k} = trim("$stat $resolution");
227
228
    # Show the bug summary in tooltips only if not shown on 
229
    # the graph and it is non-empty (the user can see the bug)
230 2533
    if (!$cgi->param('showsummary') && $summary ne "") {
231 2355
        $bugtitles{$k} .= " - $summary";
232
    }
233 1375
}
234
235
236 2137
print $fh "}\n";
237
close $fh;
238 1375
239 4317
my $webdotbase = Bugzilla->params->{'webdotbase'};
240 1375
241
if ($webdotbase =~ /^https?:/) {
242 5129
     # Remote dot server. We don't hardcode 'urlbase' here in case
243
     # 'sslbase' is in use.
244 6268
     $webdotbase =~ s/%([a-z]*)%/Bugzilla->params->{$1}/eg;
245 5129
     my $url = $webdotbase . $filename;
246 1375
     $vars->{'image_url'} = $url . ".gif";
247
     $vars->{'map_url'} = $url . ".map";
248 258
} else {
249 1375
    # Local dot installation
250 2355
251
    # First, generate the png image file from the .dot source
252
253 2137
    my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
254
                                                     SUFFIX => '.png',
255 2347
                                                     DIR => $webdotdir);
256 7249
257
    chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
258
        or warn install_string('chmod_failed', { path => $pngfilename,
259
                                                 error => $! });
260
261 2639
    binmode $pngfh;
262 3950
    open(DOT, "\"$webdotbase\" -Tpng $filename|");
263 2639
    binmode DOT;
264 2137
    print $pngfh $_ while <DOT>;
265
    close DOT;
266
    close $pngfh;
267 7249
268 2639
    # On Windows $pngfilename will contain \ instead of /
269 6721
    $pngfilename =~ s|\\|/|g if ON_WINDOWS;
270 4741
271
    # Under mod_perl, pngfilename will have an absolute path, and we
272
    # need to make that into a relative path.
273
    my $cgi_root = bz_locations()->{cgi_path};
274 5150
    $pngfilename =~ s#^\Q$cgi_root\E/?##;
275 2639
    
276 1375
    $vars->{'image_url'} = $pngfilename;
277 2137
278 2355
    # Then, generate a imagemap datafile that contains the corner data
279
    # for drawn bug objects. Pass it on to CreateImagemap that
280
    # turns this monster into html.
281
282 2137
    my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
283
                                                     SUFFIX => '.map',
284 2347
                                                     DIR => $webdotdir);
285 7249
286
    chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
287
        or warn install_string('chmod_failed', { path => $mapfilename,
288
                                                 error => $! });
289
290 2639
    binmode $mapfh;
291 3950
    open(DOT, "\"$webdotbase\" -Tismap $filename|");
292 2639
    binmode DOT;
293 2137
    print $mapfh $_ while <DOT>;
294
    close DOT;
295
    close $mapfh;
296 7249
297 1495
    $vars->{'image_map'} = CreateImagemap($mapfilename);
298 1375
}
299
300
# Cleanup any old .dot files created from previous runs.
301
my $since = time() - 24 * 60 * 60;
302 1614
# Can't use glob, since even calling that fails taint checks for perl < 5.6
303 2347
opendir(DIR, $webdotdir);
304
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
305 1614
closedir DIR;
306
foreach my $f (@files)
307 1375
{
308 2347
    $f = "$webdotdir/$f";
309 1375
    # Here we are deleting all old files. All entries are from the
310 2347
    # $webdot directory. Since we're deleting the file (not following
311 1375
    # symlinks), this can't escape to delete anything it shouldn't
312 2347
    # (unless someone moves the location of $webdotdir, of course)
313 1375
    trick_taint($f);
314 3027
    if (file_mod_time($f) < $since) {
315 1375
        unlink $f;
316
    }
317
}
318
319 4610
# Make sure we only include valid integers (protects us from XSS attacks).
320
my @bugs = grep(detaint_natural($_), split(/[\s,]+/, $cgi->param('id')));
321
$vars->{'bug_id'} = join(', ', @bugs);
322 2533
$vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
323 5017
$vars->{'display'} = $display;
324 2533
$vars->{'rankdir'} = $rankdir;
325
$vars->{'showsummary'} = $cgi->param('showsummary');
326 1375
327
# Generate and return the UI (HTML page) from the appropriate template.
328 2161
print $cgi->header();
329 1424
$template->process("bug/dependency-graph.html.tmpl", $vars)
330
  || ThrowTemplateError($template->error());

Loggerhead 1.18.1 is a web-based interface for Bazaar branches