1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072 |
- #!/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): 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::Util;
- use Bugzilla::Error;
- 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 $cgi = Bugzilla->cgi;
- return if (!$alert && Bugzilla->usage_mode == USAGE_MODE_CMDLINE && !$cgi->param('verbose'));
- if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- my $output = $cgi->param('output') || '';
- my $linebreak = $alert ? "\nALERT: " : "\n";
- $cgi->param('error_found', 1) if $alert;
- $cgi->param('output', $output . $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 $cgi = Bugzilla->cgi;
- 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 = {};
- print $cgi->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")
- || ($user->in_group('editkeywords') && $cgi->param('rebuildkeywordcache'))
- || 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());
- }
- ###########################################################################
- # Users with 'editkeywords' privs only can only check keywords.
- ###########################################################################
- unless ($user->in_group('editcomponents')) {
- check_votes_or_keywords('keywords');
- Status('checks_completed');
- $template->process('global/footer.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- exit;
- }
- ###########################################################################
- # Fix vote cache
- ###########################################################################
- if ($cgi->param('rebuildvotecache')) {
- Status('vote_cache_rebuild_start');
- $dbh->bz_start_transaction();
- $dbh->do(q{UPDATE bugs SET votes = 0});
- my $sth_update = $dbh->prepare(q{UPDATE bugs
- SET votes = ?
- WHERE bug_id = ?});
- my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
- FROM votes }. $dbh->sql_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 ($cgi->param('createmissinggroupcontrolmapentries')) {
- Status('group_control_map_entries_creation');
- my $na = CONTROLMAPNA;
- my $shown = CONTROLMAPSHOWN;
- my $insertsth = $dbh->prepare(
- qq{INSERT INTO group_control_map (
- group_id, product_id, entry,
- membercontrol, othercontrol, canedit
- )
- VALUES (
- ?, ?, 0,
- $shown, $na, 0
- )});
- my $updatesth = $dbh->prepare(qq{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(
- qq{ 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 ($cgi->param('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 entries in Bugs full_text
- ###########################################################################
- if ($cgi->param('repair_bugs_fulltext')) {
- 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 ($cgi->param('rescanallBugMail')) {
- require Bugzilla::BugMail;
- Status('send_bugmail_start');
- my $time = $dbh->sql_interval(30, 'MINUTE');
- my $list = $dbh->selectcol_arrayref(qq{
- SELECT bug_id
- FROM bugs
- WHERE (lastdiffed IS NULL
- OR lastdiffed < delta_ts)
- AND delta_ts < now() - $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 ($cgi->param('remove_invalid_bug_references')) {
- Status('bug_reference_deletion_start');
- $dbh->bz_start_transaction();
- # Include custom multi-select fields to the list.
- my @multi_selects = Bugzilla->get_fields({custom => 1, type => FIELD_TYPE_MULTI_SELECT});
- my @addl_fields = map { 'bug_' . $_->name . '/' } @multi_selects;
- 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/',
- @addl_fields)
- {
- 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 attachments
- ###########################################################################
- if ($cgi->param('remove_invalid_attach_references')) {
- Status('attachment_reference_deletion_start');
- $dbh->bz_start_transaction();
- my $attach_ids =
- $dbh->selectcol_arrayref('SELECT attach_data.id
- FROM attach_data
- LEFT JOIN attachments
- ON attachments.attach_id = attach_data.id
- WHERE attachments.attach_id IS NULL');
- if (scalar(@$attach_ids)) {
- $dbh->do('DELETE FROM attach_data WHERE id IN (' .
- join(',', @$attach_ids) . ')');
- }
- $dbh->bz_commit_transaction();
- Status('attachment_reference_deletion_end');
- }
- ###########################################################################
- # Remove all references to deleted users or groups from whines
- ###########################################################################
- if ($cgi->param('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');
- }
- 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.
- #
- # The first parameter is the primary key table name.
- # The second parameter is the primary key 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 field name.
- # the third value is optional and represents a field on the foreign key
- # table to display when the check fails.
- # the fourth value is optional and is a list reference to values that
- # are excluded from checking.
- #
- # FIXME: The excluded values parameter should go away - the QA contact
- # fields should use NULL instead - see bug #109474.
- # The same goes for series; no bug for that yet.
- 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, $exceptions) = @$ref;
- $exceptions ||= [];
- my %exceptions = map { $_ => 1 } @$exceptions;
- Status('cross_check_from', {table => $refertable, field => $referfield});
- my $query = qq{SELECT DISTINCT $refertable.$referfield} .
- ($keyname ? qq{, $refertable.$keyname } : q{}) .
- qq{ 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) {
- next if $exceptions{$value};
- 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');
- }
- }
- }
- CrossCheck('classifications', 'id',
- ['products', 'classification_id']);
- CrossCheck("keyworddefs", "id",
- ["keywords", "keywordid"]);
- CrossCheck("fielddefs", "id",
- ["bugs_activity", "fieldid"],
- ['profiles_activity', 'fieldid']);
- CrossCheck("flagtypes", "id",
- ["flags", "type_id"],
- ["flagexclusions", "type_id"],
- ["flaginclusions", "type_id"]);
- # Include custom multi-select fields to the list.
- my @multi_selects = Bugzilla->get_fields({custom => 1, type => FIELD_TYPE_MULTI_SELECT});
- my @addl_fields = map { ['bug_' . $_->name, 'bug_id'] } @multi_selects;
- CrossCheck("bugs", "bug_id",
- ["bugs_activity", "bug_id"],
- ["bug_group_map", "bug_id"],
- ["bugs_fulltext", "bug_id"],
- ["attachments", "bug_id"],
- ["cc", "bug_id"],
- ["longdescs", "bug_id"],
- ["dependencies", "blocked"],
- ["dependencies", "dependson"],
- ['flags', 'bug_id'],
- ["votes", "bug_id"],
- ["keywords", "bug_id"],
- ["duplicates", "dupe_of", "dupe"],
- ["duplicates", "dupe", "dupe_of"],
- @addl_fields);
- CrossCheck("groups", "id",
- ["bug_group_map", "group_id"],
- ['category_group_map', 'group_id'],
- ["group_group_map", "grantor_id"],
- ["group_group_map", "member_id"],
- ["group_control_map", "group_id"],
- ["namedquery_group_map", "group_id"],
- ["user_group_map", "group_id"],
- ["flagtypes", "grant_group_id"],
- ["flagtypes", "request_group_id"]);
- CrossCheck("namedqueries", "id",
- ["namedqueries_link_in_footer", "namedquery_id"],
- ["namedquery_group_map", "namedquery_id"],
- );
- CrossCheck("profiles", "userid",
- ['profiles_activity', 'userid'],
- ['profiles_activity', 'who'],
- ['email_setting', 'user_id'],
- ['profile_setting', 'user_id'],
- ["bugs", "reporter", "bug_id"],
- ["bugs", "assigned_to", "bug_id"],
- ["bugs", "qa_contact", "bug_id"],
- ["attachments", "submitter_id", "bug_id"],
- ['flags', 'setter_id', 'bug_id'],
- ['flags', 'requestee_id', 'bug_id'],
- ["bugs_activity", "who", "bug_id"],
- ["cc", "who", "bug_id"],
- ['quips', 'userid'],
- ["votes", "who", "bug_id"],
- ["longdescs", "who", "bug_id"],
- ["logincookies", "userid"],
- ["namedqueries", "userid"],
- ["namedqueries_link_in_footer", "user_id"],
- ['series', 'creator', 'series_id'],
- ["watch", "watcher"],
- ["watch", "watched"],
- ['whine_events', 'owner_userid'],
- ["tokens", "userid"],
- ["user_group_map", "user_id"],
- ["components", "initialowner", "name"],
- ["components", "initialqacontact", "name"],
- ["component_cc", "user_id"]);
- CrossCheck("products", "id",
- ["bugs", "product_id", "bug_id"],
- ["components", "product_id", "name"],
- ["milestones", "product_id", "value"],
- ["versions", "product_id", "value"],
- ["group_control_map", "product_id"],
- ["flaginclusions", "product_id", "type_id"],
- ["flagexclusions", "product_id", "type_id"]);
- CrossCheck("components", "id",
- ["component_cc", "component_id"],
- ["flagexclusions", "component_id", "type_id"],
- ["flaginclusions", "component_id", "type_id"]);
- # Check the former enum types -mkanat@bugzilla.org
- CrossCheck("bug_status", "value",
- ["bugs", "bug_status", "bug_id"]);
- CrossCheck("resolution", "value",
- ["bugs", "resolution", "bug_id"]);
- CrossCheck("bug_severity", "value",
- ["bugs", "bug_severity", "bug_id"]);
- CrossCheck("op_sys", "value",
- ["bugs", "op_sys", "bug_id"]);
- CrossCheck("priority", "value",
- ["bugs", "priority", "bug_id"]);
- CrossCheck("rep_platform", "value",
- ["bugs", "rep_platform", "bug_id"]);
- CrossCheck('series', 'series_id',
- ['series_data', 'series_id']);
- CrossCheck('series_categories', 'id',
- ['series', 'category'],
- ["category_group_map", "category_id"],
- ["series", "subcategory"]);
- CrossCheck('whine_events', 'id',
- ['whine_queries', 'eventid'],
- ['whine_schedules', 'eventid']);
- CrossCheck('attachments', 'attach_id',
- ['attach_data', 'id'],
- ['bugs_activity', 'attach_id']);
- CrossCheck('bug_status', 'id',
- ['status_workflow', 'old_status'],
- ['status_workflow', 'new_status']);
- ###########################################################################
- # 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(qq{
- SELECT DISTINCT $refertable.$referfield1,
- $refertable.$referfield2 } .
- ($keyname ? qq{, $refertable.$keyname } : q{}) .
- qq{ 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", "value",
- ["bugs", "product_id", "version", "bug_id"]);
-
- DoubleCrossCheck("milestones", "product_id", "value",
- ["bugs", "product_id", "target_milestone", "bug_id"],
- ["products", "id", "defaultmilestone", "name"]);
- ###########################################################################
- # Perform login checks
- ###########################################################################
- Status('profile_login_start');
- my $sth = $dbh->prepare(q{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(q{SELECT bug_id, votes, keywords
- FROM bugs
- WHERE votes != 0 OR keywords != ''});
- $sth->execute;
- my %votes;
- my %keyword;
- while (my ($id, $v, $k) = $sth->fetchrow_array) {
- if ($v != 0) {
- $votes{$id} = $v;
- }
- if ($k) {
- $keyword{$id} = $k;
- }
- }
- # If we only want to check keywords, skip checks about votes.
- _check_votes(\%votes) unless ($check eq 'keywords');
- # If we only want to check votes, skip checks about keywords.
- _check_keywords(\%keyword) unless ($check eq 'votes');
- }
- sub _check_votes {
- my $votes = shift;
- Status('vote_count_start');
- my $dbh = Bugzilla->dbh;
- my $sth = $dbh->prepare(q{SELECT bug_id, SUM(vote_count)
- FROM votes }.
- $dbh->sql_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;
- }
- sub _check_keywords {
- my $keyword = shift;
- Status('keyword_check_start');
- my $dbh = Bugzilla->dbh;
- my $cgi = Bugzilla->cgi;
- my %keywordids;
- my $keywords = $dbh->selectall_arrayref(q{SELECT id, name
- FROM keyworddefs});
- foreach (@$keywords) {
- my ($id, $name) = @$_;
- if ($keywordids{$id}) {
- Status('keyword_check_alert', {id => $id}, 'alert');
- }
- $keywordids{$id} = 1;
- if ($name =~ /[\s,]/) {
- Status('keyword_check_invalid_name', {id => $id}, 'alert');
- }
- }
- my $sth = $dbh->prepare(q{SELECT bug_id, keywordid
- FROM keywords
- ORDER BY bug_id, keywordid});
- $sth->execute;
- my $lastid;
- my $lastk;
- while (my ($id, $k) = $sth->fetchrow_array) {
- if (!$keywordids{$k}) {
- Status('keyword_check_invalid_id', {id => $k}, 'alert');
- }
- if (defined $lastid && $id eq $lastid && $k eq $lastk) {
- Status('keyword_check_duplicated_ids', {id => $id}, 'alert');
- }
- $lastid = $id;
- $lastk = $k;
- }
- Status('keyword_cache_start');
- if ($cgi->param('rebuildkeywordcache')) {
- $dbh->bz_start_transaction();
- }
- my $query = q{SELECT keywords.bug_id, keyworddefs.name
- FROM keywords
- INNER JOIN keyworddefs
- ON keyworddefs.id = keywords.keywordid
- INNER JOIN bugs
- ON keywords.bug_id = bugs.bug_id
- ORDER BY keywords.bug_id, keyworddefs.name};
- $sth = $dbh->prepare($query);
- $sth->execute;
- my $lastb = 0;
- my @list;
- my %realk;
- while (1) {
- my ($b, $k) = $sth->fetchrow_array;
- if (!defined $b || $b != $lastb) {
- if (@list) {
- $realk{$lastb} = join(', ', @list);
- }
- last unless $b;
- $lastb = $b;
- @list = ();
- }
- push(@list, $k);
- }
- my @badbugs = ();
- foreach my $b (keys(%$keyword)) {
- if (!exists $realk{$b} || $realk{$b} ne $keyword->{$b}) {
- push(@badbugs, $b);
- }
- }
- foreach my $b (keys(%realk)) {
- if (!exists $keyword->{$b}) {
- push(@badbugs, $b);
- }
- }
- if (@badbugs) {
- @badbugs = sort {$a <=> $b} @badbugs;
- if ($cgi->param('rebuildkeywordcache')) {
- my $sth_update = $dbh->prepare(q{UPDATE bugs
- SET keywords = ?
- WHERE bug_id = ?});
- Status('keyword_cache_fixing');
- foreach my $b (@badbugs) {
- my $k = '';
- if (exists($realk{$b})) {
- $k = $realk{$b};
- }
- $sth_update->execute($k, $b);
- }
- Status('keyword_cache_fixed');
- } else {
- Status('keyword_cache_alert', {badbugs => \@badbugs}, 'alert');
- Status('keyword_cache_rebuild');
- }
- }
- if ($cgi->param('rebuildkeywordcache')) {
- $dbh->bz_commit_transaction();
- }
- }
- ###########################################################################
- # 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 (scalar(@invalid_flags)) {
- if ($cgi->param('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(qq{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');
- 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($dbh->quote($_), BUG_STATE_OPEN);
- my $open_states = join(', ', @open_states);
- BugCheck("bugs WHERE bug_status IN ($open_states) AND resolution != ''",
- 'bug_check_status_res_error_text');
- BugCheck("bugs WHERE bug_status NOT IN ($open_states) AND resolution = ''",
- 'bug_check_status_res_error_text2');
- Status('bug_check_status_everconfirmed');
- BugCheck("bugs WHERE bug_status = 'UNCONFIRMED' AND everconfirmed = 1",
- 'bug_check_status_everconfirmed_error_text');
- my @confirmed_open_states = grep {$_ ne 'UNCONFIRMED'} BUG_STATE_OPEN;
- my $confirmed_open_states = join(', ', map {$dbh->quote($_)} @confirmed_open_states);
- BugCheck("bugs WHERE bug_status IN ($confirmed_open_states) AND everconfirmed = 0",
- 'bug_check_status_everconfirmed_error_text2');
- Status('bug_check_votes_everconfirmed');
- BugCheck("bugs INNER JOIN products ON bugs.product_id = products.id " .
- "WHERE everconfirmed = 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 = qq{
- SELECT COUNT(product_id)
- FROM group_control_map
- WHERE membercontrol NOT IN( $groups )
- OR othercontrol NOT IN( $groups )
- OR ((membercontrol != othercontrol)
- AND (membercontrol != } . CONTROLMAPSHOWN . q{)
- AND ((membercontrol != } . CONTROLMAPDEFAULT . q{)
- OR (othercontrol = } . CONTROLMAPSHOWN . q{)))};
- 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_interval(30, 'MINUTE');
- my $badbugs = $dbh->selectcol_arrayref(qq{
- SELECT bug_id
- FROM bugs
- WHERE (lastdiffed IS NULL OR lastdiffed < delta_ts)
- AND delta_ts < now() - $time
- ORDER BY bug_id});
- if (scalar(@$badbugs > 0)) {
- 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;
- ###########################################################################
- # End
- ###########################################################################
- Status('checks_completed');
- unless (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
- $template->process('global/footer.html.tmpl', $vars)
- || ThrowTemplateError($template->error());
- }
|