1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
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/
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.
14 # The Original Code is the Bugzilla Bug Tracking System.
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
21 # Contributor(s): Terry Weissman <terry@mozilla.org>
22 # Matthew Tuck <matty@chariot.net.au>
23 # Max Kanat-Alexander <mkanat@bugzilla.org>
24 # Marc Schumann <wurblzap@gmail.com>
25 # Frédéric Buclin <LpSolit@gmail.com>
33 use Bugzilla
::Constants
;
38 ###########################################################################
40 ###########################################################################
43 my ($san_tag, $vars) = @_;
44 $vars->{'san_tag'} = $san_tag;
45 return get_text
('sanitycheck', $vars);
49 my ($san_tag, $vars, $alert) = @_;
50 my $cgi = Bugzilla
->cgi;
51 return if (!$alert && Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
&& !$cgi->param('verbose'));
53 if (Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
) {
54 my $output = $cgi->param('output') || '';
55 my $linebreak = $alert ?
"\nALERT: " : "\n";
56 $cgi->param('error_found', 1) if $alert;
57 $cgi->param('output', $output . $linebreak . get_string
($san_tag, $vars));
60 my $start_tag = $alert ?
'<p class="alert">' : '<p>';
61 print $start_tag . get_string
($san_tag, $vars) . "</p>\n";
65 ###########################################################################
67 ###########################################################################
69 my $user = Bugzilla
->login(LOGIN_REQUIRED
);
71 my $cgi = Bugzilla
->cgi;
72 my $dbh = Bugzilla
->dbh;
73 # If the result of the sanity check is sent per email, then we have to
74 # take the user prefs into account rather than querying the web browser.
76 if (Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
) {
77 $template = Bugzilla
->template_inner($user->settings->{'lang'}->{'value'});
80 $template = Bugzilla
->template;
84 print $cgi->header() unless Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
;
86 # Make sure the user is authorized to access sanitycheck.cgi.
87 # As this script can now alter the group_control_map table, we no longer
88 # let users with editbugs privs run it anymore.
89 $user->in_group("editcomponents")
90 || ($user->in_group('editkeywords') && $cgi->param('rebuildkeywordcache'))
91 || ThrowUserError
("auth_failure", {group
=> "editcomponents",
93 object
=> "sanity_check"});
95 unless (Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
) {
96 $template->process('admin/sanitycheck/list.html.tmpl', $vars)
97 || ThrowTemplateError
($template->error());
100 ###########################################################################
101 # Users with 'editkeywords' privs only can only check keywords.
102 ###########################################################################
103 unless ($user->in_group('editcomponents')) {
104 check_votes_or_keywords
('keywords');
105 Status
('checks_completed');
107 $template->process('global/footer.html.tmpl', $vars)
108 || ThrowTemplateError
($template->error());
112 ###########################################################################
114 ###########################################################################
116 if ($cgi->param('rebuildvotecache')) {
117 Status
('vote_cache_rebuild_start');
118 $dbh->bz_start_transaction();
119 $dbh->do(q{UPDATE bugs SET votes = 0});
120 my $sth_update = $dbh->prepare(q{UPDATE bugs
123 my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
124 FROM votes }. $dbh->sql_group_by('bug_id'));
126 while (my ($id, $v) = $sth->fetchrow_array) {
127 $sth_update->execute($v, $id);
129 $dbh->bz_commit_transaction();
130 Status
('vote_cache_rebuild_end');
133 ###########################################################################
134 # Create missing group_control_map entries
135 ###########################################################################
137 if ($cgi->param('createmissinggroupcontrolmapentries')) {
138 Status
('group_control_map_entries_creation');
140 my $na = CONTROLMAPNA
;
141 my $shown = CONTROLMAPSHOWN
;
142 my $insertsth = $dbh->prepare(
143 qq{INSERT INTO group_control_map
(
144 group_id
, product_id
, entry
,
145 membercontrol
, othercontrol
, canedit
151 my $updatesth = $dbh->prepare(qq{UPDATE group_control_map
152 SET membercontrol
= $shown
154 AND product_id
= ?
});
157 # Find all group/product combinations used for bugs but not set up
158 # correctly in group_control_map
159 my $invalid_combinations = $dbh->selectall_arrayref(
160 qq{ SELECT bugs
.product_id
,
166 INNER JOIN bug_group_map AS bgm
167 ON bugs
.bug_id
= bgm
.bug_id
169 ON bgm
.group_id
= groups
.id
171 ON bugs
.product_id
= products
.id
172 LEFT JOIN group_control_map AS gcm
173 ON bugs
.product_id
= gcm
.product_id
174 AND bgm
.group_id
= gcm
.group_id
175 WHERE COALESCE
(gcm
.membercontrol
, $na) = $na
176 } . $dbh->sql_group_by('bugs.product_id, bgm.group_id',
177 'gcm.membercontrol, groups.name, products.name'));
179 foreach (@
$invalid_combinations) {
180 my ($product_id, $group_id, $currentmembercontrol,
181 $group_name, $product_name) = @
$_;
184 if (defined($currentmembercontrol)) {
185 Status
('group_control_map_entries_update',
186 {group_name
=> $group_name, product_name
=> $product_name});
187 $updatesth->execute($group_id, $product_id);
190 Status
('group_control_map_entries_generation',
191 {group_name
=> $group_name, product_name
=> $product_name});
192 $insertsth->execute($group_id, $product_id);
196 Status
('group_control_map_entries_repaired', {counter
=> $counter});
199 ###########################################################################
200 # Fix missing creation date
201 ###########################################################################
203 if ($cgi->param('repair_creation_date')) {
204 Status
('bug_creation_date_start');
206 my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs
207 WHERE creation_ts IS NULL');
209 my $sth_UpdateDate = $dbh->prepare('UPDATE bugs SET creation_ts = ?
212 # All bugs have an entry in the 'longdescs' table when they are created,
213 # even if no comment is required.
214 my $sth_getDate = $dbh->prepare('SELECT MIN(bug_when) FROM longdescs
217 foreach my $bugid (@
$bug_ids) {
218 $sth_getDate->execute($bugid);
219 my $date = $sth_getDate->fetchrow_array;
220 $sth_UpdateDate->execute($date, $bugid);
222 Status
('bug_creation_date_fixed', {bug_count
=> scalar(@
$bug_ids)});
225 ###########################################################################
226 # Fix entries in Bugs full_text
227 ###########################################################################
229 if ($cgi->param('repair_bugs_fulltext')) {
230 Status
('bugs_fulltext_start');
232 my $bug_ids = $dbh->selectcol_arrayref('SELECT bugs.bug_id
234 LEFT JOIN bugs_fulltext
235 ON bugs_fulltext.bug_id = bugs.bug_id
236 WHERE bugs_fulltext.bug_id IS NULL');
238 foreach my $bugid (@
$bug_ids) {
239 Bugzilla
::Bug
->new($bugid)->_sync_fulltext('new_bug');
242 Status
('bugs_fulltext_fixed', {bug_count
=> scalar(@
$bug_ids)});
245 ###########################################################################
247 ###########################################################################
249 if ($cgi->param('rescanallBugMail')) {
250 require Bugzilla
::BugMail
;
252 Status
('send_bugmail_start');
253 my $time = $dbh->sql_interval(30, 'MINUTE');
255 my $list = $dbh->selectcol_arrayref(qq{
258 WHERE
(lastdiffed IS NULL
259 OR lastdiffed
< delta_ts
)
260 AND delta_ts
< now
() - $time
263 Status
('send_bugmail_status', {bug_count
=> scalar(@
$list)});
265 # We cannot simply look at the bugs_activity table to find who did the
266 # last change in a given bug, as e.g. adding a comment doesn't add any
267 # entry to this table. And some other changes may be private
268 # (such as time-related changes or private attachments or comments)
269 # and so choosing this user as being the last one having done a change
270 # for the bug may be problematic. So the best we can do at this point
271 # is to choose the currently logged in user for email notification.
272 $vars->{'changer'} = Bugzilla
->user->login;
274 foreach my $bugid (@
$list) {
275 Bugzilla
::BugMail
::Send
($bugid, $vars);
278 Status
('send_bugmail_end') if scalar(@
$list);
280 unless (Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
) {
281 $template->process('global/footer.html.tmpl', $vars)
282 || ThrowTemplateError
($template->error());
287 ###########################################################################
288 # Remove all references to deleted bugs
289 ###########################################################################
291 if ($cgi->param('remove_invalid_bug_references')) {
292 Status
('bug_reference_deletion_start');
294 $dbh->bz_start_transaction();
296 # Include custom multi-select fields to the list.
297 my @multi_selects = Bugzilla
->get_fields({custom
=> 1, type
=> FIELD_TYPE_MULTI_SELECT
});
298 my @addl_fields = map { 'bug_' . $_->name . '/' } @multi_selects;
300 foreach my $pair ('attachments/', 'bug_group_map/', 'bugs_activity/',
301 'bugs_fulltext/', 'cc/',
302 'dependencies/blocked', 'dependencies/dependson',
303 'duplicates/dupe', 'duplicates/dupe_of',
304 'flags/', 'keywords/', 'longdescs/', 'votes/',
307 my ($table, $field) = split('/', $pair);
311 $dbh->selectcol_arrayref("SELECT $table.$field FROM $table
312 LEFT JOIN bugs ON $table.$field = bugs.bug_id
313 WHERE bugs.bug_id IS NULL");
315 if (scalar(@
$bug_ids)) {
316 $dbh->do("DELETE FROM $table WHERE $field IN (" . join(',', @
$bug_ids) . ")");
320 $dbh->bz_commit_transaction();
321 Status
('bug_reference_deletion_end');
324 ###########################################################################
325 # Remove all references to deleted attachments
326 ###########################################################################
328 if ($cgi->param('remove_invalid_attach_references')) {
329 Status
('attachment_reference_deletion_start');
331 $dbh->bz_start_transaction();
334 $dbh->selectcol_arrayref('SELECT attach_data.id
336 LEFT JOIN attachments
337 ON attachments.attach_id = attach_data.id
338 WHERE attachments.attach_id IS NULL');
340 if (scalar(@
$attach_ids)) {
341 $dbh->do('DELETE FROM attach_data WHERE id IN (' .
342 join(',', @
$attach_ids) . ')');
345 $dbh->bz_commit_transaction();
346 Status
('attachment_reference_deletion_end');
349 ###########################################################################
350 # Remove all references to deleted users or groups from whines
351 ###########################################################################
353 if ($cgi->param('remove_old_whine_targets')) {
354 Status
('whines_obsolete_target_deletion_start');
356 $dbh->bz_start_transaction();
358 foreach my $target (['groups', 'id', MAILTO_GROUP
],
359 ['profiles', 'userid', MAILTO_USER
])
361 my ($table, $col, $type) = @
$target;
363 $dbh->selectcol_arrayref("SELECT DISTINCT mailto
366 ON $table.$col = whine_schedules.mailto
367 WHERE mailto_type = $type AND $table.$col IS NULL");
369 if (scalar(@
$old_ids)) {
370 $dbh->do("DELETE FROM whine_schedules
371 WHERE mailto_type = $type AND mailto IN (" .
372 join(',', @
$old_ids) . ")");
375 $dbh->bz_commit_transaction();
376 Status
('whines_obsolete_target_deletion_end');
379 Status
('checks_start');
381 ###########################################################################
382 # Perform referential (cross) checks
383 ###########################################################################
385 # This checks that a simple foreign key has a valid primary key value. NULL
386 # references are acceptable and cause no problem.
388 # The first parameter is the primary key table name.
389 # The second parameter is the primary key field name.
390 # Each successive parameter represents a foreign key, it must be a list
391 # reference, where the list has:
392 # the first value is the foreign key table name.
393 # the second value is the foreign key field name.
394 # the third value is optional and represents a field on the foreign key
395 # table to display when the check fails.
396 # the fourth value is optional and is a list reference to values that
397 # are excluded from checking.
399 # FIXME: The excluded values parameter should go away - the QA contact
400 # fields should use NULL instead - see bug #109474.
401 # The same goes for series; no bug for that yet.
404 my $table = shift @_;
405 my $field = shift @_;
406 my $dbh = Bugzilla
->dbh;
408 Status
('cross_check_to', {table
=> $table, field
=> $field});
412 my ($refertable, $referfield, $keyname, $exceptions) = @
$ref;
415 my %exceptions = map { $_ => 1 } @
$exceptions;
417 Status
('cross_check_from', {table
=> $refertable, field
=> $referfield});
419 my $query = qq{SELECT DISTINCT
$refertable.$referfield} .
420 ($keyname ?
qq{, $refertable.$keyname } : q{}) .
423 ON
$refertable.$referfield = $table.$field
424 WHERE
$table.$field IS NULL
425 AND
$refertable.$referfield IS NOT NULL
};
427 my $sth = $dbh->prepare($query);
430 my $has_bad_references = 0;
432 while (my ($value, $key) = $sth->fetchrow_array) {
433 next if $exceptions{$value};
434 Status
('cross_check_alert', {value
=> $value, table
=> $refertable,
435 field
=> $referfield, keyname
=> $keyname,
436 key
=> $key}, 'alert');
437 $has_bad_references = 1;
439 # References to non existent bugs can be safely removed, bug 288461
440 if ($table eq 'bugs' && $has_bad_references) {
441 Status
('cross_check_bug_has_references');
443 # References to non existent attachments can be safely removed.
444 if ($table eq 'attachments' && $has_bad_references) {
445 Status
('cross_check_attachment_has_references');
450 CrossCheck
('classifications', 'id',
451 ['products', 'classification_id']);
453 CrossCheck
("keyworddefs", "id",
454 ["keywords", "keywordid"]);
456 CrossCheck
("fielddefs", "id",
457 ["bugs_activity", "fieldid"],
458 ['profiles_activity', 'fieldid']);
460 CrossCheck
("flagtypes", "id",
461 ["flags", "type_id"],
462 ["flagexclusions", "type_id"],
463 ["flaginclusions", "type_id"]);
465 # Include custom multi-select fields to the list.
466 my @multi_selects = Bugzilla
->get_fields({custom
=> 1, type
=> FIELD_TYPE_MULTI_SELECT
});
467 my @addl_fields = map { ['bug_' . $_->name, 'bug_id'] } @multi_selects;
469 CrossCheck
("bugs", "bug_id",
470 ["bugs_activity", "bug_id"],
471 ["bug_group_map", "bug_id"],
472 ["bugs_fulltext", "bug_id"],
473 ["attachments", "bug_id"],
475 ["longdescs", "bug_id"],
476 ["dependencies", "blocked"],
477 ["dependencies", "dependson"],
480 ["keywords", "bug_id"],
481 ["duplicates", "dupe_of", "dupe"],
482 ["duplicates", "dupe", "dupe_of"],
485 CrossCheck
("groups", "id",
486 ["bug_group_map", "group_id"],
487 ['category_group_map', 'group_id'],
488 ["group_group_map", "grantor_id"],
489 ["group_group_map", "member_id"],
490 ["group_control_map", "group_id"],
491 ["namedquery_group_map", "group_id"],
492 ["user_group_map", "group_id"],
493 ["flagtypes", "grant_group_id"],
494 ["flagtypes", "request_group_id"]);
496 CrossCheck
("namedqueries", "id",
497 ["namedqueries_link_in_footer", "namedquery_id"],
498 ["namedquery_group_map", "namedquery_id"],
501 CrossCheck
("profiles", "userid",
502 ['profiles_activity', 'userid'],
503 ['profiles_activity', 'who'],
504 ['email_setting', 'user_id'],
505 ['profile_setting', 'user_id'],
506 ["bugs", "reporter", "bug_id"],
507 ["bugs", "assigned_to", "bug_id"],
508 ["bugs", "qa_contact", "bug_id"],
509 ["attachments", "submitter_id", "bug_id"],
510 ['flags', 'setter_id', 'bug_id'],
511 ['flags', 'requestee_id', 'bug_id'],
512 ["bugs_activity", "who", "bug_id"],
513 ["cc", "who", "bug_id"],
515 ["votes", "who", "bug_id"],
516 ["longdescs", "who", "bug_id"],
517 ["logincookies", "userid"],
518 ["namedqueries", "userid"],
519 ["namedqueries_link_in_footer", "user_id"],
520 ['series', 'creator', 'series_id'],
521 ["watch", "watcher"],
522 ["watch", "watched"],
523 ['whine_events', 'owner_userid'],
524 ["tokens", "userid"],
525 ["user_group_map", "user_id"],
526 ["components", "initialowner", "name"],
527 ["components", "initialqacontact", "name"],
528 ["component_cc", "user_id"]);
530 CrossCheck
("products", "id",
531 ["bugs", "product_id", "bug_id"],
532 ["components", "product_id", "name"],
533 ["milestones", "product_id", "value"],
534 ["versions", "product_id", "value"],
535 ["group_control_map", "product_id"],
536 ["flaginclusions", "product_id", "type_id"],
537 ["flagexclusions", "product_id", "type_id"]);
539 CrossCheck
("components", "id",
540 ["component_cc", "component_id"],
541 ["flagexclusions", "component_id", "type_id"],
542 ["flaginclusions", "component_id", "type_id"]);
544 # Check the former enum types -mkanat@bugzilla.org
545 CrossCheck
("bug_status", "value",
546 ["bugs", "bug_status", "bug_id"]);
548 CrossCheck
("resolution", "value",
549 ["bugs", "resolution", "bug_id"]);
551 CrossCheck
("bug_severity", "value",
552 ["bugs", "bug_severity", "bug_id"]);
554 CrossCheck
("op_sys", "value",
555 ["bugs", "op_sys", "bug_id"]);
557 CrossCheck
("priority", "value",
558 ["bugs", "priority", "bug_id"]);
560 CrossCheck
("rep_platform", "value",
561 ["bugs", "rep_platform", "bug_id"]);
563 CrossCheck
('series', 'series_id',
564 ['series_data', 'series_id']);
566 CrossCheck
('series_categories', 'id',
567 ['series', 'category'],
568 ["category_group_map", "category_id"],
569 ["series", "subcategory"]);
571 CrossCheck
('whine_events', 'id',
572 ['whine_queries', 'eventid'],
573 ['whine_schedules', 'eventid']);
575 CrossCheck
('attachments', 'attach_id',
576 ['attach_data', 'id'],
577 ['bugs_activity', 'attach_id']);
579 CrossCheck
('bug_status', 'id',
580 ['status_workflow', 'old_status'],
581 ['status_workflow', 'new_status']);
583 ###########################################################################
584 # Perform double field referential (cross) checks
585 ###########################################################################
587 # This checks that a compound two-field foreign key has a valid primary key
588 # value. NULL references are acceptable and cause no problem.
590 # The first parameter is the primary key table name.
591 # The second parameter is the primary key first field name.
592 # The third parameter is the primary key second field name.
593 # Each successive parameter represents a foreign key, it must be a list
594 # reference, where the list has:
595 # the first value is the foreign key table name
596 # the second value is the foreign key first field name.
597 # the third value is the foreign key second field name.
598 # the fourth value is optional and represents a field on the foreign key
599 # table to display when the check fails
601 sub DoubleCrossCheck
{
602 my $table = shift @_;
603 my $field1 = shift @_;
604 my $field2 = shift @_;
605 my $dbh = Bugzilla
->dbh;
607 Status
('double_cross_check_to',
608 {table
=> $table, field1
=> $field1, field2
=> $field2});
612 my ($refertable, $referfield1, $referfield2, $keyname) = @
$ref;
614 Status
('double_cross_check_from',
615 {table
=> $refertable, field1
=> $referfield1, field2
=>$referfield2});
617 my $d_cross_check = $dbh->selectall_arrayref(qq{
618 SELECT DISTINCT
$refertable.$referfield1,
619 $refertable.$referfield2 } .
620 ($keyname ?
qq{, $refertable.$keyname } : q{}) .
623 ON
$refertable.$referfield1 = $table.$field1
624 AND
$refertable.$referfield2 = $table.$field2
625 WHERE
$table.$field1 IS NULL
626 AND
$table.$field2 IS NULL
627 AND
$refertable.$referfield1 IS NOT NULL
628 AND
$refertable.$referfield2 IS NOT NULL
});
630 foreach my $check (@
$d_cross_check) {
631 my ($value1, $value2, $key) = @
$check;
632 Status
('double_cross_check_alert',
633 {value1
=> $value1, value2
=> $value2,
634 table
=> $refertable,
635 field1
=> $referfield1, field2
=> $referfield2,
636 keyname
=> $keyname, key
=> $key}, 'alert');
641 DoubleCrossCheck
('attachments', 'bug_id', 'attach_id',
642 ['flags', 'bug_id', 'attach_id'],
643 ['bugs_activity', 'bug_id', 'attach_id']);
645 DoubleCrossCheck
("components", "product_id", "id",
646 ["bugs", "product_id", "component_id", "bug_id"],
647 ['flagexclusions', 'product_id', 'component_id'],
648 ['flaginclusions', 'product_id', 'component_id']);
650 DoubleCrossCheck
("versions", "product_id", "value",
651 ["bugs", "product_id", "version", "bug_id"]);
653 DoubleCrossCheck
("milestones", "product_id", "value",
654 ["bugs", "product_id", "target_milestone", "bug_id"],
655 ["products", "id", "defaultmilestone", "name"]);
657 ###########################################################################
658 # Perform login checks
659 ###########################################################################
661 Status
('profile_login_start');
663 my $sth = $dbh->prepare(q{SELECT userid, login_name FROM profiles});
666 while (my ($id, $email) = $sth->fetchrow_array) {
667 validate_email_syntax
($email)
668 || Status
('profile_login_alert', {id
=> $id, email
=> $email}, 'alert');
671 ###########################################################################
672 # Perform vote/keyword cache checks
673 ###########################################################################
675 check_votes_or_keywords
();
677 sub check_votes_or_keywords
{
678 my $check = shift || 'all';
680 my $dbh = Bugzilla
->dbh;
681 my $sth = $dbh->prepare(q{SELECT bug_id, votes, keywords
683 WHERE votes != 0 OR keywords != ''});
689 while (my ($id, $v, $k) = $sth->fetchrow_array) {
698 # If we only want to check keywords, skip checks about votes.
699 _check_votes
(\
%votes) unless ($check eq 'keywords');
700 # If we only want to check votes, skip checks about keywords.
701 _check_keywords
(\
%keyword) unless ($check eq 'votes');
707 Status
('vote_count_start');
708 my $dbh = Bugzilla
->dbh;
709 my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
711 $dbh->sql_group_by('bug_id'));
714 my $offer_votecache_rebuild = 0;
716 while (my ($id, $v) = $sth->fetchrow_array) {
718 Status
('vote_count_alert', {id
=> $id}, 'alert');
720 if (!defined $votes->{$id} || $votes->{$id} != $v) {
721 Status
('vote_cache_alert', {id
=> $id}, 'alert');
722 $offer_votecache_rebuild = 1;
724 delete $votes->{$id};
727 foreach my $id (keys %$votes) {
728 Status
('vote_cache_alert', {id
=> $id}, 'alert');
729 $offer_votecache_rebuild = 1;
732 Status
('vote_cache_rebuild_fix') if $offer_votecache_rebuild;
735 sub _check_keywords
{
738 Status
('keyword_check_start');
739 my $dbh = Bugzilla
->dbh;
740 my $cgi = Bugzilla
->cgi;
743 my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
746 foreach (@
$keywords) {
747 my ($id, $name) = @
$_;
748 if ($keywordids{$id}) {
749 Status
('keyword_check_alert', {id
=> $id}, 'alert');
751 $keywordids{$id} = 1;
752 if ($name =~ /[\s,]/) {
753 Status
('keyword_check_invalid_name', {id
=> $id}, 'alert');
757 my $sth = $dbh->prepare(q{SELECT bug_id, keywordid
759 ORDER BY bug_id, keywordid});
763 while (my ($id, $k) = $sth->fetchrow_array) {
764 if (!$keywordids{$k}) {
765 Status
('keyword_check_invalid_id', {id
=> $k}, 'alert');
767 if (defined $lastid && $id eq $lastid && $k eq $lastk) {
768 Status
('keyword_check_duplicated_ids', {id
=> $id}, 'alert');
774 Status
('keyword_cache_start');
776 if ($cgi->param('rebuildkeywordcache')) {
777 $dbh->bz_start_transaction();
780 my $query = q{SELECT keywords.bug_id, keyworddefs.name
782 INNER JOIN keyworddefs
783 ON keyworddefs.id = keywords.keywordid
785 ON keywords.bug_id = bugs.bug_id
786 ORDER BY keywords.bug_id, keyworddefs.name};
788 $sth = $dbh->prepare($query);
795 my ($b, $k) = $sth->fetchrow_array;
796 if (!defined $b || $b != $lastb) {
798 $realk{$lastb} = join(', ', @list);
810 foreach my $b (keys(%$keyword)) {
811 if (!exists $realk{$b} || $realk{$b} ne $keyword->{$b}) {
815 foreach my $b (keys(%realk)) {
816 if (!exists $keyword->{$b}) {
821 @badbugs = sort {$a <=> $b} @badbugs;
823 if ($cgi->param('rebuildkeywordcache')) {
824 my $sth_update = $dbh->prepare(q{UPDATE bugs
828 Status
('keyword_cache_fixing');
829 foreach my $b (@badbugs) {
831 if (exists($realk{$b})) {
834 $sth_update->execute($k, $b);
836 Status
('keyword_cache_fixed');
838 Status
('keyword_cache_alert', {badbugs
=> \
@badbugs}, 'alert');
839 Status
('keyword_cache_rebuild');
843 if ($cgi->param('rebuildkeywordcache')) {
844 $dbh->bz_commit_transaction();
848 ###########################################################################
849 # Check for flags being in incorrect products and components
850 ###########################################################################
852 Status
('flag_check_start');
854 my $invalid_flags = $dbh->selectall_arrayref(
855 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
858 ON flags.bug_id = bugs.bug_id
859 LEFT JOIN flaginclusions AS i
860 ON flags.type_id = i.type_id
861 AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
862 AND (bugs.component_id = i.component_id OR i.component_id IS NULL)
863 WHERE i.type_id IS NULL');
865 my @invalid_flags = @
$invalid_flags;
867 $invalid_flags = $dbh->selectall_arrayref(
868 'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id
871 ON flags.bug_id = bugs.bug_id
872 INNER JOIN flagexclusions AS e
873 ON flags.type_id = e.type_id
874 WHERE (bugs.product_id = e.product_id OR e.product_id IS NULL)
875 AND (bugs.component_id = e.component_id OR e.component_id IS NULL)');
877 push(@invalid_flags, @
$invalid_flags);
879 if (scalar(@invalid_flags)) {
880 if ($cgi->param('remove_invalid_flags')) {
881 Status
('flag_deletion_start');
882 my @flag_ids = map {$_->[0]} @invalid_flags;
883 # Silently delete these flags, with no notification to requesters/setters.
884 $dbh->do('DELETE FROM flags WHERE id IN (' . join(',', @flag_ids) .')');
885 Status
('flag_deletion_end');
888 foreach my $flag (@
$invalid_flags) {
889 my ($flag_id, $bug_id, $attach_id) = @
$flag;
891 {flag_id
=> $flag_id, attach_id
=> $attach_id, bug_id
=> $bug_id},
898 ###########################################################################
900 ###########################################################################
903 my ($middlesql, $errortext, $repairparam, $repairtext) = @_;
904 my $dbh = Bugzilla
->dbh;
906 my $badbugs = $dbh->selectcol_arrayref(qq{SELECT DISTINCT bugs
.bug_id
908 ORDER BY bugs
.bug_id
});
910 if (scalar(@
$badbugs)) {
911 Status
('bug_check_alert',
912 {errortext
=> get_string
($errortext), badbugs
=> $badbugs},
916 $repairtext ||= 'repair_bugs';
917 Status
('bug_check_repair',
918 {param
=> $repairparam, text
=> get_string
($repairtext)});
923 Status
('bug_check_creation_date');
925 BugCheck
("bugs WHERE creation_ts IS NULL", 'bug_check_creation_date_error_text',
926 'repair_creation_date', 'bug_check_creation_date_repair_text');
928 Status
('bug_check_bugs_fulltext');
930 BugCheck
("bugs LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id " .
931 "WHERE bugs_fulltext.bug_id IS NULL", 'bug_check_bugs_fulltext_error_text',
932 'repair_bugs_fulltext', 'bug_check_bugs_fulltext_repair_text');
934 Status
('bug_check_res_dupl');
936 BugCheck
("bugs INNER JOIN duplicates ON bugs.bug_id = duplicates.dupe " .
937 "WHERE bugs.resolution != 'DUPLICATE'", 'bug_check_res_dupl_error_text');
939 BugCheck
("bugs LEFT JOIN duplicates ON bugs.bug_id = duplicates.dupe WHERE " .
940 "bugs.resolution = 'DUPLICATE' AND " .
941 "duplicates.dupe IS NULL", 'bug_check_res_dupl_error_text2');
943 Status
('bug_check_status_res');
945 my @open_states = map($dbh->quote($_), BUG_STATE_OPEN
);
946 my $open_states = join(', ', @open_states);
948 BugCheck
("bugs WHERE bug_status IN ($open_states) AND resolution != ''",
949 'bug_check_status_res_error_text');
950 BugCheck
("bugs WHERE bug_status NOT IN ($open_states) AND resolution = ''",
951 'bug_check_status_res_error_text2');
953 Status
('bug_check_status_everconfirmed');
955 BugCheck
("bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
956 'bug_check_status_everconfirmed_error_text');
958 my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN
;
959 my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
961 BugCheck
("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
962 'bug_check_status_everconfirmed_error_text2');
964 Status
('bug_check_votes_everconfirmed');
966 BugCheck
("bugs INNER JOIN products ON bugs.product_id = products.id " .
967 "WHERE everconfirmed = 0 AND votestoconfirm <= votes",
968 'bug_check_votes_everconfirmed_error_text');
970 ###########################################################################
972 ###########################################################################
974 # Checks for values that are invalid OR
975 # not among the 9 valid combinations
976 Status
('bug_check_control_values');
977 my $groups = join(", ", (CONTROLMAPNA
, CONTROLMAPSHOWN
, CONTROLMAPDEFAULT
,
978 CONTROLMAPMANDATORY
));
980 SELECT COUNT
(product_id
)
981 FROM group_control_map
982 WHERE membercontrol NOT IN
( $groups )
983 OR othercontrol NOT IN
( $groups )
984 OR
((membercontrol
!= othercontrol
)
985 AND
(membercontrol
!= } . CONTROLMAPSHOWN
. q{)
986 AND ((membercontrol != } . CONTROLMAPDEFAULT
. q{)
987 OR (othercontrol = } . CONTROLMAPSHOWN
. q{)))};
989 my $entries = $dbh->selectrow_array($query);
990 Status
('bug_check_control_values_alert', {entries
=> $entries}, 'alert') if $entries;
992 Status
('bug_check_control_values_violation');
994 INNER JOIN bug_group_map
995 ON bugs.bug_id = bug_group_map.bug_id
996 LEFT JOIN group_control_map
997 ON bugs.product_id = group_control_map.product_id
998 AND bug_group_map.group_id = group_control_map.group_id
999 WHERE ((group_control_map.membercontrol = " . CONTROLMAPNA
. ")
1000 OR (group_control_map.membercontrol IS NULL))",
1001 'bug_check_control_values_error_text',
1002 'createmissinggroupcontrolmapentries',
1003 'bug_check_control_values_repair_text');
1006 INNER JOIN group_control_map
1007 ON bugs.product_id = group_control_map.product_id
1009 ON group_control_map.group_id = groups.id
1010 LEFT JOIN bug_group_map
1011 ON bugs.bug_id = bug_group_map.bug_id
1012 AND group_control_map.group_id = bug_group_map.group_id
1013 WHERE group_control_map.membercontrol = " . CONTROLMAPMANDATORY
. "
1014 AND bug_group_map.group_id IS NULL
1015 AND groups.isactive != 0",
1016 'bug_check_control_values_error_text2');
1018 ###########################################################################
1020 ###########################################################################
1022 Status
('unsent_bugmail_check');
1024 my $time = $dbh->sql_interval(30, 'MINUTE');
1025 my $badbugs = $dbh->selectcol_arrayref(qq{
1028 WHERE
(lastdiffed IS NULL OR lastdiffed
< delta_ts
)
1029 AND delta_ts
< now
() - $time
1033 if (scalar(@
$badbugs > 0)) {
1034 Status
('unsent_bugmail_alert', {badbugs
=> $badbugs}, 'alert');
1035 Status
('unsent_bugmail_fix');
1038 ###########################################################################
1040 ###########################################################################
1042 Status
('whines_obsolete_target_start');
1044 my $display_repair_whines_link = 0;
1045 foreach my $target (['groups', 'id', MAILTO_GROUP
],
1046 ['profiles', 'userid', MAILTO_USER
])
1048 my ($table, $col, $type) = @
$target;
1049 my $old = $dbh->selectall_arrayref("SELECT whine_schedules.id, mailto
1050 FROM whine_schedules
1052 ON $table.$col = whine_schedules.mailto
1053 WHERE mailto_type = $type AND $table.$col IS NULL");
1055 if (scalar(@
$old)) {
1056 Status
('whines_obsolete_target_alert', {schedules
=> $old, type
=> $type}, 'alert');
1057 $display_repair_whines_link = 1;
1060 Status
('whines_obsolete_target_fix') if $display_repair_whines_link;
1062 ###########################################################################
1064 ###########################################################################
1066 Status
('checks_completed');
1068 unless (Bugzilla
->usage_mode == USAGE_MODE_CMDLINE
) {
1069 $template->process('global/footer.html.tmpl', $vars)
1070 || ThrowTemplateError
($template->error());