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