#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is the Mozilla
# Corporation. Portions created by Mozilla are
# Copyright (C) 2006 Mozilla Foundation. All Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
#                 Alex Brugh <alex@cs.umn.edu>
#                 Dave Miller <justdave@mozilla.com>
#                 Byron Jones <glob@mozilla.com>

use strict;

use lib qw(.);

use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Util;

use Getopt::Long;

my $dbh = Bugzilla->dbh;

# This SQL is designed to sanitize a copy of a Bugzilla database so that it 
# doesn't contain any information that can't be viewed from a web browser by
# a user who is not logged in.                                              

# Last validated against Bugzilla version 4.0

my ($dry_run, $from_cron, $keep_attachments, $keep_groups,
    $keep_passwords, $keep_insider, $trace) = (0, 0, 0, '', 0, 0, 0);
my $keep_groups_sql = '';

GetOptions(
    "dry-run" => \$dry_run,
    "from-cron" => \$from_cron,
    "keep-attachments" => \$keep_attachments,
    "keep-passwords" => \$keep_passwords,
    "keep-insider" => \$keep_insider,
    "keep-groups:s" => \$keep_groups,
    "trace" => \$trace,
) or exit;

if ($keep_groups ne '') {
    my @groups;
    foreach my $group_id (split(/\s*,\s*/, $keep_groups)) {
        my $group;
        if ($group_id =~ /\D/) {
            $group = Bugzilla::Group->new({ name => $group_id });
        } else {
            $group = Bugzilla::Group->new($group_id);
        }
        die "Invalid group '$group_id'\n" unless $group;
        push @groups, $group->id;
    }
    $keep_groups_sql = "NOT IN (" . join(",", @groups) . ")";
}

$dbh->{TraceLevel} = 1 if $trace;

if ($dry_run) {
    print "** dry run : no changes to the database will be made **\n";
    $dbh->bz_start_transaction();
}
eval {
    delete_non_public_products();
    delete_secure_bugs();
    delete_insider_comments() unless $keep_insider;
    delete_security_groups();
    delete_sensitive_user_data();
    delete_attachment_data() unless $keep_attachments;
    print "All done!\n";
    $dbh->bz_rollback_transaction() if $dry_run;
};
if ($@) {
    $dbh->bz_rollback_transaction() if $dry_run;
    die "$@" if $@;
}

sub delete_non_public_products {
    # Delete all non-public products, and all data associated with them
    my @products = Bugzilla::Product->get_all();
    my $mandatory = CONTROLMAPMANDATORY;
    foreach my $product (@products) {
        # if there are any mandatory groups on the product, nuke it and
        # everything associated with it (including the bugs)
        Bugzilla->params->{'allowbugdeletion'} = 1; # override this in memory for now
        my $mandatorygroups = $dbh->selectcol_arrayref("SELECT group_id FROM group_control_map WHERE product_id = ? AND (membercontrol = $mandatory)", undef, $product->id);
        if (0 < scalar(@$mandatorygroups)) {
            print "Deleting product '" . $product->name . "'...\n";
            $product->remove_from_db();
        }
    }
}

sub delete_secure_bugs {
    # Delete all data for bugs in security groups.
    my $buglist = $dbh->selectall_arrayref(
        $keep_groups
        ? "SELECT DISTINCT bug_id FROM bug_group_map  WHERE group_id $keep_groups_sql"
        : "SELECT DISTINCT bug_id FROM bug_group_map"
    );
    $|=1; # disable buffering so the bug progress counter works
    my $numbugs = scalar(@$buglist);
    my $bugnum = 0;
    print "Deleting $numbugs bugs in " . ($keep_groups ? 'non-' : '') . "security groups...\n";
    foreach my $row (@$buglist) {
        my $bug_id = $row->[0];
        $bugnum++;
        print "\r$bugnum/$numbugs" unless $from_cron;
        my $bug = new Bugzilla::Bug($bug_id);
        $bug->remove_from_db();
    }
    print "\rDone            \n" unless $from_cron;
}

sub delete_insider_comments {
    # Delete all 'insidergroup' comments and attachments
    print "Deleting 'insidergroup' comments and attachments...\n";
    $dbh->do("DELETE FROM longdescs WHERE isprivate = 1");
    $dbh->do("DELETE attach_data FROM attachments JOIN attach_data ON attachments.attach_id = attach_data.id WHERE attachments.isprivate = 1");
    $dbh->do("DELETE FROM attachments WHERE isprivate = 1");
    $dbh->do("UPDATE bugs_fulltext SET comments = comments_noprivate");
}

sub delete_security_groups {
    # Delete all security groups.
    print "Deleting " . ($keep_groups ? 'non-' : '') . "security groups...\n";
    $dbh->do("DELETE user_group_map FROM groups JOIN user_group_map ON groups.id = user_group_map.group_id WHERE groups.isbuggroup = 1");
    $dbh->do("DELETE group_group_map FROM groups JOIN group_group_map ON (groups.id = group_group_map.member_id OR groups.id = group_group_map.grantor_id) WHERE groups.isbuggroup = 1");
    $dbh->do("DELETE group_control_map FROM groups JOIN group_control_map ON groups.id = group_control_map.group_id WHERE groups.isbuggroup = 1");
    $dbh->do("UPDATE flagtypes LEFT JOIN groups ON flagtypes.grant_group_id = groups.id SET grant_group_id = NULL WHERE groups.isbuggroup = 1");
    $dbh->do("UPDATE flagtypes LEFT JOIN groups ON flagtypes.request_group_id = groups.id SET request_group_id = NULL WHERE groups.isbuggroup = 1");
    if ($keep_groups) {
        $dbh->do("DELETE FROM groups WHERE isbuggroup = 1 AND id $keep_groups_sql");
    } else {
        $dbh->do("DELETE FROM groups WHERE isbuggroup = 1");
    }
}

sub delete_sensitive_user_data {
    # Remove sensitive user account data.
    print "Deleting sensitive user account data...\n";
    $dbh->do("UPDATE profiles SET cryptpassword = 'deleted'") unless $keep_passwords;
    $dbh->do("DELETE FROM profiles_activity");
    $dbh->do("DELETE FROM profile_search");
    $dbh->do("DELETE FROM namedqueries");
    $dbh->do("DELETE FROM tokens");
    $dbh->do("DELETE FROM logincookies");
    $dbh->do("DELETE FROM login_failure");
    $dbh->do("DELETE FROM ts_error");
    $dbh->do("DELETE FROM ts_exitstatus");
    $dbh->do("DELETE FROM ts_funcmap");
    $dbh->do("DELETE FROM ts_job");
    $dbh->do("DELETE FROM ts_note");
}

sub delete_attachment_data {
    # Delete unnecessary attachment data.
    print "Removing attachment data to preserve disk space...\n";
    $dbh->do("UPDATE attach_data SET thedata = ''");
}