summaryrefslogtreecommitdiffstats
path: root/showdependencygraph.cgi
diff options
context:
space:
mode:
Diffstat (limited to 'showdependencygraph.cgi')
-rwxr-xr-xshowdependencygraph.cgi405
1 files changed, 208 insertions, 197 deletions
diff --git a/showdependencygraph.cgi b/showdependencygraph.cgi
index c99d45394..1a84e004a 100755
--- a/showdependencygraph.cgi
+++ b/showdependencygraph.cgi
@@ -24,9 +24,10 @@ use Bugzilla::Status;
Bugzilla->login();
-my $cgi = Bugzilla->cgi;
+my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
-my $vars = {};
+my $vars = {};
+
# Connect to the shadow database if this installation is using one to improve
# performance.
my $dbh = Bugzilla->switch_to_shadow_db();
@@ -46,50 +47,53 @@ my $bug_count = 0;
# force this to be lexical, so it can close over %bugtitles
my $CreateImagemap = sub {
- my $mapfilename = shift;
- my $map = "<map name=\"imagemap\">\n";
- my $default = "";
-
- open MAP, "<", $mapfilename;
- while(my $line = <MAP>) {
- if($line =~ /^default ([^ ]*)(.*)$/) {
- $default = qq{<area alt="" shape="default" href="$1">\n};
- }
-
- if ($line =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) (http[^ ]*) (\d+)(?:\\n.*)?$/) {
- my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
-
- # Pick up bugid from the mapdata label field. Getting the title from
- # bugtitle hash instead of mapdata allows us to get the summary even
- # when showsummary is off, and also gives us status and resolution.
- # This text is safe; it has already been escaped.
- my $bugtitle = $bugtitles{$bugid};
-
- # The URL is supposed to be safe, because it's built manually.
- # But in case someone manages to inject code, it's safer to escape it.
- $url = html_quote($url);
-
- $map .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" } .
- qq{title="$bugtitle" href="$url" } .
- qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
- }
+ my $mapfilename = shift;
+ my $map = "<map name=\"imagemap\">\n";
+ my $default = "";
+
+ open MAP, "<", $mapfilename;
+ while (my $line = <MAP>) {
+ if ($line =~ /^default ([^ ]*)(.*)$/) {
+ $default = qq{<area alt="" shape="default" href="$1">\n};
+ }
+
+ if ($line
+ =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) (http[^ ]*) (\d+)(?:\\n.*)?$/)
+ {
+ my ($leftx, $rightx, $topy, $bottomy, $url, $bugid) = ($1, $3, $2, $4, $5, $6);
+
+ # Pick up bugid from the mapdata label field. Getting the title from
+ # bugtitle hash instead of mapdata allows us to get the summary even
+ # when showsummary is off, and also gives us status and resolution.
+ # This text is safe; it has already been escaped.
+ my $bugtitle = $bugtitles{$bugid};
+
+ # The URL is supposed to be safe, because it's built manually.
+ # But in case someone manages to inject code, it's safer to escape it.
+ $url = html_quote($url);
+
+ $map
+ .= qq{<area alt="bug $bugid" name="bug$bugid" shape="rect" }
+ . qq{title="$bugtitle" href="$url" }
+ . qq{coords="$leftx,$topy,$rightx,$bottomy">\n};
}
- close MAP;
+ }
+ close MAP;
- $map .= "$default</map>";
- return $map;
+ $map .= "$default</map>";
+ return $map;
};
my $AddLink = sub {
- my ($blocked, $dependson, $fh) = (@_);
- my $key = "$blocked,$dependson";
- if (!exists $edgesdone{$key}) {
- $edgesdone{$key} = 1;
- print $fh "$dependson -> $blocked\n";
- $bug_count++;
- $seen{$blocked} = 1;
- $seen{$dependson} = 1;
- }
+ my ($blocked, $dependson, $fh) = (@_);
+ my $key = "$blocked,$dependson";
+ if (!exists $edgesdone{$key}) {
+ $edgesdone{$key} = 1;
+ print $fh "$dependson -> $blocked\n";
+ $bug_count++;
+ $seen{$blocked} = 1;
+ $seen{$dependson} = 1;
+ }
};
ThrowCodeError("missing_bug_id") if !defined $cgi->param('id');
@@ -99,22 +103,24 @@ ThrowCodeError("missing_bug_id") if !defined $cgi->param('id');
my @valid_rankdirs = ('LR', 'RL', 'TB', 'BT');
my $rankdir = $cgi->param('rankdir') || 'TB';
+
# Make sure the submitted 'rankdir' value is valid.
if (!grep { $_ eq $rankdir } @valid_rankdirs) {
- $rankdir = 'TB';
+ $rankdir = 'TB';
}
my $display = $cgi->param('display') || 'tree';
my $webdotdir = bz_locations()->{'webdotdir'};
-my ($fh, $filename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.dot',
- DIR => $webdotdir,
- UNLINK => 1);
+my ($fh, $filename) = File::Temp::tempfile(
+ "XXXXXXXXXX",
+ SUFFIX => '.dot',
+ DIR => $webdotdir,
+ UNLINK => 1
+);
chmod Bugzilla::Install::Filesystem::CGI_WRITE, $filename
- or warn install_string('chmod_failed', { path => $filename,
- error => $! });
+ or warn install_string('chmod_failed', {path => $filename, error => $!});
my $urlbase = Bugzilla->localconfig->{urlbase};
@@ -127,110 +133,117 @@ node [URL="${urlbase}show_bug.cgi?id=\\N", style=filled, color=lightgrey]
my %baselist;
foreach my $i (split('[\s,]+', $cgi->param('id'))) {
- my $bug = Bugzilla::Bug->check($i);
- $baselist{$bug->id} = 1;
+ my $bug = Bugzilla::Bug->check($i);
+ $baselist{$bug->id} = 1;
}
my @stack = keys(%baselist);
if ($display eq 'web') {
- my $sth = $dbh->prepare(q{SELECT blocked, dependson
+ my $sth = $dbh->prepare(
+ q{SELECT blocked, dependson
FROM dependencies
- WHERE blocked = ? OR dependson = ?});
-
- foreach my $id (@stack) {
- my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
- foreach my $dependency (@$dependencies) {
- my ($blocked, $dependson) = @$dependency;
- if ($blocked != $id && !exists $seen{$blocked}) {
- push @stack, $blocked;
- }
- if ($dependson != $id && !exists $seen{$dependson}) {
- push @stack, $dependson;
- }
- $AddLink->($blocked, $dependson, $fh);
- }
+ WHERE blocked = ? OR dependson = ?}
+ );
+
+ foreach my $id (@stack) {
+ my $dependencies = $dbh->selectall_arrayref($sth, undef, ($id, $id));
+ foreach my $dependency (@$dependencies) {
+ my ($blocked, $dependson) = @$dependency;
+ if ($blocked != $id && !exists $seen{$blocked}) {
+ push @stack, $blocked;
+ }
+ if ($dependson != $id && !exists $seen{$dependson}) {
+ push @stack, $dependson;
+ }
+ $AddLink->($blocked, $dependson, $fh);
}
+ }
}
+
# This is the default: a tree instead of a spider web.
else {
- my @blocker_stack = @stack;
- foreach my $id (@blocker_stack) {
- my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
- foreach my $blocker_id (@$blocker_ids) {
- push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
- $AddLink->($id, $blocker_id, $fh);
- }
+ my @blocker_stack = @stack;
+ foreach my $id (@blocker_stack) {
+ my $blocker_ids = Bugzilla::Bug::EmitDependList('blocked', 'dependson', $id);
+ foreach my $blocker_id (@$blocker_ids) {
+ push(@blocker_stack, $blocker_id) unless $seen{$blocker_id};
+ $AddLink->($id, $blocker_id, $fh);
}
- my @dependent_stack = @stack;
- foreach my $id (@dependent_stack) {
- my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
- foreach my $dep_bug_id (@$dep_bug_ids) {
- push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
- $AddLink->($dep_bug_id, $id, $fh);
- }
+ }
+ my @dependent_stack = @stack;
+ foreach my $id (@dependent_stack) {
+ my $dep_bug_ids = Bugzilla::Bug::EmitDependList('dependson', 'blocked', $id);
+ foreach my $dep_bug_id (@$dep_bug_ids) {
+ push(@dependent_stack, $dep_bug_id) unless $seen{$dep_bug_id};
+ $AddLink->($dep_bug_id, $id, $fh);
}
+ }
}
foreach my $k (keys(%baselist)) {
- $seen{$k} = 1;
+ $seen{$k} = 1;
}
my $sth = $dbh->prepare(
- q{SELECT bug_status, resolution, short_desc
+ q{SELECT bug_status, resolution, short_desc
FROM bugs
- WHERE bugs.bug_id = ?});
+ WHERE bugs.bug_id = ?}
+);
foreach my $k (keys(%seen)) {
- # Retrieve bug information from the database
- my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
- $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
+ # Retrieve bug information from the database
+ my ($stat, $resolution, $summary) = $dbh->selectrow_array($sth, undef, $k);
- # Resolution and summary are shown only if user can see the bug
- if (Bugzilla->user->can_see_bug($k)) {
- $summary = html_quote(clean_text($summary));
- }
- else {
- $resolution = $summary = '';
- }
-
- my @params;
-
- if ($summary ne "" && $cgi->param('showsummary')) {
- # Wide characters cause GraphViz to die.
- if (Bugzilla->params->{'utf8'}) {
- utf8::encode($summary) if utf8::is_utf8($summary);
- }
- $summary = wrap_comment($summary);
- $summary =~ s/([\\\"])/\\$1/g;
- $summary =~ s/\n/\\n/g;
- push(@params, qq{label="$k\\n$summary"});
- }
+ $vars->{'short_desc'} = $summary if ($k eq $cgi->param('id'));
- if (exists $baselist{$k}) {
- push(@params, "shape=box");
- }
-
- if (is_open_state($stat)) {
- push(@params, "color=green");
- }
+ # Resolution and summary are shown only if user can see the bug
+ if (Bugzilla->user->can_see_bug($k)) {
+ $summary = html_quote(clean_text($summary));
+ }
+ else {
+ $resolution = $summary = '';
+ }
- if (@params) {
- print $fh "$k [" . join(',', @params) . "]\n";
- } else {
- print $fh "$k\n";
- }
+ my @params;
- # Push the bug tooltip texts into a global hash so that
- # $CreateImagemap sub (used with local dot installations) can
- # use them later on.
- $bugtitles{$k} = trim("$stat $resolution");
+ if ($summary ne "" && $cgi->param('showsummary')) {
- # Show the bug summary in tooltips only if not shown on
- # the graph and it is non-empty (the user can see the bug)
- if (!$cgi->param('showsummary') && $summary ne "") {
- $bugtitles{$k} .= " - $summary";
+ # Wide characters cause GraphViz to die.
+ if (Bugzilla->params->{'utf8'}) {
+ utf8::encode($summary) if utf8::is_utf8($summary);
}
+ $summary = wrap_comment($summary);
+ $summary =~ s/([\\\"])/\\$1/g;
+ $summary =~ s/\n/\\n/g;
+ push(@params, qq{label="$k\\n$summary"});
+ }
+
+ if (exists $baselist{$k}) {
+ push(@params, "shape=box");
+ }
+
+ if (is_open_state($stat)) {
+ push(@params, "color=green");
+ }
+
+ if (@params) {
+ print $fh "$k [" . join(',', @params) . "]\n";
+ }
+ else {
+ print $fh "$k\n";
+ }
+
+ # Push the bug tooltip texts into a global hash so that
+ # $CreateImagemap sub (used with local dot installations) can
+ # use them later on.
+ $bugtitles{$k} = trim("$stat $resolution");
+
+ # Show the bug summary in tooltips only if not shown on
+ # the graph and it is non-empty (the user can see the bug)
+ if (!$cgi->param('showsummary') && $summary ne "") {
+ $bugtitles{$k} .= " - $summary";
+ }
}
@@ -238,96 +251,94 @@ print $fh "}\n";
close $fh;
if ($bug_count > MAX_WEBDOT_BUGS) {
- unlink($filename);
- ThrowUserError("webdot_too_large");
+ unlink($filename);
+ ThrowUserError("webdot_too_large");
}
my $webdotbase = Bugzilla->params->{'webdotbase'};
if ($webdotbase =~ /^https?:/) {
- $webdotbase =~ s/%(?:sslbase|urlbase)%/Bugzilla->localconfig->{urlbase}/eg;
- my $url = $webdotbase . $filename;
- $vars->{'image_url'} = $url . ".gif";
- $vars->{'map_url'} = $url . ".map";
-} else {
- # Local dot installation
-
- # First, generate the png image file from the .dot source
-
- my ($pngfh, $pngfilename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.png',
- DIR => $webdotdir);
-
- chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
- or warn install_string('chmod_failed', { path => $pngfilename,
- error => $! });
-
- binmode $pngfh;
- open(DOT, '-|', "\"$webdotbase\" -Tpng $filename");
- binmode DOT;
- print $pngfh $_ while <DOT>;
- close DOT;
- close $pngfh;
-
- # On Windows $pngfilename will contain \ instead of /
- $pngfilename =~ s|\\|/|g if ON_WINDOWS;
-
- # Under mod_perl, pngfilename will have an absolute path, and we
- # need to make that into a relative path.
- my $cgi_root = bz_locations()->{cgi_path};
- $pngfilename =~ s#^\Q$cgi_root\E/?##;
-
- $vars->{'image_url'} = $pngfilename;
-
- # Then, generate a imagemap datafile that contains the corner data
- # for drawn bug objects. Pass it on to $CreateImagemap that
- # turns this monster into html.
-
- my ($mapfh, $mapfilename) = File::Temp::tempfile("XXXXXXXXXX",
- SUFFIX => '.map',
- DIR => $webdotdir);
-
- chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
- or warn install_string('chmod_failed', { path => $mapfilename,
- error => $! });
-
- binmode $mapfh;
- open(DOT, '-|', "\"$webdotbase\" -Tismap $filename");
- binmode DOT;
- print $mapfh $_ while <DOT>;
- close DOT;
- close $mapfh;
-
- $vars->{'image_map'} = $CreateImagemap->($mapfilename);
+ $webdotbase =~ s/%(?:sslbase|urlbase)%/Bugzilla->localconfig->{urlbase}/eg;
+ my $url = $webdotbase . $filename;
+ $vars->{'image_url'} = $url . ".gif";
+ $vars->{'map_url'} = $url . ".map";
+}
+else {
+ # Local dot installation
+
+ # First, generate the png image file from the .dot source
+
+ my ($pngfh, $pngfilename)
+ = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.png', DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $pngfilename
+ or warn install_string('chmod_failed', {path => $pngfilename, error => $!});
+
+ binmode $pngfh;
+ open(DOT, '-|', "\"$webdotbase\" -Tpng $filename");
+ binmode DOT;
+ print $pngfh $_ while <DOT>;
+ close DOT;
+ close $pngfh;
+
+ # On Windows $pngfilename will contain \ instead of /
+ $pngfilename =~ s|\\|/|g if ON_WINDOWS;
+
+ # Under mod_perl, pngfilename will have an absolute path, and we
+ # need to make that into a relative path.
+ my $cgi_root = bz_locations()->{cgi_path};
+ $pngfilename =~ s#^\Q$cgi_root\E/?##;
+
+ $vars->{'image_url'} = $pngfilename;
+
+ # Then, generate a imagemap datafile that contains the corner data
+ # for drawn bug objects. Pass it on to $CreateImagemap that
+ # turns this monster into html.
+
+ my ($mapfh, $mapfilename)
+ = File::Temp::tempfile("XXXXXXXXXX", SUFFIX => '.map', DIR => $webdotdir);
+
+ chmod Bugzilla::Install::Filesystem::WS_SERVE, $mapfilename
+ or warn install_string('chmod_failed', {path => $mapfilename, error => $!});
+
+ binmode $mapfh;
+ open(DOT, '-|', "\"$webdotbase\" -Tismap $filename");
+ binmode DOT;
+ print $mapfh $_ while <DOT>;
+ close DOT;
+ close $mapfh;
+
+ $vars->{'image_map'} = $CreateImagemap->($mapfilename);
}
# Cleanup any old .dot files created from previous runs.
my $since = time() - 24 * 60 * 60;
+
# Can't use glob, since even calling that fails taint checks for perl < 5.6
opendir(DIR, $webdotdir);
my @files = grep { /\.dot$|\.png$|\.map$/ && -f "$webdotdir/$_" } readdir(DIR);
closedir DIR;
-foreach my $f (@files)
-{
- $f = "$webdotdir/$f";
- # Here we are deleting all old files. All entries are from the
- # $webdot directory. Since we're deleting the file (not following
- # symlinks), this can't escape to delete anything it shouldn't
- # (unless someone moves the location of $webdotdir, of course)
- trick_taint($f);
- my $mtime = file_mod_time($f);
- if ($mtime && $mtime < $since) {
- unlink $f;
- }
+foreach my $f (@files) {
+ $f = "$webdotdir/$f";
+
+ # Here we are deleting all old files. All entries are from the
+ # $webdot directory. Since we're deleting the file (not following
+ # symlinks), this can't escape to delete anything it shouldn't
+ # (unless someone moves the location of $webdotdir, of course)
+ trick_taint($f);
+ my $mtime = file_mod_time($f);
+ if ($mtime && $mtime < $since) {
+ unlink $f;
+ }
}
# Make sure we only include valid integers (protects us from XSS attacks).
my @bugs = grep(detaint_natural($_), split(/[\s,]+/, $cgi->param('id')));
-$vars->{'bug_id'} = join(', ', @bugs);
+$vars->{'bug_id'} = join(', ', @bugs);
$vars->{'multiple_bugs'} = ($cgi->param('id') =~ /[ ,]/);
-$vars->{'display'} = $display;
-$vars->{'rankdir'} = $rankdir;
-$vars->{'showsummary'} = $cgi->param('showsummary');
+$vars->{'display'} = $display;
+$vars->{'rankdir'} = $rankdir;
+$vars->{'showsummary'} = $cgi->param('showsummary');
# Generate and return the UI (HTML page) from the appropriate template.
print $cgi->header();