590bb573c786993f53518219d8e11b39b8a8e160
[vuplus_webkit] / Websites / bugs.webkit.org / duplicates.cgi
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 #
4 # 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 # The Original Code is the Bugzilla Bug Tracking System.
15 #
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
20 #
21 # Contributor(s): Gervase Markham <gerv@gerv.net>
22 #
23 # Generates mostfreq list from data collected by collectstats.pl.
24
25
26 use strict;
27
28 use AnyDBM_File;
29
30 use lib qw(. lib);
31
32 use Bugzilla;
33 use Bugzilla::Constants;
34 use Bugzilla::Util;
35 use Bugzilla::Error;
36 use Bugzilla::Search;
37 use Bugzilla::Product;
38
39 my $cgi = Bugzilla->cgi;
40 my $template = Bugzilla->template;
41 my $vars = {};
42
43 # collectstats.pl uses duplicates.cgi to generate the RDF duplicates stats.
44 # However, this conflicts with requirelogin if it's enabled; so we make
45 # logging-in optional if we are running from the command line.
46 if ($::ENV{'GATEWAY_INTERFACE'} eq "cmdline") {
47     Bugzilla->login(LOGIN_OPTIONAL);
48 }
49 else {
50     Bugzilla->login();
51 }
52
53 my $dbh = Bugzilla->switch_to_shadow_db();
54
55 my %dbmcount;
56 my %count;
57 my %before;
58
59 # Get params from URL
60 sub formvalue {
61     my ($name, $default) = (@_);
62     return Bugzilla->cgi->param($name) || $default || "";
63 }
64
65 my $sortby = formvalue("sortby");
66 my $changedsince = formvalue("changedsince", 7);
67 my $maxrows = formvalue("maxrows", 100);
68 my $openonly = formvalue("openonly");
69 my $reverse = formvalue("reverse") ? 1 : 0;
70 my @query_products = $cgi->param('product');
71 my $sortvisible = formvalue("sortvisible");
72 my @buglist = (split(/[:,]/, formvalue("bug_id")));
73
74 # Make sure all products are valid.
75 foreach my $p (@query_products) {
76     Bugzilla::Product::check_product($p);
77 }
78
79 # Small backwards-compatibility hack, dated 2002-04-10.
80 $sortby = "count" if $sortby eq "dup_count";
81
82 # Open today's record of dupes
83 my $today = days_ago(0);
84 my $yesterday = days_ago(1);
85
86 # We don't know the exact file name, because the extension depends on the
87 # underlying dbm library, which could be anything. We can't glob, because
88 # perl < 5.6 considers if (<*>) { ... } to be tainted
89 # Instead, just check the return value for today's data and yesterday's,
90 # and ignore file not found errors
91
92 use Errno;
93 use Fcntl;
94
95 my $datadir = bz_locations()->{'datadir'};
96
97 if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$today",
98          O_RDONLY, 0644)) {
99     if ($!{ENOENT}) {
100         if (!tie(%dbmcount, 'AnyDBM_File', "$datadir/duplicates/dupes$yesterday",
101                  O_RDONLY, 0644)) {
102             my $vars = { today => $today };
103             if ($!{ENOENT}) {
104                 ThrowUserError("no_dupe_stats", $vars);
105             } else {
106                 $vars->{'error_msg'} = $!;
107                 ThrowUserError("no_dupe_stats_error_yesterday", $vars);
108             }
109         }
110     } else {
111         ThrowUserError("no_dupe_stats_error_today",
112                        { error_msg => $! });
113     }
114 }
115
116 # Copy hash (so we don't mess up the on-disk file when we remove entries)
117 %count = %dbmcount;
118
119 # Remove all those dupes under the threshold parameter. 
120 # We do this, before the sorting, for performance reasons.
121 my $threshold = Bugzilla->params->{"mostfreqthreshold"};
122
123 while (my ($key, $value) = each %count) {
124     delete $count{$key} if ($value < $threshold);
125     
126     # If there's a buglist, restrict the bugs to that list.
127     delete $count{$key} if $sortvisible && (lsearch(\@buglist, $key) == -1);
128 }
129
130 my $origmaxrows = $maxrows;
131 detaint_natural($maxrows)
132   || ThrowUserError("invalid_maxrows", { maxrows => $origmaxrows});
133
134 my $origchangedsince = $changedsince;
135 detaint_natural($changedsince)
136   || ThrowUserError("invalid_changedsince", 
137                     { changedsince => $origchangedsince });
138
139 # Try and open the database from "changedsince" days ago
140 my $dobefore = 0;
141 my %delta;
142 my $whenever = days_ago($changedsince);    
143
144 if (!tie(%before, 'AnyDBM_File', "$datadir/duplicates/dupes$whenever",
145          O_RDONLY, 0644)) {
146     # Ignore file not found errors
147     if (!$!{ENOENT}) {
148         ThrowUserError("no_dupe_stats_error_whenever",
149                        { error_msg => $!,
150                          changedsince => $changedsince,
151                          whenever => $whenever,
152                        });
153     }
154 } else {
155     # Calculate the deltas
156     ($delta{$_} = $count{$_} - ($before{$_} || 0)) foreach (keys(%count));
157
158     $dobefore = 1;
159 }
160
161 my @bugs;
162 my @bug_ids; 
163
164 if (scalar(%count)) {
165     # use Bugzilla::Search so that we get the security checking
166     my $params = new Bugzilla::CGI({ 'bug_id' => [keys %count] });
167
168     if ($openonly) {
169         $params->param('resolution', '---');
170     } else {
171         # We want to show bugs which:
172         # a) Aren't CLOSED; and
173         # b)  i) Aren't VERIFIED; OR
174         #    ii) Were resolved INVALID/WONTFIX
175
176         # The rationale behind this is that people will eventually stop
177         # reporting fixed bugs when they get newer versions of the software,
178         # but if the bug is determined to be erroneous, people will still
179         # keep reporting it, so we do need to show it here.
180
181         # a)
182         $params->param('field0-0-0', 'bug_status');
183         $params->param('type0-0-0', 'notequals');
184         $params->param('value0-0-0', 'CLOSED');
185
186         # b) i)
187         $params->param('field0-1-0', 'bug_status');
188         $params->param('type0-1-0', 'notequals');
189         $params->param('value0-1-0', 'VERIFIED');
190
191         # b) ii)
192         $params->param('field0-1-1', 'resolution');
193         $params->param('type0-1-1', 'anyexact');
194         $params->param('value0-1-1', 'INVALID,WONTFIX');
195     }
196
197     # Restrict to product if requested
198     if ($cgi->param('product')) {
199         $params->param('product', join(',', @query_products));
200     }
201
202     my $query = new Bugzilla::Search('fields' => [qw(bugs.bug_id
203                                                      map_components.name
204                                                      bugs.bug_severity
205                                                      bugs.op_sys
206                                                      bugs.target_milestone
207                                                      bugs.short_desc
208                                                      bugs.bug_status
209                                                      bugs.resolution
210                                                     )
211                                                  ],
212                                      'params' => $params,
213                                     );
214
215     my $results = $dbh->selectall_arrayref($query->getSQL());
216
217     foreach my $result (@$results) {
218         # Note: maximum row count is dealt with in the template.
219
220         my ($id, $component, $bug_severity, $op_sys, $target_milestone, 
221             $short_desc, $bug_status, $resolution) = @$result;
222
223         push (@bugs, { id => $id,
224                        count => $count{$id},
225                        delta => $delta{$id}, 
226                        component => $component,
227                        bug_severity => $bug_severity,
228                        op_sys => $op_sys,
229                        target_milestone => $target_milestone,
230                        short_desc => $short_desc,
231                        bug_status => $bug_status, 
232                        resolution => $resolution });
233         push (@bug_ids, $id); 
234     }
235 }
236
237 $vars->{'bugs'} = \@bugs;
238 $vars->{'bug_ids'} = \@bug_ids;
239
240 $vars->{'dobefore'} = $dobefore;
241 $vars->{'sortby'} = $sortby;
242 $vars->{'sortvisible'} = $sortvisible;
243 $vars->{'changedsince'} = $changedsince;
244 $vars->{'maxrows'} = $maxrows;
245 $vars->{'openonly'} = $openonly;
246 $vars->{'reverse'} = $reverse;
247 $vars->{'format'} = $cgi->param('format');
248 $vars->{'query_products'} = \@query_products;
249 $vars->{'products'} = Bugzilla->user->get_selectable_products;
250
251
252 my $format = $template->get_format("reports/duplicates",
253                                    scalar($cgi->param('format')),
254                                    scalar($cgi->param('ctype')));
255
256 # We set the charset in Bugzilla::CGI, but CGI.pm ignores it unless the
257 # Content-Type is a text type. In some cases, such as when we are
258 # generating RDF, it isn't, so we specify the charset again here.
259 print $cgi->header(
260     -type => $format->{'ctype'},
261     (Bugzilla->params->{'utf8'} ? ('charset', 'utf8') : () )
262 );
263
264 # Generate and return the UI (HTML page) from the appropriate template.
265 $template->process($format->{'template'}, $vars)
266   || ThrowTemplateError($template->error());
267
268
269 sub days_ago {
270     my ($dom, $mon, $year) = (localtime(time - ($_[0]*24*60*60)))[3, 4, 5];
271     return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
272 }