Dump of bugs.webkit.org's Bugzilla instance.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@30048 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/BugsSite/duplicates.cgi b/BugsSite/duplicates.cgi
new file mode 100755
index 0000000..744ad81
--- /dev/null
+++ b/BugsSite/duplicates.cgi
@@ -0,0 +1,286 @@
+#!/usr/bin/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(.);
+
+require "globals.pl";
+require "CGI.pl";
+
+use Bugzilla;
+use Bugzilla::Search;
+use Bugzilla::Config qw(:DEFAULT $datadir);
+use Bugzilla::Constants;
+
+my $cgi = Bugzilla->cgi;
+
+# Go directly to the XUL version of the duplicates report (duplicates.xul)
+# if the user specified ctype=xul.  Adds params if they exist, and directs
+# the user to a signed copy of the script in duplicates.jar if it exists.
+if (defined $cgi->param('ctype') && $cgi->param('ctype') eq "xul") {
+    my $params = CanonicaliseParams($cgi->query_string(), ["format", "ctype"]);
+    my $url = (-e "duplicates.jar" ? "duplicates.jar!/" : "") . 
+          "duplicates.xul" . ($params ? "?$params" : "") . "\n\n";
+
+    print $cgi->redirect($url);
+    exit;
+}
+
+# Use global templatisation variables.
+use vars qw($template $vars);
+
+GetVersionTable();
+
+# 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();
+}
+
+Bugzilla->switch_to_shadow_db();
+
+use vars qw ($userid @legal_product);
+
+my %dbmcount;
+my %count;
+my %before;
+
+# Get params from URL
+sub formvalue {
+    my ($name, $default) = (@_);
+    return $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")));
+
+my $product_id;
+foreach my $p (@query_products) {
+    $product_id = get_product_id($p);
+    if (!$product_id) {
+        ThrowUserError("invalid_product_name",
+                       { 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 extention 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;
+
+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 = Param("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,
+                                    );
+
+    SendSQL($query->getSQL());
+
+    while (MoreSQLData()) {
+        # Note: maximum row count is dealt with in the template.
+
+        my ($id, $component, $bug_severity, $op_sys, $target_milestone, 
+            $short_desc, $bug_status, $resolution) = FetchSQLData();
+
+        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;
+my @selectable_products = GetSelectableProducts();
+$vars->{'products'} = \@selectable_products;
+
+
+my $format = GetFormat("reports/duplicates", scalar($cgi->param('format')),
+                       scalar($cgi->param('ctype')));
+
+print $cgi->header($format->{'ctype'});
+
+# 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;
+}