# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::OldBugMove;

use 5.14.0;
use strict;
use warnings;

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);

use constant VERSION => BUGZILLA_VERSION;

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

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'
    && $invocant->resolution ne 'MOVED'
    && !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;
