Initial patch.
[vuplus_webkit] / Websites / bugs.webkit.org / duplicates.cgi
diff --git a/Websites/bugs.webkit.org/duplicates.cgi b/Websites/bugs.webkit.org/duplicates.cgi
new file mode 100755 (executable)
index 0000000..590bb57
--- /dev/null
@@ -0,0 +1,272 @@
+#!/usr/bin/env perl -wT
+# -*- 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>
+#
+# Generates mostfreq list from data collected by collectstats.pl.
+
+
+use strict;
+
+use AnyDBM_File;
+
+use lib qw(. lib);
+
+use Bugzilla;
+use Bugzilla::Constants;
+use Bugzilla::Util;
+use Bugzilla::Error;
+use Bugzilla::Search;
+use Bugzilla::Product;
+
+my $cgi = Bugzilla->cgi;
+my $template = Bugzilla->template;
+my $vars = {};
+
+# collectstats.pl uses duplicates.cgi to generate the RDF duplicates stats.
+# However, this conflicts with requirelogin if it's enabled; so we make
+# logging-in optional if we are running from the command line.
+if ($::ENV{'GATEWAY_INTERFACE'} eq "cmdline") {
+    Bugzilla->login(LOGIN_OPTIONAL);
+}
+else {
+    Bugzilla->login();
+}
+
+my $dbh = Bugzilla->switch_to_shadow_db();
+
+my %dbmcount;
+my %count;
+my %before;
+
+# Get params from URL
+sub formvalue {
+    my ($name, $default) = (@_);
+    return Bugzilla->cgi->param($name) || $default || "";
+}
+
+my $sortby = formvalue("sortby");
+my $changedsince = formvalue("changedsince", 7);
+my $maxrows = formvalue("maxrows", 100);
+my $openonly = formvalue("openonly");
+my $reverse = formvalue("reverse") ? 1 : 0;
+my @query_products = $cgi->param('product');
+my $sortvisible = formvalue("sortvisible");
+my @buglist = (split(/[:,]/, formvalue("bug_id")));
+
+# Make sure all products are valid.
+foreach my $p (@query_products) {
+    Bugzilla::Product::check_product($p);
+}
+
+# Small backwards-compatibility hack, dated 2002-04-10.
+$sortby = "count" if $sortby eq "dup_count";
+
+# Open today's record of dupes
+my $today = days_ago(0);
+my $yesterday = days_ago(1);
+
+# We don't know the exact file name, because the extension depends on the
+# underlying dbm library, which could be anything. We can't glob, because
+# perl < 5.6 considers if (<*>) { ... } to be tainted
+# Instead, just check the return value for today's data and yesterday's,
+# and ignore file not found errors
+
+use Errno;
+use Fcntl;
+
+my $datadir = bz_locations()->{'datadir'};
+
+if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$today",
+         O_RDONLY, 0644)) {
+    if ($!{ENOENT}) {
+        if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$yesterday",
+                 O_RDONLY, 0644)) {
+            my $vars = { today => $today };
+            if ($!{ENOENT}) {
+                ThrowUserError("no_dupe_stats", $vars);
+            } else {
+                $vars->{'error_msg'} = $!;
+                ThrowUserError("no_dupe_stats_error_yesterday", $vars);
+            }
+        }
+    } else {
+        ThrowUserError("no_dupe_stats_error_today",
+                       { error_msg => $! });
+    }
+}
+
+# Copy hash (so we don't mess up the on-disk file when we remove entries)
+%count = %dbmcount;
+
+# Remove all those dupes under the threshold parameter. 
+# We do this, before the sorting, for performance reasons.
+my $threshold = Bugzilla->params->{"mostfreqthreshold"};
+
+while (my ($key, $value) = each %count) {
+    delete $count{$key} if ($value < $threshold);
+    
+    # If there's a buglist, restrict the bugs to that list.
+    delete $count{$key} if $sortvisible && (lsearch(\@buglist, $key) == -1);
+}
+
+my $origmaxrows = $maxrows;
+detaint_natural($maxrows)
+  || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
+
+my $origchangedsince = $changedsince;
+detaint_natural($changedsince)
+  || ThrowUserError("invalid_changedsince", 
+                    { changedsince => $origchangedsince });
+
+# Try and open the database from "changedsince" days ago
+my $dobefore = 0;
+my %delta;
+my $whenever = days_ago($changedsince);    
+
+if (!tie(%before, 'AnyDBM_File', "$datadir/duplicates/dupes$whenever",
+         O_RDONLY, 0644)) {
+    # Ignore file not found errors
+    if (!$!{ENOENT}) {
+        ThrowUserError("no_dupe_stats_error_whenever",
+                       { error_msg => $!,
+                         changedsince => $changedsince,
+                         whenever => $whenever,
+                       });
+    }
+} else {
+    # Calculate the deltas
+    ($delta{$_} = $count{$_} - ($before{$_} || 0)) foreach (keys(%count));
+
+    $dobefore = 1;
+}
+
+my @bugs;
+my @bug_ids; 
+
+if (scalar(%count)) {
+    # use Bugzilla::Search so that we get the security checking
+    my $params = new Bugzilla::CGI({ 'bug_id' => [keys %count] });
+
+    if ($openonly) {
+        $params->param('resolution', '---');
+    } else {
+        # We want to show bugs which:
+        # a) Aren't CLOSED; and
+        # b)  i) Aren't VERIFIED; OR
+        #    ii) Were resolved INVALID/WONTFIX
+
+        # The rationale behind this is that people will eventually stop
+        # reporting fixed bugs when they get newer versions of the software,
+        # but if the bug is determined to be erroneous, people will still
+        # keep reporting it, so we do need to show it here.
+
+        # a)
+        $params->param('field0-0-0', 'bug_status');
+        $params->param('type0-0-0', 'notequals');
+        $params->param('value0-0-0', 'CLOSED');
+
+        # b) i)
+        $params->param('field0-1-0', 'bug_status');
+        $params->param('type0-1-0', 'notequals');
+        $params->param('value0-1-0', 'VERIFIED');
+
+        # b) ii)
+        $params->param('field0-1-1', 'resolution');
+        $params->param('type0-1-1', 'anyexact');
+        $params->param('value0-1-1', 'INVALID,WONTFIX');
+    }
+
+    # Restrict to product if requested
+    if ($cgi->param('product')) {
+        $params->param('product', join(',', @query_products));
+    }
+
+    my $query = new Bugzilla::Search('fields' => [qw(bugs.bug_id
+                                                     map_components.name
+                                                     bugs.bug_severity
+                                                     bugs.op_sys
+                                                     bugs.target_milestone
+                                                     bugs.short_desc
+                                                     bugs.bug_status
+                                                     bugs.resolution
+                                                    )
+                                                 ],
+                                     'params' => $params,
+                                    );
+
+    my $results = $dbh->selectall_arrayref($query->getSQL());
+
+    foreach my $result (@$results) {
+        # Note: maximum row count is dealt with in the template.
+
+        my ($id, $component, $bug_severity, $op_sys, $target_milestone, 
+            $short_desc, $bug_status, $resolution) = @$result;
+
+        push (@bugs, { id => $id,
+                       count => $count{$id},
+                       delta => $delta{$id}, 
+                       component => $component,
+                       bug_severity => $bug_severity,
+                       op_sys => $op_sys,
+                       target_milestone => $target_milestone,
+                       short_desc => $short_desc,
+                       bug_status => $bug_status, 
+                       resolution => $resolution });
+        push (@bug_ids, $id); 
+    }
+}
+
+$vars->{'bugs'} = \@bugs;
+$vars->{'bug_ids'} = \@bug_ids;
+
+$vars->{'dobefore'} = $dobefore;
+$vars->{'sortby'} = $sortby;
+$vars->{'sortvisible'} = $sortvisible;
+$vars->{'changedsince'} = $changedsince;
+$vars->{'maxrows'} = $maxrows;
+$vars->{'openonly'} = $openonly;
+$vars->{'reverse'} = $reverse;
+$vars->{'format'} = $cgi->param('format');
+$vars->{'query_products'} = \@query_products;
+$vars->{'products'} = Bugzilla->user->get_selectable_products;
+
+
+my $format = $template->get_format("reports/duplicates",
+                                   scalar($cgi->param('format')),
+                                   scalar($cgi->param('ctype')));
+
+# We set the charset in Bugzilla::CGI, but CGI.pm ignores it unless the
+# Content-Type is a text type. In some cases, such as when we are
+# generating RDF, it isn't, so we specify the charset again here.
+print $cgi->header(
+    -type => $format->{'ctype'},
+    (Bugzilla->params->{'utf8'} ? ('charset', 'utf8') : () )
+);
+
+# Generate and return the UI (HTML page) from the appropriate template.
+$template->process($format->{'template'}, $vars)
+  || ThrowTemplateError($template->error());
+
+
+sub days_ago {
+    my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5];
+    return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
+}