# -*- 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 OldBugMove Bugzilla Extension.
#
# The Initial Developer of the Original Code is Everything Solved, Inc.
# Portions created by the Initial Developer are Copyright (C) 2010 the
# Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Max Kanat-Alexander <mkanat@bugzilla.org>

package Bugzilla::Extension::OldBugMove;
use strict;
use base qw(Bugzilla::Extension);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field::Choice;
use Bugzilla::Mailer;
use Bugzilla::User;
use Bugzilla::Util qw(trim);

use Scalar::Util qw(blessed);
use Storable qw(dclone);

# This is 4 because that's what it originally was when this code was
# a part of Bugzilla.
use constant CMT_MOVED_TO => 4;

our $VERSION = BUGZILLA_VERSION;

sub install_update_db {
    my $reso_type = Bugzilla::Field::Choice->type('resolution');
    my $moved_reso = $reso_type->new({ name => 'MOVED' });
    # We make the MOVED resolution inactive, so that it doesn't show up
    # as a valid drop-down option.
    if ($moved_reso) {
        $moved_reso->set_is_active(0);
        $moved_reso->update();
    }
    else {
        print "Creating the MOVED resolution...\n";
        $reso_type->create(
           { value   => 'MOVED', sortkey => '30000', isactive => 0 });
    }
}

sub config_add_panels {
    my ($self, $args) = @_;
    my $modules = $args->{'panel_modules'};
    $modules->{'OldBugMove'} = 'Bugzilla::Extension::OldBugMove::Params';
}

sub template_before_create {
    my ($self, $args) = @_;
    my $config = $args->{config};

    my $constants = $config->{CONSTANTS};
    $constants->{CMT_MOVED_TO} = CMT_MOVED_TO;

    my $vars = $config->{VARIABLES};
    $vars->{oldbugmove_user_is_mover} = \&_user_is_mover;
}

sub object_before_delete {
    my ($self, $args) = @_;
    my $object = $args->{'object'};
    if ($object->isa('Bugzilla::Field::Choice::resolution')) {
        if ($object->name eq 'MOVED') {
            ThrowUserError('oldbugmove_no_delete_moved');
        }
    }
}

sub object_before_set {
    my ($self, $args) = @_;
    my ($object, $field) = @$args{qw(object field)};
    if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
        # Store the old value so that end_of_set can check it.
        $object->{'_oldbugmove_old_resolution'} = $object->resolution;
    }
}

sub object_end_of_set {
    my ($self, $args) = @_;
    my ($object, $field) = @$args{qw(object field)};
    if ($field eq 'resolution' and $object->isa('Bugzilla::Bug')) {
        my $old_value = delete $object->{'_oldbugmove_old_resolution'};
        return if $old_value eq $object->resolution;
        if ($object->resolution eq 'MOVED') {
            $object->add_comment('', { type => CMT_MOVED_TO,
                                       extra_data => Bugzilla->user->login });
        }
    }
}

sub object_end_of_set_all {
    my ($self, $args) = @_;
    my $object = $args->{'object'};

    if ($object->isa('Bugzilla::Bug') and Bugzilla->input_params->{'oldbugmove'}) {
        my $new_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
        $object->set_bug_status($new_status, { resolution => 'MOVED' });
    }
}

sub object_validators {
    my ($self, $args) = @_;
    my ($class, $validators) = @$args{qw(class validators)};
    if ($class->isa('Bugzilla::Comment')) {
        my $extra_data_validator = $validators->{extra_data};
        $validators->{extra_data} = 
            sub { _check_comment_extra_data($extra_data_validator, @_) };
    }
    elsif ($class->isa('Bugzilla::Bug')) {
        my $reso_validator = $validators->{resolution};
        $validators->{resolution} =
            sub { _check_bug_resolution($reso_validator, @_) };
    }
}

sub _check_bug_resolution {
    my $original_validator = shift;
    my ($invocant, $resolution) = @_;

    if ($resolution eq 'MOVED' and !Bugzilla->input_params->{'oldbugmove'}) {
        # MOVED has a special meaning and can only be used when
        # really moving bugs to another installation.
        ThrowUserError('oldbugmove_no_manual_move');
    }

    return $original_validator->(@_);
}

sub _check_comment_extra_data {
    my $original_validator = shift;
    my ($invocant, $extra_data, undef, $params) = @_;
    my $type = blessed($invocant) ? $invocant->type : $params->{type};

    if ($type == CMT_MOVED_TO) {
        return Bugzilla::User->check($extra_data)->login;
    }
    return $original_validator->(@_);
}

sub bug_end_of_update {
    my ($self, $args) = @_;
    my ($bug, $old_bug, $changes) = @$args{qw(bug old_bug changes)};
    if (defined $changes->{'resolution'}
        and $changes->{'resolution'}->[1] eq 'MOVED')
    {
        $self->_move_bug($bug, $old_bug);
    }
}

sub _move_bug {
    my ($self, $bug, $old_bug) = @_;

    my $dbh = Bugzilla->dbh;
    my $template = Bugzilla->template;

    _user_is_mover(Bugzilla->user) 
        or ThrowUserError("auth_failure", { action => 'move',
                                            object => 'bugs' });

    # Don't export the new status and resolution. We want the current
    # ones.
    local $Storable::forgive_me = 1;
    my $export_me = dclone($bug);
    $export_me->{bug_status} = $old_bug->bug_status;
    delete $export_me->{status};
    $export_me->{resolution} = $old_bug->resolution;

    # Prepare and send all data about these bugs to the new database
    my $to = Bugzilla->params->{'move-to-address'};
    $to =~ s/@/\@/;
    my $from = Bugzilla->params->{'mailfrom'};
    $from =~ s/@/\@/;
    my $msg = "To: $to\n";
    $msg .= "From: Bugzilla <" . $from . ">\n";
    $msg .= "Subject: Moving bug " . $bug->id . "\n\n";
    my @fieldlist = (Bugzilla::Bug->fields, 'group', 'long_desc',
                     'attachment', 'attachmentdata');
    my %displayfields = map { $_ => 1 } @fieldlist;
    my $vars = { bugs => [$export_me], displayfields => \%displayfields };
    $template->process("bug/show.xml.tmpl", $vars, \$msg)
      || ThrowTemplateError($template->error());
    $msg .= "\n";
    MessageToMTA($msg);
}

sub _user_is_mover {
    my $user = shift;

    my @movers = map { trim($_) } split(',', Bugzilla->params->{'movers'});
    return ($user->id and grep($_ eq $user->login, @movers)) ? 1 : 0;
}

__PACKAGE__->NAME;
