814 lines
28 KiB
Perl
Executable File
814 lines
28 KiB
Perl
Executable File
#!/usr/bin/perl -wT
|
|
# 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): Terry Weissman <terry@mozilla.org>
|
|
# Matthew Tuck <matty@chariot.net.au>
|
|
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
# Marc Schumann <wurblzap@gmail.com>
|
|
# Frédéric Buclin <LpSolit@gmail.com>
|
|
|
|
use strict;
|
|
|
|
use lib qw(. lib);
|
|
|
|
use Bugzilla;
|
|
use Bugzilla::Bug;
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Hook;
|
|
use Bugzilla::Util;
|
|
use Bugzilla::Status;
|
|
|
|
###########################################################################
|
|
# General subs
|
|
###########################################################################
|
|
|
|
sub get_string
|
|
{
|
|
my ($san_tag, $vars) = @_;
|
|
$vars->{san_tag} = $san_tag;
|
|
return get_text('sanitycheck', $vars);
|
|
}
|
|
|
|
sub Status
|
|
{
|
|
my ($san_tag, $vars, $alert) = @_;
|
|
my $ARGS = Bugzilla->input_params;
|
|
return if !$alert && Bugzilla->usage_mode == USAGE_MODE_CMDLINE && !$ARGS->{verbose};
|
|
if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE)
|
|
{
|
|
my $linebreak = $alert ? "\nALERT: " : "\n";
|
|
$ARGS->{error_found} = 1 if $alert;
|
|
$ARGS->{output} = ($ARGS->{output} || '') . $linebreak . get_string($san_tag, $vars);
|
|
print $linebreak . get_string($san_tag, $vars);
|
|
}
|
|
else
|
|
{
|
|
my $start_tag = $alert ? '<p class="alert">' : '<p>';
|
|
print $start_tag . get_string($san_tag, $vars) . "</p>\n";
|
|
}
|
|
}
|
|
|
|
###########################################################################
|
|
# Start
|
|
###########################################################################
|
|
|
|
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
|
|
|
my $ARGS = Bugzilla->input_params;
|
|
my $dbh = Bugzilla->dbh;
|
|
# If the result of the sanity check is sent per email, then we have to
|
|
# take the user prefs into account rather than querying the web browser.
|
|
my $template;
|
|
if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE)
|
|
{
|
|
$template = Bugzilla->template_inner($user->settings->{lang}->{value});
|
|
}
|
|
else
|
|
{
|
|
$template = Bugzilla->template;
|
|
}
|
|
my $vars = {};
|
|
|
|
Bugzilla->cgi->send_header() unless Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
|
|
|
|
# Make sure the user is authorized to access sanitycheck.cgi.
|
|
# As this script can now alter the group_control_map table, we no longer
|
|
# let users with editbugs privs run it anymore.
|
|
$user->in_group("editcomponents") || ThrowUserError("auth_failure", {
|
|
group => "editcomponents",
|
|
action => "run",
|
|
object => "sanity_check",
|
|
});
|
|
|
|
unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE)
|
|
{
|
|
$template->process('admin/sanitycheck/list.html.tmpl', $vars)
|
|
|| ThrowTemplateError($template->error());
|
|
}
|
|
|
|
unless ($user->in_group('editcomponents'))
|
|
{
|
|
Status('checks_completed');
|
|
$template->process('global/footer.html.tmpl', $vars)
|
|
|| ThrowTemplateError($template->error());
|
|
exit;
|
|
}
|
|
|
|
###########################################################################
|
|
# Fix vote cache
|
|
###########################################################################
|
|
|
|
if ($ARGS->{rebuildvotecache})
|
|
{
|
|
Status('vote_cache_rebuild_start');
|
|
$dbh->bz_start_transaction();
|
|
$dbh->do('UPDATE bugs SET votes = 0');
|
|
my $sth_update = $dbh->prepare('UPDATE bugs SET votes = ? WHERE bug_id = ?');
|
|
my $sth = $dbh->prepare('SELECT bug_id, SUM(vote_count) FROM votes GROUP BY bug_id');
|
|
$sth->execute();
|
|
while (my ($id, $v) = $sth->fetchrow_array)
|
|
{
|
|
$sth_update->execute($v, $id);
|
|
}
|
|
$dbh->bz_commit_transaction();
|
|
Status('vote_cache_rebuild_end');
|
|
}
|
|
|
|
###########################################################################
|
|
# Create missing group_control_map entries
|
|
###########################################################################
|
|
|
|
if ($ARGS->{createmissinggroupcontrolmapentries})
|
|
{
|
|
Status('group_control_map_entries_creation');
|
|
|
|
my $na = CONTROLMAPNA;
|
|
my $shown = CONTROLMAPSHOWN;
|
|
my $insertsth = $dbh->prepare(
|
|
'INSERT INTO group_control_map (group_id, product_id, membercontrol, othercontrol)'.
|
|
' VALUES (?, ?, $shown, $na)'
|
|
);
|
|
|
|
my $updatesth = $dbh->prepare(
|
|
'UPDATE group_control_map SET membercontrol = $shown WHERE group_id = ? AND product_id = ?'
|
|
);
|
|
my $counter = 0;
|
|
|
|
# Find all group/product combinations used for bugs but not set up
|
|
# correctly in group_control_map
|
|
my $invalid_combinations = $dbh->selectall_arrayref(
|
|
"SELECT bugs.product_id, bgm.group_id, gcm.membercontrol, groups.name, products.name FROM bugs".
|
|
" INNER JOIN bug_group_map AS bgm ON bugs.bug_id = bgm.bug_id".
|
|
" INNER JOIN groups ON bgm.group_id = groups.id".
|
|
" INNER JOIN products ON bugs.product_id = products.id".
|
|
" LEFT JOIN group_control_map AS gcm".
|
|
" ON bugs.product_id = gcm.product_id AND bgm.group_id = gcm.group_id".
|
|
" WHERE COALESCE(gcm.membercontrol, $na) = $na".
|
|
$dbh->sql_group_by('bugs.product_id, bgm.group_id', 'gcm.membercontrol, groups.name, products.name')
|
|
);
|
|
|
|
foreach (@$invalid_combinations)
|
|
{
|
|
my ($product_id, $group_id, $currentmembercontrol, $group_name, $product_name) = @$_;
|
|
$counter++;
|
|
if (defined($currentmembercontrol))
|
|
{
|
|
Status('group_control_map_entries_update', {
|
|
group_name => $group_name,
|
|
product_name => $product_name,
|
|
});
|
|
$updatesth->execute($group_id, $product_id);
|
|
}
|
|
else
|
|
{
|
|
Status('group_control_map_entries_generation', {
|
|
group_name => $group_name,
|
|
product_name => $product_name,
|
|
});
|
|
$insertsth->execute($group_id, $product_id);
|
|
}
|
|
}
|
|
|
|
Status('group_control_map_entries_repaired', {counter => $counter});
|
|
}
|
|
|
|
###########################################################################
|
|
# Fix missing creation date
|
|
###########################################################################
|
|
|
|
if ($ARGS->{repair_creation_date})
|
|
{
|
|
Status('bug_creation_date_start');
|
|
|
|
my $bug_ids = $dbh->selectcol_arrayref('SELECT bug_id FROM bugs WHERE creation_ts IS NULL');
|
|
my $sth_UpdateDate = $dbh->prepare('UPDATE bugs SET creation_ts=? WHERE bug_id=?');
|
|
|
|
# All bugs have an entry in the 'longdescs' table when they are created,
|
|
# even if no comment is required.
|
|
my $sth_getDate = $dbh->prepare('SELECT MIN(bug_when) FROM longdescs WHERE bug_id = ?');
|
|
foreach my $bugid (@$bug_ids)
|
|
{
|
|
$sth_getDate->execute($bugid);
|
|
my $date = $sth_getDate->fetchrow_array;
|
|
$sth_UpdateDate->execute($date, $bugid);
|
|
}
|
|
Status('bug_creation_date_fixed', { bug_count => scalar @$bug_ids });
|
|
}
|
|
|
|
###########################################################################
|
|
# Fix everconfirmed
|
|
###########################################################################
|
|
|
|
if ($ARGS->{repair_everconfirmed})
|
|
{
|
|
Status('everconfirmed_start');
|
|
|
|
my $unconfirmed_states = join(', ', map { $dbh->quote($_->name) } grep { !$_->is_confirmed } Bugzilla::Status->get_all);
|
|
my $confirmed_states = join(', ', map { $dbh->quote($_->name) } grep { $_->is_confirmed } Bugzilla::Status->get_all);
|
|
|
|
$dbh->do("UPDATE bugs SET everconfirmed = 0 WHERE bug_status IN ($unconfirmed_states)");
|
|
$dbh->do("UPDATE bugs SET everconfirmed = 1 WHERE bug_status IN ($confirmed_states)");
|
|
|
|
Status('everconfirmed_end');
|
|
}
|
|
|
|
###########################################################################
|
|
# Fix entries in Bugs full_text
|
|
###########################################################################
|
|
|
|
if ($ARGS->{repair_bugs_fulltext} && !Bugzilla->localconfig->{sphinx_index})
|
|
{
|
|
Status('bugs_fulltext_start');
|
|
|
|
my $bug_ids = $dbh->selectcol_arrayref(
|
|
'SELECT bugs.bug_id FROM bugs'.
|
|
' LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id'.
|
|
' WHERE bugs_fulltext.bug_id IS NULL'
|
|
);
|
|
foreach my $bugid (@$bug_ids)
|
|
{
|
|
Bugzilla::Bug->new($bugid)->_sync_fulltext('new_bug');
|
|
}
|
|
|
|
Status('bugs_fulltext_fixed', { bug_count => scalar @$bug_ids });
|
|
}
|
|
|
|
###########################################################################
|
|
# Send unsent mail
|
|
###########################################################################
|
|
|
|
if ($ARGS->{rescanallBugMail})
|
|
{
|
|
require Bugzilla::BugMail;
|
|
|
|
Status('send_bugmail_start');
|
|
my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
|
|
|
|
my $list = $dbh->selectcol_arrayref(
|
|
"SELECT bug_id FROM bugs WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)".
|
|
" AND delta_ts < $time ORDER BY bug_id"
|
|
);
|
|
|
|
Status('send_bugmail_status', { bug_count => scalar @$list });
|
|
|
|
# We cannot simply look at the bugs_activity table to find who did the
|
|
# last change in a given bug, as e.g. adding a comment doesn't add any
|
|
# entry to this table. And some other changes may be private
|
|
# (such as time-related changes or private attachments or comments)
|
|
# and so choosing this user as being the last one having done a change
|
|
# for the bug may be problematic. So the best we can do at this point
|
|
# is to choose the currently logged in user for email notification.
|
|
$vars->{changer} = Bugzilla->user->login;
|
|
|
|
foreach my $bugid (@$list)
|
|
{
|
|
Bugzilla::BugMail::Send($bugid, $vars);
|
|
}
|
|
|
|
Status('send_bugmail_end') if scalar(@$list);
|
|
|
|
unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE)
|
|
{
|
|
$template->process('global/footer.html.tmpl', $vars)
|
|
|| ThrowTemplateError($template->error());
|
|
}
|
|
exit;
|
|
}
|
|
|
|
###########################################################################
|
|
# Remove all references to deleted bugs
|
|
###########################################################################
|
|
|
|
if ($ARGS->{remove_invalid_bug_references})
|
|
{
|
|
Status('bug_reference_deletion_start');
|
|
|
|
$dbh->bz_start_transaction();
|
|
|
|
foreach my $pair (
|
|
'attachments/', 'bug_group_map/', 'bugs_activity/',
|
|
'bugs_fulltext/', 'cc/',
|
|
'dependencies/blocked', 'dependencies/dependson',
|
|
'duplicates/dupe', 'duplicates/dupe_of',
|
|
'flags/', 'keywords/', 'longdescs/', 'votes/')
|
|
{
|
|
my ($table, $field) = split('/', $pair);
|
|
$field ||= "bug_id";
|
|
|
|
my $bug_ids = $dbh->selectcol_arrayref(
|
|
"SELECT $table.$field FROM $table".
|
|
" LEFT JOIN bugs ON $table.$field = bugs.bug_id".
|
|
" WHERE bugs.bug_id IS NULL"
|
|
);
|
|
if (scalar @$bug_ids)
|
|
{
|
|
$dbh->do("DELETE FROM $table WHERE $field IN (" . join(',', @$bug_ids) . ")");
|
|
}
|
|
}
|
|
|
|
$dbh->bz_commit_transaction();
|
|
Status('bug_reference_deletion_end');
|
|
}
|
|
|
|
###########################################################################
|
|
# Remove all references to deleted users or groups from whines
|
|
###########################################################################
|
|
|
|
if ($ARGS->{remove_old_whine_targets})
|
|
{
|
|
Status('whines_obsolete_target_deletion_start');
|
|
$dbh->bz_start_transaction();
|
|
foreach my $target (['groups', 'id', MAILTO_GROUP], ['profiles', 'userid', MAILTO_USER])
|
|
{
|
|
my ($table, $col, $type) = @$target;
|
|
my $old_ids = $dbh->selectcol_arrayref(
|
|
"SELECT DISTINCT mailto FROM whine_schedules".
|
|
" LEFT JOIN $table ON $table.$col = whine_schedules.mailto".
|
|
" WHERE mailto_type = $type AND $table.$col IS NULL"
|
|
);
|
|
if (scalar(@$old_ids))
|
|
{
|
|
$dbh->do(
|
|
"DELETE FROM whine_schedules WHERE mailto_type = $type".
|
|
" AND mailto IN (" . join(',', @$old_ids) . ")"
|
|
);
|
|
}
|
|
}
|
|
$dbh->bz_commit_transaction();
|
|
Status('whines_obsolete_target_deletion_end');
|
|
}
|
|
|
|
###########################################################################
|
|
# Repair hook
|
|
###########################################################################
|
|
|
|
Bugzilla::Hook::process('sanitycheck_repair', { status => \&Status });
|
|
|
|
###########################################################################
|
|
# Checks
|
|
###########################################################################
|
|
Status('checks_start');
|
|
|
|
###########################################################################
|
|
# Perform referential (cross) checks
|
|
###########################################################################
|
|
|
|
# This checks that a simple foreign key has a valid primary key value.
|
|
# NULL references are acceptable and cause no problem.
|
|
# FIXME: CrossCheck is useless on DBMSes with foreign key support (mostly on ALL DBMSes).
|
|
sub CrossCheck
|
|
{
|
|
my $table = shift @_;
|
|
my $field = shift @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
Status('cross_check_to', { table => $table, field => $field });
|
|
|
|
while (@_)
|
|
{
|
|
my $ref = shift @_;
|
|
my ($refertable, $referfield, $keyname) = @$ref;
|
|
|
|
Status('cross_check_from', {table => $refertable, field => $referfield});
|
|
|
|
my $query = "SELECT DISTINCT $refertable.$referfield".
|
|
($keyname ? ", $refertable.$keyname" : "").
|
|
" FROM $refertable LEFT JOIN $table ON $refertable.$referfield = $table.$field".
|
|
" WHERE $table.$field IS NULL AND $refertable.$referfield IS NOT NULL";
|
|
|
|
my $sth = $dbh->prepare($query);
|
|
$sth->execute;
|
|
|
|
my $has_bad_references = 0;
|
|
|
|
while (my ($value, $key) = $sth->fetchrow_array)
|
|
{
|
|
Status('cross_check_alert', {
|
|
value => $value,
|
|
table => $refertable,
|
|
field => $referfield,
|
|
keyname => $keyname,
|
|
key => $key,
|
|
}, 'alert');
|
|
$has_bad_references = 1;
|
|
}
|
|
# References to non existent bugs can be safely removed, bug 288461
|
|
if ($table eq 'bugs' && $has_bad_references)
|
|
{
|
|
Status('cross_check_bug_has_references');
|
|
}
|
|
# References to non existent attachments can be safely removed.
|
|
if ($table eq 'attachments' && $has_bad_references)
|
|
{
|
|
Status('cross_check_attachment_has_references');
|
|
}
|
|
}
|
|
}
|
|
|
|
my $sch = Bugzilla->dbh->_bz_schema;
|
|
for my $table (keys %$sch)
|
|
{
|
|
my %fields = @{$sch->{$table}->{FIELDS} || []};
|
|
for my $f (keys %fields)
|
|
{
|
|
if (my $r = $fields{$f}{REFERENCES})
|
|
{
|
|
CrossCheck($r->{TABLE}, $r->{COLUMN}, [ $table, $f ]);
|
|
}
|
|
}
|
|
}
|
|
|
|
###########################################################################
|
|
# Perform double field referential (cross) checks
|
|
###########################################################################
|
|
|
|
# This checks that a compound two-field foreign key has a valid primary key
|
|
# value. NULL references are acceptable and cause no problem.
|
|
#
|
|
# The first parameter is the primary key table name.
|
|
# The second parameter is the primary key first field name.
|
|
# The third parameter is the primary key second field name.
|
|
# Each successive parameter represents a foreign key, it must be a list
|
|
# reference, where the list has:
|
|
# the first value is the foreign key table name
|
|
# the second value is the foreign key first field name.
|
|
# the third value is the foreign key second field name.
|
|
# the fourth value is optional and represents a field on the foreign key
|
|
# table to display when the check fails
|
|
sub DoubleCrossCheck
|
|
{
|
|
my $table = shift @_;
|
|
my $field1 = shift @_;
|
|
my $field2 = shift @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
|
|
Status('double_cross_check_to', { table => $table, field1 => $field1, field2 => $field2 });
|
|
|
|
while (@_)
|
|
{
|
|
my $ref = shift @_;
|
|
my ($refertable, $referfield1, $referfield2, $keyname) = @$ref;
|
|
|
|
Status('double_cross_check_from', { table => $refertable, field1 => $referfield1, field2 => $referfield2 });
|
|
|
|
my $d_cross_check = $dbh->selectall_arrayref(
|
|
"SELECT DISTINCT $refertable.$referfield1, $refertable.$referfield2" .
|
|
($keyname ? ", $refertable.$keyname" : "") .
|
|
" FROM $refertable LEFT JOIN $table ON $refertable.$referfield1 = $table.$field1".
|
|
" AND $refertable.$referfield2 = $table.$field2".
|
|
" WHERE $table.$field1 IS NULL AND $table.$field2 IS NULL".
|
|
" AND $refertable.$referfield1 IS NOT NULL AND $refertable.$referfield2 IS NOT NULL"
|
|
);
|
|
foreach my $check (@$d_cross_check)
|
|
{
|
|
my ($value1, $value2, $key) = @$check;
|
|
Status('double_cross_check_alert', {
|
|
value1 => $value1,
|
|
value2 => $value2,
|
|
table => $refertable,
|
|
field1 => $referfield1,
|
|
field2 => $referfield2,
|
|
keyname => $keyname,
|
|
key => $key,
|
|
}, 'alert');
|
|
}
|
|
}
|
|
}
|
|
|
|
DoubleCrossCheck(
|
|
'attachments', 'bug_id', 'attach_id',
|
|
['flags', 'bug_id', 'attach_id'],
|
|
['bugs_activity', 'bug_id', 'attach_id']
|
|
);
|
|
DoubleCrossCheck(
|
|
'components', 'product_id', 'id',
|
|
['bugs', 'product_id', 'component_id', 'bug_id'],
|
|
['flagexclusions', 'product_id', 'component_id'],
|
|
['flaginclusions', 'product_id', 'component_id']
|
|
);
|
|
DoubleCrossCheck(
|
|
'versions', 'product_id', 'id',
|
|
['bugs', 'product_id', 'version', 'bug_id']
|
|
);
|
|
DoubleCrossCheck(
|
|
'milestones', 'product_id', 'id',
|
|
['bugs', 'product_id', 'target_milestone', 'bug_id']
|
|
);
|
|
|
|
###########################################################################
|
|
# Perform login checks
|
|
###########################################################################
|
|
|
|
Status('profile_login_start');
|
|
|
|
my $sth = $dbh->prepare("SELECT userid, login_name FROM profiles");
|
|
$sth->execute;
|
|
while (my ($id, $email) = $sth->fetchrow_array)
|
|
{
|
|
validate_email_syntax($email) || Status('profile_login_alert', { id => $id, email => $email }, 'alert');
|
|
}
|
|
|
|
###########################################################################
|
|
# Perform vote/keyword cache checks
|
|
###########################################################################
|
|
|
|
check_votes_or_keywords();
|
|
|
|
sub check_votes_or_keywords
|
|
{
|
|
my $check = shift || 'all';
|
|
|
|
my $dbh = Bugzilla->dbh;
|
|
my $sth = $dbh->prepare("SELECT bug_id, votes FROM bugs WHERE votes != 0");
|
|
$sth->execute;
|
|
|
|
my %votes;
|
|
while (my ($id, $v, $k) = $sth->fetchrow_array)
|
|
{
|
|
$votes{$id} = $v;
|
|
}
|
|
|
|
Status('vote_count_start');
|
|
$sth = $dbh->prepare(
|
|
"SELECT bug_id, SUM(vote_count) FROM votes GROUP BY bug_id"
|
|
);
|
|
$sth->execute;
|
|
|
|
my $offer_votecache_rebuild = 0;
|
|
while (my ($id, $v) = $sth->fetchrow_array)
|
|
{
|
|
if ($v <= 0)
|
|
{
|
|
Status('vote_count_alert', { id => $id }, 'alert');
|
|
}
|
|
else
|
|
{
|
|
if (!defined $votes{$id} || $votes{$id} != $v)
|
|
{
|
|
Status('vote_cache_alert', { id => $id }, 'alert');
|
|
$offer_votecache_rebuild = 1;
|
|
}
|
|
delete $votes{$id};
|
|
}
|
|
}
|
|
foreach my $id (keys %votes)
|
|
{
|
|
Status('vote_cache_alert', { id => $id }, 'alert');
|
|
$offer_votecache_rebuild = 1;
|
|
}
|
|
|
|
Status('vote_cache_rebuild_fix') if $offer_votecache_rebuild;
|
|
}
|
|
|
|
###########################################################################
|
|
# Check for flags being in incorrect products and components
|
|
###########################################################################
|
|
|
|
Status('flag_check_start');
|
|
|
|
my $invalid_flags = $dbh->selectall_arrayref(
|
|
'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id FROM flags'.
|
|
' INNER JOIN bugs ON flags.bug_id = bugs.bug_id'.
|
|
' LEFT JOIN flaginclusions AS i ON flags.type_id = i.type_id'.
|
|
' AND (bugs.product_id = i.product_id OR i.product_id IS NULL)'.
|
|
' AND (bugs.component_id = i.component_id OR i.component_id IS NULL)'.
|
|
'WHERE i.type_id IS NULL'
|
|
);
|
|
|
|
my @invalid_flags = @$invalid_flags;
|
|
|
|
$invalid_flags = $dbh->selectall_arrayref(
|
|
'SELECT DISTINCT flags.id, flags.bug_id, flags.attach_id FROM flags'.
|
|
' INNER JOIN bugs ON flags.bug_id = bugs.bug_id'.
|
|
' INNER JOIN flagexclusions AS e ON flags.type_id = e.type_id'.
|
|
' WHERE (bugs.product_id = e.product_id OR e.product_id IS NULL)'.
|
|
' AND (bugs.component_id = e.component_id OR e.component_id IS NULL)'
|
|
);
|
|
|
|
push @invalid_flags, @$invalid_flags;
|
|
|
|
if (@invalid_flags)
|
|
{
|
|
if ($ARGS->{remove_invalid_flags})
|
|
{
|
|
Status('flag_deletion_start');
|
|
my @flag_ids = map { $_->[0] } @invalid_flags;
|
|
# Silently delete these flags, with no notification to requesters/setters.
|
|
$dbh->do('DELETE FROM flags WHERE id IN (' . join(',', @flag_ids) .')');
|
|
Status('flag_deletion_end');
|
|
}
|
|
else
|
|
{
|
|
foreach my $flag (@$invalid_flags)
|
|
{
|
|
my ($flag_id, $bug_id, $attach_id) = @$flag;
|
|
Status('flag_alert', { flag_id => $flag_id, attach_id => $attach_id, bug_id => $bug_id }, 'alert');
|
|
}
|
|
Status('flag_fix');
|
|
}
|
|
}
|
|
|
|
###########################################################################
|
|
# General bug checks
|
|
###########################################################################
|
|
|
|
sub BugCheck
|
|
{
|
|
my ($middlesql, $errortext, $repairparam, $repairtext) = @_;
|
|
my $dbh = Bugzilla->dbh;
|
|
my $badbugs = $dbh->selectcol_arrayref(
|
|
"SELECT DISTINCT bugs.bug_id FROM $middlesql ORDER BY bugs.bug_id"
|
|
);
|
|
if (scalar(@$badbugs))
|
|
{
|
|
Status('bug_check_alert', { errortext => get_string($errortext), badbugs => $badbugs }, 'alert');
|
|
if ($repairparam)
|
|
{
|
|
$repairtext ||= 'repair_bugs';
|
|
Status('bug_check_repair', { param => $repairparam, text => get_string($repairtext) });
|
|
}
|
|
}
|
|
}
|
|
|
|
Status('bug_check_creation_date');
|
|
|
|
BugCheck(
|
|
"bugs WHERE creation_ts IS NULL", 'bug_check_creation_date_error_text',
|
|
'repair_creation_date', 'bug_check_creation_date_repair_text'
|
|
);
|
|
|
|
if (!Bugzilla->localconfig->{sphinx_index})
|
|
{
|
|
Status('bug_check_bugs_fulltext');
|
|
BugCheck(
|
|
"bugs LEFT JOIN bugs_fulltext ON bugs_fulltext.bug_id = bugs.bug_id " .
|
|
"WHERE bugs_fulltext.bug_id IS NULL", 'bug_check_bugs_fulltext_error_text',
|
|
'repair_bugs_fulltext', 'bug_check_bugs_fulltext_repair_text'
|
|
);
|
|
}
|
|
|
|
Status('bug_check_res_dupl');
|
|
|
|
BugCheck(
|
|
"bugs INNER JOIN duplicates ON bugs.bug_id = duplicates.dupe " .
|
|
"WHERE bugs.resolution != 'DUPLICATE'", 'bug_check_res_dupl_error_text'
|
|
);
|
|
|
|
BugCheck(
|
|
"bugs LEFT JOIN duplicates ON bugs.bug_id = duplicates.dupe WHERE " .
|
|
"bugs.resolution = 'DUPLICATE' AND duplicates.dupe IS NULL", 'bug_check_res_dupl_error_text2'
|
|
);
|
|
|
|
Status('bug_check_status_res');
|
|
|
|
my @open_states = map($_->id, grep { $_->is_open } Bugzilla::Status->get_all);
|
|
my $open_states = join(', ', @open_states);
|
|
|
|
BugCheck(
|
|
"bugs WHERE bug_status IN ($open_states) AND resolution IS NOT NULL",
|
|
'bug_check_status_res_error_text'
|
|
);
|
|
BugCheck(
|
|
"bugs WHERE bug_status NOT IN ($open_states) AND resolution IS NULL",
|
|
'bug_check_status_res_error_text2'
|
|
);
|
|
|
|
Status('bug_check_status_everconfirmed');
|
|
|
|
my $unconfirmed_states = join(', ', map { $_->id } grep { !$_->is_confirmed } Bugzilla::Status->get_all);
|
|
|
|
BugCheck(
|
|
"bugs WHERE bug_status IN ($unconfirmed_states) AND everconfirmed = 1",
|
|
'bug_check_status_everconfirmed_error_text', 'repair_everconfirmed'
|
|
);
|
|
|
|
my $confirmed_states = join(', ', map { $_->id } grep { $_->is_confirmed } Bugzilla::Status->get_all);
|
|
|
|
BugCheck(
|
|
"bugs WHERE bug_status IN ($confirmed_states) AND everconfirmed = 0",
|
|
'bug_check_status_everconfirmed_error_text2', 'repair_everconfirmed'
|
|
);
|
|
|
|
Status('bug_check_votes_everconfirmed');
|
|
|
|
BugCheck(
|
|
"bugs INNER JOIN products ON bugs.product_id = products.id " .
|
|
"WHERE everconfirmed = 0 AND votestoconfirm > 0 AND votestoconfirm <= votes",
|
|
'bug_check_votes_everconfirmed_error_text'
|
|
);
|
|
|
|
###########################################################################
|
|
# Control Values
|
|
###########################################################################
|
|
|
|
# Checks for values that are invalid OR
|
|
# not among the 9 valid combinations
|
|
Status('bug_check_control_values');
|
|
my $groups = join(", ", (CONTROLMAPNA, CONTROLMAPSHOWN, CONTROLMAPDEFAULT, CONTROLMAPMANDATORY));
|
|
my $query = "SELECT COUNT(product_id) FROM group_control_map".
|
|
" WHERE membercontrol NOT IN ($groups)".
|
|
" OR othercontrol NOT IN ($groups) OR ((membercontrol != othercontrol)".
|
|
" AND (membercontrol != " . CONTROLMAPSHOWN . ")".
|
|
" AND ((membercontrol != " . CONTROLMAPDEFAULT . ")".
|
|
" OR (othercontrol = " . CONTROLMAPSHOWN . ")))";
|
|
|
|
my $entries = $dbh->selectrow_array($query);
|
|
Status('bug_check_control_values_alert', { entries => $entries }, 'alert') if $entries;
|
|
|
|
Status('bug_check_control_values_violation');
|
|
BugCheck(
|
|
"bugs INNER JOIN bug_group_map ON bugs.bug_id = bug_group_map.bug_id".
|
|
" LEFT JOIN group_control_map ON bugs.product_id = group_control_map.product_id".
|
|
" AND bug_group_map.group_id = group_control_map.group_id".
|
|
" WHERE ((group_control_map.membercontrol = " . CONTROLMAPNA . ")".
|
|
" OR (group_control_map.membercontrol IS NULL))",
|
|
'bug_check_control_values_error_text',
|
|
'createmissinggroupcontrolmapentries',
|
|
'bug_check_control_values_repair_text'
|
|
);
|
|
|
|
BugCheck(
|
|
"bugs INNER JOIN group_control_map ON bugs.product_id = group_control_map.product_id".
|
|
" INNER JOIN groups ON group_control_map.group_id = groups.id".
|
|
" LEFT JOIN bug_group_map ON bugs.bug_id = bug_group_map.bug_id".
|
|
" AND group_control_map.group_id = bug_group_map.group_id".
|
|
" WHERE group_control_map.membercontrol = " . CONTROLMAPMANDATORY .
|
|
" AND bug_group_map.group_id IS NULL AND groups.isactive != 0",
|
|
'bug_check_control_values_error_text2'
|
|
);
|
|
|
|
###########################################################################
|
|
# Unsent mail
|
|
###########################################################################
|
|
|
|
Status('unsent_bugmail_check');
|
|
|
|
my $time = $dbh->sql_date_math('NOW()', '-', 30, 'MINUTE');
|
|
my $badbugs = $dbh->selectcol_arrayref(
|
|
"SELECT bug_id FROM bugs WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)".
|
|
" AND delta_ts < $time ORDER BY bug_id"
|
|
);
|
|
if (@$badbugs)
|
|
{
|
|
Status('unsent_bugmail_alert', { badbugs => $badbugs }, 'alert');
|
|
Status('unsent_bugmail_fix');
|
|
}
|
|
|
|
###########################################################################
|
|
# Whines
|
|
###########################################################################
|
|
|
|
Status('whines_obsolete_target_start');
|
|
|
|
my $display_repair_whines_link = 0;
|
|
foreach my $target (['groups', 'id', MAILTO_GROUP], ['profiles', 'userid', MAILTO_USER])
|
|
{
|
|
my ($table, $col, $type) = @$target;
|
|
my $old = $dbh->selectall_arrayref(
|
|
"SELECT whine_schedules.id, mailto FROM whine_schedules".
|
|
" LEFT JOIN $table ON $table.$col = whine_schedules.mailto".
|
|
" WHERE mailto_type = $type AND $table.$col IS NULL"
|
|
);
|
|
if (scalar @$old)
|
|
{
|
|
Status('whines_obsolete_target_alert', { schedules => $old, type => $type }, 'alert');
|
|
$display_repair_whines_link = 1;
|
|
}
|
|
}
|
|
Status('whines_obsolete_target_fix') if $display_repair_whines_link;
|
|
|
|
###########################################################################
|
|
# Check hook
|
|
###########################################################################
|
|
|
|
Bugzilla::Hook::process('sanitycheck_check', { status => \&Status });
|
|
|
|
###########################################################################
|
|
# End
|
|
###########################################################################
|
|
|
|
Status('checks_completed');
|
|
|
|
unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE)
|
|
{
|
|
$template->process('global/footer.html.tmpl', $vars)
|
|
|| ThrowTemplateError($template->error());
|
|
exit;
|
|
}
|